在C语言中,string是一个标准库类(class),用于处理字符串,它提供了一种更高级、更便捷的字符串操作方式,string 类提供了一系列成员函数和重载运算符,以便于对字符串进行操作和处理。
一、string类
在学习 string 前,我们不妨先来了解一下 string 类到底是什么,有什么用呢?我们先来了解一下基本的概念吧
C++标准库都是英语解释。我们也应该试着去适应,不懂的可以查阅。当然,在这里我就直接给出翻译,主要是以下内容:
字符串是表示字符序列的类;
标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,但添加了专门用于操作单字节字符字符串的设计特性。
string类是使用char(即作为它的字符类型,使用它的默认char_traits和分配器类型(关于模板的更多信息,请参阅basic_string)。
string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类,并用char_traits和allocator作为basic_string的默认参数(根于更多的模板信息请参考basic_string)。
注意,这个类独立于所使用的编码来处理字节:如果用来处理多字节或变长字符(如UTF-8)的序列,这个类的所有成员(如长度或大小)以及它的迭代器,将仍然按照字节(而不是实际编码的字符)来操作。
二、string的常用见用法
2.1 string对象的构造
2.1.1 string对象的构造的使用方法
最为常用的无非就是我们用串string来构造一个对象,也就是存储一个字符,常用的方法有如下几点:
string()——构造空的 string 类对象,即空字符串;
string(const char* s)——用 char* 来构造 string 类对象;
string(size_t n, char c)——string类对象中包含n个字符c;
string(const string&s)——拷贝构造函数。
下面是使用方法所对应的实例,帮助更好的理解其用法。
三、string常用结构的底层实现
3.1 初建结构
我们通过上述的构造,不难发现也不难理解string的底层其实就是一个字符指针,该指针指向一个数组。当然,我们还需要两个变量来维护其有效长度(_size)和数组容量(_capacity)。
其次,我们自己实现的string类为了区分std命名空间,我们可自己设置一个命名空间。处型的模拟实现如下:
namespace gtm
{class string{public://string()// :_str(new char[1])// , _size(0)// ,_capacity(0)//{//}//string(const char* str)// :_str(new char[strlen(str) + 1]) //三次strlen函数,效率低。// ,_size(strlen(str))// ,_capacity(strlen(str))//{// strcpy(_str, str);//}// 不再使用strlen函数,初始化列表与变量声明顺序固定string(const char* str = "") //默认空串。注意:空串是以 \0 结尾{_size = strlen(str);_capacity = _size;_str = new char[_size + 1];strcpy(_str, str);}~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}private:char* _str;size_t _size;size_t _capacity;};
3.2 返回大小和容量
这两个部分,是比较容易实现的两部分。同时也是较为常用的两部分。具体如下:
size_t size() const{return _size;}size_t capacity() const{return _capacity;}
3.3 拷贝构造和赋值重载
这两部分较为复杂的两部分。其中均需要深拷贝去实现完成,而浅拷贝是不可以的。注意:拷贝构造使用一个已定义变量去初始化另一个变量,赋值重载是两个已定义变量进行赋值。
具体实现如下:
//深拷贝//string(const string& s)// :_str(new char[s._capacity+1])// ,_size(s._size)// ,_capacity(s._capacity)//{// strcpy(_str, s._str);//}void swap(string& tmp){//调用全局的swap::swap(_str, tmp._str);::swap(_size, tmp._size);::swap(_capacity, tmp._capacity);}//借助变量tmpstring(const string& s):_str(nullptr) , _size(0), _capacity(0){string tmp(s._str);swap(tmp);}//赋值//string& operator=(const string& s)//{// if(this == &s)// {// return *this;// }// //先开空间拷贝数据,以防new失败销毁原来的空间// char* tmp = new char[s._capacity + 1];// strcpy(tmp, s._str);// delete[] _str;// _str = tmp;// _size = s._size;// _capacity = s._capacity;// return *this;// //delete[] _str;// //_str = new char[s._capacity + 1];// //strcpy(_str, s._str);// //_size = s._size;// //_capacity = s._capacity;// return *this;//}//string& operator=(const string& s)//{// if(this == &s)// {// return *this;// }// string tmp(s._str);// swap(tmp);// return *this;//}string& operator=(string s){if (this == &s){return *this;}swap(s);return *this;}
上述的辅助重载我们巧妙地借助了临时变量s。当赋值完成后,出了作用域s会自动调用戏后进行销毁,这里是需要反复理解的。
3.4 扩容(reserve)
我们可简单的理解reserve为扩容(扩容的前提为要求的容量比原来的大),但是我们要记得把字符数组中原有的内容拷贝过来,并且释放之前所动态开辟的空间。 具体实现如下:
void reserve(size_t capacity){if (capacity > _capacity){char* tmp = new char[capacity + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = capacity;}}
3.5 插入(push_back、append、operator+=、insert)
插入的实现,主要的点就是是否要进行扩容。其次,当我们实现push_back和append后,其他的均可复用这两个结构进行实现。具体实现如下:
void push_back(char ch){if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size] = ch;_size++;_str[_size] = '\0';}void append(const char* str){size_t len = strlen(str);if (len + _size > _capacity){reserve(len + _size >= _capacity * 2 ? len + _size : _capacity * 2);}strcpy(_str + _size, str);_size += len;}void append(const string& s){append(s._str);}void append(int n, char ch){reserve(_size + n);for (int i = 0; i < n; i++){push_back(ch);}}string& operator+= (char ch){push_back(ch);return *this;}string& operator+= (const char* str){append(str);return *this;}string& insert(size_t pos, char ch){assert(pos <= _size);if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}//注意,当运算数一个是有符号,另一个是无符号时,有符号的运算数会强制类型转换为无符号数。pos等于0的位置插入,end--后为超大数据,会出错。//int end = _size;//while (end >= (int)pos)//{// _str[end + 1] = _str[end];// end--;//}size_t end = _size+1;while (end > pos){_str[end] = _str[end - 1];end--;}_str[pos] = ch;_size++;return *this;}string& insert(size_t pos, const char* str){assert(pos <= _size);size_t len = strlen(str);if (len + _size > _capacity){reserve(len + _size >= _capacity * 2 ? len + _size : _capacity * 2);}size_t end = _size + len;while (end >= pos+len){_str[end] = _str[end - len];end--;}for (int i = pos,j=0; j < len;j++, i++){_str[i] = str[j];}_size += len;return *this;}
string 在C++中算是比较重要的了,也是入门时必须所学的容器。在平常中使用的频率较高,所以我们不仅要掌握其简单的用法,更应该去了解其底层的实现。这有助于我们后续的使用和理解。本篇文章列举出了string中常用的语法和接口底层的底层实现,这些都是我们应该熟练掌握的内容。