C++初阶之一篇文章让你掌握string类(了解和使用)
1.我们为什么要学习string类
学习 string 类是在 C++ 中非常重要的一步,string 类是 C++ 标准库提供的用于处理字符串的类,它相比 C 语言中的字符串处理函数更为高级、灵活和安全。以下是学习 string 类的一些重要理由:
- 功能强大:string 类提供了丰富的成员函数和操作符,用于处理字符串的拼接、查找、替换、截取、插入等操作。使用 string 类能够更方便地实现复杂的字符串处理任务。
- 安全性:string 类自动处理字符串的内存分配和释放,不会像 C 语言中的字符数组那样容易出现缓冲区溢出等安全问题。使用 string 类能够有效避免许多与字符数组相关的安全漏洞。
- 可读性:使用 string 类可以使代码更加易读,不需要过多关注底层的字符处理,而是通过成员函数和操作符来操作字符串,使代码更加清晰明了。
- 便捷性:string 类可以直接进行赋值、比较和连接等操作,无需额外的库函数支持,让代码编写更为简洁。
- STL 通用性:string 类是 C++ 标准模板库(STL)中的一部分,学习 string 类也是为了更好地理解和使用 STL 的其他组件,如向量、链表、映射等。
- 实际应用广泛:在 C++ 开发中,字符串处理是一个常见的任务,学习 string 类可以使您在实际开发中更加得心应手。
总的来说,学习 string 类对于掌握 C++ 编程是非常重要的。它是处理字符串的常用工具,能够让你更高效地编写代码,提高代码的质量和可读性,并帮助你更好地应对实际开发中的字符串处理任务。
2. 标准库中的string类
2.1 string类的实例化标准
类的实例化标准分为string、u16string、u32string 和 wstring 四种,他们都是 C++ 中用于表示字符串的类,但它们的底层字符类型和编码方式不同:
string:
底层字符类型:char
编码方式:使用单字节编码,通常采用 ASCII 或 UTF-8 编码。
示例:
std::string myString = "Hello, world!";
u16string:
底层字符类型:char16_t
编码方式:使用两个字节表示一个字符,通常采用 UTF-16 编码。
示例:
std::u16string myString = u"Hello, world!";
u32string:
底层字符类型:char32_t
编码方式:使用四个字节表示一个字符,通常采用 UTF-32 编码。
示例:
std::u32string myString = U"Hello, world!";
wstring:
底层字符类型:wchar_t
编码方式:宽字符编码,可以是 UTF-16 或 UTF-32,具体取决于平台。
示例:
std::wstring myString = L"Hello, world!";
需要注意的是,不同编译器和平台对宽字符编码的实现可能不同。在 Windows 平台上,wchar_t 通常是 16 位的,而在一些其他平台上,它可能是 32 位的。因此,在使用 wstring 时需要注意平台的差异。
选择使用哪种字符串类型取决于具体需求。通常情况下,如果程序不涉及多语言字符或 Unicode 字符,使用 string 就足够了。如果需要处理多语言字符或 Unicode 字符,可以根据需要选择 u16string、u32string 或 wstring。
这些差异是因为在编码时,根据不同的语言文字需求,而创建的不同的标准,比如UTF-8编码标准,UTF-8 是一种可变长度的 Unicode 编码方式,它可以表示世界上几乎所有的字符,包括 ASCII 字符和其他国际字符。在 C++ 中,string 类使用单字节 char 类型来存储字符,因此可以直接用于处理 UTF-8 编码的字符串。
UTF-8 编码中,ASCII 字符(0-127范围内的字符)只占用一个字节,而其他字符可能占用多个字节,通常是 2 个或 3 个字节。由于 string 类使用单字节 char 类型,它可以直接处理 UTF-8 编码的字符串,无需进行额外的转换或处理。
以下是一个示例,展示了如何使用 string 类来处理 UTF-8 编码的字符串:
#include <iostream> #include <string> int main() { std::string utf8String = u8"Hello, 世界!"; std::cout << utf8String << std::endl; return 0; }
在上述示例中,我们使用了 u8 前缀来表示一个 UTF-8 字符串,然后将它存储在 string 类型的变量 utf8String 中,并打印输出(这种方式需要编译器编码格式对应UTF-8,若要按照编译器正常编码格式打印,则无需加u8)。
需要注意的是,尽管 string 类适用于 UTF-8 编码,但如果需要处理更大范围的 Unicode 字符(如 Emoji 表情符号等),可能需要使用 u16string 或 u32string 类,因为它们可以更好地处理多字节的 Unicode 字符。在处理 UTF-8 编码的大部分应用场景中,string 类是非常方便和实用的选择。
2.2 了解string
- 字符串是表示字符序列的类
- 标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,但添加了专门用于操作单字节字符字符串的设计特性。
- 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<char, char_traits, allocator>string;
- 不能操作多字节或者变长字符的序列。
- 在使用string类时,必须包含#include头文件以及using namespace std(或是单独展开std::string)
3.string类的常用接口说明
3.1 string类对象的常见构造
函数名称 | 功能说明 |
string(); |
构造空的string类对象,即空字符串 |
string (const string& str); |
拷贝构造函数 |
string (const string& str, size_t pos, size_t len = npos); |
使用另一个 std::string 对象的子串来创建一个新的字符串对象,从给定的位置 pos 复制长度为 len 的子串。 |
string (const char* s); |
用C-string来构造string类对象 |
string (const char* s, size_t n); |
根据部分字符数组来创建一个新的字符串对象 |
string (size_t n, char c); |
string类对象中包含n个字符c |
template <class InputIterator> string (InputIterator first, InputIterator last); |
使用迭代器范围 [first, last) 来创建一个新的字符串对象,范围内的字符将被复制到字符串中。 |
显示详细信息
示例:
1.默认构造函数
default (1) string();
std::string str; // 创建一个空字符串
2.拷贝构造函数
copy (2) string (const string& str);
std::string str1 = "Hello"; std::string str2 = str1; // 使用复制构造函数创建新的字符串对象
3.子串构造函数
substring (3) string (const string& str, size_t pos, size_t len = npos);
std::string str1 = "Hello, World!"; std::string str2 = str1.substr(0, 5); // 使用子串构造函数创建新的字符串对象,内容为 "Hello"
4.字符串字面值构造函数
from c-string (4) string (const char* s);
std::string str = "Hello, World!"; // 使用字符串字面值初始化字符串
5.部分字符数组构造函数
from sequence (5) string (const char* s, size_t n);
const char* str = "Hello, World!"; std::string new_str(str, 5); // 使用字符数组的前5个字符 "Hello" 来创建新的字符串对象
6.字符重复构造函数
fill (6) string (size_t n, char c);
std::string str(5, 'A'); // 创建一个包含5个字符'A'的字符串 "AAAAA"
7.迭代器范围构造函数
range (7) template <class InputIterator> string (InputIterator first, InputIterator last);
std::string str("Hello, World!"); std::string sub_str(str.begin(), str.begin() + 5); // 使用迭代器范围构造函数创建新的字符串对象,内容为 "Hello"
3.2 string类对象的容量操作
函数名称 | 功能说明 |
size | 返回字符串有效字符长度 |
length | 返回字符串有效字符长度 |
max_size | 返回字符串对象能够容纳的最大字符数 |
resize | 用于改变字符串对象的长度 |
capacity | 返回空间总大小 |
reserve | 为字符串预留空间 |
clear | 清空有效字符 |
empty | 检测字符串释放为空串,是返回true,否则返回false |
shrink_to_fit | 要求字符串对象释放多余的内存空间,将容量调整为与字符串长度相同 |
示例:
1.返回字符串有效字符长度
size_type size() const;
string s1("hello world"); //利用有效长度逐个打印字符,并间隔打印 for (size_t i = 0; i < s1.size(); ++i) { cout << s1[i] << ' '; }
2.返回字符串有效字符长度
size_type length() const;
size() 和 length() 是完全等价的,它们都返回字符串对象中字符的数量,没有区别。
这两个函数的作用是相同的,只是命名不同,为了提供更多的编程灵活性和可读性,可以根据个人偏好选择使用 size() 或 length()。
在实际使用中,你可以根据自己的喜好和项目约定,选择使用 size() 或 length() 来获取字符串的长度,两者效果完全相同。一般情况下,size() 更为常用,因为在 C++ 标准库中,很多容器都提供了 size() 成员函数用于获取容器中元素的数量,包括 std::string、std::vector 等。所以,使用 size() 可以保持代码的一致性和易读性。
3.返回字符串对象能够容纳的最大字符数
size_type max_size() const;
std::string str; std::cout << "Max size: " << str.max_size() << std::endl;
max_size() 函数返回字符串对象在当前系统环境下能够容纳的最大字符数,这个值取决于平台和编译器,并且通常是一个非常大的值。它的含义是字符串对象的理论上限,表示在当前平台和环境下,该类型的字符串对象最多能够容纳的字符数量。
4.用于改变字符串对象的长度
void resize (size_type n);void resize (size_type n, charT c);
第一个版本的 resize() 函数用于改变字符串对象的长度。
resize() 会将字符串的长度调整为 n,如果 n 小于当前长度,则会截断字符串;如果 n 大于当前长度,则会在末尾添加空字符以扩展字符串长度。
第二个版本的 resize() 会将字符串的长度调整为 n,同时用字符 c 来填充新添加的字符(仅当 n 大于当前长度时生效)。
示例:
std::string str = "Hello"; str.resize(10); // 调整字符串长度为 10,现在 str 为 "Hello " str.resize(15, '-'); // 调整字符串长度为 15,用字符 '-' 填充,现在 str 为 "Hello-----"
5.返回空间总大小
size_type capacity() const;
capacity() 函数返回当前字符串对象的容量,即字符串对象在不重新分配内存的情况下所能容纳的最大字符数。它的值可能小于或等于 max_size(),取决于当前字符串对象的实际内存分配情况。在 C++ 中,std::string 类采用动态分配内存的方式存储字符串,当字符串的长度超过当前容量时,字符串会自动重新分配更大的内存空间。
6.为字符串预留空间
void reserve (size_type n = 0);
在 C++ 的 std::string 类中,reserve() 是一个成员函数,用于请求字符串对象重新分配内存,以便能够容纳至少指定数量的字符。这个函数在字符串需要频繁增长长度时非常有用,它可以避免多次内存分配和拷贝操作,从而提高性能。
std::string str = "Hello"; std::cout << "Capacity before reserve: " << str.capacity() << std::endl; // 输出当前容量 str.reserve(20); // 请求重新分配至少能容纳20个字符的内存空间 std::cout << "Capacity after reserve: " << str.capacity() << std::endl; // 输出重新分配后的容量
在上面的示例中,我们先输出了字符串 str 的当前容量,然后调用 reserve() 函数请求重新分配至少能容纳 20 个字符的内存空间,最后再次输出字符串 str 的容量。请注意,重新分配内存后,容量可能超过请求的 n 值,这是为了避免频繁的内存重新分配。
使用 reserve() 函数可以优化字符串对象的内存使用,特别是在预知字符串会变得较长时,提前分配足够的内存空间,减少动态内存分配和拷贝操作,从而提高程序的性能。但要注意,过度分配内存可能导致资源浪费,应根据实际情况合理选择容量。
7.清空有效字符
void clear();
使用 clear() 函数会将字符串对象的内容清空,即将其长度设置为 0,但并不会释放内存或缩小容量。它会将字符串对象变为空字符串,等效于给字符串赋值一个空字符串。
std::string str = "Hello, World!"; std::cout << "Before clear: " << str << std::endl; // 输出原字符串内容 str.clear(); // 清空字符串内容,使其变为空字符串 std::cout << "After clear: " << str << std::endl; // 输出空字符串
在上面的示例中,我们先输出字符串 str 的内容,然后调用 clear() 函数将其内容清空,最后再次输出字符串 str,此时已经变为空字符串。
clear() 函数在清空字符串内容时非常方便,尤其在需要重新使用同一个字符串对象或者需要重置字符串内容的情况下,可以使用这个函数来快速清空字符串。请注意,clear() 只清空字符串内容,不会释放内存或改变容量。如果需要释放内存或缩小容量,可以使用 shrink_to_fit() 函数。
8.检测字符串释放为空串
bool empty() const;
empty() 函数返回一个 bool 类型的值,如果字符串对象为空,则返回 true,否则返回 false。
std::string str1 = "Hello"; std::string str2; if (str1.empty()) { std::cout << "str1 is empty." << std::endl; } else { std::cout << "str1 is not empty." << std::endl; } if (str2.empty()) { std::cout << "str2 is empty." << std::endl; } else { std::cout << "str2 is not empty." << std::endl; }
在上面的示例中,我们创建了两个 std::string 对象 str1 和 str2,然后使用 empty() 函数检查它们是否为空。str1 包含字符 “Hello”,所以 str1.empty() 返回 false,而 str2 是一个空字符串,所以 str2.empty() 返回 true。
empty() 函数在实际编程中非常有用,可以用于检查字符串是否为空,从而根据情况进行逻辑判断和处理。
9.释放多余的内存空间
void shrink_to_fit();
该函数用于要求字符串对象释放多余的内存空间,将容量调整为与字符串长度相同。这样可以减少不必要的内存使用。
std::string str = "Hello"; str.reserve(20); // 请求重新分配至少能容纳20个字符的内存空间 // 其他操作... str.shrink_to_fit(); // 释放多余的内存空间,容量调整为与字符串长度相同
使用 reserve() 和 shrink_to_fit() 函数可以有效地控制字符串对象的内存使用,避免不必要的内存分配和释放。请注意,在字符串需要频繁改变长度时,动态地调整容量可能是一个优化的策略。不过,也要根据实际情况进行合理的内存管理,以避免过度分配内存导致的资源浪费。
注意:
- size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()。
- clear()只是将string中有效字符清空,不改变底层空间大小。
- resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。
- reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserve不会改变容量大小。
3.3 string类对象的元素访问
函数名称 | 功能说明 |
operator[] | 访问字符串中指定位置 pos 处的字符 |
at | 访问字符串中指定位置 pos 处的字符 |
back | 获取字符串的第一个字符 |
front | 获取字符串的最后一个字符 |
1. operator[]:
reference operator[](size_type pos);
const_reference operator[](size_type pos) const;
operator[] 函数用于访问字符串中指定位置 pos 处的字符。返回一个引用,允许您读取或修改该位置上的字符。如果使用 const 修饰符,则表示对字符串进行只读访问。
std::string str = "Hello"; char first_char = str[0]; // 获取字符串的第一个字符 'H' str[0] = 'h'; // 修改字符串的第一个字符为 'h',现在 str 为 "hello"
2.at():
reference at(size_type pos);
const_reference at(size_type pos) const;
at() 函数与 operator[] 类似,也用于访问字符串中指定位置 pos 处的字符。不同之处在于,at() 函数会检查索引是否有效,如果索引超出字符串范围,则抛出 std::out_of_range 异常。
std::string str = "Hello"; char first_char = str.at(0); // 获取字符串的第一个字符 'H' str.at(0) = 'h'; // 修改字符串的第一个字符为 'h',现在 str 为 "hello"
3.front():
reference front();
const_reference front() const;
front() 函数用于获取字符串的第一个字符,返回一个引用。如果使用 const 修饰符,则表示对字符串进行只读访问。
std::string str = "Hello"; char first_char = str.front(); // 获取字符串的第一个字符 'H'
4.back():
reference back();
const_reference back() const;
back() 函数用于获取字符串的最后一个字符,返回一个引用。如果使用 const 修饰符,则表示对字符串进行只读访问。
std::string str = "Hello"; char last_char = str.back(); // 获取字符串的最后一个字符 'o'
这些函数提供了多种方式来访问字符串的内容,你可以根据需要选择合适的函数来读取或修改字符串中的字符。请注意,在使用这些函数时要注意索引的有效范围,避免访问越界导致的未定义行为。
3.4 string类对象的Iterators(迭代器)接口
在 C++ 的 std::string 类中,提供了许多 Iterators(迭代器)接口函数,用于遍历和访问字符串中的字符。这些函数允许你以迭代器的方式访问字符串的内容,从而对字符串进行遍历和操作。
以下是 std::string 类的一些 Iterators 接口函数及其介绍和使用方法:
函数名称 | 功能说明 |
begin() 和 end() | begin() 函数返回一个指向字符串首字符的迭代器,而 end() 函数返回一个指向字符串末尾后一个字符的迭代器 |
rbegin() 和 rend() | rbegin() 函数返回一个指向字符串最后一个字符的逆向迭代器,而 rend() 函数返回一个指向字符串首字符前一个位置的逆向迭代器 |
cbegin() 和 cend() | cbegin() 函数返回一个指向字符串首字符的 const 迭代器,而 cend() 函数返回一个指向字符串末尾后一个字符的 const 迭代器(末尾后的一个位置)。 |
crbegin() 和 crend() | crbegin() 函数返回一个指向字符串最后一个字符的 const 逆向迭代器,而 crend() 函数返回一个指向字符串首字符前一个位置的 const 逆向迭代器。 |
1.begin() 和 end():
iterator begin();
const_iterator begin() const;
iterator end();
const_iterator end() const;
begin() 函数返回一个指向字符串首字符的迭代器,而 end() 函数返回一个指向字符串末尾后一个字符的迭代器(末尾后的一个位置)。const 修饰符版本的函数用于在 const 对象上获取 const 迭代器。
string s("hello"); string::iterator it = s.begin(); while (it != s.end()) { cout << *it << " "; ++it; } cout << endl;
2.rbegin() 和 rend():
reverse_iterator rbegin();
const_reverse_iterator rbegin() const;
reverse_iterator rend();
const_reverse_iterator rend() const;
rbegin() 函数返回一个指向字符串最后一个字符的逆向迭代器,而 rend() 函数返回一个指向字符串首字符前一个位置的逆向迭代器。const 修饰符版本的函数用于在 const 对象上获取 const 逆向迭代器。
std::string str = "Hello"; for (auto it = str.rbegin(); it != str.rend(); ++it) { std::cout << *it; // 逆向遍历输出字符串的所有字符 "olleH" }
3.cbegin() 和 cend():
const_iterator cbegin() const;
const_iterator cend() const;
cbegin() 函数返回一个指向字符串首字符的 const 迭代器,而 cend() 函数返回一个指向字符串末尾后一个字符的 const 迭代器(末尾后的一个位置)。
4.crbegin() 和 crend():
const_reverse_iterator crbegin() const;
const_reverse_iterator crend() const;
crbegin() 函数返回一个指向字符串最后一个字符的 const 逆向迭代器,而 crend() 函数返回一个指向字符串首字符前一个位置的 const 逆向迭代器。
这些 Iterators 接口函数提供了多种方式来遍历字符串的内容,并且支持正向和逆向迭代器。你可以根据需要选择合适的迭代器类型来遍历字符串,其中 begin() 和 end() 是最常用的迭代器接口函数。
范围for和迭代器的关系
范围for循环(Range-based for loop)和迭代器(Iterators)是 C++ 中两种不同的用于遍历容器或可迭代对象的方式。它们在语法和用法上有一些区别,但本质上都是用来迭代访问容器或序列中的元素。
- 范围for循环(Range-based for loop):
范围for循环是 C++11 引入的一种语法糖,用于简化容器或序列的遍历。它的语法形式如下:
for (element_type element : container) { // 对每个元素执行的操作 }
在这个循环中,container 是一个可迭代对象,可以是标准容器(如 std::vector、std::list、std::map 等)、数组、字符串等等。element 是容器中的每个元素的副本,可以用于访问和操作该元素。范围for循环会自动遍历容器,并在每次迭代时将容器中的元素赋值给 element,直到容器的末尾为止。
示例:
std::string str = "Hello"; for (char c : str) { std::cout << c << " "; // 输出:H e l l o }
- 迭代器:
迭代器也可以用于遍历 std::string 中的每个字符,它提供了更灵活的方式来访问字符串的内容,可以自定义遍历方式。std::string 类提供了两种迭代器:iterator 和 const_iterator。
iterator:用于修改 std::string 对象中的字符。
const_iterator:用于只读访问 std::string 对象中的字符。
示例:
std::string str = "Hello"; for (std::string::iterator it = str.begin(); it != str.end(); ++it) { std::cout << *it << " "; // 输出:H e l l o }
或者使用 auto 关键字简化迭代器声明:
for (auto it = str.begin(); it != str.end(); ++it) { std::cout << *it << " "; // 输出:H e l l o }
需要注意的是,范围for循环和迭代器都可以用来遍历 std::string 中的字符,范围for循环更简洁方便,特别适用于遍历整个字符串;而迭代器提供了更多灵活性,可以用于实现更复杂的遍历和访问方式,比如只遍历部分字符或以逆序遍历字符串。在实际应用中,你可以根据具体需求选择合适的遍历方式。