刷题时遇到的那些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编译选项,则编译或者运行过程中出问题一下子就能指出代码错误的那一行,非常清晰!

总结

这次刷题收获很大,不仅仅是锻炼了自己的逻辑思维能力和编码能力,更为重要的是再次重重的给我上了一堂课:一名工程师一定要脚踏实地,认认真真,否则走不长远!!!

posted @ 2022-07-27 19:15  时间的风景  阅读(82)  评论(0编辑  收藏  举报