【C语言】预处理

在这里插入图片描述

个人主页点这里~


预处理

  • 一、预处理符号
  • 二、#define定义常量
  • 三、#define定义宏
  • 四、带有副作用的宏参数
  • 五、宏替换的规则
  • 六、宏与函数的对比
    • (一)、宏的优势
    • (二)、宏的劣势
    • (三)、宏和函数的对比
  • 七、#和##
    • 1、#运算符
    • 2、##运算符
  • 八、命名约定
  • 九、#undef
  • 十、命令行定义
  • 十一、条件编译
  • 十二、头文件的包含
    • 1、头文件被包含的方式
      • ①本地文件包含
      • ②嵌套文件包含
  • 十三、其他预处理指令

一、预处理符号

__FILE__ //进⾏编译的源⽂件
__LINE__ //⽂件当前的⾏号
__DATE__ //⽂件被编译的⽇期
__TIME__ //⽂件被编译的时间
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义

以上是C语言设置的一些预定义符号,是可以直接使用的,预定义符号在预处理阶段处理

二、#define定义常量

基本语法:

#define name stuff

例子:

#define MAX 1000  //将MAX赋值为1000
#define reg register //为 register这个关键字,创建⼀个简短的名字reg
#define do_forever for(;;) //⽤更形象的符号来替换⼀种实现
//for(;;)是无条件的for循环,是一个死循环
#define CASE break;case //在写case语句的时候⾃动把 break写上。

当我们在使用#define的时候,变量有两项,一是name,二是stuff,而stuff中的值将会代替代码中所有的name,相当于是办公软件word里边的替换,所以我们遇到以下的问题,就可以一下解决出来:

#define MAX 1000;
#define MAX 1000

我们说,这两种被定义的方式是不同的,上边的第一个定义可以用来完成以下代码:

#define MAX 1000;
#include <stdio.h>
int main()
{
    int a = MAX
    return 0;
}

第二个可以完成以下代码:

#define MAX 1000
#include <stdio.h>
int main()
{
    int a = MAX;
    return 0;
}

都是可以的,但是我们要注意,当我们想要直接用printf输出MAX的值时,用第一个是不可以的
这是使用第二个定义,正确的做法:

#define MAX 1000
#include <stdio.h>
int main()
{
    printf("%d",MAX);
    return 0;
}

使用第一个定义:

#define MAX 1000;
#include <stdio.h>
int main()
{
    printf("%d",MAX);
    return 0;
}

我们在这里发现,MAX被替换成了1000;
1000;是不能通过printf打印的
所以我们得出一个结论:在使用#define定义数字时,尽量不要加入符号

三、#define定义宏

#define机制包括了一个机制,就是允许把参数替换到文本中,这种实现通常称为宏或者定义宏
宏的声明方式:

#define name( parament-list ) stuff

parament-list是一个由逗号隔开的符号表,它们可能出现在stuff中
name与旁边的参数列表的左圆括号必须紧邻,如果二者之间有任何空白存在,参数列表就会被认为是stuff的一部分

举一个求平方的例子:

#define SQUARE( x ) x * x

当我们使用SQUARE( 9 )时,编译器就会将它替换成9*9

注意:
在使用宏定义的时候,我们为了不让我们所定义的量出现错误,最好给每个变量都加上括号,不然就会出现错误

例子:

#define SQUARE( x ) x * x
#include <stdio.h>
int main()
{
    int a = 5;
    printf("%d\n" ,SQUARE(a + 1) );
    return 0;
}

这会发现一个错误,替换之后变成5+1*5+1,最终的答案是11而不是36
所以最好给每个量都套一个括号

修改后:

#define SQUARE( x ) ((x) * (x))
#include <stdio.h>
int main()
{
    int a = 5;
    printf("%d\n" ,SQUARE(a + 1) );
    return 0;
}

四、带有副作用的宏参数

#define x++;

上面这个宏就是一个副作用宏,因为替换之后会持续造成作用,这样就可能会导致危险,简单来说,副作用就是表达式求值的时候会出现的永久性效果
我们来举一个例子

#define MIN(a, b) ( (a) > (b) ? (a) : (b) )
...
x = 6;
y = 9;
z = MIN(x++, y++);//z = ((x++) < (y++) ? (x++) : (y++))
printf("x=%d y=%d z=%d\n", x, y, z);

结果是x=8 y=10 z = 7
我们先来计算<左右两边的值,x++是先赋值再++,y++也是先赋值再++,x<y,所以执行问号后边的语句,即x++先赋值再++,此时x=7,y=10,所以z=7,然后++,x=8,最终得到答案就是上述答案

五、宏替换的规则

1、调用宏的时候,首先对参数进行检查,看是否包含任何由#define定义的符号,如果是,他们首先被替换,也就是首先替换参数
2、然后替换文本随后被插入到程序中原本文本的位置,对于宏,参数名被它们的值所替换,也就是把宏定义的值替换被替换的值
3、最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号,如果是,就重复上述处理过程,也就是再次扫描然后重复上述过程
4、宏参数和#define定义中可以出现其他#define定义的符号,但是宏是不能够递归的
5、在字符串中的#define定义的符号不能被替换

六、宏与函数的对比

(一)、宏的优势

当我们要进行一些简单的计算时,使用宏替换比函数更有优势一些
1、因为不管是简单的还是复杂的计算,使用函数都会在栈中开辟一块空间(在我们之前的博文函数栈帧的创建和销毁一文中有详细的内容,大家有兴趣可以看一下),然后还有销毁空间,在开辟空间之前会有开辟空间之前的指令,这会增长运行时间,而反观用宏替换的方式,直接将代码替换,省去了开辟空间的时间,速度更快
2、使用函数要声明数据类型,所以一个函数只能由特定数据类型的数据使用,但是宏可以使用任意的数据,宏的参数与类型无关,只是一个简单的替换
3、宏的参数可以是类型,函数不行
例子:
( \ 这个符号是连字符,如果代码内容较长,可以用连字符来连接,程序生成的过程中会自动将 \ 去掉并连接上下)

#define MALLOC(num, type)\
 (type*)malloc(num*sizeof(type))
int main()
{
    MALLOC(5,int);//(int*)malloc(5*sizeof(int));
    return 0
}

(二)、宏的劣势

再进行复杂计算时,使用函数会更有优势一些
1、每次使用宏的时候,宏定义的代码会插入到程序中,在宏较长的情况下可能会导致大幅度增加程序的长度
2、宏无法调试
3、宏与类型无关,这虽然是它的一个优点,也是一个缺点,因为这导致它不够严谨
4、宏可能会带来运算优先级问题,如上面第三条所说,容易导致程序出错

(三)、宏和函数的对比

属性#define定义宏函数
代码长度每次使用时,宏代码都会被插入到程序当中,除了非常小的宏之外,程序的长度会大幅度增长每次使用函数时,都调用同一个地方的同一份代码
执行速度更快存在函数栈帧的创建和销毁,相对于宏会慢一些
操作符优先级宏在书写的时候要多加括号,否则会因为临近操作符优先级不同,使目的与代码不匹配的问题表达式的求值容易被预测,不会出现宏一样的前后操作符优先级问题
带有副作用的参数参数可能会被替换到程序中的多个位置,如果宏的参数被多次计算,带有副作用的参数求值可能会造成风险函数参数只在传参的时候求值一次,结果容易被控制
参数类型宏的参数与类型无关,只要对参数的操作合法,就可以使用任何数据类型函数的参数与类型有关,必须严格按照参数类型来进行使用,不同参数类型不同,所需要的函数就不同,尽管它们的任务相同
调试不能调试可逐句调试
递归不能递归可以递归

七、#和##

1、#运算符

#运算符可以将宏的一个参数转换为字符串字面量,它仅允许出现在带参数的宏的替换列表中
简单来说它的功能就是字符串化
例子:
当我们想打印出来一个数字的大小:the value of number is 6
我们可以这样做:

#define PRINT(n) printf("the value of "#n " is %d", n);
int main()
{
    int number = 6;
    PRINT(number);
    return 0;
}

它在使用时,#n就是#number,#number就是转换成"number",这时字符串内的代码就会被预处理为:

printf("the value of ""number" " is %d", number);

然后正常执行代码,就得到了the value of number is 6

2、##运算符

##被称为记号粘合,它可以把左右两边的符号合成一个符号,它允许宏定义从分离的文本片段创建标识符,当然这样的连接是要产生一个合法的标识符的,否则其结果就是未定义的,我们可以用这个运算符来写一个类似于宏的函数,这样的函数是可以定义自由定义数据类型的,使用起来是非常方便的

#define GENERIC_MIN(type) \
type type##_min(type x, type y)\
{ \
 return (x<y?x:y); \
}
GENERIC_MIN(int) //替换到宏体内后int##_min ⽣成了新的符号 int_min做函数名
GENERIC_MIN(float) //替换到宏体内后float##_min ⽣成了新的符号 float_min做函数名
int main()
{
 //调⽤函数
 int m = int_min(6, 3);
 printf("%d\n", m);
 float b = float_min(1.5f, 4.5f);
 printf("%f\n", b);
 return 0;
}

在这里插入图片描述

八、命名约定

函数与宏的使用比较相似,我们这里约定俗成的规则就是将宏全部大写,函数部分大写,然后其他代码使用小写,这样可以很好的区分宏、函数以及其他代码

九、#undef

#undef 可以移除一个宏定义,如果现存的一个名字需要被重新定义,那么就使用它进行移除

#undef NAME

十、命令行定义

许多C的编译器提供了在命令行中定义符号的能力,用于启动编译过程
在这里我们可以调节数组的大小,或者循环次数的大小等

十一、条件编译

我们平常写代码的时候,我们不清楚所写的代码是否能够实现目标时,我们往往会对某一个某块进行调试,但有一些代码是专门用来调试时加上的,删了有些可惜,保留又碍事,这时我们就可以选择性的编译,使用条件编译指令

例子:

#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函数用来检查赋值是否成功,#ifdef用来检查后边的指令是否被定义,如果被定义了,那么就进行编译,如果未被定义则编译,调试结束之后将#define语句注释掉就行了

常见的条件编译指令

//1.
#if 常量表达式
 //...
#endif
//常量表达式由预处理器求值。
//如:
#define __DEBUG__ 1
#if __DEBUG__
 //..
#endif
//2.多个分⽀的条件编译
#if 常量表达式
 //...
#elif 常量表达式
 //...
#else
 //...
#endif
//3.判断是否被定义
#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol
//4.嵌套指令
#if defined(OS_UNIX)
 #ifdef OPTION1
 unix_version_option1();
 #endif
 #ifdef OPTION2
 unix_version_option2();
 #endif
#elif defined(OS_MSDOS)
 #ifdef OPTION2
 msdos_version_option2();
 #endif
#endif

十二、头文件的包含

1、头文件被包含的方式

①本地文件包含

#include "filename"

查找策略:先在源文件所在目录下查找,如果未找到,就在标准位置查找,即库函数所在的位置,如果找不到就提示编译错误

②嵌套文件包含

我们知道,每一条代码就可能使用一块空间,如果我们在一个大的程序里边写代码时,我们可能多次包含同一个头文件,那么包含了几次,这条代码就编译几次,极大的影响效率,我们可以通过使用条件编译避免头文件的重复引入

#ifndef __TEST_H__
#define __TEST_H__
//头⽂件的内容
#endif

#pragma once

十三、其他预处理指令

c语言给我们很多预处理指令,我们工作的过程中可能会用到,大家自行查找学习


今日分享就到这里了~

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/547309.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Emacs之增加/取消输入括号自动匹配(一百三十六)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

vue webpack打包配置生成的源映射文件不包含源代码内容、加密混淆压缩

前言&#xff1a;此案例使用的是vue-cli5 一、webpack源码泄露造成的安全问题 我们在打包后部署到服务器上时&#xff0c;能直接在webpack文件下看到我们项目源码&#xff0c;代码检测出来是不安全的。如下两种配置解决方案&#xff1a; 1、直接在项目的vue.config.js文件中加…

C语言 | Leetcode C语言题解之第30题串联所有单词的子串

题目&#xff1a; 题解&#xff1a; typedef struct {char key[32];int val;UT_hash_handle hh; } HashItem;int* findSubstring(char * s, char ** words, int wordsSize, int* returnSize){ int m wordsSize, n strlen(words[0]), ls strlen(s);int *res (int *)mall…

【测试开发学习历程】python常用的模块(中)

目录 5 time模块 5.1、Python中的四种格式的时间&#xff1a; 5.2、time模块中的常用函数 6 I/O流操作 6.1 创建文件 6.2 读取一个文件存入到另外一个文件 6.3 with open as 结构 6.4 open和with open as的区别 7 Excel的操作模块-openpyxl 7.1、新建Excel文件进行读…

11.范式与反范式设计

范式 1.问题 MySQL的库表设计&#xff0c;在很多时候我们都是率性而为&#xff0c;往往在前期的设计中考虑不全面&#xff0c;同时对于库表结构的划分也并不明确&#xff0c;所以很多时候在开发过程中&#xff0c;代码敲着敲着会去重构某张表结构&#xff0c;甚至大面积重构多…

bestvike 资料 --Spring Boot 2.5.0

Spring Boot 2.5.0 SSM环境搭建 springspringmvcmybatisspring springmvc mybatis # 项目 - 需求分析 概要设计(库表设计) 详细设计(验证库表正确性) 编码(环境搭建业务代码) 测试 部署上线# 员工添加 查询所有功能 SSM - 库表 库: ssm 数据库:mysql 表: id na…

spring-cloud微服务gateway

核心部分&#xff1a;routes(路由)&#xff0c; predicates(断言)&#xff0c;filters(过滤器) id&#xff1a;可以理解为是这组配置的一个id值&#xff0c;请保证他的唯一的&#xff0c;可以设置为和服务名一致 uri&#xff1a;可以理解为是通过条件匹配之后需要路由到&…

RabbbitMQ基本使用及其五种工作模型

初识MQ 同步通讯和异步通讯 什么是同步通讯呢&#xff1f;举个例子&#xff0c;你认识了一个小姐姐&#xff0c;聊的很火热&#xff0c;于是你们慢慢开始打电话&#xff0c;视频聊天&#xff0c;这种方式就成为同步通讯&#xff0c;那什么是一部通讯呢&#xff0c;同样的&…

gitlab(docker)安装及使用

GitLab GitLab 是一个用于仓库管理系统的开源项目&#xff0c;使用Git作为代码管理工具&#xff0c;并在此基础上搭建起来的Web服务。 下载(docker) 查询docker镜像gitlab-ce gitlab-ce是它的社区版 [rootlocalhost ~]# docker search gitlab-ce NAME …

OpenCV基本图像处理操作(六)——直方图与模版匹配

直方图 cv2.calcHist(images,channels,mask,histSize,ranges) images: 原图像图像格式为 uint8 或 float32。当传入函数时应 用中括号 [] 括来例如[img]channels: 同样用中括号括来它会告函数我们统幅图 像的直方图。如果入图像是灰度图它的值就是 [0]如果是彩色图像 的传入的…

SETR——Rethinking系列工作,展示使用纯transformer在语义分割任务上是可行的,但需要很强的训练技巧

题目:Rethinking Semantic Segmentation from a Sequence-to-Sequence Perspective with Transformers 作者: 开源:https://fudan-zvg.github.io/SETR 1.研究背景 1.1 为什么要研究这个问题? 自[ 36 ]的开创性工作以来,现有的语义分割模型主要是**基于全卷积网络( FCN )的…

ubuntu20.04安装+ros-noetic安装+内网穿透frp

刷机后的系统安装 ubuntu20.04安装安装ros-noetic安装各种必要的插件安装vscode内网穿透连接实验室主机配置frpc和frps文件运行完成自动化部署免密登录linux的免密登录windows上的免密登录 内网穿透的参考链接&#xff1a;如何优雅地访问远程主机&#xff1f;SSH与frp内网穿透配…

Python学习笔记 - 正则表达式

前言 正则表达式&#xff08;Regular Expression&#xff0c;在代码中常简写为 regex、regexp、RE 或 re&#xff09;是预先定义好的一个“规则字符串”&#xff0c;通过这个“规则字符串”可以匹配、查找、替换那些符合“规则”的文本&#xff0c;也就是说正则表达式针对的目标…

MSTP/RSTP的保护功能

目录 原理概述 实验目的 实验内容 实验拓扑 1.配置RSTP/MSTP 2.配置BPDU保护 3.配置根保护 4.配置环路保护 5.配置TC-BPDU保护 原理概述 在RSTP或MSTP交换网络中&#xff0c;为了防止恶意攻击或临时环路的产生&#xff0c;可配置保护功能来增强网络的健壮性和安全性。…

C++vector类(个人笔记)

vector类 1.熟悉vector接口以及使用1.1vector的定义1.2vector迭代器使用1.3vector空间增长1.4vector增删查改1.5vector迭代器失效问题&#xff08;重点&#xff09; 2.vector的一些笔试题3.模拟实现vector 1.熟悉vector接口以及使用 vector的C官网文档 1.1vector的定义 (con…

用python快速读取大文件几个GB以上的csv数据文件

用python快速读取大文件几个GB以上的csv数据文件 遇到几个GB的csv大文件,用python读取时,可以通过next()函数一行行来读取以提高效率,然后分批量进行处理。 1、文件格式例图 其中第一、第二行是数据行数、列数汇总。 2、流程 1、把csv第一、第二行的数据说明,先读取出来…

Windows远程桌面连接虚拟机Linux

Windows远程桌面连接虚拟机Linux 需要先打开虚拟机的启用VNC连接使用VNC客户端进行连接 yum install -y tigervnc-server #安装tigervnc-server vncserver #启动一个vnc进程 #第一次启动会要求设置密码 #如果需要更改密码可以使用vncpasswd进行更改密码 vncserver -list #查看…

ASUS华硕ROG幻13笔记本电脑GV301R工厂模式原厂OEM预装Windows11系统,恢复出厂开箱状态

适用于型号&#xff1a;GV301RC、GV301RE、GV301RA 工厂模式安装包&#xff1a;https://pan.baidu.com/s/1gLme1VqidpUjCLocgm5ajQ?pwddnbk 提取码&#xff1a;dnbk 工厂模式Win11安装包带有ASUS RECOVERY恢复功能、自带所有驱动、出厂主题壁纸、系统属性专属联机支持标志…

java算法day55 | 动态规划part16 ● 583. 两个字符串的删除操作 ● 72. 编辑距离

583. 两个字符串的删除操作 思路&#xff1a; 和1143.最长公共子序列这道题思路相同&#xff0c;只不过需要对return的数据做一些操作。 class Solution {public int minDistance(String word1, String word2) {int[][] dpnew int[word1.length()1][word2.length()1];for(int …

06_定时器中断

72分频 72MHz 72000000 经过72分频 1000000