刷题时遇到的那些C++的”坑“
break 与 continue
- 在循环语句中,break只能跳出一层循环,如果外边还有一层或者该循环代码后还有代码的话只break一下后续代码还会执行,这是你想要的效果吗???
- 循环语句中continue会跳过本次循环,但是去执行下一次循环,你确定自己的目的是这样吗???
- switch 语句中,default的情况下往往会执行break,这个语义是不符合上边的任何一种情况,不做任何行动,break只是跳出switch语句;
内存访问越界的情况实际很容易出现,使用容器并不能避免代码本身的问题
这段代码中,本意是tmpStr如果出现非数字的话跳出循环重新开始下一轮遍历,然而,break只能跳出本层for循环,所以实际上还是以空字符串向下执行了(外层的那个{实际上是比较懒用了{}分段处理,正常该封装成函数的)。向下执行的时候已经在计划之外了,此时的我判断这种意外会崩溃在 if (tmpStr[0] == 'A')
这行代码,呵呵,实际上崩溃在 std::cout << "tmpStr[i]:" << tmpStr[i] << "\t";
里面了。不对啊,字符串为空这个循环内的代码按理说不会执行啊?? 这又涉及到重要知识点“有符号和无符号整型问题,无符号整型变量0-1会变成最大的正整数啊!!!”,所以这个循环就进来了呢。
数据处理一定要保持一致性
比如,一个数组我们用的时候要么保持[), 要么保持[],不然处理的一塌糊涂
数组下标最大值不能与API接口中指定数组大小的参数混淆
下标计算从0开始,涉及到数组的接口往往会指定数组的大小,千万注意下标的最大值 = 数组大小 - 1,不然小则崩溃,大则接口输出结果不稳定,十分诡异,排查去吧。。。。。。
插入排序的荒唐事
常规的插入排序
vector<int> iVec{3, 6, 8, 2, 99, 3, 6, 46, 2};
size = iVec.size();
for (int i = 1; i < size; i++)
{
int tmpValue = iVec[i];
int j = i - 1;
//只有当前值比排好序的最大值小时才需要挪移(如果当前值 >= 拍好序的最大值那就呆在原地好了)
while (j >= 0 && tmpValue < iVec[j])
{
iVec[j + 1] = iVec[j];
j--;
}
iVec[j + 1] = tmpValue;
}
一丁点优化的插入排序
vector<int> iVec{3, 6, 8, 2, 99, 3, 6, 46, 2};
size = iVec.size();
for (int i = 1; i < size; i++)
{
//一个非常小的优化:如果当前值 >= 已排序最大值则[0,i]均已排序,直接开始下一轮优化即可
if (iVec[i] < iVec[i - 1])
{
int tmpValue = iVec[i];
int j = i - 1;
while (j >= 0 && tmpValue < iVec[j])
{
iVec[j + 1] = iVec[j];
j--;
}
iVec[j + 1] = tmpValue;
}
}
for 循环用错了,出故障版本
vector<int> iVec{3, 6, 8, 2, 99, 3, 6, 46, 2};
size = iVec.size();
for (int i = 1; i < size; i++)
{
if (iVec[i] < iVec[i - 1])
{
int tmpValue = iVec[i];
int j = i - 1;
for (; j >= 0; j--)
{
//这里出错了:不论是否挪移数据,j都会一直减到0为止,那就乱套了
if (tmpValue < iVec[j])
{
iVec[j + 1] = iVec[j];
}
}
iVec[j + 1] = tmpValue;
}
}
那么,非用for循环该怎么写呢:
string类型变量初始化的值类型错误
一般写代码可能不会出这种错误,但是今天写排序写多了有点晕,把string类型变量的初始值设为0,然后排查了好长一段时间,当然了事实上这种类型的错误ASAN也排查不到,gdb可以定位出来,不过这种错误得先确定一个范围,不然的话gdb单行调试费功夫可就大了,try也可以捕获,可惜我太想当然了,以为是自己写的归并排序代码有问题,而没有去捕获变量及容器的初始化。。。所以啊,做事做人一定要踏踏实实,稳稳当当,切勿轻浮盲目!!!
GDB的却是可以调试的
值语义还是引用语义(对象语义)
- 值语义:对象的拷贝与原对象无关,c++中将基础类型都定义为值语义,c++规定凡是能够放入标准容器内的类型必须具有值语义。
- 引用语义:一个对象无法被复制/复制后与原来的对象"绑定",只要一个改变了另外一个就会改变。
快速排序正确的方式:
void quicksort(vector<int> &nums, int first, int last)
{
if (first >= last)
return;
int &pivot = nums[first];
int i = first;
int j = last;
while (i < j)
{
while (nums[j] > pivot && i < j)
j--;
while (nums[i] <= pivot && i < j)
i++;
if (i < j)
{
swap(nums[i], nums[j]);
}
}
swap(pivot, nums[j]);
quicksort(nums, first, j - 1);
quicksort(nums, j + 1, last);
}
上述代码只需要去掉一个引用符号及变成了错误的:
其实while内的两处pivot需要的是一个值即可(值语义),但是循环外的swap需要交换容器内两个位置的元素的值,此处需要的是引用语义。也就是:
没想明白的4行代码
正确的快排:
错误的快排:
连续写了三天排序,呵呵,自己都蒙了,这里暂且不考虑了,换换脑子
泛型算法由用户自己保证安全
比如这段代码(取vector中最大值):
如果没有判断size > 0
则会初始化空的vector,继而在使用max_element
函数的时候会返回空指针,然后我又给它来了个解引用,那不就崩了嘛。。。
我们来看下max_element
函数底层如何实现的呢?
可以看到,当容器元素为空的时候,直接返回了空指针,这时候再去解引用肯定有问题啊! 因此,泛型算法的使用需要有用户保证安全,不可大意!
ASAN工具一定要用起来
编译的时候CXX_FLAGS增加-fsanitize=address -fsanitize-recover=all -fsanitize=leak
编译选项,则编译或者运行过程中出问题一下子就能指出代码错误的那一行,非常清晰!
总结
这次刷题收获很大,不仅仅是锻炼了自己的逻辑思维能力和编码能力,更为重要的是再次重重的给我上了一堂课:一名工程师一定要脚踏实地,认认真真,否则走不长远!!!