文章目录
1. 预处理
1.1 概述(了解)
C 语言程序在编译过程中,完成的操作,可以将代码中需要进行预处理的内容,例如 :宏,头文件引入,条件编译……在预处理阶段加载完毕
gcc -E 目标.c -o 目标.i # 预处理,将文件中的预处理内容引入到代码中
gcc -S 目标.i -o 目标.s # 编译,预处理的 C 文件转换为汇编文件
gcc -c 目标.s -o 目标.o # 汇编,将汇编文件转换为二进制文件
gcc 目标.o -o a.out # 链接,将二进制 .o 组装打包为可执行文件
1.2 宏
1.2.1 无参宏
格式:
#define COUNT 10 /* #define 标识符 替换数据 1. 标识符通常要大写,不同的单词之间采用下划线分隔 2. 替换数据可以是数值常量,字符常量,字符串等等。 3. 替换数据之后没有分号 ; 4. 标识符和替换数据之间没有赋值号 = */
理解含义:
无参宏可以理解为带有名称的常量,预处理阶段采用最基本的替换逻辑,将代码中使用的无参宏替换为 宏 对应的数据。
【注意】:由于宏定义仅是做简单的文本替换,故替换列表中如有表达式,必须把该表达式用括号括起来,否则可能会出现逻辑上的“错误”。
#include <stdio.h>
//在预处理过程中,宏之后是什么,就替换什么
#define COUNT 10
int main(int argc, char const *argv[])
{
/*
#define COUNT 10
预处理结果
printf("count : %d\n", 10);
#define COUNT 10;
预处理结果
printf("count : %d\n", 10;);
*/
printf("count : %d\n", COUNT);
printf("%s:%d\n", __FILE__, __LINE__);
/*
#define COUNT 10
预处理结果
10 = 200;
无参宏数据一旦在定义时确定,后续无法修改,本身就不是变量仅提供宏之后的代码内容,或者数据
*/
// COUNT = 200;
return 0;
}
1.2.2 有参宏
格式:
#define F(a, b) (a + b) /* #define 标识符(参数1,参数2,...,参数n) 替换列表 */
理解含义:
带有参数的宏,会将用户提供的所谓的宏参数替换到后续的代码中,这里需要关注替换之后的结果情况,必要时可以提供小括号保证代码的完整性。
【注意】:标识符与参数表的左括号之间不能有空格,否则预处理器会把该宏理解为普通的无参宏定义。
#include <stdio.h>
#define F(a, b) (a + b)
int main(int argc, char const *argv[])
{
printf("结果: %d\n", F(2, 5));
/*
#define F(a, b) a + b
printf("结果: %d\n", 5 * F(2, 5));
预处理替换之后
printf("结果: %d\n", 5 * 2 + 5);
#define F(a, b) (a + b)
printf("结果: %d\n", 5 * F(2, 5));
预处理替换之后
printf("结果: %d\n", 5 * (2 + 5));
*/
printf("结果: %d\n", 5 * F(2, 5));
return 0;
}
1.3 条件编译
1.3.1 #if
#if
条件语句编译判断
#if
之后需要一定的条件判断,通常是常量整数表达式,必须有#endif
结尾。【注意】
#if
是要判断它后面表达式的真假,为真执行#if
后的代码,直到#else
结束或者#endif
结束。
#include <stdio.h>
#define JH 0
#define HG 1
int main(int argc, char const *argv[])
{
#if JH
printf("今天中吃黄焖鸡米饭!\n");
#else
printf("今天中吃杨记炒拉条(城东路东大街交叉口西北角) 新疆拉条烧烤城!\n");
#endif
return 0;
}
1.3.2 #ifdef
#ifdef
是判断当前代码中指定【宏】是否存在
- 如果存在,执行
#ifdef
之后的代码,直到#else
或者#endif
出现为止。(无论#else
和#endif
谁先出现)- 不存在,如果有
#else
那么宏不存在会执行#else
到#endif
之间的语句,#else
可以不使用- 【注意】
#ifdef
只是判断后面的宏有没有定义,而不在乎宏的值,宏是 0 是 1 对它来说都没有区别,只要预先定义了,执行#ifdef
后的代码- 必须有
#endif
配合使用
#include <stdio.h>
/*
当前定义了一个宏,但是没有提供任何的替换要求和数据
仅声明当前宏存在
*/
#define JH
/*
undef 后面对应的宏名称,表示取消当前宏定义
*/
// #undef JH
int main(int argc, char const *argv[])
{
/*
#ifdef 是用于判断当前代码中指定宏是否存在,如果有
执行 #ifdef 到 #else 之间的语句,不存在,执行
#else 到 #endif 之间的语句,#else 可以不使用
*/
#ifdef JH
printf("今天中吃黄焖鸡米饭!\n");
#else
printf("今天中吃杨记炒拉条(城东路东大街交叉口西北角) 新疆拉条烧烤城!\n");
#endif
return 0;
}
1.3.3 #ifndef
#ifndef
和#ifdef
作用相反,
- 宏 如果不存在,执行
#ifndef
之后的内容。- 存在执行会执行
#else
到#endif
之间的语句。
#include <stdio.h>
/*
当前定义了一个宏,但是没有提供任何的替换要求和数据
仅声明当前宏存在
*/
#define JH
/*
undef 后面对应的宏名称,表示取消当前宏定义
*/
#undef JH
int main(int argc, char const *argv[])
{
/*
#ifndef 表示如果指定宏没有定义执行 #ifndef 到 #else 或者 到 #endif
内容,如果对应宏定义了,执行 #else 到 #endif 之间的内容,或者不执行
任何 #ifndef 包括内容
*/
#ifndef JH
printf("今天中吃黄焖鸡米饭!\n");
#else
printf("今天中吃杨记炒拉条(城东路东大街交叉口西北角) 新疆拉条烧烤城!\n");
#endif
return 0;
}
1.3.4 #undef
取消宏定义
#define JH // 定义了一个宏 #undef JH // 取消当前定义宏
#include <stdio.h>
// 定义了宏 VALUE 替换数据为 100
#define VALUE 100
int test();
int main(int argc, char const *argv[])
{
printf("main value : %d\n", VALUE); // 100
test();
// 取消 VALUE 宏,从取消位置开始,整个代码无法再次使用对应的宏
#undef VALUE
// 重新定义了一个 宏,宏对应的范围是从定义位置开始,到文件末尾
#define VALUE 10000
return 0;
}
int test()
{
printf("test value : %d\n", VALUE); // 10000
}
2. 自定义头文件
2.1 头文件概述
头文件是 .h 文件,主要是 C/C++ 的声明文件
作用:
- 定义宏
- 声明数据类型,数据类型别名定义
- 声明函数
固定的文档格式
#ifndef 头文件名称英文全拼大写 #define 头文件名称英文全拼大写 // 头文件内容 #endif
例子:
jh.h
#ifndef JH #define JH // 头文件内容 #endif
#include <stdio.h> #include "jh.h" int main() {}
以上
.c
代码预处理之后#ifndef JH // 当前 JH 宏没有定义,执行#ifndef 到 #endif 之间的内容 #define JH // 当前条件编译执行之后,代码中已经定义了 JH 宏 // 头文件内容 #endif #ifndef JH // 再次引入对应的头文件,JH 已存在,#ifndef 到 #endif 之间内容不参与编译 // 不会出现,头文件多次引入,类型,函数多次声明。 #define JH // 头文件内容 #endif int main() {}
2.2 头文件案例
student.h
#ifndef STUDENT
#define STUDENT
/*
头文件中的内容
1. 声明的数据类型
2. 声明的函数
3. 引入其他头文件
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct student
{
int id;
char name[32];
int age;
} Student;
/*
根据用户提供的数据内容,创建一个 Student 数据,数据所在内存空间为【堆区】
@param id 学生 ID
@param name 学生姓名
@param age 学生年龄
@return 创建成功返回学生在内存堆区的地址,否则返回 NULL
*/
Student *create_student(int id, char *name, int age);
/*
展示学生相关数据
@param stu 学生数据在内存中的首地址
*/
void show(Student *stu);
/*
释放学生占用的内存空间
@param stu 学生数据所在内存【堆区】空间首地址
*/
void free_student(Student *stu);
#endif
student.c
/*
<> 方式引入头文件,是直接在系统指定库中搜索目标头文件
"" 方式引入头文件,是在当前文件夹中搜索目标头文件,如果没有找到
到系统指定库文件夹搜索
一般情况下,"" 引入头文件都是自定义头文件
*/
#include "student.h"
Student *create_student(int id, char *name, int age)
{
Student *stu = (Student *)malloc(sizeof(Student));
if (NULL == stu)
{
return NULL;
}
memset(stu, 0, sizeof(Student));
stu->id = id;
strcpy(stu->name, name);
stu->age = age;
return stu;
}
void free_student(Student *stu)
{
free(stu);
}
void show(Student *stu)
{
printf("ID: %d, Name: %s, Age: %d\n",
stu->id, stu->name, stu->age);
}
main.c
/*
虽然在 student.h 已经存在对应的头文件,但是建议系统头文件
如果在不同的文件中,需要再次引入,避免丢失必要的头文件内容
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "student.h"
int main(int argc, char const *argv[])
{
Student *stu = create_student(1, "JH", 56);
show(stu);
free_student(stu);
return 0;
}
编译
gcc main.c student.c
# 得到 a.out 可执行文件