2022-07-18-106期-预处理(2)

目录

#define定义宏

#define的替换规则

#和##

##

带有副作用的宏参数。

命名约定

#undef

条件编译

文件包含:

头文件中,<>和""的区别。


#define定义宏

 我们举一个例子

#define SQUARE(x) x*x

我们这样定义宏,就像函数的形式:把SQUARE(x)(x为参数)替换成x*x,我们写代码进行尝试。

#include<stdio.h>
#define SQUARE(x) x*x
int main()
{
	printf("%d\n", SQUARE(5));
	return 0;
}

我们进行编译

 得到我们对应的5的平方的结果。

接下来我们讲一下需要注意的一些事项。

参数列表要紧贴我们对应的名字

例如

#define SQUARE (x) x*x

这样写是不对的,这样写表示把SQUARE替换成(x) x*x。

第二个需要注意的事项:

不要省略括号,例如

#include<stdio.h>
#define SQUARE(x) x*x
int main()
{
	printf("%d\n", SQUARE(5+1));
	return 0;
}

假如我们要计算SQUARE(5+1)对应的值,我们进行编译

 对应的结果并不是我们期望的36,原因是什么呢?

答:实际上,这里我们计算的结果是5+1*5+1,因为乘法优先级大于加法的原因,所以我们计算的结果会出现问题,这里我们可以这样进行修正。

#define SQUARE(x) (x)*(x)

我们进行编译

打印出我们期望的36,是不是这种写法就一定对呢?不一定。

#include<stdio.h>
#define DOUBLE(x) (x)+(x)
int main()
{
	printf("%d\n",10* DOUBLE(3));
	return 0;
}

我们写成加法的形式,DOUBLE(x)表示x+x的值,我们想要求出的结果是60,我们进行编译

得出的结果是33,原因是什么呢? 

我们实际计算的是:10*3+3=33,也是因为优先级的问题而出错。

综上所述,最好的写法是这样,完全避免了因为优先级而出现的问题

#define DOUBLE(x) ((x)+(x))

#define的替换规则

 第一条,很好解释:

#define x 3
#define ADD(x) ((x)+(x))
int main()
{
	int a = ADD(x + 2);
	return 0;
}

 我们这里的第二个宏有其他#define定义的符号x,我们在进行调用的时候,发生替换

替换的结果就是

	int a = ADD(3 + 2);

第二条是这样:

再发生替换,就是这样进行计算,计算的ADD就是

((5) + (5))

第三条就是重复以上步骤。

注意:宏是不能自己调用自己形成递归的

字符串中的内容假如有宏是不会产生替换的。例如

#define ADD(x) ((x)+(x))
int main()
{
	
	printf("ADD(2)");
	return 0;
}

我们进行编译

当预处理器搜索#define定义的符号,字符串常量的内容并不被搜索。

#和##

我们先写一串代码引入

int main()
{
	int a = 10;
	printf("the value of a is %d\n", a);
	int b = 20;
	printf("the value of b is %d\n", b);
	return 0;
}

我们进行打印

 打印出我们想要的结果10和20.

我们尝试用函数实现

void print(int n)
{
	printf("the value of n=%d\n",n);
}
int main()
{
	int a = 10;
	print(a);
	int b = 20;
	print(b);
	return 0;
}

我们进行编译

 并不是我们所期望的

我们思考能不能用宏的写法写呢?

许多人会写出这样的代码

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

我们这里在N的前面加上",在N的后面加上一个”,表示形成两个字符串,N被隔开,想法是对的,但是这里的结构的写法就是错误的。

这时候,我们引入#,#能够让参数插入到字符串。

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

我们进行编译:

是我们所期望的结果。

这里的#N的意思就相当于再给N加一个双引号,不同的点在于这个n是参数。

 printf("the value of ""N"" is %d\n",N);

由三个字符串组成一个字符串。

总结:#可以把参数插入到字符串。

接下来,我们分析打印对于不同类型的参数的值又该怎样写呢?

#define PRINT(N,FORMAT) printf("the value of "#N" is "FORMAT"\n",N)
int main()
{
	int a = 10;
	PRINT(a, "%d");
	float f = 3.14f;
	PRINT(f, "%lf");
	return 0;

}

我们进行编译

 打印结果成功,我们的第一个参数是需要插入到字符串中的,所以加#。

因为FORMAT本身就是字符串,我们需要加上""表示他是字符串。

##

##能够起到合并参数的作用。

#define CAT(Class,Num) Class##NUM
int main()
{
	int Class106 = 100;
	printf("%d\n", CAT(Class, 106));
	return 0;
}

Class##Num表示把Class和Num合成同一个参数,所以我们print打印CAT(Class,106)对应的结果其实是Class 106,所以对应的结果就是100.

带有副作用的宏参数。

副作用:顾名思义,既有好的作用,也有坏的作用,所以有两种作用。

我们举一个例子。

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

int main()
{
	int a = 5;
	int b = 4;
	int m = MAX(a++, b++);
	printf("%d\n", m);
	printf("a=%d b=%d\n", a, b);
	return 0;
}

这段代码打印的结果是多少?

答:6 7 5

我们来解释原因

int m = MAX(a++, b++);

由于我们对MAX(a,b)的宏定义,所以MAX(a,b)就等价于

((a++)>(b++) ? (a++) : (b++))

相当于计算这个三目表达式

分析:首先,我们判断,a大于b,然后a+1,b+1,对应的结果为6,5   。对应的结果为(a++),所以m对应的值为6,然后a+1,a对应的值为7,因为是三木表达式满足第一个条件,最后一个条件就不再计算,所以b的结果为5,对应结果 6 7 5.

这里就导致a计算了两次,b计算了一次,假如a<b的话,就是a计算了1次,b计算了两次了

我们可以发现,宏和函数有许多相同的点,那么宏和函数哪一个更好呢?

答:假如我们要求两个数的较大值。

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

int MAX(int a, int b)
{
	return (a > b ? a : b);
}

我们用宏更好,原因有以下两点

1:类型,宏与类型无关,我们既可以计算浮点型,也可以计算整型,而函数没有这种优点。

2:速度,在这种情境下,宏能更快的求出结果。

但是宏也是有缺点的

1:宏代码比较长时,多次调用会大幅度增加程序的长度。

2:宏是没办法调试的。

3:宏由于类型无关,也就不够严谨。

4:宏可能会带来优先级的问题,导致程序容易出错。

我们最后将一个宏有但函数绝对没有的优点

假如我们要进行动态内存开辟40个字节,并且开辟的40个字节都是整型,我们要怎么写?

int*p=(int*)malloc(40);

我们思考能不能有一种更好的写法

MALLOC(10, int);

这里表示我们开辟10个空间,每一个空间都是整型。

我们可以用宏实现

#define MALLOC(num,type) (type*)malloc(num*sizeof(type))

int main()
{
	int *p = MALLOC(10, int);

	return 0;
}

 这种方法很好。

总结:假如代码非常简单,就用宏来写

假如代码容易出错,就用函数来写。

命名约定

简单的说:把宏名全部大写

函数名不要全部大写。

但是有例外,例如

offsetof是宏定义

getchar在某些部分也可以被当作宏定义。

#undef

这条指令用于移除一个宏定义

我们进行尝试

#define M 100

int main()
{
	printf("%d\n", M);
//#undef M
	printf("%d\n", M);
	return 0;
}

我们进行编译

 假如我们使用#undef M

#define M 100

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

我们进行编译

会报错,并提示我们未声明的标识符。

条件编译

在编译一个程序的时候,我们可以决定一条语句(一组语句)编译或者放弃。

#ifdef __DEBUG__和#endif

#include<stdio.h>
#define __DEBUG__
int main()
{
	int i = 0;
	int arr[10] = { 0 };
	for (i = 0; i < 10; i++)
	{
		arr[i] = i;
#ifdef __DEBUG__
		printf("%d\n", arr[i]);
#endif
	}
	return 0;
}

这条语句是这样的,我们创建一个变量,创建一个数组,对该数组的每一个进行赋值,然后我们可以选择打印这个函数(这个语句只是用来检测我们是否赋值成功的),但我们真正想要做的并不是打印而是对数组赋值。那我们该如何放弃printf语句呢?

答:很简单,因为我们在前面就对__DEBUG__进行了定义,我们在printf前面的语句#ifdef __DEBUG__是用来判断是否定义的,假如定义的话,我们再打印#ifdef __DEBUG__和#endif之间的语句,假如没有定义,我们就放弃该部分语句。

所以想要放弃printf语句,我们只需要注释掉#define __DEBUG__即可。

常见的条件编译指令:

#if

#endif

:假如if语句为真,执行if到endif之间的语句,假如为假,放弃该部分语句。

例如

int main()
{
#if 1
	printf("hehe\n");
#endif
	return 0;
}

进行编译

 假如我们想要放弃该printf语句,把1置为0即可。

我们也可以判断表达式:

 灰色的代码代表我们已经放弃。

还有的的条件编译

#define __DEBUG__ 1

#if __DEBUG__

#endif

多分支的条件编译

#if

#elif

#else

#endif

我们举一个例子

#define M 3
int main()
{
#if M<5
	printf("hehe\n");
#elif M==5
	printf("haha\n");
#else
	printf("hehei\n");
#endif
	return 0;
}

这里的M=3,所以第二第三个语句都不满足,所以都放弃,打印的结果是hehe

注意:#defined和#!defined是一个反面。

#ifdef和#ifndef是一个反面。

#if defined()和ifdef 是等价的。

文件包含:

当我们的头文件被多次包含时,我们对应的函数的声明也会多次出现。

当头文件代码量较大时,被重复包含会导致代码量过于冗余,有什么方法能解决这个问题呢?

#ifndef __TEST_H__
#define __TEST_H__

int Add(int x, int y);
#endif

1:我们进行解析“首先,我们判断是否定义__TEST_H__,没有定义的话,#ifndef到#endif的语句是有效的。

然后我们定义__TEST_H__,因为我们的#ifndef __TEST_H__已经判定过了,所以不会影响。

然后进入我们函数的创建,结束。

假如再进入对应的头文件时,我们再进行判断是否定义__TEST_H__,定义过了,所以放弃#ifnedf到#endif之间的语句。所以就不会导致头文件的多次包含了。

第二种写法。

#pragma once
int Add(int x, int y);

这里#pragma once表示我们的头文件只能包含一次。

头文件中,<>和""的区别。

例如

#include<stdio.h>

查找策略:直接去库目录下查找。

#include<test.h>

先去代码所在的路径下去查找。

再去库目录下去查找。

所以,#include"stdio.h"

这种写法也是可以的,无非就是耗费多一些的时间。

接下来,我们将一道面试题目

 

macro表示offsetof是一个宏,参数是一个类型,一个成员,返回值是返回成员的偏移量。

我们写一串代码进行解析

#include<stddef.h>
struct S
{
	char c1;
	int i;
	char c2;
};
int main()
{
	struct S s = { 0 };
	printf("%d\n", offsetof(struct S, c1));
	printf("%d\n", offsetof(struct S, i));
	printf("%d\n", offsetof(struct S, c2));
	return 0;
}

 创建一个结构体,分析每一个成员变量的偏移量。

我们进行绘图

 我们进行分析,c1的对齐数是1,i的对齐数是4,c2的对齐数是1,最大对齐数是4

 由图像可知:c1的偏移量为0,i的偏移量为4,c2的偏移量为8

假如对应的0的位置为0地址,我们可以用地址的差值来求偏移量。

 由此我们可以自定义宏来实现offsetof

#include<stddef.h>
#define OFFSETOF(type,m_name) (size_t)&((struct S*)0)->m_name
struct S
{
	char c1;
	int i;
	char c2;
};
int main()
{
	struct S s = { 0 };
	printf("%d\n", OFFSETOF(struct S, c1));
	printf("%d\n", OFFSETOF(struct S, i));
	printf("%d\n", OFFSETOF(struct S, c2));
	return 0;
}

我们对自定义的宏进行解析:(struct S*)0表示把0强制类型转化为结构体指针,然后指向对应的成员变量,取出成员变量的地址,将其强制类型转化为无符号整型,因为我们的起始地址是0,所以我们求出的无符号整数就是对应的偏移量。

接下来,讲几道题目

描述
KiKi学习了循环,BoBo老师给他出了一系列打印图案的练习,该任务是打印用“*”组成的箭形图案。
输入描述:
本题多组输入,每行一个整数(2~20)。
输出描述:
针对每行输入,输出用“*”组成的箭形。

示例1
输入:
2
复制
输出:
    *
  **
***
  **
    *
复制
示例2
输入:
3
复制
输出:
      *
    **
  ***
****
  ***
    **
      *
复制
示例3
输入:
4
复制
输出:
        *
      **
    ***
  ****
*****
  ****
    ***
      **
        *

这道题我们这样写

#include<stdio.h>
int main()
{
	int a = 0;
	while (scanf("%d", &a) == 1)
	{
		int i = 0;
		int j = 0;
		for (i = 0; i<a; i++)
		{
			for (j = 0; j<a - i; j++)
			{
				printf("  ");
			}
			for (j = 0; j<i + 1; j++)
			{
				printf("*");
			}
			printf("\n");
		}
		for (i = 0; i<a + 1; i++)
		{
			printf("*");
		}
		printf("\n");
		for (i = 0; i<a; i++)
		{
			for (j = 0; j<i + 1; j++)
			{
				printf("  ");
			}
			for (j = 0; j<a - i; j++)
			{
				printf("*");
			}
			printf("\n");
		}
	}
	return 0;
}

下一道题目:

描述
公务员面试现场打分。有7位考官,从键盘输入若干组成绩,每组7个分数(百分制),去掉一个最高分和一个最低分,输出每组的平均成绩。
(注:本题有多组输入)
输入描述:
每一行,输入7个整数(0~100),代表7个成绩,用空格分隔。
输出描述:
每一行,输出去掉最高分和最低分的平均成绩,小数点后保留2位,每行输出后换行。
示例1
输入:
99 45 78 67 72 88 60
复制
输出:
73.00

答案

#include<stdio.h>
int main()
{
    int n=0;
    int sum=0;
    int score=0;
    int max=0;
    int min=100;
    while(scanf("%d",&score)==1)
    {
        n++;
        if(score>max)
        {
            max=score;
        }
        if(score<min)
        {
            min=score;
        }
        sum+=score;
        if(n==7)
        {
            printf("%.2lf\n",(sum-max-min)/5.0);
            n=0;
            sum=0;
            max=0;
            min=100;
        }
    }
    return 0;
}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值