在学习string之前,我们先来了解一下各种编码。
关于ASCII码,百度百科给出了这样的解释:
ASCII (American Standard Code for Information Interchange):美国信息交换标准代码是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言。它是最通用的信息交换标准,并等同于国际标准 ISO/IEC 646。ASCII第一次以规范标准的类型发表是在1967年,最后一次更新则是在1986年,到目前为止共定义了128个字符。
ASCII的长度为一个字节,共8个比特位,理论上能够表示256个字符,至于为什么只有128个,是因为:第一位比特位是符号位,为了能够兼顾数字和字符,就不使用第一位,这样就剩下7位可以编码。后来为了加入更多字符,又把第一位用上了,就形成了扩展ASCII。
统一码(Unicode),也叫万国码、单一码,由统一码联盟开发,是计算机科学领域里的一项业界标准,包括字符集、编码方案等。
统一码是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。
其中包括:UTF-8,UTF-16,UTF-32,最常使用的是UTF-8。
GBK全称《汉字内码扩展规范》(GBK即“国标”、“扩展”汉语拼音的第一个字母,英文名称:Chinese Internal Code Specification) ,中华人民共和国全国信息技术标准化技术委员会1995年12月1日制订,国家技术监督局标准化司、电子工业部科技与质量监督司1995年12月15日联合以技监标函1995 229号文件的形式,将它确定为技术规范指导性文件。2000年已被GB18030-2000《信息交换用 汉字编码字符集 基本集的扩充》国家强制标准替代。 2005年GB18030-2005发布,替代了GB18030-2000。
- 字符串是表示字符序列的类
- 标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,但添加了专门用于操作 单字节字符字符串的设计特性。
- string类是使用char(即作为它的字符类型,使用它的默认char_traits和分配器类型(关于模板的更多信 息,请参阅basic_string)。
- string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类,并用char_traits
和allocator作为basic_string的默认参数(根于更多的模板信息请参考basic_string)。- 注意,这个类独立于所使用的编码来处理字节:如果用来处理多字节或变长字符(如UTF-8)的序列,这个 类的所有成员(如长度或大小)以及它的迭代器,将仍然按照字节(而不是实际编码的字符)来操作。
总结:
- string是表示字符串的字符串类
- 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。
- string在底层实际是:basic_string模板类的别名,typedef basic_string
string; - 不能操作多字节或者变长字符的序列。
上述说法太过繁琐,我们先知道它就是一个字符串类型,然后内置了许多成员函数可以对其进行操作就行。
1.创建一个空字符串。
2.拷贝构造一个字符串。
3.从pos位置开始,拷贝构造长度为len的字符串。
4.使用C风格字符串来构造字符串。
5.使用C风格字符串重复n次来构造字符串。
6.用n个字符c构造字符串。
7.使用迭代器构造字符串(先不示范)
int main()
{
string s1; // (1)
string s2(s1); // (2)
string s3("abcdefg"); // (4)
// string s3 = "abcdefg"; // 也可以这样写,发生隐式类型转换
string s4(s3, 3, 4); // (3) 这个位置的len的缺省值npos,实际上就是-1,如果不给len传值,就默认从pos位拷贝到末尾
string s5("123", 3); // (5)
string s6(5, 'n'); // (6)
cout << s1 << endl; // string类中还重载了流插入和流提取,可以很方便地打印字符串,提高了代码的可读性
cout << s2 << endl;
cout << s3 << endl;
cout << s4 << endl;
cout << s5 << endl;
cout << s6 << endl;
return 0;
}
通过这张图片可以看出string类是基于类模板basic_string的,除此以外,还有wstring,u16string,u32string,参数分别为wchar_t,char16_t,char32_t。
template<class T>
class my_basic_string
{
private:
T* _str;
size_t _size; // 长度
size_t _capacity; // 容量
};
typedef my_basic_string<char> my_string;
这里的成员变量都是私有的,我们想要访问需要通过成员函数。
length()和size()使用起来是一样的,原本string类只有length(),但是用在其他容器中就显得奇怪,比如树的长度?所以为了能得到一个所有容器都能使用的,就引入了size()。
int main()
{
string str = "abcdef";
cout << str.size() << endl; // 打印字符串长度
cout << str.length() << endl; // 打印字符串长度
cout << str.capacity() << endl; // 打印字符串容量
cout << str.c_str() << endl; // 打印C风格字符串
return 0;
}
operator[]和at都是通过下标访问,不过当发生越界访问时,at会抛异常,operator[]则会assert断言。
front和back分别用来返回字符串的首尾元素,不是很常用。
string str("abcdefg");
cout << str[3] << endl;
cout << str.at(4) << endl;
cout << str.front() << endl;
cout << str.back() << endl;
string str = "abcdef";
for (int i = 0; i < str.size(); ++i)
{
++str[i];
cout << str[i] << " ";
}
cout << endl;
对于上述下标+[]的遍历方法,我们在string时可以很方便的使用,但是等到之后的链表、二叉树等就无法使用了,为此C++提供了一个具有普适性的遍历方法,就是用迭代器。
迭代器在使用上类似于指针,定义是要指定类域,比如:string::iterator。
begin()会返回第一个字符的迭代器。
end()会返回最后一个字符下一个位置的迭代器。
通过解引用迭代器可以获得其指向的字符。
使用:
string str = "hello world!";
string::iterator it = str.begin();
while (it != str.end())
{
cout << *it;
++it;
}
cout << endl;
除此之外,还提供了rbegin和rend,这两个需要用反向迭代器reverse_iterator来接收,使用方法和普通的正向迭代器一样。
string str = "hello world!";
string::reverse_iterator it = str.rbegin();
while (it != str.rend())
{
cout << *it;
++it;
}
cout << endl;
还有const迭代器,修饰的是const指向的内容,用const_iterator或const_reverse_iterator来接收,当需要字符串内容不可修改的时候使用。
在C++11中引入了cbegin,cend,crbegin,crend,用来针对const修饰的对象,但实际并没有什么用处。
范围for是C++11引入的新特性,使用方法如下。
范围for的底层实现其实就是迭代器。
string s1 = "abcdef";
for (auto& ch : s1)
{
++ch;
}
cout << s1 << endl;
size,length,capacity前面已经演示过了,这里就不再赘述。
max_size这个函数没有什么意义,是一个写死了的函数,返回值的大小根据不同编译器和x86/x64都有所不同。
int main()
{
string s1("123456");
string s2;
cout << s1.max_size() << endl;
cout << s2.max_size() << endl;
return 0;
}
clear():用于删除字符串中所有数据,会将size置零,但容量不变。
string s1("123456");
cout << s1.size() << endl;
cout << s1.capacity() << endl;
s1.clear();
cout << s1.size() << endl;
cout << s1.capacity() << endl;
reserve():
在学习reserve之前,我们先来了解一下string的扩容机制:
通过如下代码来研究不同平台下的机制:
string s1;
int capacity = s1.capacity();
cout << "start capacity:" << s1.capacity() << endl;
for (int i = 0; i < 1000; ++i)
{
s1.push_back('a');
if (s1.capacity() != capacity)
{
cout << "capacity change:" << s1.capacity() << endl;
capacity = s1.capacity();
}
}
在vs编译器中:
可以看出,起始容量为15,第一次扩容为2倍扩容,后续都是1.5倍扩容
g++编译器下:
g++编译器下没有初始容量,每次扩容都是2倍扩容
但是这样扩容的代价是很大的,为了能一次性扩容,便创建了reserve()函数。
string s1;
int capacity = s1.capacity();
cout << "start capacity:" << s1.capacity() << endl;
s1.reserve(1000);
for (int i = 0; i < 1000; ++i)
{
s1.push_back('a');
if (s1.capacity() != capacity)
{
cout << "capacity change:" << s1.capacity() << endl;
capacity = s1.capacity();
}
}
由于对其因素,vs实际开辟的空间会大一些。
linux开辟的空间就是指定的空间。
另外一件需要注意的事情是:reserve的参数如果小于目前的容量,不会缩容。
resize():
n>capacity:扩容+填充数据
n
n
string s1 = "hello world";
cout << s1 << endl;
cout << s1.size() << endl;
cout << s1.capacity() << endl;
s1.resize(15, 'a');
cout << s1 << endl;
cout << s1.size() << endl;
cout << s1.capacity() << endl;
s1.resize(20, 'c');
cout << s1 << endl;
cout << s1.size() << endl;
cout << s1.capacity() << endl;
s1.resize(5);
cout << s1 << endl;
cout << s1.size() << endl;
cout << s1.capacity() << endl;
strink_to_fit():
C++11新引入的函数,用于将capaci减少至与size相等。
push_back:用于尾插单个字符 。
append():使用方式有很多,参数类似构造函数,使用起来也很相似。
operator+=:既可以尾插字符,也可以尾插字符串,而且可读性很高。
insert():在指定位置插入或字符串,好用,但是效率很低,因为需要移动数据。
string s1("hello");
s1.push_back('a');
cout << s1 << endl;
s1.append("abcdef", 2, 3);
cout << s1 << endl;
s1 += "world";
cout << s1 << endl;
s1.insert(3, "ddd");
cout << s1 << endl;
string s1("hello world");
s1.erase(4, 1);
cout << s1 << endl;
s1.erase(4); // 不传len或者len值过大,则直接删到末尾
cout << s1 << endl
assign():类似于赋值重载,用新的字符串替换旧的字符串。
string s1("abcdef");
string s2("hello world");
s1.assign(s2, 3, 5);
cout << s1 << endl;
string s1("abcdef");
string s2("hello world");
s1.replace(2, 3, s2, 3, 5);
cout << s1 << endl;
swap():用于两个string的交换
string s1("abcdef");
string s2("hello world");
s1.swap(s2);
cout << s1 << endl;
cout << s2 << endl;
find():从pos位置开始找,返回索引值,一般搭配insert或者erase使用。
rfind():和find类似,这不过这个是从后向前找。
string s1("abcdefg");
string s2 = s1.substr(3, 3);
cout << s1 << endl;
cout << s2 << endl;
operator+:就是将两个字符串合并,但由于是传值返回,导致深拷贝效率低。
operator>>和operator<<:流提取和流插入,用来方便的打印string类型的变量。
getline():
如果我们有一个字符串“hello world”想要输入给s1变量,尝试用cin试一下。
string s1;
cin >> s1;
cout << s1 << endl;
我们发现没有全部输入进去,这是因为不论是cin还是scanf都是用空格和换行符来作为分割的。
那我们像得到一个带空格的字符串该怎么办呢?
就只能用getline了
string s1;
//cin >> s1;
getline(cin, s1);
cout << s1 << endl;
relational operators:这是一些字符串比较函数
vs下string的结构
string总共占28个字节,内部结构稍微复杂一点,先是有一个联合体,联合体用来定义string中字符串的存储空间:
- 当字符串长度小于16时,使用内部固定的字符数组来存放
- 当字符串长度大于等于16时,从堆上开辟空间
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字段保存从堆上开辟空间总的容量,还一个指针做一些其他事情。
所以总共占28个字节。
g++下string的结构
g++下,string是通过写时拷贝实现的,string对象总共占4个字节,内部只包含了一个指针,该指针将来指向一块堆空间,内部包含了如下字段:
- 空间总大小
- 字符串有效长度
- 引用计数