目录
2.3.1begin和end(结合遍历和访问解释)还有operator[]
2.3.4cbegin,cend,crbegin,crend
2.5.7find_first_of(),find_first_last_of(),find_first_not_of(),find_last_first_not_of()
注意一些函数原型图源自C library - C++ Reference
1.基本了解(网上一搜一大堆,这里就cv一下)
1. 字符串是表示字符序列的类2. 标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,但添加了专门用于操作单字节字符字符串的设计特性。3. string 类是使用 char( 即作为它的字符类型,使用它的默认 char_traits 和分配器类型 ( 关于模板的更多信息,请参阅 basic_string) 。4. string 类是 basic_string 模板类的一个实例,它使用 char 来实例化 basic_string 模板类,并用 char_traits和 allocator 作为 basic_string 的默认参数 ( 根于更多的模板信息请参考 basic_string) 。5. 注意,这个类独立于所使用的编码来处理字节 : 如果用来处理多字节或变长字符 ( 如 UTF-8) 的序列,这个类的所有成员 ( 如长度或大小 ) 以及它的迭代器,将仍然按照字节 ( 而不是实际编码的字符 ) 来操作。1. string 是表示字符串的字符串类2. 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作 string 的常规操作。3. string 在底层实际是: basic_string 模板类的别名, typedef basic_string<char, char_traits, allocator>string;4. 不能操作多字节或者变长字符的序列。在使用 string 类时,必须包含 #include 头文件以及 using namespace std;
2.常见接口
2.1常见构造
string a1;//第一种,空字符串 string a2("sdadwa");//第二种,用一个常量字符串初始化 string a3 = a2;//第三种,本质上是拷贝构造函数,即string a3(a2); string a4(3, 'f');//第四种,n个字符,不怎么用 string a5(a2, 3, 2);//第5种从a2的3下标(注意,string类型本质上个字符串, //而这个字符串 //我们又能理解为字符串数组)开始2个字符都拷贝过去,从而构造a5。 // string a5(const string &str ,size_t pos,size_t len=npos) //如果len没有给值,也就是缺省参数npos,而npos是整型最大值(给的-1,size_t //是unsigned int ,-1是就是全1,那就是整型最大值) //而这个值肯定是大于字符串长度的,因此,根据string的设计,最终会拷贝到str的最 //后一个字符。 //总结的来说,就是如果len大于字符串str长度或不给值,就是拷贝全部 string a6("gasda", 3);//第6种,第常量字符串的前n个字符来初始化a6 //还有第7种,但这个涉及迭代器,不急。 //这里再讲下关于string的赋值语句 a4=a2; a4="dwada"; a4='w';
2.2容量操作
2.2.1size()
string a1 = "wdawdadwa"; a1.size(); a1.lenght(); size和length都是返回字符串长度(不包含\0) 原理相同,区别不大,只是跟其他容器保持一致,才出现size()
2.2.2max_size()
const string a1 = "wdawdadwa"; cout << a1.max_size(); 返回string类型最大能开的空间,就参考下, 一般实际不一定能开这么大 没啥实践意义
2.2.3reserve()
string a1 = "wdawdadwa"; a1.reserve(10); /*这个函数就是用来修改string对象的capacity 也就是给这个对象预留的空间大小*/
我们要知道,string类型内部一般有3个成员变量除了字符串本身,还有2个,一个是size也就是现有的字符串长度,还有一个就是capacity,是该string对象最多能存的字符串大小。
2.2.4capacity()
string a1 = "wdawdadwa"; cout << a1.capacity(); 一般不作修改,是返回15,也就是最多存15个字符,空间一共16个字节 其中15个字符空间,1个\0空间。 编译器是vs和window。 但如果是在linux因为用的库不太一样,stl的版本不一样,会出现不一样的结果
capacity一般我们不用管,因为string类型可以看作是存储字符串的顺序表,也就是说,会自动扩容。但reserve也有意义,可以减少不必要的重复扩容操作,可以一步到位(前提是我们自己确定要多少空间)而且reserve设置的空间也是要按规律走的,比如vs下的15,31,47,我们如果reserve给的不是这些值,编译器开的时候会自动向这么数值上靠,比如给40,可能开的47。但也不一定,有一定随机性。但这是vs环境,别的环境可能也不一样(比如g++下就比较标准,直接按你给的值开)
另外,关于reserve能不能缩空间呢,vs环境下是不能缩的,不管是否有数据,但是在g++环境下,可以缩(如果无数据,就按你给的缩,有数据并且给的值小于数据空间大小,就缩到数据的长度(简而言之,就是在不影响数据的情况下缩))
但不管什么环境,可以确定的是,不会影响数据。
2.2.5resize()
resize既可以影响容量也可以影响数据,比如给的值大于capacity,那么会自动扩容,且会填充字符在string对象里,如果不给字符,填充的是\0,如果给定字符比如a1.resize(100,'s')那么会填充s字符。
如果给定值大于size但小于capacity呢,在vs和g++环境下,size会变成给定值,capacity不会变。
如果给定值小于size呢,在vs环境下,size会变成给定值,capacity不会变,相当于会保留前给定值个数据,多出来的数据相当于删除。
string对象的resize更多是用来初始化,比如
2.2.6clear()
string a1; a1.resize(100, '*'); a1.clear(); cout << a1.capacity() << endl; cout << a1.size() << endl; clear会清空字符串,size变为0,capacity不变。
2.2.7empty()
string a1; a1.resize(100, '*'); cout << a1.empty() << endl; //判断字符串是否为空,为空返回真,不为空返回假即0
2.2.8shrink_to_fit()
这个是缩减string对象容量,来让容量与字符串长度更加匹配,不会改变字符串长度和内容。
2.3遍历和访问
2.3.1begin和end(结合遍历和访问解释)还有operator[]
begin()是第一个字符的迭代器,end()是最后一个字符的下一个位置的迭代器
第一种: string a1 = "wdawdadwa"; for (int i = 0; i < a1.size(); i++) { cout << a1[i] << ' '; a1[i]='w'; //cout<<a1.operator[](i)<<' '; } string对于[]是进行了函数重载的,在使用上跟访问数组元素一样。 operator[]; 注意,[]的重载之后,既可以返回该下标的字符,也能对该下标字符进行修改 因为有两种重载,一个是返回const 类型字符的引用,一个是返回普通字符的引用 虽然非const可以调用const的那条重载,但是const的不能调用非const的那条重载 所以有2条。而且利用const,可以避免修改const类型的字符串。 第二种:运用了迭代器,用法像指针 string a1 = "wdawdadwa"; string::iterator it = a1.begin(); while(it!=a1.end()) { cout << *it << " "; *it += 1; it++; } begin()是第一个字符的迭代器,end()是最后一个字符的下一个位置的迭代器 虽然我们用指针辅助理解。 iterator就像是用指针的方式遍历访问。 第一种方法是针对物理连续的空间,但第二种可以针对逻辑连续而物理不连续
另外,关于begin,是分为了const和非const的。 const string a1 = "wdawdadwa"; string::const_iterator it = a1.begin(); //注意const_iterator是保护*it不被修改 //const string::iterator it,是保护it不被修改 while(it!=a1.end()) { cout << *it << " "; it++; }
2.3.2另外对于大多数容器,都支持范围for
string a1 = "wdawdadwa"; string::iterator it = a1.begin(); for (auto e : a1) { cout << e << ' '; } 且对于auto,其实本质就是傻瓜式替换成了迭代器 一旦我们自己在容器里写个迭代器,然后名字不规范,那么auto就会 出错
2.3.3rbegin(),rend().
非const的rbegin,rend string a1 = "wdawdadwa"; string::reverse_iterator it = a1.rbegin(); while (it != a1.rend()) { cout << (*it); it++; } const的rbegin,rend const string a1 = "wdawdadwa"; string::const_reverse_iterator it = a1.rbegin(); while (it != a1.rend()) { cout << (*it); it++; } r就是逆置的reverse,也就是说倒着遍历
迭代器的类型可能比较长,可以用auto。
2.3.4cbegin,cend,crbegin,crend
cbegin和cend就是const的string类型的迭代器,crbegin和crend就是const修饰的string类型的反向迭代器。写auto的时候清晰点,明确返回值
2.3.5at()
at和[]用途很像,只是用法和一些细节不一样
string a1 = "wdawdawda"; cout << a1.at(1) << endl; []访问元素,如果越界访问,是通过断言,判断是否超出范围来进行报错的 at的话,如果越界访问,会抛异常,稍微比较柔和
2.3.6back和front
这个很简单,就是返回首和尾。比如下面的话就是返回w和a
2.4修改
2.4.1operator+=
从下面的图,我们就可以看出,+=可以增加字符也可以增加字符串和string对象,且是尾插
2.4.2push_back
push_back也是尾插,但是一次只能加一个字符,不能加字符串
2.4.3append
string a1 = "wdawdawda"; string a2 = "wwwwwww"; a2.append(a1); //a2:wwwwwwwwdawdawda //string& append (const string& str); //将一个现有的string对象,进行尾插 string a3 = "gggg"; a3.append(a1, 3, 10); //a3:ggggwdawda //将a1下标为3开始的10个字符尾插到a3 string a4 = "hhhh"; a4.append("dwada"); //a4:hhhhdwada //将一个常量字符串尾插进a4 string a5 = "jjjj"; a5.append("wdadadwd", 2); //a5:jjjjwd //将常量字符串的前2个字符尾插进a5 string a6 = "kkkk"; a6.append(10, 'd'); //a6:kkkkdddddddddd //将10个d字符尾插进a6 string a7 = "lll"; a7.append(a6.begin(), a6.end()); //a7:lllkkkkdddddddddd //给定一个string对象左闭右开的区间,将这个区间的字符尾插进a7
2.4.4assign()
string a1 = "wdawdawda"; string a2; a2.assign(a1); //a2:wdawdawda //注意是替换,不是追加 string a3; a3.assign(a1, 2, 3); //a3:awd //从a1下标2的位置开始,将3个字符替换掉a3 string a4; a4.assign("sdadwa"); //a4:sdadwa string a5; a5.assign("dwadwadwa", 2); //a5:dw //将常量字符串的前2个字符替换进a5 string a6; a6.assign(5, 'd'); //a6:ddddd string a7; a7.assign(a1.begin(), a1.end()); //a7:wdawdawda //用迭代器给个左闭右开的区间,在这区间内的字符替换进a7
2.4.5insert()
string a1 = "wdawdawda"; string a2 = "wq"; a2.insert(1, a1); //a2:wwdawdawdaq //在a2的1下标前将a1的内容插入进去; string a3 = "wq"; a3.insert(1, a1, 3, 2); //a3:wwdq //在a3的1下标前将a1的3下标开始的2个字符插入进去; string a4 = "wa"; char a[10] = "dwadwa"; a4.insert(1, a); //a4:wdwadwaa //可以将常量字符串或者字符数组插入到a4的1下标前 string a5; a5.insert(0,"dwadwad", 3); //a5:dwa //将常量字符串或字符数组的前3个字符插入进a5的0下标前 //注意对于这个函数来说,如果被插入字符串是空字符串, // 只能用0下标,否则会报错 string a6 = "wddcw"; a6.insert(2, 10, 'd'); //a6:wddddddddddddcw //将a6的2下标前插入10个d字符,注意这个2下标可以用迭代器替换 /*string::iterator h = a6.begin(); a6.insert(h, 2, 'f');*/ //a6:ffwddddddddddddcw string a7 = "dwada"; string::iterator h = a7.begin(); a7.insert(h, 'g'); //a7:gdwada //在迭代器所指向位置的前面插入一个字符g string a8 = "dwadwada"; string::iterator b = a8.begin(); b++; a8.insert(b,a1.begin(), a1.end()); //a8:dwdawdawdawadwada //将b这个迭代器指向的下标前面插入一个区间内的字符,用迭代器来标识区间
2.4.6.erase()
string a1 = "fwagdwa"; a1.erase(2, 3); //a1:fwwa //将2下标开始3个字符删除 //注意,第一个参数默认不给就是从0开始删,第二个参数不给,默认就是最大值 //也就是第一个参数下标开始有多少删多少。 string a2 = "Dwyktkra"; string::iterator h = a2.begin(); h++; a2.erase(h); //a2:Dyktkra //删除迭代器指向的字符 a2.erase(a2.begin(), a2.end()); //删除这个区间内的字符,用迭代器标识区间
2.4.7replace()
偷个懒,上面那些打的好累,反正大家看了上面那些,大概也懂了,我这边直接照着函数表说了。(这个表是扒string::replace - C++ Reference (cplusplus.com))
1:从被替换的(string)字符串的pos位置开始,len个字符都被替换成str,(注意这个len是针对被替换的,实际上str字符串替换进去是全部放进去的)
可以用迭代器替换第一个和第二个参数
2:跟第1个的区别就是,可以只将str的subpos下标开始的sublen长度的字符替换进去
3:跟第1个的区别就是,str可以是常量字符串或字符数组(第一个是针对string的)
同理,第一个和第二个参数可以替换成迭代器
4:跟第3个的区别就是,只把s的前n个字符替换进去,同理,第一个和第二个参数可以替换成迭代器
5:跟第1个的区别是,替换进去的是n个字符c,同理第一和第二参数可替换迭代器
6:用迭代器构成的区间内的字符替换进第一个参数(这个参数是被替换字符串的开始被替换的起始位置),直到第二个参数(这个迭代器是被替换字符串的最后被替换的位置)这个区间。
2.4.8swap()
string a1 = "ggdwdwa"; string a2 = "ff23"; a2.swap(a1); //a1:ff23 //a2:ggdwdwa //简单的替换
2.4.9pop_back()
这个函数就是尾删,把字符串的最后一个字符删除
2.5操作
2.5.1c_str()
返回一个跟string字符串等效的字符数组的首元素指针
就是把string转换成字符数组,注意,是这个字符数组是不能修改的,但有\0终止符
2.5.2data()
这个跟c_str的区别是,这个没有\0终止符
2.5.3copy()
将当前字符串(string)从pos下标开始的len个字符复制到s字符数组里。(从s字符数组的第一个位置开始覆盖,注意是逐个覆盖,所以s字符数组可能还会保留几个字符)
2.5.4find()第一条,从当前字符串的pos位置开始找str字符串(string),找到了返回找到的这个字符串位置的第一个下标,比如fawfafwa,找w,从0开始找,返回的就是2下标。
如果没有找到返回-1,pos默认是0,可以自己给
第二条,区别就是要找的字符串不一定是string类型,也可以是字符数组或者常量字符串
第3条,
可以看到区别,第3个参数可以决定究竟匹配多少个字符,比如只匹配一个,那么第一个f就会返回,匹配2个,第二个f才返回(因为第二f开始才是fa)
第4条,找c这个字符,从pos位置开始找。
2.5.5rfind()
跟find的区别就是,rfind是从后往前找
2.5.6substr()
string a1 = "dwffafwa"; string a2 = a1.substr(2, 3); //a2::ffa //从a1的2下标开始取3个字符,返回一个临时的string对象(包含 // 这3个字符),通过拷贝构造,将这个临时的string对象 // 赋给a2 cout << a1.substr(2, 3); 注意,可以直接输出
string substr (size_t pos = 0, size_t len = npos) const;不给第一个参数,默认就是0下标,不给第二个参数默认就是最大值,也就是第一个参数开始的全部
2.5.7find_first_of(),find_first_last_of(),find_first_not_of(),find_last_first_not_of()
string a1 = "dwffafwa"; int a = a1.find_first_of("dwwfg"); //这个从前往后在a1里面找dwwfg中任意一个字符,只要在a1里面找到了dwwfg任意一个 //就直接返回 //find_last_of就是从后往前找 //加not的两个,就是反下,找到不是dwwfg里的就返回
2.5.8compare()
a1.compare("dwda")
如果a1的字典序比字符串里先,返回负数,后则正数,相等返回0(逐个字符比较)
1:当前字符串(string)与str比较大小
2:从当前字符串(string)的pos位置开始len个字符,与str比较(可以与整个str比较,也可以与str的subpos位置开始的sublen个字符比较)
3:从当前字符串(string)的pos位置开始len个字符,与常量字符串或字符数组比较
4:与第3个的区别是,是当前字符串(string)与常量字符串或字符数组的前n个比较
因为string还函数重载了==,所以compare一般用处不多。
2.5.9运算符重载
很冗余,不多说了,xdm平时尽管用就是了,报错了再看看这个原型图,
+如下,不多说了,平时尽管加,报错了再看
2.5.10其他
swap和<<,>>,getline
void swap (string& x, string& y); 交换
<<和>>不需要了解太多,这个重载主要是用来输入输出的
istream& operator>> (istream& is, string& str); ostream& operator<< (ostream& os, const string& str);
getline
istream& getline (istream& is, string& str, char delim); 例子 string a1; getline(cin, a1); cout << a1; 这个是读取一行的 delim一般没什么用,这里不多解释,想了解可以看c++的网站 istream& getline (istream& is, string& str);
3.模拟string
#pragma once #include<assert.h> namespace test { class string { public: //string() // :_str(new char[1]) // , size(0) // ,capacity(0) //{ // _str[0] = '\0'; // //注意,千万不能把str初始化为空指针 // //否则,假如我们对一个空字符串输出,那就会报错 // //因为输出一个字符串,那肯定要对_str先解引用 // //从而得到理论上的第一个字符,然后开始输出,直到遇到 // //\0,但是,如果_str存的空指针,那么第一步就会出错 // //也就是对空指针解引用,因此我们要开辟一个空间 // //存\0; //} //考虑到简洁性,我们整合到下面 //构造函数 string(const char *str="")//注意,""自带\0, { _size = strlen(str); _capacity = _size; _str = new char[_size + 1]; strcpy(_str, str); } //拷贝构造 //string(const string& s) //{ // _str = new char[s._capacity + 1]; // strcpy(_str, s._str); // _size = s._size; // _capacity = s._capacity; //} string(const string& s) { string tmp(s._str); swap(tmp); //注意,这里拷贝构造,我们采用深拷贝的形式 //this->swap(tmp); } //析构函数 ~string() { delete[] _str; _str = nullptr; _size = 0; _capacity = 0; } //一些函数我们要用const修饰,因为有时候传的是const类型的char, //这样的话权限放大,就报错了 //返回一个字符数组的指针 const char* c_str() const { //注意,返回值是const,否则的话就可以绕过string这个包装 //直接对_str这个字符数组操作了。 return _str; } //返回现有的字符数 size_t size() const { return _size; } //这里重载了两个版本,一个可读可写,一个只读 const char& operator[] (size_t pos) const { assert(pos <= _size);//这里允许引用\0 return _str[pos]; } char& operator[] (size_t pos) { assert(pos <= _size);//这里允许引用\0 return _str[pos]; } 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的迭代器可以这样写,但链表等会不一样 //扩容 void reserve(size_t n) { if(n>_capacity) { char* a = new char[n + 1];//多个给\0 strcpy(a, _str); delete[] _str; _str = a; _capacity = n; } } void resize(size_t n, char c = '\0') { if (n > _capacity) { char* a = new char[n + 1];//多个给\0 strcpy(a, _str); delete[] _str; _str = a; for (int i = _size; i < n; i++) { _str[i] = c; } _str[n] = '\0'; _size = n; _capacity = n; } else if (n > _size && n < _capacity) { for (int i = _size; i < _capacity; i++) { _str[i] = c; } _str[n] = '\0'; _size = n; } else if(n<_size) { _str[n] = '\0'; _size = n; } } //尾插一个字符 void push_back(char ch) { //判断空间大小,为0先给4 if (_size == _capacity) { size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2; reserve(newcapacity); } _str[_size] = ch; _size++; _str[_size] = '\0';//记得给\0 } //尾插字符串 void append(const char* str) { size_t len = strlen(str); //判断空间大小 if (_size + len > _capacity) { reserve(_size + len); } strcpy(_str+_size, str); _size += len; } //运算符重载 string& operator+=(char ch) { push_back(ch); return *this; } string& operator+=(const char* str) { append(str); return *this; } string& operator=(string s) //注意,不能加引用,否则就不是赋值,而是交换了 { // if (this != &s) // { // char* tmp = new char[s._capacity + 1]; // strcpy(tmp, s._str); // delete[] _str; // _str = tmp; // _size = s._size; // _capacity = s._capacity; // } // return *this; swap(s); return *this; } //pos位置头插一个字符 void insert(size_t pos,char ch) { //断言pos合法 assert(pos <= _size); //空间大小判断 if (_size == _capacity) { size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2; reserve(newcapacity); } //end如果是size_t,那么end就不能小于0了,这样 //如果pos是0,那就死循环了 int end = _size; //同样,如果强转int,那么pos本身是size_t //比较大小时会发生隐式类型转换,那end还是会变成size_t //还是死循环 while (end >= (int)pos) { //如果不强转 //end>pos //_str[end]=_str[end-1]; _str[end + 1] = _str[end]; end--; } _str[pos] = ch; _size++; } //pos位置头插一段字符串 void insert(size_t pos, const char* str) { assert(pos <= _size); //空间判断稍微改下 size_t len = strlen(str); if (_size + len > _capacity) { reserve(_size + len); } int end = _size; //+1变+len while (end >= (int)pos) { _str[end + len] = _str[end]; end--; } //strcpy会拷贝\0,所以用strcnpy strncpy(_str + pos, str,len); _size += len; } //删除字符 void erase(size_t pos, size_t len = npos) { //断言pos合法 assert(pos < _size); //数组的删除只需直接变size,另外给个\0 //注意,len==npos不能缺,因为npos是size_t也就是 //无符号整型unsigned int的最大数(给是给-1,但转过来就是最大数) //最大数不能再加了,所以只能直接判断 if (len == npos || pos + len >= _size) { _str[pos] = '\0'; _size = pos; } else { strcpy(_str + pos, _str + pos + len); _size -= len; } } //交换字符串 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) { 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 char* ptr = strstr(_str + pos, str); if (ptr == nullptr) { return npos; } else { return ptr - _str; } } //取子串 string substr(size_t pos, size_t len = npos) { assert(pos < _size); size_t end = pos + len; if (len == npos || len + pos >= _size) { end = _size; } string str; str.reserve(end - pos); for (size_t i = pos; i < end; i++) { str += _str[i]; } return str; } void clear() { _size = 0; _str[0] = '\0'; } size_t capacity()const { return _capacity; } bool empty()const { return !(_size); } bool operator<(const string& s) { if (strcmp(_str, s._str) < 0) { return 1; } return 0; } bool operator<=(const string& s) { if (strcmp(_str, s._str)<=0) { return 1; } return 0; } bool operator>(const string& s) { if (strcmp(_str, s._str) > 0) { return 1; } return 0; } bool operator>=(const string& s) { if (strcmp(_str, s._str) >= 0) { return 1; } return 0; } bool operator==(const string& s) { if (strcmp(_str, s._str) == 0) { return 1; } return 0; } bool operator!=(const string& s) { if (strcmp(_str, s._str) != 0) { return 1; } return 0; } private: //给定缺省值,以免上面构造函数遇到一些编译器不初始化内置类型的问题 //存储字符串 char* _str = nullptr; //当前有效字符个数 size_t _size = 0; //可容纳的有效字符最大个数 size_t _capacity = 0; //是一个静态全局变量,或者说是针对string类的全局变量 //平时用来确认返回值的 const static size_t npos = -1; }; ostream& operator<<(ostream& out, const string& s) { for (char ch : s) { out << ch; } //范围for会根据迭代器,自动开始遍历。 //关于流提取重载更多详细内容,可以看我《类与对象》文章 return out; } istream& operator>>(istream& in, string& s) { s.clear(); char buff[128]; char ch = in.get(); int i = 0; while (ch != ' ' && ch != '\n') { buff[i++]= ch; if (i == 127) { buff[i] = '\0'; i = 0; s += buff; } ch = in.get(); } if (i > 0) { buff[i] = '\0'; s += buff; } return in; } void print_string(const string& s) { /*for (size_t i = 0; i < s.size(); i++) { cout << s[i] << " "; }*/ string::const_iterator h = s.begin(); while (h != s.end()) { cout << *h << " "; h++; } cout << endl; } void test_string() { string a1 = "dwadwa"; string a2 = "dwadwa"; if (a1 <= a2)cout << 1; } }
4.其他
4.1编码
string是针对utf-8编码,u16是针对utf-16,u32是针对utf-32,wstring是针对GBK(windows系统一般都是用GBK编码,linux是Utf-8).
不同编码对各种语言(比如中文、英文等)的存储方式,都有所不同。
具体这里不说了,也说不完,网上相关文章也很多。
因为有很多编码,所以string是先设计模板,再产生不同编码的string。我们平时用utf-8的string
4.2引用计数、写时拷贝
关于string类对象的大小,是
char* _str = nullptr; size_t _size = 0; size_t _capacity = 0; 上面一共12个字节,下面的const类型不计算,是静态全局变量,不计算在内。 而实际上我们用sizeof算出来是28字节(vs、window环境,32位),是因为,库里的string 针对一些字符串较短的,会直接采用一个字符数组存 buf[16]; 短的直接存这个数组里,大于15个字符的才用str存 因此是12+16=28 const static size_t npos = -1; 但是linux环境是8字节(64位) 因为linux下string是类似这样的 char *_str=nullptr; const static size_t npos = -1; 也就是说,除了这个静态外,只存了个指针,又因为是64位,所以指针占8字节
而linux环境下string才用引用计数,计算有多少个对象指向了这个字符数组(我上面_str指针指向的那个字符数组),也就是有多少个指针指向了这个字符数组,因为浅拷贝会出现析构两次(因为有两个string对象的里的指针都指向同一个字符数组),所以利用这个,在析构的时候,如果引用计数是大于1的,那么只是这个数减1,直到是1的时候,再调用析构,这样就不会出现析构2次了。
除了引用计数,还引用了写时拷贝。因为引用计数的存在,会出现多个指针指向同一个字符数组,这样如果解引用了其中一个指针修改了字符数组,那么用其他指针解引用得到的字符数组也会改变,这样就会出事,写时拷贝就是当你要修改字符数组内容的时候才会采用深拷贝的方式,如果不改就皆大欢喜(也就是继续保持多个指针指向同一个字符数组的状态)
!!!!注意,对于现有字符长度,可容纳字符长度,引用计数,这三个东西都是跟字符串本身存在一起的,所以sizeof不会计算这3个。