C语言学习常见误区探析
2020-07-07杨存伟汪维清
杨存伟,汪维清
(西南大学 信息管理系,重庆 402460)
计算机相关专业学生的第一门编程语言往往是C语言,作为一门高级语言,有一定的抽象性。未接触过计算机科学的初学者,往往会因为各种原因形成一些理念误区,学习理念出错会导致事倍功半,使初学者丧失学习兴趣。
1 认为仅学习C语言可以理解计算机底层原理
C语言具有一定的抽象性,初学者在学习C语言大多无编程经验,C语言教材往往会较为抽象地介绍C语言,而不具体讲解涉及的底层原理,因为这更有助于初学者编程入门。
例一:以一个C语言版本的以递归方法寻找两个正整数最大公约数的程序为例[1]:
数组,函数,判断语句等基本语法在常见的编程语言中都有,相对不那么“底层”的Python,C#,Java等都可实现这个算法,与编程语言的种类关系较小。
很多C语言的资料讲到,arr是一个指针,它保存内存的一个地址,通过此地址可访问内存中一个int型数据,误操作指针会导致程序错误甚至系统崩溃[2]。初学者往往理解为arr保存的是物理地址,虽不影响程序实现,但并不准确。
以32位的使用NT内核的Windows为例,在Windows中,每个进程都有自己的虚拟地址空间(0x00000000~0xFFFFFFFF),指针arr保存了虚拟地址空间的地址,而非物理内存地址。指针arr所指向的数据可能存储在物理内存中,可能在硬盘上(由于Windows虚拟内存技术),也可能在其他地方[3]。
误操作指针可以导致系统崩溃或硬件损坏,但是初学者编写的此类简单控制台程序很难导致系统崩溃或硬件损坏,相对来说,编写Windows内核模式驱动程序,单片机控制程序这样的偏底层的程序才易因指针问题出现严重错误。操作系统中进程之间相对独立,在Windows操作系统中,一个进程若需要访问另一个进程的数据,可以调用Write Process Memory函数、使用DLL注入技术、创建内存文件映射对象共享数据等,而一个简单的指针问题很难办到。
以上涉及到计算机组成原理,操作系统原理中的知识,只学习C语言本身,即使屏蔽掉这些知识,也可以做到写出有一定复杂度的程序。初学者较难理解的概念,如函数指针,共用体,可以用抽象的方式理解,学习计算机科学可以是从顶层向底层和从底层向顶层相结合的。
例二:直接调用Windows API编写窗体程序的代码片段
对于初学者来说,宏定义的用法,函数的编写方法,switch语句的使用等并不困难,上述代码没有涉及不常见的语法,但初学者并不容易理解,Windows API编写窗体程序涉及到很多Windows操作系统的知识。
由以上两例可以看出,仅凭C语言的学习,不能理解计算机的底层原理。作为一种高级语言,能够提供的抽象程度可以允许一定范围内忽略硬件及操作系统之类的细节而编写程序,所以对于初学者来说不管学习Python,Java,C#还是C语言,都不会直接了解底层的原理,C语言只是相对来说能更好的引申出这些知识而已。
2 编写代码过于复杂
源代码应是方便程序员阅读和修改的,越复杂,通常越难以维护,更容易出错。在不影响程序的最终实现效果(比如不用位运算就会严重拖慢运行速度)的前提下,应该使代码更通俗易懂。但是,初学者往往会“炫耀”技巧,使得代码过于复杂、艰涩。
2.1 滥用技巧
例三:交换两个变量的值
不考虑int的表示范围以上代码是没有问题的,但若*x+*y?[INT_MIN,INT_MAX]会溢出,这个函数便不能交换两变量的值。而更易懂的写法应该引入第三个变量进行交换。
2.2 表达思路不清晰
例四:“HelloWorld!”转化成大写输出
这个程序的目的是将一串字符转化成大写输出,在禁用编译器优化的情况下,可能会调用很多次strlen函数。而且并非需要每次都调用printf函数,可将其存入一个字符数组后一次性输出。较合理的写法如下:
2.3 滥用不常用的特性
以下将C语言的库函数,语法等,统一称作C语言的特性。C语言教材的第一示例程序通常都是一个简单的”Hello World”,然后逐渐介绍新的特性,并要求读者在程序中使用,会让部分读者有“学到新特性就要尽可能在程序中使用”的习惯,使用常用的,被大多数编译器支持的特性是正常的,而不常用的,只被很少编译器支持的特性,如_Generic关键字,_Noreturn函数标记,_Atomic类型修饰符和头文件
如在判断正整数的奇偶性,判断整数的符号是否相同时,不应总是使用位运算[5];有两个有关联的数据应该使用结构体而不是利用C99标准中定义的复数的实部和虚部进行表示;在不需要特别注意内存占用或没有其他特殊需求时不使用“位域”等。
3 不注意undefined behavior(未定义行为)
例五:C语言源代码
头文件
例六:求出a,b的值:
使用Visual C++6.0编译,a,b分别为30和37,在更老的Turbo C 3.0中,a,b分别为30和39。这一歧义与序列点(sequence point)有关。”a=(i++)+(i++)+(i++);”这样的表达式没有任何实际意义,是错误的[7]。
除此之外,不确定行为(unspecified behavior),实现定义行为 (implementation-defined behavior)等也可能给程序造成意外的结果。
4 过于急切学习编写GUI程序
在PC、智能手机等设备上,接触最多的是图形化的界面,普通用户很少使用命令行界面,初学者总想早一些能编写出来他们所熟悉的GUI程序,但编写程序,学习计算机科学,不等于编写GUI程序。一些初学者学习了C语言以及计算机其他课程之后依然只能写出“黑框框”程序,十分焦躁。并非编写GUI程序就一定有技术含量,而编写算法程序,学习计算机网络原理,理解操作系统原理这些与GUI关系不大的就没有技术含量。编写GUI程序有各种成熟的解决方案,如Windows平台上的WPF技术、Java的JavaFX技术、Python的Tkinter模块等,使用这些技术编写简单的GUI程序是十分容易的,而计算机相关专业的学生应该将精力放在计算机组成原理、计算机网络、数据结构、操作系统、编译原理等核心内容上,浮于表面的学习很难对整个计算机科学有清晰的认知,更难在计算机行业有所发展。
5 结语
(1)对于C语言初学者来说,写出各种不良或者错误的代码是很正常的,但是如果学习理念有误区,就应当及时纠正。有些初学者在反复纠结“a+=a-=a*a;”这种错误的,意义不明的代码,有些初学者迷恋各种似是而非的技巧,有些初学者想“学完”C语言(指的是,掌握所有特性)……这些都是不正确的理念,无助于培养计算机科学的思维习惯。
(2)C语言可以认为简单,因为语法简单,没有像C++那么多的关键字和语法;但C语言也很难,语法简单,一些语义很难理解,而理解这些语义,不是靠背语法、程序代码可以解决的。
(3)学习计算机科学需要正反馈,可以自顶层向底层和从底层向顶层相结合的方式学习,而不应该总是线性的,从“Hello World”到学习联合体,函数指针这种高级特性,应逐渐的加入操作系统、计算机组成原理等其他知识,一步步加深一些语言特性的理解,同时也促进其他理论的学习。