首页 > 其他分享 >【零基础C语言】预处理解析

【零基础C语言】预处理解析

时间:2024-03-29 21:30:34浏览次数:14  
标签:__ main return int C语言 printf 解析 预处理 define

 

预定义符号

 c语言中设置一些预定的符号,我们可以直接使用

 //列:
__FILE__ //进⾏编译的源⽂件
__LINE__ //⽂件当前的⾏号
__DATE__ //⽂件被编译的⽇期
__TIME__ //⽂件被编译的时间
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义

 / 续行符号

int main()
{
	printf("file = %s\nline = %d\ndate = %s\ntime = %s\n", \
		__FILE__, __LINE__, \
		__DATE__, __TIME__);

	return 0;
}

#define 用法

 //定义常量
#define max 100
int main()
{
	printf("%d\n", max);
	return 0;
}
 //给关键字创建一个别的名称
#define i int
i main()
{
	i a = 10;
	printf("%d\n", a);
	return 0;
}

我们平时使用代码时结尾都是以;,那么#define后面需不需要加上呢?

#define min 1;
#define max 1
int main()
{
	printf("%d\n", min); //err
    printf("%d\n", max);
	return 0;
}

 答案是不能因为在预处理阶段,编译器会删除所有的#define,并且替换定义的变量
 拿上面的代码举例
 下面是预处理替换后的代码

int main()
{
	printf("%d\n", 1;);
	printf("%d\n", 1);
	return 0;
}

#define 定义宏 - 可以传入参数

 //比如: 
#define add(x) x * x
int main()
{
	printf("%d\n", add(2 + 1));
  return 0;
}

这段代码会输出多少?有木有小伙伴算的是9,毕竟3*3是9,那么这样就错了
下面我们一起来看看

首先我们前面了解到#define的内容会直接被替换
替换后

int main()
{
	printf("%d\n", 2 + 1 * 2 + 1);
	return 0;
}
// 根据优先级, 2+(1*2)+1 == 5
// 这种直接的替换往往也会带来一些小麻烦,要想解决也很容易,只要勤加()就行了
#define add(x) (x)*(x)
int main()
{
	printf("%d\n", add(2 + 1));
	return 0;
}
#endif
 //这样就不用担心优先级带来的麻烦了

 //这样就真的改好了吗,我们来看下面一段代码
#define add(x) (x)+(x)
int main()
{
	printf("%d\n", 10 * add(5)); //我们想要的结果是100
	return 0;
}

// 输出的确实55,这还是犯了与上面同样的问题,我们的()加少了
// 最好的方案是在外面一层也加上

#define add(x) ((x)+(x))
int main()
{
	printf("%d\n", 10 * add(5));
	return 0;
}

 带有副作用的宏参数

 举例:

#define MAX(a, b) ((a) > (b) ? (a) : (b))
int main()
{
	int a = 3;
	int b = 10;
	int z = MAX(a++, b++);
	printf("%d\n %d\n %d\n", a, b, z);
	return 0;
}

 //由上面的函数我们来进行替换
int main()
{
	int a = 3;
	int b = 10;
	int z = ((a++) > (b++) ? (a++) : (b++));
	// 先执行(a++)>(b++)  -  a=4, b=11;
	// 因为a<b, 所以执行表达式2(b++) - a=4,z=11,b=12
	printf("%d\n%d\n%d\n", a, z, b);
	return 0;
}
 //这就是宏参数的副作用,如果换成函数又如何呢?
int fution(int a, int b)
{
	return a > b ? a : b;
}

int main()
{
	int a = 3;
	int b = 10;
	int z = fution(a++, b++);
	printf("%d\n%d\n%d\n", a, z, b);
	return 0;
}
 //我们可以看见z的结果是10,而a,b的结果分别加了1,函数在传参后函数中a与b不再改变。

宏替换的规则

  • 1.在调⽤宏时,⾸先对参数进⾏检查,看看是否包含任何由#define定义的符号。如果是,它们⾸先被替换。
  • 2. 替换⽂本随后被插⼊到程序中原来⽂本的位置。对于宏,参数名被他们的值所替换。
  • 3. 最后,再次对结果⽂件进⾏扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。

注意:

  • 1. 宏参数和#define定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。
  • 2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。
     

 宏函数的对比

// 宏函数常常应用于比较简单的运算

// 如:比较大小

// 宏写

#define MAX(a, b)  ((a)>(b)?(a):(b))
int main()
{
	printf("%d\n", MAX(2, 3));
	return 0;
}

// 函数写

int MAX(int a, int b)
{
	return a > b ? a : b;
}
int main()
{
	printf("%d\n", MAX(2, 3));
	return 0;
}

为什么使用宏来运算而不使用函数
1. 相比较函数的调用,直接替换的计算工作时间更快
2. 宏与类型无关,你可以比较任意类型,然而函数却需要更改返回类型

宏函数的劣势
1.使用宏的时候,就相当于插入一段定义的代码,如果宏过长会导致代码太多,不好观察
2.宏无法调试
3.宏可能会带来优先级的问题,与类型也无关,不够严谨

 #和##

 #运算符

 //#运算符
// #运算符所执行的操作可以将宏中的一个参数转换为字符串字面量

//如:我们有一个变量 int a = 20;
//我们想要使用宏然后打印出 the num of a is 20.

#define PRINT(n) printf("the num of "#n " is %d",n);
int main()
{
	int a = 20;
	PRINT(a);
	return 0;
}

当我们按照下⾯的⽅式调⽤的时候:
PRINT(a);//当我们把a替换到宏的体内时,就出现了#a,⽽#a就是转换为"a",这时⼀个字符串
代码就会被预处理为:
printf("the num of "a " is %d",a);

 ##运算符

#define FUNTION_MAX(type) \
type type##_max(type a, tpye b)\
{                              \
      return (a > b ? a : b); \
}

// 这样使用后我们可以传入不同类型的参数了

 命名约定

 我们一般宏的使用命名时都全部大写,对于函数的应用却不全部大写

#undef

这条命令可以移除一个宏定义 

#define MAX 100
int main()
{
	printf("%d\n", MAX);
#undef MAX
	printf("%d\n", MAX); //err
	return 0;
}

条件编译
有时我们需要用一段代码,但用后后面可能还要使用,我们希望保留,并且设定一个开关,这就是条件编译

#define __DEBUG__
int main()
{
	int i = 0;
	int arr[10] = { 0 };
	for (i = 0; i < 10; i++)
	{
		arr[i] = i + 1;
#ifdef __DEBUG__
		printf("%d ", arr[i]);
#endif
	}
	return 0;
}
// 这样之后我们只有在前面定义 __DEBUG__ 我们才可以使用中间的代码

标签:__,main,return,int,C语言,printf,解析,预处理,define
From: https://blog.csdn.net/2301_81577798/article/details/137156522

相关文章

  • 【零基础C语言】动态内存管理
     目录1.动态内存分配的意义 2.malloc函数 3.free函数 4.calloc函数 5.realloc函数 6.常见的动态内存错误 6.1对NULL指针的解引⽤操作6.2对动态开辟空间的越界访问6.3对非动态开辟内存使⽤free释放 6.4使⽤free释放⼀块动态开辟内存的⼀部分 6.5......
  • 2024年03月CCF-GESP编程能力等级认证C++编程八级真题解析
    本文收录于专栏《C++等级认证CCF-GESP真题解析》,专栏总目录:点这里。订阅后可阅读专栏内所有文章。一、单选题(每题2分,共30分)第1题为丰富食堂菜谱,炒菜部进行头脑风暴。肉类有鸡肉、牛肉、羊肉、猪肉4种,切法有肉排、肉块、肉末3种,配菜有圆白菜、油菜、豆腐3种,辣度有......
  • 2024年03月CCF-GESP编程能力等级认证C++编程七级真题解析
    本文收录于专栏《C++等级认证CCF-GESP真题解析》,专栏总目录:点这里。订阅后可阅读专栏内所有文章。一、单选题(每题2分,共30分)第1题下列关于排序的说法,正确的是()。A.冒泡排序是最快的排序算法之一。B.快速排序通常是不稳定的。C.最差情况,N个元素做归并排序......
  • 深入解析Java继承机制:面向对象编程的核心探究【Java面试题】
    作为一名对技术充满热情的学习者,我一直以来都深刻地体会到知识的广度和深度。在这个不断演变的数字时代,我远非专家,而是一位不断追求进步的旅行者。通过这篇博客,我想分享我在某个领域的学习经验,与大家共同探讨、共同成长。请大家以开放的心态阅读,相信你们也会在这段知识之......
  • 用C语言实现汉诺塔游戏
    汉诺塔游戏。游戏目标是将A柱子上的盘子移动到C柱子上,且每次小的盘子要放在大的盘子上面。如只有一个盘子则直接移至C柱子。以如图所示为例子。3个盘子要移动至C柱子,具体步骤为:A到C,A到B,C到B,A到C,B到A,B到C,A到C。总共7步。也就是先把A柱子两个盘子(n-1)通过C柱子移......
  • 简单介绍c语言程序的编译与链接
    程序运行的背后程序在运行时经历了四个步骤,分别是预编译(Prepressing)、编译(Compilation)、汇编(Assembly)和链接(Linking)。预编译预编译也称预处理,源代码文件(.c)和相关的头文件(.h)等被预编译器cpp预编译成一个.i文件。编译编译过程就是将预处理后的文件进行一系列......
  • C语言 结构体和联合体、解释C语言中的结构体及其用途。
    一、结构体和联合体结构体和联合体在C语言中都是重要的数据类型,但它们在使用和特性上有所区别。结构体是由一批数据组合而成的结构型数据,由一组称为成员(或称为域,或称为元素)的不同数据组成,其中每个成员可以具有不同的类型。结构体中的每个数据成员都占用自己的内存空间,结构体......
  • 描述C语言中的循环结构(for,while,do-while)
    一、描述C语言中的循环结构(for,while,do-while)C语言提供了三种主要的循环结构:for循环、while循环和do-while循环。这些循环结构允许你重复执行一段代码,直到满足特定的条件为止。for循环for循环通常用于已知循环次数的场景。它的基本语法如下:c复制代码for(初始化;条件;更......
  • C语言之编译链接
    目录一、翻译环境和运行环境二、预编译1.预定义符号2.#define3.头文件的包含三、翻译环境1.预编译2.编译3.汇编4.链接一、翻译环境和运行环境在ANSIC的任何⼀种实现中,存在两个不同的环境第一种是翻译环境,在这个环境中,源代码被转换为可执行的二进制指令。翻译环......
  • 【拯救大学牲】人民邮电出版社C语言程序设计第四章编程题答案
     注:本文所有程序均为笔者自己编写,仅供交流学习使用,欢迎一切纠错与指正。目录1.根据x的值,计算y。2.输入4个整数,从小到大排序。3.求解一元二次方程的实根4.输入一个整数,判断它能否被3和5整除,并输出一下信息之一:5.输入整存整取金额及存期,计算出利息(不计利息税)。利息=金额×......