示例:leetcode188.买卖股票的最佳时机
最开始手误的一版错误写法
class Solution {
public:
int maxProfit(int k, vector<int>& prices) {
if(prices.size()==1) return 0;
vector<vector<int>>dp(prices.size(),vector<int>(2*k+1,0));
//初始化
for(int i=1;i<=2*k;i++){
//此处手误写错,-prices[i]会导致下标越界
dp[0][i]=(i%2==0)?0:-prices[i];
}
//递推
for(int i=1;i<prices.size();i++){
for(int j=1;j<=2*k;j++){
dp[i][j]=max(dp[i-1][j],dp[i-1][j-1]+(j%2==0?prices[i]:-prices[i]));
}
}
//return *max_element(dp[prices.size()-1].begin(),dp[prices.size()-1].end());
return dp[prices.size()-1][2*k];
}
};
这个写法运行的时候,遇到下面用例输出了如下结果:
输入prices的size是3,而k=2的时候2*k=4,也就是说下面这个for循环访问了prices数组不存在的下标prices[4]
for(int i=1;i<=2*k;i++){
//此处手误写错,-prices[i]会导致下标越界
dp[0][i]=(i%2==0)?0:-prices[i];
}
但是这种情况并没有报错,只是输出了一个极大的数值1094795580。
本来以为这种情况是vector底层发生了动态扩容,所以并没有报错。但是实际上,vector下标越界而没有报错,并非由于 vector 的动态扩容机制导致的。
而是因为,在 C++ 中,访问数组(包括 vector)下标超出其大小时,其行为是未定义的,也就是说,它可能产生任何结果,包括但不限于返回一个随机值、导致程序崩溃等。
得到的 1094795590 这个很大的数字,是因为代码访问了 vector 空间之外的内存,那块内存恰好有这个值。这个数值并不取决于 C++ 的版本,而是取决于访问的那块内存中恰好存储的值,这个值是随机的。
为什么c++数组下标越界了但是没有报错?
C++ 的数组或者向量在下标访问时,不会进行边界检查,这是因为 C++ 设计哲学中有一条就是"你不需要付出你不用的代价",也就是说,如果每次访问数组或者向量都进行边界检查,那么会带来额外的性能开销,而这部分开销对于绝大多数情况是没有必要的。
对于下标访问的数组或者向量,如果下标超过了其大小,那么会进入未定义行为(Undefined Behavior),即任何事情都可能发生。可能是崩溃,可能是返回一个不可预期的值,也可能是毫无表现。在上面的情况下,看到的就是返回了一个看似随机的值。
那么平时编程为什么下标为负数的时候会报错?
平时我们编程的时候,数组下标是负数的时候,C++ 会报错,这其实是因为我们使用的编译器或者环境开启了 AddressSanitizer 这样的内存错误检测工具。AddressSanitizer 通过添加一些运行时检查,可以发现一些内存错误,比如数组越界访问(包括下标为负和下标超过大小)。但是,AddressSanitizer 并不能保证检测所有的数组越界访问,特别是当下标超过数组大小很多时,可能就无法检测到了。
总的来说,C++ 并不会在运行时对数组或者向量的下标访问进行边界检查,如果下标超过了其大小,那么会进入未定义行为,即任何事情都可能发生。因此程序员需要自己确保不会发生越界访问。如果需要额外的安全性,可以使用一些工具来检测可能的错误,但是这些工具也不能保证能发现所有的问题。