突破编程_C++_基础教程(字符串)

1 std::string 基础使用

std::string 是 C++ 标准库中的一个类,用于表示和操作字符串。这个类在 头文件中定义,是 C++ 中处理字符串的首选方式。std::string 提供了许多方便的功能,使得字符串操作变得简单而高效。

1.1 std::string 的定义与初始化

1.1.1 构造函数

(1)默认构造函数: std::string s; 创建一个空字符串。
(2)带参数的构造函数: std::string s(“hello”); 创建一个包含 “hello” 的字符串。
(3)使用字符数组初始化: std::string s = “hello”; 或 std::string s = {‘h’, ‘e’, ‘l’, ‘l’, ‘o’, ‘\0’};
(4)使用字符数量和字符初始化: std::string s(5, ‘x’); 创建一个包含 5 个 ‘x’ 的字符串。

1.1.2 赋值操作

(1)赋值运算符 =
赋值运算符 = 是最常用且最直观的方式来给 std::string 对象赋值。它接受一个 std::string、字符数组、单个字符或 C 风格字符串作为参数,并将该值赋给 std::string 对象。

std::string str;  
str = "hello";           		// 从 const char* 字符串赋值  
str = std::string("hello"); 	// 从另一个 std::string 赋值  
str = 'h';               		// 从单个字符赋值

注意:使用赋值运算符 = 时,如果赋值的字符串与当前字符串共享相同的内存(例如,都是同一个字符串字面量的引用),则不会发生内存分配和复制操作。
(2)assign() 函数
assign() 成员函数也是用来给 std::string 对象赋值的,它提供了更多的灵活性。assign() 可以接受不同类型的参数,并根据参数的类型和数量来设置字符串的内容。

std::string str;  
str.assign("hello");                    // 从 const char* 字符串赋值  
str.assign(std::string("hello"));       // 从另一个 std::string 赋值  
str.assign(5, 'h');                     // 用 5 个 'h' 字符赋值  
str.assign("hello", 2, 3);              // 从 "hello" 中的第 2 个字符开始,取 3 个字符赋值(即 "llo")  

char chStr[6] = "hello";
str.assign(std::begin(chStr), std::end(chStr)); // 按照字符数组长度进行赋值

使用 assign() 时,总是会发生字符串内容的更改,即使新赋的值与当前值相同。这意味着即使两个字符串原本共享内存,使用 assign() 之后,它们通常将不再共享内存。
(3)赋值运算符 = 与 assign() 函数的区别
由于 assign() 总是进行字符串内容的更改,而赋值运算符 = 在某些情况下可能不进行任何操作(例如,当两个字符串引用相同的内存时),所以在某些性能敏感的场景中,使用赋值运算符 = 可能会更加高效。然而,这种性能差异通常只有在处理大量字符串或在非常关注性能的特定场景中才会变得显著。总的来说,assign() 提供了更多的灵活性,允许使用不同的方式设置字符串的内容,而赋值运算符 = 则是一种简洁且直观的方式来更新字符串的值。

1.2 std::string 的长度和容量

(1)size() 和 length() 函数
size() 和 length() 函数都用于获取字符串中字符的数量,即字符串的长度。这两个函数在功能上是等价的,它们返回的结果相同。
注意 size() 和 length() 函数在计算字符串的长度时,是否包括终止的空字符(‘\0’)的情况:

#include   
#include   

using namespace std;

int main() {
    
    char chStr[6] = "hello";
    string str1;
    str1.assign(std::begin(chStr), std::end(chStr));	// 按固定长度对 string 设值
    size_t str1Size = str1.size();
    size_t str1Length = str1.length();

    printf("str1 size = %zd\n", str1Size);
    printf("str1 length = %zd\n", str1Length);

    string str2 = chStr;			// 通过字符串数组赋值,会计算至空字符('\0')
    size_t str2Size = str2.size();
    size_t str2Length = str2.length();

    printf("str2 size = %zd\n", str2Size);
    printf("str2 length = %zd\n", str2Length);


    return 0;
}

上面代码的输出为:

str1 size = 6
str1 length = 6
str2 size = 5
str2 length = 5

注意上面的字符串长度区别,这在使用 string 做序列化的时候需要重点关注。
(2)capacity() 函数
std::string 的 capacity() 成员函数返回当前为字符串分配的内存量,以字节为单位。这个容量至少与字符串的长度(可以通过 size() 或 length() 获得)一样大,但可能更大。这是因为为了效率,std::string 通常会分配比实际需要的更多的内存,以便在添加新字符时不需要频繁地重新分配和复制内存。
capacity() 返回的内存量包括用于存储字符串内容的空间以及额外的空间,这些额外的空间是为了在字符串增长时避免频繁的内存重新分配。当字符串的长度增加并超过当前分配的容量时,std::string 会分配一个新的、更大的内存块,并将字符串的内容复制到新的内存块中。
capacity() 返回的容量通常大于或等于 size() 返回的长度。如果字符串的长度增加并接近或超过当前的容量,std::string 可能会重新分配内存,以便能够容纳更多的字符。为了避免频繁添加大量字符时造成的性能损失,可以使用 reserve() 成员函数来预先分配足够的内存。
如下为样例代码:

#include   
#include   

int main() 
{
    std::string str = "hello";
    printf("size = %zd\n", str.size());
    printf("capacity = %zd\n", str.capacity());

    str.reserve(50); // 请求至少50字节的容量  
    printf("new capacity = %zd\n", str.capacity());

    return 0;
}

上面代码的输出为:

size = 5
capacity = 15
new capacity = 63

(3)resize() 函数
std::string 的 resize() 成员函数用于改变字符串的大小。可以使用 resize() 来增加或减少字符串的长度,并可以选择用特定的字符来填充新添加的部分。
增加长度
如果 resize() 的参数大于当前字符串的长度,那么字符串将被增长,新添加的部分将被填充为指定的字符(如果提供了的话)。如果没有提供填充字符,新添加的部分将被初始化为空字符(‘\0’)。如下为样例代码:

std::string str = "hello";  
str.resize(10, '#'); // 将字符串增长到10个字符,新添加的部分用'#'填充  
// 现在 str = "hello#####"

减少长度
如果 resize() 的参数小于当前字符串的长度,那么字符串将被截断,丢弃超出的部分。如下为样例代码:

std::string str = "hello";  
str.resize(3); // 将字符串截断为3个字符
// 现在 str = "hel"

不指定填充字符
如果不提供第二个参数给 resize(),那么新添加的部分(如果有的话)将被初始化为空字符(‘\0’)。

#include   
#include   

int main() 
{
    std::string str = "Hello";
    str.resize(10); // 将字符串增长到10个字符,新添加的部分初始化为'\0' 
    printf("size = %zd\n", str.size());

    // 现在 str = "Hello\0\0\0\0"
    return 0;
}

上面代码的输出为:

size = 10

1.3 std::string 的拼接

std::string 类中的 + 运算符和 append() 函数都用于字符串拼接,但它们在性能特性和使用场景方面有一些区别。
+ 运算符

  • 运算符用于连接两个 std::string 对象或将一个 std::string 对象与一个 C 风格字符串(以空字符 ‘\0’ 结尾的字符数组)连接起来。这个运算符返回一个新的 std::string 对象,该对象包含了两个操作数的内容。如下为样例代码:
std::string str1 = "abc";  
std::string str2 = "123";  
std::string str = str1 + str2; // str 现在包含 "abc123"  

使用 + 运算符时,每次拼接都会创建一个新的 std::string 对象,这涉及到内存分配和可能的数据复制。因此,如果频繁进行大量的字符串拼接操作,使用 + 运算符可能会导致性能下降和内存使用增加。
append() 函数
append() 函数是 std::string 类的一个成员函数,用于将一个字符串追加到现有字符串的末尾。append() 函数有多个重载版本,可以接受不同类型的参数,包括 std::string、C 风格字符串、字符数组以及单个字符。

std::string str1 = "abc";  
std::string str2 = "123";  
std::string str = str1.append(str2); // str 现在包含 "abc123"  

使用 append() 函数时,字符串的拼接是在现有 std::string 对象的内存块中进行的,这意味着不需要分配新的内存块来存储结果。因此,在需要频繁拼接字符串的情况下,使用 append() 函数通常比使用 + 运算符更高效。
总体而言,如果需要频繁地拼接大量字符串,那么使用 append() 函数可能是更好的选择。然而,对于简单的、不频繁的拼接操作,使用 + 运算符可能更加简洁和直观。

1.4 std::string 的比较

相等比较以及不等于比较是 std::string 类型最常用的两种比较方法,分别对应 == 和 != 运算符。
== 运算符
用于判断两个字符串是否相等。如果两个字符串的内容完全相同,则返回 true ,否则返回 false 。如下为样例代码:

std::string str1 = "abc";  
std::string str2 = "123";  
if (str1 == str2) 
{  
    // 字符串相等  
} 
else 
{  
    // 字符串不相等  
}

!= 运算符
用于判断两个字符串是否不相等。如果两个字符串的内容不同,则返回 true ,否则返回 false 。
其他比较运算符
除了这两种基本的比较运算符外,std::string 还提供了其他比较运算符,如 < 、 <= 、 > 和 >= ,这些运算符按照字典顺序对字符串进行比较。如下为样例代码:

std::string str1 = "abc";  
std::string str2 = "123";  
if (str1 < str2) 
{  
    // str1小于str2  
} 
else if (str1 > str2) 
{  
    // str1大于str2  
} 
else 
{  
    // str1等于str2  
}

注意事项
std::string 的比较是区分大小写的,即大写字母和小写字母被视为不同的字符。此外,std::string 的比较还考虑了字符串的结束符 \0 ,因此即使两个字符串的长度相同且内容相同,但如果一个字符串以 \0 结尾而另一个字符串不是,那么这两个字符串在比较时也会被认为是不相等的。

1.5 std::string 的查找和替换

查找
(1)find()
find() 方法用于在字符串中查找第一次出现指定子字符串或字符的位置。如果找到了,它返回子字符串或字符的起始位置的索引;如果没有找到,它返回 std::string::npos 。如下为样例代码:

std::string str = "abc123";  
size_t pos = str.find("c1"); // 查找 "c1"   
if (pos != std::string::npos) 
{  
    // 找到,pos 是 "c1" 的起始位置  
} else {  
    // 没有找到  
}

(2)rfind()
rfind() 方法类似于find() ,但是它从字符串的末尾开始向前查找。
(3)find_first_of()
find_first_of() 方法查找字符串中第一个出现的指定字符集中的字符。
(4)find_last_of()
find_last_of() 方法查找字符串中最后一个出现的指定字符集中的字符。
(5)find_first_not_of()
find_first_not_of() 方法查找字符串中第一个不包含在指定字符集中的字符。
(6)find_last_not_of()
find_last_not_of() 方法查找字符串中最后一个不包含在指定字符集中的字符。
替换
(1)replace()
replace()方法用于替换字符串中的一部分内容。它有多种重载形式,可以替换指定位置的单个字符、替换指定位置的子字符串,或者替换第一次出现的指定子字符串。如下为样例代码:

std::string str = "abc123";  
str.replace(2, 1, "dd"); // 从索引 2 开始,替换 1 个字符为 "dd"  
// 现在 str 是 "abdd123" 

(2)assign()
assign()方法可以用于替换整个字符串的内容。它接受一个字符串或字符数组作为参数,并将当前字符串的内容设置为该参数的内容。如下为样例代码:

std::string str = "abc123";  
str.assign(dd); // 替换整个字符串 
// 现在 str 是 "dd" 

1.6 std::string 子字符串

substr()方法用于获取字符串中的一个子字符串。它接受两个参数:起始位置和子字符串的长度。如下为样例代码:

std::string str = "abc123";  
std::string subStr = str.substr(2, 3); // 从索引 2 开始,长度为 3 的子字符串  
// subStr 现在是 "c12"

如果省略第二个参数(子字符串的长度),则substr()将从起始位置一直到字符串的末尾。如下为样例代码:

std::string str = "abc123";  
std::string subStr = str.substr(2); // 从索引 2 开始到字符串末尾的子字符串  
// subStr 现在是 "c123"

1.7 std::string 插入和删除

插入
insert() 方法用于在字符串的指定位置插入字符或子字符串。它有多个重载版本,允许以不同的方式插入内容。如下为样例代码:

std::string str = "abc123";  
str.insert(1, "dd"); // 在索引 1 的位置插入子字符串 "dd" 
// 现在 str 是 "addbc123"

删除
erase() 方法用于删除字符串中的字符或子字符串。同样,它也有多个重载版本。如下为样例代码:

std::string str = "abc123";
str.erase(1, 3); // 从索引1开始删除3个字符
// 现在 str 是 "a23"

2 std::string 与其他类型的相互转换

std::string 可以与其他数据类型进行相互转换。以下是一些常见的转换示例:
转换为整数类型
使用 std::stoi、std::stol、std::stoul 等函数可以将 std::string 转换为整数类型(如 int、long、unsigned long 等)。如下为样例代码:

std::string str = "12345";  
int val = std::stoi(str); // 将字符串转换为整数

如果字符串不能被解析为有效的整数,std::stoi 等函数将抛出 std::invalid_argument 异常,或者如果转换结果超出了目标类型的表示范围,将抛出 std::out_of_range 异常。
转换为浮点数类型
使用 std::stof、std::stod 等函数可以将 std::string 转换为浮点数类型(如 float、double)。如下为样例代码:

std::string str = "3.14159";  
float val1 = std::stof(str); // 将字符串转换为单精度浮点数
double val2 = std::stod(str); // 将字符串转换为双精度浮点数

同样,如果字符串不能被解析为有效的浮点数,这些函数将抛出异常。
从整数或浮点数转换为 std::string
使用 std::to_string 函数可以将整数或浮点数转换为 std::string 。如下为样例代码:

int val1 = 12345;  
std::string str1 = std::to_string(val1); // 将整数转换为字符串  
  
double val2 = 3.14159;  
std::string str2 = std::to_string(val2); // 将双精度浮点数转换为字符串

从 std::string 转换为字符数组(C字符串)
使用 c_str() 方法可以将 std::string 转换为字符数组(C字符串)。如下为样例代码:

std::string str = "abc123";  
const char* chStr = str.c_str(); // 获取指向字符串内容的指针

注意:c_str() 返回的是一个指向 std::string 内部数据的常量指针,该数据在 std::string 对象生命周期内有效。如果需要在 std::string 对象之外保留这个字符串,则需要使用 memcpy 做字符串复制。
从字符数组(C字符串)转换为 std::string
可以直接将C风格的字符串(字符数组)赋值给 std::string。如下为样例代码:

const char* chStr = "abc123";  
std::string str(chStr); // 使用C字符串初始化 std::string
std::string str2;  
str2.assign(chStr); // 也可以使用 assign() 方法

3 std::string 扩展应用

虽然 std::string 已经提供了很多方法,比如查找、替换等,但是相比于 Java、JS等语言,还是缺少一些比较常用的方法,如 trim() 方法, replace() 方法、split() 方法等。

3.1 去除字符串两端的空白字符(trim() 方法)

std::string 没有内置的 trim 方法来去除字符串两端的空白字符(如空格、制表符和换行符)。可以通过编写辅助函数来实现这一功能。如下为实现代码:

#include   
#include   
#include   
  
std::string trim(const std::string& str) 
{  
    auto trimmed = str;  
    trimmed.erase(trimmed.begin(), std::find_if_not(trimmed.begin(), trimmed.end(),  
        [](unsigned char ch){ return std::isspace(ch); }));  
    trimmed.erase(std::find_if_not(trimmed.rbegin(), trimmed.rend(),  
        [](unsigned char ch){ return std::isspace(ch); }).base(), trimmed.end());  
    return trimmed;  
}  
  
int main() {  
    std::string str = "   123abc   ";  
    std::string trimmedStr = trim(str);  
    // trimmedStr 现在包含 "123abc"  
    return 0;  
}

3.2 替换所有子字符串(replace() 方法)

std::string 类没有内置的直接替换所有子字符串的方法。可以在一个循环中多次调用 find() 和 replace() 方法,直到 find () 方法返回 std::string::npos ,表示没有更多的匹配项。如下为实现代码:

#include   
#include   

void replaceAll(std::string& str, const std::string& strFrom, const std::string& strTo) 
{
	size_t pos = 0;
	while ((pos = str.find(strFrom, pos)) != std::string::npos)
	{
		str.replace(pos, strFrom.length(), strTo);
		pos += strTo.length(); // 更新搜索起始位置  
	}
}

int main() 
{
	std::string str = "abcdefabcdef";
	replaceAll(str, "abc", "123");
	// str 现在为 "123def123def"  
	return 0;
}

3.3 分割子字符串(split() 方法)

std::string 没有内置的 split 方法来分割字符串。可以使用 std::istringstream 以及 std::getline 函数来分割字符串。如下为实现代码:

#include   
#include   
#include   
#include   

std::vector<std::string> split(const std::string& str, char delimiter) 
{
	std::vector<std::string> tokens;
	std::istringstream tokenStream(str);
	std::string token;

	while (std::getline(tokenStream, token, delimiter)) 
	{
		tokens.push_back(token);
	}

	return tokens;
}

int main() {
	std::string str = "abc,123,def";
	char delimiter = ',';
	std::vector<std::string> tokens = split(str, delimiter);

	// tokens 现在为 {"abc","123","def"}

	return 0;
}

你可能感兴趣的:(突破编程_C++_基础教程,c++)