1. C++常用函数与算法精要
作为C++开发者,熟练掌握标准库函数是提升编码效率的关键。下面我将分享几个最常用的函数及其实际应用场景。
1.1 sort()函数的深度解析
sort()函数是<algorithm>头文件中最常用的排序工具,其时间复杂度为O(N logN)。理解它的正确用法能避免很多常见错误。
cpp复制#include <algorithm>
using namespace std;
// 对数组排序
int arr[] = {5, 2, 8, 1, 9, 3};
int n = sizeof(arr)/sizeof(arr[0]);
sort(arr, arr + n); // 经典用法:arr + n表示结束位置
特别注意:sort()使用的是左闭右开区间[first, last),所以arr + n指向的是数组末尾的下一个位置,这与C++标准库的迭代器规范一致。
对于vector容器,更推荐使用迭代器方式:
cpp复制vector<int> nums = {5, 2, 8, 1, 9, 3};
sort(nums.begin(), nums.end()); // 标准迭代器用法
降序排序有两种实现方式:
cpp复制// 方法一:使用greater<int>()
sort(nums.begin(), nums.end(), greater<int>());
// 方法二:使用lambda表达式(C++11)
sort(nums.begin(), nums.end(), [](int a, int b) {
return a > b;
});
实际项目中,我们经常需要对自定义结构体排序。例如对学生按成绩排序:
cpp复制struct Student {
string name;
int score;
};
vector<Student> students;
// ... 添加数据
sort(students.begin(), students.end(), [](const Student& a, const Student& b) {
return a.score > b.score; // 按成绩降序
});
1.2 find函数的两种形式
find函数用于在容器中查找元素,有两种常用形式:
cpp复制// 形式1:在整个范围内查找
size_t pos = str.find(sub);
// 形式2:从指定位置开始查找
size_t pos = str.find(sub, start_pos);
一个典型应用场景是查找所有出现的子串:
cpp复制string log = "Error:404;Error:500;Error:200";
string target = "Error";
size_t pos = 0;
while((pos = log.find(target, pos)) != string::npos) {
cout << "Found at: " << pos << endl;
pos += target.length(); // 移动到匹配项之后
}
经验之谈:find返回的是size_t类型,当未找到时会返回string::npos(通常是-1的无符号表示)。在比较时,建议使用string::npos而不是-1,以保证代码可移植性。
1.3 reverse函数的妙用
reverse函数用于反转容器中元素的顺序,其声明如下:
cpp复制void reverse(iterator first, iterator last);
实际应用示例:
cpp复制string s = "hello";
reverse(s.begin(), s.end()); // 变为"olleh"
vector<int> v = {1,2,3,4,5};
reverse(v.begin()+1, v.end()-1); // 反转中间部分→{1,4,3,2,5}
在算法题中,reverse常用于处理回文、旋转数组等问题。例如旋转数组的经典解法:
cpp复制void rotate(vector<int>& nums, int k) {
k %= nums.size();
reverse(nums.begin(), nums.end()); // 整体反转
reverse(nums.begin(), nums.begin()+k); // 前k个反转
reverse(nums.begin()+k, nums.end()); // 剩余部分反转
}
2. 字符串操作全攻略
C++的string类提供了丰富的字符串处理方法,下面深入解析最常用的功能。
2.1 字符串构造与初始化
string有多种构造方式,适用于不同场景:
cpp复制string str1; // 空字符串
string str2("123456789"); // 直接初始化
string str3("12345", 0, 3); // 从位置0开始取3个字符→"123"
string str4(5, 'A'); // 5个'A'→"AAAAA"
string str5(str2, 2); // 从str2索引2开始到结尾→"3456789"
在文件处理时,这种灵活性特别有用:
cpp复制ifstream file("data.txt");
string buffer(1024, '\0'); // 预分配1KB空间
file.read(&buffer[0], 1024);
2.2 字符串比较机制
string的比较不是简单的指针比较,而是基于字典序的字符逐个比较:
cpp复制string a = "apple";
string b = "banana";
cout << (a < b); // 输出1,因为'a'<'b'
compare方法提供了更细致的控制:
cpp复制string s1 = "Hello";
string s2 = "hello";
int result = s1.compare(1, 3, s2, 1, 3); // 比较"ell"和"ell"
性能提示:对于简单的相等判断,直接使用==运算符比compare更快,因为compare需要计算差值。
2.3 字符串修改操作
字符串修改是日常开发中的高频操作:
cpp复制string s = "Hello";
s.push_back('!'); // "Hello!"
s.insert(5, " World"); // "Hello World!"
s.replace(6, 5, "C++"); // "Hello C++!"
s.erase(5, 4); // "Hello!"
一个常见的坑是连续修改时的索引失效:
cpp复制string s = "abcde";
s.erase(1, 1); // 删除'b'→"acde"
// 此时原位置2的'c'现在位于位置1
s.insert(2, "x"); // 在'd'前插入→"acxde"
2.4 字符串查找与分割
find系列方法功能强大:
cpp复制string log = "[INFO] 10:30:45 System started";
size_t timePos = log.find("10:30");
if(timePos != string::npos) {
string time = log.substr(timePos, 5);
}
对于复杂分割,可以结合find和substr:
cpp复制vector<string> split(const string& s, char delim) {
vector<string> tokens;
size_t start = 0, end = s.find(delim);
while(end != string::npos) {
tokens.push_back(s.substr(start, end-start));
start = end + 1;
end = s.find(delim, start);
}
tokens.push_back(s.substr(start));
return tokens;
}
性能优化:对于大量字符串处理,考虑使用string_view(C++17)避免不必要的拷贝。
3. 动态数组vector深度探索
vector是C++中最常用的动态数组容器,理解其内部机制能写出更高效的代码。
3.1 vector的内存管理
vector的内存增长策略因实现而异,但通常是按2倍或1.5倍增长:
cpp复制vector<int> v;
cout << "初始容量:" << v.capacity() << endl; // 0
v.push_back(1);
cout << "添加1个元素后:" << v.capacity() << endl; // 可能是1
v.push_back(2);
cout << "添加第2个元素后:" << v.capacity() << endl; // 可能是2
v.push_back(3);
cout << "添加第3个元素后:" << v.capacity() << endl; // 可能是4
预分配空间可以显著提升性能:
cpp复制vector<int> bigData;
bigData.reserve(1000000); // 一次性分配
for(int i=0; i<1000000; ++i) {
bigData.push_back(i); // 不会触发多次扩容
}
3.2 vector的迭代器失效问题
vector在修改操作时可能导致迭代器失效:
cpp复制vector<int> v = {1,2,3,4,5};
auto it = v.begin() + 2;
v.insert(v.begin(), 0); // 插入导致内存重分配
// 此时it已失效!
cout << *it; // 未定义行为
安全的使用模式:
cpp复制for(auto it = v.begin(); it != v.end(); ) {
if(*it % 2 == 0) {
it = v.erase(it); // erase返回下一个有效迭代器
} else {
++it;
}
}
3.3 高效vector使用技巧
- 使用emplace_back避免临时对象:
cpp复制vector<pair<int, string>> v;
v.emplace_back(1, "one"); // 直接构造,无需创建临时pair
- 移动语义优化大对象:
cpp复制vector<string> bigStrings;
string largeStr(100000, 'x');
bigStrings.push_back(std::move(largeStr)); // 移动而非拷贝
- 正确使用shrink_to_fit:
cpp复制vector<int> v(1000);
v.resize(10);
v.shrink_to_fit(); // 释放多余内存
4. 栈(stack)的实战应用
stack是LIFO(后进先出)数据结构,标准库实现基于deque。
4.1 栈的基本操作
cpp复制stack<int> s;
s.push(1); // 栈:1
s.push(2); // 栈:2 1
s.push(3); // 栈:3 2 1
cout << s.top(); // 3
s.pop(); // 移除3
cout << s.size(); // 2
4.2 经典应用:表达式求值
cpp复制int evaluate(const string& expr) {
stack<int> nums;
stack<char> ops;
for(char c : expr) {
if(isdigit(c)) {
nums.push(c-'0');
} else if(c == '(') {
ops.push(c);
} else if(c == ')') {
while(ops.top() != '(') {
calculate(nums, ops);
}
ops.pop();
} else {
while(!ops.empty() && precedence(ops.top()) >= precedence(c)) {
calculate(nums, ops);
}
ops.push(c);
}
}
while(!ops.empty()) {
calculate(nums, ops);
}
return nums.top();
}
4.3 栈与递归的关系
所有递归算法都可以用栈改写,例如DFS的非递归实现:
cpp复制void dfs(Node* root) {
stack<Node*> s;
s.push(root);
while(!s.empty()) {
Node* curr = s.top();
s.pop();
process(curr);
for(auto child : curr->children) {
s.push(child);
}
}
}
4.4 常见错误与调试技巧
- 空栈访问:
cpp复制stack<int> s;
// 错误:未检查空栈
int val = s.top(); // 未定义行为
// 正确做法
if(!s.empty()) {
val = s.top();
}
- 遍历时的修改:
cpp复制stack<int> s;
// ... 填充数据
while(!s.empty()) {
process(s.top());
s.pop(); // 正确:边处理边移除
}
// 错误示例:试图用索引访问
for(int i=0; i<s.size(); i++) { // size()在变化!
cout << s.top();
s.pop();
}
- 对象生命周期问题:
cpp复制stack<MyClass*> s;
{
MyClass obj;
s.push(&obj); // 错误:obj即将销毁
} // obj离开作用域被销毁
s.top()->method(); // 悬垂指针!
在实际项目中,我经常使用栈来处理撤销操作、函数调用、表达式解析等场景。理解栈的核心特性后,可以灵活运用它解决各类问题。