我们举一个例子
#define SQUARE(x) x*x
我们这样定义宏,就像函数的形式:把SQUARE(x)(x为参数)替换成x*x,我们写代码进行尝试。
#include#define SQUARE(x) x*x int main() { printf("%dn", SQUARE(5)); return 0; }
我们进行编译
得到我们对应的5的平方的结果。
接下来我们讲一下需要注意的一些事项。
参数列表要紧贴我们对应的名字
例如
#define SQUARE (x) x*x
这样写是不对的,这样写表示把SQUARE替换成(x) x*x。
第二个需要注意的事项:
不要省略括号,例如
#include#define SQUARE(x) x*x int main() { printf("%dn", SQUARE(5+1)); return 0; }
假如我们要计算SQUARE(5+1)对应的值,我们进行编译
对应的结果并不是我们期望的36,原因是什么呢?
答:实际上,这里我们计算的结果是5+1*5+1,因为乘法优先级大于加法的原因,所以我们计算的结果会出现问题,这里我们可以这样进行修正。
#define SQUARE(x) (x)*(x)
我们进行编译
打印出我们期望的36,是不是这种写法就一定对呢?不一定。
#include#define DOUBLE(x) (x)+(x) int main() { printf("%dn",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 %dn", a);
int b = 20;
printf("the value of b is %dn", b);
return 0;
}
我们进行打印
打印出我们想要的结果10和20.
我们尝试用函数实现
void print(int n)
{
printf("the value of n=%dn",n);
}
int main()
{
int a = 10;
print(a);
int b = 20;
print(b);
return 0;
}
我们进行编译
并不是我们所期望的
我们思考能不能用宏的写法写呢?
许多人会写出这样的代码
#define PRINT(N) printf("the value of "N"is %dn",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 %dn",N);
int main()
{
int a = 10;
PRINT(a);
int b = 20;
PRINT(b);
return 0;
}
我们进行编译:
是我们所期望的结果。
这里的#N的意思就相当于再给N加一个双引号,不同的点在于这个n是参数。
printf("the value of ""N"" is %dn",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("%dn", 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("%dn", m);
printf("a=%d b=%dn", 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
我们可以发现,宏和函数有许多相同的点,那么宏和函数哪一个更好呢?
答:假如我们要求两个数的较大值。
#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("%dn", M);
//#undef M
printf("%dn", M);
return 0;
}
我们进行编译
假如我们使用#undef M
#define M 100
int main()
{
printf("%dn", M);
#undef M
printf("%dn", M);
return 0;
}
我们进行编译
会报错,并提示我们未声明的标识符。
条件编译在编译一个程序的时候,我们可以决定一条语句(一组语句)编译或者放弃。
#ifdef __DEBUG__和#endif
#include#define __DEBUG__ int main() { int i = 0; int arr[10] = { 0 }; for (i = 0; i < 10; i++) { arr[i] = i; #ifdef __DEBUG__ printf("%dn", 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("hehen");
#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("hehen");
#elif M==5
printf("hahan");
#else
printf("hehein");
#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
查找策略:直接去库目录下查找。
#include
先去代码所在的路径下去查找。
再去库目录下去查找。
所以,#include"stdio.h"
这种写法也是可以的,无非就是耗费多一些的时间。
接下来,我们将一道面试题目
macro表示offsetof是一个宏,参数是一个类型,一个成员,返回值是返回成员的偏移量。
我们写一串代码进行解析
#includestruct S { char c1; int i; char c2; }; int main() { struct S s = { 0 }; printf("%dn", offsetof(struct S, c1)); printf("%dn", offsetof(struct S, i)); printf("%dn", offsetof(struct S, c2)); return 0; }
创建一个结构体,分析每一个成员变量的偏移量。
我们进行绘图
我们进行分析,c1的对齐数是1,i的对齐数是4,c2的对齐数是1,最大对齐数是4
由图像可知:c1的偏移量为0,i的偏移量为4,c2的偏移量为8
假如对应的0的位置为0地址,我们可以用地址的差值来求偏移量。
由此我们可以自定义宏来实现offsetof
#include#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("%dn", OFFSETOF(struct S, c1)); printf("%dn", OFFSETOF(struct S, i)); printf("%dn", OFFSETOF(struct S, c2)); return 0; }
我们对自定义的宏进行解析:(struct S*)0表示把0强制类型转化为结构体指针,然后指向对应的成员变量,取出成员变量的地址,将其强制类型转化为无符号整型,因为我们的起始地址是0,所以我们求出的无符号整数就是对应的偏移量。
接下来,讲几道题目
描述
KiKi学习了循环,BoBo老师给他出了一系列打印图案的练习,该任务是打印用“*”组成的箭形图案。
输入描述:
本题多组输入,每行一个整数(2~20)。
输出描述:
针对每行输入,输出用“*”组成的箭形。
示例1
输入:
2
复制
输出:
*
**
***
**
*
复制
示例2
输入:
3
复制
输出:
*
**
***
****
***
**
*
复制
示例3
输入:
4
复制
输出:
*
**
***
****
*****
****
***
**
*
这道题我们这样写
#includeint main() { int a = 0; while (scanf("%d", &a) == 1) { int i = 0; int j = 0; for (i = 0; i
下一道题目:
描述 公务员面试现场打分。有7位考官,从键盘输入若干组成绩,每组7个分数(百分制),去掉一个最高分和一个最低分,输出每组的平均成绩。 (注:本题有多组输入) 输入描述: 每一行,输入7个整数(0~100),代表7个成绩,用空格分隔。 输出描述: 每一行,输出去掉最高分和最低分的平均成绩,小数点后保留2位,每行输出后换行。 示例1 输入: 99 45 78 67 72 88 60 复制 输出: 73.00
答案
#includeint 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



