C++ STL容器 —— string

一、概述

std::string 是 C++ 标准模板库(STL)中的字符串类,用于表示和操作字符序列。与传统的 C 风格字符串(即字符数组)相比,std::string 提供了更高层次的抽象和更丰富的功能接口,使得字符串的操作更加安全和便捷。它内部管理动态内存,避免了手动管理内存的复杂性。

std::string 的一些显著特点包括:

  • 动态内存管理:自动处理内存分配和释放,避免手动管理内存。
  • 丰富的接口:支持各种字符串操作,如查找、拼接、删除、替换等。
  • 迭代器支持:可以像容器一样通过迭代器来遍历和操作字符串。

二、使用

要在 C++ 程序中使用 <string> 库,首先需要包含这个头文件:

#include <string>

1、初始化

// string constructor
#include <iostream>
#include <string>

int main()
{

	// 1. 默认构造函数 string();
	// 功能:创建一个空字符串对象
	std::string s1;

	// 2. 拷贝构造函数 string(const string& str);
	// 功能:使用已有字符串 s0 拷贝构造 s2
	std::string s0("Initial string"); // 初始化字符串 "Initial string"
	std::string s2(s0);

	// 3. 子串构造函数 string(const string& str, size_t pos, size_t len = npos);
	// 功能:从 s0 的第 8 个字符开始,取 3 个字符
	std::string s3(s0, 8, 3); // 子串 -> "str"

	// 4.用字符串初始化对象 string(const char* s);
	std::string s4("A character sequence"); // C 风格字符串 -> "A character sequence"

	// 5. 取字符串前n个字符初始化对象 string(const char* s, size_t n);
	std::string s5("Another character sequence", 12); // 前 12 个字符 -> "Another char"

	// 6. 以n个字符初始化对象 string(size_t n, char c);
	// 功能:构造一个由 10 个 'x' 字符组成的字符串
	std::string s6(10, 'x'); // -> "xxxxxxxxxx"

	// 7. 使用字符 ASCII 码构造 string(size_t n, char c);
	// 功能:构造一个由 10 个 ASCII 码为 42(字符 '*')的字符组成的字符串
	std::string s7(10, 42); // 42 是 '*' 的 ASCII 码 -> "**********"

	// 8. 迭代器范围构造 template <class InputIterator> string(InputIterator first, InputIterator last);
	// 功能:从 s0 的开始到第 7 个字符构造字符串
	std::string s8(s0.begin(), s0.begin() + 7); // -> "Initial"

	// 打印输出所有字符串内容
	std::cout << "s1: " << s1 << std::endl;  // 输出为空字符串
	std::cout << "s2: " << s2 << std::endl;  // 输出:Initial string
	std::cout << "s3: " << s3 << std::endl;  // 输出:str
	std::cout << "s4: " << s4 << std::endl;  // 输出:A character sequence
	std::cout << "s5: " << s5 << std::endl;  // 输出:Another char
	std::cout << "s6: " << s6 << std::endl;  // 输出:xxxxxxxxxx
	std::cout << "s7: " << s7 << std::endl;  // 输出:**********
	std::cout << "s8: " << s8 << std::endl;  // 输出:Initial

	return 0;
}

2、迭代器

C++ 标准库中的 std::string 实际上是一个动态字符数组,可以使用 STL(标准模板库)的迭代器来遍历、操作和访问字符串的内容。迭代器是一种抽象的指针,用于遍历容器中的元素。

常见的 std::string 迭代器类型
  • str.begin():返回指向字符串第一个字符的迭代器。
  • str.end():返回指向字符串最后一个字符的下一个位置的迭代器,即不包含任何内容的位置。
  • str.rbegin():返回指向字符串末尾的反向迭代器,反向遍历字符串。
  • str.rend():返回指向字符串第一个字符前一个位置的反向迭代器。
  • str.cbegin():返回指向字符串第一个字符的常量迭代器,常量迭代器不能修改内容。
  • str.cend():返回指向字符串末尾的常量迭代器
  • str.crbegin():返回指向字符串末尾的常量反向迭代器
  • str.crend():返回指向字符串第一个字符前一个位置常量反向迭代器
  1. 正向迭代器:遍历字符串从头到尾。使用 begin()end()
  2. 反向迭代器:遍历字符串从尾到头。使用 rbegin()rend()
  3. 常量迭代器:遍历字符串只能读取,不能修改。使用 cbegin()cend()/ crbegin()crend()
#include <iostream>
#include <string>

int main() {
    std::string str = "Hello, World!";

    // 1. 正向迭代器
    std::string::iterator it = str.begin();  // 定义正向迭代器
    std::cout << "Using forward iterator: ";
    while (it != str.end()) {
        std::cout << *it << ' ';
        ++it;  // 移动到下一个字符
    }
    std::cout << std::endl;

    // 2. 反向迭代器
    std::string::reverse_iterator rit = str.rbegin();  // 定义反向迭代器
    std::cout << "Using reverse iterator: ";
    while (rit != str.rend()) {
        std::cout << *rit;  // 输出字符
        ++rit;  // 移动到下一个字符
    }
    std::cout << std::endl;

    // 3. 常量迭代器
    std::string::const_iterator cit = str.cbegin();  // 定义常量迭代器
    std::cout << "Using constant iterator: ";
    while (cit != str.cend()) {
        std::cout << *cit << ' ';
        ++cit;  // 移动到下一个字符
    }
    std::cout << std::endl;

    return 0;
}

3、元素访问

string::operator[]:下标运算符,可以用来访问字符串中指定位置的字符。

string::at(pos):和 operator[] 类似,通过 pos 访问字符串中对应位置的字符。

std::string::front():返回字符串的第一个字符的引用,支持读写。

std::string::back():返回字符串的最后一个字符的引用,支持读写。

使用方法:
  • 读取字符char ch = str[i];
  • 修改字符str[i] = 'x';
#include <iostream>
#include <string>

int main() {
    std::string str = "Programming";

    // 1. 使用 operator[] 访问
    std::cout << str[2] << std::endl;  // 输出 'o'

    // 2. 使用 at() 访问并修改
    try {
        std::cout <<  str.at(2) << std::endl;  // 输出 'o'
        str.at(2) = 'X';  // 修改字符
        std::cout <<  str << std::endl;  // 输出 "PrXgramming"
    }
    catch (const std::out_of_range& e) {
        std::cout <<  e.what() << std::endl;
    }

    // 3. 使用 front() 访问并修改
    std::cout <<  str.front() << std::endl;  // 输出 'P'
    str.front() = 'M';  // 修改第一个字符
    std::cout <<  str << std::endl;  // 输出 "MrXgramming"

    // 4. 使用 back() 访问并修改
    std::cout << str.back() << std::endl;  // 输出 'g'
    str.back() = '!';
    std::cout  << str << std::endl;  // 输出 "MrXgrammin!"

    return 0;
}
  • operator[]:通过下标访问字符串中的字符,不进行边界检查,适合需要高效访问和修改的场景。要小心边界检查,因为越界访问会导致未定义行为。

  • at():与 operator[] 类似,但会进行边界检查,如果 pos 超出范围则抛出 std::out_of_range 异常。适合需要安全访问的场景。

  • back()通过 return _data[size() - 1] 返回最后一个字符的引用,如果字符串为空会产生未定义行为。

  • front()通过 return _data[0]返回第一个字符的引用,同样,如果字符串为空会产生未定义行为。

4、长度及容量

  • size()length():返回字符串的当前字符个数。
std::string str = "Hello, World!";
std::cout << "Size: " << str.size() << std::endl;    // 输出: 13
std::cout << "Length: " << str.length() << std::endl; // 输出: 13
  • max_size():返回字符串可以容纳的最大字符数,具体数值与平台相关。
  • capacity():返回分配给字符串的内存容量,可以比字符串的实际长度大,以减少内存的频繁分配。
std::string str = "Hello";
std::cout << "Capacity: " << str.capacity() << std::endl;  // 15
std::cout << "Size: " << str.size() << std::endl;          // 5
  • resize():改变字符串的长度,自动扩展或截断字符串。

resize(size_t n):调整字符串的长度为 n,如果 n 小于当前长度,则截断字符串;如果 n 大于当前长度,则在末尾添加空字符。

resize(size_t n, char c):调整字符串长度为 n,并用字符 c 填充不足的部分。

std::string str = "Hello";

str.resize(10);  // 将长度扩展到10,用默认字符填充
std::cout << "Resized (default): " << str << std::endl;  // 输出 "Hello"

std::cout << "Size: " << str.size() << std::endl;  // 输出 10

str.resize(15, 'X');  // 将长度扩展到15,用字符 'X' 填充
std::cout << "Resized with 'X': " << str << std::endl;  // 输出 "HelloXXXXX"

std::cout << "Size: " << str.size() << std::endl;  // 输出 15
  • resize() 扩展字符串时

    • 如果新的长度大于当前容量,内存会重新分配。
    • 所有迭代器都会失效,因为内存地址发生了变化。
  • resize() 截断字符串时

    • 容量不会改变,不会发生内存重新分配。
    • 指向被截断部分的迭代器会失效。例如,指向截断后被删除的字符的迭代器失效,因为那些字符已经不存在了。
    • 指向未被截断部分的迭代器仍然有效,它们指向的内容没有变化。
  • reserve():为字符串预留至少 n 个字符的内存容量,以减少频繁的内存分配。

reserve() 函数只会调整字符串的容量,而不会改变实际的长度。如果新请求的容量大于当前容量,则会重新分配内存(迭代器失效),否则不做任何操作(迭代器不会失效)。

#include <iostream>
#include <string>

int main() {

    std::string str = "Hello";
    std::cout << str.capacity() << std::endl;  // 15
    std::string::iterator it = str.begin();  // 获取迭代器

    str.reserve(12);  // 预留内存
    std::cout << str.capacity() << std::endl;  //15
    std::cout << *it << std::endl;  //迭代器不失效

    str.reserve(100);  // 预留内存,会重新分配
    std::cout << str.capacity() << std::endl;
    std::cout << *it << std::endl; //迭代器失效


    return 0;
}
  • clear():清空字符串内容,长度变为 0,容量保持不变。
  • empty():检查字符串是否为空。如果字符串长度为 0,则返回 true,否则返回 false

任何对容器进行可能导致重新分配内存的操作(如 resize()push_back() 等),都有可能导致迭代器失效.

5、修饰

(1)append()在结尾添加字符串

string & append(const string & str)//在结尾添加一个string字符串

string & append(const string & str, size_type subpos, size_type sublen)//追加str中从subpos开始的sublen个字符(子串)

string & append(const charT * s)//C语言字符串

string & append(const charT * s, size_type n)//C语言字符串(长度为n的子串)

string & append(size_type n, charT c)//n个字符c

string & append(InputIterator first, InputIterator last)//使用迭代器append

// appending to string
#include <iostream>
#include <string>

int main()
{
	std::string str;
	std::string str2 = "Writing ";
	std::string str3 = "print 10 and then 5 more";

	// 1. append(const string &str)
	// 在字符串末尾追加 string 对象的内容
	str.append(str2); 
	std::cout << str << '\n';  // 输出: Writing 

	// 2. append(const string &str, size_type subpos, size_type sublen)
	// 追加 str3 中从下标 6 开始的 3 个字符
	str.append(str3, 6, 3);  
	std::cout << str << '\n';  // 输出: Writing 10 

	// 3. append(const char *s, size_type n)
	// 追加字符串的前 5 个字符
	str.append("dots are cool", 5); 
	std::cout << str << '\n';  // 输出: Writing 10 dots 

	// 4. append(const char *s)
	// 追加字符串  "here: "
	str.append("here: ");  
	std::cout << str << '\n';  // 输出: Writing 10 dots here: 

	// 5. append(size_type n, char c)
	// 追加 10 个字符 '.'
	str.append(10u, '.'); 
	std::cout << str << '\n';  // 输出: Writing 10 dots here: ..........

	// 6. append(InputIterator first, InputIterator last)
	// 追加迭代器范围 [str3.begin() + 8, str3.end()] 
	str.append(str3.begin() + 8, str3.end());  
	std::cout << str << '\n';  // 输出: Writing 10 dots here: .......... and then 5 more


	return 0;
}

如果 append() 操作导致重新分配内存(即字符串的容量不足以容纳新的数据),则指向原字符串的迭代器都会失效。

(2)push_back(char c)  将单个字符字符添加到串尾

std::string str = "Hello";
std::cout << str << std::endl;  // 输出: "Hello"
// 使用 push_back() 向末尾添加字符
str.push_back('!');
std::cout <<  str << std::endl;  // 输出: "Hello!"

如果 push_back() 导致了内存重新分配,则指向该字符串的迭代器会失效。如果没有发生重新分配,现有的迭代器仍然有效。

(3)assign赋值

string &assign(const char *s);///char*类型的字符串赋给当前字符串

string &assign(const char *s,int n);//用c字符串s开始的n个字符赋值

string &assign(const string &s);//把字符串s赋给当前字符串

string &assign(int n,char c);//用n个字符c赋值给当前字符串

string &assign(const string &s,int start,int n);//把字符串s中从start开始的n个字符赋给当前字符串 string &assign(const_iterator first,const_itertor last);//把first和last迭代器之间的部分赋给字符串

#include <iostream>
#include <string>

int main() {
    std::string str;        // 定义一个空字符串
    std::string s2 = "Hello World!";  // 用于赋值操作的字符串

    // 1. assign(const char *s) 赋值字符串
    str.assign("Hello");
    std::cout << str << std::endl;  // 输出: Hello

    // 2. assign(const char *s, int n)  从字符串取前n个字符赋值
    str.assign("Hello World", 7);
    std::cout << str << std::endl;  // 输出: Hello W

    // 3. assign(const string &s)  用string对象赋值
    str.assign(s2);
    std::cout << str << std::endl;  // 输出: Hello World!

    // 4. assign(int n, char c)  用n个字符c赋值
    str.assign(5, 'A');
    std::cout << str << std::endl;  // 输出: AAAAA

    // 5. assign(const string &s, int start, int n)  取s2中从start 开始的n个字符赋值
    str.assign(s2, 6, 5);
    std::cout << str << std::endl;  // 输出: World

    // 6. assign(const_iterator first, const_iterator last)  用迭代器赋值
    str.assign(s2.begin(), s2.begin() + 7);   // 取s2的前7个字符
    std::cout << str << std::endl;  // 输出: Hello W

    return 0;
}

(4)insert 在字符串中间插入

string & insert(size_type pos, const string & str)//在pos位置插入字符串str

string & insert(size_type pos, const string & str,size_type subpos, size_type sublen)// 在pos位置插入从subpos开始的sublen的子串

string & insert(size_type pos, const charT * s)//在pos位置插入字符串

string & insert(size_type pos, const charT * s, size_type n) // 在pos 插入字符串的前 n个字符

string & insert(size_type pos, size_type n, charT c) //在pos位置插入 n 个字符 c

iterator insert(const_iterator p, size_type n, charT c)//使用迭代器索引插入n个字符c

iterator insert(const_iterator p, charT c) //使用迭代器索引插入单个字符c

iterator insert(iterator p, InputIterator first, InputIterator last)//使用迭代器insert

#include <iostream>
#include <string>

int main() {
    std::string str = "abcdef";
    std::string s2 = "Hello World!";

    // 1. insert(size_type pos, const string & str) 在pos位置插入字符串str
    str.insert(3, s2);
    std::cout  << str << std::endl;  // 输出: abcHello World!def

    // 2. insert(size_type pos, const string & str, size_type subpos, size_type sublen) 
    // 从字符串 s2 的位置 6 开始取 5 个字符插入到位置 3
    str = "abcdef";  // 重置 str
    str.insert(3, s2, 6, 5);  // 插入 "World"
    std::cout  << str << std::endl;  // 输出: abcWorlddef

    // 3. insert(size_type pos, const charT * s)   在pos 插入字符串 
    str = "abcdef";  // 重置 str
    str.insert(3, "12345");
    std::cout  << str << std::endl;  // 输出: abc12345def

    // 4. insert(size_type pos, const charT * s, size_type n)   在pos 插入字符串的前 n个字符
    str = "abcdef";  // 重置 str
    str.insert(3, "12345", 5);
    std::cout << str << std::endl;  // 输出: abc12345def

    // 5. insert(size_type pos, size_type n, charT c)    在pos位置插入 n 个字符 c
    str = "abcdef";  // 重置 str
    str.insert(3, 5, '*');
    std::cout  << str << std::endl;  // 输出: abc*****def

    // 6. insert(const_iterator p, size_type n, charT c)    在迭代器索引位置插入 n 个字符 c
    str = "abcdef";  // 重置 str
    str.insert(str.begin() + 4, 3, '#');
    std::cout << str << std::endl;  // 输出: abcd###ef

    // 7. insert(const_iterator p, charT c)   在迭代器索引位置插入单个字符 c
    str = "abcdef";  // 重置 str
    str.insert(str.begin() + 2, 'B');
    std::cout<< str << std::endl;  // 输出: abBcdef

    // 8. insert(iterator p, InputIterator first, InputIterator last)  使用迭代器insert
    str = "abcdef";  // 重置 str
    str.insert(str.begin() + 3, s2.begin(), s2.begin() + 5);  // 插入 "Hello"
    std::cout << str << std::endl;  // 输出: abcHellodef

    return 0;
}

(5)erase 删除字符串中的特定字符

string & erase(size_type pos=0, size_type len=npos)//从pos处删除len长度的字符

iterator erase(const_iterator p)//删除迭代器所指的单一字符

iterator erase(const_iterator first, const_iterator last)//删除2迭代器中间的字符

#include <iostream>
#include <string>

int main() {
    std::string str = "Hello, World!";

    // 1. erase(size_type pos = 0, size_type len = npos)  从pos位置开始删除 n 个字符
    str.erase(7, 5);  // 删除 ", Wor"
    std::cout <<  str << std::endl;  // 输出: Hello, !

    // 2. erase(const_iterator p)      删除字符串中迭代器索引所在位置
    str = "Hello, World!";  // 重置 str
    str.erase(str.begin() + 2);  // 删除 'l'
    std::cout << str << std::endl;  // 输出: Helo, World!

    // 3. erase(const_iterator first, const_iterator last)    删除first 和last 迭代器之间的内容
    str = "Hello, World!";  // 重置 str
    str.erase(str.begin() + 2, str.begin() + 5);  // 删除 "llo"
    std::cout  << str << std::endl;  // 输出: He, World!

    return 0;
}

pop_back 删除最后一个字符

std::string str = "Hello, World!";

// 使用 pop_back 删除最后一个字符
str.pop_back();  // 删除 '!'
std::cout << str << std::endl;  // 输出: Hello, World

如果对空字符串调用 pop_back() 会产生未定义行为(例如:崩溃)。因此在调用之前,应该通过 empty() 函数检查字符串是否为空。

指向末尾的迭代器失效pop_back() 会使指向最后一个字符(例如 end()rbegin())的迭代器失效,因为最后一个字符被移除。

(7) swap 交换两个字符串

  • void swap (& str);//交换self和str
// swap strings
#include <iostream>
#include <string>

int main()
{
	std::string buyer("money");
	std::string seller("goods");

	std::cout << "Before the swap, buyer has " << buyer;
	std::cout << " and seller has " << seller << '\n';

	seller.swap(buyer);

	std::cout << " After the swap, buyer has " << buyer;
	std::cout << " and seller has " << seller << '\n';
	//输出
	//Before the swap, buyer has money and seller has goods
	//After the swap, buyer has goodsand seller has money
	return 0;
}

swap() 操作只交换以下元数据:

  • 指向字符串数据的指针(这个指针指向堆上的内存)。
  • 字符串的长度(size)。
  • 字符串的容量(capacity)。

只交换了两个字符串对象内部的指针和其他元数据。这意味着,虽然字符串对象的内容被交换了,但指向原始字符串数据的指针没有改变,因此迭代器仍然指向有效的内存位置。

短字符串优化(SSO, Short String Optimization):在许多标准库实现中,短字符串不使用堆内存,而是存储在字符串对象的内部固定缓冲区里。当调用 swap() 时,可能涉及内部指针和内存块的重新分配。

所以字符串小于某个范围(15~23)时,会导致迭代器失效,如果都在堆内存上,迭代器不会失效。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值