C语法Day5-预处理
# 头文件引用
# 说明
1、函数声明和宏定义单独放在.h文件中,供其他文件 #include 引用
2、编译的过程中,会将 .h 文件内容直接 复制一份 到被引用的文件中供使用,就是这么简单粗暴
3、include : 编译预处理指令
# 引用
使用预处理指令 #include 可以引用用户和系统头文件。它的形式有以下两种:
// 使用尖括号引用头文件,一般引用系统提供的文件
// 编译时优先搜索系统目录的标准列表
#include <file>
// 使用引号引入文件,一般指用户自定义文件
// 编译时优先搜索当前文件的目录
#include "file"
# 头文件编译
Animal.h 文件:
char *eat(void);
主程序文件:main.c
// 引用头文件
#include "Animal.h"
int main (void)
{
puts (eat());
}
编译器编译过程:main.c
// 复制 Animal.h 中的内容到当前文件,并替换掉 #include "Animal.h"
char *eat(void);
int main (void)
{
puts (eat());
}
# 预处理器
# 说明
C 预处理器:不是编译器的组成部分,但是它是编译过程中一个单独的步骤。
⚠️ 简单理解是,可以通过字符串形式编写或者拼接代码,在编译之前,会通过替换工具将其替换成真正的代码块,并插入到对应的位置,完成之后,后面编译才会检查其语法。
所有的预处理器命令都是以井号(#)开头。它必须是第一个非空字符,为了增强可读性,预处理器指令应从第一列开始。下面列出了所有重要的预处理器指令:
指令 | 描述 |
---|---|
#define | 定义宏 |
#include | 包含一个源代码文件 |
#undef | 取消已定义的宏 |
#ifdef | 如果宏已经定义,则返回真 |
#ifndef | 如果宏没有定义,则返回真 |
#if | 如果给定条件为真,则编译下面代码 |
#else | #if 的替代方案 |
#elif | 如果前面的 #if 给定条件不为真,当前条件为真,则编译下面代码 |
#endif | 结束一个 #if……#else 条件编译块 |
#error | 当遇到标准错误时,输出错误消息 |
#pragma | 使用标准化方法,向编译器发布特殊的命令到编译器中 |
# 避免重复引用
头文件引用在编译时直接复制替换,如果重复引用到话会导致重复copy :
#ifndef HEADER_FILE
#define HEADER_FILE
头文件代码,宏定义,include 等等
#endif
ifndef :判断是否定义过的标记,定义过,避免重复
格式:
# #ifdef 和 #ifndef 命令
上面代码可以发现,我们可以通过 #ifdef 和 #ifndef 命令测试某个宏是否已被定义。它们的语法是:
// 假如已经定义过
#ifdef 标识符
// 假如没有经定义过
#ifndef 标识符
// if结束指令
#endif
这等同于下面的 #if 命令:
#if defined 标识符
// 和
#if !defined 标识符
#endif
// 使用:
#if defined( __unix__ ) && defined( __GNUC__ )
/* ... */
#endif
# 条件编译
#if 表达式1
[ 组1]
[#elif 表达式2
[ 组2]]
...
[#elif 表达式n
[ 组n ]]
[#else
[ 组n+1 ]]
#endif
通过条件判断引用文件:
#define SYSTEM_N 10.3
// 使用
#if (SYSTEM_N <= 10.5)
#include "system_10.h"
#elif (SYSTEM_N <= 15)
#include "system_15.h"
#else
...
#endif
或者:
#define SYSTEM_N "system_15.h"
...
// 使用(这种方式更简洁)
#ifdef SYSTEM_N
#include SYSTEM_H
#endif
# 预定义宏
ANSI C 定义了许多宏(不可修改),可以直接拿来使用即可
宏 | 描述 |
---|---|
DATE | 当前日期,一个以 "MMM DD YYYY" 格式表示的字符常量。 |
TIME | 当前时间,一个以 "HH:MM:SS" 格式表示的字符常量。 |
FILE | 这会包含当前文件名,一个字符串常量。 |
LINE | 这会包含当前行号,一个十进制常量。 |
STDC | 当编译器以 ANSI 标准编译时,则定义为 1。 |
# 预处理器运算符
C 预处理器提供了下列的运算符来帮助您创建宏:
# 1、宏使用-代码块
例如:求和
#include <stdio.h>
// 求和
void plus(int a, int b)
{
int total = a + b;
printf("%d + %d = %d", a, b, a + b);
}
int main(void)
{
int num_a = 1, num_b = 2;
plus(num_a, num_b);
return 0;
}
运行结果: 1 +2 = 3
# 宏延续运算符(\)
多行内容换行使用:一个宏通常写在一个单行上。但是如果宏太长,一个单行容纳不下,则使用宏延续运算符(\)。
现在,我准备将上面plus代码直接使用宏来完成
#include <stdio.h>
// 求和
#define plus(a, b) \
int total = a + b; \
printf("%d + %d = %d", a, b, a + b);
int main(void)
{
int num_a = 1, num_b = 2;
plus(num_a, num_b);
return 0;
}
运行结果: 1 +2 = 3
结果和直接使用函数一样。
# 2、字符串常量化运算符(#)
将上述宏改如下:
#include <stdio.h>
// 加了#号,不管a,b传入的变量是否声明,都只获取变量名称的字符串
#define plus(a, b) \
printf(#a " and " #b)
int main(void)
{
int a = 1;
plus(num_a, num_b); // num_b 未声明
return 0;
}
运行结果: num_a and num_b
不是打印变量值,而是变量名称,甚至未定义的变量都可以。
# 3、标记粘贴运算符(##)
宏定义内的标记粘贴运算符(##)会合并两个参数。它允许在宏定义中两个独立的标记被合并为一个标记。例如:
# 实例
#include <stdio.h>
#define plus(a, b) printf (#a #b " = %d", a##b)
int main(void)
{
int ab = 88;
plus(a, b);
return 0;
}
结果:
ab = 88
其中:
#a :表示第一个参数变量名称
#b :表示第二个参数变量名称
a##b :表示a和b拼接成 ab 的值 88
# 参数化的宏
CPP 一个强大的功能是可以使用参数化的宏来模拟函数。例如,下面的代码是计算一个数的平方:
int square(int x) {
return x * x;
}
我们可以使用宏重写上面的代码,如下:
#define square(x) ((x) * (x))
在使用带有参数的宏之前,必须使用 #define 指令定义。参数列表是括在圆括号内,且必须紧跟在宏名称的后边。宏名称和左圆括号之间不允许有空格。例如:
# 实例
#include <stdio.h>
#define MAX(x,y) ((x) > (y) ? (x) : (y))
int main(void)
{
printf("最大值是 %d\n", MAX(80, 20));
return 0;
}
结果:
最大值是 80
# 别名
别名可以给类型取一个缩写或者自己更好记忆的名称,比如:
# 1、未使用别名的结构体:
#include <stdio.h>
#include <string.h>
struct animals
{
char name[50];
int age;
};
int main( )
{
struct animals bird;
strcpy(bird.name, "小鸟");
bird.age = 2;
printf( "bird name : %s\n", bird.name);
printf( "bird age : %d\n", bird.age);
return 0;
}
结果:
bird name : 小鸟
bird age : 2
# 2、使用别名的结构体:
#include <stdio.h>
#include <string.h>
typedef struct animals
{
char name[50];
int age;
} People;
int main( )
{
// 这个写法不需要写 struct,妥妥的的面向对象的概念了
People peo;
strcpy(peo.name, "小明");
peo.age = 15;
printf( "peo name : %s\n", peo.name);
printf( "peo age : %d\n", peo.age);
return 0;
}
结果:
peo name : 小明
peo age : 15
其他例子比如 字节:
typedef unsigned char BYTE;
typedef 仅限于为类型定义符号名称
#define 不仅可以为类型定义别名,也能为数值定义别名,比如您可以定义 1 为 ONE。
typedef 是由编译器执行解释的,#define 语句是由预编译器进行处理的。