一.string简介
string其实是一个存储字符的顺序结构,也就是字符数组??
从文档可以看出来,string是typedef出来的,它其实是basic_string<>这个类模板显式实例化为basic_string
basic_string还实例化除了其他的类?
这些类的区别在于使用的编码不同
- string-> UTF8
- u16string->UTF16
- u32string->UTF32
- wstring->宽字符
string的其他介绍可查看该文档?string - C++ Reference
二.string的使用
1.constructor
C++98提供了7个构造函数,只需要掌握重要的即可
1.1无参构造(默认构造函数)
构造一个空的string对象,长度和容量都是0.
string s1;
1.2拷贝构造
用一个已经存在的对象,初始化一个正在创建的对象。这两个对象有着相同的内容、长度、容量
string s1;
string s2(s1);
1.3带参构造
用一个以\0为结尾的字符串构造出一个string对象
string s3("hello world");
前三个都是需要重点掌握的构造函数,剩余的构造函数只需知道即可。?
string (const string& str, size_t pos, size_t len = npos);获取str内容的一部分,从pos位置,长度为len个字符
长度len带有缺省值,如果不传长度,就会默认从pos位置拷贝到字符串结束。
npos是string类的一个const静态成员,值是整型的最大值。
如果传的len比当前内容的长度还长,也会默认从pos位置拷贝到字符串结束。
string (const char* s, size_t n);获取字符串s的前n个字符
n大于字符串长度时,只会提取该字符串\0之前的内容
string (size_t n, char c);?用n个c字符构造string对象
templatestring (InputIterator first, InputIterator last); ?用一个迭代器区间初始化一个string对象
2.destructor?
string对象会自动调用其析构函数,所以不需要管
3.流插入和流提取
string类重载了流插入和流提取
4.sring对象的访问
4.1operator[]
string重载了[],可以像数组那样用下标访问数据。
它返回的是该位置元素的引用,意味着可以修改
4.2其他的访问方式?
string s1("hello world");
cout << s1[0] << endl;
s1[0] = 'x';
cout << s1 << endl;
string s2("hello world");
cout << s2.at(0) << endl;
cout << s2.front() << endl;
cout << s2.back() << endl;
at的作用和[]的作用相同,都是返回指定下标位置元素的引用
区别:1、[]越界会报错,at越界会抛异常;2、当下标等于length时,[]返回\0,at会抛异常
front是返回string对象的第一个字符
back是返回string对象的最后一个字符(\0之前的字符)
注意:空的string对象不能调用front()、back()
?5.string对象的遍历
通过下标遍历,size()是string的成员函数,返回的是string对象的长度
int i = 0; for (; i < s1.size(); i++) { cout << s1[i] << " "; }
通过迭代器遍历
string::iterator si = s1.begin(); while (si != s1.end()) { cout << *si << " "; ++si; }
?可以将迭代器理解成类似于指针的东西,begin()会返回一个指向对象第一个位置的迭代器,end()会指向对象的最后一个位置的下一个位置的迭代器
范围for遍历
for (auto e : s1) { cout << e << ' '; }
范围for是一种自动推导类型,自动遍历,自动判断结束的一种遍历方式,他底层其实就是迭代器。?
auto是一个关键字,用来自动推导类型,当我们知道类型时,也可以显示写类型
for(char e : s1)
在范围for中,e只是s1中每一个字符的拷贝,e的修改不会影响s1本身,如果想要修改,就加上引用
for(auto& e : s1)
6.iterator?
iterator——迭代器,是stl的六大组件之一。虽然string出现的比stl早,但是string在形式上也是一种容器。
而迭代器是所有容器通用的遍历方式,因为不是所有的容器都支持下标访问。
那么所有的容器也都可以使用范围for来遍历,因为范围for底层就是迭代器。
迭代器有可能是原生指针,也有可能不是,但可以借助指针的逻辑来理解迭代器。
迭代器一共有四种:
- iterator->普通迭代器,返回的是元素的引用,可以修改
- cosnt_iterator->const迭代器,只可读,不可修改
- reverse_iteraotr->反向迭代器,逆序遍历
- const_reverse_iterator->const反向迭代器,逆序遍历,且不可修改
string s1("hello world");
cout << s1 << endl;
string::iterator si = s1.begin();
while (si != s1.end())
{
*si += 2;
cout << *si << " ";
++si;
}
cout << endl;
string::reverse_iterator rsi = s1.rbegin();
while (rsi != s1.rend())
{
cout << *rsi << " ";
++rsi;
}
cout << endl;
const string s2(s1);
string::const_iterator csi = s2.begin();
while (csi != s2.end())
{
*csi += 2;//不可修改
cout << *csi << " ";
++csi;
}
cout << endl;
需要注意的是,反向迭代器遍历时不是--,依旧是++?
?普通迭代器也实现了const版本,但是C++11后,重新设计了一个cbegin()和cend(),const对象也可以调用这个迭代器,与普通版本的const作用相同。
7.auto?
在C++11中,auto是一个类型指定符,它可以自动推导类型
auto a = 1;//int
auto b = a;//int
auto c = 1.1;//double
auto d = 'x';//char
typeid().name()可以获取变量的类型?
当一个类型使用auto时必须初始化?
auto e;
不可以用auto定义数组
auto arrary[] = {1,2,3};
?auto不可以做函数参数,但可以做返回值(谨慎使用)
void func(auto a) {}
auto不能做返回值可以认为是一个规定,即使给了缺省值也不行?
auto func2() { return 1; }
?auto做返回值时,其类型由返回的值来确定。
但auto做返回值需要谨慎使用,遇到函数链式调用时,不方便推断其类型:
当然为了解决这种问题,我们可以直接用auto来接收返回值:
auto a = func4();
auto真正的作用其实是为了省略那些长类型:?
string::const_reverse_iterator crsi = s.crbegin();
auto crsi = s.crbegin();
长类型写起来麻烦费时间,利用auto就可以很快速的写完。但是会牺牲掉一些可读性。?
8.string的容量操作
string其实就是一个可动态增长的字符数组,所以我们猜测其结构如下:
class string
{
private:
char* _str;
size_t size;
size_t capacity;
};
_str指向的是堆上面的数组,size是数组的有效长度,capacity是其容量。当size==capacity时,就需要进行扩容操作,以此来实现动态增长的效果。?
8.1size()/length()
返回string对象中字符串的长度,单位是字节。
size()和length()两者都是该效果,没有任何区别。
对于字符串来说,length这个接口就很符合。但是对其他容器来说,长度这个接口就不再合适了。比如map。所以为了与后续的stl统一,就给string引入了size这个接口。?
8.2capacity()
返回已分配的空间大小,也就是其容量,要与size区分开。size是当前有效字符的长度,capacity则是当前string对象可以容纳的字符的个数。
?分析VS环境与g++环境下的扩容:
VS:
观察到,当一个空string时,就已经有了容量,这和我们推测的结构不同。实际上,在VS中,有一个buffer数组,大小是16,当字符串小于16时,会存到buffer中,当超过时,才会存到堆上。
class string
{
private:
char buffer[16];
char* _str;
size_t _size;
size_t _capacity;
};
我们可以通过sizeof来验证:?
16+4+4+4 = 28;?
?容量是之所以是15是因为没有包含\0,所以在VS下,第一次扩容时2倍i,接下来的都是1.5倍。
g++:?
g++环境下,空string的容量就是0,所以和我们最先推测的结构类似?,而其扩容遵循严格的2倍扩容。
8.3.reserve(size_t n)
reserve会请求容量的变化,但是不会影响字符串的长度,也就是不会引起size的变化
resereve有以下三种情况:
1、n > capacity,一定会扩容,使capacit到达n或者比n还大
2、size < n < capacity,有可能会发生缩容,也有可能不会,取决于编译器的实现
3、n < size,一定不会缩容,因为reserve一定不会对原字符串产生影响
//测试代码
void test_string7()
{
string s("hello worldxxxxxx");
cout << "size:" << s.size() << endl;
cout << "capacity" << s.capacity() << endl << endl;
s.reserve(100);
cout << "size:" << s.size() << endl;
cout << "capacity" << s.capacity() << endl << endl;
s.reserve(20);
cout << "size:" << s.size() << endl;
cout << "capacity" << s.capacity() << endl << endl;
s.reserve(10);
cout << "size:" << s.size() << endl;
cout << "capacity" << s.capacity() << endl << endl;
}
VS下结果: g++下结果: ?g++环境下,当 n resize是对size进行请求,使其改变。它会使size变化到n: n size n>capacity:不仅会将size扩大到n,用指定字符填充,还会进行扩容 如果没有指定字符的话,就会用\0填充 清空当前对象的内容,但是不引起capacity的变化。 VS和g++下都遵循这一原则 判断当前string对象是否为空 shrink_to_fit是C++11提供的一个接口,其功能类似于reserve的size push_back() 在字符串后面插入一个字符 append() 注意:在插入一个string对象的一部分时,如果没有指定长度,会默认将string对象的pos位置开始到结束都插入到原字符串后面;如果指定的长度大于剩余部分的话,也会将剩余的所有字符全都尾插到后面 ?operator+= 尾插一个string对象、一个字符串、一个字符? 因为+=非常形象,所以使用的最多? insert就是在指定位置上插入数据。 insert设计了非常多的接口,?但是因为insert涉及到数据的挪动以及扩容等,效率较低,所以使用的也不是很多 用指定内容替代原内容 assign也设计了多个接口,但assign依旧不常用,需要用时查文档即可 1、删除指定位置开始的n个字符,如果不指定长度或者长度大于剩余字符,都会将剩余字符全部删除 2、删除一个指定迭代器位置上的字符 可以借此来实现头删 3、删除一个迭代器区间 因为erase删除数据也要挪动数据,而且对于字符串来说经常都是只加不减,所以这个接口也不常用? replace就是用指定的内容提供替换指定位置上的元素 replace可以和find等查找接口结合使用,达到替换指定元素的目的 下面代码的功能就是将字符串中的所有空格都替换成%%。? 因为这里是将一个字符替换成了两个字符,不仅需要扩容还需要挪动数据。但是如果遇到空格比较多且连续的话需要频繁扩容,效率低? 可以采取另一种方式来实现替换:创建一个空string,然后遍历原字符串,遇到非空格就加等该字符,遇到空格就加等%%。?而且我们还可以提前开好空间,避免多次扩容 swa()就是交换两个string对象 pop_back()是C++11提供的一个尾删接口 find 从指定位置开始查找一个string对象/字符串/字符出现的第一个位置,如果找到了就会返回该位置的下标,如果没找到就会返回npos。 ?rfind 和find的功能类似,只不过是倒着查找第一个出现的位置。 当遇到需要查找文件的后缀时,且该文件不只一个‘.’。如果采用find查找的话就会出错,这时候从后向前查找更好。 ? find_first_of ?从字符串中找出第一个与指定字符串中的任何一个元素 就如这段代码,我们可以将指定字符串中的abcdef都替换成*。? find_last_of? 与find_first_of类似,其实就是倒着开始找。 这个接口可以用来实现路径与文件名的分离。而在windows操作系统和Linux操作系统中,Windows的文件分隔符是\,而Linux是/。为了同时区分这两个系统下的文件,我们就可以采用find_last_of来实现。? ?因为在程序中\是转义字符,所以不会单独存在,为了表示\,所以需要用\\来表示。 find_first_not_of和find_last_not_of 它们的功能就是上面两者的反面,返回第一个不是指定字符里的任意一个的字符 下面代码的意思就是将abcdef之外的字符全部替换成*。? ?返回堆上存储字符串的空间的地址 这个接口的实现,主要是为了兼容C语言。使C++程序可以调用C语言的接口。 ?从string对象拷贝内容到指定字符串中 生成一个字串 重载+是为了可以实现多种相加,不只局限于第一个参数是string对象。但接口设计的过于冗余且+的使用场景也不多。 getline是string对象专用的一种流提取方式。 因为以往使用cin的话,会以空格或者换行为分隔符,但是如果一个字符串中含有空格,用cin输入的话,就会丢失空格之后的内容。 而getline是以换行符为分隔符,遇到空格不会停止读取,就不会丢失内容。 在VS2022环境下实现string时,为了简单实现,就不再设计buffer字符数组用来存储长度小于15的字符串,且只实现些重要接口 模拟实现string的结构 为了避免与库里面的string冲突,所以将模拟实现的string定义到命名空间中。我们这里实现声明与定义到两个文件中:string.h string.cpp。 npos是一个静态成员,属于类,所以要在类外定义,放在.cpp文件中 不同文件的同一命名空间会合并。 首先实现无参构造和有参构造 在实现无参构造时,不能直接给_str赋空指针,也要开一个空间给\0,否则当打印由无参构造创建的对象时会发生对空指针的解引用 实现有参构造时,要注意多来一个空间给\0,因为size和capacity都是不包含\0的。? 我们也可以将两者合并,写一个带缺省值的有参构造: 我们给了一个空的常量字符串,而常量字符串结尾会自带一个\0,所以上面的写法就涵盖了之前的两种写法。 迭代器iterator其实是一种封装的体现。每一个容器都有iterator,但是每一个容器的底层的实现又是不一样的。而且iterator的实现也是不固定的,有可能时原生指针,也有可能是其他的机制来实现的。 所以每一个容器中的iterator其实是typedef出来的,实现一个统一的接口,让每一个容器的遍历方式都变得相同。 string的模拟实现过程中,可以用原生指针来实现迭代器,因为可以发现,在string中,迭代器其实是在模拟指针的行为。 迭代器实现后,范围for也就可以使用了。这也就从侧面反映出范围for的底层是迭代器。? 对于string的访问来说,没必要实现其他的接口,只需要实现该接口即可。 push_back及reserve 在每一次插入前都要先判断是否还有容量,如果容量满了就需要扩容? 扩容的操作交给reserve来实现,每次开空间时都要多开一个空间给\0。因为在VS环境下,n append 实现append时,不能直接2倍扩容,如果s很长,扩大2倍可能还是不够,所以就要判断目前的长度加上s的长度与2*capacity谁大,来判断扩多少。? operator+= 实现+=时,可以直接复用前面的尾插操作。? 在指定位置插入一个字符 在指定位置插入一个字符串 实现substr时,这里返回了一个临时变量,出了作用域就销毁了,实际上会返回由这里临时变量拷贝出来的中间变量,但是这里默认实现的是浅拷贝。而且string类里面涉及到了资源的申请,实现浅拷贝的话,就会导致对同一块空间多次释放。 所以这里还需要实现拷贝构造来实现深拷贝。? 拷贝构造 赋值运算符重载 在实现流提取时,为了避免多次扩容,可以开一个数组先用来存储数据,等数组满了在插入string中,减少扩容次数。 拷贝构造现代写法 现代写法与传统写法的区别就在于:传统写法是自己开空间自己挪动数据;现代写法则是将这些过程都交给tmp去干。 我们用我们用s的内容去构造一个string对象,此时tmp就有和s一样大的空间和内容。此时我们再交换tmp和this的内容、size和capacity即可。 需要注意的是,tmp交换之后就得到this的值,而this还没有创建是随机值,tmp出了作用域就会销毁,此时就会导致对野指针进行销毁。为了避免这样,我们可以在声明私有成员变量时,给出缺省值 赋值运算符重载的现代写法? 之前依旧时自己开空间拷贝数据,现代写法用一个tmp来帮忙实现之前需要自己干的事情。对于赋值运算符重载来说,s1要先释放自己之前的空间,再开一个和s一样大的空间和内容。 先用tmp创建一个和s一样的string对象,然后交换s1和tmp,此时s1就完成了目的,然后在该函数结束之后,tmp就会销毁,而它现在指向的又是s1之前的空间,刚好销毁了,不会造成内存泄露的问题。 ?现代写法可以再进行简化:此时传参不再传引用,而只是s的拷贝,此时直接交换s1和s,s1就得到了目标内容,而s得到了s1需要释放的空间,而s又是临时变量,出了作用域会销毁,会带走s1之前的空间,依旧不会造成内存泄露。 swap 在实现现代写法时,涉及到了交换,这个交换是我们自己实现的,为什么不直接调用库里面的swap呢? 库里面swap实现成了模板,当我们直接调用时,在该函数中会发生三个深拷贝。而我们用其交换自定义类型时就不会产生太多消耗。 但其实并不需要太多担心,当我们调用库里的swap时,其会调用swap(string& str). 浅拷贝的问题在于: 1、同一块空间析构两次或多次 2、修改一个另一个也会跟着改变 引用计数是指,一开始并不采用深拷贝,而是浅拷贝。只不过此时有一个count变量用来记录有多少个对象指向该空间 ?当我们要析构s2时,不会直接析构该空间,而是让count--,直到count等于0时,才会真正释放该空间。 而当我们要对s2进行修改时,为了不改变s1,此时才会进行深拷贝,让s2指向另一块空间,这时候修改变不会影响s1。 引用计数和写时拷贝很好的解决了浅拷贝出现的问题。但是这在现在已经慢慢退出使用了。 VS2022环境下: 可以看到它们指向的地址并不相同,所以并没有采用引用计数和写时拷贝这种方式 g++环境下: 在g++环境下,我们可以看到,s1和s2指向的是同一块堆空间。 当我们对s2进行修改时,此时就会发生深拷贝,s2不在指向该地址:? 编码其实是在计算机中,值和符号的一种映射关系。在计算机中只能存储010101这样的整数,然而我们需要经常表示字母符号中文等等。 Ascii码值就是一种编码,它将英文中常用的字母符号数字等等用整数来表示。 我们看到的abc在内存中其实就是97、98、99等数字,只不过在内存中是以十六进制存储的。? 而我们现在常用的就是Unicode(统一码、万国码)。它为世界上所有的文字和符号提供一个统一且唯一的编码的标准。 而其中包括了三种编码方式:UTF-8,UTF-16,UTF-32。 最常用的是UTF-8,它是一种变长编码。 UTF-16则都用两个字节来表示 UTF-32?则都用四个字节来表示?在VS2022编译环境下,当n
8.4resize()
8.5.clear()
8.6.empty()
string s("hello worldxxxxxx");
cout << s.empty() << endl;
s.clear();
cout << s.empty() << endl;
8.7shrik_to_fit()
9.string的修改操作
9.1尾插
?在字符串结尾插入一个string对象、string对象的一部分、一个字符串、一个字符串的前n个字符、n个c字符(没指定字符就会默认插入\0)或者一段迭代器区间。
string s3("hello world");
s3 += 'x';
s3 += "abcdefg";
s3 += s1;
?9.2insert()
9.3assign()?
9.4erase()
string s("hello world");
cout << s << endl;
//s.erase(6);
s.erase(6, 100);
cout << s << endl;//hello
//s.erase(0,1);
s.erase(s.begin());
9.5replace()
string s1("hello wrold sunday xxx");
size_t pos = s1.find(' ');
while (pos != string::npos)
{
s1.replace(pos, 1, "%%");
pos = s1.find(' ', pos + 2);//指定从那个位置开始查找
}
string tmp;
tmp.reserve(s1.size());
for (auto e : s1)
{
if (e == ' ')
{
tmp += "%%";
}
else
{
tmp += e;
}
}
9.6swap()、pop_back()
10.string的其他操作
string s("hello.zip.cpp");
size_t pos = s.rfind('.');
string ans = s.substr(pos);
10.1查找
string s("hello.zip.cpp");
size_t pos = s.rfind('.');
string ans = s.substr(pos);
std::string str("Please, replace the vowels in this sentence by asterisks.");
std::cout << str << '\n';
std::size_t found = str.find_first_of("abcdef");
while (found != std::string::npos)
{
str[found] = '*';
found = str.find_first_of("abcdef", found + 1);
}
std::cout << str << '\n';
void SplitFilename(const std::string& str)
{
std::cout << "Splitting: " << str << '\n';
std::size_t found = str.find_last_of("/\\");
std::cout << " path: " << str.substr(0, found) << '\n';
std::cout << " file: " << str.substr(found + 1) << '\n';
}
int main ()
{
std::string str1 ("/usr/bin/man");
std::string str2 ("c:\\windows\\winhelp.exe");
SplitFilename (str1);
SplitFilename (str2);
return 0;
}
std::string str("Please, replace the vowels in this sentence by asterisks.");
std::cout << str << '\n';
std::size_t found = str.find_first_not_of("abcdef");
while (found != std::string::npos)
{
str[found] = '*';
found = str.find_first_not_of("abcdef", found + 1);
}
std::cout << str << '\n';
10.2c_str()
10.3copy()
10.4substr()
?默认会从头开始获取,当不指定长度或者长度大于剩余字符都会获取到字符串结尾。
11.string的非成员函数?
11.1string重载了比较运算符,其比较逻辑与strcmp相同。
11.2operator+?
11.3getline?
三.string的模拟实现?
namespace xsc
{
class string
{
private:
char* _str;
size_t _size;
size_t _capacity;
static const size_t npos;
};
}
namespace xsc
{
static const size_t npos = -1;
}
1.构造函数?
string()
{
_str = new char('\0');
_size = 0;
_capacity = 0;
}
string(const char* s)
{
size_t len = strlen(s);
_str = new char[len + 1];
_size = len;
_capacity = len;
strcpy(_str, s);
}
string(const char* s = "")
{
size_t len = strlen(s);
_str = new char[len + 1];
_size = len;
_capacity = len;
strcpy(_str, s);
}
2.析构函数
~string()
{
delete[]_str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
3.c_str()/size()/capacity
const char* c_str() const
{
return _str;
}
size_t size() const
{
return _size;
}
size_t capacity() const
{
return _capacity;
}
4.迭代器
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;
}
5.operator[]?
char& operator[](size_t pos)
{
assert(pos < _size && pos >= 0);
return _str[pos];
}
const char& operator[](size_t pos) const
{
assert(pos < _size && pos >= 0);
return _str[pos];
}
6.clear()
void clear()
{
_str[0] = '\0';
_size = 0;
}
7.尾插
void string::reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
_capacity = n;
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
}
}
void string::push_back(char ch)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
_str[_size] = ch;
++_size;
_str[_size] = '\0';
}
void string::append(const char* s)
{
size_t len = strlen(s);
if (len + _size > _capacity)
{
reserve(len + _size > 2 * _capacity ? len + _size : 2 * _capacity);
}
_size += len;
strcpy(_str + _size, s);
}
string& string::operator+=(char ch)
{
push_back(ch);
return *this;
}
string& string::operator+=(const char* s)
{
append(s);
return *this;
}
?8.insert
void string::insert(size_t pos, char ch)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
size_t end = _size + 1;
while (end >= pos + 1)
{
_str[end] = _str[end - 1];
--end;
}
_str[pos] = ch;
++_size;
}
void string::insert(size_t pos, const char* s)
{
size_t len = strlen(s);
if (len + _size > _capacity)
{
reserve(len + _size > 2 * _capacity ? len + _size : 2 * _capacity);
}
size_t end = _size + len;
while (end >= pos + len)
{
_str[end] = _str[end - len];
--end;
}
size_t i = 0;
for (; i < len; i++)
{
_str[pos + i] = s[i];
}
_size += len;
}
9.erase
void string::erase(size_t pos, size_t len)
{
assert(pos < _size);
if (len > _size - pos)
{
_str[pos] = '\0';
}
else
{
size_t i = pos + len;
for (; i <= _size; ++i)
{
_str[i - len] = _str[i];
}
}
_size -= len;
}
10.find/substr
size_t string::find(char ch, size_t pos)
{
assert(pos < _size);
size_t i = pos;
for (; i < _size; ++i)
{
if (_str[i] == ch)
{
return i;
}
}
return -1;
}
size_t string::find(const char* s, size_t pos)
{
assert(pos < _size);
char* tmp = strstr(_str + pos, s);
if (tmp == nullptr)
{
return -1;
}
return tmp - _str;
}
string string::substr(size_t pos, size_t len)
{
string ans;
if (len > _size - pos)
{
len = _size - pos;
}
ans.reserve(len);
size_t i = 0;
for (; i < len; i++)
{
ans += _str[i+pos];
}
return ans;
}
//s1(s)
string(const string& s)
{
_str = new char[s._capacity + 1];
strcpy(_str, s.c_str());
_size = s._size;
_capacity = s._capacity;
}
//s1 = s
string& operator=(const string& s)
{
//避免自己对自己赋值
if (this != &s)
{
delete[]_str;
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
11.Cmpare
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);
}
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);
}
12.流插入和流提取
ostream& operator<<(ostream& out, const string& s)
{
for (auto ch : s)
{
out << ch;
}
return out;
}
istream& operator>>(istream& in, string& s)
{
s.clear();
const int N = 256;
char buff[N];
int i = 0;
char ch;
//in >> ch;
ch = in.get();
while (ch != ' ' && ch != '\n')
{
buff[i++] = ch;
if (i == N - 1)
{
buff[i] = '\0';
s += buff;
i = 0;
}
//in >> ch;
ch = in.get();
}
if (i > 0)
{
buff[i] = '\0';
s += buff;
}
return in;
}
四.现代写法?
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
//s1(s)
string(const string& s)
{
string tmp(s.c_str());
swap(tmp);
}
//s1 = s
string& operator=(const string& s)
{
string tmp(s.c_str());
swap(tmp);
return *this;
}
?
//s1 = s
string& operator=(string s)
{
swap(s);
return *this;
}
五.引用计数和写时拷贝
六.编码
?它即可以表示字母,也可以表示中文等其他符号。中文通常用2个或者3个字节来表示。不同的符号用不同的字节数来表示。