C语言 - 指针理解专项自测题答案

自测题

一、 单项选择题 (每题3分,共30分)

  1. 正确答案: C) 指针变量可以存储任何类型数据的内存地址。
  2. 解析:
  3. A) 错误。指针变量存储的是内存地址,而不是数据的值。数据的值存储在内存地址所指向的位置。
  4. B) 错误。指针可以指向任何数据类型,包括整型、浮点型、字符型、结构体等。只需要在声明指针时指定指针指向的数据类型即可。
  5. C) 正确。指针的本质就是存储内存地址,它可以存储任何类型的内存地址。
  6. D) 错误。指针变量本身也占用内存空间,其大小取决于系统架构(例如32位系统通常是4字节,64位系统通常是8字节),且指针变量正是用来存储内存地址的。
  7. 正确答案: D) 变量 a 的值。
  8. 解析:
  9. A) 错误。 ptr 是指针变量的名字,不是取地址操作。要获取 ptr 本身的地址应该使用 &ptr
  10. B) 错误。 &a 是变量 a 的地址,赋值给了 ptr,但 *p 是解引用操作,不是取地址。
  11. C) 错误。 ptr 的值是变量 a 的地址,而不是变量 a 的值。
  12. D) 正确。 * 是解引用运算符,*p 表示访问指针 p 所指向的内存地址中存储的值,由于 p 指向 a,所以 *p 就是变量 a 的值,即 10。
  13. 正确答案: D) 错误原因是指针 ptr 未被初始化就进行解引用操作。
  14. 解析:
  15. A) 正确,声明指针变量 int *ptr; 本身没有错误。
  16. B) 正确, printf("%d", *ptr); 从语法上来说是解引用操作,本身没有语法错误。
  17. C) 错误。 代码片段存在潜在的运行时错误。
  18. D) 正确。 指针 ptr 声明后未被初始化,它的值是随机的,即它可能指向内存中的任何位置。直接对未初始化的指针进行解引用操作 \*ptr 是非常危险的,会导致未定义行为,通常会导致程序崩溃(Segmentation Fault)或者读取到非法内存数据。这就是典型的野指针问题。
  19. 正确答案: C) 第 3 个元素 arr[2]
  20. 解析:
  21. A) 错误。 arr[0] 对应 *(p + 0)*p
  22. B) 错误。 arr[1] 对应 *(p + 1)
  23. C) 正确。 指针 p 指向数组 arr 的首元素 arr[0]。 指针运算 p + 2 会将指针 p 向后移动两个 int 类型的大小(假设 int 为 4 字节,则移动 8 字节)。 因此 *(p + 2) 访问的是数组 arr 的第三个元素 arr[2]
  24. D) 错误。 arr[3] 对应 *(p + 3)
  25. 正确答案: C) 指针与整数相加,其地址值会增加,增加的大小与指针类型有关。
  26. 解析:
  27. A) 错误。 指针不能与浮点型常量或其他类型常量直接进行加法运算,通常只能与整型常量进行加法运算,表示指针的偏移。
  28. B) 错误。 指针之间通常不能进行乘法运算,指针运算主要包括指针的算术运算(加减)和关系运算(比较)。
  29. C) 正确。 指针与整数 n 相加 (或相减),指针的地址值会增加 (或减少) n * sizeof(指针类型) 字节。 例如 int *p; p + 1; 指针 p 的地址值增加 sizeof(int) 字节。
  30. D) 错误。 两个指针相加在C语言中通常没有实际意义,也不被允许。指针运算主要是指针的算术运算和关系运算。
  31. 正确答案: D) int num = 10; func(&num);
  32. 解析:
  33. A) 错误。 函数 func 期望接收一个 int * 类型的参数(整型指针),而 func(10) 传递的是整型值 10,类型不匹配。
  34. B) 错误。 func(&10) &10 试图获取常量 10 的地址,这是不允许的,常量通常存储在只读内存区域,不能被取地址修改。
  35. C) 错误。 func(num) 传递的是变量 num 的值,仍然是值传递,类型不匹配函数 func 的指针形参。
  36. D) 正确。 int num = 10; func(&num); 首先定义一个整型变量 num,然后使用 &num 获取变量 num 的地址,&num 的类型是 int \*,与函数 func 的形参类型匹配,实现了地址传递。
  37. 正确答案: D) 结构体可以定义在函数内部,作用域仅限于该函数。
  38. 解析:
  39. A) 错误。 结构体可以包含不同类型的数据成员,这是结构体的优势之一,用于组合不同类型的数据。
  40. B) 错误。 结构体变量默认不能直接进行加减运算,需要自定义运算符重载或者逐个成员进行运算。
  41. C) 错误。 使用 -> 运算符访问结构体指针的成员是正确的,但题干说的是使用 . 运算符,这是访问结构体变量成员的方式,而不是指针。应该使用 ptr->member(*ptr).member 来访问结构体指针 ptr 指向的结构体的成员 member
  42. D) 正确。 结构体类型定义可以放在全局作用域(函数外部),也可以放在局部作用域(例如函数内部),如果定义在函数内部,则结构体类型的作用域仅限于该函数内部及其嵌套的代码块。
  43. 正确答案: B) #include
  44. 解析:
  45. A) 错误。 #define 用于宏定义,定义宏常量或宏函数。
  46. B) 正确。 #include 是文件包含预处理指令,用于将指定头文件的内容包含到当前源文件中。
  47. C) 错误。 #if 是条件编译预处理指令,用于根据条件选择性编译代码。
  48. D) 错误。 #pragma 也是预处理指令,用于指定一些编译器特定的指令,不同的 #pragma 指令有不同的作用。
  49. 正确答案: C) malloc, free
  50. 解析:
  51. A) 错误。 scanf 是标准输入函数,用于从标准输入读取数据; malloc 是动态内存分配函数。
  52. B) 错误。 printf 是标准输出函数,用于向标准输出打印数据; free 是动态内存释放函数。
  53. C) 正确。 malloc 函数用于在堆区动态分配指定大小的内存块,返回指向该内存块首地址的 void * 指针。 free 函数用于释放 malloc, calloc, realloc 等动态分配的内存,防止内存泄漏。
  54. D) 错误。 callocrealloc 也是动态内存分配函数,但 calloc 会初始化分配的内存为零, realloc 用于重新调整已分配内存块的大小。 但题目问的是“主要使用哪个函数动态分配内存”, malloc 是最基础和最常用的动态内存分配函数。
  55. 正确答案: D) &
  56. 解析:
  57. A) 错误。 | 是按位或运算符。
  58. B) 错误。 ^ 是按位异或运算符。
  59. C) 错误。 ~ 是按位取反运算符。
  60. D) 正确。 & 是按位与运算符,对两个操作数的对应位进行与运算,只有当两位都为 1 时,结果位才为 1,否则为 0。

二、 填空题 (每空3分,共30分)

  1. 指针变量的大小在同一平台下是 固定 的,与指针指向的数据类型 无关
  • 解析: 指针变量存储的是内存地址,内存地址的长度取决于计算机的寻址能力,对于同一平台(例如 64 位系统),地址长度是固定的,因此指针变量的大小也是固定的。 指针类型只影响指针运算的步长和解引用时访问的内存大小,不影响指针变量本身的大小。
  1. 使用 sizeof 运算符可以获取指针变量自身占用的内存大小(以字节为单位)。
  • 解析: sizeof(指针变量名)sizeof(指针类型) 都可以用来获取指针变量在内存中占用的字节数。
  1. 若要定义一个指向字符数组的指针,可以使用 char * 类型的指针。
  • 解析: 字符数组存储的是字符,要指向字符数组,指针类型应该是 char *,表示指向字符的指针。
  1. 函数指针的类型由函数的 返回值类型参数列表 决定。
  • 解析: 函数指针的类型必须与它指向的函数的类型完全匹配,包括返回值类型和参数列表(参数类型和数量)。
  1. do-while 循环至少会执行一次循环体。
  • 解析: do-while 循环的特点是先执行循环体,再判断循环条件,因此循环体至少会被执行一次,即使初始条件不满足。
  1. void * 指针可以指向任何数据类型。
  • 解析: void * 指针被称为“通用指针”或“无类型指针”,它可以指向任何类型的内存地址,但在使用 void * 指针进行解引用操作时,需要先将其强制类型转换为具体的指针类型。
  1. * 运算符用于解引用指针,访问指针指向的值。
  • 解析: * 运算符是解引用运算符,也称为间接访问运算符,用于获取指针所指向的内存地址中存储的数据。
  1. 使用 fopen 函数可以打开文件。
  • 解析: fopen 函数是C标准库中用于打开文件的函数,它接受文件路径和打开模式作为参数,并返回指向 FILE 结构体的文件指针,用于后续的文件读写操作.
  1. 指针 数组是指元素为指针的数组,而 数组 指针是指指向数组的指针。
  • 解析: 理解这两个概念的关键在于区分修饰词的位置。“指针数组”的中心词是“数组”,本质是一个数组,数组的每个元素是指针。“数组指针”的中心词是“指针”,本质是一个指针,它指向的是一个数组。
  1. 为了防止内存泄漏,动态分配的内存必须在不再使用时通过 free 函数显式释放。
  • 解析: 动态分配的内存(例如使用 malloc, calloc, realloc 分配的内存)不会像栈区变量一样自动释放,必须由程序员手动调用 free 函数来释放,否则会造成内存泄漏,即分配的内存无法再被程序使用,但也没有归还给操作系统。

三、 代码阅读题 (每题10分,共20分)

  1. 输出结果: 交换前:num1 = 10, num2 = 20 交换后:num1 = 20, num2 = 10
  • 代码分析:
    • swap 函数接受两个 int * 类型的指针参数 ab
    • swap 函数内部,通过解引用指针 *a*b,直接操作指针 ab 指向的内存地址,即 main 函数中的 num1num2 变量的内存。
    • 函数首先将 *a 的值(num1 的值)保存到临时变量 temp 中,然后将 *b 的值(num2 的值)赋值给 *anum1),最后将 temp 的值(原 num1 的值)赋值给 *bnum2)。
    • 因此,swap 函数通过指针实现了对 num1num2 变量值的交换(地址传递)。
    • main 函数中,在调用 swap 函数前后分别打印 num1num2 的值,可以看到值已经被成功交换。
  1. 功能分析: 代码的功能是计算动态分配的整型数组 dynamic_arr 中所有元素的和,并将每个元素的值乘以 2。 实际输出结果只显示了乘以2之后的结果。
  • 输出结果:
  • 动态数组元素:2 4 6 8
  • 代码分析:
    • modify_array 函数接受一个整型指针 arr 和数组大小 size 作为参数。
    • modify_array 函数内部使用 for 循环遍历数组,通过指针运算 *(arr + i) 访问数组元素,并将每个元素的值乘以 2,修改了数组元素的值。
    • main 函数中,首先使用 malloc 动态分配了一个包含 4 个 int 元素的数组 dynamic_arr
    • 然后初始化数组元素为 1, 2, 3, 4。
    • 调用 modify_array 函数修改数组元素的值。
    • 最后,打印修改后的数组元素的值,可以看到每个元素都变成了原来的两倍。
    • 程序结束前,使用 free 释放了动态分配的内存,防止内存泄漏。

四、 简答题 (每题10分,共20分)

  1. 值传递和地址传递的区别及地址传递的优势:
  • 值传递: 值传递是指函数调用时,将实参的 复制一份传递给形参。函数内部对形参的修改不会影响到实参本身。 相当于“复印件”,函数操作的是副本,不是原件。
  • 地址传递(指针传递): 地址传递是指函数调用时,将实参的 内存地址 传递给形参。形参是指针类型,指向实参的内存地址。函数内部可以通过指针形参 直接访问和修改实参所指向的内存,因此函数内部对形参的修改会直接影响到实参。 相当于“遥控器”,函数可以通过遥控器直接操作实参指向的物体。
  • 地址传递的优势:
    • 修改函数外部变量的值: 函数可以通过地址传递修改调用函数中变量的值,实现双向数据传递。值传递只能实现单向传递(从调用者传递给被调用者)。
    • 高效传递大型数据结构: 当需要传递大型数据结构(如结构体、数组)时,使用地址传递只需要传递地址(指针),开销很小,避免了值传递中大量数据拷贝的开销,提高了效率。
    • 实现动态数据结构: 例如链表、树等数据结构,通常需要使用指针进行链接和操作,地址传递是实现这些数据结构的基础。
  • 例子说明地址传递的优势 (例如交换两个数的值): swap 函数的例子就很好地体现了地址传递的优势。如果使用值传递,swap 函数内部只能交换形参的值,无法交换 main 函数中实参 num1num2 的值。只有使用地址传递,通过指针操作 num1num2 的内存地址,才能真正实现值的交换。
  1. 野指针的概念、危害及避免方法:
  • 野指针的概念: 野指针是指 指针变量存储的内存地址是不可预期的、无效的或已经释放的内存地址。 通俗地说,野指针指向了一个“不确定”或“危险”的内存区域。 野指针并不是空指针 NULL,空指针是明确指向地址 0,而野指针是指向的内存区域是随机的、未知的。
  • 野指针可能造成的危害:
    • 程序崩溃 (Segmentation Fault): 如果野指针指向的内存区域是受保护的,程序尝试访问该区域会引发操作系统错误,导致程序崩溃。
    • 数据错误: 如果野指针指向的内存区域恰好可以访问,但该区域的数据不是程序所期望的,程序可能会读取到错误的数据,导致逻辑错误,甚至产生不可预测的结果。
    • 破坏系统稳定性: 在某些情况下,恶意使用野指针可能导致系统不稳定,甚至被利用进行安全攻击。
  • 避免野指针的产生:
    • 初始化指针: 最重要的一点,在声明指针变量时,要 立即初始化。 如果尚不清楚指针应该指向哪里,可以先初始化为 NULL,表示空指针。
    • 避免返回局部变量的地址: 函数内部局部变量的内存在函数结束时会被释放,如果返回局部变量的地址,函数外部获得的指针将成为野指针。
    • 释放内存后将指针置为 NULL 当使用 free 函数释放了动态分配的内存后,应该 立即将指向该内存的指针置为 NULL。 这样可以避免该指针成为悬空指针,即使后续错误地使用了该指针,解引用空指针会比解引用野指针更容易被检测和定位。
    • 指针使用前进行有效性检查: 在使用指针之前,要 检查指针是否为 NULL,或者是否指向了合法的内存区域。 但这并不能完全避免野指针问题,只能在一定程度上减少错误。
    • 良好的编程习惯:
    • 养成良好的编程习惯,例如:
      • 明确指针的生命周期和作用域。
      • 谨慎使用指针运算,避免越界访问。
      • 仔细检查代码,特别是涉及指针操作的代码。
      • 使用内存分析工具辅助检测内存错误。
原文链接:,转发请注明来源!