【从C到C++的算法竞赛迁移指南】第二篇:动态数组与字符串完全攻略 —— 写给C程序员的全新世界

系列导航:

  1. [第一篇] C++基础与竞赛优势
  2. [▶ 本篇] 动态数组与字符串革命
  3. [第三篇] 映射与集合的终极形态
  4. [第四篇] STL算法与迭代器
  5. [第五篇] 现代语法糖精粹
  6. [第六篇] 竞赛实战技巧

一、动态数组:彻底告别malloc(手把手教学)

1.1 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)基本概念
vector<int> arr;  // 创建一个空数组
  • vector:动态数组类型(类似C的int[]但自动扩容)
  • <int>:模板参数(理解为"数组元素类型")
  • arr:对象名称(你可以用任何合法变量名)
(2)添加元素(对比教学)
/* C版本 */
add_element(42); // 需要手动管理内存

/* C++版本 */
arr.push_back(42); // 自动处理内存
  • push_back():成员函数(类似C结构体中的函数指针)
  • 参数42会被自动添加到数组末尾
  • 内存自动翻倍扩容(无需手动realloc)
(3)访问元素(安全版vs快速版)
// 安全访问(推荐新手使用)
int first = arr.at(0); // 越界会抛出异常

// 快速访问(与C数组相同行为)
int second = arr[1];  // 越界导致未定义行为

二、string:20个必会高效操作详解

1. 字符串构造

C++方式

string s1;             // 空字符串(类似char s[1] = {0})
string s2("Hello");    // 从C字符串构造(自动计算长度)
string s3(5, 'A');     // "AAAAA"(C中需malloc+循环)

C对比:无需手动分配内存,自动处理’\0’结束符


2. 获取字符串长度

C++方式

int len = s.size();    // O(1)时间复杂度

C对比strlen(s)需要O(n)遍历,C++内部维护长度值


3. 访问字符元素

安全访问

char c = s.at(2);     // 越界抛出异常(建议调试使用)

快速访问

char c = s[2];        // 与C数组相同(越界未定义行为)

4. 字符串拼接

C++方式

string s = "Hello";
s += " World";       // 自动扩展内存(C需realloc)
s.append("!");       // 等同+=但可指定追加长度

C对比:无需计算目标缓冲区大小,自动管理内存


5. 字符串比较

C++方式

if(s1 == s2) { ... }            // 直接比较内容
if(s1.compare(0,3,"App") == 0) // 比较前3字符是否"App"

C对比:替代strcmp,更直观安全


6. 提取子串

C++方式

string sub = s.substr(2, 5); // 从位置2取5字符(自动处理边界)

C对比:替代strncpy+手动添加'\0'操作


7. 查找子串

基础查找

size_t pos = s.find("World"); // 返回首次出现位置
if(pos != string::npos) { ... }

逆向查找

pos = s.rfind('o');          // 从后向前查找字符

8. 替换子串

C++方式

s.replace(6, 5, "CPP"); // 从位置6替换5字符为"CPP"

C对比:自动处理内存扩展,无需手动移动字符


9. 插入字符串
s.insert(5, " dear"); // 在位置5插入字符串(自动后移字符)

10. 删除子串
s.erase(5, 3);       // 从位置5删除3个字符(自动前移字符)

11. 首尾字符处理
s.front() = 'h';      // 修改首字符(等同s[0])
s.pop_back();         // 删除末尾字符(比C少计算长度)

12. 清空字符串
s.clear();           // 清空内容(内存保留)
s.shrink_to_fit();   // 释放多余内存(C中需realloc)

13. 判断空字符串
if(s.empty()) { ... } // 比s.size() == 0更直观

14. 数据指针访问
const char* cstr = s.c_str(); // 获取C风格字符串(只读)
char* buf = &s[0];            // 直接访问缓冲区(C++11起)

15. 流式处理
stringstream ss("3.14 hello");
double d; string str;
ss >> d >> str;      // d=3.14, str="hello"(类似sscanf)

16. 数值转换
int num = stoi("42");        // 字符串转int
double d = stod("3.14");     // 转double
string s = to_string(123);   // 数值转字符串

17. 大小写转换
transform(s.begin(), s.end(), s.begin(), ::tolower);

C对比:无需逐个字符处理,一行代码完成


18. 删除空白字符
s.erase(remove_if(s.begin(), s.end(), ::isspace), s.end());

效果:删除所有空白字符(空格、\t、\n等)


19. 字符串分割
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)
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语言版邻接表

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版

// 创建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遍历 */
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 典型错误场景

vector<int> vec(3); // [0,0,0]
cout << vec[5];     // 越界访问(未定义行为)

string s;
cout << s[0];       // 访问空字符串(崩溃)

4.2 防御性编程技巧

  1. 始终检查空容器
if(!vec.empty()) {
    // 安全访问vec[0]
}
  1. 使用at()替代[]
try {
    cout << vec.at(5); // 抛出std::out_of_range
} catch(const exception& e) {
    cerr << e.what();
}
  1. 迭代器有效性检查
auto it = vec.begin();
vec.push_back(42);  // 可能导致迭代器失效
// it可能失效,需重新获取
it = vec.begin();
  1. 预分配内存避免中间扩容
vector<int> vec;
vec.reserve(1000);  // 预分配足够空间
  1. 智能指针管理资源(C++11)
unique_ptr<int[]> arr(new int[100]); // 自动释放内存
  1. 范围检查工具(本地调试)
#define _GLIBCXX_DEBUG  // GCC专用检查
vector<int> vec(3);
cout << vec[5]; // 立即触发错误提示

五、从C到C++的关键思维转变

5.1 变量即对象(理解.操作符)

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 自动析构(内存自动释放)

void func() {
    vector<int> arr(1000); // 分配内存
} // 函数结束时自动释放arr内存

/* 对比C语言 */
void func() {
    int* arr = malloc(1000 * sizeof(int));
    // ... 
    free(arr); // 必须手动释放!
}

六、编译与调试须知(重要!)

6.1 启用C++11标准

g++ -std=c++11 your_code.cpp  # 必须添加这个编译选项
  • 支持范围for循环、auto等现代特性

6.2 调试vector内存布局

vector<int> arr = {1,2,3};
// 查看实际容量(非元素数量)
cout << "容量:" << arr.capacity() << endl;
// 输出:容量:4 (不同编译器可能有差异)

下篇预告

第三篇:映射与集合的终极形态

  • 如何用map替代C的手写二叉搜索树
  • unordered_map的哈希魔法
  • set实现自动去重排序
  • 选择数据结构的黄金法则

欢迎在评论区留下你在使用vector和string时遇到的问题~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值