“朝朝暮暮”
猛戳订阅🍁🍁 👉 [C++详解专栏] 👈 🍁🍁
这里是目录
一、编码
为什么string不能针对char来写,因为编码不同,。char只能表示256个字符。
所以这时候要用模板。
string管理的是一个char的字符串。
u16string:一个字符是*两个字节
u32string:一个字符是四个字节
wstring:叫做宽字符,一个字符占两个字节
1.ASCII码
ASCII码表。是美国设计的。
ASCII码表是:计算机当中存的值,和字符的映射
但是只有256个字符的表示。用char表示
2.unicode编码
也叫做万国码。
Unicode是针对全世界的语言而设计的一种编码。
常见的有utf-8 utf-16 utf-32
3.gbk
gbk是叫做国标码。是针对中文创建的一个编码。其中还涉及台湾的。
计算机上不止有英文,还要有中文,日文等语言。但是ASCII码表不足以表示。
二、string的使用
string是C++标准库的一部分,但是不属于stl。
使用就不多讲了,详细操作多看C++参考网站。
因为你不可以全记住。记不住很正常,可以查。
网站如下。
C++reference
string是typedef出来的,原生叫做basic_string
可以很好兼容char字符。
linux下用的是utf-8,Windows一般用ASCII
string的头文件就是,C++不分头文件不分后缀。
为了和C语言的string.h做区分,所以可以不加.h。
string是属于std命名空间的。所以需要加上using namesapce std。
学习类先学习构造。
先不学C++11的右值引用。
学习string不是要学全部的函数,学习常用的函数就行,常用的函数如下。string的函数100多个,不认识的可以查文档。
1.构造函数
string类一个管理动态增长字符的数组,可以认为这个就是字符数组,因为要兼容C语言,所以结尾是\0。
注意npos是负一。负一的整形是全1,也就是整形最大值。这个参数是默认的,原因为,假如不给参数,给的就是整形最大值。
无参构造函数string()
千万不要在创建对象的时候加括号()。和普通函数的调用不一样。
string s1;
常量字符串构造string(const char* s)
注意:缺省参数不能给nullptr,这样传过去就崩了,只能给双引号也就是空字符串“”.
空字符串后面默认有\0
这个参数是Cstring类型的。
string s1("hello");
底层实现是开一段空间去存放所传递的hello字符串的。
为什么底层要这么麻烦?非要开一个空间,再把东西拷贝过去?
因为这样可以支持后期的增删查改。
假如直接赋值的话后期不支持增删查改。
template<classT>classbasic_string{public:basic_string(const T* str){
size_t len =strlen(str);//开空间,方便增删查改。
_str =new T[len+1];strcpy(_str, str);private:
T* _str;
size_t _size;
size_t _capacity;}
拷贝构造string(const string& s)
string s1(s2);
2.析构函数
析构函数就简单了,析构函数是自动调用的。
3.遍历string
重载operator[] (size_t i)
第一种方式是下表+[]
string 重载下表[].
重载的意义:在于让自定义类型用着像内置类型。
内置类型是原生的指针,直接去内存空间找偏移了多少。
可以像数组一样使用下表访问其中的每个元素
重载函数[]返回的是char的引用。
返回引用的意义:在于可以支持连续赋值。这样字符可以读也可以修改。
假如是自定义类型还可以减少拷贝。
重载const[]。一般重载[]还有一个针对const对象的,const对象只可读不可修改,所以返回值类型也必须是const引用,也要在函数的括号()加上const。
方式一
相当于调的函数s1.operator[] (i);
for(size_t i =0; i < s1.s1.size();i++){
cout << s1[i]<< endl;}
还有一个at()函数和重载[]效果一样,不同点在于at会检查越界,以断言的方式进行检查的。
at一般不用,都是用[].
迭代器遍历
为什么要用迭代器?现阶段学的string底层物理空间连续,可以用数组下表访问,但是当学了map等底层空间不连续的容器,用迭代器非常的方便!因为它是像指针一样的东西。
迭代器是内嵌在string整个类中。vector,list,map通用,都内嵌在各自的类中。
一般使用如下。
方式二
string::iterator it = s1.begin()while(it != s1.end()){
cout <<*it <<" ";}
cout << endl;
迭代器是像指针一样的东西。
具体要看底层的具体实现。
重点:
begin()返回的第一个元素的位置。
end()是指向最后一个数据的下一个位置。
本质上属于左闭右开 [ )
一般迭代器的标准语法都是用不等于。这样的话可以使得vector,map,set更加的使用。因为他们不是连续的结点。地址不连续。就像vector的迭代器是封装的。
迭代器遍历也有注意点。
const迭代器:
假设有个函数func,用普通迭代器遍历不了。
普通迭代器具有读和写的能力。
形式参数是const对象接收的。假如传的是普通对象,就会报错。
voidFunc(const string& s){}
1.const迭代器返回的是const的迭代器对象。这样传参就不会报错了
反向迭代器的const_reverse_iterator也如此。
假如感觉类型太长了。可以用atuo来简便书写。
反向迭代器:
reverse_iterator
注意:
1.反向迭代器是倒着走的。
2.rbegin()在最后一个数据的位置。
3.rend()在第一个数据的前一个位置。
string reverse_iterator rit = s.rbegin()while(rit != s.rend()){
cout <<*rit <<" ";}
cout << endl;
范围for
范围for是C++11的。底层还是迭代器。
实际的使用会被替换成迭代器。
在linux中使用这个语法需要加上-std=c++11
for(auto ch : s1){
cout << ch <<" ";}
cout << endl;
4.插入
reserve()
作用:reserve可以提前开空间只改变容量。但是在vs下不会缩容量。
push_back扩容在g++下面是1.5倍扩容的。
在vs下面是
因为扩容有代价,所以有什么方法可以减少扩容?
这时候就有一个函数就叫做reserve()
resize
作用:开空间+初始化。可以改变size
push_back(size_t i)
append
作用:一般用来在尾部插入字符串。尾插的时候使用。但是日常一般不实用,**一般都用重载后的+=**。
insert
作用:一般在pos位置**插入字符或者字符串(对象)**。
c_str()
作用:返回指向字符串的指针,可以很好地和C语言的接口配合。
find()
作用:从pos(默认为0)位置查找某个字符或者字符串。如果不匹配返回npos。
substr
作用:取得pos位置的字符串。
三、string模拟实现
string 当前不涉及模板,所以比较简单。
1.浅拷贝
strcpy拷贝的时候会把\0也拷贝过去。
浅拷贝:我们不写,编译器会默认生成一个拷贝构造函数,这个拷贝是值拷贝。也叫做浅拷贝。
浅拷贝没有写拷贝构造函数,完成值拷贝。
这样的缺点是:
1.被析构两次
2.改变一个对象,会牵涉到另一个对象。
string s1("hello");//析构报错,同一块内存空间析构两次。
string s2(s1);
实际上内存空间不是你用完后就销毁了。而是使用权被操作系统回收了。回收后可能会分配给其他人用。
内存空间就像是你租的房子,房租到了你只会还一次,而不是还两次,你还的第二次可能是别人的房子。你不能拿着别人的房子还,因为你没有使用权。
2.深拷贝
深拷贝需要自己写。
1.先另开一个和原空间一样大的内存空间。
2.再拷贝数据。
s2(s1)
string(const string& s)//浅拷贝//:_str(s._str):_str(newchar[strlen(s._str+1)])//深拷贝{strcpy(_str, s._str);}
3.赋值重载构造
赋值重载是在两个都已经存在的对象赋值。
有两个问题
1.怕空间不够,空间不够没法拷贝。
2.怕空间太大,浪费了空间。
所以在实际中一般都不会直接拷贝。
1.先把原空间释放
2.再开一个和要赋值对象一样大小的新空间。
3.拷贝数据。
注意,引用返回的原因有两个。
1.出了作用域,对象没有销毁。
2.返回的是一个临时对象,临时对象还需要完成拷贝,而string完成的是深拷贝,代价有点大,所以需要返回引用。
string&operator=(const string& s){delete[] _str;
_str =newchar[strlen(s._str)+1];strcpy(__str,s._str);return*this;}
上面代码有些瑕疵,万一开空间失败了呢?
失败了的话,不仅新空间没有了,我的_str也没了。
所以推荐下面写法。
这是传统的写法。还有一种现代写法。
string&operator=(const string& s){char* tmp =newchar[strlen(s._str)+1];strcpy(tmp,s._str);delete[] _str;
_str = tmp;return*this;}
4.增删查改
reserve
C++扩容不用考虑realloc。
voidreserve(size_t n){if(n > _capacity){char* tmp =newchar[n+1];strcpy(tmp,_str);delete[] _str;
_str = tmp;
_capacity = n;}}
push_back
voidpush_back(char ch){if(_size == _capacity){//开空间,拷贝,释放原来空间,管理新空间/*
char* tmp = new char[_capacity*2+1];
strcpy(tmp,_str);
delete[] _str;
_str = tmp;
_capacity *= 2;
*///复用reservereserve(_capacity ==0?4:_capacity *2);}//赋值
_str[size]= ch;++_size;
_str[_size]='\0';}
append
voidappend(constchar* str){
size_t len = _size +strlen(str);//如果大于当前容量,扩容if(len > _capacity){reserve(len);}strcpy(_str+_size,str);
_size = len;}
operator+=
一般尾部加字符或字符串都是用+=,而不用push_back或者append.
string&operator+=(char ch){push_back(ch);return*this;}
string&operator+=(constchar* str){append(str);return*this;}
resize
有三种情况。假如容量是15,_size是11.
voidresize(size_t n,char ch ='\0'){//如果小于sizeif(n < _size){
_size = n;
_str[_size]='\0';}//如果大于sizeelse{//假如n比size大,比容量小//如果n大于容量,也就是容量不够。if(n > _capacity){reserve(n);}for(size_t i = _size; i < n; i++){
_str[i]= ch;}
_size = n;
_str[_size]='\0';}}
insert
在任意位置前插入数据。
//在pos位置前插入字符
string&insert(size_t pos,char ch){assert(pos <= _size);if(_size == _capacity){reserve(_capacity ==0?4: _capacity *2);}char* end = _str + _size;//将pos位置及其之后的字符向后挪动一位while(end >= _str + pos){*(end +1)=*end;
end--;}
_str[pos]= ch;
_size++;return*this;}
erase
npos是static的,static变量在类内声明,类外定义。
erase有个npos
这个比较有意思。
//erase删除pos位置后的len个字符,len默认为npos
string&erase(size_t pos, size_t len = npos){assert(pos < _size);
size_t n = _size - pos;if(len >= n)//说明pos位置后面的字符全部删除{
_size = pos;
_str[_size]='\0';}else{strcpy(_str + pos, _str + pos + len);
_size -= len;}return*this;}
5.迭代器
string迭代器没啥说的,和指针差不多,当作指针用就行。
需要注意的是:
const迭代器是返回值是const的。
因为返回值是const的,才能避免值被修改。
typedefconstchar* const_iterator;
const_iterator begin()const{return _str;}
6.<<流插入
友元函数是为了访问私有,才设置的。
流插入必须写成全局的,这样out对象才能抢占第一个参数。
一般不用out << s打印字符串。
一般使用如下写法
因为输出字符串打印不出来\0.
ostream&operator<<(ostream & out,const string & s){//使用范围for遍历字符串并输出for(auto e : s){
cout << e;}return out;//支持连续输出}
7.>>流提取
流提取的第二个参数不能加const了,因为需要放字符串到里面。
要注意不能用In >> ch 因为in 还是调用的cin不能获取空格,但是get()是从缓冲区获取字符的。
istream&operator>>(istream& in, string& s){
s.clear();//in >> ch;//这样写不可以。char ch = in.get();//读取一个字符while(ch !=' '&& ch !='\n'){
s += ch;//将读取到的字符尾插到字符串后面//in >> ch;char ch = in.get();//读取一个字符}return in;}
版权归原作者 乔 巴 所有, 如有侵权,请联系我们删除。