目录
#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;
}