系列导航:
第一篇\] C++基础与竞赛优势
第三篇\] 映射与集合的终极形态
第五篇\] 现代语法糖精粹
一、动态数组:彻底告别malloc(手把手教学)
1.1 C程序员熟悉的痛苦场景
假设我们需要处理一个动态增长的整数数组,传统C代码是这样的:
c
int* arr = NULL; // 数组指针
int size = 0; // 当前元素个数
int capacity = 0; // 总容量
// 添加元素
void add_element(int val) {
if(size >= capacity) {
capacity = capacity ? capacity*2 : 1;
arr = realloc(arr, capacity * sizeof(int)); // 可能失败!
}
arr[size++] = val;
}
1.2 C++的救星:vector容器
(1)基本概念
cpp
vector<int> arr; // 创建一个空数组
vector
:动态数组类型(类似C的int[]
但自动扩容)<int>
:模板参数(理解为"数组元素类型")arr
:对象名称(你可以用任何合法变量名)
(2)添加元素(对比教学)
c
/* C版本 */
add_element(42); // 需要手动管理内存
/* C++版本 */
arr.push_back(42); // 自动处理内存
push_back()
:成员函数(类似C结构体中的函数指针)- 参数
42
会被自动添加到数组末尾 - 内存自动翻倍扩容(无需手动realloc)
(3)访问元素(安全版vs快速版)
cpp
// 安全访问(推荐新手使用)
int first = arr.at(0); // 越界会抛出异常
// 快速访问(与C数组相同行为)
int second = arr[1]; // 越界导致未定义行为
二、string:20个必会高效操作详解
1. 字符串构造
C++方式:
cpp
string s1; // 空字符串(类似char s[1] = {0})
string s2("Hello"); // 从C字符串构造(自动计算长度)
string s3(5, 'A'); // "AAAAA"(C中需malloc+循环)
C对比:无需手动分配内存,自动处理'\0'结束符
2. 获取字符串长度
C++方式:
cpp
int len = s.size(); // O(1)时间复杂度
C对比 :strlen(s)
需要O(n)遍历,C++内部维护长度值
3. 访问字符元素
安全访问:
cpp
char c = s.at(2); // 越界抛出异常(建议调试使用)
快速访问:
cpp
char c = s[2]; // 与C数组相同(越界未定义行为)
4. 字符串拼接
C++方式:
cpp
string s = "Hello";
s += " World"; // 自动扩展内存(C需realloc)
s.append("!"); // 等同+=但可指定追加长度
C对比:无需计算目标缓冲区大小,自动管理内存
5. 字符串比较
C++方式:
cpp
if(s1 == s2) { ... } // 直接比较内容
if(s1.compare(0,3,"App") == 0) // 比较前3字符是否"App"
C对比 :替代strcmp
,更直观安全
6. 提取子串
C++方式:
cpp
string sub = s.substr(2, 5); // 从位置2取5字符(自动处理边界)
C对比 :替代strncpy+手动添加'\0'
操作
7. 查找子串
基础查找:
cpp
size_t pos = s.find("World"); // 返回首次出现位置
if(pos != string::npos) { ... }
逆向查找:
cpp
pos = s.rfind('o'); // 从后向前查找字符
8. 替换子串
C++方式:
cpp
s.replace(6, 5, "CPP"); // 从位置6替换5字符为"CPP"
C对比:自动处理内存扩展,无需手动移动字符
9. 插入字符串
cpp
s.insert(5, " dear"); // 在位置5插入字符串(自动后移字符)
10. 删除子串
cpp
s.erase(5, 3); // 从位置5删除3个字符(自动前移字符)
11. 首尾字符处理
cpp
s.front() = 'h'; // 修改首字符(等同s[0])
s.pop_back(); // 删除末尾字符(比C少计算长度)
12. 清空字符串
cpp
s.clear(); // 清空内容(内存保留)
s.shrink_to_fit(); // 释放多余内存(C中需realloc)
13. 判断空字符串
cpp
if(s.empty()) { ... } // 比s.size() == 0更直观
14. 数据指针访问
cpp
const char* cstr = s.c_str(); // 获取C风格字符串(只读)
char* buf = &s[0]; // 直接访问缓冲区(C++11起)
15. 流式处理
cpp
stringstream ss("3.14 hello");
double d; string str;
ss >> d >> str; // d=3.14, str="hello"(类似sscanf)
16. 数值转换
cpp
int num = stoi("42"); // 字符串转int
double d = stod("3.14"); // 转double
string s = to_string(123); // 数值转字符串
17. 大小写转换
cpp
transform(s.begin(), s.end(), s.begin(), ::tolower);
C对比:无需逐个字符处理,一行代码完成
18. 删除空白字符
cpp
s.erase(remove_if(s.begin(), s.end(), ::isspace), s.end());
效果:删除所有空白字符(空格、\t、\n等)
19. 字符串分割
cpp
vector<string> split(const string& s, char delim) {
vector<string> tokens;
stringstream ss(s);
string token;
while(getline(ss, token, delim))
if(!token.empty()) tokens.push_back(token);
return tokens;
}
优势 :替代C中strtok
函数,线程安全且不修改原字符串
20. 正则表达式(C++11)
cpp
regex pattern(R"((\d+).(\d+))"); // 匹配数字(如3.14)
smatch match;
if(regex_search(s, match, pattern)) {
string intPart = match[1]; // 获取第一个捕获组
string decPart = match[2];
}
应用场景:复杂模式匹配,替代C中繁琐的手动解析
总结对比表(C vs C++ string)
操作需求 | C语言实现 | C++ string版本 | 优势总结 |
---|---|---|---|
构造字符串 | char[固定大小]或malloc | 自动内存管理 | 无需预判长度 |
拼接字符串 | strcat可能越界 | +=自动扩展 | 安全便捷 |
获取长度 | strlen遍历计数 | size() O(1)获取 | 效率提升 |
子串替换 | 手动memmove+修改 | replace自动处理 | 避免内存操作错误 |
字符串分割 | strtok破坏原字符串 | 非破坏性split函数 | 保留原数据 |
模式匹配 | 手写解析循环 | 正则表达式 | 复杂模式易实现 |
通过掌握这20个核心操作,可替代C中90%的字符串处理代码,同时提升安全性和开发效率。每个操作都经过编译器高度优化,在算法竞赛中可放心使用。
三、邻接表示例深度解析
3.1 C语言版邻接表
c
struct Node {
int val;
struct Node* next;
};
struct Node* graph[100]; // 100个节点的邻接表
// 添加边(1->5)
struct Node* newNode = malloc(sizeof(struct Node));
newNode->val = 5;
newNode->next = graph[1];
graph[1] = newNode;
3.2 C++ vector版
cpp
// 创建100个节点的邻接表(每个节点对应一个vector)
vector<int> graph[100];
// 添加边(1连接5)
graph[1].push_back(5);
// 解读:
// 1. graph是包含100个vector的数组
// 2. graph[1]访问第2个vector(下标从0开始)
// 3. push_back(5)向这个vector添加元素5
3.3 遍历邻居(对比教学)
c
/* C遍历 */
struct Node* curr = graph[1];
while(curr != NULL) {
printf("%d ", curr->val);
curr = curr->next;
}
/* C++遍历 */
for(int neighbor : graph[1]) { // 自动遍历每个元素
cout << neighbor << " ";
}
四、避免Segmentation Fault的6大实践
4.1 典型错误场景
cpp
vector<int> vec(3); // [0,0,0]
cout << vec[5]; // 越界访问(未定义行为)
string s;
cout << s[0]; // 访问空字符串(崩溃)
4.2 防御性编程技巧
- 始终检查空容器
cpp
if(!vec.empty()) {
// 安全访问vec[0]
}
- 使用at()替代[]
cpp
try {
cout << vec.at(5); // 抛出std::out_of_range
} catch(const exception& e) {
cerr << e.what();
}
- 迭代器有效性检查
cpp
auto it = vec.begin();
vec.push_back(42); // 可能导致迭代器失效
// it可能失效,需重新获取
it = vec.begin();
- 预分配内存避免中间扩容
cpp
vector<int> vec;
vec.reserve(1000); // 预分配足够空间
- 智能指针管理资源(C++11)
cpp
unique_ptr<int[]> arr(new int[100]); // 自动释放内存
- 范围检查工具(本地调试)
cpp
#define _GLIBCXX_DEBUG // GCC专用检查
vector<int> vec(3);
cout << vec[5]; // 立即触发错误提示
五、从C到C++的关键思维转变
5.1 变量即对象(理解.
操作符)
cpp
vector<int> arr;
arr.push_back(1); // arr是对象,调用其成员函数
/* 类似C的结构体操作 */
struct Vector {
void (*push_back)(struct Vector*, int);
};
struct Vector arr;
arr.push_back(&arr, 1);
5.2 自动析构(内存自动释放)
cpp
void func() {
vector<int> arr(1000); // 分配内存
} // 函数结束时自动释放arr内存
/* 对比C语言 */
void func() {
int* arr = malloc(1000 * sizeof(int));
// ...
free(arr); // 必须手动释放!
}
六、编译与调试须知(重要!)
6.1 启用C++11标准
bash
g++ -std=c++11 your_code.cpp # 必须添加这个编译选项
- 支持范围for循环、auto等现代特性
6.2 调试vector内存布局
cpp
vector<int> arr = {1,2,3};
// 查看实际容量(非元素数量)
cout << "容量:" << arr.capacity() << endl;
// 输出:容量:4 (不同编译器可能有差异)
下篇预告
第三篇:映射与集合的终极形态
- 如何用
map
替代C的手写二叉搜索树 unordered_map
的哈希魔法set
实现自动去重排序- 选择数据结构的黄金法则
欢迎在评论区留下你在使用vector和string时遇到的问题~