C++: string的模拟实现

news2024/11/16 7:36:28

C++: string的模拟实现

  • 一.前置说明
    • 1.模拟实现string容器的目的
    • 2.我们要实现的大致框架
  • 二.默认成员函数
    • 1.构造函数
    • 2.拷贝构造函数
      • 1.传统写法
      • 2.现代写法
    • 3.析构函数
    • 4.赋值运算符重载
      • 1.传统写法
      • 2.现代写法
  • 三.遍历和访问
    • 1.operator[]运算符重载
    • 2.iterator迭代器
  • 四.容量相关函数
    • 1.size,capacity
    • 2.reserve
    • 3.resize
  • 五.尾插
    • 1.push_back
    • 2.append
    • 3.operator+=运算符重载
  • 六.指定位置插入删除
    • 1.insert
      • 1.插入一个字符的版本
      • 2. 插入一个字符串的版本
    • 2.erase
  • 七.查找,交换,截取操作
    • 1.find
    • 2.swap
    • 3.substr
  • 八.几个小函数
    • 1.c_str
    • 2.clear
    • 3.empty
  • 九.流插入和流提取
    • 1.<<流插入
    • 2.>>流提取
  • 十.完整代码
    • 1.my_string.h:
    • 2.my_string.cpp
    • 3.test.cpp

一.前置说明

注意:本文会用到strcpy,strstr,strncpy,strlen这几个函数
我会说明它们的功能和用法
如果大家想要彻底了解这几个函数
可以看一下我之前的博客:
征服C语言字符串函数(超详细讲解,干货满满)

1.模拟实现string容器的目的

在这里插入图片描述
比如说leetcode字符串相加这道题
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
既然string的模拟实现对我们这么重要
那就让我们一起踏上string的模拟实现之旅吧

2.我们要实现的大致框架

我们经过string容器的使用的学习之后
我们知道string容器其实就是一个顺序表,只不过这个顺序表里面存储的都是char类型的数据
而且末尾有一个隐含的’\0’
因此我们的string容器可以这样定义

#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include <iostream>
using namespace std;
#include <assert.h>
namespace wzs
{
	class string
	{
	public:
		//npos
		static size_t npos;
	private:
		char* _str;//这就是string容器中的那个字符数组
		int _size;
		int _capacity;
	};
	//static size_t string::npos = -1;//err
	size_t string::npos = -1;//yes
}

下面就是我们要实现的框架

#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include <iostream>
using namespace std;
#include <assert.h>
namespace wzs
{
	class string
	{
	public:
		//npos
		static size_t npos;
		//1.全缺省的构造函数
		string(const char* str = "");
		~string();
		//2.拷贝构造函数
		string(const string& s);
		//3.赋值运算符重载
		string& operator=(const string& s);
		//4.返回C风格的字符串
		const char* c_str() const;
		//5.iterator
		typedef char* iterator;
		iterator begin();
		iterator end();
		typedef const char* const_iterator;
		const_iterator begin() const;
		const_iterator end() const;
		//6.流插入运算符重载
		//friend ostream& operator<<(ostream& out, const string& s);
		//7.operator[]重载
		char& operator[](int index);
		const char& operator[](int index) const;
		//8.size() capacity()
		const int size() const;
		const int capacity() const;
		//9..reserve:这里不允许缩容
		void reserve(int capacity);
		//10.push_back
		void push_back(const char c);
		//11.append
		void append(const char* str);
		//12.+=
		string& operator+=(const char c);
		string& operator+=(const char* str);
		//13.insert
		void insert(size_t pos, const char c);
		void insert(size_t pos, const char* str);
		//14.erase
		void erase(size_t pos = 0, size_t len = npos);
		//15.resize
		void resize(size_t n, char c = '\0');
		//16.swap
		void swap(string& s);
		//17.find
		size_t find(const string& s, size_t pos = 0) const;
		size_t find(const char c, size_t pos = 0) const;
		//18.substr
		string substr(size_t pos = 0, size_t len = npos)const;
		//19.clear
		void clear();
		//20.empty
		bool empty()const;
		//21.>>
		//friend istream& operator>>(istream& in, string& s);
	private:
		char* _str;
		int _size;
		int _capacity;
	};
	//static size_t string::npos = -1;//err
	size_t string::npos = -1;//yes
	ostream& operator<<(ostream& out, const string& s);
	istream& operator>>(istream& in, string& s);
}

下面我们就逐个完成这些函数

大家会发现,我这里把流插入和流提取的友元给注释掉了
其实对于string类来说,流插入和流提取是不需要在类内用友元的
因为:
首先,友元是一种突破封装的方式,会增加耦合度,所以并不建议使用
其次,我们使用友元是因为我们想要访问类内的私有数据
可是对于string类来说
它的私有数据:size,capacity,_str都已经对外提供接口了
因此流插入和流提取只需要调用那些接口即可
无需使用友元来访问类内私有数据了

二.默认成员函数

1.构造函数

//1.全缺省的构造函数
string(const char* str = "")
{
	//1.申请空间
	int len = strlen(str);
	_str = new char[len + 1];
	//2.拷贝数据
	strcpy(_str, str);
	//3.修改_size和_capacity
	_size = len;
	_capacity = len;
}

在这里我们实现的是一个全缺省的构造函数
注意几点:
1.strlen求字符串长度时不会算上’\0’,因此我们开空间时要开len+1大小,因为我们还要在末尾存储’\0’
2.这里的

strcpy(_str,str);
会把str字符串中的从首元素开始一直延伸到'\0'为止的数据拷贝给_str
'\0'也会被拷贝进去!!!

在这里插入图片描述
对于这个构造函数:
其实就是
1.申请一个空间
2.拷贝数据
3.修改_size和_capacity

2.拷贝构造函数

1.传统写法

注意:string中的_str是开辟在堆区的
所以我们要实现深拷贝,否则会造成同一空间多次释放等问题

//2.拷贝构造函数
string(const string& s)
{
	//1.申请空间
	int len = s._size;
	_str = new char[len + 1];
	//2.拷贝数据
	strcpy(_str, s._str);
	//3.修改_size和_capacity
	_size = len;
	_capacity = len;
}

跟刚才的构造函数基本相同

2.现代写法

string(const string& s)
	:_str(nullptr)
	,_size(0)
	,_capacity(0)
{
	string tmp(s._str);
	swap(tmp);//this->swap(tmp)
}

使用s._str调用构造函数来构造tmp
然后再去把tmp和*this交换

非常巧妙

3.析构函数

~string()
{
	//释放空间
	delete[] _str;
	_str=nullptr;
	//重置_size和_capacity
	_size = 0;
	_capacity = 0;
}

1.释放空间
2.重置_size和_capacity

4.赋值运算符重载

1.传统写法

//3.赋值运算符重载
string& operator=(const string& s)
{
	if (this != &s)//这里是为了防止自己给自己拷贝的冗余操作
	{
		//1.开辟一块新空间
		int len = s._size;
		char* tmp = new char[len + 1];
		//2.拷贝数据  把s._str的数据拷贝给tmp
		strcpy(tmp, s._str);
		//3.释放原有空间
		delete[] _str;
		_str = tmp;
		tmp = nullptr;
		//4.修改_size和_capacity
		_size = len;
		_capacity = len;
	}
	return *this;
}

其实就是
1.开辟一块新空间
2.拷贝数据
3.释放原有空间
4.修改_size和_capacity

2.现代写法

//现代写法1
string& operator = (const string& s)
{
	if (this != &s)
	{
		//调用拷贝构造函数,然后交换
		string tmp(s);
		swap(tmp);
	}
	return *this;
}
//现代写法2
//直接传值传参,然后交换
string& operator = (string s)
{
	swap(s);
	return *this;
}

三.遍历和访问

1.operator[]运算符重载

//7.operator[]重载
//同STL库中的operator[]重载:有两个重载版本
//一个没有被const修饰
//另一个是被const修饰的
char& operator[](int index)
{
	//STL中的string允许[]访问最后的'\0'
	assert(index >= 0 && index <= _size);
	return _str[index];
}
const char& operator[](int index) const
{
	//STL中的string允许[]访问最后的'\0'
	assert(index >= 0 && index <= _size);
	return _str[index];
}

就是直接返回对应位置的引用的即可

2.iterator迭代器

我们在C++ 带你吃透string容器的使用末尾提到了iterator迭代器
这里就不赘述了

同STL库,iterator有两个版本:const和非const
//5.iterator
typedef char* iterator;
iterator begin()
{
	return _str;
}
iterator end()
{
	return _str + _size;
}
typedef const char* const_iterator;
const_iterator begin() const
{
	return _str;
}
const_iterator end() const
{
	return _str + _size;
}

四.容量相关函数

1.size,capacity

因为size和capacity对于类外是只读属性,因此用const修饰

//8.size() capacity()
const int size() const
{
	return _size;
}
const int capacity() const
{
	return _capacity;
}

2.reserve

我们在这里实现的版本是不允许缩容的
能不能缩容取决于编译器的具体实现
这一点我们这篇博客: C++ 带你吃透string容器的使用也提到过

//9.reserve:这里不允许缩容
void reserve(int capacity)
{
	//只有需要扩容时才会进行扩容
	if (_capacity < capacity)
	{
		//1.开辟新空间
		char* tmp = new char[capacity + 1];
		//2.将原有数据拷贝至新空间
		strcpy(tmp, _str);
		//3.释放原有空间
		delete[] _str;
		//4.将_str指向新空间
		_str = tmp;
		tmp = nullptr;
		//5.修改_capacity
		_capacity = capacity;
	}
}

1.开辟新空间
2.将原有数据拷贝至新空间
3.释放原有空间
4.将_str指向新空间
5.修改_capacity

3.resize

这里我复用了erase和push_back
大家可以最后再来看这个resize,因为这个resize不会再其他接口中调用,最后看也无妨

//15.resize
//这里给字符设置了缺省参数'\0',将两个版本合二为一
void resize(size_t n, char c = '\0')
{
	//1.n<_size  :  删除多余字符,修改_size,但不修改_capacity
	if (n < _size)
	{
		erase(n);//erase负责修改_size
	}
	//2._size<=n<=_capacity  :  尾插字符c直到_size == n
	else if (n <= _capacity)
	{
		//尾插数据
		while (_size < n)
		{
			push_back(c);//push_back负责修改_size
		}
	}
	//3.n>_capacity:  需要reserve
	else
	{
		//扩容:将容量扩为n
		reserve(n);//reserve负责修改_capacity
		//尾插数据
		while (_size < n)
		{
			push_back(c);//push_back负责修改_size
		}
	}
}

分为三种情况:
在这里插入图片描述

五.尾插

1.push_back

//10.push_back
void push_back(const char c)
{
	//1.容量不够了的话:扩容
	if(_capacity==_size)
	{
		int newcapacity = _capacity == 0 ? 4 : _capacity * 2;
		reserve(newcapacity);
	}
	//2.尾插
	_str[_size] = c;
	//3.修改_size
	_size++;
	//4.不要忘了末尾的'\0'
	_str[_size] = '\0';
}

1.容量不够了的话:扩容
2.尾插
3.修改_size
4.不要忘了末尾的’\0’

2.append

//11.append
void append(const char* str)
{
	//1.容量不够的话: 扩容
	int len = strlen(str);
	int newcapacity = _size + len;
	//如果容量够,reserve不会执行任何语句(具体原因请看reserve函数)
	reserve(newcapacity);
	//2.拷贝数据
	strcpy(_str + _size, str);
	//3.修改_size
	_size += len;
}

1.容量不够的话: 扩容
2.拷贝数据
3.修改_size

注意:
_str+_size就指向了_str的末尾(‘\0’)
然后strcpy从_str末尾(‘\0’)开始进行拷贝

问题来了:
能不能用strcat呢?
不建议用
因为strcat使用时会先遍历目标字符串找到’\0’
然后再去进行拷贝
这样的话就多了一个遍历目标字符串的消耗
而我们直接_str+_size就找到目标字符串末尾(‘\0’)了

可以看出:当我们掌握了一些库函数的底层实现之后就是可以优化掉很多不必要的消耗

3.operator+=运算符重载

直接复用push_back和append即可
也是两个版本

//12.+=
string& operator+=(const char c)
{
	push_back(c);
	return *this;
}
string& operator+=(const char* str)
{
	append(str);
	return *this;
}

六.指定位置插入删除

1.insert

1.插入一个字符的版本

注意:下面就有坑了
大家看一下下面这个代码有什么问题?
提示:
1.while循环位置的错误
2.而且这个错误只有当我进行头插的时候才会出现

void insert(size_t pos, const char c)
{
	assert(pos >= 0 && pos <= _size);
	//1.容量不够的话要扩容
	if (_capacity == _size)
	{
		int newcapacity = _capacity == 0 ? 4 : _capacity * 2;
		reserve(newcapacity);
	}
	//2.将[pos,_size]的数据往后挪动
	//这里把_size位置的数据(也就是'\0')也往后挪动是为了
	//让我们最后的时候就不用单独再去在末尾添加'\0'了
	//因为'\0'已经被我们挪动到最后了
	size_t end = _size;
	while (end >= pos)
	{
		_str[end + 1] = _str[end];
		end--;
	}
	//3.在pos位置插入字符c
	_str[pos]=c;
	//4.修改_size
	_size++;
}

1.容量不够的话要扩容
2.将[pos,_size]的数据往后挪动
3.在pos位置插入字符c
4.修改_size

下面我们来调试看一下
在这里插入图片描述
一开始时大家可以重点看_str的变化(理解数据后移的过程)
等到end快要减为0时再去看end的变化
在这里插入图片描述
因为end是size_t类型(也就是无符号整数)
永远大于等于0
当pos==0时,while(end>=pos)永远都成立
因此陷入死循环

那么怎么办呢?
第一种解决方案:强制类型转换

//把[pos,_size]的数据右移
//解决方案1:强制类型转换
size_t end = _size;
while ((int)end >= (int)pos)
{
	_str[end + 1] = _str[end];
	end--;
}

此时insert就正常运行了
在这里插入图片描述
尽管我end还是会减为-1时变为42亿多
但是while循环中我end强制类型转换转为了int类型(其实是产生了int类型的临时变量,然后用临时变量去比较的)
所以真实的比较场景是while(-1>=0),此时就退出while循环了

不过还有一个小点:
如果我这样写呢? 能不能顺利运行呢?

int end = _size;
while (end >= pos)
{
	_str[end + 1] = _str[end];
	end--;
}

在这里插入图片描述
答案是:还是不行
因为int类型的end跟size_t类型的pos比较时
int类型会进行整形提升产生size_t类型的临时变量,然后用临时变量跟pos去比较
也就是size_t类型的-1,-2,-3…跟size_t类型的pos去比
我们都知道size_t类型的-1,-2,-3那可是无符号整型的前几个最大值啊
肯定比我这个pos大嘛
所以还是死循环

只能强制类型转换吗?
当然不是,条条大路通罗马
还可以这样挪动数据

//13.insert
void insert(size_t pos, const char c)
{
	assert(pos >= 0 && pos <= _size);
	if (_capacity == _size)
	{
		int newcapacity = _capacity == 0 ? 4 : _capacity * 2;
		reserve(newcapacity);
	}
	//把[pos,_size]的数据右移
	//解决方案2
	size_t end = _size + 1;
	while (end > pos)
	{
		_str[end] = _str[end - 1];
		end--;
	}
	_str[pos] = c;
	_size++;
}

此时当end减为0时就已经挪动完数据了
并且while循环也可以正常退出
在这里插入图片描述
我们在这里就采用第二种解决方案啦
大家也可以用一下第一种解决方案

2. 插入一个字符串的版本

跟插入字符的操作差不多
只不过挪动的步长不是1而是字符串的长度

void insert(size_t pos, const char* str)
{
	assert(pos >= 0 && pos <= _size);
	//1.容量不够的话要扩容
	int len = strlen(str);
	int newcapacity = _size + len;
	reserve(newcapacity);
	//2.将[pos,_size]的数据往后挪动len个位置
	int end = _size + 1;
	while (end > pos)
	{
		_str[end + len - 1] = _str[end - 1];
		end--;
	}
	//3.在pos位置插入字符串的前len个字符(也就是不插入'\0')
	strncpy(_str + pos, str, len);//使用strncpy,而不能使用strcpy
	//因为我们不要拷贝'\0'
	//而strcpy是拷贝到'\0'才结束
	//4.修改_size
	_size += len;
}

1.容量不够的话要扩容
2.将[pos,_size]的数据往后挪动len个位置
3.在pos位置插入字符串的前len个字符(也就是不插入’\0’)
4.修改_size

strncpy就是可以指定拷贝n个字符
而不是跟strcpy一样直接拷贝到’\0’
在这里插入图片描述

2.erase

//14.erase
void erase(size_t pos = 0, size_t len = npos)
{
	assert(pos >= 0 && pos <= _size);
	//1.判断要删除的长度和[pos,_size)长度的大小
	//要删除的长度大于等于[pos,_size)的长度
	//此时直接将pos位置置为'\0'并且修改_size即可
	if (len >= _size - pos)
	{
		_str[pos] = '\0';
		_size = pos;
	}
	//要删除的长度小于[pos,_size)的长度
	//那么把[pos+len,_size]的数据往前移动len位置来覆盖我们要删除的字符
	//然后修改_size即可
	else
	{
		strcpy(_str + pos, _str + pos + len);
		_size -= len;
	}
}

1.判断要删除的长度和[pos,_size)长度的大小
2.如果要删除的长度大于等于[pos,_size)的长度
那么直接将pos位置置为’\0’并且修改_size即可
3.如果要删除的长度小于[pos,_size)的长度
那么把[pos+len,_size]的数据往前移动len位置来覆盖我们要删除的字符
并且修改_size即可

注意:这里if语句中要使用len>=_size-pos
而不能使用len+pos>=_size
因为如果len为npos的话
那么len+pos就会溢出
会出现一些意想不到的错误
比如这样:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
最后的结果:
在这里插入图片描述
我们注意到len+pos溢出之后变为了1
而我们的pos是2
因此:
strcpy(_str + pos, _str + pos + len);
就是把1位置的数据往2位置去拷贝,直到遇到’\0’为止
这就搞得非常荒诞了,因此这里使用的是len>=_size-pos

使用len>=_size-pos的话
因为len本身是npos
所以就会执行if语句的那个分支,而不是进入else执行strcpy导致出现千奇百怪的错误
在这里插入图片描述

七.查找,交换,截取操作

1.find

跟STL库一样,我们实现两个版本
查找字符串和查找字符

//17.find
size_t find(const char c, size_t pos = 0) const
{
	assert(pos >= 0 && pos < _size);
	for (size_t i = pos; i < _size; i++)
	{
		if (_str[i] == c)
		{
			return i;
		}
	}
	return npos;
}

这个查找字符就很简单了,遍历一遍即可

但是这个查找字符串我们要用到strstr字符串匹配函数
在这里插入图片描述

size_t find(const string& s, size_t pos = 0) const
{
	assert(pos >= 0 && pos < _size);
	char* index = strstr(_str + pos, s._str);
	if (index == nullptr)
	{
		return npos;
	}
	else
	{
		return index - _str;
	}
}

唯一需要注意的一点就是:
返回空指针的话代表没有查找到,需要加一个if判断
如果查找到了
返回index-_str即可(指针-指针的问题,是C语言的基础知识)

2.swap

//16.swap
void swap(string& s)
{
	//这里调用的是std(标准库中的那个swap函数)
	std::swap(_str, s._str);
	std::swap(_size, s._size);
	std::swap(_capacity, s._capacity);
}

这里的swap的实现其实就是
交换指针,交换_size和_capacity
这样做的好处是无需再去拷贝string了,而是直接交换指针即可,大大优化了swap的效率
可见这个设计是非常巧妙的

3.substr

大家看一下这个代码有没有什么问题

string& substr(size_t pos = 0, size_t len = npos)const
{
	assert(pos >= 0 && pos < _size);
	string s;
	//1.判断要截取的长度和[pos,_size)长度的大小
	//要截取的长度大于[pos,_size)的长度  ->  那么修改len方便后续形成子串
	if (len >= _size - pos)
	{
		len = _size - pos;
	}
	//2.插入数据形成子串
	for (size_t i = 0; i < len; i++)
	{
		s += _str[i + pos];
	}
	//3.返回子串
	return s;
}

1.判断要截取的长度和[pos,_size)长度的大小 如果大了,那么就修改len
2.插入数据形成子串
3.返回子串

其实这个代码是有问题的,只不过并不是我们实现逻辑的问题
而是这个引用做返回值的问题
在这里插入图片描述
在这里插入图片描述

因为我们的这个s串是substr函数中的局部变量
出了作用域后会自动销毁(调用析构函数)
但是因为我们是引用作为返回值
因此s1.substr(1,7)这个对象就是我们刚才析构后的s串
然后用这个已经析构了的串给s2进行拷贝构造
在这里插入图片描述
于是拷贝构造时
strcpy访问s._str导致出现了对空指针的解引用操作
因此报错

怎么办呢?
把引用做返回值改为值做返回值
就好了
但是好的前提一定是你的拷贝构造,赋值运算符重载都是用的深拷贝,而不是浅拷贝
否则就会出现同一空间多次释放的错误
在这里插入图片描述
下面我把拷贝构造函数给干掉
那么就会用编译器默认形成的拷贝构造函数(默认形成的是浅拷贝)
在这里插入图片描述
此时,尽管你是值拷贝,但是你是浅拷贝
也就是说返回是我指向已经析构后的空间的那个指针
因此在s2析构的时候会对那个空间再次进行析构

也就是发生了同一空间多次释放的问题
也就是野指针的问题

下面我把拷贝构造函数恢复

string substr(size_t pos = 0, size_t len = npos)const
{
	assert(pos >= 0 && pos < _size);
	string s;
	//1.判断要截取的长度和[pos,_size)长度的大小
	//要截取的长度大于[pos,_size)的长度  ->  那么修改len方便后续形成子串
	if (len >= _size - pos)
	{
		len = _size - pos;
	}
	//2.插入数据形成子串
	for (size_t i = 0; i < len; i++)
	{
		s += _str[i + pos];
	}
	//3.返回子串
	return s;
}

此时就正常运行了
在这里插入图片描述
整个流程是:
s串返回值进行拷贝构造形成临时变量
然后返回那个临时变量
然后再由那个临时变量给这个s2进行拷贝构造

关于引用的知识点:
C++入门-引用
深浅拷贝的知识点
C++类和对象中(构造函数,析构函数,拷贝构造函数)详解

八.几个小函数

1.c_str

//4.返回C风格的字符串
const char* c_str() const
{
	return _str;
}

2.clear

//19.clear
void clear()
{
	_str[0] = '\0';//关于这里为什么要把第一个字符置为'\0'
	//是因为我们要保证
	//此时即使使用cout<<c_str()来打印string对象时也不会跟使用流插入打印string对象产生歧义
	//因为使用cout<<c_str()是打印到'\0'才会停止
	//而流插入是按照size来打印的
	_size = 0;
}

3.empty

//20.empty
bool empty()const
{
	return _size == 0;
}

九.流插入和流提取

对于流插入和流提取我们之前就在日期类接触了。
不能重载成成员,因为会和this指针抢占第一个位置。
所以需要定义成全局的
不过无需使用友元,因为可以调用接口来访问类内数据

1.<<流插入

按照_size打印

ostream& operator<<(ostream& out, const string& s)
{
	for (int i = 0; i < s.size(); i++)
	{
		out << s[i];
	}
	return out;
}

对于<<和c_str()的区别:<<按照size进行打印,跟\0没有关系,而c_str()遇到\0结束
在这里插入图片描述
因此推荐使用<<

2.>>流提取

cin和scanf一样,都是读到’ ‘或者’\n’后就会停止
因此我们需要一个字符一个字符地读取

所以使用get()函数来读取字符
第一个版本:

istream& operator>>(istream& in, string& s)
{
//注意:使用流提取时,会覆盖原有数据
//因此需要先clear一下
	s.clear();
	char ch = in.get();
	while (ch != ' ' && ch != '\n')
	{
		s += ch;
		ch = in.get();
	}
	return in;
}

但是第一个版本有一个缺陷:
当我们想要读取的数据特别特别多的时候
就会频繁扩容,造成不必要的麻烦
因此我们可以设置一个"缓冲区"似的数组
第二个版本:

istream& operator>>(istream& in, string& s)
{
	s.clear();
	char tmp[128] = { '\0' };
	char ch = in.get();
	size_t index = 0;
	while (ch != ' ' && ch != '\n')
	{
		if(index == 127)
		{
			s += tmp;
			index = 0;
		}
		tmp[index++] = ch;
		ch = in.get();
	}
	if (index >= 0)
	{
		tmp[index] = '\0';
		s += tmp;
	}
	return in;
}

十.完整代码

1.my_string.h:

#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include <string>
#include <iostream>
using namespace std;
#include <assert.h>
//小函数定义在.h里面,弄成内联函数
namespace wzs
{
	class string
	{
	public:
		//npos
		static size_t npos;
		//1.全缺省的构造函数
		string(const char* str = "");
		~string();
		//2.拷贝构造函数
		string(const string& s);
		//18.substr
		string substr(size_t pos = 0, size_t len = npos)const;
		//3.赋值运算符重载
		string& operator=(string s);
		//4.返回C风格的字符串
		const char* c_str() const
		{
			return _str;
		}
		//5.iterator
		typedef char* iterator;
		iterator begin()
		{
			return _str;
		}
		iterator end()
		{
			return _str + _size;
		}
		typedef const char* const_iterator;
		const_iterator begin() const
		{
			return _str;
		}
		const_iterator end() const
		{
			return _str + _size;
		}
		//6.流插入运算符重载
		//friend ostream& operator<<(ostream& out, const string& s);
		//7.operator[]重载
		char& operator[](int index);
		const char& operator[](int index) const;
		//8.size() capacity()
		const int size() const
		{
			return _size;
		}
		const int capacity() const
		{
			return _capacity;
		}
		//9..reserve:这里不允许缩容
		void reserve(int capacity);
		//10.push_back
		void push_back(const char c);
		//11.append
		void append(const char* str);
		//12.+=
		string& operator+=(const char c);
		string& operator+=(const char* str);
		//13.insert
		void insert(size_t pos, const char c);
		void insert(size_t pos, const char* str);
		//14.erase
		void erase(size_t pos = 0, size_t len = npos);
		//15.resize
		void resize(size_t n, char c = '\0');
		//16.swap
		void swap(string& s);
		//17.find
		size_t find(const string& s, size_t pos = 0) const;
		size_t find(const char c, size_t pos = 0) const;
		//19.clear
		void clear()
		{
			_str[0] = '\0';
			_size = 0;
		}
		//20.empty
		bool empty()const
		{
			return _size == 0;
		}
		//21.>>
		//friend istream& operator>>(istream& in, string& s);
	private:
		char* _str;
		int _size;
		int _capacity;
	};
	ostream& operator<<(ostream& out, const string& s);
	istream& operator>>(istream& in, string& s);
	void test1();
	void test2();
	void test3();
	void test4();
	void test5();
	void test6();
	void test7();
	void test8();
	void test9();
	void test10();
	void test11();
	void test12();
	void test13();
	void test14();
	void test15();
	void test16();
	void test17();
}

2.my_string.cpp

#include "my_string.h"
namespace wzs
{
		//1.全缺省的构造函数
		string::string(const char* str)
		{
			int len = strlen(str);
			_str = new char[len + 1];
			strcpy(_str, str);
			_size = len;
			_capacity = len;
		}
		string::~string()
		{
			delete[] _str;
			_str = nullptr;
			_size = 0;
			_capacity = 0;
		}
		//2.拷贝构造函数
		/*
		string(const string& s)
		{
			int len = s._size;
			_str = new char[len + 1];
			strcpy(_str, s._str);
			_size = len;
			_capacity = len;
		}
		*/
		//拷贝构造的现代写法
		//有些编译器上有问题
		//因为如果_str没有被初始化为nullptr
		//那么就完了,因为tmp除了这个构造函数就会调用析构
		//所以我们可以先给缺省值或者初始化列表来让这个_str初始化为nullptr
		string::string(const string& s)
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{
			//先构造
			string tmp(s.c_str());
			//再交换
			swap(tmp);
		}
		//18.substr
		string string::substr(size_t pos, size_t len)const
		{
			assert(pos >= 0 && pos < _size);
			string s;
			if (len >= _size - pos)
			{
				len = _size - pos;
			}
			for (size_t i = 0; i < len; i++)
			{
				s += _str[i + pos];
			}
			return s;
		}
		//3.赋值运算符重载
		/*
		string& operator=(const string& s)
		{
			if (this != &s)
			{
				int len = s._size;
				char* tmp = new char[len + 1];
				strcpy(tmp, s._str);
				delete[] _str;
				_str = tmp;
				tmp = nullptr;
				_size = len;
				_capacity = len;
			}
			return *this;
		}
		*/
		//赋值运算符重载的现代写法1
		/*
		string& operator=(const string& s)
		{
			if (this != &s)
			{
				//先拷贝构造
				string tmp(s);
				//再交换
				swap(tmp);
				return *this;
			}
		}
		*/
		//赋值运算符重载的现代写法2
		//直接传值调用
		//传参的时候值传参  ->  调用拷贝构造函数传参
		string& string::operator=(string s)
		{
			//没有必要判断是否是自己给自己赋值
			//因为这个对象已经拷贝出来了,覆水已经难收
			swap(s);
			//*this原来的空间是这个形参s释放的
			return *this;
		}
		//7.operator[]重载
		char& string::operator[](int index)
		{
			//stl中的string允许[]访问最后的'\0'
			assert(index >= 0 && index <= _size);
			return _str[index];
		}
		const char& string::operator[](int index) const
		{
			//stl中的string允许[]访问最后的'\0'
			assert(index >= 0 && index <= _size);
			return _str[index];
		}
		//9..reserve:这里不允许缩容
		void string::reserve(int capacity)
		{
			if (_capacity < capacity)
			{
				char* tmp = new char[capacity + 1];
				strcpy(tmp, _str);
				delete[] _str;
				_str = tmp;
				tmp = nullptr;
				_capacity = capacity;
			}
		}
		//10.push_back
		void string::push_back(const char c)
		{
			if (_capacity == _size)
			{
				int newcapacity = _capacity == 0 ? 4 : _capacity * 2;
				reserve(newcapacity);
			}
			_str[_size] = c;
			_size++;
			_str[_size] = '\0';
		}
		//11.append
		void string::append(const char* str)
		{
			int len = strlen(str);
			int newcapacity = _size + len;
			reserve(newcapacity);
			strcpy(_str + _size, str);
			_size += len;
		}
		//12.+=
		string& string::operator+=(const char c)
		{
			push_back(c);
			return *this;
		}
		string& string::operator+=(const char* str)
		{
			append(str);
			return *this;
		}
		//13.insert
		void string::insert(size_t pos, const char c)
		{
			assert(pos >= 0 && pos <= _size);
			if (_capacity == _size)
			{
				int newcapacity = _capacity == 0 ? 4 : _capacity * 2;
				reserve(newcapacity);
			}
			//把[pos,_size]的数据右移
			//size_t end = _size;
			//while (end >= pos)
			//{
			//	_str[end + 1] = _str[end];
			//	end--;
			//}
			//解决方案1:强制类型转换
			//int end = _size;
			//while (end >= pos)
			//{
			//	_str[end + 1] = _str[end];
			//	end--;
			//}

			//解决方案2
			size_t end = _size + 1;
			while (end > pos)
			{
				_str[end] = _str[end - 1];
				end--;
			}

			_str[pos] = c;
			_size++;
		}
		void string::insert(size_t pos, const char* str)
		{
			assert(pos >= 0 && pos <= _size);
			int len = strlen(str);
			int newcapacity = _size + len;
			reserve(newcapacity);
			//[pos,_size]的数据右移len位
			int end = _size + 1;
			while (end > pos)
			{
				_str[end + len - 1] = _str[end - 1];
				end--;
			}
			strncpy(_str + pos, str, len);//使用strncpy,而不能使用strcpy
			//因为我们不要拷贝'\0'
			//而strcpy是拷贝到'\0'才结束
			_size += len;
		}
		//14.erase
		void string::erase(size_t pos, size_t len)
		{
			assert(pos >= 0 && pos <= _size);
			//要删除的长度大于[pos,_size)的长度
			if (len >= _size - pos)
				//if (len + pos >= _size)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			//把[pos+len,_size]的数据往前移动len位置
			else
			{
				strcpy(_str + pos, _str + pos + len);
				_size -= len;
			}
		}
		//15.resize
		void string::resize(size_t n, char c)
		{
			//1.n<_size  :  删除多余字符,修改_size,但不修改_capacity
			if (n < _size)
			{
				erase(n);//erase负责修改_size
			}
			//2._size<=n<=_capacity  :  尾插字符c直到_size == n
			else if (n <= _capacity)
			{
				while (_size < n)
				{
					push_back(c);//push_back负责修改_size
				}
			}
			//3.n>_capacity:  需要reserve
			else
			{
				reserve(n);//reserve负责修改_capacity
				while (_size < n)
				{
					push_back(c);//push_back负责修改_size
				}
			}
		}
		//16.swap
		void string::swap(string& s)
		{
			std::swap(_str, s._str);
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
		}
		//17.find
		size_t string::find(const string& s, size_t pos) const
		{
			assert(pos >= 0 && pos < _size);
			char* index = strstr(_str + pos, s._str);
			if (index == nullptr)
			{
				return npos;
			}
			else
			{
				return index - _str;
			}
		}
		size_t string::find(const char c, size_t pos) const
		{
			assert(pos >= 0 && pos < _size);
			for (size_t i = pos; i < _size; i++)
			{
				if (_str[i] == c)
				{
					return i;
				}
			}
			return npos;
		}
	//static size_t string::npos = -1;//err
	size_t string::npos = -1;//yes
	ostream& operator<<(ostream& out, const string& s)
	{
		for (int i = 0; i < s.size(); i++)
		{
			out << s[i];
		}
		return out;
	}
	istream& operator>>(istream& in, string& s)
	{
		//库里的流提取输入的时候会覆盖之前的旧内容!!
		//运行看一下
		//因此要clear
		s.clear();//clear一般不会释放空间,就是把第一个元素置为'\0'并且把size置为0
		//如果clear不把第一个元素置为'\0'
		//那么>>和c_str就不一样了!!!!
		//因为>>是按size打印,c_str打印到'\0'之前为止
		char tmp[128] = { '\0' };
		//get是istream类型调用的成员函数
		char ch = in.get();
		//get()是C++的,getchar()是C语言的,C++和C语言的缓冲区是不一样的,(C++是可以设计成不兼容C语言的)
		//
		//尽管C++是兼容C语言的,不过尽量还是用C++的
		size_t index = 0;
		//getline其实是当ch遇到'\n'之后才会停止while循环!!!!
		while (ch != ' ' && ch != '\n')
		{
			//index是下一个要插入数据的下标
			//注意:这里是127时就要放s里面并清空
			//因为tmp的最后一个数据一定要保证是'\0'!!!!!!
			if (index == 127)
			{
				s += tmp;
				index = 0;
			}
			tmp[index++] = ch;
			ch = in.get();
		}
		if (index >= 0)
		{
			tmp[index] = '\0';
			s += tmp;
		}
		return in;
	}
}

3.test.cpp

#include "my_string.h"
namespace wzs
{
	void test1()
	{
		const string s1("hello world");
		string s2;
		cout << s1.c_str() << endl;
		cout << s2.c_str() << endl;
		string s3(s1);
		cout << s3.c_str() << endl;
		s2 = s1;
		cout << s2.c_str() << endl;
	}
	void test2()
	{
		//string s2 = "hello iterator";
		//string::iterator it = s2.begin();
		//while (it != s2.end())//注意:我们使用iterator访问和遍历时要注意左闭右开使用[begin,end)
		//{
		//	cout << *it << " ";//这里可以暂时理解为像是指针解引用的用法一样
		//	it++;//这里可以暂时理解为像是指针自增(也就是后移)的用法一样
		//}
		//cout << endl;
		//cout << s2 << endl;
		string s2 = "hello iterator";
		string::iterator it = s2.begin();
		while (it != s2.end())//注意:我们使用iterator访问和遍历时要注意左闭右开使用[begin,end)
		{
			*it += 1;//(*it)++;这样也可以,不过不要忘了加小括号(运算符优先级的问题)
			cout << *it << " ";//这里可以暂时理解为像是指针解引用的用法一样
			it++;//这里可以暂时理解为像是指针自增(也就是后移)的用法一样
		}
		cout << endl;
		cout << s2 << endl;


	}
	void test3()
	{
		std::string s("hello world");
		char c = s[s.size()];

		cout << s[s.size()] << endl;
	}
	void test4()
	{
		string s1("hello world");
		s1.reserve(100);

	}
	void test5()
	{
		string s1;
		int old_capacity = s1.capacity();
		cout << old_capacity << endl;
		cout << s1 << endl;
		for (int i = 0; i < 100; i++)
		{
			s1.push_back('w');//将'w'这个字符尾插进入s1当中
			if (old_capacity != s1.capacity())
			{
				cout << s1.capacity() << endl;
				old_capacity = s1.capacity();
			}
		}
		cout << s1 << endl;
	}
	void test6()
	{
		string s("hello world");
		/*s.push_back('2');
		s.push_back('3');
		s.append(" 1124");*/
		s += "2";
		s += '3';
		s += " 1124";
		cout << s << endl;
	}
	void test7()
	{
		string s("[hello world]");
		s.insert(0, 'w');//从0位置头插一个字符:'w'
		cout << s << endl;
	}
	void test8()
	{
		string s("0123456789");
		s.erase(3, 4);//从3号下标位置开始删除4个字符,也就是删除了3456
		cout << s << endl;
		s.erase(2);//默认从2号下标开始的删除所有字符
		cout << s << endl;
		s.erase();//默认删除所有字符
		cout << s << endl;
	}
	void test9()
	{
		//string s1("hello world");
		1.n<size
		//s1.resize(4);
		//cout << s1 << endl;
		//string s1("hello world");
		//2.size<n<capacity
		//cout << s1.capacity() << endl;
		s1.resize(13,'q');
		//s1.resize(30, '2');
		//cout << s1;

		string s1;
		s1.resize(10, 'w');
		cout << s1 << endl;
	}
	void test10()
	{
		string s1("123");
		string s2("456");
		s1.swap(s2);
		cout << s1 << endl;
		cout << s2 << endl;
	}
	void test11()
	{
		string s1("abcd 1234 xxxx");
		size_t index1 = s1.find("cd", 1);//从1号下标开始查找字符串cd
		cout << index1 << endl;
		size_t index3 = s1.find(' ', 1);//从1号下标开始查找字符' '
		cout << index3 << endl;

		//查不到的情况:
		size_t index4 = s1.find("abcd", 1);//从1号下标开始查找字符串abcd  ->  查不到,返回npos
		cout << index4 << endl;//无符号整形最大值

	}
	void test12()
	{
		string s1("hello world");
		string s2 = s1.substr(1, 7);
		cout << s2 << endl;
	}
	void test13()
	{
		string s("http://www.baidu.com/index.html?name=mo&age=25#dowell");
		string substr1, substr2, substr3;
		//我们的目标是:
		//substr1:http
		//substr2:www.baidu.com
		//substr3:index.html?name=mo&age=25#dowell
		size_t pos1 = s.find(':', 0);//从0下标开始出发查找':'
		substr1 = s.substr(0, pos1 - 0);//[0,pos1):左闭右开的区间:长度是右区间-左区间 也就是pos1-0
		size_t pos2 = s.find('/', pos1 + 3);//pos1位置此时是':'  我们下一次要从pos1+3的位置开始查找 也就是第一个'w'的位置
		substr2 = s.substr(pos1 + 3, pos2 - (pos1 + 3));//[pos1+3,pos2):这个区间内的子串
		substr3 = s.substr(pos2 + 1);//从pos2+1开始一直截取到最后即可
		//substr传值返回,是临时变量,临时变量具有常性
		cout << substr1 << endl;
		cout << substr2 << endl;
		cout << substr3 << endl;

	}
	void test14()
	{
		//char ch1, ch2;
		//cin >> ch1 >> ch2;
		//因为输入多个值时,' '和'\n'默认是分割符,不是有效数据!!!!!!
		//scanf也是拿不到的
		string s1, s2, s3;
		cin >> s1 >> s2 >> s3;
		cout << s1 << endl << s2 << endl << s3 << endl;
	}
	void test15()
	{
		string s1("012345");
		cout << s1 << endl;
		cout << s1.c_str() << endl;

		s1.insert(2, '\0');
		cout << s1 << endl;
		cout << s1.c_str() << endl;
	}
	void test16()
	{
		string s1("hello world");
		cout << s1 << endl;
		cout << s1.c_str() << endl;
		//此时clear没有把第一个元素置为'\0'
		s1.clear();
		cout << s1 << endl;
		cout << s1.c_str() << endl;
	}

	//整形转字符串:to_string
	//stoi:字符串转整形
	void test17()
	{
		//整形转字符串:to_string
		std::string s = std::to_string(123);
		//stoi:字符串转整形
		int i = stoi(s);
		cout << s << endl;
		cout << i << endl;
	}
}

以上就是C++: string的模拟实现的全部内容,希望能对大家有所帮助!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1272140.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ant design vue3 处理 ant-card-head ant-tabs靠左边对齐之has选择器不生效

火狐浏览器是不支持has的。 解决方法&#xff1a;通过position来解决。

拥抱未来:大语言模型解锁平台工程的无限可能

01 了解大型语言模型 (LLM) 大型语言模型&#xff08;LLM&#xff09;是一种人工智能&#xff08;AI&#xff09;算法&#xff0c;它使用深度学习技术和海量数据集来理解、总结、生成和预测新内容。凭借合成大量信息的能力&#xff0c;LLM 可以提高以前需要人类专家的业务流程的…

2002-2021年全国各省产业结构合理化高级化指数数据(含原始数据+计算过程+计算结果)

2002-2021年全国各省产业结构合理化高级化指数数据&#xff08;含原始数据计算过程计算结果&#xff09; 1、时间&#xff1a;2002-2021年 2、指标&#xff1a;地区、时间、就业总人数&#xff08;万人&#xff09;、第一产业就业人数&#xff08;万人&#xff09;、第二产业…

C语言从入门到实战——常用字符函数和字符串函数的了解和模拟实现

常用字符函数和字符串函数的了解和模拟实现 前言1. 字符分类函数2. 字符转换函数3. strlen的使用和模拟实现4. strcpy的使用和模拟实现5. strcat的使用和模拟实现6. strcmp的使用和模拟实现7. strncpy函数的使用8. strncat函数的使用9. strncmp函数的使用10. strstr的使用和模拟…

中通快递查询,中通快递单号查询,分析筛选出多次揽收件

批量查询中通快递单号的物流信息&#xff0c;并将其中的多次揽收件分析筛选出来。 所需工具&#xff1a; 一个【快递批量查询高手】软件 中通快递单号若干 操作步骤&#xff1a; 步骤1&#xff1a;运行【快递批量查询高手】软件&#xff0c;第一次使用的朋友记得先注册&…

阿里云ACP认证考试快速通关攻略,请收好!

目前云计算人才紧缺&#xff0c;预计2025年我国云计算产业人才缺口预计将达到150万&#xff0c;因此现在云计算工程师的薪资是相当可观的。而阿里云从2016年起就一直在国内市场占据着领先地位 。 阿里云目前稳居国内云计算市场第一&#xff0c;比排后面5名同行市场占有率的总和…

换抵挡装置(按位运算符的运用)

给出两个长度分别为n1&#xff0c;n2&#xff08;n1 n2 <32)且每列高度只为1或2的长条&#xff08;保证高度为1的地方水平上一致&#xff09;。需要将它们放入一个高度为3的容器长度&#xff0c;问能够容纳它们的最短容器长度 用手画的 本来是n1&#xff0c;n2 < 100的…

串口更新app程序(参考他人资料)

一&#xff1a;参考资料 1.选定镁光的falsh Debug一下bootloader工程&#xff0c;串口只输出了一个SREC SPI Bootloader。单步调试发现&#xff0c;是XIsf_Initialize出错了&#xff0c;返回了1&#xff0c;程序直接退出了main函数。再继续分析&#xff0c;发现程序走的是Atm…

2023.11.29 -hmzx电商平台建设项目 -核销主题阶段总结

目录 1.准备源数据 2.准备数仓工具进行源数据同步到ods层,本项目使用Datax 3.使用Datax完成数据同步前建表时的方案选择 3.1同步方式区别: 3.2存储格式和压缩区别: 4.在hive中创建表,共31个表 5.数仓概念 和 数仓建模方案 5.1数仓的基本概念 5.2 数仓建模方案 关系建模…

java论坛数据以及搜索接口实现

一. 内容简介 java论坛数据以及搜索接口实现 二. 软件环境 2.1 java 1.8 2.2 mysql Ver 8.0.13 for Win64 on x86_64 (MySQL Community Server - GPL) 2.3 IDEA ULTIMATE 2019.3 2.4d代码地址 三.主要流程 3.1 创建数据库,创建数据表 3.2 开始编写接口&#xff0c;并测…

C/C++不定参数的使用

文章目录 C语言的不定参C的不定参 C语言的不定参 C语言的不定参数最常见的应用示例就是printf函数&#xff0c;如下&#xff0c;参数列表中的...表示不定参数列表 #include <stdio.h> int printf(const char *format, ...);试着模拟实现C语言的printf函数 void myprin…

新手村之SQL——分组与子查询

1.GROUP BY GROUP BY 函数就是 SQL 中用来实现分组的函数&#xff0c;其用于结合聚合函数&#xff0c;能根据给定数据列的每个成员对查询结果进行分组统计&#xff0c;最终得到一个分组汇总表。 mysql> SELECT country, COUNT(country) AS teacher_count-> FROM teacher…

Linux系统部署Tale个人博客并发布到公网访问

文章目录 前言1. Tale网站搭建1.1 检查本地环境1.2 部署Tale个人博客系统1.3 启动Tale服务1.4 访问博客地址 2. Linux安装Cpolar内网穿透3. 创建Tale博客公网地址4. 使用公网地址访问Tale 前言 今天给大家带来一款基于 Java 语言的轻量级博客开源项目——Tale&#xff0c;Tale…

敏捷开发实现测试自动化的6个步骤

许多敏捷软件开发中的自动化测试的工作都失败了&#xff0c;或者并没有发挥它们最大的潜力。本文研究分析了自动化测试也许不能满足测试人员和其他利益相关者期望的两个主要原因&#xff0c;然后列举了六个能够避免陷入这些陷阱的步骤。以下是在敏捷环境中成功实现测试自动化的…

Linux系统iptables扩展

目录 一. iptables规则保存 1. 导出规则保存 2. 自动重载规则 ①. 当前用户生效 ②. 全局生效 二. 自定义链 1. 新建自定义链 2. 重命名自定义链 3. 添加自定义链规则 4. 调用自定义链规则 5. 删除自定义链 三. NAT 1. SNAT 2. DNAT 3. 实验 ①. 实验要求 ②. …

设计模式-创建型模式之原型、建造者设计模式

文章目录 七、原型模式八、建造者模式 七、原型模式 原型模式&#xff08;Prototype Pattern&#xff09;是用于创建重复的对象&#xff0c;同时又能保证性能。它提供了一种创建对象的最佳方式。 这种模式是实现了一个原型接口&#xff0c;该接口用于创建当前对象的克隆。当直…

C题目12:请写一个函数,判断一个数是否为质数,并在main函数中调用

一.每日小语 人的一切痛苦&#xff0c;本质上都是对自己的无能的愤怒。——王小波 自己思考 判断一个函数是否为质数&#xff0c;这个我在之前练过&#xff0c;我想至少两次&#xff0c;而这一次则是问我如何在main函数中调用&#xff0c;这个概念我不理解&#xff0c;所以我…

软件测试面试最全八股文

请你说一说测试用例的边界 参考回答&#xff1a; 边界值分析法就是对输入或输出的边界值进行测试的一种黑盒测试方法。通常边界值分析法是作为对等价类划分法的补充&#xff0c;这种情况下&#xff0c;其测试用例来自等价类的边界。 常见的边界值 1)对16-bit 的整数而言 32…

在gitlab上使用server_hooks

文章目录 1. 前置条件2. Git Hook2.1 Git Hook 分为两部分&#xff1a;本地和远程2.1.1 本地 Git Hook&#xff0c;由提交和合并等操作触发&#xff1a;2.1.2 远程 Git Hook&#xff0c;运行在网络操作上&#xff0c;例如接收推送的提交&#xff1a; 3. 操作步骤3.1 对所有的仓…

Linux 命令pwd

命令作用 pwd是Linux中一个非常有用而又十分简单的命令&#xff0c;pwd是词组print working directory的首字母缩写&#xff0c;即打印工作目录&#xff1b;工作目录就是你当前所处于的那个目录。 pwd始终以绝对路径的方式打印工作目录&#xff0c;即从根目录&#xff08;/&am…