题一:宏实现计算偏移量
写一个宏,计算结构体中某变量相对于首地址的偏移,并给出说明
考察:offsetof宏的实现
StructType是结构体类型名,MemberName是成员名。具体操作方法是:
1、先将0转换为一个结构体类型的指针,相当于某个结构体的首地址是0。此时,每一个成员的偏移量就成了相对0的偏移量,这样就不需要减去首地址了。
2、对该指针用->访问其成员,并取出地址,由于结构体起始地址为0,此时成员偏移量直接相当于对0的偏移量,所以得到的值直接就是对首地址的偏移量。
3、取出该成员的地址,强转成size_t并打印,就求出了这个偏移量。
#include <stdio.h> struct S { char arr1; int age; char sex; }; #define OFFSETOF(type,arr1) (size_t)&(((type*)0)->arr1)//将struct S的地址放到0处 int main() { struct S s = { 0 }; printf("%d\n",OFFSETOF(struct S,sex)); printf("%d\n", OFFSETOF(struct S, age)); printf("%d\n", OFFSETOF(struct S, arr1)); return 0; }
题二:宏实现二进制奇偶位交换
交换奇偶位
写一个宏,可以将一个整数的二进制位的奇数位和偶数位交换。
交换奇偶位,需要先分别拿出奇偶位。既然是宏,分别拿出用循环不是很现实,那就用&这些位的方式来做。奇数位拿出,那就是要&上010101010101……,偶数位拿出,就是要&上101010101010……,对应十六进制分别是555……和aaa……,一般我们默认是32位整数,4位对应一位16进制就是8个5,8个a。通过& 0x55555555的方式拿出奇数位和& 0xaaaaaaa的方式拿出偶数位。奇数位左移一位就到了偶数位上,偶数位右移一位就到了奇数位上,最后两个数字或起来,就完成了交换。
※这个宏只能完成32位以内的整形,要想完成64位的,那就将5和a的数量翻倍即可。
#include <stdio.h> //全部奇数位向右移动1位,偶数位向左移动1位 #define SWAP(n) (((n&0x55555555)<<1) + ((n&0xaaaaaaaa)>>1)) int main() { int n = 0; scanf("%d", &n); printf("%d\n", SWAP(n)); return 0; }
做错的选择题
下面说法不正确的是:( )
A.scanf和printf是针对标准输入、输出流的格式化输入、输出语句
B.fscanf和fprintf是针对所有输入、输出流的格式化输入、输出语句
C.sscanf是从字符串中读取格式化的数据
D.sprintf是把格式化的数据写到输出流中
D选项中,sprintf是把格式化的数据写到字符串中,与输出流无关。其他三句都准确描述了函数功能。
关于feof函数描述不正确的是:( )
A.feof函数是用来判断文件是否读取结束的
B.feof函数是在文件读取结束的时候,检测是否是因为遇到了文件结束标志EOF,而读取结束
C.读取文本判断是否结束时,fgetc看返回值是否为EOF, fgets看返回值是否为NULL
D.二进制文件判断读取结束,看实际读取个数是否小于要求读取个数
说明:feof函数是在文件读取结束后,判断文件读取结束的原因的,是读取失败结束,还是遇到文件尾结束。所以A是错误的,其他选项均正确。
下面哪个不是预处理指令:( )
A.#define
B.#if
C.#undef
D.#end
#define执行查找替换,#if可以区分是否编译,#undef可以反定义,也就是取消#define宏定义的东西,#end并没有这玩意,只有#endif,故选D。
下面哪个是条件编译指令( )
A.#define
B.#ifdef
C.#pragma
D.#error
A是宏定义,C是一个比较复杂的预编译语句,但跟条件肯定扯不上关系,D是报错用的,条件编译指令包括#if、#ifdef,#ifndef,#else,#elif、#endif等。除此之外还有#if defined(xxx)的用法。故选B。
以下关于头文件,说法正确的是( )
A.#include<filename.h>,编译器寻找头文件时,会从当前编译的源文件所在的目录去找
B.#include“filename.h”,编译器寻找头文件时,会从通过编译选项指定的库目录去找
C.多个源文件同时用到的全局整数变量,它的声明和定义都放在头文件中,是好的编程习惯
D.在大型项目开发中,把所有自定义的数据类型、函数声明都放在一个头文件中,各个源文件都只需要包含这个头文件即可,省去了要写很多#include语句的麻烦,是好的编程习惯。
AB说反了,尖括号是直接去库找,双引号是先从当前目录找,再去库里找。C选项头文件不能定义全局变量,否则如果有多个文件,那链接时会冲突。故选D。D也不是十全十美,在大型项目的开发中,这也并不是一个很好的编程习惯,分类放在不同的头文件并根据特点命名是更好的选择,因为这样更加方便代码的管理和维护,就目前而言,算是一个好习惯吧。
经典题型
由多个源文件组成的C程序,经过编辑、预处理、编译、链接等阶段会生成最终的可执行程序。下面哪个阶段可以发现被调用的函数未定义?( )
A.预处理
B.编译
C.链接
D.执行
预处理只会处理#开头的语句,编译阶段只校验语法,链接时才会去找实体,所以是链接时出错的,故选C。这里附上每个步骤的具体操作方式:
预处理:相当于根据预处理指令组装新的C/C++程序。经过预处理,会产生一个没有头文件(都已经被展开了)、宏定义(都已经替换了),没有条件编译指令(该屏蔽的都屏蔽掉了),没有特殊符号的输出文件,这个文件的含义同原本的文件无异,只是内容上有所不同。
编译:将预处理完的文件逐一进行一系列词法分析、语法分析、语义分析及优化后,产生相应的汇编代码文件。编译是针对单个文件编译的,只校验本文件的语法是否有问题,不负责寻找实体。
链接:通过链接器将一个个目标文件(或许还会有库文件)链接在一起生成一个完整的可执行程序。 链接程序的主要工作就是将有关的目标文件彼此相连接,也就是将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够被操作系统装入执行的统一整体。在此过程中会发现被调用的函数未被定义。需要注意的是,链接阶段只会链接调用了的函数/全局变量,如果存在一个不存在实体的声明(函数声明、全局变量的外部声明),但没有被调用,依然是可以正常编译执行的。
test.c文件中包括如下语句:
#define INT_PTR int*
typedef int*int_ptr;
INT_PTR a,b;
int_ptr c,d;
文件中定义的四个变量,哪个变量不是指针类型?( )
A.a
B.b
C.c
D.d
预处理的#define是查找替换,所以替换过后的语句是“int*a,b;”,其中b只是一个int变量,如果要让b也是指针,必须写成“int *a, *b;”。而typedef没有这个问题,c、d都是指针。故选B。