《你必须知道的495个C语言问题》读书笔记之第1-2章:声明和初始化

1. C标准中并没有精确定义数值类型的大小,但作了以下约束:

(1) char类型可以存放小于等于127的值;

(2) short int和int可以存放小于等于32767的值;

(3) long int可以存放小于等于2147483647;

(4) char至少有8位,short int和int至少有16位,long int至少有32位,long long至少有64位。

可以在头文件<limits.h>中找到特定机器下上述类型的最大值和最小值。

2. Q:C语言为什么不精确定义标准类型的大小?

    A: C语言认为对象的具体大小应该由具体的实现来决定。多数程序不需要精确控制这些大小,那些试图达到这一目的的程序如果不这样做也许会更好。

3. Q:"char *p1,p2;"是什么意思?

    A: 在C语言中,声明的语法并非“类型 标识符”,而是"基本类型 声明符"。其中的“声明符”或者是一个简单标识符,或者是如同*p, a[10], f()这样的符号,表明被声明的变量是指向基本类型的指针、基本类型的数组或返回基本类型的函数。上述声明中基本类型是char,第一个声明符是"*p1",表明p1是指向char类型的指针;第二个声明符是"p2",表明p2是char型变量。如果想声明两个指针,应该写成"char *p1, *p2;"。(由于*是声明符的一部分,所以应该让*紧挨p1旁边)

4. Q:怎样声明和定义全局变量和函数最好?

    A:当希望在多个源文件中共享变量和函数时,需要确保定义和声明的一致性。最好的做法是在某个相关的.c文件中定义,然后在.h文件中进行外部声明,在需要使用时只要包含对应的头文件即可。定义变量的.c文件也应该包含该头文件,以便编译器检查定义和声明的一致性。注意,永远不要把外部函数的原型放到.c文件中,因为如果函数的定义发生改变,会很容易忘记修改原型,而错误的原型贻害无穷。

5. Q:extern在函数声明中是什么意思?

    A:存储类型extern只对数据声明有意义,对于函数声明,它可以用作提示函数的定义可能在另一个源文件,但"extern int f();"和"int f()"并无实质区别。

6. Q:auto有什么用?

    A:毫无用途,auto已经过时了。它是从C语言的无类型前身B语言中继承下来的。在B语言中,没有像int这样的关键字,声明必须包含存储类型。

7. Q:下面的声明中,为什么是p而不是它所指向的字符为const?

typedef char *charp;
const charp p;

    A:typedef的替换并不完全基于文本的。在声明"const charp p;"中,p被声明为const的原因跟"const int i;"将i声明为const的原因一样,编译器不会“深入”typedef的内容来发现涉及了指针。

8. Q:为什么不能在初始化和数组维度中使用const值?

const int n = 5;
int a[n];

    A:const限定词真正的含义是read-only,用它限定的对象通常是运行时不能被赋值的对象。因此用const限定的对象的值并不是真正的常量,不能用作数组维度、case行标或类似环境。在C++中则可以。

9. Q:"const char *p", "char const *p", "char *const p"有什么区别?

    A:从里往外看。前两个可以互换,它们都声明了一个指向字符常量的指针,第三个则声明了一个指向字符的指针常量。解读复杂C声明的一种方法是遵循“从内到外”的阅读顺序,并谨记[], ()的优先级比*高。

10. Q:如下所示,为什么在file2.c中sizeof取不到array的大小?

// file1.c
int array[] = {1,2,3};
// file2.c
extern int array[];

    A:未指定大小的extern数组是不完全类型,不能对它使用sizeof,因为sizeof在编译时发生作用,它不能获得定义在另一个文件中的数组的大小。

11. Q:main的正确定义是什么?void main正确吗?

     A: main的正确定义只有以下四种:

int main();
int main(void);
int main(int argc, char **argv);
int main(int argc, char *argv[]);

      void main不正确。将main函数声明为void不仅关掉或重新排列了警告信息,它可能还会导致跟调用者(C的运行时启动代码)的期望不同的函数调用/返回序列。就是说,如果返回void和返回int的函数调用序列不同,则启动代码会使用返回int的调用序列调用main函数。如果main被错误地声明为void,它可能不会运行。

12. Q:如何判断哪些标识符可用、哪些被保留了?

     A:首先我们必须理解标识符的3个属性:作用域、命名空间和连接类型。

(1) 作用域:4种,分别是函数、文件、块和原型(原型仅仅存在于函数原型声明的参数列表中)。

(2) 命名空间:4种,分别是行标(label, 即goto的目的地)、标签(tag, 结构、联合和枚举的名称)、结构或联合成员、其他普通标识符(函数、变量、类型定义名称和枚举常量)。

(3) 连接类型:3种,分别是外部连接(全局、非静态变量和函数)、内部连接(限于文件作用域内的静态函数和变量)、无连接(局部变量、typedef名称和枚举常量)。

     然后,我们来看ANSI关于命名空间的规则:

(1) 所有以下划线开头,后跟一个大写字母或另一个下划线的标识符永远保留。

(2) 所有以下划线开头的标识符作为文件作用域的普通标识符(函数、变量、类型定义和枚举变量)保留。

(3) 被包含的标准头文件中的宏名称的所有用法保留。

(4) 标准库中的所有具有外部连接属性的标识符(即函数名)永远保留用作外部连接标识符。

(5) 在标准头文件中定义的类型定义和标签名称,如果对应的头文件被包含,则在(同一个命名空间中的)文件作用域内保留。

13. Q:没有显式初始化的变量的初始值是什么?

     A:具有静态生存期的未初始化变量(即在函数外声明的变量及静态存储类型变量,包括数组和结构)可以确保初始值为零,具有自动生存期的变量(即非静态存储类型的局部变量)如果没有显式初始化,则包含的是垃圾内容,对垃圾内容不能做任何有用的假定。

14. Q:以下的初始化有什么区别?为什么向p[i]赋值时程序会崩溃?

char a[] = "string literal";
char *a = "string literal";

     A:字符串字面量(string literal)有两种稍有区别的用法:

(1) 用作数组初始值时,它指明该数组中字符的初始值;

(2) 其他情况下,它会转化为一个无名的静态字符数组,可能会存储在只读内存中,这就导致它不能被修改。在表达式环境下,数组通常被立即转化为一个指针,因此第二个声明把

p初始化成指向无名数组的第一个元素。

15. Q:下面两个声明有何区别?

struct x1 {...};
typedef struct {...} x2;

    A:第一种形式声明了一个“结构标签”,第2种形式声明了一个“类型定义”。前者在声明结构的实例时必须使用struct关键字,如"struct x1 a;",后者则不需要使用struct关键字,如"x2 b;"。但这个区别在C++编译器和某些模仿C++的C编译器中已经完全不存在了,在C++中结构标签在本质上都自动声明为类型定义。

16. Q:在C语言中是否有模拟继承等面向对象程序设计特性的好方法?

    A:把函数指针直接加入到结构中就可以实现简单的“方法”。你可以使用各种不雅而暴力的方法来实现继承,例如通过预处理器或让“基类”的结构作为初始的子集,但这些方法都不完美。另外,也没有操作符的重载和覆盖。

17. Q:为什么不能用內建的==和!=操作符比较结构?

    A:简单的按字节比较的方法可能会在遇到结构中没有使用的“洞”的随机内容的时候失败(这些补位是用来保证后续的成员正确对齐的)。而按域比较在处理大结构时可能需要难以接受的大量重复代码。任何编译器生成的比较代码都不能期望在所有情况下都正确比较指针域,例如比较char *域的时候一般都希望使用strcmp而不是==。如果需要比较两个结构,必须自己写函数按域比较。

18. Q:怎样从/向数据文件读/写结构?

    A:使用fwrite可以写一个结构:"fwrite(&somestruct, sizeof somestruct, 1, fp);"但这样用内存映像写出的数据文件却不能移植,尤其是当结构中包含浮点成员或指针的时候。结构的内存布局跟机器和编译器都有关。不同的编译器可能使用不同数量的填充位,不同机器上基本类型的大小和字节顺序也不尽相同。一致性更好的方案是写一对函数,用可移植的方式按域读写结构。

19. Q:为什么我的编译器在结构体中留下了“空洞”?能否关掉“空洞”?

    A:当内存中的值合理对齐时,很多机器都能非常高效地访问,而且某些机器甚至根本不能访问没有对齐的地址,因此必须要求所有的数据都正确地对齐。

20. Q:如何确定域在结构中的字节偏移量?

    A:在<stddef.h>中定义了offsetof()宏,用offsetof(structs, f)可以计算出域在结构s中的偏移量。(offsetof只能求结构体非位域成员的偏移量,不能求位域成员的偏移量)

21. Q:有办法初始化联合吗?

    A:C99引入了“指定初始式”,可以用来初始化任意成员。

原文地址:https://www.cnblogs.com/wuhualong/p/ReadingNote_C_Programming_FAQs_Chap1-2.html