《你必须知道的495个C语言问题》知识笔记及补充

1. extern在函数声明中是什么意思?

它能够用作一种格式上的提示表明函数的定义可能在还有一个源文件里。但在

extern int f(); 和 int f(); 之间并没有实质的差别。


补充:extern能够置于变量或者函数前,以标示变量或者函数的定义在别的文件里提示编译器遇到此变量和函数时在其他模块中寻找其定义。此外extern也可用来进行链接指定。也就是说extern有两个作用:

第 一,当它与"C"一起连用时,如: extern "C" void fun(int a, int b);则告诉编译器在编译fun这个函数名时按着C的规则去翻译对应的函数名而不是C++的,C++的规则在翻译这个函数名时会把fun这个名字变得面目全非,可能是fun@aBc_int_int#%$也可能是别的。这要看编译器的"脾气"了(不同的编译器採用的方法不一样),为什么这么做呢,由于 C++支持函数的重载。
第二。当extern不与"C"在一起修饰变量 或函数时,如在头文件里: extern int g_Int; 它的作用就是声明函数或全局变量的作用范围的keyword,其声明的函数和变量能够在本模块活其他模块中使用,记住它是一个声明不是定义!也就是 说B模块(编译单元)要是引用模块(编译单元)A中定义的全局变量或函数时,它仅仅要包括A模块的头文件就可以,在编译阶段。模块B虽然找不到该函数或变量, 但它不会报错。它会在连接时从模块A生成的目标代码中找到此函数。


2. 如何建立和理解非常复杂的声明?比如定义一个包括N个指向返回指向字符的指针的函数的指针的数组?

这个问题至少有以下3种答案:

(1)char *(*(*a[N])())();

(2)用typedef逐步完毕声明:

typedef char *pc;        /* 字符指针 */
typedef pc fpc();        /* 返回字符指针的函数 */
typedef fpc *pfpc;       /* 上面函数的指针 */
typedef pfpc fpfpc();    /* 返回函数指针的函数 */
typedef fpfpc *pfpfpc;   /* 上面函数的指针 */
pfpfpc a[N];             /* 上面指针的数组 */

(3)使用cdecl程序,它能够把英文翻译成C或者把C翻译成英文:


通过类型转换。cdecl也能够用于解释复杂的声明,指出參数应该进入哪一对括号(如同在上述的复杂函数定义中)。

补充:cdecl程序非常实用,它能够在C语言的声明和英语之间进行转换。

它能够解释一个现存的C语言声明,cdecl 程序能够帮助你分析复杂的声明。

一个在线的cdecl.


3. 为什么struct x { ... }; x thestruct; 不正确?

C不是C++。

结构标签不能自己主动生成类型。

补充:也就是在C++中这样是对的。

在C中不想用struct仅仅能借助于typedef。


4. NULL能够确保是0。但空指针(null)却不一定。


5. 数组和指针的差别是什么?

数组自己主动分配空间。可是不能重分配或改变大小。指针必须明白赋值以指向分配的空间(可能使用malloc),可是能够任意又一次赋值(即指向不同的对象),同一时候除了表示一个内存块的基址之外,还有很多其他的用途。

由于数组和指针所谓的等价性,数组和指针经常看起来能够互换,而其实指向malloc分配的内存块的指针通常被看作一个真正的数组(也能够用[]引用)。

可是。要小心sizeof。

6. 当我向一个接受指针的指针的函数传入二维数组的时候,编译器报错了。

数组蜕化为指针的规则不能递归应用。数组的数组(即二维数组)蜕化为数组的指针。而不是指针的指针。数组指针经常令人困惑,须要小心对待:

假设你向函数传递二维数组:
int array[NROWS][NCOLUMNS];
f(array);
那么函数的声明必须匹配:
void f(int a[][NCOLUMNS]){ ... }
或者
void f(int (*ap)[NCOLUMNS]){ ... }  /* ap是个数组指针 */

在第一个声明中,编译器进行了通常的从“数组的数组”到“数组的指针”的隐式转换;另外一种形式中的指针定义显而易见。

由于被调函数并不为数组分配地址,所以它并不须要知道总的大小,所以行数NROWS能够省略。但数组的宽度依旧重要。所以列维度NCOLUMNS(对于三维或多维数组,相关的维度)必须保留。

假设一个函数已经定义为接受指针的指针。那么差点儿能够肯定直接向它传入二维数组毫无意义。

7. 为什么这段代码不行?

char *str;
gets(str);
printf("%s\n", str);

str没有指向不论什么合法的位置,换言之。我们不知道指针str指向何处。由于局部变量没有初始化,通常包括垃圾信息,所以甚至都不能保证str是一个合法的指针。
改正方法:用局部变量或用malloc()分配str缓冲区。

8. 我刚才试了这种代码char *p; strcpy(p, "abc"); 而它执行正常?怎么回事?为什么它没有崩溃?

未初始化的指针p所指向的随机地址恰好对你来说是可写的。并且显然也没实用于什么重要的作用。
char *p; 编译器仅仅分配了足够容纳指针本身的内存; 也就是说。这种情况下,你分配了sizeo(char*)个字节的内存。

但你还没有分配不论什么让指针指向的内存,因此此时p所指向的内存可能是垃圾信息也可能是可写的区域。没有崩溃说明是后者。



9. 以下关于itoa的实现为什么是错误的?如何改正?

char *itoa(int n)
{
     char retbuf[20];   // error
     sprintf(retbuf, "%d", n);
     return retbuf;     // error
}
这样在编译的时候会出现这种警告信息“warning C4172: returning address of local variable or temporary”,说明retbuf的地址是暂时变量,是暂时的,函数返回时就没有了也就是不能直接返回。

一种解决方式是把返回缓冲区声明为静态变量:
static char retbuf[20];

总结:若要返回字符串或其他集合,则返回指针必须是静态分配的缓冲区。或者调用者传入的缓冲区。或者用malloc()获得的内存。但不能是局部(自己主动)数组。

10. 为什么在调用free()之后指针没有变空?使用(赋值,比較)释放之后的指针有多么不安全?

当你调用free()的时候,传入指针指向的内存被释放,但调用函数的指针值可能保持不变,由于C的按值传參语义意味着被调函数永远不会改变參数的值。严格的说。被释放的指针值是无效的。对它的不论什么使用,即使没有解參照,也可能带来问题,虽然作为一种实现质量的表现,多数实现都不会对无伤大雅的无效指针使用产生例外。

补充:
free()过后的指针也叫做“野指针”,建议free()过后马上将指针置为NULL,具体原因请看“
为什么free(re)过后re不为NULL呢”。



11. calloc()和malloc()有什么差别?利用calloc的零填充功能安全吗?free()能够释放calloc()分配的内存吗,还是须要一个cfree()?

calloc(m, n)本质上等价于
p = malloc(m * n);
memset(p, 0, m * n);
填充的全是零。因此不能确保生成实用的空指针值或浮点零值,free()能够安全的用来释放calloc()分配的内存。



12. 在C中,sizeof('a')=4而不是1。由于C语言中的字符常数是int型,因此sizeof('a')是sizeof(int),这是与C++不同的地方。


13. C语言中没有提高标准的布尔类型,须要用#define或枚举常数定义true/false。


14. 我试图用ANSI“字符串化”预处理操作符#向信息中插入符号常量的值。但它字符串化的总是宏的名字而不是它的值。

你能够用以下这种两步方法迫使宏既字符串化又扩展:
#define Str(x) #x
#define Xstr(x) Str(x)
#define OP plus
char *opname = Xstr(OP);

这段代码把opname置为“plus”而不是“OP”。

总结:在使用符号粘接操作符##连接两个宏的值(而不是名字)时也要採用相同的“迂回战术”。



15. memcpy()和memmove()有什么差别?

假设源和目的參数有重叠,memmove()提供有保证的行为。
而memcpy()则不能提供这种保证,因此能够实现的更加有效率。
假设有疑问,最好使用memmvoe()。

16. 我如何在printf的格式串中输出一个'%'?我试过"\%"。可是不行。

仅仅须要反复百分号:%%。

\%不行,由于\是编译器的转义字符,而这里我们的问题终于是printf的转义字符。

17. 为什么大家都说不要使用gets()?

跟fgets()不同,gets()不能被告知输入缓冲区的大小。因此不能避免缓冲区的溢出。

标准库的fgets()函数对gets()作了非常大的改进,虽然它仍不完好。假设真的可能输入非常长的行,还是须要细致思考,正确处理。

18. 一个粗陋但通常有效的測试NaN的方法:

#define isnan(x)    ((x) != (x))
C99提高isnan(), fpclassify()及其他一些类别的函数。

标准C函数库的源码:
GNUproject有一个全然实现的C函数库(http://www.gnu.org/software/libc/

19. 如何推断机器的字节顺序是高字节在前还是低字节在前?

有个使用指针的方法:
int x = 1;
if(*(char *)&x == 1)
    printf("little-endian\n");
else
    printf("big-endian\n");
另外一个可能是用联合。

20. 由一个日期,如何知道是星期几?

用mktime()或localtime()(注:假设tm_hour的值为0。要注意DST(夏时制)的调整);或者Zeller的congruence;或者这个由Tomohiko Sakamoto提供的优雅的代码:
int dayofweek(int y, int m, int d)  /* 0 = Sunday */
{
	static int t[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
	y -= m < 3;
	return (y + y/4 - y/100 + y/400 + t[m - 1] + d) % 7;
}

http://blog.csdn.net/lanxuezaipiao/article/details/18953917


posted @ 2016-02-28 13:15  zfyouxi  阅读(232)  评论(0编辑  收藏  举报