在C语言中,字符串和字符串相关的函数是分开的,不太符合面向对象的思想,封装性太低,同时需要用户自己管理底层空间,稍不留神可能就会越界访问;
为此,C++在这些问题的基础上提出了string类;
string类文档介绍;
- string类也是利用模板实例化出来的一个具体类,string本身并不是模板;
- 字符串是表示字符序列的类
- 标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,但添加了专门用于操作
单字节字符字符串的设计特性。- string类是使用char(即作为它的字符类型,使用它的默认char_traits和分配器类型(关于模板的更多信
息,请参阅basic_string)。- string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类,并用char_traits
和allocator作为basic_string的默认参数(根于更多的模板信息请参考basic_string)。- 注意,这个类独立于所使用的编码来处理字节:如果用来处理多字节或变长字符(如UTF-8)的序列,这个
类的所有成员(如长度或大小)以及它的迭代器,将仍然按照字节(而不是实际编码的字符)来操作。
总结:
1.string是表示字符串的字符串类
2.该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。
3.string在底层实际是:basic_string模板类的别名,typedef basic_stringstring;
4.不能操作多字节或者变长字符的序列。
(constructor)
1、string();//无参构造函数,利用空串构造string类对象
2、string(const char*str);//利用C语言字符串构造string类对象;
3、string(const string &s);//拷贝构造函数
4、string(size_t n,char c);//利用n个c字符来构造string类对象
(destructor)
~string();//析构函数
operator=
1、string&operator=(const string&s);//string对象赋值给string对象;
2、string&operator=(const char*str);//C语言字符串赋值给string对象;
3、string&operator=(char ch);//C字符给string赋值;
Capacity:
1、size();//返回字符串长度;
2、length();//返回字符串长度
size与length在底层实现上是一样的,早期的string是不在容器之内的,求字符长度是用的length,多出来一个size主要是为了与后面string加入容器队伍过后名称的统一!
3、capacity();//返回当前的字符数组的容量;
、4、empty();//判断string对象是否为空串;是:true;不是:false;
5、clear();//清除有效字符,size置0,容量不变
6、reserev(size_t n=0);//请求更改容量,若n大于当前已开辟的容量,那么就开辟n个空间(或则更多,vs环境下就是这样,Linux:g++下就是要多少开多少);若n小于等于当前已开辟的容量,编译器不做处理;此操作不会更改size的大小;
7、resize(size_t n,char c=‘\0’)//重新调整字符串有效字符的大小;如果n大于当前size的大小,那么当前对象可能会发生扩容,size=n,新出来的空间利用指定字符c来填充;若n小于当前的大小,那么size=n,将当前对象的有效字符缩短置n个,capacity不变;
iterator:
begin();//返回开头的指针
end();//返回最后一个元素的下一个位置的指针;
我们可以利用迭代器完成字符串遍历的操作;
rbegin();//反向迭代器,返回最后一个元素的指针;
rend();//反向迭代器,返回第一个元素的前一个位置的指针;
反向迭代器也就是反着遍历:
begin/end有重载,rbegin/rend也有重载
主要是重载的const对象;非const对象调用begin/end,rbegin/rebd;const对象调用begin()const/end()const,rbegin()const/rend()const;
当然在C++11中const对象,调用正向迭代器,可以用cbegin/cend;
反向迭代调用crbegin/crend;
按照非const对象与const对象来分:
非const对象调用begin/end,rbegin/rend;
const对象调用cbegin/cend,crbegin。crend;
按照正向迭代器、反向迭代器来分:
begin/end可以被非const对象和const对象调用;
rbegin/rend可以被非const对象和const对象调用;
注意:要注意调用的迭代器类型要匹配;
迭代器总的来说有四种:iterator(非const对象正向迭代器)、const_iterator(const对象正向迭代器);
reverse_iterator(非const对象反向迭代器)、const_reverse_iterator(const对象反向迭代器);
Element access:
operator[](size_t pos);//重载运算符[]//这是利用assert的方式来检查越界
at(size_t pos);//使用at成员函数访问//这是利用抛出异常的方式来检查越界
Modifiers:
operator+=(const string&s);//追加一个string对象
operator+=(const char*s);//追加一个C字符串
operator+=(char ch);//追加一个字符;
append(const string&s);
append(const char*s);
append(size_t n.char c);
push_back(char c);//插入一个字符
pop_back();//删除最后一个字符
insert(size_t pos,const string&s);//pos位置插入一个string对象
insert(size_t pos,const char*s);//pos位置插入C字符串
insert(size_t pos,size_t n ,char c);//pos位置插入n个指定字符;
erase (size_t pos = 0, size_t len = npos)//从pos位置开始删除len个字符;
swap (string& str);//此swap是在string类内部实现的,与模板swap不一样;
我们可思考一下,为什么要在string内部实现实现一个swap呢?模板swap不够我们用吗?
当然不是,两个swap都能用,但是在使用上存在效率问题,string内部的swap效率要高于模板swap;
下面我们通过画图来解释:
String operations:
const char* c_str() const;//将我们的string转换为C字符串
size_t find(const string &str,size_t pos=0);//从pos位置开始寻找str字符串,找到了,返回其所在位置,找不到返回npos;
size_t find(const char *s,size_t pos=0);
size_t find( char ch,size_t pos=0);
Non-member function overloads:
relational operators:
getline();
从istream流获取字符串,以指定字符delim作为分隔符;delim默认是’\n’;
string.h
#pragma once
#pragma warning(disable:4996)
#include
#include
#include
namespace MySpace
{class string//字符串末尾需要存储'\0',但是'\0'不算做_capacity的大小{// friend std::ostream& operator<<(std::ostream& cout, const string& str);friend std::istream& operator>>(std::istream& cin, string& str);public://string()///无参构造//根据STL的表现,string对象会被初始化为一个空串//:_size(0),_capacity(0)//{// _str = new char[1];//这里为什么不用new char?答:虽然语法支持这样做,但是考虑到析构函数的统一写法:delete[],建议用new[]开空间// _str[0] = '\0';//}//对于无参构造函数和有参构造函数,我们可以考虑利用缺省值string(const char* str = ""):_size(strlen(str)){_capacity = _size + 4;//这里可以考虑多开一点,保持capacity不为0;//不一定要多少开开多少//可以多开4个空间_str = new char[_capacity + 1];//这里需要将'\0'的空间考虑进来,但是_capacity不记录'\0'的空间strcpy(_str, str);}//拷贝构造//使用编译器默认的会发生浅拷贝问题string(const string& str):_size(str._size), _capacity(str._capacity){_str = new char[_capacity + 1];strcpy(_str, str._str);}//析构~string(){delete[] _str;_size = _capacity = 0;}//赋值运算符,不能用编译器提供的,编译器提供的是浅拷贝string& operator=(const string& str){if (this != &str){char* tmp = new char[str._capacity + 1];//开辟失败,抛出异常,不会丢失原数据//开辟成功strcpy(tmp, str._str);_size = str._size;_capacity = str._capacity;delete[]_str;_str = tmp;}return *this;}string& operator=(const char* str){if (str != _str){resize(0);*this += str;}return *this;}string& operator=(char ch){resize(0);*this += ch;return *this;}//字符串长度size_t size()const{return _size;}//清理有效字符void clear() {_size = 0;_str[_size] = '\0';}//判断是否是空串bool empty()const{return _size == 0;}//获取当前的有效存储容量size_t capacity()const{return _capacity;}//[]访问//1、非const对象char& operator[](size_t pos){assert(pos < _size);return _str[pos];}//2、const对象const char& operator[](size_t pos)const//返回值是没必要加const的,因为const对象的const是修饰_str的不是修饰*_str的{assert(pos < _size);return _str[pos];}//比较运算符bool operator>(const string& str)const{size_t l1 = 0;size_t l2 = 0;while (l1 <= _size && l2 <= str._size){if (_str[l1] == str._str[l2]){l1++;l2++;}else if (_str[l1] > str._str[l2])return true;elsereturn false;}return false;}bool operator==(const string& str)const{if (_size != str._size)return false;else{size_t l1 = 0;size_t l2 = 0;while (l1 <= _size && l2 <= str._size){if (_str[l1] == str._str[l2]){l1++;l2++;}elsereturn false;}return true;}}bool operator<(const string& str)const{return !(*this > str || *this == str);}bool operator>=(const string& str)const{return !(*this < str);}bool operator<=(const string& str)const{return !(*this > str);}bool operator!=(const string& str)const{return !(*this == str);}//string转换成C字符串const char* c_str() const{return _str;}//迭代器//1、非const对象typedef char* iterator;iterator begin(){return _str;}iterator end(){return _str + _size;}//2、const对象typedef const char* const_iterator;const_iterator begin()const{return _str;}const_iterator end()const{return _str + _size;}//重设size的大小void resize(size_t n, char ch = '\0'){if (n > _size && n <= _capacity){memset(_str+_size,ch,(n-_size)*sizeof(char));}else if(n>_capacity){reserve(n);memset(_str+_size,ch,(n-_size)*sizeof(char));}_str[n] = '\0';_size = n;}//pos位置插入字符string& insert(size_t pos, char ch){assert(pos<=_size);if (_size + 1 > _capacity){reserve(_capacity * 2);}size_t end=_size+1;while(end>=pos+1){_str[end]=_str[end-1];end--;}_str[pos]=ch;_size++;return *this;}//pos位置插入字符串string& insert(size_t pos, const char* str){assert(pos <= _size);size_t len = strlen(str);if (len + _size > _capacity){reserve(len + _size);}size_t end=_size+len;while(end>=pos+len){_str[end]=_str[end-len];end--;}strncpy(_str+pos,str,len);_size+=len;return *this;}//从pos位置开始删除len个字符string& erase(size_t pos = 0, size_t len = npos){assert(empty() != true && pos < _size);int left = pos;int right = pos+len-1;if(len==npos||pos+len>=_size)right=_size-1;int end = right + 1;while (end <= _size){_str[end - (right - left + 1)] = _str[end];end++;}_size -= (right - left + 1);return *this;}//交换void swap(string& str){std::swap(_str,str._str);std::swap(_size,str._size);std::swap(_capacity,str._capacity);}//扩容,重新设置有效容量void reserve(size_t n = 0){//开空间,拷数据if (n > _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[]_str;_str = tmp;_capacity = n;}}//插入字符void push_back(char ch){//检查容量if (_size +1> _capacity)//容量已满,扩二倍{reserve(_capacity*2);}_str[_size] = ch;_str[_size + 1] = '\0';//'\0'需要我们自己手动插入_size++;}//追加字符串string& append(const char* str){size_t len = strlen(str);const char* sour = str;char* dest = _str;//先记录一下原来的指针if (_size + len > _capacity)//需要扩容{reserve(_size+len);}//strcpy(_str+_size,str);//这里不建议用strcat,因为strcat会去找_str的\0,这是O(N)的消耗,没必要//这里用strcpy的话会造成自己给自己追加不能完成//可是如果考虑到自己给自己追加的话,strcpy也会不妥,会发生无穷拷贝if (dest == sour)//dest==str,说明是自己给自己追加,由于已经开好了空间,之前的空间也就被释放了,sour也就是个也sour = _str;//野指针,我们需要更换sour的指向memmove(_str+_size,sour,len*sizeof(char));_size += len;_str[_size] = '\0';return *this;}//重载+=运算符string& operator+=(char ch){push_back(ch);return *this;}string& operator+=(const char* str){append(str);return *this;}string& operator+=(const string& str){append(str._str);return *this;}//从pos位置开始查找字符c第一次出现的位置size_t find(char c,size_t pos=0)const{size_t i=pos;for(i=pos;i<_size;i++){if(_str[i]==c)return i;}return npos;}int *GetNext(const char*p)const {int len=strlen(p);int *next=new int[len];next[0]=-1;int j=0;int k=-1;while(jif(-1==k||p[k]==p[j]){next[j+1]=k+1;k=next[j+1];j++;}else{k=next[k];}}return next;}//从pos位置开始查找字串第一次出现的位置size_t find(const char*str,size_t pos=0)const{int i=pos;int j=0;int len1=_size;int len2=strlen(str);if(len2==0)return 0;int *next=GetNext(str);//根据字串获的next数组while(iwhile(j==-1||(ii++;j++;}if(j>=len2){return i-j;}else{j=next[j];}}return npos;}private:char* _str;size_t _size;//记录当前已经存储的字符个数size_t _capacity;//记录当前能存储的字符的空间,不包含最后以'\0'结尾字符串的空间,但是实际我们需要将这个空间开出来;public:static const size_t npos;//与STL保持一致};size_t const string::npos = -1;//重载<<运算符std::ostream& operator<<(std::ostream& cout, const string& str){//cout << str._str << std::endl;string::const_iterator it=str.begin();while(it!=str.end()){std::cout<<*it;it++;}return cout;}std::istream& operator>>(std::istream& cin, string& str){//char tmp[1000];//std::cin >> tmp;//str = tmp;//return cin;// char ch;//ch= cin.get();//while(ch!=' '&&ch!=9&&ch!='\n')//{// str+=ch;// ch=cin.get();srvar cin>>ch;//}char buff[128];int i=0;char ch;ch= cin.get();while(ch!=' '&&ch!=9&&ch!='\n'){buff[i++]=ch;if(i==127){buff[127]='\0';str+=buff;i=0;}ch=cin.get();}if(i){buff[i]='\0';str+=buff;}return cin;}
void test7()
{string str1="Hello World";size_t index=str1.find("ld",10);std::cout<//string str1;//str1 += '*';//str1 += "hahaha";//string str2 = "999";//str1 += str2;/* str1.reserve(100);str1.reserve(10);str1.push_back('a');str1.append("hahahaha");*/string str1 = "Hello World";std::cin >> str1;string str2 = "jkjj";str1.swap(str2);//str1.erase();//str1.insert(5,"YY");//str1.resize(5);str1.resize(20,'*');}//Void test3()//{// string str1 = "Hello World!";// //string::iterators it = str1.begin();// for (auto it : str1)// {// std::cout << it << std::endl;// }//}void test1(){string str1;string str2("Hahaha");/*std::cout << str1 << std::endl;std::cout << str2 << std::endl;*/std::cout << str1.c_str() << std::endl;//cout打印char*时不会及那个char*当作地址来打印;//而是将char*当作字符串首元素地址来打印,也就是将char*指针当作字符串来打印,遇到\0停止;std::cout << str2.c_str() << std::endl;//std::cout << (char*)nullptr << std::endl;string str3 = str2;std::cout << str3 << std::endl;string str4="dfdhbvdawovhewvabkjawbvjewbovnoeiwhvbejw clcKJFHEBWKLVNWEAUOGBUB";str4 = str3;std::cout << str4 << std::endl;std::cout << str4.size() << std::endl;}void test2(){//const string str1 = "Hello World!";//for (size_t i = 0; i < str1.size(); i++)//{// //str1[i]++;// std::cout << str1[i] << " ";//}//std::cout << std::endl;string str1 = "abccd";string str2 = "fgwaghbre ewAR";std::cout << str1.capacity() << std::endl;std::cout << str1.size() << std::endl;/* if (str1 == str2){std::cout << "str1==str2" << std::endl;}else{std::cout << "str1!=str2" << std::endl;}*/}void test5(){string str1;string str2 = "Hello World!";string str3 = str2;std::cout << (str1+=str2+=str2 )<< std::endl;std::cout << "--------------" << std::endl;std::cout << str2.capacity() << std::endl;std::cout << "--------------" << std::endl;std::cout << str3.capacity() << std::endl;std::cout << "--------------" << std::endl;}
void test6()
{// string str1="xxxxxxxxx";// str1.erase(1,4);// std:: cout<>str;
std::cout<
请移步至博客:C++深浅拷贝
为了演示方便,以下演示都是在64位平台下验证:
VS平台下,string总共占28个字节,内部结构稍微复杂 ,先是有一个联合体,联合体用来定义string中字符串的存储空间:
当字符串长度小于等于15时,使用内部固定的字符数组来存放
当字符串长度大于15时,从堆上开辟空间;
union _Bxty
{ // storage for small buffer or pointer to larger one
value_type _Buf[_BUF_SIZE];
pointer _Ptr;
char _Alias[_BUF_SIZE]; // to permit aliasing
} _Bx;
这种设计也是有一定道理的,大多数情况下字符串的长度都小于16,那string对象创建好之后,内
部已经有了16个字符数组的固定空间,不需要通过堆创建,效率高。
其次:还有一个size_t字段保存字符串长度,一个size_t字段保存从堆上开辟空间总的容量
最后:还有一个指针做一些其他事情。
故总共占16+4+4+4=28个字节。
g++环境下string的总共占4/8字节,g++环境下的string是通过指针来实现的,该指针指向一块空间:
lenth:字符串长度;
capacity:字符串长度;
refcount:引用计数;
像这样实现string,在拷贝和赋值方面效率会很高,为什么这么说呢?
那么g++为什么要这么做?
因为如果每次都老老实实的完成拷贝和赋值的话(也就是向VS环境下的string)是需要代价的,我们需要完成从另一个空间将数据拷贝到其他空间,这是一个不小的时间消耗,但是g++向这样设计的话,就不需要进行数据的拷贝,只需要完成string内部指针的赋值即可,更加高效!
那么如果是多个对象指向同一块空间的话,万一我想对某个对象进行修改或删除操作呢?
那么这时候我们就不能在str1与str2共享的空间上直接进行修改,因为如果我们对其直接进行修改,str1对象上的数据也会跟着被修改!这不是我们所希望的,遇到这种情况,g++提供了“写时拷贝的技术” 就是当我们需要对str2对象进行修改数据的操作时,OS会在内存重新开辟一块空间给str2,然后让str2在新空间上进行修改操作,这样就避免了修改str2的数据时误修改了str1的数据!
g++的这种作法其实就是一种赌徒思想,它在赌用户不会进行修改数据的操作,赌对了它就赚了,赌错了,就老老实实开辟空间,然后拷贝数据!