原文地址: Teaching C++ Badly: How to Misuse Arrays
我上次写了篇文章列举了我所看到的一些不好的C++教学,并且承诺详细地解释这些技术。这篇就是其中的第一篇。 我见到有归因于Trenchard More(*定义了More Array Theory)的断言,说数组是所有数据结构中最基本的一个。 事实上几乎没有哪个在世的程序员没有使用过数组。如果没有足够的证据,想象一下数组是一种线性寻址机制的抽象,它是所有计算机硬件通常使用的最基础的部分。
线性寻址的想法认为计算机的内存由一组具有连号地址的存储单元构成。通过使用普通的整数来计算存储单元的地址,我们可以很容易从“数组的第几个元素”的概念出发找到相应的地址。
因为这种数组概念与底层地址概念上的联系,数组操作在所有支持它的编程语言中都很相似。 一个数组有许多元素,每个元素都有一个下标或索引,并且这些索引数都是连续的整数。 有些语言从0开始索引,另外一些则从1开始,也有少数一些可以从任意位置开始,这关乎于语言间的差异性。
大多数支持数组的语言都会阻止在数组建立后修改数组的大小。 C与C++要求的更多: 它们要求数据的大小必须在编译时明确。不然,程序必须建立一个动态数组,它虽然和简单的数组有类似的属性,在一些细节上还是有所不同。 即便是这些动态数组,也不能在创建后修改大小。
现在让我们来考虑一下程序如何使用数组。大部分的程序员有三种完全不同的方式使用数组。有时这三件事是交错的,但通常一次只做一件:
1. 存储数据
2. 访问数据
3. (可选)取出所有数据
第2步包括重新安排数据, 或许是排序。 第3步包括打印数据。
举个例子,考虑一个程序来计算素数。 每次它测试一个数字以检查它是不是一个素数,它也许使用在数组中已经存储的数据(第2步).每次它发一个素数,就会将它存储到一个数组中 (第1步). 最后,它也许要将所有找到数据打印出来 (第3步). 在这里,第1步和第2步是交错进行的,第3步有可能交错,也有可能独力运行。(*取决于边找找输出,还是最后一次性输出。)
当程序按这三步来操作数组时,必须面对两个问题。首先,如果编程语言要求程序员在创建数组时就冻结它的大小,那么程序员必须知道需要多少元素。再者,即使程序员已经知道元素数量,固定的大小意味着在程序运行的大部分情况下,数组中总是有元素的值是没有意义的。
这些限定就引出两个一般性的编程实践,以我的观点来看,它们比学习如何编程要难得多:
*推测数组应当需要多少元素
*使用一个辅助变量来追踪使用了多少元素
例如,让我们回到之前假设的素数程序. 你应当看到过许多程序以类似下面的方式开始:
int primes[1000]; // 我们不需要超过1000个素数
primes[0] = 2; // 第一个素数是2
n = 1; // 目前为止我们已经有一个素数
这个程序推测出我们会发现的素数数量(1000),并且使用一个辅助变量(n)来追踪我已经得到了多少素数。当我们得到一个新的素数时,程序会执行:
primes[n++] = prime;
如果程序经过认真思考,就会使用检查是否n>=1000的语句,否则程序就可能因为溢出而crash.
从教学的观点来看这个程序很差,三个理由:
*它为程序的能力强加了一个限制 -- 它不能计算超过指定数量的素数。
*在只需要一个变量时却使用了两个(primes和n),因此使得代码远比它所需要的复杂。
*它在分配数组时浪费了资源。
标准的vector模板可以避免这三个问题。对于上面的例子,我们可以使用如下代码重写:
vector<int> primes; // primes开始并没有任何元素
primes.push_back(2); //第一个素数是2
在即将发布的C++标准,我们可以将这个例子简化为一句话:
vector<int> primes = {2};
当得到一个新的素数时,我们可以通过下面的语句来追加:
primes.push_back(prime);
这个语句会将primes的大小增加1,追加了一个变量prime的复本到primes中. 实际上,它是将prime压到primes的背后。这样我们就处理掉了之前三个问题中的两个:1.使用vector替代数据移除了在元素数量上的约束。 2.只需要使用一个变量 (不再需要额外的辅助变量)。
当然,这并没有解决资源浪费的问题,因为vector在背后也分配了超出数组所需要的内存。然而现在是程序在浪费内存,而不是程序员。而且这种浪费让程序员有机会在不彻底重写代码的情况下,在多种空间与时间的权衡中进行选择。相对于教初学者如何写不必要的晦涩代码,我们可以教他们写合适的代码,然后在代码可以工作后,再来判断如何或者是否需要进行优化。
简言之,程序员往往乐于使数组在程序执行过程中增长。C/C++中内建的数据并不能达到要求,而vector可以做到。所以使得vcetor对教学目的而言更为有用,特别是面对初学者的时候。 此外,数组并不能限制学生撰写本质上不值得的代码。相反地,学生可以获得一个良好的习惯:首先让程序可以运行,然后就只需要思考如何进行优化。
分享到:
相关推荐
许多初学者会误用指针和数组,例如,将指针误认为数组,或者在没有正确计算长度的情况下使用指针遍历数组,这些都可能导致运行时错误。 在“混淆指针和数组所导致的错误”这篇文章中,可能涉及了以下几点: 1. **...
4. **动态数组操作中误用索引**:如ArrayList或LinkedList等动态数组,在添加或删除元素后未更新迭代器或索引。 ### 处理`ArrayIndexOutOfBoundsException` 1. **异常捕获**:使用try-catch语句块来捕获并处理该...
这不仅可以避免因误用默认排序函数导致的问题,还能提高代码的可读性和可维护性。 总结来说,中文数组排序是一个涉及到编码理解、排序算法和语言特性的综合性问题。开源项目FFChineseSort通过提供专门针对中文字符...
若将返回值4误用为数组,则可能引发错误。 此外,对于数组的其他几个方法如pop(), shift(), unshift(), concat(), splice()和slice(),它们各自有不同的行为和返回值,理解这些差异对于正确使用数组非常重要: 1. ...
常见的错误和警告在编程过程中很常见,包括未定义的变量、逻辑表达式错误、IF...ELSE结构不匹配、对字符串和表达式的误用、输入/输出操作的错误、函数声明和定义的问题以及标点符号错误。例如,错误地将cin和cout的...
5. **释放后设NULL**:释放内存后,将指针设为NULL,防止后续误用。 接下来,我们讨论`new`和`malloc`等动态内存分配方法: `new`是C++特有的运算符,用于动态分配对象和数组。例如: ```cpp int *a = new int; //...
在JavaScript编程中,正确使用数组方法对于优化代码和提高效率至关重要。本文将探讨两种常见的错误用例,并...在处理数组时,应根据具体需求选择最合适的数组方法,避免过度使用或误用导致不必要的复杂度和性能损失。
以下便是七个容易被忽略或误用的JavaScript基础知识点: 1. **正则表达式中的全局标志位 /g 和 /i** - 在`String.prototype.replace`方法中,如果不使用全局标志位 `/g`,只会替换第一个匹配的子串。例如: ```...
### JavaScript有趣而诡异...无论是关联数组的误用还是动态数组的特性,都需要开发者具备深入的理解。同时,“The Miller Device”方法提供了一种简单有效的方式来检测对象类型,这对于日常开发来说是非常有用的技巧。
3. 避免硬编码数组长度:如果可能,将数组长度存储在一个常量或变量中,这样可以减少因误用数组长度而导致的错误。 4. 使用边界检查库:某些库提供了边界检查的数组,例如`__attribute__((bounded))` 在GCC中,这...
例如,在排序中,可能会误用未初始化的指针导致内存访问错误。 2. **指针越界**:访问数组超出其实际大小是另一个常见错误。当使用指针遍历数组时,如果没有正确跟踪边界,可能会导致程序崩溃或数据损坏。在排序...
5. **代码规范与风格**:此插件的使用可以强制执行一种特定的代码风格,即`for...of`只能用于数组,这有助于保持代码一致性,减少因误用而导致的错误。 6. **代码质量与维护**:通过限制`for...of`的使用场景,...
// 误用 `new Array()` 的示例 var badArray = new Array(10); // 创建一个空数组,预设容量为10个元素 var goodArray = [10]; // 创建一个数组,其中第一个元素是数字10 ``` 在上述例子中,`badArray`和`goodArray...
多维数组与数组的数组在概念上有很大的不同,尽管在实际应用中它们经常被误用。 - **数组的数组**是指数组中的每个元素也是一个数组。例如,`int arr[2][3];`,这里`arr`是一个包含两个元素的数组,每个元素都是一...
选择排序的指针实现可以提高程序的效率和可读性,但是需要注意指针的使用和管理,以避免指针的误用和内存泄露。 选择排序的指针实现是程序设计基础课程中的一种重要技术,能够帮助学生更好地理解程序设计的基本概念...
12. 数组元素引用错误:访问数组元素时不要误用圆括号。 13. 数组定义与实际使用混淆:数组的索引是从0开始的,最大索引是定义的元素个数减1。 14. 二维或多维数组操作不当:正确定义和引用数组,注意行与列的顺序。...
在C语言中,字符数组和字符指针都是用来存储字符串的,但是它们有一个非常重要的区别:字符数组需要分配存储空间,而字符指针不需要分配存储空间。如果使用不当,可能会导致程序崩溃或产生不可预测的结果。 为了...
5. **库函数误用**:某些库函数可能在内部操作数组,如果使用不当,可能会引发越界问题。 解决数组越界问题的方法包括: 1. **边界检查**:在访问数组元素之前,确保索引在有效范围内。 2. **静态分析工具**:使用...