C语言宏的实现作业

答案:

解析:

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并打印,就求出了这个偏移量

答案:

解析:

确定整数的二进制位的奇数位和偶数位

位掩码提取法

c语言如何取二进制数的某几位 | PingCode智库

其中(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
相关推荐
CodeCraft Studio4 小时前
3D文档控件Aspose.3D实用教程:使用 C# 构建 OBJ 到 U3D 转换器
开发语言·3d·c#·3d渲染·aspose·3d文件格式转换·3d sdk
superlls4 小时前
(Redis)主从哨兵模式与集群模式
java·开发语言·redis
chenglin0165 小时前
C#_gRPC
开发语言·c#
骑驴看星星a6 小时前
数学建模--Topsis(Python)
开发语言·python·学习·数学建模
yueyuebaobaoxinx8 小时前
MATLAB 与 Simulink 联合仿真:控制系统建模与动态性能优化
开发语言·matlab·性能优化
小莞尔8 小时前
【51单片机】【protues仿真】基于51单片机宠物投食器系统
c语言·stm32·单片机·嵌入式硬件·51单片机·proteus
躲着人群8 小时前
次短路&&P2865 [USACO06NOV] Roadblocks G题解
c语言·数据结构·c++·算法·dijkstra·次短路
superlls9 小时前
(计算机网络)JWT三部分及 Signature 作用
java·开发语言·计算机网络
一只鲲9 小时前
56 C++ 现代C++编程艺术5-万能引用
开发语言·c++