C++STL详解(一):string类
C++STL详解(一):string类
- 一、auto和范围for
- 1、auto关键字
- 2、范围for
- 二、string类的常用接口说明(注意下面我只讲解最常用的接口)
- 1. string类对象的常见构造
- 2、string类对象的容量操作
- 3、string类对象的访问及遍历操作
- 4、string类对象的修改操作
- 5、string类非成员函数
- 三、vs和g++下string结构的说明
- 1、vs下string的结构
- 2、g++下string的结构
有关string类文档介绍详解
一、auto和范围for
在这里补充2个C++11的小语法,方便我们后面的学习。
1、auto关键字
- 在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,后来这个不重要了。C++11中,标准委员会变废为宝赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。
- 用auto 声明指针类型时,用auto和auto*没有任何区别,但用auto 声明引用类型时 则必须加&。
- 当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
- auto不能作为函数的参数,可以做返回值,但是建议谨慎使用。
- auto不能直接用来声明数组。
#include<iostream>
using namespace std;
int func1()
{return 10;
}// 不能做参数
void func2(auto a)
{}// 可以做返回值,但是建议谨慎使用
auto func3()
{return 3;
} int main()
{int a = 10;auto b = a;auto c = 'a';auto d = func1();// 编译报错:rror C3531: “e”: 类型包含“auto”的符号必须具有初始值设定项auto e;cout << typeid(b).name() << endl;cout << typeid(c).name() << endl;cout << typeid(d).name() << endl;int x = 10;auto y = &x;auto* z = &x;auto& m = x;cout << typeid(x).name() << endl;cout << typeid(y).name() << endl;cout << typeid(z).name() << endl;auto aa = 1, bb = 2;// 编译报错:error C3538: 在声明符列表中,“auto”必须始终推导为同一类型auto cc = 3, dd = 4.0;// 编译报错:error C3318: “auto []”: 数组不能具有其中包含“auto”的元素类型auto array[] = { 4, 5, 6 };return 0;
}
#include<iostream>
#include <string>
#include <map>
using namespace std;
int main()
{std::map<std::string, std::string> dict = { { "apple", "苹果" },{ "orange","橙子" }, {"pear","梨"} };// auto的用武之地//std::map<std::string, std::string>::iterator it = dict.begin();auto it = dict.begin();while (it != dict.end()){cout << it->first << ":" << it->second << endl;++it;}return 0;
}
这段代码通过 std::map 存储了三个水果名称及其对应的中文翻译,使用迭代器遍历并打印出每一个键值对。 auto 使得迭代器的类型自动推导,简化了代码。
2、范围for
- 对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围,自动迭代,自动取数据,自动判断结束。
- 范围for可以作用到数组和容器对象上进行遍历。
- 范围for的底层很简单,容器遍历实际就是替换为迭代器,这个从汇编层也可以看到。
#
include<iostream>
#include <string>
#include <map>
using namespace std;
int main()
{int array[] = { 1, 2, 3, 4, 5 };// C++98的遍历for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i){array[i] *= 2;} for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i){cout << array[i] << endl;}// C++11的遍历for (auto& e : array)e *= 2;for (auto e : array)cout << e << " " << endl;string str("hello world");for (auto ch : str){cout << ch << " ";} cout << endl;
return 0;
}
二、string类的常用接口说明(注意下面我只讲解最常用的接口)
1. string类对象的常见构造
- string()(空字符串构造)
#include <iostream>
#include <string>
using namespace std;int main()
{string s1; // 调用默认构造函数,构造一个空字符串cout << "s1: " << s1 << endl; // 输出空内容cout << "s1.size(): " << s1.size() << endl; // 输出 0return 0;
}
功能说明:
构造一个空的 string 对象,不包含任何字符。
注意事项:
默认初始化后,size() 返回 0,但可以通过 push_back 或 += 动态添加字符。
- string(const char* s)(C字符串构造)
#include <iostream>
#include <string>
using namespace std;int main()
{const char* cstr = "Hello, C++";string s2(cstr); // 用C风格字符串构造string对象cout << "s2: " << s2 << endl; // 输出 "Hello, C++"return 0;
}
功能说明:
通过C风格字符串(const char*)初始化 string 对象。
注意事项:
需确保传入的指针指向有效的以 \0 结尾的字符串,否则行为未定义。
- string(const string& s)(拷贝构造)
#include <iostream>
#include <string>
using namespace std;int main() {string original = "Copy this";string copy(original); // 调用拷贝构造函数cout << "copy: " << copy << endl; // 输出 "Copy this"return 0;
}
功能说明:
通过另一个 string 对象构造新对象,内容与原对象完全一致。
注意事项:
深拷贝实现,修改原对象不会影响新对象。
总结:
2、string类对象的容量操作
- size()(返回有效字符长度)
#include <iostream>
#include <string>
using namespace std;int main()
{string s = "Hello";cout << "s.size(): " << s.size() << endl; // 输出 5return 0;
}
功能说明:
返回字符串中当前存储的有效字符数量(不包含结尾的 \0)。
注意事项:
与 length() 功能完全相同,但 size() 是STL容器的通用接口,推荐优先使用。
- empty()(检查字符串是否为空)
#include <iostream>
#include <string>
using namespace std;int main()
{string s1 = "abc";string s2;cout << "s1.empty(): " << s1.empty() << endl; // 输出 0(false)cout << "s2.empty(): " << s2.empty() << endl; // 输出 1(true)return 0;
}
功能说明:
若字符串长度为0(即 size() == 0),返回 true,否则返回 false。
注意事项:
比手动检查 size() == 0 更直观,推荐使用。
- clear()(清空字符串)
#include <iostream>
#include <string>
using namespace std;int main()
{string s = "Hello";s.clear();cout << "After clear, s: " << s << endl; // 输出空cout << "s.size(): " << s.size() << endl; // 输出 0cout << "s.capacity(): " << s.capacity() << endl; // 容量可能不变return 0;
}
功能说明:
清空字符串内容,但不释放内存(capacity() 可能保持不变)。
注意事项:
若需彻底释放内存,可结合 shrink_to_fit()(C++11)使用。
- reserve()(预留空间)
#include <iostream>
#include <string>
using namespace std;int main()
{string s;s.reserve(100); // 预分配至少100字节的空间cout << "s.capacity(): " << s.capacity() << endl; // 可能 >=100cout << "s.size(): " << s.size() << endl; // 输出 0(内容仍为空)return 0;
}
功能说明:
提前分配内存以减少后续动态扩容的开销。
注意事项:
实际容量可能大于请求值(由实现决定)。
仅影响容量,不改变内容或长度。
- resize()(调整有效字符数量)
#include <iostream>
#include <string>
using namespace std;int main()
{string s = "Hello";s.resize(8, 'x'); // 扩展至8字符,不足部分用'x'填充cout << "s: " << s << endl; // 输出 "Helloxxx"s.resize(3); // 截断至3字符cout << "s: " << s << endl; // 输出 "Hel"return 0;
}
功能说明:
调整字符串长度:
若 n > size(),填充指定字符(默认 \0)。
若 n < size(),截断多余字符。
注意事项:
- size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()。
- clear()只是将string中有效字符清空,不改变底层空间大小。
- resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用0来填充多出的元素空resize(size_t n, charc)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。
- eserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小。
总结:
3、string类对象的访问及遍历操作
- operator[](下标访问字符)
#include <iostream>
#include <string>
using namespace std;int main()
{string s = "Hello";// 非const对象:可读可写s[0] = 'h'; // 修改第一个字符cout << s[0] << endl; // 输出 'h'(读取第一个字符)// const对象:只读const string cs = "World";cout << cs[2] << endl; // 输出 'r'(只能读取)// cs[0] = 'w'; // 错误!const对象不可修改return 0;
}
功能说明:
通过下标直接访问指定位置的字符(从 0 开始)。
注意事项:
不检查越界(若 pos >= size(),行为未定义)。
对 const string 返回 const char&,禁止修改。
- begin() 和 end()(正向迭代器)
#include <iostream>
#include <string>
using namespace std;int main()
{string s = "C++";// 使用迭代器遍历for (auto it = s.begin(); it != s.end(); ++it) {cout << *it << " "; // 输出:C + + }cout << endl;// 修改内容*s.begin() = 'c'; // 将首字符改为 'c'cout << s << endl; // 输出 "c++"return 0;
}
功能说明:
begin() 返回指向首字符的迭代器,end() 返回末尾的“哨兵”迭代器(指向最后一个字符的下一个位置)。
注意事项:
若字符串为空,begin() == end()。
支持通过迭代器修改字符(除非是 const 迭代器)。
- rbegin() 和 rend()(反向迭代器)
#include <iostream>
#include <string>
using namespace std;int main()
{string s = "12345";// 反向遍历for (auto rit = s.rbegin(); rit != s.rend(); ++rit) {cout << *rit << " "; // 输出:5 4 3 2 1}cout << endl;return 0;
}
功能说明:
rbegin() 返回指向最后一个字符的反向迭代器,rend() 返回首字符前一个位置的迭代器。
注意事项:
反向迭代器的 ++ 操作会向字符串开头移动。
- 范围 for 循环(C++11)
#include <iostream>
#include <string>
using namespace std;int main()
{string s = "Range";// 简洁遍历(只读)for (char ch : s) {cout << ch << " "; // 输出:R a n g e}cout << endl;// 修改内容需用引用for (char& ch : s) {ch = toupper(ch); // 转为大写}cout << s << endl; // 输出 "RANGE"return 0;
}
功能说明:
语法上,底层通过 begin() 和 end() 实现。
注意事项:
若需修改元素,需使用 char& 引用类型。
总结:
4、string类对象的修改操作
- operator+=(追加字符串/字符)
#include <iostream>
#include <string>
using namespace std;int main()
{string s = "Hello";s += " World"; // 追加字符串s += '!'; // 追加字符cout << s << endl; // 输出 "Hello World!"return 0;
}
功能说明:
支持追加另一个 string、C风格字符串或单个字符。
注意事项:
直接修改原字符串,是最常用的拼接方式(高效且简洁)。
- c_str()(返回C风格字符串)
#include <iostream>
#include <string>
#include <cstring>
using namespace std;int main()
{string s = "C++";const char* p = s.c_str(); // 获取C风格字符串指针cout << "C-string: " << p << endl; // 输出 "C++"cout << "Length via strlen: " << strlen(p) << endl; // 输出 3// 注意:若s被修改,p可能失效s += "11";// cout << p; // 危险!p可能指向无效内存return 0;
}
功能说明:
返回 const char* 指针,指向以 \0 结尾的字符数组。
注意事项:
不要存储返回的指针,若原 string 被修改,指针可能失效(如扩容后)。
常用于需要C风格字符串的接口(如文件操作、C库函数)。
- find() 和 npos(查找字符/子串)
#include <iostream>
#include <string>
using namespace std;int main()
{string s = "Hello, C++!";size_t pos;// 查找字符pos = s.find('C');if (pos != string::npos) {cout << "'C' found at: " << pos << endl; // 输出 7}// 查找子串pos = s.find("C++");if (pos != string::npos){cout << "\"C++\" found at: " << pos << endl; // 输出 7}// 查找不存在的字符pos = s.find('x');if (pos == string::npos) {cout << "'x' not found!" << endl; // 输出此提示}return 0;
}
功能说明:
find() 返回首次匹配的位置(从 0 开始),失败时返回 string::npos。
可查找字符或子串,支持指定起始位置(如 s.find(‘o’, 5))。
注意事项:
检查返回值时必须使用 npos,不可直接与 -1 比较(npos 是无符号类型)。
rfind() 为反向查找(从后往前)。
- substr()(提取子串)
#include <iostream>
#include <string>
using namespace std;int main()
{string s = "Programming";string sub = s.substr(3, 4); // 从位置3开始,截取4个字符cout << sub << endl; // 输出 "gram"// 省略长度参数:截取到末尾sub = s.substr(7);cout << sub << endl; // 输出 "ming"return 0;
}
功能说明:
返回从 pos 开始、长度为 n 的子串(默认 n 到末尾)。
注意事项:
1. 在string尾部追加字符时,s.push_back© / s.append(1, c) / s += 'c’三种的实现方式差不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。
2. 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好
总结:
5、string类非成员函数
- operator>> 和 operator<<(输入/输出运算符重载)
#include <iostream>
#include <string>
using namespace std;int main()
{string s;// 输入示例(遇到空格/换行停止)cout << "Enter a word: ";cin >> s; // 输入 "Hello World"cout << "You entered: " << s << endl; // 输出 "Hello"(空格截断)// 输出示例string name = "Alice";cout << "Name: " << name << endl; // 输出 "Name: Alice"return 0;
}
功能说明:
从流中读取数据到 string,默认以空格/换行分隔。
将 string 输出到流。
注意事项:
会忽略前导空白符,读取到下一个空白符为止。
需配合 getline() 读取整行(见下文)。
- getline()(获取整行输入)
#include <iostream>
#include <string>
using namespace std;int main()
{string line;cout << "Enter a line: ";getline(cin, line); // 读取整行(包括空格)cout << "Line: " << line << endl; // 输出完整输入// 指定分隔符(默认'\n')getline(cin, line, ';'); // 读取直到遇到分号cout << "Until ';': " << line << endl;return 0;
}
功能说明:
从输入流中读取一行(默认以 \n 结尾),可指定分隔符。
注意事项:
与 cin >> 混用时,需先清除缓冲区残留的换行符:
cin.ignore(); // 忽略前一个输入的换行
- relational operators(比较运算符)
#include <iostream>
#include <string>
using namespace std;int main()
{string a = "apple";string b = "banana";// 比较示例(按字典序)cout << boolalpha;cout << "a < b: " << (a < b) << endl; // 输出 true('a' < 'b')cout << "a == \"apple\": " << (a == "apple") << endl; // 输出 true// 支持所有比较运算符:>, >=, <=, !=return 0;
}
功能说明:
按字符的字典序(ASCII值)逐位比较。
注意事项:
比较时区分大小写(“A” < “a” 为真)。
可直接与C风格字符串比较(如 s == “text”)。
- operator+(不推荐使用)
string s1 = "Hello", s2 = "World";
string s3 = s1 + " " + s2; // 拼接,但每次+生成临时对象
为什么不推荐?
每次 + 操作会生成临时对象,频繁拼接时效率低(尤其是循环中)。
替代方案:
使用 += 或 append() 直接修改原字符串:
string s;
s += s1;
s += " ";
s += s2; // 无额外拷贝
总结:
三、vs和g++下string结构的说明
注意:下述结构是在32位平台下进行验证,32位平台下指针占4个字节。
1、vs下string的结构
string总共占28个字节,内部结构稍微复杂一点,先是有一个联合体,联合体用来定义。
string中字符串的存储空间:
- 当字符串长度小于16时,使用内部固定的字符数组来存放
- 当字符串长度大于等于16时,从堆上开辟空间
这种设计也是有一定道理的,大多数情况下字符串的长度都小于16,那string对象创建好之后,内部已经有了16个字符数组的固定空间,不需要通过堆创建,效率高。
其次:还有一个size_t字段保存字符串长度,一个size_t字段保存从堆上开辟空间总的容量。
最后:还有一个指针做一些其他事情。故总共占16+4+4+4=28个字节。
2、g++下string的结构
G++下,string是通过写时拷贝实现的,string对象总共占4个字节,内部只包含了一个指针,该指针将来指向一块堆空间,内部包含了如下字段:
- 空间总大小
- 字符串有效长度
- 引用计数
- 指向堆空间的指针,用来存储字符串