【c++】字符串 string 以及与右值结合
std::string
类介绍
std::string
是 C++ 标准库中的一个用于处理字符串的类,定义在头文件 <string>
中。std::string
封装了一个可变长度的字符数组,可以动态地存储和操作文本字符串。
在使用char类型指针指向字符数据时
例如字符串”yyyyyyy“字符串存储在数据区,并且是只读属性,多个char类型指针指向同一字符串”yyyyyyy“,他们的地址是一样的。不能够对其进行修改。
如果要对其修改,使用一个可以修改的字符数组或者动态分配的内存来存储字符串
std::string
内部的基本结构通常由两个主要部分组成:
- 字符数组:用于存储字符串的字符数据。
- 大小和容量:存储当前字符串的长度(字符数)和容量(分配的内存大小)。
string 对象占据内存大小
string 对象占据内存大小 在vs2022 下面是40字节 这指的是string对象大小 与存储的数据无关
string 容量默认 在vs2022 下面是15字节
int main() {int main() {string s;cout << "-----------s-------------------" << endl;cout << "string容量:" << s.capacity() << endl;cout << "string大小:" << sizeof(s) << endl;cout << "------------s1------------------" << endl;string s1 = "abc";cout << "string容量:" << s1.capacity() << endl;cout << "string大小:" << sizeof(s1) << endl;cout << "--------------------------------" << endl;vector<string> a = { "12","34","56" };cout << sizeof(a[0]) << "\n";cout << endl;return 0;
}
std::string
std::string
的内存管理是其核心特性之一。它负责字符串内存的分配、扩展和释放。其内存分配通常分为两部分:字符串内容的存储空间 和 控制信息(如长度、容量、指向字符数组的指针)。
1. 内部实现
字符数据指针(char*): 指向堆上分配的内存区域,用于存储字符串的字符数据。
大小(size_t): 当前字符串的长度,即字符数据的实际使用长度。
容量(size_t): 当前分配的内存容量,即字符数据指针所指向的内存区域的大小。
当字符串的长度超过当前容量时,std::string 会在堆上重新分配更大的内存区域,并将原有的字符数据复制到新的内存区域。
2. 引用计数(Reference Counting)
标准的 std::string
并不直接使用引用计数来管理内存。但是,在一些实现(如 GCC 的 libstdc++)中,std::string
可能采用了 小对象优化(SSO,Short String Optimization) 和 复制-写(Copy-On-Write, COW) 机制,这些机制与引用计数密切相关。
- 小对象优化(SSO):对于小字符串,
std::string
会直接在对象内部存储字符数据(而不是在堆上分配内存),以避免内存分配的开销。通常只有当字符串的长度超过一定阈值时(比如 15 )才会在堆上分配内存。
然而,现代的 C++ 标准库实现(如 C++11 以后)已经放弃了 COW 机制,主要是为了避免多线程环境中的潜在问题。现在,std::string
通常直接在每个对象中管理内存,并在发生拷贝时进行深拷贝。
在 C++ 中,std::string
类通过小字符串优化(Small String Optimization,SSO)来提高短字符串的性能。对于长度较短的字符串,std::string
会直接在对象内部存储字符数据,而不是在堆上分配内存,以避免内存分配的开销。通常,只有当字符串的长度超过一定阈值时(例如 15 个字符),std::string
才会在堆上分配内存。
#include <iostream>
#include <string>int main() {std::string shortStr = "Hello"; // 长度为 5 的短字符串std::string longStr = "This is a much longer string."; // 长度超过 15 的长字符串std::cout << "短字符串容量:" << shortStr.capacity() << std::endl;std::cout << "长字符串容量:" << longStr.capacity() << std::endl;return 0;
}
输出示例:
短字符串容量:15
长字符串容量:32
shortStr
是一个长度为 5 的短字符串,而 longStr
是一个长度超过 15 的长字符串。由于 shortStr
的长度小于 15,std::string
会在对象内部直接存储字符数据,而不是在堆上分配内存。因此,shortStr
的容量为 15。而 longStr
的长度超过了 15,std::string
会在堆上分配内存来存储字符数据,因此其容量为 32。
2. 扩展空间(Capacity)
std::string
对象的内存分配通常会预先为字符串分配一个比实际长度稍大的空间,称为“容量(capacity)”。这种容量是为了避免在字符串增长时频繁的内存重新分配,通常采用指数增长的策略,即每次容量增加时都会分配一个比之前大一倍的内存空间。
- 大小(size):表示当前字符串的长度(字符数)。
- 容量(capacity):表示为当前字符串分配的内存大小(以字符为单位)。容量可能大于实际的字符串长度,目的是为了优化内存分配和避免频繁的内存分配。
- 扩展机制:当字符串需要增长时(例如插入、拼接等),如果现有容量不足以容纳新的字符,
std::string
会分配一个更大的内存区域,将旧的字符数据复制到新的区域,并释放原来的内存。
#include <iostream>
#include <string>int main() {std::string s = "Hello"; // 长度为 5 的短字符串s += "This is a much longer string."; // 长度超过 15 的长字符串std::cout << "短字符串容量:" << s.capacity() << std::endl;std::cout << "长字符串容量:" << s.capacity() << std::endl;return 0;
}
s = “Hello”;
s += “This is a much longer string.”;
std::string
提供了 reserve()
和 resize()
方法来控制容量和大小的行为:
reserve(n)
:预分配至少n
字符的内存空间,避免在插入字符时频繁地重新分配内存。resize(n)
:改变字符串的大小,如果增加大小,会使用默认值或指定的字符填充。
3. 深拷贝
std::string
实现了深拷贝操作,这意味着当 std::string
对象被复制时,底层的字符数据也会被复制。深拷贝确保每个 std::string
对象有自己的独立数据,并且对其中一个对象的修改不会影响其他对象。
当发生拷贝构造或拷贝赋值时,std::string
会为目标对象分配新的内存,并将源对象的数据复制到目标对象的内存区域。
示例:
#include <iostream>
#include <string>int main() {std::string str1 = "Hello, world!";std::string str2 = str1; // 深拷贝// 修改 str1 后,str2 的值不会受到影响str1[0] = 'h';std::cout << "str1: " << str1 << std::endl; // 输出 "hello, world!"std::cout << "str2: " << str2 << std::endl; // 输出 "Hello, world!"return 0;
}
std::string 类的移动构造和移动赋值
std::string 在 C++11 引入了移动构造函数和移动赋值操作符来进一步提高性能,尤其是在涉及到字符串的传递和返回时,避免了不必要的内存拷贝。
4.1移动构造函数
当使用右值传递 std::string 对象时,std::string 会通过移动构造函数将源对象的字符数据“转移”到目标对象中,而不进行深拷贝。
#include <iostream>
#include <string>std::string create_large_string() {std::string large_str = "A very large string";return large_str; // 使用移动语义返回
}int main() {std::string str = create_large_string(); // 移动构造,避免拷贝std::cout << str << std::endl;
}
4.2移动赋值操作符
当将一个 std::string 的右值赋值给另一个 std::string 时,std::string 会通过移动赋值操作符来转移资源。
#include <iostream>
#include <string>int main() {std::string str1 = "Hello";std::string str2 = "World";str2 = std::move(str1); // 移动赋值,避免拷贝std::cout << str2 << std::endl; // 输出 "Hello"std::cout << str1 << std::endl; // str1 已为空,输出 ""
}