最全c语言预处理 国庆期间不休息弯道超车一篇就够了_c语言,国庆促销,2024年最新字节跳动超高难度三面C C++程序员面经

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

链接的过程做了哪些事

  • 1、合并段表
  • 2、符号表的合并和重定位

在这里插入图片描述

程序的执行环境

运行环境

  1. 程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。
  2. 在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。
  3. 程序的执行便开始。接着便调用main函数。
  4. 开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同 时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。
  5. 终止程序。正常终止main函数;也有可能是意外终止

预处理详解

预定义符号

  • FILE //进行编译的源文件
  • LINE //文件当前的行号
  • DATE //文件被编译的日期
  • TIME //文件被编译的时间
  • STDC //如果编译器遵循ANSI C,其值为1,否则未定义

以下简单地使下这几个预定义符号,给读者看看测试效果

一个测试代码

void functest()
{
	int arr[10] = {1,2,3,4,5,6,7,8,9,10};
	for (int i = 0; i < 10; i++) 
	{
		//按照格式化的形式将数据输出到文件当中
		printf("file:%s line:%d arr[%d] = %d\n",
		 \_\_FILE\_\_, \_\_LINE\_\_,i,arr[i]);
	}
}

在这里插入图片描述

在这里不仅可以看到程序在哪个文件下编译,还可以现在printf打印所处的这一行

多使用几组预定义符号

函数功能是将格式化的数据写入到文件当中

void functest()
{
	FILE \*pf = fopen("C:\\Users\\26961\\Desktop\\data.txt","w");
	if (pf == NULL)
	{
		perror("fopen: file");
		exit(-1);
	}
	int arr[10] = {1,2,3,4,5,6,7,8,9,10};

	for (int i = 0; i < 10; i++) 
	{
		//按照格式化的形式将数据输出到文件当中
		fprintf(pf,"file:%s line:%d date: %s time : %s arr[%d] = %d\n", \_\_FILE\_\_, \_\_LINE\_\_, \_\_DATE\_\_, \_\_TIME\_\_,i,arr[i]);
	}
}

呈现的效果
在这里插入图片描述

#define

#define 定义标识符

语法:
 #define int INT

#define 定义INT,相当于给int取了一个小名叫INT,但是INT和int是同一个人是一回事,在预处理阶段只是将INT进行文本替换成int

一些你没使用过的五花八门的宏定义
#define reg register //为 register这个关键字,取别名reg
#define do\_forever for(;;) //用更形象的符号来替换一种实现
#define CASE break;case //在写case语句的时候自动把 break写上。
// 如果定义的 stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。
#define DEBUG\_PRINT printf("file:%s\tline:%d\t \
 date:%s\ttime:%s\n" ,\
 \_\_FILE\_\_,\_\_LINE\_\_ , \
 \_\_DATE\_\_,\_\_TIME\_\_ )

解释 #define CASE break;case

#define CASE break;case
void functest()
{
	int input = 0;
	scanf("%d",&input);
	switch (input) 
	{
		case 1:
			//语句
		CASE 2 :
			//语句
			//预处理展开后 break;case 2:
		CASE 3 :
			//语句
			预处理展开后 break;case 3:
		CASE 4 :
			//语句
			预处理展开后 break;case 4:
		;  //分号结束
	}
}

在使用switch语句的时候 #define CASE break;case 是可行的,会将#define定义的符号替换成break;case,但是不建议这么写,可读性不好,但是确确实实是#define 的一个强大之处

解释#define do_forever for(; ; )

#define do\_forever for(;;)
int main()
{
	do_forever for(;;)
	printf("hello c");
}

用更形象的符号来替换一种实现,实现的功能是循环打印hello c,并且程序不会终止

再来看一个

 #define DEBUG\_PRINT printf("file:%s\tline:%d\t \
 date:%s\ttime:%s\n" ,\
 \_\_FILE\_\_,\_\_LINE\_\_ ,)\
 \_\_DATE\_\_,\_\_TIME\_\_ )

后面的反斜杠表示的是一个续行符,只有在写#define 定义标识符的时候才会出现的语法,但是值得注意的是千万不要在续行符的后面加上空格、回车之类的,如果反斜杠跟空格回车组合在一起会成转义字符,那么你的程序可能会出问题,小心使用即可

错误示范
#define MAX 1000;
void functest()
{
	int max = 0;
	if (1)
		max = MAX;
		//宏展开后max = 1000;;
	else
		max = 0;
}

在这里插入图片描述

if语句并没有带{},所以他只针对一行语句有效,可是宏展开后max = 1000;; ,在c语言中以分号结束的可以看出是一条空语句,这样就会导致if和else没有匹配上的问题,除非用{}把 max = MAX;括起来

带参数的宏定义

#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(definemacro)

容易坑人的写法

#define SQUARE(x) x \* x
void functest()
{
	printf("%d \n", SQUARE(5));
	printf("%d \n", SQUARE(4 + 1));
}

写成第一种确实不会有什么问题,但是当写成第二种的时候会被替换成
4 + 1 * 4 + 1 = 9,原因是在预处理阶段会将4 + 1直接替换x,x并不看作是一个整体
稍作修改

#define SQUARE(x) (x) \* (x)
void functest()
{
	printf("%d \n", SQUARE(5));
	printf("%d \n", SQUARE(4 + 1));
}

(x) * (x)即使被替换了也是(4 + 1) * (4 + 1),并不会影响最后的结果,因为x已经被看作是一个整体了

#define 替换规则

  • 在程序中扩展#define定义符号和宏时,需要涉及几个步骤。
  • 1、 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
  • 2、 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值替换。
  • 3、 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。

#和##

使用 # ,把一个宏参数变成对应的字符串

#define PRINTF(x) printf("the value of "#x" is %d\n",x);
void functest()
{
	int a = 100;
	PRINTF(a)
}

#的作用不将参数名替换成100,而是保留参数名,将这个参数名变成一个字符串

执行结果
在这里插入图片描述
看一个复杂的宏定义

#define PRINTF(FORMAT,x)\
printf("the value of "#x" is "FORMAT"\n",x);

#x :保留参数名
FORMAT:指定格式

void functest()
{
	int a = 100;
	int b = 200;
	float f = 5.51;
	PRINTF("%d",a)
	PRINTF("%d",b)
	PRINTF("%f",f)
}

这个宏的功能是按照指定格式进行输出

程序运行结果
在这里插入图片描述
##

  • ##可以把位于它两边的符号合成一个符号。 它允许宏定义从分离的文本片段创建标识符
#define CAT(x,y) x##y
void functest()
{
	
	int class102 = 100;
	printf("%d",CAT(class,102));
}

程序功能是将两个标识符合并成一个标识符,再对他进行宏替换,x##y替换的就是class102,打印结果就是100

程序运行结果
在这里插入图片描述

带副作用的宏参数

  • 当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。 例如:

x+1;//不带副作用
x++;//带有副作用

#define MAX(x,y) ((x) > (y) ? (x):(y))
void functest()
{
	
	int a = 10;
	int b = 11;  

	int ret = MAX(a++,b++);// ((a++) > (b++) ? (a++) : (b++))
	printf("%d\n", ret);//12
	printf("%d %d",a,b);//11,13
}

宏的两个参数是a++和b++,当它们传递过去后a++会替换x,b++会替换y在宏展开的时候就变成了 ((a++) > (b++) ? (a++):(b++)),由于是后置++,先使用在++,10 > 11 ? 执行的是后面的那个表达式所以b++会被执行两次,而a++只会在做判断的时候执行一次,

程序运行结果
在这里插入图片描述

宏和函数的对比

宏和函数对比:
宏通常被应用于执行简单的运算。比如在两个数中找出较大的一个。

#define MAX(a, b) ((a)>(b)?(a):(b))

那为什么不用函数来完成这个任务? 原因有二:

  • 1、用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序 的规模和速度方面更胜一筹。
  • 2、更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之这个宏怎可 以适用于整形、长整型、浮点型等可以用于>来比较的类型。宏是类型无关的。

为了方便大家认清宏和函数的区别这里举例一个例子出来,请看下面代码

//从以下程序来看宏确实是要比函数好的,因为他写的更加的简洁
#define MAX(a, b) ((a)>(b)?(a):(b))

int Max(int x,int y) 
{
	return x > y ? x : y;
}

int main() 
{
	//举例一:
	printf("max = %d ",Max(1, 3));//函数
	printf("max = %d",MAX(1,3));//宏
	//举例二:
	printf("max = %f ",Max(1.5, 3.6));//函数 
	printf("max = %f",MAX(1.5,3.6));//宏
	return 0;
}

第一个例子看得出宏确实要比函数好,好在哪里好在够简洁,即使是一个简单得计算可能花费的时间就需要1毫秒,但是如果使用函数的话,函数的调用以及返回都是需要占用时间的这个过程会花费2秒的时间,而同样是计算一个max的值带参数的宏定义只需要花费1秒(从汇编代码的角度观察)
但是从例子二来看的话并不是如此,预处理期间会将宏进行文本替换,他还是一个浮点数,但是Max函数就不一样了,他的两个参数类型是int,浮点型数据传递过去会丢失小数从而变成1和3,即使返回结果却也发了变化

当然和宏相比函数也有劣势的地方:

  1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
  2. 宏是没法调试的。
  3. 宏由于类型无关,也就不够严谨。
  4. 宏可能会带来运算符优先级的问题,导致程容易出现错。

解释:
1、宏会大幅度增加程序的长度? 使用宏的时候在预处理阶段会将宏替换成文本,代码长度增加可想而知
2、宏是没法调试的?预处理阶段排在程序执行前,预处理阶段完成的替换工作后,才会将代码插入到程序中去,我们调试的时候其实用的是编译后的代码,所以宏没法调试
3、宏由于类型无关,也就不够严谨?在上一个例子里面已经解释过
4、宏可能会带来运算符优先级的问题?SQUARE(x) x * x 这个例子x并不看作整体

宏有时候可以做函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到。

//num :要开辟几个type大小的空间
//type:类型
#define Malloc(num,type) (type\*)malloc(num \* sizeof(type));
//预处理替换后:(int \*)malloc(10 \* sizeof(int));
int main()
{
	//使用宏定义开辟40个字节的空间
	int \*p = Malloc(10,int);
	return 0;
}

宏和函数的区别
在这里插入图片描述

命名约定

宏名全部大写 ,函数名不要全部大写

预处理指令 #undef

功能:这条指令用于移除一个宏定义。

#define MAX 100
int main()
{
	printf("%d\n",MAX);//MAX被替换成100,正常打印
	#undef MAX //取消MAX的宏定义
	printf("%d\n",MAX);//err,MAX未定义
	return 0;
}

条件编译

条件编译

  • 在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的。因为我们有条件编译指令。

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

预处理指令 #undef

功能:这条指令用于移除一个宏定义。

#define MAX 100
int main()
{
	printf("%d\n",MAX);//MAX被替换成100,正常打印
	#undef MAX //取消MAX的宏定义
	printf("%d\n",MAX);//err,MAX未定义
	return 0;
}

条件编译

条件编译

  • 在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的。因为我们有条件编译指令。

[外链图片转存中…(img-IoOATQyv-1715814820832)]
[外链图片转存中…(img-DQFc1ql7-1715814820833)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值