数组的误用

原文地址: 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对教学目的而言更为有用,特别是面对初学者的时候。 此外,数组并不能限制学生撰写本质上不值得的代码。相反地,学生可以获得一个良好的习惯:首先让程序可以运行,然后就只需要思考如何进行优化。

时间: 2024-09-11 14:05:11

数组的误用的相关文章

C语言编程时常犯十八个错误小结

C语言的最大特点是:功能强.使用方便灵活.C编译的程序对语法检查并不象其它高级语言那么严格,这就给编程人员留下"灵活的余地",但还是由于这个灵活给程序的调试带来了许多不便,尤其对初学C语言的人来说,经常会出一些连自己都不知道错在哪里的错误   看着有错的程序,不知该如何改起,本人通过对C的学习,积累了一些C编程时常犯的错误,写给各位学员以供参考. 1.书写标识符时,忽略了大小写字母的区别. 复制代码 代码如下: main() { int a=5; printf("%d&quo

基础C语言编程时易犯错误有哪些_C 语言

C编译的程序对语法检查并不象其它高级语言那么严格,这就给编程人员留下"灵活的余地",但还是由于这个灵活给程序的调试带来了许多不便,尤其对初学C语言的人来说,经常会出一些连自己都不知道错在哪里的错误.看着有错的程序,不知该如何改起,通过对C的学习,积累了一些C编程时常犯的错误,以供参考. 1.书写标识符时,忽略了大小写字母的区别. main() { int a=5; printf("%d",A); } 编译程序把a和A认为是两个不同的变量名,而显示出错信息.C认为大写

C语言编程时常犯十八个错误小结_C 语言

看着有错的程序,不知该如何改起,本人通过对C的学习,积累了一些C编程时常犯的错误,写给各位学员以供参考. 1.书写标识符时,忽略了大小写字母的区别. 复制代码 代码如下: main(){ int a=5; printf("%d",A);} 编译程序把a和A认为是两个不同的变量名,而显示出错信息.C认为大写字母和小写字母是两个不同的字符.习惯上,符号常量名用大写,变量名用小写表示,以增加可读性. 2.忽略了变量的类型,进行了不合法的运算. 复制代码 代码如下: main(){ float

C语言十八种常见错误

C语言的最大特点是:功能强.使用方便灵活.C编译的程序对语法检查并不象其它高级语言那么严格,这就给编程人员留下"灵活的余地",但还是由于这个灵活 给程序的调试带来了许多不便,尤其对初学C语言的人来说,经常会出一些连自己都不知道错在哪里的错误.看着有错的程序,不知该如何改起,本人通过对C的学 习,积累了一些C编程时常犯的错误,写给各位学员以供参考. 1.书写标识符时,忽略了大小写字母的区别. main() { int a=5; printf("%d",A); } 编译

C安全问题与指针误用

指针的声明与初始化 1.不恰当的指针声明 考虑如下的声明: int* ptr1, ptr2; // ptr1为指针,ptr2为整数 正确的写法如下: int* ptr1, *ptr2; 用类型定义代替宏定义是一个好的习惯,类型定义允许编译器检查作用域规则,而宏定义不一定会. 使用宏定义辅助声明变量,如下所示: #define PINT int* PINT ptr1, ptr2; 不过结果和前面所说的一致,更好的方法是使用下面的类型定义: typedef int* PINT; PINT ptr1,

理解C语言——从小菜到大神的晋级之路(8)——数组、指针和字符串

       本期视频点击这里        在前面几次我们接触的数据类型都是简单数据类型,使用一个数据个体表示一个元素.C语言中还提供了多种复杂数据类型,其中最简单的一种就是数组.数组这一结构使用内存中一段连续的内存空间保存一组相同类型的变量,这些变量通过数组的下标/索引的不同相互区分.数组与指针有着十分紧密的联系,通常使用数组下标能实现的操作都可以使用指针完成,而且使用指针的程序通常效率更高.但是指针和数组也存在着一些明显的差别,如果误用将导致错误.另外,C语言中还定义了一种极为常用的特殊的

详解js数组的完全随机排列算法_javascript技巧

Array.prototype.sort 方法被许多 JavaScript 程序员误用来随机排列数组.最近做的前端星计划挑战项目中,一道实现 blackjack 游戏的问题,就发现很多同学使用了 Array.prototype.sort 来洗牌. 洗牌 以下就是常见的完全错误的随机排列算法: function shuffle(arr){ return arr.sort(function(){ return Math.random() - 0.5; }); } 以上代码看似巧妙利用了 Array.

python对数组进行反转的方法

  本文实例讲述了python对数组进行反转的方法.分享给大家供大家参考.具体实现方法如下: ? 1 2 3 arr = [1,2,3] arr.reverse() print(arr) 输出: [3,2,1] 希望本文所述对大家的Python程序设计有所帮助.

php对关联数组循环遍历的实现方法

 这篇文章主要介绍了php对关联数组循环遍历的实现方法,涉及php操作数组的技巧,具有一定参考借鉴价值,需要的朋友可以参考下     本文实例讲述了php对关联数组循环遍历的实现方法.分享给大家供大家参考.具体分析如下: php对于类似 ? 1 $age = array("zhangshan"=>14,"lisi"=>15,"sharejs"=>16); 这样的数组可以通过foreach的方法进行遍历,下面是详细的代码: ? 1