活动介绍

你必须知道的495个C语言问题.pdf

preview
4星 · 超过85%的资源 需积分: 0 15 下载量 165 浏览量 更新于2011-08-09 收藏 1.01MB PDF 举报
### 重要知识点解析 #### 一、声明和初始化 **1.1 我如何决定使用那种整数类型?** 在C语言中选择合适的整数类型非常重要。通常情况下,`int`是最常用的数据类型,适用于大多数情况。但在某些特定场景下,如需要确保整数占用的字节数时,则需要使用`short`, `long`或`long long`。另外,如果关注的是数据类型的范围而非大小,可以考虑使用`stdint.h`头文件中定义的固定宽度整数类型,如`int32_t`或`uint64_t`等。 **1.2 64位机上的64位类型是什么样的?** 在64位系统上,通常`long`和`long long`被定义为64位类型。这意味着它们分别可以存储64位的带符号和无符号整数。具体而言,`long`类型的范围大约是从-9,223,372,036,854,775,808到9,223,372,036,854,775,807,而`long long`的范围相同。这些类型的使用可以帮助开发者确保其程序在不同架构的机器上具有一致的行为。 **1.3 怎样定义和声明全局变量和函数最好?** 定义全局变量时应尽量保持简洁并遵循良好的命名习惯。例如: ```c int globalVariable; ``` 函数声明应明确指出函数的返回类型、参数列表及其类型: ```c int function(int param1, double param2); ``` 同时,为了提高程序的可维护性和可读性,应尽可能将函数声明放在独立的头文件中,并通过`extern`关键字在其他源文件中引用这些函数。 **1.4 extern在函数声明中是什么意思?** `extern`关键字用于声明一个变量或函数是在另一个文件中定义的。在函数声明中使用`extern`通常是多余的,因为默认情况下,所有外部变量和函数都具有外部链接。例如: ```c extern int func(); // 不推荐,因为默认就是extern ``` **1.5 关键字auto到底有什么用途?** `auto`关键字最初是为了声明局部变量而设计的,但现在更多地用于告诉编译器变量的类型由初始化表达式推断。例如,在C99及以后的版本中: ```c auto x = 5; // x 的类型被推断为int ``` **1.6 我似乎不能成功定义一个链表。我试过typedefstruct{char *item;NODEPTR next;}*NODEPTR;但是编译器报了错误信息。难道在C语言中一个结构不能包含指向自己的指针吗?** 在C语言中,结构体确实可以包含指向自身的指针。问题可能在于定义的方式不正确。正确的做法是先定义结构体,再定义指向该结构体的指针类型。例如: ```c typedef struct Node { char *item; struct Node *next; } NODEPTR; ``` **1.7 怎样建立和理解非常复杂的声明?例如定义一个包含N个指向返回指向字符的指针的函数的指针的数组?** 构建和理解复杂的声明可以通过逐步分析来完成。例如,要定义一个包含N个指向返回指向字符的指针的函数的指针的数组,可以分解如下: 1. 指向字符的指针:`char *` 2. 返回指向字符的指针的函数:`char *(*function)()` 3. 指向上述函数的指针:`char *(*function_ptr)()` 4. 包含N个这样的函数指针的数组:`char *(*function_ptr_array[N])()` **1.8 函数只定义了一次,调用了一次,但编译器提示非法重定义了。** 这种情况通常是由于在不同的文件中重复定义了相同的函数。解决方法是确保每个函数只在一个文件中定义,并且在其他文件中使用`extern`关键字声明函数。 **1.9 main()的正确定义是什么?void main()正确吗?** `main()`函数是程序的入口点。根据C标准,它的正确原型应该是: ```c int main(void) ``` 或 ```c int main(int argc, char *argv[]) ``` `void main()`不是标准C的一部分,不应使用。 **1.10 对于没有初始化的变量的初始值可以作怎样的假定?如果一个全局变量初始值为“零”,它可否作为空指针或浮点零?** 未初始化的局部变量的值是不确定的,不应依赖其初始值。对于未初始化的全局变量,C标准规定其值将被初始化为0(对于数字类型)或NULL(对于指针)。因此,可以将未初始化的全局指针视为NULL。 **1.11 代码int f(){char a[]="Hello, world!";}不能编译。** 这段代码的问题在于尝试在一个局部数组中初始化字符串文字。局部数组的初始化只能使用常量表达式,而字符串文字不是常量表达式。解决方法是将其改为静态数组: ```c int f() { static char a[] = "Hello, world!"; } ``` **1.12 这样的初始化有什么问题?char *p = malloc(10);编译器提示“非法初始式”云云。** `malloc()`函数返回的是分配的内存地址,而不是字符指针。因此,正确的初始化方式应该是: ```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";`则创建了一个指向字符串文字的指针。字符串文字在C语言中是不可修改的,试图修改它会导致未定义行为。 **1.14 我总算弄清楚函数指针的声明方法了,但怎样才能初始化呢?** 函数指针可以通过指向函数名来初始化,函数名本身就是指向该函数的指针。例如: ```c int add(int a, int b) { return a + b; } int (*func_ptr)(int, int) = add; ``` #### 二、结构、联合和枚举 **2.1 声明struct x1{};和typedef struct{} x2;有什么不同?** `struct x1{}`声明了一个名为`x1`的结构体类型,而`typedef struct{} x2;`则是定义了一个名为`x2`的结构体类型别名。后者在使用时更加方便。 **2.2 为什么struct x{}; x theStruct;不对?** 这种方式的错误在于`struct x`后面没有跟大括号。正确的形式应该是: ```c struct x { int a; } theStruct; ``` **2.3 一个结构可以包含指向自己的指针吗?** 结构体可以包含指向自身的指针。例如: ```c typedef struct Node { int data; struct Node *next; } Node; ``` **2.4 在C语言中实现抽象数据类型什么方法最好?** 在C语言中实现抽象数据类型的最佳方法之一是利用结构体和函数封装数据和操作。可以将数据成员设置为私有,并通过公共接口函数提供访问和修改这些成员的方法。 **2.5 在C中是否有模拟继承等面向对象程序设计特性的好方法?** C语言本身不支持面向对象编程中的继承概念,但可以通过结构体嵌套和函数指针等方式来模拟面向对象编程的一些特性。 **2.6 我遇到这样声明结构的代码:struct name{int namelen;char namestr[1];};然后又使用一些内存分配技巧使namestr数组用起来好像有多个元素。这样合法和可移植吗?** 这种技巧被称为“Flexible Array Member”(FAM),它是C99引入的一个特性。只要正确使用动态内存分配,这种方式是合法的,并且可以在大多数现代C编译器上工作。 **2.7 是否有自动比较结构的方法?** C语言本身并没有内置的支持结构体比较的功能。不过,可以手动编写函数来逐个比较结构体中的成员。 **2.8 如何向接受结构参数的函数传入常数值?** 如果需要向函数传递结构体的常数值,可以创建一个包含这些值的结构体实例,并将它作为参数传递给函数。 **2.9 怎样从/向数据文件读/写结构?** 要从文件读取结构体数据或向文件写入结构体数据,可以使用`fread`和`fwrite`函数。例如: ```c struct Person { char name[50]; int age; }; void writePersonToFile(FILE *file, struct Person *person) { fwrite(person, sizeof(struct Person), 1, file); } struct Person readPersonFromFile(FILE *file) { struct Person person; fread(&person, sizeof(struct Person), 1, file); return person; } ``` **2.10 我的编译器在结构中留下了空洞,这导致空间浪费而且无法与外部数据文件进行”二进制”读写。能否关掉填充,或者控制结构域的对齐方式?** 大多数编译器允许通过编译器选项来关闭结构体填充或调整结构体域的对齐方式。例如,在GCC中可以使用`__attribute__((packed))`属性来避免填充: ```c struct PackedPerson { char name[50]; int age; } __attribute__((packed)); ``` **2.11 为什么sizeof返回的值大于结构的期望值,是不是尾部有填充?** `sizeof`操作符返回的是结构体的大小,包括任何填充的空间。这是因为编译器可能会在结构体的不同成员之间添加填充以优化性能或满足对齐要求。 **2.12 如何确定域在结构中的字节偏移?** 可以通过计算每个成员的偏移量来确定成员在结构体中的位置。对于GCC等编译器,可以使用`offsetof`宏来获取成员的偏移量: ```c #include <stddef.h> size_t offset = offsetof(struct Person, age); ``` **2.13 怎样在运行时用名字访问结构中的域?** 在C语言中,通常无法直接通过名字来访问结构体中的成员。然而,可以使用一些间接的方法,如哈希表和函数指针,来实现类似的功能。 **2.14 程序运行正确,但退出时却“core dump”了,怎么回事?** “core dump”通常表示程序在运行时遇到了严重错误,比如访问了非法内存区域。检查结构体的使用,特别是指针的解引用操作,确保没有释放后的指针或悬空指针的使用。 **2.15 可以初始化一个联合吗?** 联合可以在定义时初始化,但需要注意联合中成员的类型和初始化值的兼容性。例如: ```c union Data { int i; float f; } u = {42}; // 正确初始化 ``` **2.16 枚举和一组预处理的#define有什么不同?** 枚举提供了更安全和类型安全的方式来表示整数常量,而`#define`仅仅是在文本级别上进行替换,可能导致类型不匹配等问题。 **2.17 有什么容易的显示枚举值符号的方法?** 可以使用预处理宏或函数来实现枚举值到符号名称的映射。例如,可以定义一个宏或查找表来完成这个转换。 #### 三、表达式 **3.1 为什么这样的代码:a[i]=i++;不能工作?** 这种代码可能会导致未定义行为,因为它在一个表达式中修改了同一个变量两次。正确的做法是先递增`i`,然后再使用它。 **3.2 使用我的编译器,下面的代码int i=7; printf("%d\n", i++ * i++);返回49?不管按什么顺序计算,难道不该打印出56吗?** 表达式`i++ * i++`的结果取决于编译器对表达式求值顺序的选择,这可能导致未定义行为。正确的做法是避免在同一表达式中多次修改同一个变量。 **3.3 对于代码int i=3; i=i++;不同编译器给出不同的结果,有的为3,有的为4,哪个是正确?** 这个表达式同样会导致未定义行为。正确的做法是避免在同一表达式中多次修改同一个变量。推荐的做法是使用两行代码来实现自增操作: ```c int i = 3; i++; ``` **3.4 这是个巧妙的表达式:a ^= b ^= a ^= b它不需要临时变量就可以交换a和b的值。** 这个表达式确实可以用来交换两个变量的值,但它依赖于`^`(异或)运算符的特性。这种方法虽然巧妙,但可能不如使用临时变量直观和易于理解。 **3.5 我可否用括号来强制执行我所需要的计算顺序?** 可以使用括号来明确指定计算顺序,这对于避免因运算符优先级引起的问题非常有用。 **3.6 可是&&和||运算符呢?我看到过类似while((c=getchar())!= EOF && c!=’\n’)的代码…** `&&`和`||`运算符具有短路求值的特点,即左侧表达式的结果足以决定整个表达式的结果时,右侧表达式将不会被计算。例如,在`while((c=getchar())!= EOF && c!=’\n’)`中,如果`c`等于`EOF`,则不会检查`c!=’\n’`。 **3.7 我怎样才能理解复杂表达式?“序列点”是什么?** 在C语言中,序列点是一个概念,用于定义表达式求值的顺序。序列点之间的修改操作必须按照定义的顺序执行。理解复杂表达式的关键是识别序列点,以此来确定哪些操作先发生,哪些后发生。 **3.8 那么,对于a[i]=i++;我们不知道a[]的哪一个分量会被改写,但i的确会增加1,对吗?** 这个表达式会导致未定义行为,因为`i`在表达式中被修改了两次。正确的行为应该是先递增`i`,然后再使用它。 **3.9 ++i和i++有什么区别?** `++i`是前缀递增,它先将`i`的值加1,然后使用新的值;而`i++`是后缀递增,它先使用`i`的当前值,然后再将其加1。 **3.10 如果我不使用表达式的值,我应该用++i或i++来自增一个变量吗?** 如果你不关心表达式的值,使用哪种递增方式都可以。但在实践中,`++i`通常更为高效,因为它不需要额外的副本操作。 **3.11 为什么如下的代码int a=100, b=100; long int c=a*b;不能工作?** 这段代码中的问题在于整数溢出。在乘法之前,`a`和`b`都被提升为`int`类型,然后相乘。如果结果超出了`int`的范围,就会发生溢出。解决方法是将其中一个操作数显式转换为`long int`: ```c long int c = (long int)a * b; ``` **3.12 我需要根据条件把一个复杂的表达式赋值给两个变量中的一个。可以用下边这样的代码吗?((condition)?a:b)=complicated_expr;** 这种写法不符合C语言语法。正确的写法是: ```c ((condition) ? ((a) = complicated_expr) : ((b) = complicated_expr)); ``` 但更清晰的方式是分开写成两行: ```c if (condition) { a = complicated_expr; } else { b = complicated_expr; } ``` 以上知识点覆盖了《你必须知道的495个C语言问题》中提及的一些关键问题。这些问题不仅涉及C语言的基础知识,还包括了一些较为高级的概念,如结构体、联合、枚举、表达式等。掌握这些知识点对于深入理解和运用C语言至关重要。
身份认证 购VIP最低享 7 折!
30元优惠券
danfeiwll
  • 粉丝: 8
上传资源 快速赚钱
voice
center-task 前往需求广场,查看用户热搜

最新资源