这一节主要总结string类的常见接口,以及完成了string类的模拟实现。
目录
标准库的String类
string类常见接口
string类对象的常见构造
string析构函数:~string
string类对象的容量操作
string类对象的访问及遍历操作
string类对象的修改操作
string类非成员函数
string类的模拟实现
经典的string类问题
浅拷贝
深拷贝
传统版写法的String类
现代版写法的String类
string类的模拟实现
标准库的String类
为了符合C++面向对象的特性,引入了string类,string是表示字符串的字符串类。下面说明一下string类的特点:
1.string是表示字符串的字符串类
2.string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类,并用char_traits和allocator作为basic_string的默认参数
3.string类独立于所使用的编码来处理字节,如果用来处理多字节或变长字符(如UTF-8)的序列,这个类的所有成员(如长度或大小)以及它的迭代器,将仍然按照字节(而不是实际编码的字符)来操作。
提示: 在使用string类时,必须包含#include头文件以及using namespace std;
string类常见接口
string类对象的常见构造
(重点)1.string()
默认构造,构造空的string类对象,即空字符串
(重点)2.string(const string& str)
拷贝构造,使用str拷贝构造string类对象
(重点)3.string(const char* s)
使用C-string来构造string类对象
4.string(const string& str, size_t pos, size_t len=npos)
从str的第pos个位置,取len个字符构造,如果len超过字符串长度,那么只取到字符串末尾,如果len未给出,那么采用默认参数npos(0xFFFFFFFF)
5.string(const char* s, size_t n)
使用C-string的前n个字符来构造string类对象
6.string(size_t n, char c)
string类对象包含n个字符c
void test_string1()
{string s0;string s1("hello world");string s2(s1);string s3(s1, 5, 3);string s4(s1, 5, 10);string s5(s1, 5);string s6(10, '$');cout << s0 << endl;cout << s1 << endl;cout << s2 << endl;cout << s3 << endl;cout << s4 << endl;cout << s5 << endl;cout << s6 << endl;
}
string析构函数:~string
在string类对象生命周期结束时,会被自动调用。
string类对象的容量操作
1.size()和length()
这两个均可以返回字符串有效字符长度,功能一样。可以通过size遍历字符串。有两个功能一样的函数是由于历史原因造成的!
2.max_size()
返回string最大的大小
3.capacity()
返回string类对象的容量大小。
//查看扩容机制
void test_string4()
{string s;size_t sz = s.capacity();cout << "capacity change:" << sz << endl;cout << "making s growing" << endl;for (int i = 0; i < 100; i++){s.push_back('c');if (sz != s.capacity()){sz = s.capacity();cout << "capacity changed" << sz << endl;}}
}
虽然是15、31...,但是没有包括‘\0’,所以实际大小应该是16、32、48...,第一次是2倍扩容,以后每次是1.5倍扩容(VS平台下)。但是在Linux平台下,扩容机制不太一样:
这就说明,STL是一个标准,具体怎么实现由编译器决定!
4.clear()
清除对象的数据,但是不会清空间。
5.shrink_to_fit()
在clear()后,为了释放一些已开辟的空间,可以缩容
6.reserve()
如果提前知道要开辟空间的大小,可以用reserve()为字符串提前预留空间,防止频繁扩容,因为扩容一般是异地扩容,效率低下。
避免了频繁扩容!
还有一个问题,reserve会不会缩容呢?不会的!当reserve()比capacity大,才会扩容!
7. resize()
调整字符串大小。
如果采用第一种,那么默认用‘\0’插入,第二种用指定的char插入。
string类对象的访问及遍历操作
1.operator[],返回pos位置的字符
string s1("hello world");
for (size_t i = 0; i < s1.size(); i++)
{cout << s1[i] << " ";
}
2.迭代器,begin+end,可以通过迭代器的方式遍历字符串,begin获取第一个字符的迭代器,end获取最后一个字符下一个位置的迭代器
string::iterator it1 = s1.begin();while (it1 != s1.end()){cout << *it1 << " ";++it1;}
我们感觉到迭代器很像指针,但是并不是指针!
3.rbegin+rend,支持倒着遍历,循环中用++rit,而不是--rit,对于反向迭代器而言,++就是倒着走
string s1("hello world");
string::reverse_iterator rit = s1.rbegin();
while (rit != s1.rend())
{cout << *rit << " ";++rit;
}
除了正向和反向的正常iterator外,还有正向和反向的const_iterator,是用来为const string构造迭代器,其特点是只能读不能写:
//只读
const string s3("hello world");
string::const_iterator it3 = s3.begin();
while (it3 != s3.end())
{//*it3 += 3;//报错,因为只读cout << *it3 << " ";
}
cout << endl;
//string::const_reverse_iterator
4.用范围for遍历字符串
for (auto s : s1){cout << s << " ";}
但是范围for并没有什么特别之处,其底层是迭代器!
string类对象的修改操作
1.pushback()
在字符串末尾尾插字符
2.append()
在字符串后追加字符串
3.operator+=
在字符串后追加字符串str
4.assign()
用新内容覆盖原字符串
5.insert()
在某一个位置插入字符串
注意:insert()尽量不要使用,因为在使用过程中会挪动数据,效率低。
6.erase()
删除字符
同样,erase()函数能不用就不用。
7.replace()
字符的替换
8.find()
查找某个字符的位置
小练习:将一个字符串中的空格替换成“%20”。
总结一下,insert、erase、replace能少用就少用,因为基本都要挪动数据,效率不高。
9.c_str
返回C格式字符串
在C++程序中,我们有可能会调用C语言的函数,比如fopen函数,但是在程序中文件名可能是string对象,需要转换成C字符串才行。
void test_string()
{string filename("test.cpp");FILE* fout = fopen(filename.c_str(), "r");
}
10.find
找到某一个字符或字符串第一次出现的位置
例如:获取后缀
void test_string()
{string s1("test.cpp");size_t pos = s1.find('.');if (pos != string::npos){string suffix = s1.substr(pos);cout << suffix << endl;}else{cout << "没有后缀" << endl;}
}
11.rfind
从后往前找某一个字符或字符串第一次出现的位置
例如:获取后缀“file.c.tar.zip”的zip
void test_string()
{string s1("test.c.tar.zip");size_t pos = s1.rfind('.');if (pos != string::npos){string suffix = s1.substr(pos);cout << suffix << endl;}else{cout << "没有后缀" << endl;}
}
string类非成员函数
1.operator+
尽量少用,因为传值返回,导致深拷贝效率低
2.operator>>&&operator<<
输入和输出运算符重载
3.getline
获取一行字符串。弥补了cin>>的缺点,cin遇到了空格或者换行就提取结束。
4.== != > >= < <=
大小比较
string类的模拟实现
经典的string类问题
首先来看一个经典关于浅拷贝的问题:
class String
{
public:
/*String()
:_str(new char[1])
{*_str = '\0';}
*/
//String(const char* str = "\0") 错误示范
//String(const char* str = nullptr) 错误示范,使用strlen时会涉及解引用
String(const char* str = "")
{// 构造String类对象时,如果传递nullptr指针,可以认为程序非if (nullptr == str){assert(false);return;}_str = new char[strlen(str) + 1];strcpy(_str, str);
}
~String()
{if (_str){delete[] _str;_str = nullptr;}
}
private:char* _str;
};
// 测试
void TestString()
{String s1("hello bit!!!");String s2(s1);
}
说明:在上面的String类中没有显式定义拷贝构造和赋值运算符重载,因此,当使用s1去构造时s2时,编译器会调用默认构造函数。这样导致的问题是,s1和s2指向同一块内存空间,在释放空间时,同一块空间会被释放两次而引起程序崩溃,这种拷贝方式,称为浅拷贝。
浅拷贝
浅拷贝:也称值拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规。
可以用深拷贝解决浅拷贝问题,也就是:每个对象都有一份独立的资源,不要和其他对象共享。
深拷贝
如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情况都是按照深拷贝方式提供。
传统版写法的String类
namespace ghs
{class string{public:string(const char* str=""):_size(strlen(str)){_str = new char[_size + 1];strcpy(_str, str);_capacity = _size;}string(const string& str){_str = new char[str._capacity + 1];strcpy(_str, str._str);_size = str._size;_capacity = str._capacity;}//s1 = s2string& operator=(const string& str){char* tmp = new char[str._capacity + 1];strcpy(tmp, str._str);delete[] _str;_str = tmp;_size = str._size;_capacity = str._capacity;return *this;}~string(){delete[] _str;_str = nullptr;_size = 0;_capacity = 0;}private:char* _str = nullptr;size_t _size = 0; size_t _capacity = 0;public:static const int npos;};const int string::npos = -1;
}
在传统版的写法中,拷贝构造和赋值构造都是通过自己手动开空间,然后使用strcpy函数拷贝完成的,下面的现代写法在实现上更为简洁:
现代版写法的String类
namespace ghs
{class string{public:string(const char* str=""):_size(strlen(str)){_str = new char[_size + 1];strcpy(_str, str);_capacity = _size;}string(const string& s){string tmp(s._str);swap(tmp);}//s1 = s2//string& operator=(const string& str)//{// string ss(str);// swap(ss);// return *this;//}//上面的再优化,这种更推荐string& operator=(string ss)//传值传参,调用拷贝构造{swap(ss);return *this;}~string(){delete[] _str;_str = nullptr;_size = 0;_capacity = 0;}private:char* _str = nullptr;size_t _size = 0; size_t _capacity = 0;public:static const int npos;};const int string::npos = -1;
}
在现代写法中,拷贝构造是通过调用string(const char* str="")构造函数来生成临时的string对象(tmp),再将*this和tmp交换,完成拷贝。在赋值构造中,使用传值传参,其实调用拷贝构造,生成临时对象ss,再将ss和*this交换,完成拷贝。
总结:传统版的现代版的效率一样,只是现代版调用了之前已经实现好的函数,所以看起来更加简洁!
string类的模拟实现
namespace ghs
{class string{public:/*string():_str(nullptr),_size(0),_capacity(0){}*//*string():_str(new char[1]),_size(0),_capacity(0){_str[0] = '\0';}*/typedef char* iterator;typedef const char* const_iterator;iterator begin(){return _str;}iterator end(){return _str + _size;}const_iterator begin()const{return _str;}const_iterator end()const{return _str + _size;}string(const char* str=""):_size(strlen(str)){_str = new char[_size + 1];strcpy(_str, str);_capacity = _size;}//string(const string& str)//{// _str = new char[str._capacity + 1];// strcpy(_str, str._str);// _size = str._size;// _capacity = str._capacity;//}string(const string& s){string tmp(s._str);swap(tmp);}//s1 = s2//string& operator=(const string& str)//{// char* tmp = new char[str._capacity + 1];// strcpy(tmp, str._str);// delete[] _str;// _str = tmp;// _size = str._size;// _capacity = str._capacity;// return *this;//}string& operator=(const string& str){string ss(str);swap(ss);return *this;}//上面的再优化,这种更推荐//string& operator=(string ss)//传值传参,调用拷贝构造//{// swap(ss);// return *this;//}const char* c_str()const{return _str;}size_t size()const{return _size;}char& operator[](size_t pos){assert(pos < _size);return _str[pos];}void reserve(size_t sz){if (sz > _capacity){char* p = new char[sz + 1];strcpy(p, _str);delete[] _str;_str = p;_capacity = sz;}}void push_back(char ch){//扩容2倍/*if (_size == _capacity){reserve(_capacity == 0 ? 4 : 2 * _capacity);}_str[_size] = ch;_size++;_str[_size] = '\0';*/insert(_size, ch);}void append(const char* str){//扩容//size_t len = strlen(str);//if (_size + len > _capacity)//{// reserve(_size + len);//}//strcpy(_str + _size, str);///*for (size_t i = 0; i < len; i++)//{// push_back(str[i]);//}*///_size += len;insert(_size, str);}string& operator+=(char ch){push_back(ch);return *this;}string& operator+=(const char* str){append(str);return *this;}const char& operator[](size_t pos)const{assert(pos < _size);return _str[pos];}size_t capacity()const{return _capacity;}void insert(size_t pos, char ch){assert(pos <= _size);if (_size == _capacity){reserve(_capacity == 0 ? 4 : 2 * _capacity);}for (size_t end = _size+1; end > pos; end--){_str[end] = _str[end - 1];}_str[pos] = ch;_size++;}void insert(size_t pos, const char* str){assert(pos <= _size);size_t len = strlen(str);if (_size + len > _capacity){reserve(len + _size);}for (size_t end = _size+len; end > pos+len-1; end--){_str[end] = _str[end-len];}/*for (size_t i = 0; i < len; i++){_str[pos + i] = str[i];}*/strncpy(_str + pos, str, len);_size += len;}void erase(size_t pos, size_t len = npos){assert(pos < _size);if (len == npos || len>=_size-pos){_size = pos;_str[_size] = '\0';}else{strcpy(_str + pos, _str + pos + len);_size -= len;}}void resize(size_t n, char ch = '\0'){if (n <= _size){_str[n] = '\0';_size = n;}else{reserve(n);for (size_t i = _size; i < n; i++){_str[i] = ch;}_str[n] = '\0';_size = n;}}void swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}size_t find(char ch, size_t pos = 0)const{assert(pos < _size);for (size_t i = pos; i < _size; i++){if (_str[i] == ch){return i;}}return npos;}size_t find(const char* str, size_t pos = 0)const{assert(pos < _size);char* tmp = strstr(_str+pos, str);if (tmp != nullptr){return tmp - _str;}else{return npos;}}string substr(size_t pos = 0, size_t len = npos){string s;if (len == npos || len >= _size-pos){for (size_t i = pos; i < _size; i++){s += _str[i];}}else{for (size_t i = pos; i < pos + len; i++){s += _str[i];}}return s;}void clear(){_size = 0;_str[_size] = '\0';}/*bool operator==(const string& str){int ret = strcmp(_str, str._str);return ret == 0;}*/~string(){delete[] _str;_str = nullptr;_size = 0;_capacity = 0;}private:char* _str = nullptr;size_t _size = 0; size_t _capacity = 0;public:static const int npos;};const int string::npos = -1;void swap(string& x, string& y){x.swap(y);}bool operator==(const string& s1,const string& s2){int ret = strcmp(s1.c_str(), s2.c_str());return ret == 0;}bool operator<(const string& s1, const string& s2){int ret = strcmp(s1.c_str(), s2.c_str());return ret < 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);}ostream& operator<<(ostream& out, const string& str){for (auto ch : str){out << ch; }return out;}istream& operator>>(istream& in, string& str){char ch;str.clear();//in >> ch;ch = in.get();char buff[100];size_t i = 0;while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == 99){buff[99] = '\0';str += buff;i = 0;}ch = in.get();}if (i > 0){buff[i] = '\0';str += buff;}return in; }istream& getline(istream& in, string& str){char ch;str.clear();char buff[100];//in >> ch;/*ch = in.get();while (ch != '\n'){str += ch;ch = in.get();}*/ch = in.get();size_t i = 0;while (ch != '\n'){buff[i++] = ch;if (i == 99){buff[99] = '\0';str += buff;i = 0;}ch = in.get();}if (i > 0){buff[i] = '\0';str += buff;}return in;}
}