APP下载

C语言教学中的几个常见问题

2009-06-20

计算机教育 2009年10期
关键词:C语言程序设计教学

黄 宇

文章编号:1672-5913(2009)10-0096-03

摘要:作者在多年的C语言教学过程中,对学生中经常遇到的问题进行了一些总结。本文就学生中经常遇到的5个带有普遍性的问题,通过C程序示例进行了分析,指出了出现错误的原因,给出了改正的方法。

关键词:C语言;程序设计;教学

中图分类号:G642

文献标识码:B

1引言

随着计算机应用技术的普及,大学中很多专业都开设了计算机编程课程。在非计算机专业中,大多以教授C语言编程为主。本人长期从事对非计算机专业学生的C语言编程课的教学工作,在长期的教学实践中,发现了一些学生在编程中经常会遇到的问题。在此,就几个典型的常见问题,展开一些探讨。这些问题的解决,对于更深入的理解C语言,将起到一定的帮助作用。

2几个常见问题

2.1无符号数运算问题

大家知道,在C语言中,不同类型的数据一起运算时是按照隐式类型转换的规则进行的,也就是将两个类型不一致的数据首先转换成一致的,然后再进行运算。其转换的基本原则有两点,一是小数据类型向大数据类型转换,二是有符号类型向无符号类型转换。比如,当一个float类型数据和一个double类型的数据进行运算时,就是首先将float类型的数据转换成double类型的数据,然后再进行运算;而当一个int类型的数据和一个unsigned int类型的数据进行运算时,则首先把int类型的数据转换为unsigned int类型的数据,然后再进行运算。对于第一种情况,一般不会遇到问题,但是对于第二种情况,初学者往往注意不到这种转换中可能会隐含的问题,导致程序运行结果出现与自己设想不一致的情况。

下面给一个具体的例子:

#include

int main()

{

unsigned int n = 1;

int m = -1;

if (m < n)printf("m < n");

else printf("m >= n");

return 1;

}

在这段程序中,n = 1,m = -1,显然应该是m= n。初学者遇到这种情况,往往百思不得其解,最终往往会归咎于是不是系统出现了问题。

为什么会出现这种情况呢?这是因为n是unsigned int类型的,而m是int类型的,在m和n进行比较运算时,由于二者的类型不一致,首先要进行类型转换。按照C语言隐式类型转换规则,有符号类型的int转换为无符号类型的unsigned int。这样,m(=-1)中的符号位被当成了“数字”进行转换,有符号的-1成为了无符号的4294967295(四字节的情况下。如果是二字节的则是65535)。而4294967295当然要大于1了,所以就有了以上的运行结果。

不单单是在比较运算中会出现这种情况,在其他运算中,比如加减乘除等,也一样会有类似的情况出现。所以,当有符号和无符号的数据混合运算时,一定要注意这个问题,除非特殊情况,应尽量避免有符号和无符号的混合运算。

2.2计算数组的长度

在C语言中,操作符sizeof( )可以计算一个类型或者一个变量所占用的字节数。比如:sizeof(int)或者sizeof(x)(假定x是int类型的),当一个整数占用4个字节时,就可以得到4的结果。

再比如,一个整数数组:int a[8];

可以通过sizeof(a)/sizeof(int)得到数组a的元素个数。因为sizeof(a)得到的是a数组占用的总字节数,除以每个int所占用的字节数sizeof(int),就是该数组的长度。

由于很多情况下需要知道一个数组的长度,比如在对一个数组排序时,因此,有些初学者就利用sizeof在函数中计算数组的长度。举例如下:

mysort(int a[])

{

int len;

len = sizeof(a)/sizeof(int); //得到数组a的长度

//以下对a进行排序

}

但是往往会发现,这样的结果并不正确,len经常得到的是1(假定是32位系统,一个整数占4个字节)。这又是为什么呢?

这个问题,与C语言中数组参数的传递方式有关。在C语言中,当一个数组当作参数传递时,数组被转换为指针。在上面的例子中,无论你在函数定义是mysort(int a[])还是mysort(int a[100]),在函数内部,a均被转换成int *a类型,与定义mysort(int *a)是一致的。因此,在函数内部,当计算sizeof(a)时,实际上计算的是sizeof(int *)。因此,当作为形参时,无论你的mysort(int a[N])定义中,有无N,或者N是多大,sizeof(a)得到的都是4(假定在32位系统中)。

因此,在一个函数内部,是无法得到一个数组参数的长度的,其长度只能通过参数进行传递。所以,上述的排序函数应该定义成如下的形式:

mysort(int a[], int len)

{

//以下对a进行排序

}

2.3常量字符串问题

在学习完字符串的操作之后,同学们往往会编写一些简单的练习程序,这时经常会遇到一些“莫名其妙”的情况,使得程序不能正确运行。

比如下面这个程序,非常简单,就是把字符串"abcde"中的'a'换成'A':

int main()

{

char *p = "abcde";

p[0] = 'A';

return 1;

}

程序编译没有问题,一运行就出现错误。这是为什么呢?

在这个程序中,字符串"abcde"是一个常量,指针p指向了这个常量。而“常量”顾名思义是不能修改的,而该程序试图通过指针p修改一个常量字符串,导致运行错误。

把程序修改如下,就没有问题了:

int main()

{

char p[] = "abcde";

p[0] = 'A';

return 1;

}

这是因为p是一个字符数组,并通过初始化的方法对该数组进行了赋值。虽然字符数组p也是一个字符串,但是p不是常量,可以修改,因此就不会出现运行错误了。

同样,如果是这样,也不会出现错误:

int main()

{

char p[] = "abcde";

char *q = p;

q[0] = 'A';

return 1;

}

因为在这里q指向是字符数组p,而不是字符串常量"abcde"。

2.4文件结束判断问题

在C语言中,函数feof( )可以判断文件结束,但是初学者在使用feof( )时,经常会犯错误。请看下面这个例子,该程序实现将文件a.txt拷贝到b.txt的功能,通过feof判断a.txt是否结束,在结束之前,每次读一个字符,并写到b.txt中。程序如下:

int main()

{

FILE *pi, *po;

char c;

pi = fopen("a.txt", "rb");

po = fopen("b.txt", "wb");

while (!feof(pi))

{

c = fgetc(pi);

fputc(c, po);

}

fclose(pi);

fclose(po);

return 1;

}

程序很简单,看似没有什么错误。但是这里却隐含了一个初学者经常会犯的错误。运行一下该程序,就会发现在b.txt的最后,会“奇怪”地多出了一个字符。

问题出现在什么地方呢?主要是对feof的认识有误造成的。

仔细看一下feof的功能,会发现当读完了最后一个字符后,feof还是保持“假”,只有当读完了最后一个字符再试图读文件时,feof才为真。也就是说,feof判断是否到达文件尾比实际情况要“晚”一步。按照上面的程序,当fgetc从a.txt中读完了最后一个字符后,feof并不马上为真,还要循环再读一次a.txt,并通过fputc函数将这次得到的结果(在字符c中)写入到b.txt中,造成了b.txt中多了一个字符。而这时,feof才变成了真,程序退出循环结束。

明白了这一点,程序按照如下方式增加一个if语句就可以了:

int main()

{

FILE *pi, *po;

char c;

pi = fopen("a.txt", "rb");

po = fopen("b.txt", "wb");

while (!feof(pi))

{

c = fgetc(pi);

if (feof(pi)) break;//新增加的一个判断

fputc(c, po);

}

fclose(pi);

fclose(po);

return 1;

}

2.5结构体的大小问题

通过sizeof可以计算一个结构体占用的字节数,比如下面一段程序是计算结构体S所占的字节数。程序的输出应该是多少呢?

int main()

{

struct S

{

char a;

int n;

};

int n;

n = sizeof(struct S);

printf("%d", n);

return 1;

}

char占1个字节,int占4个字节,初学者往往会回答S长度是5。但是一运行程序,发现输出的结果却是8。见到这样的结果,初学者往往又不得其解,不知为什么会这样。

这里涉及的就是所谓的地址对齐的问题,编译程序,在默认的情况下,会按照一定的原则,比如让宽度为2的基本数据类型(short等)都位于能被2整除的地址上,让宽度为4的基本数据类型(int等)都位于能被4整除的地址上等等,这样做的原因是为了加快程序的运行速度。也就是说,对结构体进行了一定的填充,使得它的成员的地址满足一定的要求。关于如何对齐问题涉及的内容比较多,这里就不详细解释了,有兴趣的读者可以参考有关计算机组成原理方面的书。

3结束语

我们在C语言教学过程中,经常会遇到学生提出的各种问题,有些问题是个别性的,有些问题则是普遍性的,从学生遇到的最多的问题中,整理出了这5个具有普遍性的问题,希望对有关C语言的教与学能起到一定的帮助。

参考文献:

[1] 薛胜军. 计算机组成原理[M]. 武汉:华中科技大学出版社,2000.

[2] 王诚, 刘卫东,宋佳兴. 计算机组成与设计[M]. 3版. 北京:清华大学出版社,2008.

[3] 严蔚敏,吴伟民.数据结构(C语言版)[M].北京:清华大学出版社,2006.

[4] 谭浩强. C程序设计[M]. 北京:清华大学出版社,2006.

[5] David J. Kruglinski. Visual. C++技术内幕[M]. 4版. 北京:清华大学出版社,2001.

[6] H.M.Deitel,P.J.Deitel. C程序设计教程[M]. 北京:机械工业出版社,2001.

猜你喜欢

C语言程序设计教学
基于OBE的Java程序设计个性化教学研究
项目化教学在Python程序设计课程中的应用
C++程序设计课程教学改革研究
医学专业“Python程序设计”课程教学改革总结与思考
“C语言程序设计”课程混合教学探索
基于C语言的计算机软件编程技术探究
中职C语言单片机课堂教学中的趣味性探讨
“自我诊断表”在高中数学教学中的应用
计算机原理中C语言的应用价值
在遗憾的教学中前行