- 该文章记录笔者在学习 C++ 中所接触到的 需要注意的细节 以及 可以提升编程效率 或是 可以使代码变得简洁易懂的小技巧 ,随学习进程不定时更新
- 系统环境:Win10,Visual Studio 2019
先进行无脑的堆砌,如果后续条目数变得很多再分类整理
遍历字符串,修改字符串
C++ 11中新增的用法,在for循环中可以这么写,遍历或打印字符串更为方便
string s = "abcdefg";
for (char c : s){
cout << c ;
}
cout << endl;
//@result: abcdefg
也可像下述操作,使用 “&c” 来直接修改整串字符串的字符
string s = "abcdefg";
for (char& c : s){
c = 'A';
}
for (char c : s) {
cout << c ;
}
cout << endl;
//@result:AAAAAAA
也可用于数组遍历
double num[5] = { 1, 2, 3, 4, 5 };
for (double d : num){
cout << d;
}
cout << endl;
//@result:12345
unordered_set 非排序set容器
该容器将元素以非排序的形式存储,直接存储数据的内容而不是键值,不可添加重复元素,元素不可被修改,但可以通过删除再插入的方式进行 “修改”
在使用时需要添加头文件:
#include
比较有用的成员函数是 count ,其可以返回容器中有无待添加元素的值,因其容器的不重复特性,所以返回值限定为 “1” 或 “0” ,即有或没有,可以进行如下操作:
unordered_setmySet; mySet.insert(1); mySet.insert(2); mySet.insert(3); mySet.insert(4); // 是否有"4"? 若有则插入"5" if (mySet.count(4)) { mySet.insert(5); } else mySet.insert(4); // 打印结果 for (int c : mySet) { cout << c << " "; } //@result: 1 2 3 4 5
如果多次往 unordered_set 中插入重复元素,编译会通过,实际执行过程中也不会报错,但插入重复元素那条语句会被自动忽略掉
unordered_setmySet; mySet.insert(1); mySet.insert(2); mySet.insert(2); mySet.insert(2); mySet.insert(3); // 打印结果 for (int c : mySet) { cout << c << " "; } //@result: 1 2 3
匹配子串,KMP算法模式串预处理
对于在母串中找到匹配的子串问题,我自然想到的是暴力解法,即没有什么技术含量,同时时间复杂度也较高的解法,其主要思路是整块匹配:
// hlen: 母串长度,nlen:子串长度
for (int i = 0; i <= hlen - nlen; ++i) {
if (haystack.substr(i, nlen) == needle) {
return i;
}
}
return -1;
运用 KMP 算法 可以提高搜索字串的效率,这里谨浅探讨KMP算法中对于模式串的预处理工作,对于字符串 “ABCABCACA”, 可以根据其字符排列特征对其进行预处理,标记在某一位与子串匹配失败时,指针该向前移动的个数,当然,这个“个数”越小就代表前移越少,遍历相对会更快一些,理想情况是子串的所有标记都为“0”,对于上述字符串,计算后的标记结果对应为:
模式串 A B C A B C A C A 标记位 0 0 0 0 1 2 3 1 0
当上述模式串的倒数第三个元素“A”匹配失败时,则在母串前移三位继续匹配。我们可以大致看出,模式串中包含越多的重复字符和相似的字符排列会给KMP算法带来时间上的拖延,像下述字符串则很好:
模式串 b e a u t y 标记位 0 0 0 0 0 0
二分相关问题,优化O(N)至O(logN)
在有序数组的搜索与插入相关问题中,使用二分法可以将时间复杂度由传统方法的 O(n) 优化到 O(logn) ,在编制程序时需要注意以下问题:
- 首先在C++中,int类型整数相除所得结果并不是四舍五入,而是全舍,例如 3 / 2 = 1,1 / 2 = 0, 29 / 10 = 2 等等,因此在二分法的mid变量相关设计中要注意不要越界
- 在mid变量与目标值不等时,下一次搜索边界值的赋予可以根据mid与目标值的大小关系,在等于mid的基础上再加一或减一,可以提升一定的效率,下方示例代码(力扣官方):
int n = nums.size();
int left = 0, right = n - 1, ans = n;
while (left <= right) {
int mid = ((right - left) >> 1) + left;
if (target <= nums[mid]) {
ans = mid;
right = mid - 1;
} else {
left = mid + 1;
}
}
return ans;
vector
容器最大值的寻找
可以使用遍历方法,花费O(n), 也可以利用STL中的相关功能,没研究过源码,但估计对于无序数组的查找应该也是O(n)
首先添加头文件:
#include
其次对于vector容器进行如下操作:
int maxVal = 0; vectornums = { -2, 1, -3, 4, -1, 2, 1, -5, 4 }; maxVal = *max_element(nums.begin(), nums.end()); cout << maxVal << endl; //@result: 4
其中 max_element 函数的参数为容器的首尾地址(范围),返回值为最值元素的地址,故需要对返回值进行取 * 处理
跳出多重循环 (使用goto语句)
有时在内循环已经达成我们想要的目的时,我们希望直接跳出外循环并接着执行后续的业务逻辑,此时可以使用goto语句进行实现
for (int i = 0; i < 10; ++i) {
for (int i = 0; i < 10; ++i) {
if (true) {
goto outloop;
}
}
}
outloop:
// do something
在《C++ Primer》中,作者不建议我们使用goto语句,因为其会使程序变得复杂难懂
循环语句中 int 溢出问题
在 C++ 中,普通有符号 int 类型可以表示的数介于 - 2,147,483,648 ~ 2,147,483,647 之间,换言之也就是 4字节32位带符号整数 (在C++ Primer中有描述“C++中规定的尺寸的最小值”中,int类型占位是16位,但这个问题应该是取决于编译器实现的,在vs中将int类型的大小设定在了32位),用其中一位来表示正负,其余位最多可以表示 2^31 体量的数字,若在编程中出现溢出问题,则程序会报错,若是在循环语句中,我们可以进行如下操作:
while ((long long)i * i <= x) {
i += 1;
}
// 数据类型 "long long" 自C++ 11中新定义
while (size_t (i * i) <= x) {
i += 1;
}
或者将循环中的局部变量提前先声明为容量更大的数据类型,也可解决问题
vector数组的下标问题
在利用vector数组作为一维线性存储结构时,我们声明数组时可以不进行初始化(例如直接声明成 vector
当需要进行类二维操作时,我们应用下标对vector数组进行操作,则要事先声明数组的大小(维度),例如(vector
力扣第六题是一个很好的应用了该技术的例子:
static string convert(string s, int numRows) {
if (numRows == 1) return s;
vector rows(min(numRows, int(s.size()))); // 防止s的长度小于行数
int curRow = 0;
bool goingDown = false;
for (char c : s) {
rows[curRow] += c;
if (curRow == 0 || curRow == numRows - 1) {// 当前行curRow为0或numRows -1时,箭头发生反向转折
goingDown = !goingDown;
}
curRow += goingDown ? 1 : -1;
}
string ans;
for (string row : rows) {// 从上到下遍历行
ans += row;
}
return ans;
}
// 参考用户"Mia"


![[ C++编程 ] 编程细节与增效技巧随记 [ C++编程 ] 编程细节与增效技巧随记](http://www.mshxw.com/aiimages/31/1021779.png)
