为了正常的体验网站,请在浏览器设置里面开启Javascript功能!

学习C语言100问

2010-02-03 19页 doc 113KB 23阅读

用户头像

is_810761

暂无简介

举报
学习C语言100问学习C语言100问 问题1:什么是分程序(复合语句)? 分程序是指一对大括号{}之间的一段C语言程序。每一个C函数的函数体都是包括在一对大括号中,switch语句所有取值情况的列举也是包括在一对大括号中,以此可以看出分程序在C语言中使用非常广泛,用户也可以根据需要自己组织分程序(更多的是程序功能的需要),它在C程序中的功能相当于一局部程序块,其间可以定义变量,这种变量称为局部变量,只能定义在分程序的开始部分,变量的有效范围是分程序内部。如果局部变量与分程序以外的变量重名,在本分程序内部,该局部变量对外面的同名变量进行屏蔽,...
学习C语言100问
学习C语言100问 问题1:什么是分程序(复合语句)? 分程序是指一对大括号{}之间的一段C语言程序。每一个C函数的函数体都是包括在一对大括号中,switch语句所有取值情况的列举也是包括在一对大括号中,以此可以看出分程序在C语言中使用非常广泛,用户也可以根据需要自己组织分程序(更多的是程序功能的需要),它在C程序中的功能相当于一局部程序块,其间可以定义变量,这种变量称为局部变量,只能定义在分程序的开始部分,变量的有效范围是分程序内部。如果局部变量与分程序以外的变量重名,在本分程序内部,该局部变量对外面的同名变量进行屏蔽,另外提示一点的是,一般不用分程序来保存变量,例如: #include int test=5; void main( ) { int test=10; void fun1( );/*-----5----*/ fun1( ); printf(“2--%d\n”,test);/*-----10-----*/ {int test=15; printf(“3--%d\n”,test);/*-----15-----*/ } } void fun1( ) { printf(“1--%d\n”,test); } 问题2:什么情况下用switch语句比if语句的多重嵌套更适合? 如果有两个以上基于同一个数字型变量(整型变量,字符型变量,枚举类型变量等)的条件表达式,尤其是对于作为判断的数字型变量的取值很有限,且对每一个不同的取值,其所做的处理也不一样的情况,最好使用一条switch语句,这样更易于阅读各维护。这里有两点需要注意就是,第一就是用于作为判断条件的变量一定要是数字型的,另一点就是所有的判断条件都是基于同一个数字变量,而不是多个变量。 例如:有如左下的if嵌套更适合用右下的switch语句表达。 char grade; if(grade= =’A’) printf(“85~100\n”); else if(grade= =’B’) printf(“70~84\n”); else if(grade= =’C’) pritnf(“60~69\n”); else if(grade= =’D’) printf(“<60\n”); else printf(“error\n”); switch(grade) {case ‘A’: printf(“85~100\n”);break; case ‘B’: printf(“70~84\n”);break; case ‘C’: printf(“60~69\n”);break; case ‘D’: printf(“<60\n”);break; default: printf(“error”);break;} 问题3:switch语句必须包含default分支吗? 回答是否定的。但是为了逻辑上的严密性,一般应写default分支,这样在出现所有的case子句以外的取值时才不至于难以确定其错误所在,例如象上面的,如省略了后面的default子句,而用户如输入的是除 ‘A’,‘B’,‘C’,‘D’以外的其他字符时,程序不做出任何反应,而这本身是一种非法输入。 问题4:switch语句的最后一个分支可以不要break语句吗?每一个case分支后面是否必须要加break语句? 两者的都是“不”。大家知道,在switch 语句中,如果作为条件判断的数字表达式的值与某一个case后面的取值相等,则以该case分支语句作为入口顺序执行后续语句,如遇上break语句,则结束switch语句的处理,转而处理switch语句的后续语句,根据这个道理显然最后一个分支中的break语句可以省略,因为无论是否有break语句,此时都会结束switch语句的处理。 问题5:怎样判断循环是否提前结束?在多循环条件下,又如何知道是因为哪个条件不满足而使循环提前结束的? 解决这种问题通常是在循环语句的后面再用一个或多个判断语句检查循环变量的取值,从而确知循环是正常结束还是提前结束,如循环条件是由多个循环变量构成,则可以对各个变量分别进行判断。例如: #include void isprime(int num) { int s; for(s=2;s<=sqrt(num);s++) if(num % s = =0) flag=0; if(s>sqrt(num)) return(1); else return(0); } 以上的例子用于判断给定的数是否为素数,函数中的循环语句正常结束是当s的取值大于给定数的平方根,但只要该数能够被某一个大于1而小于其平方根的数整除,即说明该数不是素数,循环提前结束,于是在循环语句后面加一个判断,以得出正确结果。 问题6:什么叫左子值?数组名为什么不能作为左子值? 左子值是指可以被赋值的表达式,即可以出现在赋值符号的左侧的表达式。同理出现在赋值符号右侧的称为右子值,每一个赋值语句都必须有一个左子值和一个右子值。左子值必须是内存的一个存储单元(在程序中通常表现为一个变量),而不能是一个常量。我们知道数组名在C语言中,代表的是数组所分配的存储单元的起始地址,而C语言中数组不可以在程序运行的过程进行移动,也就是说,数组一旦定义,其所占据的存储单元是固定的,也即他的起始地址也是固定不变,所以C语言在的数组名被当作是一个常量,也因此不能作为左子值。 问题7:++var和var++有什么区别? “++”运算符在C语句中的使用可以说多得不能胜数,但是要真正掌握它的运算却也并非易事,尤其是在多个运算符混合使用的情况下更是如此。“++”有前辍运算(++var)和后辍运算(var++)两种,大家都知道前者在表达式中是先把var加1,而后引用var的值,后者是先引用var的值,而后var加1,但在如下的运用中,就很难用这种规则来解释了: 如:int x=10;y=x+++x++;经以上运算y的值是多少呢?答案是20,这里首先要确定各运算符的运算关系,按C语言确定运算符的规则(从左至右取尽量多的字符构成一个运算符),以上表达式相当y=(x++)+(x++),其中有两个后辍运算的自加运算,值得注意的是,引用时先把x的值在整个表达式中使用,而后进行两次加1操作,所以y后来的取值为20,而x的值为12,不理解的话,很容易得出y的值最后是21的错误结果。 再如:int x=5; printf(“%d,%d\n”,x++,x++);输出的究竟是“5,6”,或“6,5”,还是“5,5”,再或者是其他取值呢?按照一般的思路要得出正确结果还是颇费周折的,正确结果是“6,5”,这里牵涉到C语言实参的求值顺序(自右至左计算各实参表达式)。 问题8:变量存储在内存中的什么地方? 变量名是一个符号地址,代表内存中的某个内存单元,那么它究竟存储在什么位置呢?对于一般程序设计人员,并不需要具体知道变量的存储位置,但应该根据变量的存储属性判断变量的存储区域。 第一种是在函数外部定义的变量(全局变量或静态外部变量)和在函数内部定义的static变量,其生存期是程序运行的全过程,这些变量被存储在数据段中。数据段是在内存中为这些变量留出的一段大小固定的空间。 第二种是在函数内部定义的auto变量(不加关键字static的变量)的生存期从程序开始执行其所在的程序块代码时开始,到程序离开该程序块时为止,即定义该变量的函数的部。作为函数参数的变量只在调用该函数期间存在。这些变量被存储在栈中。栈是内存中的一段空间,开始很小,以后逐渐自动增大,这个界限由系统决定,并且通常非常大,因此程序员不必担心用尽栈空间。 第三种是内存空间实际上并不存储变量,但是可以用来存储变量所指向的数据。如果把调用malloc()函数的结果赋给一个指针变量,那么这个指针变量将包含一块动态分配的内存的地址,这块内存位于一段名“堆(heap)”的内存空间中。堆开始时也很小,但当程序员调用malloc()或calloc()等内存分配函数时它就会增大。堆可以和数据段或栈共用一个内存段,也可以有它自己的内存段,这取决于编译选项和操作系统。 问题9:使用register变量的意义何在?有什么用途? register作名词的本义是“注册,寄存器”的意思,在程序中如果用register修饰定义变量,表示将该变量保存在CPU的寄存器中,显然可以加快访问速度,但也不可以随意使用,而有以下几点限制: 第一,register变量必须是能被CPU寄存器所接受的数据类型,如不能把一个组合类型的变量指定为register变量。 第二,因为register变量保存在CPU中,显然不应该用取地址运算符“&”来获取变量的地址。 除去以上的限制外,也并不是说我们尽可能地多地定义register变量就能加快程序的运行速度,毕竟CPU中寄存器是有限的,如果你把变量指定为register变量,意味着可用于别的用途的寄存器就减少了,如程序运算产生的中间结果,它们的应用又很频繁,在寄存器不足的情况下,只好借助于内存,这样反倒会降低程序的运算速度。 在现今的C版本中,大多已没有定义register变量的必要,因为编译程序忽略register修饰符,而根据寄存器的使用情况和变量的情况决定是否把变量解释为register变量。 问题10:浮点数比较是否可靠? 众所周知,计算机只能表示离散的数据,而浮点数是连续,任意两个浮点数之间都有无限多个浮点数的存在,所以浮点数是计算机编程中的一个盲点。 由于浮点数很难对付,因此比较一个浮点数和某个值是否相等或不等通常是不好的编程习惯。但是,判断一个浮点数是否大于或小于某个数就安全多了。 例如:如果你想以较小的步长依次使用一个范围内的数字,你可能编写这样一个程序: #include main() { float f; for(f=0.0;f!=70.0&&f<71.0;f+=0.007) ; printf(“f is now %g\n”,f); } 然而,舍入误差可能导致f的值永远不等于70.0,这样,循环会跳过70.0。加入不等式“f<71.0”变是为了防止在这种情况下程序继续运行很长时间。 一种安全的做法是用不等式“f<71.0”作为条件来终止循环,例如: float f; for(f=0.0;f<71.0;f+=0.007) ; 或事先算出循环次数,然后通过整数进行循环计数: float f; int count=70.0/0.007; for(f=0.0;count-- >0;f+=0.007) ; 问题11:说明一个变量和定义一个变量有什么区别? 说明一个变量意味着向一个编译程序描述变量的类型,但并不为变量分配空间。定义一个变量意味着在说明变量的同时还为变量分配存储空间。在定义一个变量的同时可以对变量进行初始化。 一个变量可以被说明多次,但只能被定义一次。因此,不应该在头文件中定义变量,因为一个头文件可能会被一个程序的多个源文件包含。 函数说明和函数定义也有类似的性质,当然不是说头文件中也不应该定义函数。 问题12:为什么在定义变量的时候应该说明变量的数据类型? 数据类型是变量的一个重要的属性。不同的数据类型通常代表所定义的变量所占据的内存长度不同,编程时应该根据你所要的变量的取值范围选择一个合适的数据类型,例如:同样是作为循环计数变量,如果只循环100次,则可以选择循环变量的类型为字符型(char),而在循环次数为50000次的时候,则应该选择循环变量的类型为长整型。 此外,不同的数据类型往往也代表其所属类型可以参加的运算也不同。比如:整型变量可以有取模(%)、按位与(&)等运算,而浮点数则不具有这些运算。因此,编程时也应该根据变量所要参加的运算而选择相应的数据类型。 问题13:怎样使用宏定义(#define)?使用宏定义有什么好处? 宏定义(#define)是一种预处理指令,在程序中用字符串(宏体)来被定义的宏名。有以下几点好处: 第一,在输入源代码时,可省去许多键入操作,因为宏名往往比宏体要短。 第二,因为宏只需定义一次,但可以多次使用,而且宏名往往代表一定的含义,所以使用宏能增强程序的易读性和可靠性。 第三,使用宏不需要额外的开销,因为宏所代表的代码只在宏名出现的地方展开,因此不象函数调用会引起程序跳转,宏展开不会引起程序跳转。 第四,宏的参数对类型不敏感,因此你不必考虑将何种类型的数据传递给宏。 宏虽然有诸多好处,但在使用也应该小心行事,尤其要注意以下几个方面: 第一,在宏名和括起来的参数的括号之间绝对不能有任何空格类字符。 第二,为避免在宏展开时产生歧义,宏体也应该用括号括起来。 第三,对传递给宏的实参要千万小心,尤其是中把自增(减)变量作为参数传递给宏时。 问题14:可以用#include指令包含扩展名不是“.h”的文件吗? 回答是肯定的。可以用#include命令包含任何一个文件。如果一个程序是由多个源程序文件所构成,用在一个源程序文件中使用#include把其它程序文件都包含进来的方法是解决多文件程序的一种有效方法。当然,为了程序的可读性考虑,最好不要把不以“.h”为扩展名的文件用#include命令包含进来,这样不容易区分哪些文件是用于编译预处理的。 问题15:在程序中加入注释有什么好处? 注释作为一种被编译程序所忽略的说明性文字,往往被初学编程人员所忽视,认为它对于程序的功能没有任何作用,不加并没有损害,所以往往很少使用注释,这种看法是完全错误的。适当使用注释至少有以下两个方面的好处: 第一,在软件技术相对滞后的今天,为了开发软件的需要,大量的程序除了要能在计算机上运行外,出于种种原因,还需要程序源代码能够让编程人员读懂,而这本身是一件非常费力的工作,此时,注释的作用就不可低估了,有无良好的注释成了程序能不能读懂的关键点之一,这也现代源程序文档化要求的一个重要方面。 第二,注释还可以为程序调试带来很大的方便。无论是多么有的程序人员,他编制的程序都不可能一次即保证完全正确,也因此需要对程序进行调试。调试工作的第一步是查错,这也是一件很费力的工作,往往是按模块分部进行。如怀疑某模块有错时,可以先将原来的模块注释掉,加入一个新编模块,如错误消失,则说明原来的模块确实有问题,此时才将原来的模块删除。 问题16:怎样检查一个符号是否已经被定义? 利用预处理指令#ifdef可以检查一个符号是否已被定义,例如,在指针一节中,你不能确切知道NULL这个符号是否已经有定义,你可以用如下的一段程序实现这一点: #ifdef NULL #undef NULL #endif #define NULL(void *) 0 这里,首先检查NULL符号是否已被定义,如果已有定义,则取消原来的定义,然后重新定义NULL符号。 问题17:什么是变量的间接引用? 用变量名对变量的值进行引用的方式称为变量的直接引用。对指向变量或内存中的任何对象的指针来说,指针就是对变量值的间接引用。如果p是一个指针,p的值就是其所指向的对象的地址;*p的值就是其所指向的内存单元的值。 值得注意的是,用指针间接引用变量的值时,一定要明确知道指针的当前指向,断不可以随意通过指针间接引用而修改一个内存单元的值。 如:int *p,a; *p=5; 的做法就是很危险的,指针p当前的值不知道的情况下,它既可能指向用户程序区,也可能指向别的用户的程序区,还有可能指向操作系统区,盲目修改可能会带来严重后果。 问题18:什么是空指针? 在指针和链表中用的非常广泛的空指针,表示的是不指向任何对象的一种指针,其值定义为NULL,它是在头文件“stdio.h”中定义的一个宏,其值与任何有效指针的值都不同,NULL是一个纯粹的零,它可能被强制转换为void *或char *类型。即NULL可能是0或(void *) 0等。 值得注意的是,绝对不能间接引用一个空指针,否则,你的程序可能会得到一个毫无意义的结果,或得到一个全部是零的值,或者会突然停止运行。 问题19:什么是void指针? void指针一般称为通用指针,千万要与空指针严格区分,void指针指向某个对象,但该对象不属于任何类型,但之所以称它为通用指针,是因为任何时候你都可能用其它类型的指针来代替void指针或者用void指针来代替其它类型的指针,如:函数malloc()的返回值是void类型,你可以在程序中将它的返回值赋给任何类型的指针。 问题20:两个指针可以相减吗? 从一般意义上讲,回答是可以。但作为一种编程工具,在程序中做任何事都得考虑其物理意义,两个指向不同类型对象的指针其相减就没有任何意义,故不应该做减法,同样不是同一组相关单元的两个指针其相减也没有任何意义,这就象问人家两个不同街道的两个门牌号之间相差多少个门牌号一样,是没有任何价值的,因此真正有意义的两个指针相减,只是在当两个指针指向相关的同类型的对象时,通常是数组,才对它们进行相减,其值为两个指针所指向的对象之间相差的同类型的对象的个数,即数组元素的个数。 问题21:数组的尺寸可以在程序运行时才确定吗? 在C语言中,回答是肯定的,也就是说C语言不可以定义动态数组,数组的大小必须是在程序编译的时候就已经确定。可能这样会给程序的灵活性带来一定的影响,但动态数组的定义会得程序的运行速度的降低。 如果你确实事先难以确定数组的大小,其实C语言还是可以帮你实现的,那就是我们可以定义一个指针,然后调用malloc()或calloc()函数从堆中为这个数组分配内存空间,如: main() { int *pa,num; scanf(“%d”,&num); pa=(int *)malloc(num*sizeof(int)); ………… } 问题22:malloc()函数与calloc()函数有什么区别? 这两个函数都可以用来分配动态内存空间,但两者除了调用时的参数的数目不同以外,还有一点重要的区别,那就是 malloc()函数不能初始化所分配的内存空间,而calloc()函数会将所分配的内存空间的每一位都初始化为零。这样一来,用malloc()函数所分配的空间,如果原来没有使用过,则其中的每一位都是0,但如果这部分内容曾被分配、释放和重新分配,则其中可能遗留原有的数据。用calloc()函数所分配的空间,无论以前是否使用过,都将初始化为零。所以在使用中,程序员应该根据你的实际需要选择相应的函数,除此外,从使用的角度看,这两个函数没有太大的区别。 问题23:什么叫做内部函数?怎样说明? 只在定义某函数的程序文件中才能调用的函数称为内部函数。与变量不同,函数的默认属性是外部的,即可以被其他程序文件中函数所调用,因此为了说明一个函数是内部函数,在定义该函数或说明该函数时应该加修饰符static, 如:/*文件1..c*/ #include “stdio.h” static void sort(int *a, int n); void select(int *a, int n, int num); …………… void sort(int *a, int n) { int j,k; ……… } void select(int *a, int n, int num) { int k,mid; sort(a,n); ……… } 在“文件1.c”中,函数sort就是内部函数,它只能在该程序文件中调用,而不能在其他程序文件中调用,而函数select就是外部函数,它可以在其他程序文件中调用。 问题23:为什么要说明函数原型? 函数原型提供函数调用时所需的参数个数及各参数的类型,编译程序就据此检查函数调用时给定的实参是否符合要求。当然,如果函数定义总在函数调用处之前,可以不必说明函数原型,但是,因为C程序可以编制得非常大,函数调用之处与函数的定义之处可能相隔非常之远,这样编程时每次都去参考函数定义处的参数的数目和类型,其不方便之处显而易见;如果使用函数说明,则因为可以与调用处相隔很近(因为函数可以说明多次)。在多程序文件程序中尤其如此,往往是在程序的开头对程序中所使用到的函数都进行说明,以便查找。 值得注意的是,如果函数定义在后,调用在前,应务必在调用前对函数原型进行说明。 问题24:一个函数可以有多少个参数吗? 对于一个函数的参数的数目C语言并没有明确的说明,但参数不应该太多,这也是编程人员应该遵循的一种程序风格。因为函数参数在调用过程中存放在堆栈中,参数数目太多的情况下,意味着更多的数据需要保留,从而降低了函数的运行速度,从另一个方面说,参数越多编程的难度显然也会加大,有利于发现程序在错误,因此,通常应该尽可能地减少参数的数目,如果实在要使用很多的参数时,可以考虑用一个结构体类型变量来容纳这些参数,用这个结构体类型的变量来作为函数的参数。 问题25:如果一个函数没有返回值,是否需要加入return语句? 如果一个函数没有返回值(函数原型的“返回值”类型为void),此时没有必要加入return语句,但函数执行如可能引起严重错误(可以预料的),则应该预设return语句返回,另外注意一点的是,不应该在函数中设多处return语句,这程序编程习惯不可取,而应该让函数的退出操作尽量集中和简洁(必要时不妨使用goto语句)。 需要注意的是,函数没有返回值不是在函数定义时缺省返回值类型的情况,其实应该对所有函数都明确给定其返回值类型,包括返回值为空(即没有返回值)。 问题26:用数组名或指针作为函数的参数其实质是什么? 众所周知,数组名做为一个符号地址其所代表的是数组所分配的内存单元的起始地址,而指针本身就是地址,用这两者作为函数的形参时,它们是等价的,都是相当于用指针变量作为函数的形参(也正因于此,用数组名作函数形参时,可以进行自增、自减等运算);用这两者作为函数的实参时,按C语言中“用值传递”参数传递原则,即是把实参数组名(指针)的值传递给相应的形参(因传递的值是一个地址,故有些资料介绍这是一种“地址传递”),也就是说,经过参数传递后,形参数组名(指针)与实参数组名(指针)指向同一内存单元,故此,如果在函数中通过形参数组名(指针)的引用可以修改实参数组名(指针)所代表的数组元素的值。 问题27:exit()与return有什么不同? 用exit()可以退出程序的处理而把控制权交回操作系统,而用return语句可以结束当前函数的处理,控制权交回调用该函数的函数(即所谓主调函数)。前者通常是用在程序中可能发生的不可忽略的错误时使用,如在文件打开出错时,我们通常用exit()来退出程序的处理,而后者是作为函数运行的正常情况或非严重错误时使用,但如在main()中使用return语句,其效果与exit()没有多大差别。 问题28:为什么要谨慎对待紧跟在数组后面的元素? C语言不检查数组下标的越界,这是众所周知的事,在给程序员带来了一定的灵活性的同时,却也给程序编制留下了一个不小的陷阱,要切记C语言的数组元素下标是从0开始的,也就是说下标值为数组长度的元素已经不是数组的元素,例如:int a[10];a[10]=5;如果不是特意安排,象这种赋值在程序中就不应该出现。在定义一个数组后,系统为其分配相应的存储区间,而数组后面的存储单元既然不是数组存储区内,系统可能会将它分给其他的程序或系统程序使用,显然,如果你在程序随意将这些单元进行修改,可能会影响其他程序甚至系统程序,从而产生不可预料的后果,所以对紧跟数组的存储单元应该谨慎而又谨慎。 问题29:通过指针和数组下标方式都可以访问数组元素,哪一种方式更好? 首先让我们看一下两种引用方式的引用过程。例如: int a[10],*p,k; /*方式1*/ for(k=0;k<10;k++) printf(“a[%d]=%d\n”,k,a[k]); /*方式2*/ for(p=a,k=0;p0) {x0=Xm;F0=Fm;} else {x1=Xm;F1=Fm;} } finish:printf(“\nThe root of this equation is %f\n”,Xm); } /*程序段2*/ float F0,F1,F(float x),x,x1,x0,a,b; float Fm,Xm; int k,finished; F0=F(a);F1=F(b); if(F0*F1<=0) { x0=a;x1=b;k=1;finished=0; while(k<=100&&finished= =0) { Xm=(x0+x1)/2;Fm=F(Xm); if(fabs(Fm)0) {x0=Xm;F0=Fm;} else {x1=Xm;F1=Fm;} k++; } printf(“\nThe root of this equation is %f\n”,Xm); } 程序段2中各结构化都符合单入口和单出口的要求,但由于引入了一个变量finished作为标志,反倒使程序难以理解了,而这还只是一层循环,如果是多重循环,象程序段2的这种程序将更加难懂,如此看来,有时使用goto语句,还可以收到意想不到的效果。 问题39:for语句与while语句在实现循环时,哪一个更好? for语句在很多语言中,都只是一个计数循环语句,即是在已知循环的执行次数的情况下适用,但在C语言中,for语句与while语句的功能一样强大,在有些方面显得更有优越性,跟while语句相比,for语句显得更为紧凑,它把与循环控制有关的部分都放在for语句的三个表达式中,使得for语句更清晰,更容易使用。 但对于初学者要掌握for语句的执行过程似乎比较困难,主要是难以接受这种形式,既如此,何不去努力适应呢?下面还是把它的执行过程再赘述一次: for语句的一般形式:for(表达式1;表达式2;表达式3)循环体 其执行过程是: (1)计算表达式1; (2)计算表达式2,若其值为真(非0),则执行循环体,然后执行(3);若为假(值为0),则结束循环,转至(4); (3)计算表达3,转(2); (4)执行for语句的后续语句。 写成与之等价的while语句形式是: 表达式1; while(表达式2) { 循环体; 表达式3; } 只要你认真分析for语句的执行过程,多加练习,相信你会很快掌握它的使用,并喜欢上它。 问题40:break语句和continue语句的作用是什么?什么时候使用它们? break语句的作用是使流程跳出switch结构(参问题4)或用来从循环体中跳出,即提前结束这两种结构的处理,转而处理它们的后续语句。 continue语句的作用是用在循环体中,提前结束本次循环,即跳过循环体中下面尚未执行的语句,接着进行下一次是否执行循环的判断。 用在循环体,它们两者的区别是前者用于结束整个循环的处理,转而执行循环的后续语句(循环体外部),而后者只是放弃执行它后面的循环体内语句的处理,转而继续判断循环判断语句(循环语句内部)。 应该指出,这两者也都不符合结构化程序设计,合理安排程序完全可能不使用这两个语句,但与goto语句一样,在某些时候使用它们,可以收到意想不来的好效果。 问题41:如何看待二维数组中的行地址与列地址的关系? 二维数组是语言学习中的难点,在C语言中更是如此,原因是C语言中的数组处理非常灵活,它与指针的关系非常密切,很难加以区分,当然也没必要有意将两者割裂开来,从某种意义上讲,两者就是一会事。 C语言把二维数组看成一特殊的一维,即数组元素是一维数组的一维数组,为与元素地址相区分,而把这个特殊一维数组(二维数组中的一行)的地址称作行地址,把数组元素的地址称为列地址。 例如:int array1[3],array2[3][4]; 在一维数组array1中,一共有三个元素array1[0],array1[1],array1[2],每个元素都有相应的地址&array1[0],&array1[1],&array1[2]; 相应地,在二维数组array2中,也有三个元素,即array2[0],array2[1],array2[2],所不同的是这三个元素又都是一维数组,它们也有相应的地址&array2[0],&array2[1],&array2[2],这就是行地址,由于array2[0],array2[1],array2[2]又都是一维数组(不妨想象为a0,a1,a2),它们又各有自己的元素,以array2[0]为例,它的元素是array2[0][0],array2[0][1],array2[0][2],它们的地址是&array2[0][0],&array2[0][1],&array2[0][2],称为列地址。 现在我们已经从概念上对行、列地址进行了区分,一个更明显的区别还在于在进行指针(地址)运算时的不同,仍以上面的例子为准,&array2[0]与&array2[0][0]的值是相同的,但前者是行地址,相当于一个指向一维数组的指针,&array2[0]+1即&array2[0]所指内存单元往下再移动一个一维数组,在此,是4个整型单元,而&array2[0][0]是列地址,相当于一个指向整型的指针,&array2[0][0]+1即指&array2[0][0]往下移动1个整型单元长度。 问题42:如何看待“空函数”? 所谓“空函数”即函数体为空的函数,也就是函数体只是由一对花括号构成的函数。可能你会想既如此,空函数没有任何可执行的实体,又何必要定义呢?确实,从这个意义上讲空函数没有存在的必要,但“存在的就是合理的”,正因为它的函数体是空的,所以也就不可能在函数体出现任何错误,也因此,空函数可以在程序调试过程中广泛使用,尤其是多模块程序的调试。我们在调试一个函数时,可以先用空函数暂时代替待检函数所调用的函数,在确定该函数无误以后,我们在把空函数用实际函数代替,如有错,则很容易获知错误是来自刚加入的这个函数。 注意的是,不能把空函数与无参函数混为一谈。 问题43:什么递归?为什么要使用递归? 递归调用就是函数直接或间接调用自身的调用方式称为递归调用它包括“回推”和“递归”两个过程。直接调用自身的即称为直接递归调用,间接调用自身的即称为间接递归调用,你应该首先掌握直接递归调用。 递归调用是设计和描述算法的一种有力工具,在解决复杂问题时,经常采用。理论上讲,任何递归算法都有其非递归算法,但实际运用中,能够写出相应的非递归算法并加以实现的递归算法是很少的,所以递归仍是解决复杂问题的常用方法之一。 另外,同一个问题用递归方法实现比用相应的非递归方法实现,更清晰,接口更。 问题44:如何正确使用递归调用? 能解决实际问题的递归算法都是由两部分构成,即递归表达式和递归出口,其中任何一个方面缺少问题都无法正确解。能采用递归描述的算法通常有这样的特征:为求解规模为N的问题,设法将它分解成一些规模较小的问题,然后从这些小问题的解方便地构造出大问题的解,并且这些规模较小的问题也采用同样的分解和综合方法,分解成规模更小的问题,并从这些更小问题构造出规模稍大问题的解,最后当N=1(或其它确定值),时能直接得到问题的解(即递归出口)。 根据笔者的经验,在用递归解决问题时,切忌过深的去了解递归过程各变量的值,这样对于问题的求解是没有任何好处的,要记住本就是因为问题过于复杂,我们才采用递归方法,如果再过深了解其实现细节就失去了这个根本的宗旨了。 需要指出的是,同一个问题用递归实现的效率不如用非递归方法实现的高,所以对简单问题应该尽可能不使用递归方法实现。 问题45:定义时用static修饰的外部变量是何含义?又有什么作用? 在函数外部定义的变量称为外部变量。在定义外部变量的时候加static修饰符的含义是使得该外部变量只能供本程序文件内部各函数使用,对于程序的其他程序文件是不可见的(千万别以为是使该外部变量存储在静态存储区,即为静态变量,记住所有外部变量都是静态变量)。这样做的好处是,为了减少各个程序文件之间的相互干扰,即出现同名的外部变量,尤其是在一个程序由多个程序员开发的情况下,加static的外部变量可以使各程序文件出现同名的外部变量,程序员可以完全按自己的意愿定义变量,而把要大家都可见的变量专门定义在一个文件中,然后在各程序文件加以说明。 问题46:什么是变量的作用域?如何安排变量的作用域? 变量的作用域指的是当修改一个变量的值时,这种修改所影响到的空间范围,根据变量的作用域可以把变量分为全局变量和局部变量。全局变量即在整个程序范围内都可见的变量,而局部变量只能在某个程序的一倍分范围内可见,如一个程序文件,一个函数,甚至一个分程序内部。在编程的时候应该尽可能地少使用全局变量,尽量不用全局变量在函数之间传递数据,这样可以减少全局变量的负作用,而在定义局部变量时,应该弄清楚它具体只在那个函数内有效,为容易区分,最好不要定义同名变量,除非是出于一种特殊目的。 问题47:什么是变量的存储属性?如何正确使用变量的存储属性? 所谓变量的存储属性即指变量所在内存区域的属性,主要反映在变量存在的时间上不同,可以分为静态存储方式和动态存储方式,前者存放在静态存储区,而后者存放在动态存储区。静态存储区中的数据在程序整个运行期间都有效,而动态存储区中的数据只在本函数执行时有效。外部变量都存储在静态存储区中,函数内部或分程序内部定义的变量,默认的情况下存储在动态存储区,但可以通过加static修饰符指定它存储在静态存储区,函数的形参也存储在动态存储区。定义变量除非实际需要,应该指定变量为静态变量。 问题48:什么是变量的指针和指针变量?两者有什么区别? 所谓变量的指针即指变量在内存所占的存储单元的起始地址,而指针变量是指专门存放某类对象的地址(指针)的变量。如:int a,*pa; &a就是变量a的指针,即变量a的地址,而pa是一个指针变量,用它可以指向整型数据的变量,即存放一整型变量的地址(指针),如pa=&a,此时pa指向变量a。两者的区别光从概念上区分是容易的,但实际使用时要区分并不容易,需要注意的是,并不一定要定义一个指针变量才可以使用变量的指针,而指针变量也只有指向一具体的变量时,它才真正有意义,它提供一种间接访问变量的一种方式。 问题49:如何用指针变量作为函数参数带回多个“返回值”? 用指针作为函数的参数可以在函数调用时使形参指针变量指向实参指针,此时如在函数修改形参指针变量所指向的存储单元内容相当于间接引用实参指针所指向的变量的值,从某种意义上说,似乎把在被调用函数中对变量的修改返回到了主调函数,我们可以利用这种方法在被调用函数中往主调函数带回多个“返回值”(因一个函数真正最多只能有一个返回值,所以在此加引号)。如需要带回一个值,就设置一个指针变量作为函数的形参。 问题50:为什么可以把一个字符串赋值给一个字符指针变量?但不能赋值给一个字符数组名? C语言不提供字符串数据类型,对于所有字符串都是将其视为数组存储一连续的存储单元中,将一个字符串赋值给一个字符指针变量的实质是把该字符串在内存中所占的连续存储单元的起始地址(字符串的指针),赋予字符指针变量,即让字符指针变量指向该字符串。而字符数组名是一个常量,不能作为左子值,所以不能赋值。 例:char str[10],*ptr;ptr=“China”的赋值是正确的,而str= “China”是不允许的。 问题51:用scanf()和gets()函数输入字符串各有何利弊? 用scanf()函数输入数据时,默认分隔符是空格、跳格(Tab)等,也因此scanf()函数不能输入含有上述字符的字符串,这是其不足之处;与gets()相比,其优点是它可以一次输入多个字符串,而且还可以用于输入不同类型的数据,;应用面较广。 与scanf()函数相反,用gets()函数输入时,可以输入含空格、跳格等字符的字符串,但其不足之处在于,它只能用于输入字符串,且一次只能输入一个。 问题52:什么是函数的指针?如何用指向函数的指针变量的方式来调用相应的函数? 所谓函数的指针指的是函数的入口地址,可以认为是函数第一个可执行性指令所在存储单元的地址,在C语言中用函数名与标识函数的入口地址,即函数的指针,这一点与数组有相似之处,但与数组不同的,在数组中,可以考虑数组元素的地址,而在函数中,除了考虑函数的入口地址以外,不考虑函数中某具体指令或数据所在存储单元的地址。 通过指向函数的指针变量也可以间接调用相应的函数,需要注意的是,让一个指向函数的指针变量指向某一函数时,只需将函数的函数名赋予该指针变量,而一定不能带上相应的函数参数,也不需要带括号,例如:有一函数,其函数原型为float fun(float x);且有一指向函数的指针变量float (*fun_p)();则应该是fun_ p=fun;而不应该是p=fun(6.0);显然后赋值号右边就是一次函数调用,得到的应该是当参数值为6.0时的函数值,所以这种赋值是错误码的。 问题53:用指
/
本文档为【学习C语言100问】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。 本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。 网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。

历史搜索

    清空历史搜索