一、宏
宏
是根据一系列预定义的规则替换一定的文本模式,编译器在遇到宏时会自动进行这一模式替换。
1、宏的定义与使用
#define 宏名称 要替换的代码
使用的时直接使用宏的名称即可(类似变量的使用),在编译时首先会将宏名称替换为对应的代码。
例:
#define MY 666
printf("%d", MY);
以上代码在编译时,会被替换为:
printf("%d", 666);
-
宏名称的命名规则与变量的命名规则相同
-
宏定义通常被用来定义常量,如:
#define PI 3.14159
注意:宏定义的后面没有 “;”
2、宏函数
宏函数与普通函数类似。
例:
#define add(a, b) a+b
int d = 4;
int e = 5;
printf("%d", add(d, e));
以上代码在编译时,会被替换为:
#define add(a, b) a+b
int d = 4;
int e = 5;
printf("%d", d + e);
| 宏函数带来的问题 |
观察下面的代码,猜猜它会输出什么?
#include <stdio.h>
#define mul(a, b) a*b
void main()
{
int d = 2;
int e = 3;
int f = mul(d+e, d+e);
printf("%d", f);
}
可能会出乎你的意料,它的输出结果是 11 ,而不是25,这是为什么呢?
因为在编译时,代码首先会被替换为:
#include <stdio.h>
void main()
{
int d = 2;
int e = 3;
int f = d+e * d+e;
printf("%d", f);
}
那么,我们应该怎么避免这种情况发生呢?
正确的做法是:
在定义宏函数时,如果涉及到运算,需要为每个变量和宏函数添加括号。
例如上面的宏函数就应该改为:
#define mul(a, b) ((a)*(b))
二、多文件编程
在编程时,通常不会将所有的代码都写在一个文件里,而是把不同的功能写在不同的文件里,而主文件只需要调用对应的功能就可以了,这样做不仅可以使程序的结构更清晰,而且可以提高团队合作的效率。
1、头文件
头文件以 .h
为后缀名,其本质与 .c
文件一样,都是C语言的源文件,不过头文件通常只包含对变量和函数的定义,而不去进行实现。
如:
新建一个文件:library.h
#define PI 3.14159
float getCircleArea(float r);
2、include语句
a、基本格式
#include <文件名>
或
#include “文件名”
| 使用双引号与尖括号的区别|
- 尖括号:直接在系统中寻找该文件
- 双引号:先在当前目录中寻找对应文件,如果不存在则到系统中寻找
b、含义
与宏类似,在编译时,计算机会将include语句中的文件内容复制到当前文件。
例如:
新建一个文件:my.c(与 library.h 在同一文件夹下)
#include "lib.h"
int main()
{
......代码......
}
在编译时,会将 include 语句进行展开,所以 my.c 文件会变成:
#define PI 3.14159
float getCircleArea(float r);
int main()
{
......代码......
}
3、多文件编程
通常,我们在编写一个C语言程序时,会将各种功能封装成不同的函数放在对应的文件中,然后在mian.c
中调用其他功能。
示例:
mian.c
#include <stdio.h>
#include "lib.h"
void sayHello();
float getInputFloat();
void mPrint(char*, float);
void main()
{
sayHello();
printf("请输入圆的半径:");
float f = getInputFloat();
mPrint("直径", getCircleDiam(f));
mPrint("周长", getCircleLength(f));
mPrint("面积", getCircleArea(f));
}
void sayHello()
{
printf("输入圆的半径,可以获得圆的直径、周长和面积。\n");
printf("注意:默认保留两位小数\n\n");
}
float getInputFloat()
{
float f;
scanf("%f", &f);
return f;
}
void mPrint(char *c, float f)
{
printf("圆的%s是:%.2f\n", c, f);
}
lib.h
#define PI 3.14159
// 计算圆的直径
float getCircleDiam(float r);
// 计算圆的面积
float getCircleArea(float r);
// 计算圆的周长
float getCircleLength(float r);
lib.c
#include "lib.h"
float getCircleDiam(float r)
{
return 2 * r;
}
float getCircleArea(float r)
{
return PI * r * r;
}
float getCircleLength(float r)
{
return 2 * PI * r;
}
运行结果:
三、条件编译
如果我们新建如下的三个文件
// a.h
int test = 666;
// b.h
#include "a.h"
// c.h
#include "a.h"
// mian.c
#include "a.h"
#include "c.h"
void mian()
{
}
点击运行,就会出现重复定义
的错误:
为了防止这种情况发生,引入了条件编译语句。
1、条件编译语句基础
条件编译语句与if-else语句类似,不过它和if-else语句也有一些区别:
-
条件编译语句可以写在文件中的任何位置,而if-else语句只能写在函数体中。
-
条件编译语句是在编译时确定的,不符合条件的不会被编译到程序中,而if-else语句是在运行时确定的,无论是否符合条件,都会被编译到程序中。
-
条件编译语句只能判断宏是否符合条件,if-else语句可以判断宏或变量是否符合条件
2、条件编译语句的语法
条件编译语句必须以条件编译判断语句
开头,以#endif
结尾。
条件编译判断语句:
语句 | 意义 |
---|---|
#if | 如果符合条件,则执行下列代码 |
#ifdef | 如果宏已定义,则执行下列代码 |
#ifndef | 如果宏未定义,则执行下列代码 |
#else语句
#else
语句必须放在条件编译判断语句之后,类似普通的else语句,如果条件编译判断语句不成立,则执行下列代码。
#elif
类似普通的else if语句。
示例:
#include <stdio.h>
#define NOW morning
#if NOW == morning
#define HELLO "早上好"
#elif NOW == night
#define HELLO "晚上好"
#else
#define HELLO "你好"
#endif
void main()
{
printf(HELLO);
}
注意:
不能出现#else-#if
语句,必须用
#elif
代替。
// 这样写是不被允许的
#if A > 0
...
#else
#if A == 0
...
#else
...
// 必须这样写
#if A > 0
...
#elif A == 0
...
#else
...
3、应用
条件编译语句常用于解决上面提到的重复定义和重复包含问题。
上面代码的修改版:
// a.h
// LIB_A 是一个自定义的名称,尽量取得复杂一些,防止重复
#ifndef LIB_A
#define LIB_A
int test = 666;
#endif
// b.h
#include "a.h"
// c.h
#include "a.h"
// mian.c
#include "a.h"
#include "c.h"
void mian()
{
}
这样,就不会再出现重复定义和重复包含的错误。
在进行多文件编程时,一定要养成使用条件编译语句的习惯。
再见( ̄︶ ̄)↗
【C语言从零到入门】系列到此正式结束。