分享

国内C语言教材中几种值得商榷的说法

 巨之说 2022-05-08 发布于山东省

作者:巨同升

“C语言程序设计”这门课程在国内高校普遍开设已有近三十年,课程的建设和研究取得了长足的进步,涌现出了数量众多、各具特色的C语言教材。尽管如此,在许多C语言教材中还或多或少地存在着一些不准确甚至是值得商榷的说法。下面将对国内教材中常见的几种说法进行辨析,并期望与广大同行商榷。

1. 关于C语言的输入输出语句

有的教材中说“C语言中没有输入输出语句,只有输入输出函数”,果真如此吗?

完整的C语言是由语言标准和标准库两部分组成的[1]。语言标准相当于C语言的内核,标准库(主体是库函数)相当于C语言的扩展部分。这种设计极大地增强了C语言实现的灵活性和程序的可移植性,因为可以根据某类计算机的硬件特点而单独修改C语言标准库部分的实现。

在C语言的语言标准部分的确没有输入输出语句,也没有输入输出函数。不过在C语言的标准库部分,明确地定义了各种输入输出函数,从而可以在C语言程序中以语句的形式来调用这些输入输出函数。

既然这种函数调用语句可以实现输入输出的功能,将它们称为“输入输出语句”(而不必称为输入输出函数调用语句)也是顺理成章的。

因此正确的说法是:在C语言的语言标准部分没有输入输出语句,但在C语言的标准库部分有输入输出函数,从而在完整的C语言中有输入输出语句。

2.关于八进制和十六进制负整数

有的教材中说“在C语言中,八进制和十六进制整数只有正数,没有负数”,这种说法是有问题的。实际上,在C语言中,八进制和十六进制整数既可以使用正数,也可以使用负数。下面的程序就是一个很好的证据。

#include <stdio.h>

int main(void)

{int a,b,c,d;

 a=0127;

 b=-0127;

 c=0x1af;

 d=-0x1af;

 printf("a=%d,b=%d,c=%d,d=%d\n",a,b,c,d);

 printf("a=%o,b=%o,c=%x,d=%x\n",a,b,c,d);

 return 0;

}

该程序的运行结果如下图所示。

       从该程序的运行结果可以发现,程序中可以使用八进制和十六进制的负整数,但是不能以负数形式输出八进制和十六进制的整数。

因此正确的说法是:在C语言中,以八进制和十六进制形式输出整数时,只能输出正数和0,不能输出负数。

3. 关于整数在内存中的表示形式

有的教材中说“在C语言中,整数是以二进制补码的形式在内存中存储的”。其实,这种说法是不准确的。在C语言中,有符号整数的确是以二进制补码形式在内存中存储的,其最高位为符号位,用以表示该整数的正负。但是对于无符号整数来说,由于不存在负数,并不需要表示正负的符号位,因而它的每一位都是数值位,这种表示形式应称为“无符号二进制形式”。

因此正确的说法是:在C语言中,有符号整数是以二进制补码形式在内存中存储的,无符号整数是以无符号二进制形式在内存中存储的。

4. 关于后自增(减)运算符的优先级

国内绝大多数的C语言教材都说“在C语言中,后自增(减)运算符的优先级与前自增(减)运算符相同”。其实,这种说法是有问题的。

在传统C语言(即C89标准之前的C语言)规范中,后自增(减)的优先级的确是与前自增(减)相同的,都是2级。但是,从C89标准开始,已经将后自增(减)的优先级调整为1级,而前自增(减)的优先级依然为2级,从而使得后自增(减)的优先级高于前自增(减)[1] [2]。遗憾的是,这种调整并未在国内的绝大多数C语言教材中反映出来。

5. 关于scanf函数中的第二个参数

为什么scanf函数中的第二个参数只能是变量的地址,而不能是变量名本身呢?大多数教材中并未给出解释,而有的教材则认为是为了提高编程的灵活性,使得scanf函数中的第二个参数既可以是若干个变量的地址,也可以是指向某些内存单元的指针。其实,真正的原因在于C语言中函数参数的单向传递规则,即只能将实参的值传递给对应的形参,而不能将形参的值传递给对应的实参[3]

在程序中调用scanf函数完成数据的输入,是通过执行该函数的函数体语句实现的。不过在执行该函数的函数体时所输入的数据,首先存放于函数体中定义的局部变量中。调用scanf函数时,若直接以待存储数据的变量名作为实参,则函数体中局部变量(包括形参)的值并不能直接传递给实参;若改用待存储数据的变量的地址作为实参,就可以通过跨函数间接引用的方式,将函数体中局部变量的值传递给主调函数中的变量了。

6. 关于malloc函数的返回值类型

在大多数教材中,在调用malloc函数时,总是要对其返回值进行强制类型转换。例如,

int *p;

p=(int *)malloc(sizeof(int));

这些教材中认为这种强制类型转换是必不可少的,其实并非如此。在传统C语言(即C89标准之前的C语言)规范中,malloc函数的返回值为char *类型,由于这种类型与其他的指针类型都不是赋值兼容的,因此不能直接进行赋值运算,而必须先进行强制类型转换,再进行赋值运算[2]

不过,从C89标准开始,已经将malloc函数的返回值类型改为void *类型。void *是C89标准中定义的通用指针类型,通用指针与所有其他类型的指针都是赋值兼容的,即可以不经过强制类型转换而直接相互赋值[2]。例如,

int *p;

p=malloc(sizeof(int));

此外,calloc函数和realloc函数的用法与malloc函数是类似的。

7. 关于文本打开方式与二进制打开方式

C语言中的各类文件打开方式均包括两种,如读方式包括“r”和“rb”,写方式包括“w”和“wb”。在大多数C语言教材中,将文件的这两种打开方式称为“打开文本文件”与“打开二进制文件”。

这种叫法很容易使人误解为用第一种打开方式创建的就是文本文件,用第二种打开方式创建的就是二进制文件。其实一个新创建的文件是文本文件还是二进制文件的决定因素,是向文件中写入数据的函数,而不是文件的打开方式[3]。一般而言,使用fprintf、fputc和fputs等函数创建的文件,是文本文件;而使用fwrite函数创建的文件,则是二进制文件。相应地,fscanf、fgetc和fgets等函数用于读取文本文件;而fread函数则用于读取二进制文件。

既然如此,为什么还要将文件的打开方式区分为这两种方式呢?其实,这源于两类操作系统对于回车换行的不同处理方式。在第一类操作系统(如UNIX和Linux)的文本编辑软件中,采用与C语言相同的处理方式,只需用一个换行符,即可实现回车换行。而在第二类操作系统(如DOS和Windows)的文本编辑软件中,则采用与C语言不同的处理方式,需要用一个回车符和一个换行符的组合,方可实现回车换行。

因此,在第二类操作系统中,当用第一种方式打开文件并对文件进行写入操作时,将会把每一个换行符替换为一个回车符和一个换行符的组合;当用第一种方式打开文件并对文件进行读出操作时,将会进行相反的替换。然而,当用第二种方式打开文件并对文件进行写入和读出操作时,将不会对字符进行任何替换。

而在第一类操作系统中,第一种打开方式与第二种打开方式是没有区别的。不论用哪种方式打开文件,在对文件进行写入和读出操作时,都不会对字符进行任何替换。

可见,这两种打开方式均是既可以打开文本文件,也可以打开二进制文件,区别在于对回车换行的处理方式不同。因此,正确叫法应该是“文本打开方式”与“二进制打开方式”[3]

参考文献 :

[1]Samuel P. Harbison Ⅲ.C语言参考手册(第5版).北京:机械工业出版社,2003.

[2]K.N.King.C语言程序设计现代方法(第2版).北京:人民邮电出版社,2010.

[3]巨同升.C语言程序设计新思路[M].北京:科学出版社,2020.

    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多