C语言---07预定义指令

C语言编译过程与预处理深入解析
本文详细介绍了C语言编译过程中的预处理功能,包括文件包含、头文件嵌套、宏定义、条件编译,以及避免头文件重复定义的方法。重点讲解了头文件包含的两种方式,带参宏定义的区别,以及如何利用条件编译控制代码行为。

目录

一、C语言编译过程(了解就行)

(一)C编译器提供的预处理功能主要有以下四种

二、文件包含

(一)头文件包含

1、包含自定义头文件:#include "自定义头文件名.h"

2、包含系统头文件:#include <系统头文件名.h>

3、头文件嵌套包含

(二)分文件编写

三、宏定义

(一)无参宏定义

(二)带参宏定义

(三)带参数的宏(宏函数)和普通函数有啥区别?

1、带参数的宏(宏函数)

2、代参的函数

四、条件编译

(一)#if……#else(判断条件是否成立)

(二)#ifdef……#endif(测试存在)

(三)#ifndef……#endif(测试不存在)

(四)综合案例:通过条件编译控制大小写的转换

五、为了避免头文件重复定义,可以使用的方法

六、预定义符号


一、C语言编译过程(了解就行)

  • 预处理

    • 预处理是在程序源代码被编译之前,由预处理器(Preprocessor)对程序源代码进行的处理

    • 头文件包含、宏替换、条件编译、删除注释,在这里不会做语法检查,但它会把源代码分割或处理成为特定的符号为下一步的编译做准备工作

    • 将.c中的头文件展开、宏展开生成的文件是.i文件

  • 编译

    • 将预处理后的.i文件生成.s汇编文件,进行语法检查

  • 汇编

    • 将汇编文件编译二进制文件

    • .s汇编文件生成.o目标文件

  • 链接

    • 将众多的二进制文件+库+启动代码生成可执行文件

    • .o文件链接成目标文件

(一)C编译器提供的预处理功能主要有以下四种

  • 文件包含  #include

  • 宏定义     #define

  • 条件编译  #if #endif ..

  • 一些特殊作用的预定义宏

二、文件包含

(一)头文件包含

  • 文件包含关键字:#include

  • 是指一个源文件可以将另外一个文件的全部内容包含进来

1、包含自定义头文件:#include "自定义头文件名.h"

  • 自定义头文件:C语言支持程序员编写自己的头文件

  • 表示先从源文件所在的目录寻找,如果找不到再到系统指定的目录下去找

#include "test.h" // 相当于把文件内容拿过来用

2、包含系统头文件:#include <系统头文件名.h>

  • 表示从系统的指定目录下寻找

 #include <stdio.h> // 基本输入输出头文件

3、头文件嵌套包含

  • 头文件的嵌套包含:在一个头文件中可以包含另一个头文件

  • 重复包含系统头文件不会有重定义问题

  • 头文件的嵌套包含可能会引起头文件的重复包含, VS提供了解决头文件重复包含的方法:在每个头文件的第一行写上#pragma once

 // 重复包含系统头文件不会有重定义问题
 #include <stdlib.h> // 因为系统头文件中有#pragma once

(二)分文件编写

  • 一般情况下,在头文件里写函数的声明,而在.c/.cpp文件中进行函数的定义

三、宏定义

  • 在源程序中,允许一个标识符(宏名)来表示一个语言符号字符串,也就是用指定的符号代替指定的信息

  • 宏只在当前源文件有效

(一)无参宏定义

  • 格式:#define 宏名 字符串(宏定义之后可以不用加分号作为结束)

  • 终止宏定义的作用域:#undef 宏名

 #define STR "abcdefg"
 #undef STR // 终止宏定义

(二)带参宏定义

  • 可以传递参数,像函数一样使用

  • 格式:#define  宏名形参表)  字符串

  • 宏一般为大写(将宏和普通变量区分开)

  • 注意:宏定义只会替换而不会计算,替换完成之后再计算,因此使用带参的宏定义最好加上括号

 #define ADD(a,b) a+b
 int main()
 {
     int x = 10, y = 20;
     printf("%d\n", ADD(x, y)); // 30
     printf("%d\n", ADD(x, y)*ADD(x, y)); // 实际的计算步骤:x+y*x+y = 10+20*10+20 = 10+200+20 = 230
 }

(三)带参数的宏(宏函数)和普通函数有啥区别?

1、带参数的宏(宏函数)

  • 调用多少次就会展开多少次,执行代码的时候没有函数调用的过程,不需要函数的出入栈

  • 带参数的宏浪费空间、节省时间(不需要出入栈)

2、代参的函数

  • 代码只有一份,存在代码段, 调用的时候去代码段读取函数指令,调用的时候要压栈(保存调用函数前的相关信息),调用完出栈(恢复调用函数前的相关信息)

  • 函数浪费时间、节省空间

四、条件编译

(一)#if……#else(判断条件是否成立)

  • 条件为真则编译以下代码段,如果为假则不编译以下代码段

 #define TYPE 1
 #if TYPE 
 代码块1
 #else
 代码块2
 #endif

(二)#ifdef……#endif(测试存在)

  • 如果定义了宏INT则编译代码块1,否则编译#else后面的语句

 #define INT
 #ifdef INT 
 代码块1
 #else
 #ifdef CHAR // 嵌套
 代码块2
 #else
 代码块3
 #endif
 #endif

(三)#ifndef……#endif(测试不存在)

  • 如果没有定义宏INT则编译代码块1,否则编译#else后面的语句

 #ifndef INT 
 代码块1
 #else
 #ifdef CHAR // 嵌套
 代码块2
 #else
 代码块3
 #endif
 #endif

(四)综合案例:通过条件编译控制大小写的转换

#include<stdio.h>
int main()
{
    char buf[128]="";
    int i=0;
    printf("请输入字符串:");
    // fgets会获取换行符 '\n'
    fgets(buf,sizeof(buf), stdin);
    // strlen(buf)‐1 这是 换行符 的下标位置
    buf[strlen(buf)‐1] = 0;

    while(buf[i]) // 最后一个元素是'\0' == 0==假 循环进不去
    {
        #if 1
        	if(buf[i]>='A' && buf[i]<='Z')
        	buf[i] = buf[i]+32;
        #else
        	if(buf[i]>='a' && buf[i]<='z')
       		 buf[i] = buf[i]‐32;
        #endif
        i++;
    }
    
   	printf("buf = %s\n",buf);
	return 0;
}

五、为了避免头文件重复定义,可以使用的方法

  • VS提供了解决头文件重复包含的方法:在每个头文件的第一行写上#pragma once

  • 借用条件编译

 // 在文件开头
 #ifndef N // 放在开头是因为没有定义,没有定义就可以执行下列代码
 #define N // 这里的定义是防止第二次应用该段 
 #endif
 #ifndef N // 这里是第二次应用的时候,N被定义,不执行下列代码
 #define N
 #endif

六、预定义符号

  • 进行异常抛出、错误输出时可以用,输出错误的函数名、地址等内容

 __FILE__ // 当前编译的文件名
 __FUNCTION__ // 当前所在函数的函数名
 __DATE__ // 当前编译日期
 __TIME__ // 当前编译时间
 // 以上格式占位符都用%s ,如:printf("%s",__DATE__);
 __LINE__ // 当前行数,格式占位符用%d,如:printf("%d",LINE);
 // 注意这些宏定义的前后分别有两个'_',而不是一个下划线
 _CRT_SECURE_NO_WARNINGS // 关闭安全检查
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

盾山狂热粉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值