一

答案:

解析:
C语言结构体偏移量计算方法
1,使用 offsetof
宏
offsetof
宏定义在 <stddef.h>
头文件中,是获取结构体成员偏移量的标准方法

举例:

2,手动实现offsetof
宏
cpp
#define MY_OFFSETOF(type, member) \
((size_t)&((type *)0)->member)
// 或者更安全的版本
#define MY_OFFSETOF(type, member) \
((size_t)((char *)&((type *)0)->member - (char *)0))
(type*)0是将数值0强制转换为指向type类型的指针,相当于创建了一个指向地址0的虚拟结构体指针。
->member,通过指针访问结构体成员,同时由于指针指向地址0,成员的地址就是它的偏移量。
解释:将一个结构体指针指向地址0时,我们实际上是在创建一个"参考坐标系",结构体的起始位置就是地址0,成员的地址 = 起始地址 + 偏移量。
数学理解:
cpp
struct Example *ptr = some_address;
&ptr->member = (char *)ptr + offset_of_member
(char*)0就是地址0,而+ offset_of_member
= 加上偏移量,结果就是偏移量的数值。
可视化理解:
cpp
地址刻度: 0 1 2 3 4 5 ... 24 25 26 27
[id][id][id][id][n][n] ... [gpa][gpa][gpa][gpa]
↑ ↑ ↑
id开始 name开始 gpa开始
偏移量: 0 4 24
-
id
在地址 0,偏移量 = 0 - 0 = 0 -
name
在地址 4,偏移量 = 4 - 0 = 4 -
gpa
在地址 24,偏移量 = 24 - 0 = 24
&((type *)0)->member,取成员地址,其实就是成员的偏移量。
((size_t)&((type *)0)->member),最后就是
直接将成员地址转换为size_t
类型。
但是我们可以使用一个更加安全的版本
cpp
((size_t)((char *)&((type *)0)->member - (char *)0))
成员地址转换为char *
(字节指针),减去基地址(char*)0,结果就是字节偏移量,再转换为size_t。
(1)使用指针算术而非直接引用
(2)符合编译器标准
第一种方法的缺点
cpp
((size_t)&((type *)0)->member)
(1)可能出现直接对NULL指针进行解引用
(2)技术上是未定义行为,某些编译器会发出警告
注意:%zu是用于打印类型变量的格式说明符。
结构体最后的字符是结构体变量,而非结构体类型,所以宏中应该传入的是struct Student。
总结:
1、先将0转换为一个结构体类型的指针,相当于某个结构体的首地址是0。此时,每一个成员的偏移量就成了相对0的偏移量,这样就不需要减去首地址了。
2、对该指针用->访问其成员,并取出地址,由于结构体起始地址为0,此时成员偏移量直接相当于对0的偏移量,所以得到的值直接就是对首地址的偏移量。
3、取出该成员的地址,强转成size_t并打印,就求出了这个偏移量
二

答案:

解析:
确定整数的二进制位的奇数位和偶数位
位掩码提取法
其中(1U << n) - 1中1U指的是无符号整数1,<<n是将1左移n位,-1就是减去1.
举例:1U << 3
= 1000
(二进制) = 8 (十进制),(1U << 3) - 1
= 1000 - 1
= 0111
(二进制) = 7 (十进制)
打印二进制表示
cpp
void print_binary(unsigned int num, int bits) {
for (int i = bits - 1; i >= 0; i--) {
printf("%d", (num >> i) & 1); // 提取第i位的值
if (i % 4 == 0 && i != 0) printf(" "); // 每4位加空格分隔
}
printf("\n");
}
从最高位(bits-1)到最低位(0)遍历,为什么最高位是bits-1呢,可以类比数组。(num >> i) & 1
:右移i位后取最低位。
三

答案:C
解析:
宏不存在执行速度,它是查找替换,选C。A中宏是查找替换,无法设定递归跳出条件,自然无法递归。B中宏是查找替换,都没有执行,类型更是无从谈起。D中直接说了宏的本质。所以只要知道了宏是查找替换,其他问题也就不是问题了。
四

答案:B
解析:
A是宏定义,C是一个比较复杂的预编译语句,但跟条件肯定扯不上关系,D是报错用的,条件编译指令包括#if、#ifdef,#ifndef,#else,#elif、#endif等。除此之外还有#if defined(xxx)的用法。故选B。
五

答案:D
解析:
AB说反了,尖括号是直接去库找,双引号是先从当前目录找,再去库里找。C选项头文件不能定义全局变量,否则如果有多个文件,那链接时会冲突。故选D。D也不是十全十美,在大型项目的开发中,这也并不是一个很好的编程习惯,分类放在不同的头文件并根据特点命名是更好的选择,因为这样更加方便代码的管理和维护,就目前而言,算是一个好习惯吧。
六

答案:A
解析:
解决同一文件重复包含头文件
cpp
#ifndef __SOMEFILE_H__
#define __SOMEFILE_H__
// 头文件的内容(函数声明、宏定义、类型定义等)
#endif // __SOMEFILE_H__
1,#ifndef __SOMEFILE_H__,如果未定义宏,就会编译下面的代码,而第一次包含头文件时,这个宏肯定是未定义的,而第二次包含的时候,宏已经被被定义了,所以条件为假,跳过整个头文件内容
2,#define __SOMEFILE_H__,表示这个宏已经被定义了
3,#endif,结束
现代替代方案:#pragma once
cpp
#pragma once
// 头文件内容
// 不需要#endif