C++:String类
文章目录
- 前言
- 一、auto和范围for
- 二、string常用接口
- 三、浅拷贝与深拷贝
- 总结
前言
掌握String
类意味着能:
安全高效地处理文本;
避免底层编码陷阱;
利用高级功能(如正则表达式);
理解内存机制,优化性能。
无论是开发应用、算法实现还是系统设计,字符串操作都是必备技能,直接关系到程序的健壮性和效率。
一、auto和范围for
1.1 auto关键字
auto
关键字用于声明变量时自动推导类型,无需显式指定类型。主要目的:减少冗长的类型声明,尤其在处理复杂类型(如迭代器、模板或lambda表达式)时,使代码更简洁。
推导规则:编译器根据初始化表达式推断类型,类似于模板参数推导。例如:如果初始化表达式是整数,
auto
推导为int
。如果表达式是字符串字面量,推导为const char*。
注意:
1.用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&2.当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际 只对第一个类型进行推导,然后用推导出来的类型定义其他变量。3.auto不能作为函数的参数,可以做返回值,但是建议谨慎使用4.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;
}
1.2范围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常用接口
2.1 成员类型
实际底层为 char* str,_size, _capacity 的顺序表
2.2 成员函数
与顺序表数据结构类似,除了基本的增删查改,还多了一下这些内容
2.2.1 构造析构与复制重载
构造函数
析构函数
赋值运算符重载
void Teststring()
{
string s1; // 构造空的string类对象s1
string s2("hello bit"); // 用C格式字符串构造string类对象s2
string s3(s2); // 拷贝构造s3
}
2.2.2 迭代器
迭代器是C++标准库中提供的一种抽象,用于访问容器(如
vector
、list
、map
等)中的元素,而无需关心容器的内部实现细节。它的作用类似于指针,允许遍历和操作容器中的元素C++迭代器分为以下五类,每种类型支持不同的操作:
- 输入迭代器(Input Iterator):只能单向读取数据(如
istream_iterator
)。- 输出迭代器(Output Iterator):只能单向写入数据(如
ostream_iterator
)。- 前向迭代器(Forward Iterator):支持读写和单向遍历(如
forward_list
的迭代器)。- 双向迭代器(Bidirectional Iterator):支持双向遍历(如
list
、map
的迭代器)。- 随机访问迭代器(Random Access Iterator):支持随机访问和算术运算(如
vector
、array
的迭代器)。
2.2.3 对容量操作
1. size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()。2. clear()只是将string中有效字符清空,不改变底层空间大小。3. resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。4. reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小。
2.2.4 访问元素
2.2.5 修改
注意:1. 在string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c'三种的实现方式差不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。2. 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好。
2.2.6 字符串操作
#include <string>
using namespace std;string str1 = "Hello";
string str2("World");
string str3 = str1 + " " + str2;string s = "example";
char c1 = s[2]; // 'a'
char c2 = s.at(3); // 'm'for (size_t i = 0; i < s.size(); ++i) {cout << s[i];
}
for (char ch : s) {cout << ch;
}string s = "C++";
s.append(" Programming"); // "C++ Programming"
s.insert(3, " is"); // "C++ is Programming"
s.erase(6, 5); // "C++ Programming"
s.replace(4, 11, "awesome"); // "C++ awesome"string s1 = "apple";
string s2 = "banana";if (s1 < s2) {cout << "s1 is less than s2";
}
int result = s1.compare(s2); // returns <0, 0, or >0string s = "Hello, world!";
size_t pos = s.find("world"); // 7
pos = s.find('o'); // 4
pos = s.rfind('o'); // 8
pos = s.find_first_of("aeiou"); // 1 ('e')string s = "Hello, world!";
string sub = s.substr(7, 5); // "world"string numStr = "123";
int num = stoi(numStr); // 123
string strNum = to_string(456); // "456"string input;
cin >> input; // reads until whitespace
getline(cin, input); // reads entire line
cout << "You entered: " << input;string s = "example";
const char* cstr = s.c_str();
const char* data = s.data(); // C++17起保证以空字符结尾
三、浅拷贝与深拷贝
3.1 浅拷贝(Shallow Copy)
浅拷贝仅复制对象的成员值,包括指针的地址,而不复制指针指向的实际数据。这可能导致多个对象共享同一块内存,引发问题如内存泄漏或双重释放错误。
- 工作原理:如果类使用默认的拷贝构造函数或赋值运算符,C++会执行浅拷贝。指针成员被直接复制,新旧对象指向同一内存地址。
- 潜在风险:当其中一个对象修改数据或析构时,会影响其他对象,导致未定义行为。
- 代码示例: 以下是一个简单的类
ShallowClass
,包含一个指针成员。默认拷贝行为是浅拷贝。
3.2 深拷贝(Deep Copy)
深拷贝不仅复制对象成员,还复制指针指向的实际数据,创建独立的内存副本。这确保了对象间的数据隔离,避免共享内存问题。
- 工作原理:需要自定义拷贝构造函数和赋值运算符,在拷贝时手动分配新内存并复制内容。
- 优点:安全、可靠,适用于动态资源管理。
- 代码示例: 修改上述类为
DeepClass
,实现深拷贝。
#include <iostream>
using namespace std;class DeepClass {
public:int* data;DeepClass(int value) {data = new int(value);}// 自定义拷贝构造函数(深拷贝)DeepClass(const DeepClass& other) {data = new int(*other.data); // 分配新内存并复制值}// 自定义赋值运算符(深拷贝)DeepClass& operator=(const DeepClass& other) {if (this != &other) { // 避免自赋值delete data; // 释放现有内存data = new int(*other.data); // 分配新内存并复制值}return *this;}~DeepClass() {delete data; // 安全释放}
};int main() {DeepClass obj1(10);DeepClass obj2 = obj1; // 深拷贝:obj2.data 指向新内存副本*obj1.data = 20; // 修改 obj1cout << *obj1.data << endl; // 输出 20cout << *obj2.data << endl; // 输出 10,obj2 数据独立,未受影响// 析构时各自释放内存,无错误return 0;
}//在这个例子中,拷贝构造函数和赋值运算符显式创建新内存并复制值,确保obj1 和 obj2 完全独立。
总结
C++标准库中的
string
类是一个用于表示和操作字符串的类,它位于头文件<string>
中。string类提供了高效、安全且易于使用的字符串处理功能,避免了C风格字符串(如char*
)的常见问题(如缓冲区溢出)。以下是其主要特性的总结:1. 主要特性
- 动态内存管理:string对象自动管理内存,无需手动分配或释放。例如,字符串长度可以动态变化。
- 丰富的成员函数:支持各种字符串操作,如查找、替换、连接和比较。
- 运算符重载:使用运算符简化操作,例如:
+
用于字符串连接(如str1 + str2
)。==
,!=
,<
,>
等用于比较。- 兼容性:与C风格字符串无缝交互,例如可通过
c_str()
方法获取const char*
。- 安全性:内置边界检查,减少运行时错误。
2. 常用成员函数
- 长度相关:
size()
或length()
:返回字符串长度(类型为size_t
),例如长度可表示为 $n$。empty()
:检查字符串是否为空。- 子串操作:
substr(pos, len)
:从位置pos
提取长度为len
的子串。find(str)
:查找子串str
的起始位置,返回索引(从0开始)。- 修改操作:
append(str)
:在末尾添加字符串。insert(pos, str)
:在指定位置插入字符串。replace(pos, len, str)
:替换部分字符串。- 其他:
clear()
:清空字符串。at(index)
:访问指定位置的字符(带边界检查)。3. 优点
- 易用性:简化字符串处理,代码更简洁。
- 高效性:内部使用动态数组,支持快速操作。
- 类型安全:强类型设计,减少错误。
- 可扩展性:支持Unicode和多字节字符(通过
wstring
等变体)。
#include <iostream>
#include <string>
using namespace std;int main() {string str = "Hello, C++"; // 创建字符串cout << "Original: " << str << endl;cout << "Length: " << str.length() << endl; // 输出长度string sub = str.substr(0, 5); // 提取子串 "Hello"cout << "Substring: " << sub << endl;str.append(" World!"); // 添加字符串cout << "After append: " << str << endl;return 0;
}
5. 使用建议
- 在C++中优先使用
string
而非C风格字符串。- 注意:
string
类在C++11及以后版本中支持移动语义,提升性能。- 对于复杂操作,可结合STL算法(如
<algorithm>
中的函数)。总之,string类是C++中处理字符串的核心工具,它提升了代码的可靠性和可维护性。