必须弄懂的495个C语言问题

C语言常见问题集:声明和初始化,结构、联合和枚举,表达式,指针,空 (null) 指针,数组和指针,内存分配,字符和字符串,布尔表达式和变量,C 预处理器,ANSI/ISO 标准 C,标准输入输出库,库函数,浮点运算,可变参数,工具和资源等。 ### 必须弄懂的495个C语言问题 #### 1. 声明和初始化 **1.1 我如何决定使用那种整数类型?** 在选择整数类型时,通常需要考虑以下几个因素: - **范围需求**:需要存储的整数的大小范围。 - **内存效率**:不同的整数类型占用不同的内存空间。 - **性能需求**:某些类型的运算可能比其他类型更快。 常见的整数类型包括`short`, `int`, `long`, 和 `long long`。`short`一般占用2字节,`int`和`long`通常占用4字节(但在某些平台上可能不同),而`long long`则占用8字节。在C99标准中还引入了`int8_t`, `int16_t`, `int32_t`, 和 `int64_t`等类型,它们分别明确地表示8位、16位、32位和64位的整数。 **1.2 64位机上的64位类型是什么样的?** 在64位系统上,`long`类型通常被定义为64位。此外,C99标准提供了`int64_t`类型,该类型保证占用64位,可以在所有平台上保持一致的行为。因此,在编写跨平台代码时,建议使用`int64_t`而不是`long`来确保一致性。 **1.3 怎样定义和声明全局变量和函数最好?** 定义全局变量时,应考虑其作用域和生命周期。通常,全局变量应当尽量减少,因为它们可能导致代码难以维护和调试。声明全局变量时,应使用`extern`关键字来表明该变量是在另一个文件中定义的。例如: ```c extern int global_var; ``` 对于函数声明,最好提供原型(即指定返回类型和参数列表),以帮助编译器进行类型检查。例如: ```c int myFunction(int arg1, char arg2); ``` **1.4 extern在函数声明中是什么意思?** `extern`关键字用于声明变量或函数是在其他地方定义的。对于函数来说,`extern`通常出现在函数原型之前,但它实际上是可选的,因为默认情况下函数就是外部链接的。 **1.5 关键字auto到底有什么用途?** `auto`关键字用于声明变量,并告诉编译器该变量的生存期为自动(即在函数内部)。在现代C语言中,`auto`的使用已经很少见,因为它通常是默认行为。 **1.6 我似乎不能成功定义一个链表。我试过typedef struct {char *item; NODEPTR next;} *NODEPTR;但是编译器报了错误信息。难道在C语言中一个结构不能包含指向自己的指针吗?** 定义链表节点时,正确的做法是先定义结构体,然后再定义指向该结构体的指针类型。示例代码如下: ```c typedef struct Node { char *item; struct Node *next; } NODEPTR; ``` **1.7 怎样建立和理解非常复杂的声明?例如定义一个包含N个指向返回指向字符的指针的函数的指针的数组?** 复杂的声明可以通过逐步分解来理解。例如,声明`int (*ptr)[N]`表示`ptr`是一个指向含有`N`个整数的数组的指针。对于更复杂的声明,如`int (*ptr[N])()`,则表示`ptr`是一个包含`N`个指向无参函数的指针的数组。 **1.8 函数只定义了一次,调用了一次,但编译器提示非法重定义了。** 这通常是因为相同的函数在不同的源文件中被定义了多次。确保每个函数只在一个文件中定义,并且其他文件仅包含该函数的声明。 **1.9 main()的正确定义是什么?void main()正确吗?** `main`函数的正确形式应该是返回`int`类型。C标准规定`main`函数的原型为`int main(void)`或`int main(int argc, char *argv[])`。`void main()`不是标准的形式,虽然某些编译器支持这种写法,但它不应被视为最佳实践。 **1.10 对于没有初始化的变量的初始值可以作怎样的假定?如果一个全局变量初始值为“零”,它可否作为空指针或浮点零?** 未初始化的局部变量具有不确定的值。然而,未初始化的全局变量和静态局部变量会被自动初始化为零值。这意味着对于整型变量,其值为0;对于指针,其值为`NULL`;对于浮点数,其值为0.0。 **1.11 代码int f(){char a[]="Hello, world!";}不能编译。** 这是因为局部变量`a`被初始化为一个字符串字面量,而字符串字面量实际上是一个指向常量的指针。在局部变量的作用域内,字符串字面量不能被直接赋值给一个非常量字符数组。正确的方式是将字符串复制到数组中: ```c #include <string.h> int f(){ char a[50]; strcpy(a, "Hello, world!"); } ``` **1.12 这样的初始化有什么问题?char *p = malloc(10); 编译器提示“非法初始式”云云。** `malloc`函数返回的是一个指向分配的内存块的指针,类型为`void*`。将其赋值给`char*`类型的指针之前,需要显式转换类型。另外,分配的内存必须通过`free`函数释放。正确的代码如下: ```c char *p = (char *)malloc(10 * sizeof(char)); ``` **1.13 以下的初始化有什么区别?char a[] = "string literal"; char *p = "string literal"; 当我向p[i]赋值的时候,我的程序崩溃了。** `char a[] = "string literal";` 定义了一个字符数组并初始化为字符串字面量。可以修改数组中的元素。 `char *p = "string literal";` 定义了一个指向字符串字面量的指针。字符串字面量是只读的,尝试修改会导致未定义行为或程序崩溃。 **1.14 我总算弄清楚函数指针的声明方法了,但怎样才能初始化呢?** 函数指针的初始化很简单,只需要将函数的名字赋值给函数指针即可。例如: ```c int sum(int x, int y) { return x + y; } int (*func_ptr)(int, int) = sum; ``` 这里`func_ptr`现在指向`sum`函数。 #### 2. 结构、联合和枚举 **2.1 声明struct x1{};和typedef struct{} x2;有什么不同?** `struct x1 {};` 直接定义了一个名为`x1`的空结构体。而`typedef struct{} x2;`则是定义了一个匿名结构体,并为其创建了一个别名`x2`。使用`typedef`可以帮助避免重复的结构体定义。 **2.2 为什么struct x{}; x the_struct;不对?** 直接声明`struct x{}; x the_struct;`是不正确的语法。正确的做法是先定义结构体,然后再声明变量。例如: ```c struct x { // 成员变量 }; struct x the_struct; ``` **2.3 一个结构可以包含指向自己的指针吗?** 是的,结构体可以包含指向自身的指针。这是一种常见的实现自引用结构体的方法,例如链表节点。例如: ```c struct node { int data; struct node *next; }; ``` **2.4 在C语言中实现抽象数据类型什么方法最好?** 在C语言中实现抽象数据类型的最佳方法之一是使用结构体和指针。通常,可以通过隐藏数据成员的实现细节,只暴露必要的接口函数来实现封装。例如,定义一个结构体并为其提供操作接口: ```c typedef struct Stack { // 数据成员 } Stack; Stack *stack_new(); void stack_push(Stack *s, int val); int stack_pop(Stack *s); ``` **2.5 在C中是否有模拟继承等面向对象程序设计特性的好方法?** C语言本身并不支持面向对象编程,但可以通过组合结构体和函数指针来模拟某些特性。例如,可以定义一个结构体,并在其中包含指向其他结构体的指针以及函数指针,从而实现类似继承的效果。 **2.6 我遇到这样声明结构的代码:struct name{int namelen;char namestr[1];};然后又使用一些内存分配技巧使namestr数组用起来好像有多个元素。这样合法和可移植吗?** 这种方法被称为“flexible array member”(FAM),在C99标准中被引入。它允许结构体最后一个成员为长度可变的数组。这种方法在某些情况下是合法且可移植的,但需要注意编译器和平台的具体实现细节。 **2.7 是否有自动比较结构的方法?** C语言标准库并没有直接提供结构体比较的功能。通常需要手动遍历结构体的每个成员进行比较。对于简单的结构体,可以编写自定义函数来实现比较逻辑。 **2.8 如何向接受结构参数的函数传入常数值?** 通常情况下,可以通过定义一个包含常数值的结构体实例,然后将其作为参数传递给函数。例如: ```c struct MyStruct { int value; }; void process(const struct MyStruct *s); // 使用 struct MyStruct const s = {10}; process(&s); ``` **2.9 怎样从/向数据文件读/写结构?** 读写结构体到文件通常涉及序列化问题。可以使用`fwrite`和`fread`函数来直接写入和读取结构体。例如: ```c struct Person { char name[50]; int age; }; void writeToFile(struct Person p, FILE *file) { fwrite(&p, sizeof(struct Person), 1, file); } struct Person readFromFile(FILE *file) { struct Person p; fread(&p, sizeof(struct Person), 1, file); return p; } ``` **2.10 我的编译器在结构中留下了空洞,这导致空间浪费而且无法与外部数据文件进行”二进制”读写。能否关掉填充,或者控制结构域的对齐方式?** 大多数编译器允许通过编译器特定的指令来控制结构体成员的对齐方式。例如,在GCC中,可以使用`__attribute__((packed))`来禁止填充: ```c struct __attribute__((packed)) PackedStruct { char c; short s; }; ``` **2.11 为什么sizeof返回的值大于结构的期望值,是不是尾部有填充?** 是的,`sizeof`返回的值可能大于预期,这是因为编译器为了优化内存访问速度而添加了填充。可以通过使用`__attribute__((packed))`来禁用填充。 **2.12 如何确定域在结构中的字节偏移?** 确定域在结构中的字节偏移通常需要考虑编译器的填充规则。可以使用编译器提供的工具,如GCC的`offsetof`宏来获取域的偏移量: ```c #include <stddef.h> struct MyStruct { int i; char c; }; int main() { printf("Offset of i: %zu\n", offsetof(struct MyStruct, i)); printf("Offset of c: %zu\n", offsetof(struct MyStruct, c)); return 0; } ``` **2.13 怎样在运行时用名字访问结构中的域?** 在C语言中直接使用名字访问结构中的域通常需要在编译时知道域的名字。如果需要在运行时动态访问域,可能需要使用反射机制,但这在标准C语言中是不可行的。一种替代方案是使用关联数组或其他数据结构来模拟。 **2.14 程序运行正确,但退出时却“core dump”了,怎么回事?** “core dump”通常意味着程序在运行时遇到了致命错误,例如访问了非法内存。这可能是由于未正确管理结构体内的指针,例如忘记释放动态分配的内存等原因造成的。 **2.15 可以初始化一个联合吗?** 联合可以在声明时初始化。联合的所有成员共享同一段内存,因此只能同时初始化一个成员。例如: ```c union Data { int i; char c; }; union Data d = { .i = 10 }; ``` **2.16 枚举和一组预处理的#define有什么不同?** 枚举提供了更好的类型安全性和语义含义,而`#define`仅仅是一个文本替换。枚举值是整数类型,可以被类型检查,而`#define`没有类型限制。枚举还可以被用作数组的索引。 **2.17 有什么容易的显示枚举值符号的方法?** 显示枚举值符号的一种简单方法是使用`switch`语句。例如: ```c enum Color { RED, GREEN, BLUE }; void printColor(enum Color color) { switch (color) { case RED: printf("Red"); break; case GREEN: printf("Green"); break; case BLUE: printf("Blue"); break; } } ``` ### 表达式 **3.1 为什么这样的代码:a[i]=i++;不能工作?** 表达式`a[i]=i++;`的问题在于`i`的值在赋值后才递增。因此,`a[i]`和`i`都被更新,但`i`的值在赋值之前就已经确定。这种情况下,每次循环迭代都会覆盖前一次的结果,导致最终结果不正确。 **3.2 使用我的编译器,下面的代码int i=7;printf("%d\n",i++*i++);返回49?不管按什么顺序计算,难道不该打印出56吗?** 这个问题的关键在于C语言中表达式的求值顺序。在表达式`i++ * i++`中,`i`的值首先被使用两次,然后才递增两次。因此,如果`i`初始值为7,则表达式的值为7*7=49。 **3.3 对于代码int i=3;i=i++;不同编译器给出不同的结果,有的为3,有的为4,哪个是正确的?** 表达式`i=i++;`存在未定义行为,因为`i`的值在被赋值之后才递增。这意味着`i`的最终值取决于编译器的具体实现。这种情况下,最佳做法是避免使用此类表达式。 **3.4 这是个巧妙的表达式:a ^= b ^= a ^= b;它不需要临时变量就可以交换a和b的值。** 这个表达式确实可以用来交换两个变量的值,但前提是这两个变量都是整数类型。它利用了异或运算的性质,即任何数与自身异或的结果为0,任何数与0异或的结果是其本身。 **3.5 我可否用括号来强制执行我所需要的计算顺序?** 可以使用括号来控制表达式的求值顺序。括号内的表达式先于其他表达式被求值。这对于确保复杂表达式的正确计算顺序非常重要。 **3.6 可是&&和||运算符呢?我看到过类似while((c=getchar())!= EOF&&c!=’\n’)的代码⋯⋯** `&&`和`||`运算符遵循短路求值的原则。在`&&`运算符中,如果左侧表达式为假,则右侧表达式不会被求值。在`||`运算符中,如果左侧表达式为真,则右侧表达式不会被求值。 **3.7 我怎样才能理解复杂表达式?“序列点”是什么?** 序列点(sequence point)是指表达式中可以确定所有副作用都已完成的点。例如,赋值表达式中的赋值运算符左侧就是序列点。理解复杂表达式时,识别序列点有助于分析表达式的求值顺序和副作用。 **3.8 那么,对于a[i]=i++;我们不知道a[]的哪一个分量会被改写,但i的确会增加1,对吗?** 是的,表达式`a[i]=i++;`中的`i`的确会在赋值完成后增加1。然而,由于`i`在赋值前就已经确定,所以`a[i]`的值取决于`i`的原始值。 **3.9 ++i和i++有什么区别?** `++i`是前缀递增运算符,它首先将`i`的值加1,然后再使用新的值。而`i++`是后缀递增运算符,它首先使用`i`的当前值,然后才将`i`的值加1。 **3.10 如果我不使用表达式的值,我应该用++i或i++来自增一个变量吗?** 在这种情况下,使用哪种形式都可以。不过,通常推荐使用`++i`,因为它避免了可能的混淆,尤其是在复杂的表达式中。 **3.11 为什么如下的代码int a=100,b=100;long int c=a*b;不能工作?** 这段代码不会直接导致编译错误,但如果`a`和`b`的乘积超出了`int`类型的范围,则可能会导致溢出。为了避免溢出,可以将其中一个操作数强制转换为`long int`类型: ```c long int c = (long int)a * b; ``` **3.12 我需要根据条件把一个复杂的表达式赋值给两个变量中的一个。** 可以使用三元运算符(`?:`)来根据条件选择赋值给不同的变量。例如: ```c int a = 10, b = 20; int c, d; c = a > b ? a : b; // 如果a>b, 则c=a; 否则c=b; d = a < b ? a : b; // 如果a<b, 则d=a; 否则d=b; ```






- lyons_s2019-03-13很有学习价值的文档,感谢.

- 粉丝: 0
我的内容管理 展开
我的资源 快来上传第一个资源
我的收益
登录查看自己的收益我的积分 登录查看自己的积分
我的C币 登录后查看C币余额
我的收藏
我的下载
下载帮助


最新资源
- MATLAB环境下电动汽车续航里程影响因素分析与优化策略研究
- 基于 YOLOV3 算法的目标检测实现方案
- 西门子Smart系列水处理系统:反渗透+精混床除盐水工艺的自动化控制案例
- 基于JSP+Servlet实现的污水处理系统+源码(毕业设计&课程设计&项目开发)
- FPGA实现MIL-STD-1553B协议的BC、BM、RT源码解析及应用 实时通信
- 单周期控制的无桥CukPFC变换器:实现高频率(100k)的稳定电源转换
- Abaqus模拟中水力裂缝与天然裂缝相交的cohesive行为
- 电力电子MATLABSimulink仿真:三相PWM整流器及其多种控制方法的研究
- 基于ASP.NET MVC与SQL Server的C#图书及借阅管理系统的设计与实现 - Entity Framework 高级版
- 目标检测-YOLOV3实现
- 结构光3D测量技术:单双目编码解码与标定重建的应用实现
- 电力电子领域Buck双闭环控制降压电路PI调节器的设计与建模及其应用 Simulink v2.5
- 基于51单片机的测速码表仿真:Keil程序源码与Proteus仿真文件解析
- 基于C++ OpenCV 和 Qt 实现人脸(刷脸)登录+源码+项目文档+数据集(毕业设计&课程设计&项目开发)
- FPGA IP源码解密技术:从加密IP文件恢复Verilog与VHDL源代码的方法与挑战
- 基于CSI的WiFi室内被动式目标检测技术


