🎯 一、string类:现代C++字符串处理
1.1 string类的诞生背景
在C语言中,字符串以 \0 结尾,操作函数(如strcpy、strcat)与数据分离,需要手动管理内存,极易造成越界访问。C++的string类将这些操作封装,提供了安全、高效的字符串处理方式。
1.2 string类核心接口详解
1.2.1 构造与赋值
cpp
#include <string>
#include <iostream>
using namespace std;
void TestString() {
// 1. 默认构造
string s1;
// 2. C字符串构造
string s2("hello world");
// 3. 拷贝构造
string s3(s2);
// 4. n个字符构造
string s4(5, 'A'); // "AAAAA"
// 5. 赋值操作
s1 = "Hello";
s2 = s1;
// 6. assign函数
s3.assign("C++ STL", 3); // "C++"
}
1.2.2 容量操作
cpp
void TestCapacity() {
string str = "Hello";
cout << "size: " << str.size() << endl; // 5
cout << "length: " << str.length() << endl; // 5
cout << "capacity: " << str.capacity() << endl;
cout << "empty: " << str.empty() << endl; // 0
// 调整大小
str.resize(10, '!'); // "Hello!!!!!"
cout << str << endl;
// 预分配空间(避免多次扩容)
str.reserve(100);
cout << "new capacity: " << str.capacity() << endl;
}
1.2.3 访问与遍历
cpp
void TestAccess() {
string str = "Hello World";
// 1. 下标访问
for (size_t i = 0; i < str.size(); ++i) {
cout << str[i] << " "; // 或使用 str.at(i)
}
cout << endl;
// 2. 迭代器
for (auto it = str.begin(); it != str.end(); ++it) {
cout << *it << " ";
}
cout << endl;
// 3. 范围for(C++11)
for (char ch : str) {
cout << ch << " ";
}
cout << endl;
// 4. 反向迭代器
for (auto rit = str.rbegin(); rit != str.rend(); ++rit) {
cout << *rit << " ";
}
cout << endl;
}
1.2.4 修改操作
cpp
void TestModify() {
string str = "Hello";
// 1. 追加
str += " World"; // "Hello World"
str.append("!!!"); // "Hello World!!!"
str.push_back('!'); // "Hello World!!!!"
// 2. 插入
str.insert(5, " C++"); // "Hello C++ World!!!!"
// 3. 删除
str.erase(5, 4); // "Hello World!!!!"
str.erase(str.begin() + 5); // 删除位置5的字符
// 4. 替换
str.replace(6, 5, "STL"); // "Hello STL!!!!"
// 5. 交换
string s2 = "Bye";
swap(str, s2);
}
1.2.5 查找操作
cpp
void TestFind() {
string str = "Hello World Hello C++";
// 查找子串
size_t pos = str.find("Hello");
if (pos != string::npos) {
cout << "Found at: " << pos << endl; // 0
}
// 从指定位置查找
pos = str.find("Hello", 1);
cout << "Found at: " << pos << endl; // 12
// 反向查找
pos = str.rfind("Hello");
cout << "rfind: " << pos << endl; // 12
// 查找字符
pos = str.find_first_of("aeiou");
cout << "First vowel at: " << pos << endl; // 1
pos = str.find_last_of("aeiou");
cout << "Last vowel at: " << pos << endl; // 17
}
1.2.6 获取子串与比较
cpp
void TestSubstrCompare() {
string str = "Hello World";
// 获取子串
string sub = str.substr(6, 5); // "World"
cout << sub << endl;
// 比较
string s1 = "abc";
string s2 = "abd";
cout << s1.compare(s2) << endl; // -1 (s1 < s2)
cout << (s1 == s2) << endl; // 0
cout << (s1 < s2) << endl; // 1
}
1.3 string类模拟实现
1.3.1 传统深拷贝实现
cpp
class String {
public:
// 构造函数
String(const char* str = "") {
if (str == nullptr) {
_str = new char[1];
*_str = '\0';
} else {
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
}
// 拷贝构造函数(深拷贝)
String(const String& other) {
_str = new char[strlen(other._str) + 1];
strcpy(_str, other._str);
}
// 赋值运算符重载(传统写法)
String& operator=(const String& other) {
if (this != &other) {
// 先申请新空间
char* temp = new char[strlen(other._str) + 1];
strcpy(temp, other._str);
// 释放旧空间
delete[] _str;
// 赋值新空间
_str = temp;
}
return *this;
}
// 现代写法(利用拷贝构造+交换)
String& operator=(String other) {
swap(_str, other._str);
return *this;
}
// 析构函数
~String() {
delete[] _str;
_str = nullptr;
}
// 其他接口...
private:
char* _str;
};
1.3.2 写时拷贝(Copy-On-Write)简介
写时拷贝是一种优化技术,多个对象共享同一份资源,只有当某个对象需要修改时,才进行深拷贝。
// 引用计数实现写时拷贝
cpp
class CowString {
private:
struct StringData {
char* _str;
int _refCount; // 引用计数
StringData(const char* str) {
_str = new char[strlen(str) + 1];
strcpy(_str, str);
_refCount = 1;
}
~StringData() {
delete[] _str;
}
};
StringData* _data;
// 写时拷贝:需要修改时创建副本
void CopyOnWrite() {
if (_data->_refCount > 1) {
StringData* newData = new StringData(_data->_str);
--_data->_refCount;
_data = newData;
}
}
public:
// ... 其他成员函数
};
1.4 string类面试题实战
1.4.1 仅反转字母
https://leetcode.cn/problems/reverse-only-letters/description/
cpp
class Solution {
public:
string reverseOnlyLetters(string s) {
if (s.empty()) return s;
int left = 0, right = s.size() - 1;
while (left < right) {
while (left < right && !isalpha(s[left])) ++left;
while (left < right && !isalpha(s[right])) --right;
swap(s[left++], s[right--]);
}
return s;
}
};
1.4.2 第一个只出现一次的字符
cpp
class Solution {
public:
int firstUniqChar(string s) {
int count[256] = {0};
// 统计每个字符出现的次数
for (char ch : s) {
count[ch]++;
}
// 再次遍历找到第一个出现一次的字符
for (int i = 0; i < s.size(); i++) {
if (count[s[i]] == 1) {
return i;
}
}
return -1;
}
};
1.4.3 字符串相加
https://leetcode.cn/problems/add-strings/description/
cpp
class Solution {
public:
string addStrings(string num1, string num2) {
int i = num1.size() - 1;
int j = num2.size() - 1;
int carry = 0;
string result = "";
while (i >= 0 || j >= 0 || carry > 0) {
int n1 = i >= 0 ? num1[i] - '0' : 0;
int n2 = j >= 0 ? num2[j] - '0' : 0;
int sum = n1 + n2 + carry;
result.push_back(sum % 10 + '0');
carry = sum / 10;
i--;
j--;
}
reverse(result.begin(), result.end());
return result;
}
};
🎯 二、vector容器:动态数组深度解析
2.1 vector基本特性
vector是C++标准模板库中的动态数组,支持快速随机访问,在尾部插入删除高效,在中间插入删除需要移动元素。
2.2 vector核心接口
2.2.1 构造与初始化
cpp
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
void TestVectorConstruct() {
// 1. 默认构造
vector<int> v1;
// 2. 指定大小和初始值
vector<int> v2(5, 1); // 5个1
// 3. 使用迭代器范围构造
int arr[] = {1, 2, 3, 4, 5};
vector<int> v3(arr, arr + 5);
// 4. 拷贝构造
vector<int> v4(v3);
// 5. 列表初始化(C++11)
vector<int> v5 = {1, 2, 3, 4, 5};
// 6. 移动构造(C++11)
vector<int> v6(std::move(v5));
}
2.2.2 容量与大小管理
cpp
void TestCapacity() {
vector<int> v;
// 容量增长特性(不同编译器实现不同)
cout << "初始容量: " << v.capacity() << endl;
for (int i = 0; i < 20; ++i) {
v.push_back(i);
cout << "size: " << v.size()
<< ", capacity: " << v.capacity() << endl;
}
// VS下按1.5倍增长,g++下按2倍增长
// 使用reserve预分配空间
vector<int> v2;
v2.reserve(100); // 预分配100个元素空间
cout << "预分配后容量: " << v2.capacity() << endl;
}
2.2.3 访问元素
cpp
void TestAccess() {
vector<int> v = {1, 2, 3, 4, 5};
// 1. 下标访问
cout << "v[2] = " << v[2] << endl; // 3
cout << "v.at(2) = " << v.at(2) << endl; // 3
// 2. 访问首尾元素
cout << "front: " << v.front() << endl; // 1
cout << "back: " << v.back() << endl; // 5
// 3. 获取底层数组指针
int* p = v.data();
cout << "data[2] = " << p[2] << endl; // 3
// 4. 范围for遍历
for (int num : v) {
cout << num << " ";
}
cout << endl;
}
2.2.4 修改操作
cpp
void TestModify() {
vector<int> v = {1, 2, 3};
// 1. 添加元素
v.push_back(4); // {1, 2, 3, 4}
v.emplace_back(5); // {1, 2, 3, 4, 5} C++11更高效
// 2. 插入元素
v.insert(v.begin() + 1, 99); // {1, 99, 2, 3, 4, 5}
// 3. 删除元素
v.pop_back(); // 删除最后一个
v.erase(v.begin() + 1); // 删除指定位置
v.erase(v.begin(), v.begin() + 2); // 删除范围
// 4. 清空
v.clear();
// 5. 交换
vector<int> v2 = {6, 7, 8};
v.swap(v2);
}
2.3 迭代器失效问题详解
2.3.1 插入操作导致迭代器失效
cpp
void TestIteratorInvalidation1() {
vector<int> v = {1, 2, 3, 4, 5};
auto it = v.begin();
// 插入元素可能导致扩容,使迭代器失效
for (int i = 0; i < 10; ++i) {
v.push_back(i); // 可能触发扩容
// it 可能失效!
}
// 正确做法:每次插入后重新获取迭代器
it = v.begin();
}
2.3.2 删除操作导致迭代器失效
cpp
void TestIteratorInvalidation2() {
// 错误示例:删除偶数
vector<int> v = {1, 2, 3, 4, 5, 6};
auto it = v.begin();
while (it != v.end()) {
if (*it % 2 == 0) {
v.erase(it); // 删除后it失效,再++会出错
}
++it; // ERROR!
}
// 正确做法1:使用erase返回值
it = v.begin();
while (it != v.end()) {
if (*it % 2 == 0) {
it = v.erase(it); // erase返回下一个有效迭代器
} else {
++it;
}
}
// 正确做法2:使用remove-erase惯用法
v.erase(remove_if(v.begin(), v.end(),
[](int x) { return x % 2 == 0; }),
v.end());
}
2.3.3 resize/reserve导致迭代器失效
cpp
void TestIteratorInvalidation3() {
vector<int> v = {1, 2, 3, 4, 5};
auto it = v.begin();
v.reserve(100); // 扩容,it失效
// v.resize(100); // 同样会导致迭代器失效
// 正确:操作后重新获取迭代器
it = v.begin();
}
2.4 vector模拟实现关键点
2.4.1 基本框架
cpp
template<class T>
class Vector {
public:
typedef T* iterator;
typedef const T* const_iterator;
Vector() : _start(nullptr), _finish(nullptr), _endOfStorage(nullptr) {}
// 拷贝构造、赋值运算符等...
iterator begin() { return _start; }
iterator end() { return _finish; }
size_t size() const { return _finish - _start; }
size_t capacity() const { return _endOfStorage - _start; }
private:
iterator _start; // 指向第一个元素
iterator _finish; // 指向最后一个元素的下一个位置
iterator _endOfStorage; // 指向存储空间末尾
};
2.4.2 push_back实现
cpp
void push_back(const T& value) {
// 检查是否需要扩容
if (_finish == _endOfStorage) {
size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newCapacity);
}
// 放置元素
*_finish = value;
++_finish;
}
2.4.3 reserve实现(深拷贝问题)
cpp
void reserve(size_t n) {
if (n > capacity()) {
size_t oldSize = size();
T* newStart = new T[n]; // 申请新空间
// 关键:不能使用memcpy(浅拷贝)
// memcpy(newStart, _start, sizeof(T) * oldSize);
// 必须使用深拷贝
for (size_t i = 0; i < oldSize; ++i) {
// 调用拷贝构造或placement new
new (newStart + i) T(_start[i]);
}
// 释放旧空间(调用析构函数)
for (size_t i = 0; i < oldSize; ++i) {
_start[i].~T();
}
delete[] _start;
_start = newStart;
_finish = _start + oldSize;
_endOfStorage = _start + n;
}
}
2.5 vector经典应用
2.5.1 杨辉三角
cpp
class Solution {
public:
vector<vector<int>> generate(int numRows) {
vector<vector<int>> triangle(numRows);
for (int i = 0; i < numRows; ++i) {
triangle[i].resize(i + 1, 1); // 每行初始化为1
// 计算中间值(从第三行开始)
for (int j = 1; j < i; ++j) {
triangle[i][j] = triangle[i-1][j-1] + triangle[i-1][j];
}
}
return triangle;
}
};
2.5.2 只出现一次的数字
https://leetcode.cn/problems/single-number/description/
cpp
class Solution {
public:
int singleNumber(vector<int>& nums) {
int result = 0;
for (int num : nums) {
result ^= num; // 异或运算
}
return result;
}
};
2.5.3 删除有序数组中的重复项
https://leetcode.cn/problems/remove-duplicates-from-sorted-array/description/
cpp
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
if (nums.empty()) return 0;
int slow = 0;
for (int fast = 1; fast < nums.size(); ++fast) {
if (nums[fast] != nums[slow]) {
++slow;
nums[slow] = nums[fast];
}
}
return slow + 1;
}
};
