C++——STL容器——string

目录

1.构造函数

模拟实现

2.析构函数

模拟实现

3.string遍历

3.1 c_str、size、lenth、capacity等

模拟实现

3.2 字符串元素访问

3.2.1 []操作符重载、at

模拟实现

3.2.2 front、back等

3.3 迭代器

模拟实现

4.赋值操作

4.1 赋值重载函数

模拟实现

4.2 assign函数

​编辑

5.size与capacity修改

5.1 shrink_to_fit缩容、reserve扩容

模拟实现

5.2 resize函数

模拟实现

 5.3 clear函数

 模拟实现

6.增删查改

6.1 string插入

6.1.1 push_back尾插字符

 模拟实现

6.1.2 append尾插字符串

 模拟实现

6.1.3 +=操作符重载

 模拟实现

6.1.4 insert随机插入

模拟实现

6.2 string删除

模拟实现

6.3 string替换

6.4 string查找

模拟实现

6.5 字符串截取

 模拟实现

7.交换string对象的值

模拟实现

8.比较操作符重载

模拟实现

9.输入和输出

9.1 流插入

9.1.1 << 流插入运算符重载

模拟实现

9.2 流提取

9.2.1 >> 流提取操作符重载

 模拟实现

9.2.2 getline函数

模拟实现

10.拷贝构造函数、赋值重载运算符的改进


        在C语言中我们在处理字符串时采取的方案是使用一个字符数组,这样的方式对于我们管理和使用字符串会有一些麻烦。所以在C++中,一方面兼容了C语言中的字符串形式,另一方面则是通过内置的string类来进行管理。C++内置的string类配备了许多方便的成员函数,这对我们使用C++处理字符串提供了极大的便利,这篇文章主要是总结string类的使用方法以及一些特点,同时对于一些常用的函数我们会自主模拟一份,以帮助我们更细致地掌握细节。

1.构造函数

        对于一个类而言,首要考虑的就是构造函数。C++为string的构造重载了许多函数,在使用string类的时候需要注意包含头文件 #include<string> 。

void Test1()
{
//default (1):			string();
//copy(2):				string(const string& str);
//substring(3):			string(const string& str, size_t pos, size_t len = npos);
//from c - string(4):	string(const char* s);
//from sequence(5):		string(const char* s, size_t n);
//fill(6):				string(size_t n, char c);string s1;string s2("hello world!");string s3(s2);string s4(s2,3,5);string s5(s2,3,10);string s6(s2,3);string s7("good luck", 3);string s8(7,'#');cout << s1 << endl;cout << s2 << endl;cout << s3 << endl;cout << s4 << endl;cout << s5 << endl;cout << s6 << endl;cout << s7 << endl;cout << s8 << endl;
}

对于以上的构造函数进行解释:

(1)默认构造;

(2)拷贝构造;

(3)部分拷贝:str字符串的pos下标开始len个长度。如果超出str范围或未传参则会一直拷贝到结尾。npos值为-1,对于size_t则是最大值;

(4)使用C-string构造;

(5)拷贝s字符串的前n个字符;

(6)n个c字符构造;

模拟实现

        在我们模拟实现一个string类的时候首先需要注意命名空间的问题。标准库中的标识符存在std中,所以我们需要重新定义一个命名空间,并在该命名空间中完成和string类有关的函数的实现,这样因为命名空间隔绝,所以可以放心使用函数名。在类外使用自主实现的string时需要记得指定命名空间域。

        string类中有三个成员变量:存储字符串的指针char*,记录字符串长度的整型和记录存储长度上限的整型。

        构造函数初始化使用初始化列表,要注意成员变量的初始化。需要注意的是实际开辟的空间大小需要比capacity大一个,因为需要存储一个'\0'。

using namespace std;	//展开std命名空间,对标识符的搜索先局部在全局,所以在以下m_string的命名空间中,所有标识符会优先搜索局部,即m_string中的标识符namespace m_string	//定义一个新的命名空间,将我们实现的string放在其中,防止和std中的string产生冲突
{class string{public://构造函数string(const char* str = "")	//构造函数,全缺省输入c字符串,当无输入的时候""也有一个\0,不会产生错误:_size(strlen(str)),_capacity(_size),_str(new char[_capacity + 1])	//初始化列表,初始化顺序按照成员变量声明顺序,_str需要多开一个空间存储\0{strcpy(_str, str);	//使用strcpy进行赋值操作}//拷贝构造函数string(const string& s)	//string对象中有_str,需要深拷贝{_str = new char[s._capacity + 1];strcpy(_str, s._str);_capacity = s._capacity;_size = s._size;}private:size_t _size;size_t _capacity;char* _str;}
}

2.析构函数

        析构函数的用法没什么特殊的,销毁对象的时候自动调用。

模拟实现

        因为是释放char*的空间,析构函数需要自己写一份。

		//析构函数~string()	//由于_str,所以需要主动实现析构函数{delete[] _str;_str = NULL;_capacity = _size = 0;}

3.string遍历

3.1 c_str、size、lenth、capacity等

        完成string遍历的操作首先需要有手段获取string对象的信息,string类中有许多函数来提供访问对象的基本信息的渠道。

void Test1()
{//length和size没有区别,都是返回字符串长度string s1("Say hello to the world!");cout << s1.size() << endl;cout << s1.length() << endl;//最大字符串长度(很少使用)cout << s1.max_size() << endl;//返回容量cout << s1.capacity() << endl;
}
模拟实现
		//c_str,作用是返回一个C字符串const char* c_str() const	//函数内和返回值都不会修改,所以const修饰this指针,const修饰返回值{return _str;}//size,返回string的长度size_t size() const{return _size;}//capacity,返回string容量size_t capacity() const{return _capacity;}

3.2 字符串元素访问

        除此之外,我们还需要能够像C语言一样通过下标等方式访问字符串的元素。因此,C++中也有不同的成员函数来提供诸多访问方式。

3.2.1 []操作符重载、at

        在C++中,我们可以看到[]操作符被重载了两份,分别对应const和非const对象。可以通过下标访问对应位置的字符,at和[]的功能一致。

	string s1("hello world");for (int i = 0; i < s1.size(); i++)//size()函数返回字符串长度,不包含'\0'{cout << s1[i] << ' ';s1[i]++;cout << s1[i] << ' ';//[]重载可读可写//char& operator[] (size_t pos); //const字符串使用const修饰的[]重载,返回const char&,不可以写操作//const char& operator[] (size_t pos) const;}cout << endl;//[]、at()功能一样,返回对应位置字符string s1("hello world");cout << s1[3] << endl;cout << s1.at(3) << endl;
模拟实现
		//[]重载,需要重载const和非const版本,保证非const对象可以被修改,而const对象不可修改char& operator[](size_t pos)	//针对非const对象,返回对应下标字符的引用{return _str[pos];}const char& operator[](size_t pos) const	//针对const对象,返回对应下标字符的const引用{return _str[pos];}

3.2.2 front、back等

        front和back函数用于返回头元素和尾元素,使用场景很少,其实现更多的是为了和其他STL容器保持一致。

	//front()、back()分别返回头尾字符cout << s1.front() << endl;cout << s1.back() << endl;

3.3 迭代器

        迭代器的主要作用就是遍历容器内的元素,使用迭代器有利于面向对象的封装特性,迭代器在提供遍历方案的同时隐藏了容器的具体实现方法,更加安全。除此之外,使用迭代器可以让我们不用在乎容器中的数据类型,迭代器会自动帮我们处理,这就让其使用起来更加方便。

        迭代器在使用时需要定义一个iterator类型的变量,使用string类中的begin函数与end函数,获得容器中第一个元素和最后一个元素的标识,然后进行遍历访问即可。

//迭代器,it类似于一个指针,通过it访问元素时需要解引用,begin和end分别返回第一个和'\0'的位置
string::iterator it = s2.begin();
while (it != s2.end())
{*it = '*';it++;
}
cout << s2 << endl;//范围for循环:底层也是迭代器
for (auto c : s2)
{cout << c << endl;
}

        迭代器会根据迭代方向和迭代对象是否为const类型进行一些变化。反向迭代器开始位置为rbegin,终止位置为rend,在迭代过程中使用reverse_iterator变量,并且以++来迭代。const迭代器则是需要const_iterator变量。

void Test1()
{//迭代器:正向、反向、const、非conststring s1("hello world");//正向非const迭代器string::iterator it = s1.begin();while (it != s1.end()){cout << *it << ' ';it++;}cout << endl;//反向非const迭代器string::reverse_iterator rit = s1.rbegin();while (rit != s1.rend()){cout << *rit << ' ';rit++;	//注意是++}cout << endl;const string s2("hello world");//正向const迭代器string::const_iterator c_it = s2.begin();while (c_it != s2.end()){cout << *c_it << ' ';c_it++;}cout << endl;//反向const迭代器string::const_reverse_iterator c_rit = s2.rbegin();while (c_rit != s2.rend()){cout << *c_rit << ' ';c_rit++;}cout << endl;
}
模拟实现

        对于迭代器,不同的容器使用的方法是不同的。在string类中,采取了类似于指针的方式完成迭代器,其实际实现可能不是指针,但是使用指针也可以达到相似的迭代效果,所以我们使用指针模拟一个迭代器。

//非const迭代器
typedef char* iterator;
iterator begin()
{return _str;
}
iterator end()
{return _str + _size;
}
//const迭代器
typedef const char* const_iterator;
const_iterator begin() const
{return _str;
}
const_iterator end() const
{return _str + _size;
}

4.赋值操作

4.1 赋值重载函数

        赋值重载函数也有多个重载形式,支持使用string对象、c字符串和字符进行赋值。注意的一点是赋值是会覆盖原字符串的。

void Test2()
{//string(1):        string& operator= (const string & str);//c - string(2):    string & operator= (const char* s);//character(3):        string& operator= (char c);string s1("hello");string s2;s2 = s1;cout << s2 << endl;	s2 = "world";cout << s2 << endl;	s2 = '&';cout << s2 << endl;}

        对以上重载形式解释:

(1)用string对象赋值;

(2)用C-string赋值;

(3)用字符赋值。

模拟实现

        由于涉及到深拷贝,所以需要自己实现赋值重载函数,开辟空间并将信息拷贝过来即可。

		//赋值重载函数string& operator=(string& s){char* tmp = new char[s._capacity + 1];	//使用一个临时字符数组开辟空间,保存待拷贝的字符串strcpy(tmp, s._str);delete[] _str;	//原string对象可能已有值,所以需要先释放空间,然后再把新开辟的带有已有字符串的空间赋值给对象_str = tmp;_size = s._size;_capacity = s._capacity;return *this;}

4.2 assign函数

        除了赋值重载操作符外,string类还提供了另一个名为assign的函数同样具有赋值操作,但是一般很少使用,当有赋值需求时一般会用 = 重载。

	string s1("hello world");cout << s1 << endl;//assign()//赋值(覆盖)s1.assign("hello");cout << s1 << endl;

5.size与capacity修改

5.1 shrink_to_fit缩容、reserve扩容

        shrink_to_fit实现的是缩容操作,该函数会将对象的capacity缩减至合适位置,即会将capacity缩小至与size相等,即所有空间都被使用的合适位置。

        reserve实现的是对capacity的控制,对于reserve的参数n,当n>capacity的时候,会进行扩容操作,将容量扩展至n;当n<=capacity时,函数不做任何动作,直接返回。

string s1("Say hello to the world!");
//缩容
s1.shrink_to_fit();
cout << s1.capacity() << endl;
//扩容(自动扩容)
s1.reserve(40);	//扩容到参数值大小,当reverse参数大于capacity才会执行
cout << s1.capacity() << endl;
模拟实现
//reserve(size_t n):重设capacity
//	n>capacity:扩容
//	n<=capacity:不做处理
void reserve(size_t n)
{if (n > _capacity)	//当n<=_capacity直接return{char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}
}

5.2 resize函数

        resize函数用于修改对象的size。当参数n大于对象的size时,则会将size扩大至n,扩大的部分则由字符参数c来填充,如果没有输入c则使用'\0'来填充。当参数n小于等于size时,则会将字符串截断,使其的size为n。

	//void resize(int n,char c = '\0'),重设字符串size大小//n < size				删除//size < n < capacity	插入//capacity < n			扩容并插入string s2("hello world");s2.resize(9);cout << s2 << endl;cout << s2.capacity() << endl;s2.resize(15,'&');cout << s2 << endl;cout << s2.capacity() << endl;s2.resize(20,'#');cout << s2 << endl;cout << s2.capacity() << endl;
模拟实现

        需要注意,修改size需要时刻关注capacity的大小,可以使用reserve函数,来让capacity设置成为我们想要的大小。

		//resize(int n,char c)://	n>size:将size扩大至n,扩展出的空间使用参数c进行填充//	n<=size:将size缩小至n,直接截断原字符串void resize(size_t n, char c = '\0'){if (n > _size){reserve(n);	//扩容,如果不需要扩容reserve就不会发生作用for (size_t i = _size; i < n; i++){_str[i] = c;}}_str[n] = '\0';	//n<size,就截断;n>size,就在末尾补'\0'_size = n;}

 5.3 clear函数

        clear函数可以将字符串清空,也就是设置size为0。

	string s1("Say hello to the world!");//清空数据,不对容量做修改s1.clear();cout << s1 << endl;cout << s1.capacity() << endl;
 模拟实现
		//clear:将字符串置为空void clear(){_size = 0;	//置空只需要将size置为0,然后注意字符串以'\0'结尾_str[_size] = '\0';}

6.增删查改

6.1 string插入

6.1.1 push_back尾插字符

        push_back函数支持在string对象的字符串后尾插元素,注意push_back函数仅仅是尾插一个字符,不支持尾插字符串。

	string s1("hello world");cout << s1 << endl;//push_back(char c)//在字符串尾插字符s1.push_back('!');cout << s1 << endl;
 模拟实现
		//push_back:尾插字符void push_back(char c){if (_size == _capacity)	//判断容量是否足够{reserve(_capacity == 0 ? 4 : 2 * _capacity);	//扩容交给reserve,需要考虑capacity==0的情况}_str[_size] = c;++_size;_str[_size] = c;}

6.1.2 append尾插字符串

        append函数支持尾插一个字符串,而根据字符串来源的不同,C++中提供了很多重载形式来支持,比如string对象的全部或部分尾插,c字符串等。

	string s1("hello world");cout << s1 << endl;//append()--append有许多接口//在字符串尾插字符串s1.append("have a good time");cout << s1 << endl;
 模拟实现
		//append:尾插字符串void append(const char* str)	//使用c_str做参数,类型是const char*,所以如果参数写成char*,权限放大会报错{size_t len = strlen(str);reserve(_size + len);strcpy(_str + _size, str);	//strcpy会自动补一个'\0'_size += len;}

6.1.3 +=操作符重载

        当有尾插需求时,一般更习惯使用+=操作符,因为+=操作符重载多个函数后,字符和字符串的尾插都可以实现,而且逻辑清晰易懂,代码简洁。

 

	string s1("hello world");cout << s1 << endl;//+=//尾插字符或字符串s1 += '!';cout << s1 << endl;s1 += "Perfect";cout << s1 << endl;
 模拟实现
//+=运算符重载:1.尾插字符;2.尾插字符串
string& operator+=(char c)
{push_back(c);return *this;
}
string& operator+=(const char* str)	//使用c_str做参数,类型是const char*,所以如果参数写成char*,权限放大会报错
{append(str);return *this;
}

6.1.4 insert随机插入

        除了以上所说的尾插外,我们势必会面临在任意位置插入字符或字符串的需求,这个时候就需要使用insert函数了。insert函数支持任意指定下标位置的字符,字符串插入。

	//insert()//在pos位置插入string s1("hello");s1.insert(3, " *** ");cout << s1 << endl;
模拟实现

        insert模拟实现我们需要重载两个版本,分别支持字符和字符串的插入。需要注意insert函数实现时下标的关系,防止越界情况发生,具体细节可以参考代码注释。

//insert:1.在pos下标位置插入字符;2.在pos下标位置插入字符串
void insert(size_t pos, char c)
{assert(pos <= _size);if (_size == _capacity)	//判断容量是否足够{reserve(_capacity == 0 ? 4 : 2 * _capacity);	//扩容交给reserve,需要考虑capacity==0的情况}size_t end = _size + 1;	//注意此处end是交换后的下标,所以极端情况(头插)下最小为0,可以用size_t//如果end=_size则是交换前的下标,极端情况下最小为-1,所以只可以使用int类型while (end > pos)	//最后一次交换:pos位置换到pos+1位置,end是交换后的下标,所以大于pos即可{_str[end] = _str[end - 1];--end;}_str[pos] = c;++_size;
}
void insert(size_t pos, const char* str)
{assert(pos <= _size);size_t len = strlen(str);reserve(_capacity + len);size_t end = _size + len;	//注意此处end是交换后的下标,所以极端情况(头插)下最小为0,可以用size_t//如果end=_size则是交换前的下标,极端情况下最小为-1,所以只可以使用int类型while (end > pos + len - 1)	//最后一次交换:pos位置换到pos+len的位置,end是交换后的下标,所以需要大于pos+len-1{_str[end] = _str[end - len];--end;}strncpy(_str + pos, str, len);_size += len;
}

6.2 string删除

        string字符串删除需要函数erase来完成,erase会删除从pos位置开始的len个长度的字符。

        我们在这里发现了参数len的缺省值为npos,那么这个npos是什么呢,通过查询我们发现npos实际上是string类内定义的一个静态成员变量,是一个无符号整型的-1值,换言之就是整型的最大值。所以我们可以理解,当没有传入长度参数时,使用缺省值删除2^32-1个长度的字符,也就是将pos位置后的字符全部删除的意思。

	string s1("hello");//在pos位置删除len长度字符串,缺省pos从开头删,缺省len删到结尾为止s1.erase(3, 2);cout << s1 << endl;
模拟实现

        在实现erase之前,我们也需要定义一个静态成员变量npos,静态成员变量在类内声明,类外定义。

//string类内
public:static const size_t npos;	//声明静态成员变量npos,属于整个类共享//string类外
const size_t m_string::string::npos = -1;	//静态成员变量在类内声明,在类外定义,需要注意npos的所在域:命名空间m_string内的类string的静态成员npos

        在完成erase时,需要注意len==npos的情况,因为这种情况很有可能造成我们判断的时候溢出,从而产生bug。 

		//erase:删除pos位置开始的len个字符void erase(size_t pos = 0, size_t len = npos){assert(pos < _size);if (len >= _size - pos)	//截断的情况//删除的末尾位置超过或就是字符串的最后一个字符len+pos>=_size,这个表达式len+pos可能会溢出;或者剩余字符长度小于等于待删除字符长度len>=_size-pos,不存在溢出风险{_str[pos] = '\0';_size = pos;}else{strcpy(_str + pos, _str + pos + len);	//直接将后续字符覆盖到pos位置_size -= len;}}

6.3 string替换

        replace函数完成string中部分字符替换的功能,使用场景不是很多,只需要知道有这么一个函数支持这个功能就好。replace会将pos位置开始的len个字符替换为指定字符串。

	//replace(pos, len, str)//把pos位置开始的len长度替换为str字符串s1.replace(3, 2, "&& & &&");cout << s1 << endl;

 

6.4 string查找

        string类对于字符串查找给出了许多函数。分别支持顺序查找、倒序查找、找第一个匹配的、找最后一个匹配的、找第一个不匹配的、找最后一个不匹配的等。在这些函数中,我们最应该了解的就是最纯真的find函数了。

	//从前往后、从后往前找与字符串中包含的字符相匹配、不匹配的字符//size_t find_first_of (const string& str, size_t pos = 0) const;//size_t find_last_of (const string& str, size_t pos = npos) const;//size_t find_first_not_of(const string & str, size_t pos = 0) const;//size_t find_last_not_of (const string& str, size_t pos = npos) const;string s1("hello world");cout << s1.find_first_of("aeiou") << endl;cout << s1.find_last_of("aeiou") << endl;cout << s1.find_first_not_of("aeiou") << endl;cout << s1.find_last_not_of("aeiou") << endl;

        find函数也给出了很多查找的重载形式,但是使用方式一致,查找pos位置开始的字符、字符串。如果找到了就会返回对应位置的下标,否则返回npos。

	//size_t find(str/char, pos)//从pos位置找对应的字符串/字符,找到返回下标,没找到返回npossize_t pos = 0;while ((pos = s1.find(' ', pos)) != string::npos){s1.replace(pos, 1, "##");}cout << s1 << endl;
模拟实现
//find:寻找字符串中pos开始首个匹配的字符、子串的下标,找不到就返回npos
size_t find(char c, size_t pos = 0) const
{assert(pos < _size);for (size_t i = pos; i < _size; i++){if (_str[i] == c){return i;}}return npos;
}
size_t find(const char* str, size_t pos = 0) const
{assert(pos < _size);const char* tmp = strstr(_str, str);if (tmp == nullptr){return npos;}else{return tmp - _str;}
}

6.5 字符串截取

        substr函数的作用是截取字符串,substr会将从pos位置开始的len长度的字符截取出来创建一个新的string对象并返回。

	//size_t find (const string& str / char c, size_t pos = 0) const;	find从pos位置开始查找指定字符串、字符,返回下标//size_t rfind (const string& str, size_t pos = npos) const;	rfind,从后往前找//string substr(size_t pos = 0, size_t len = npos) const;	取出pos开始len长的子串string s2("abc#def#ghi");cout << s2.substr(s2.find('#')) << endl;cout << s2.substr(s2.rfind('#')) << endl;
 模拟实现
		//substr:将pos位置开始len长度的字符串作为string对象string substr(size_t pos = 0, size_t len = npos){assert(pos < _size);string ret;if (len < _size - pos)	//拷贝不到原字符串结尾,避免溢出{for (size_t i = pos; i < pos + len; i++)	//拷贝到pos+len{ret += _str[i];}}else	//拷贝剩余所有字符{for (size_t i = pos; i < _size; i++)	//拷贝到_size{ret += _str[i];}}return ret;}

7.交换string对象的值

        在c++中,string类中实现了swap函数,可以通过swap函数来交换两个string对象的值。在这里不得不提的是,C++中有swap函数模板,对于基本数据类型而言,编译器会自动推导数据类型并使用模板生成对应的函数完成交换操作。而对于自定义类型则需要自己实现,针对swap函数string类就自主实现了交换的操作。

        但是我们知道类的成员函数的参数默认第一个是this指针,而且在调用时需要使用s1.swap(s2);的方式进行调用,这和我们常规的swap函数的使用方式有一些不同。所以出于方便考虑,string在类外定义了一个swap函数,以包裹类内的swap函数以完成swap(s1,s2);的调用方式。

模拟实现
		//string类内//swap:交换两个string对象void swap(string& s)	//swap在C++中以函数模板存在,所以对于自定义类型需要自主实现{std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}//string类外//在使用swap时一般会用swap(s1,s2)的形式,所以需要再写一个swap来满足该种形式的调用void swap(string& s1, string& s2){s1.swap(s2);}

8.比较操作符重载

        string中实现了compare成员函数进行比较,但是在习惯上我们更喜欢使用比较操作符重载来完成比较操作。

        比较操作符被string类重载为了类外函数,这么做的目的主要是满足特殊的比较情况。因为字符串比较有可能将一个const char*类型的对象放在操作符的左边,但是成员函数的第一个参数this指针必然是操作符第一个操作数,因此就无法支持这种情况了,所以在类外实现重载。

模拟实现

        在实现比较操作符重载时,我们只需要重载两个参数都是string对象的即可,因为我们使用的构造函数是单参数构造函数,如果使用C字符串则会存在隐式类型转换,将字符串类型转化变为string对象。

	//比较操作符重载,因为需要支持char*和string比较,所以全局重载,若在类中重载,那么this指针必然是第一个参数,就无法支持char*在==的左侧了//由于单参数构造支持隐式类型转换,所以char*会变为string类型进行比较,因而支持了string和char*之间的任意次序的比较bool operator==(const string& s1, const string& s2){return strcmp(s1.c_str(), s2.c_str()) == 0;}bool operator!=(const string& s1, const string& s2){return !(strcmp(s1.c_str(), s2.c_str()) == 0);}bool operator<(const string& s1, const string& s2){return strcmp(s1.c_str(), s2.c_str()) < 0;}bool operator<=(const string& s1, const string& s2){return s1 < s2 || s1 == s2;}bool operator>(const string& s1, const string& s2){return !(s1 <= s2);}bool operator>=(const string& s1, const string& s2){return !(s1 < s2);}

9.输入和输出

        之前在日期类的实现中,我们提到过流插入、流提取为了满足参数顺序关系所以定义在类外。

9.1 流插入

9.1.1 << 流插入运算符重载

       

模拟实现

        可以使用迭代器来进行流插入。 

	//流插入ostream& operator<<(ostream& out, const string& s){for (auto e : s)	//使用迭代器遍历,因为编译器支持内置类型的流插入,所以直接使用就好{out << e;}return out;}

9.2 流提取

9.2.1 >> 流提取操作符重载

        对于流提取操作符而言,c++规定遇到空格字符或换行符则标志着一次提取完成,换言之>>遇到空格或换行就结束。

 模拟实现

        在实现中我们需要注意:

①流提取会覆盖原来的string对象内的数据,所以需要清空再写入;

②流提取碰到空格或换行就结束,同时c++内的>>操作符读取不到空格和换行字符,所以需要istream的成员函数get来读取字符;

③为了更好的提高效率,我们可以构造类似于缓冲区的结构,对读取的字符进行缓冲并整体写入。

//流提取:遇到空格字符或换行字符就结束
istream& operator>>(istream& in, string& s)
{s.clear();	//流提取是覆盖原数据,所以需要先清空char buff[128];size_t cou = 0;char c;//in >> c;	//C++规定:流提取自动忽略空格字符和换行字符c = in.get();	//使用istream类的成员函数get来读取字符while (c != ' ' && c != '\n')	//当没有读到空格字符和换行字符时就继续{//s += c;	//一个字符一个字符的尾插效率不高//使用一个buff数组当做缓冲区,当缓冲区满了之后一并尾插,提高效率buff[cou++] = c;if (cou == 127){buff[cou] = '\0';s += buff;cou = 0;}c = in.get();}if (cou > 0)	//最后缓冲数组还有数据,则全部尾插{buff[cou] = '\0';s += buff;}return in;
}

9.2.2 getline函数

        与流提取操作符不同,getline会读取一行数据,即会读取空格,遇到换行才会停止。

模拟实现

        getline和>>操作符实现只在判断读取终点的时候有所不同,其余部分思路代码一致。

//getline:只在遇到换行字符结束
//		   和流插入只在while判断处不同
istream& getline(istream& in, string& s)
{s.clear();char buff[128];size_t cou = 0;char c;c = in.get();while (c != '\n')	//没有读到换行字符时就继续{buff[cou++] = c;if (cou == 127){buff[cou] = '\0';s += buff;cou = 0;}c = in.get();}if (cou > 0){buff[cou] = '\0';s += buff;}return in;
}

10.拷贝构造函数、赋值重载运算符的改进

        对于拷贝构造和赋值重载运算符,我们的策略是对string对象中的每个成员变量进行拷贝复制。但是如果面对成员变量很多的对象,这种方法就会造成代码的增加,写起来会很麻烦。所以我们可以考虑函数复用的方式来减少我们所需要写的代码。

        在实现复用前,我们需要强调:①局部对象在出函数后会自动销毁;②传引用传参时不会进行拷贝,而传对象传参则会调用拷贝函数,创建一个函数内的局部变量。

拷贝构造函数
//string(const string& s)	//string对象中有_str,需要深拷贝
//{
//	_str = new char[s._capacity + 1];
//	strcpy(_str, s._str);
//	_capacity = s._capacity;
//	_size = s._size;
//}
//拷贝构造函数——函数复用
string(const string& s)
{string tmp(s._str);swap(tmp);//创建临时变量tmp,交换tmp和*this,这样*this就完成了拷贝,同时出函数后指向空的对象tmp会被销毁
}

         拷贝构造函数可以通过调用构造函数创建一个和参数一样的局部变量,然后将这个局部变量交给this指针,而把this指针的值交给局部变量,这样就可以将创建的对象带回了。

赋值重载函数
//string& operator=(string& s)
//{
//	char* tmp = new char[s._capacity + 1];	//使用一个临时字符数组开辟空间,保存待拷贝的字符串
//	strcpy(tmp, s._str);//	delete[] _str;	//原string对象可能已有值,所以需要先释放空间,然后再把新开辟的带有已有字符串的空间赋值给对象
//	_str = tmp;
//	_size = s._size;
//	_capacity = s._capacity;
//
//	return *this;
//}
赋值重载函数——函数复用1
//string& operator=(string& s)
//{
//	string tmp(s);
//	swap(tmp);//	return *this;
//	//拷贝构造创建临时对象tmp,然后和this交换,this就是和s相同的对象,而tmp在出作用域就销毁
//}
//赋值重载函数——函数复用2
string& operator=(string tmp)
{swap(tmp);return *this;//函数传参传了对象,所以会默认调用一次拷贝构造构造tmp,然后交换tmp和this,出了函数就会销毁tmp
}

        赋值重载运算符也可以创造一个相同的局部变量,然后和this指针交换带回,而原来this指针的对象因为换给了tmp,所以离开了函数就会被销毁。也可以使用传值传参的形式完成拷贝函数的调用,创建局部对象。

        如上的改进并不会对效率产生影响,但是由于函数复用,将我们所要完成的代码交给了已经实现的函数去处理可以提高我们的工作效率。

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

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

相关文章

如何保证全部流量走代理

最近因为某些原因&#xff0c;需要做一些确保高匿的事情&#xff0c;便花时间做了一定的调研&#xff0c;至于是什么事情这里不便多说。 本文主要还是聊聊我看到的一些使用代理软件误区和确保流量全部走代理的方法&#xff0c;甚至也可以说是Proxifier的用户使用手册&#xff…

吴恩达2022机器学习专项课程(一) 5.4 多元线性回归的梯度下降

问题预览/关键词 多元线性回归的函数是&#xff1f;如何向量化表达&#xff1f;如何计算多元线性回归的成本函数的梯度&#xff1f;正规方程法是什么&#xff1f;正轨方程法的缺点是什么&#xff1f; 笔记 1.多元线性回归函数 5.1章节描述过。 向量化函数 原版函数 2.计…

Redis中的持久化

持久化 .RDB手动触发save命令bgsave命令 自动触发bgsave的具体流程RDB的处理保存压缩校验 RDB的优缺点 AOF命令写入文件同步重写机制启动时恢复数据 本章重点回顾 . RDB RDB持久化是把当前进程数据生成快照保存到硬盘的过程,触发RDB持久化过程分为手动触发和自动触发 手动触发…

特朗普数字钱包被空投100万MVP,加密资产或将提供更多竞选资金

唐纳德.特朗普先生对待加密货币的态度正在发生改变&#xff0c;曾经他对加密货币持有负面的态度&#xff0c;曾多次在公开场合批评比特币等数字货币。然而&#xff0c;随着特朗普NFT等加密资产的上链&#xff0c;他对加密货币的态度也发生了巨大的转变。 据相关媒体报道&#x…

FastWiki发布`0.2.4`支持js 函数

Release v0.2.4 AIDotNet/fast-wiki (github.com) 支持JS动态functioncall调用支持动态function管理支持JS在线编辑提供智能代码提示支持JS在线编辑提供部分绑定的c#类&#xff08;默认提供Console&#xff0c;HttpClient&#xff09;支持Application绑定多个Function Call优…

地面站Mission Planner从源码编译与运行

0. 环境 - win10&#xff08;基本需要100G硬盘&#xff09; - ubuntu18 1. 安装vs2022 下载 vs2022 community 在线安装包。 https://visualstudio.microsoft.com/ 打开 Visual Studio Installer 先安装 Visual Studio Community 2022本体。占用1.2GB。 Visual Studio Inst…

【Docker系列】在 Linux 上安装 Docker Compose 的简明步骤

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

Git 如何去使用

目录 1. Git暂存区的使用 1.1. 暂存区的作用 1.2. 暂存区覆盖工作区&#xff08;注意&#xff1a;完全确认覆盖时使用&#xff09; 1.3. 暂存区移除文件 1.4. 练习 2. Git回退版本 2.1. 概念 2.2. 查看提交历史 2.3. 回退命令 2.4. 注意 3. Git删除文件 3.1. 需求 …

CAN的回环测试实验

我只有一个STM32F429的开发板,所以这次我就做个回环测试. 1.先了解一下有关CAN的一些标准库,结构体 1.CAN初始化结构体---CAN_InitTypeDef 2.CAN发送及接收结构体--- CanTxMsg(发送结构体) CanRxMsg(接收结构体) 3.CAN筛选器结构体 --- CAN_FilterInitTypeDef 2.代码编写 以及…

HarmonyOS 和 OpenHarmony

HarmonyOS 和 OpenHarmony 支持的 shell 命令不同&#xff0c;因此有时候需要做一做区分&#xff0c;目前有些文档上没有标注&#xff0c;因此可能产生歧义。 HarmonyOS 支持 getprop&#xff1a; getprop hw_sc.build.os.apiversion # 查看API版本OpenHarmony 上支持 param…

SYS-2722音频分析仪SYS2722

181/2461/8938产品概述&#xff1a; Audio Precision 2722 音频分析仪是 Audio Precision 屡获殊荣的 PC 控制音频分析仪的旗舰型号&#xff0c;长期以来一直是音频设备设计和测试的全球公认标准。功能齐全的 SYS-2722 提供了测试转换器技术最新进展所需的无与伦比的失真和噪声…

Jetson nano部署Yolov8 安装Archiconda3+创建pytorch环境(详细教程+错误解决)

由于jetson nano 是aarch64架构&#xff0c;Anaconda官方不支持aarch64架构&#xff0c;所以有了一个叫“Archiconda”&#xff0c;其目的就是将conda移植到aarch64平台上 一. 下载地址Releases Archiconda/build-tools GitHub 然后安装archiconda bash Archiconda3-0.2.3…

(学习日记)2024.03.31:UCOSIII第二十八节:消息队列常用函数

写在前面&#xff1a; 由于时间的不足与学习的碎片化&#xff0c;写博客变得有些奢侈。 但是对于记录学习&#xff08;忘了以后能快速复习&#xff09;的渴望一天天变得强烈。 既然如此 不如以天为单位&#xff0c;以时间为顺序&#xff0c;仅仅将博客当做一个知识学习的目录&a…

铸铁平台的平面度

铸铁平台的平面度是指平台的表面平整程度&#xff0c;即平台表面与其理论平面之间的最大偏差。平台的平面度通常使用国际标准符号GD&T中的平面度符号&#xff08;ⓨ&#xff09;表示&#xff0c;单位为毫米&#xff08;mm&#xff09;或微米&#xff08;μm&#xff09;。 …

KeyguardClockSwitch的父类

KeyguardClockSwitch 定义在KeyguardStatusView中, mClockView findViewById(R.id.keyguard_clock_container);KeyguardClockSwitch的父类为&#xff1a; Class Name: LinearLayout Class Name: KeyguardStatusView Class Name: NotificationPanelView Class Name: Notificat…

蓝桥杯 - 穿越雷区

解题思路&#xff1a; dfs 方法一&#xff1a; import java.util.Scanner;public class Main {static char[][] a;static int[][] visited;static int[] dx { 0, 1, 0, -1 };static int[] dy { 1, 0, -1, 0 };static long min Long.MAX_VALUE;static long count 0;publi…

记第一次eudsrc拿到RCE(上)

目录 前言 个人介绍 挖洞公式 漏洞介绍 CLI命令注入介绍 RCE漏洞介绍 漏洞详情 漏洞点1 漏洞点2 修复建议 总结 前言 免责声明 以下漏洞均已经上报漏洞平台。请勿利用文章内的相关技术从事非法测试。若因此产生一切后果与本博客及本人无关。 本来想大学四年都不会…

Linux 线程:使用管理线程、多线程、分离线程

目录 一、使用线程 1、pthread_create创建线程 2、pthread_join等待线程 主线程获取新线程退出结果 获取新线程退出返回的数组 3、线程异常导致进程终止 4、pthread_exit 5、pthread_cancel 6、主线程可以取消新线程&#xff0c;新线程可以取消主线程吗 二、如何管理线…

Linux 常用命令(持续更新中...)

1. ls 查看文件列表命令 语法&#xff1a; ls [-a -l -h] [Linux路径] -a -l -h 是可选的选项 &#xff08;-h需配合-l命令一起使用&#xff09;Linux路径是此命令可选的参数 ls #查看当前目录所有非隐藏文件(平铺方式显示) ls -a #查看当前目录下所有文件 …

【stm32】I2C通信外设

【stm32】I2C通信外设 概念部分 如果简单应用&#xff0c;选择软件I2C。如果对性能指标要求比较高 选择硬件I2C 有硬件电路自动反转引脚电平&#xff0c;软件只需要写入控制寄存器CR和数据寄存器DR 为了实时监控时序的状态&#xff0c;还要读取状态寄存器SR 写入控制寄存器CR…