C语言FAQ.docx
- 文档编号:3565366
- 上传时间:2022-11-23
- 格式:DOCX
- 页数:19
- 大小:33.30KB
C语言FAQ.docx
《C语言FAQ.docx》由会员分享,可在线阅读,更多相关《C语言FAQ.docx(19页珍藏版)》请在冰豆网上搜索。
C语言FAQ
C语言FAQ
一、基础知识
1.Q:
编程是什么
A:
编程就是编制程序。
程序是让计算机发挥功能的命令的集合。
程序有两种形式,让计算机真正执行的是电脉冲形式,叫机器码,程序员编制的通常是文本形式,叫源代码。
使用一个称为编译器的工具,可以把源代码转变为机器码。
而编程就是产生那些源代码的工作。
2.Q:
我第一次把一个程序分成多个源文件,我不知道该把什么放到 .c 文件,把什么放到 .h 文件。
(``.h" 到底是什么意思?
)
A:
作为一般规则,你应该把这些东西放入头 (.h) 文件中:
当声明或定义需要在多个文件中共享时,尤其需要把它们放入头文件中。
特别是,永远不要把外部函数原型放到 .c 文件中。
另一方面,如果定义或声明为一个 .c 文件私有,则最好留在 .c 文件中。
3.Q:
一个头文件可以包含另一头文件吗?
A:
这是个风格问题,因此有不少的争论。
很多人认为 ``嵌套包含文件" 应该避免:
盛名远播的 ``印第安山风格指南"(IndianHillStyleGuide,参见问题 17.7) 对此嗤之以鼻;它让相关定义更难找到;如果一个文件被包含了两次,它会导致重复定义错误;同时他会令 makefile 的人工维护十分困难。
另一方面,它使模块化使用头文件成为一种可能 (一个头文件可以包含它所需要的一切,而不是让每个源文件都包含需要的头文件).
4.Q:
#include<> 和 #include有什么区别?
A:
<> 语法通常用于标准或系统提供的头文件,而 通常用于程序自己的头文件。
5.Q:
我在文件的第一个声明就遇到奇怪的语法错误,但是看上去没什么问题。
A:
可能你包含的最后一个头文件的最后一行缺一个分号。
6.Q:
我如何决定使用那种整数类型?
A:
如果需要大数值 (大于 32,767 或小于 -32,767),使用 long 型。
否则,如果空间很重要 (如有大数组或很多结构),使用 short 型。
除此之外,就使用 int 型。
如果严格定义的溢出特征很重要而负值无关紧要,或者你希望在操作二进制位和字节时避免符号扩展的问题,请使用对应的无符号类型。
但是,要注意在表达式中混用有符号和无符号值的情况。
尽管字符类型 (尤其是无符号字符型) 可以当成 ``小'' 整型使用,但由于不可预知的符号扩展和代码增大有时这样做可能得不偿失。
7.Q:
为什么这样的代码:
a[i]=i++; 不能工作?
A:
子表达式 i++ 有一个副作用 --- 它会改变 i 的值 --- 由于 i 在同一表达式的其它地方被引用,这会导致无定义的结果,无从判断该引用(左边的 a[i] 中)是旧值还是新值。
(注意,尽管在 K&R 中建议这类表达式的行为不确定,但 C 标准却强烈声明它是无定义的。
8.Q:
++i 和 i++ 有什么区别?
A:
如果你的 C 语言书没有说明它们的区别,那么买一本好的。
简单而言:
++i 在 i 存储的值上增加一并向使用它的表达式 ``返回" 新的,增加后的值;而 i++ 对 i 增加一,但返回原来的是未增加的值。
9.Q:
如果我不使用表达式的值,我应该用 ++i 或 i++ 来自增一个变量吗?
A:
由于这两种格式区别仅在于生成的值,所以在仅使用它们的副作用时,二者完全一样。
但是,在 C++ 中,前缀方式却是首选。
10.Q:
对于代码 inti=3;i=i++; 不同编译器给出不同的结果,有的为 3,有的为 4,哪个是正确的?
A:
没有正确答案;这个表达式无定义。
参见问题 3.1,3.7 和 11.32。
同时注意, i++ 和 ++i 都不同于 i+1。
如果你要使 i 自增 1,使用 i=i+1, i+=1, i++ 或 ++i,而不是任何组合。
11.Q:
我需要根据条件把一个复杂的表达式赋值给两个变量中的一个。
可以用下边这样的代码吗?
((condition)?
a:
b)=complicated_expression;
A:
不能。
?
:
操作符,跟多数操作符一样,生成一个值,而不能被赋值。
换言之,?
:
不能生成一个 ``左值"。
如果你真的需要,你可以试试下面这样的代码:
*((condition)?
&a:
&b)=complicated_expression;
12.Q:
因为在 C 语言中所有的非零值都被看作 ``真",是不是把 TRUE 定义为 1 很危险?
如果某个内置的函数或关系操作符 ``返回" 不是 1 的其它值怎么办?
A:
C 语言中的确任何非零值都都被看作真,但这仅限于 ``输入",也就是说,仅限于需要布尔值的地方。
内建操作符生成布尔值时,可以保证为 1 或 0。
因此,这样的测试
if((a==b)==TRUE)
能如愿运行 (只要 TRUE 为 1),但显然这很傻。
事实上,跟 TRUE 和 FALSE 的跟 TRUE 和 FALSE 的显示比较都不合适,因为有些库函数 (如 isupper(),isalpha() 等) 在成功时返回非零值,但不一定为1。
(再说,如果你认为 ``if((a==b)==TRUE)" 比 ``if(a==b)" 好,为什么就此打住呢?
为什么不使用 ``if(((a==b)==TRUE)==TRUE)" 呢?
) 一般规则是只在向布尔变量赋值或函数参数中才使用 TRUE 和 FALSE (或类似的东西),或者用于函数的返回值,但决不用于比较。
13.Q:
什么是局部程序块(localblock)?
A:
局部程序块是指一对大括号({})之间的一段C语言程序。
一个C函数包含一对大括号,这对大括号之间的所有内容都包含在一个局部程序块中。
if语句和swich语句也可以包含一对大括号,每对大括号之间的代码也属于一个局部程序块。
此外,你完全可以创建你自己的局部程序块,而不使用C函数或基本的C语句。
你可以在局部程序块中说明一些变量,这种变量被称为局部变量,它们只能在局部程序块的开始部分说明,并且只在说明它的局部程序块中有效。
如果局部变量与局部程序块以外的变量重名,则前者优先于后者。
14.Q:
可以把变量保存在局部程序块中吗?
A:
用局部程序块来保存变量是不常见的,你应该尽量避免这样做,但也有极少数的例外。
例如,为了调试程序,你可能要说明一个全局变量的局部实例,以便在相应的函数体内部进行测试。
为了使程序的某一部分变得更易读,你也可能要使用局部程序块,例如,在接近变量被使用的地方说明一个变量有时就会使程序变得更易读。
然而,编写得较好的程序通常不采用这种方式来说明变量,你应该尽量避免使用局部程序块来保存变量。
15.Q:
什么时候用一条switch语句比用多条if语句更好?
A:
如果你有两个以上基于同一个数字(numeric)型变量的条件表达式,那么最好使用一条switch语句。
注意,使用switch语句的前提是条件表达式必须基于同一个数字型变量。
例如,尽管下述if语句包含两个以上的条件,但该例不能使用switch语句,因为该例基于字符串比较,而不是数字比较
16.Q:
switch语句必须包含default分支吗?
A:
不,但是为了进行错误检查或逻辑检查,还是应该在switch语句中加入default分支。
17.Q:
switch语句的最后一个分支可以不要break语句吗?
A:
尽管switch语句的最后一个分支不一定需要break语句,但最好还是在switch语句的每个分支后面加上break语句,包括最后一个分支。
这样做的主要原因是:
你的程序很可能要让另一个人来维护,他可能要增加一些新的分支,但没有注意到最后一个分支没有break语句,结果使原来的最后一个分支受到其后新增分支的干扰而失效。
在每个分支后面加上break语句将防止发生这种错误并增强程序的安全性。
此外,目前大多数优化编译程序都会忽略最后一条break语句,所以加入这条语句不会影响程序的性能。
18.Q:
除了在for语句中之外,在哪些情况下还要使用逗号运算符?
A:
逗号运算符通常用来分隔变量说明、函数参数、表达式以及for语句中的元素。
19.Q:
怎样才能知道循环是否提前结束了?
A:
循环通常依赖于一个或多个变量,你A:
可以在循环外检查这些变量,以确保循环被正确执行。
20.Q:
什么是左值(lvaule)?
A:
左值是指可以被赋值的表达式。
左值位于赋值语句的左侧,与其相对的右值(rvaule,)则位于赋值语句的右侧。
每条赋值语句都必须有一个左值和一个右值。
左值必须是内存中一个可存储的变量,而不能是一个常量。
下面给出了一些左值的例子:
intx;int*p_int;x=1;p_int=5;变量x是一个整数,它对应于内存中的一个可存储位置,因此,在语句“x=1”中,x就是一个左值。
注意,在第二个赋值语句“*p_int=5"中,通过“*”修饰符访问p_int所指向的内存区域;因此,p_int是一个左值。
相反,下面的几个例子就不是左值:
#defineCONST_VAL10intx/*example1*/l=x;/*example2*/CONST_VAL=5;在上述两条语句中,语句的左侧都是一个常量,其值不能改变,因为常量不表示内存中可存储的位置。
因此,这两条赋值语句中没有左值,编译程序会指出它们是错误的。
21.Q:
数组(array)可以是左值吗?
左值被定义为可被赋值的表达式。
那么,数组是可被赋值的表达式吗?
A:
不是,因为数组是由若干独立的数组元素组成的,这些元素不能作为一个整体被赋值。
下述语句是非法的:
intx[5],y[5];x=y;不过,你可以通过for循环来遍历数组中的每个元素,并分别对它们赋值,例如:
inti;intx[5];inty[5];......for(i=0;i<5,i++)x=y;
二、数组
1.Q:
我在一个源文件中定义了“chara[6]“,在另一个中声明了”externchar*a“。
为什么不行?
A:
你在一个源文件中定义了一个字符串,而在另一个文件中定义了指向字符的指针。
externchar* 的申明不能和真正的定义匹配。
类型 T 的指针和类型 T 的数组并非同种类型。
请使用 externchara[]。
2.Q:
那么为什么作为函数形参的数组和指针申明可以互换呢?
A:
这是一种便利。
由于数组会马上蜕变为指针,数组事实上从来没有传入过函数。
允许指针参数声明为数组只不过是为让它看起来好像传入了数组,因为该参数可能在函数内当作数组使用。
特别地,任何声明``看起来象"数组的参数,例如
voidf(chara[])
{...}
在编译器里都被当作指针来处理,因为在传入数组的时候,那正是函数接收到的.
voidf(char*a)
{...}
这种转换仅限于函数形参的声明,别的地方并不适用。
如果这种转换令你困惑,请避免它;很多程序员得出结论,让形参声明``看上去象"调用或函数内的用法所带来的困惑远远大于它所提供的方便。
3.Q:
如果你不能给它赋值,那么数组如何能成为左值呢?
A:
ANSIC标准定义了``可变左值",而数组不是。
4.Q:
现实地讲,数组和指针地区别是什么?
A:
数组自动分配空间,但是不能重分配或改变大小。
指针必须明确赋值以指向分配的空间(可能使用malloc),但是可以随意重新赋值(即,指向不同的对象),同时除了表示一个内存块的基址之外,还有许多其它的用途。
5.Q:
有人跟我讲,数组不过是常指针。
A:
这有些过度单纯化了。
数组名之所以为``常数"是因为它不能被赋值,但是数组不是指针
6.Q:
我如何声明一个数组指针?
A:
通常,你不需要。
当人们随便提到数组指针的时候,他们通常想的是指向它的第一个元素的指针。
考虑使用指向数组某个元素的指针,而不是数组的指针。
类型T的数组蜕变成类型T的指针(参见问题6.3),这很方便;在结果的指针上使用下标或增量就可以访问数组中单独的成员。
而真正的数组指针,在使用下标或增量时,会跳过整个数组,通常只在操作数组的数组时有用---如果还有一点用的话。
7.Q:
当数组是函数的参数时,为什么 sizeof 不能正确报告数组的大小?
A:
编译器把数组参数当作指针对待,因而报告的是指针的大小。
8.Q:
C语言中数组可以直接赋值给数组嘛?
A:
inta[]={1,2,3},b[3];b=a;这样赋值?
?
这样在C中是不行的,a[]={1,2,3},b[],它们都是数组,数组本身代表地址,还有函数本身也代表地址,同时也代表一级指针常量,在主函数里不能那样赋,但在子函数中是可以的,意义就是把a的地址给b.
9.Q:
在C语言里有字符数组的说法,我想问的是那有没有字符串数组这个说法呢?
A:
答:
c语言因为没有字符串类型(c++里有),所以c语言处理字符串的时候,需要把字符串中的字符存到一个数组里头,借助数组这个工具来对字符串进行一系列的处理。
那么怎末标识一个字符串呢,记住,字符串必须是以空字符结尾的。
空字符是'\0'。
有的时候也可以用ch==null?
来判断一个字符是不是空字符。
为什么要用空字符结尾?
因为一些处理字符串的工具函数要依赖这个结尾才来正确识别字符串,才能为进一步的处理做准备。
这些字符串处理函数就是你列出的:
strcat函数(字符串连接函数),strcpy函数(字符串复制函数)。
具体他们是什么作用,我想你去看书本会得到易懂而且更详细的答案。
10.Q:
问:
字符'\0'和字符‘’有什么区别啊?
(第二个字符是个空格字符)
A:
首先他们都是一个字符,区别是:
一个是空字符,一个是空格字符,它们在ascii码是不一样的,是两个完全不同的字符。
空字符是空白字符的一种,属于特殊字符。
enter,tab都属于空白字符。
11.Q:
C语言数组问题,这样是什么意思?
已知定义:
uint8sec[512];
程序中却有写:
memset(sec+256,0xaa,256);
请问sec+256是指什么了?
A:
A:
sec+256表示从数组sec的第256个元素的地址,数值上和&sec[256]相等.
进一步问下:
如果我是想把sec[512]中的sec[256]~sec[511]全赋初值该怎么表示?
memset(sec+256,0xaa,256);
12.Q:
在定义一维数组的时候,假设定义如下的数组:
Inta[10];
书上说不能使用a[10],这个数组元素。
但在程序中使用了a[10]这个元素,编译不会出错,并且程序运行正常。
这两者之间该怎么解释,请大家帮忙!
!
!
!
A:
这次不出错未必代表下次不出错
这个平台不出错未必代表换一个平台不出错
这是个几率问题
给你个例子
#include
intmain()
{
inti=100,a[10],j=100,k,l;
for(k=0;k
for(l=0;l<10;l++)
a[10+l]=k+2;
printf("k=%d\t%d\t%d\n",k,i,j);
}
if(getchar())
return0;
return0;
}
错误的原因是:
a[10]本来不是a的地方,因此编译时编译器完全可以把其他东西放在那个地方.如果碰巧编译器没有把其他东西放在那个地方,那你很幸运,你的程序不会出问题,但是如果编译器把那个地方分配给其他变量了,那你很倒霉,很可能你的程序要遭殃.
你要知道,有时一点点疏忽就会导致几百万的损失!
三、指针
1.Q:
什么是指针
A:
指针是一种数据类型,与其它的数据类型不同的是指针是一种“用来存放地址值的”变量。
举一个简单的例子:
如果定义了一个整型变量,根据整型变量的特点,它可以存放的数是整数。
如:
inta;a=100;这样就把整型常量赋给了变量a。
但是如果写成这样:
a=123.33;就会出问题,最后输出变量a的值结果是123。
现在说到指针,其实地址值也是一个整型数,如某某变量的地址值为36542,说明这个变量被分配在内存地址值为36542的地方。
能不能这样进行推理,既然地址值也是整型数,整型变量正好可以用来存放整型数,那不是一个整型变量可以用来存放地址的值吗。
程序写成下面这样:
inta,b;
a=&b;
很明显,这样写是错误的。
原因在于不能简单地把地址理解为整型数。
应有这样的对应关系:
地址值<--->指针; 整型数<--->int 型变量。
所以有这样的说法:
“指针就是地址”(指针就是存放地址值的一种数据类型)
下面是一段正确的程序:
inta,*p;
p=&a;/*把变量a的地址值赋给指针p*/
2、A:
什么是void指针
Q:
void的意思就是“无值”或“无类型”。
void指针一般称为“通用指针”或“泛指针”。
之所以有这样的名字是因为使用void指针可以很容易地把void指针转换成其它数据类型的指针。
例如在为一个指针分配内存空间的时候:
int*p;
p=(int*)malloc(......);本来函数malloc的返回值是void类型,在这里通过在前面加上一个带括号的int*就把void*类型转换成了int*类型。
所以不能简单的把void看成“无”的意思。
void数据类型是一种很重要的数据类型。
3、A:
指针可以相加减吗
Q:
可以相互加减。
但是一定要作有意义的运算。
当二个指针指向同一个数组的时候,它们相加减是有意义的。
如果二个指针分别指向二个不同的数组,那么指针之间的相加减就没有什么意义。
指向同一个数组时,其相加减的结果为二个指针之间的元素数目。
4、A:
什么是NULL指针
Q:
NULL指针是不指向任何一个地址的指针。
这样的指针一般是允许的。
当一个指针为NULL的时候,不要对它进行存取。
5、A:
什么是“野”指针
Q:
野指针是不由程序员或操作者所能控制的指针。
当在一个程序里面定义了一个指针而又没有给这个指针一个具体地址指向的时候,这个指针会随意地指向一个地址,这样的指针就是一个野指针。
如果这个地址后面的内存空间没有什么重要的数据则不会造成不好的后果,但是一旦这里面存放了有用的数据,那么这些数据随时都有被野指针存取的危险,如果这样,数据就会被破坏,程序也会崩溃。
所以在程序里面是一定要禁止任何野指针的存在。
当定义了一个指针的时候,要马上给这个指针分配一个内存地址的指向。
这样程序才不会因为指针而出现意外。
6、A:
NULL的值是什么
Q:
NULL不是被定义为0就是被定义成(void*)0,这二种值基本上是一样的。
如有这样的语句:
if(p==NULL) 或者写成if(p==0)其作用是一样。
7、A:
什么是“内存泄漏”
Q:
当定义了一个指针的时候,立即要为这个指针分配一个内存空间。
这只防止了野指针的产生。
当一个指针使用完毕要立即释放掉这个指针所占用的内存空间---这有二方面的意义:
1)避免了内存空间的泿费; 2)防止了内存泄漏。
为什么会产生内存泄漏:
如果没有及时释放掉指针所占用的内存空间,而在下次使用这个指针时又给这个指针分配了内存空间,这样的次数一多,内存空间就慢慢被消耗掉了。
所以形象地称这种现象为内存泄漏。
如下面这样一个程序:
void*p;
for(;;)
p=malloc(20);/*这20个字节的内存空间是随意指定的*/
这样的一个小程序,大家不要随便运行它。
你可以在集成环境中单步调试运行,可以看一下每步运行后的结果。
可以看到,每一次循环都会“吃掉”20个字节的内存,无数次之后,再多的内存也慢慢地“泄漏”,最后没有内存可用就死机。
(与这个程序配合需要一段检测整机总的内存容量的程序,以观察内存总量的变化。
这里虽然没有这一段程序,但是看得到每次分配的内存地址值是不相同的)
8、A:
我想声明一个指针并为它分配一些空间,但却不行。
这些代码有什么问题?
char*p;*p=malloc(10);
Q:
你所声明的指针是 p,而不是 *p,当你操作指针本身时 (例如当你对其赋值,使之指向别处时),你只需要使用指针的名字即可:
p=malloc(10);
当你操作指针指向的内存时,你才需要使用 * 作为间接操作符:
*p='H';
9、A:
*p++自增p还是p所指向的变量?
Q:
后缀++和--操作符本质上比前缀一目操作的优先级高,因此*p++和*(p++)等价,它自增p并返回p自增之前所指向的值。
要自增p指向的值,使用(*p)++,如果副作用的顺序无关紧要也可以使用 ++*p。
10、A:
我有一个char*型指针正巧指向一些int型变量,我想跳过它们。
为什么如下的代码((int*)p)++;不行?
Q:
在C语言中,类型转换意味着把这些二进制位看作另一种类型,并作相应的对待";这是一个转换操作符,根据定义它只能生成一个右值(rvalue)。
而右值既不能赋值,也不能用++自增。
(如果编译器支持这样的扩展,那要么是一个错误,要么是有意作出的非标准扩展。
)要达到你的目的可以用:
p=(char*)((int*)p+1);
或者,因为p是char*型,直接用
p+=sizeof(int);
但是,在可能的情况下,你还是应该首先选择适当的指针类型,而不是一味地试图李代桃僵。
11、A:
我有个函数,它应该接受并初始化一个指针voidf(int*ip){staticintdummy=5;ip=&dummy;}但是当我如下调用时:
int*ip;f(ip);调用者的指针却没有任何变化。
Q:
你确定函数初始化的是你希望它初始化的东西吗?
请记住在C中,参数是通过值传递的。
被调函数仅仅修改了传入的指针副本。
你需要传入指针的地址 (函数变成接受指针的指针),或者让函数返回指针。
12、A:
我有一个函数externintf(int*);它接受指向int型的指针。
我怎样用引用方式传入一个常数?
下面这样的调用f(&5);似乎不行。
Q:
在C99中,你可以使用 ``复合常量":
f((int[]){5});
在C99之前,你不能直接这样做;你必须先定义一个临时变量,然后把它的地址传给函数:
intfive=5;
f(&five);
13、A:
我看到了用指针调用函数的不同语法形式。
到底怎么回事?
Q:
最初,一个函数指针必须用*操作符(和一对额外的括弧)转换为一个真正的函数才能调用:
intr,func(),(*fp)()=func;
r=(*fp)();
而函数总是通过指针进行调用的,所有真正的函数名总是隐式的退化为指针(在表达式中,正如在初始化时一样。
。
这个推论表明无论fp是函数名和函数的指针
r=fp();
ANSIC标准实际上接受后边的解释,这意味着*操作符不再需要,尽管依然允许。
14、A:
如果NULL和0作为空指针常数是等价的,那我到底该用哪一个呢?
Q:
许多程序员认为在所有的指针上下文中都应该使用NULL,以表明该值
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 语言 FAQ