当前位置: 首页 > news >正文

C语言从头学42——预处理指令(一)

一、基本概念
       C语言编译器在编译程序之前,会先使用预处理器处理代码。预处理器进行删除注释、删除空行、多行语句合成一个逻辑行等等工作。然后,执行 "#" 开头的预处理指令。#号开头是预处理指令的典型特征,比如:#include <stdio.h> 就是预处理指令。
       本文重点就是学习预处理指令。预处理指令习惯上写在代码的开头部分。每个预处理指令都以 # 开头,放在一行的行首,预处理指令应当写成一行,除非在行尾使用反斜杠,将其折行。预处理指令结尾处不需要分号,这是也是预处理指令的特征。
二、预处理指令 #define
        #define 是使用频率很高的预处理指令。define 翻译成中文是定义、下定义的意思。一条完整#define 指令称为一个宏,宏这个概念在C语言中使用非常广泛。宏的本质就是用易记、易理解的内容替换不易记、不易理解的内容,进到编译器中再替换回来;这一连串的替换容易把人搞糊涂,所以我们区分一下概念:在宏命令中用于替换其它内容的称之为"宏名",被替换的部分称为"宏替换内容",到编译器内又恢复的内容称为"宏展开"。
1.使用#define定义无参宏
        使用格式:#define 参1 参2 //注意:参1与参2间只有空格,参数之间无逗号、结尾无分号
              参1 宏名 参2 宏替换内容(宏名的命名规则同变量)
        举例:#define MAX 100 //代码中凡需使用100的地方,都可以使用MAX,到编译器中MAX又展开成100
       说明:a. #define命令后可以跟注释符号"//"或"/* */";b. 宏是原样替换,程序执行时宏名会按照所途欢的内容展开;c. #define命令可出现在程序任何地方,但习惯上写在头部,如是全文件用的宏,应写在main()前面;d. #define命令应写在一行,一行容不下时,可用"\"转到下一行,转行符后不能加注释;e. 允许多重替换,即一个宏可以作为另一个宏的参数;f. 宏不能出现在字符串中,在字符串中宏不会展开。
       例子(具体见代码及注释):

#include<stdio.h>
#define MAX 10000 //定义最大值10000的宏MAX
#define SUM a+b   //a+b被SUM替换,程序中原样展开
#define TOTAL SUM\+c //宏SUM作为宏TOTAL的参数,并将宏TOTAL的定义写在两行
int main(void)
{int a = 10, b = 20, c = 30;printf("MAX=%d\n", MAX); //运行结果:MAX=10000 如写成printf("MAX");是不会显示10000的printf("SUM=%d\n", SUM); //运行结果:SUM=30printf("TOTAL=%d\n",TOTAL); //运行结果:TOTAL=60getchar();return 0;
}

2.#define定义带参数的宏
       带参宏与函数表面上难以区分,使用过程中也感觉与函数无异。从根本上说,带参宏是预处理指令定义的,函数是由C语言命令编写的,这是本质区别;但带参宏仍然是宏,还是有将被替换内容原样展开的特性,所以,使用带参宏时,实参中尽量不要有表达式,以防展开时带来意想不到的结果。
       带参宏的名称后面使用括号,能够接受一个或多个参数:
       固定个数参数情形:
       格式:#define 宏名(参数列表) 宏替换内容 //参数多于一个时中间用","隔开,注意宏名与括号间不要留空格
       说明:从上面的格式可以看出,带参宏并未涉及数据类型,因此使用时代入任何适合的类型均可。
3.与#define相关的#、## 运算符
       本来接下来应该学习不定参数宏,但由于不定参数宏中涉及到#、##运算符,所以先介绍一下这两个运算符。
       #运算符:
       功能一:指定宏替换内容为字符串,做法是在在宏替内容前加上 "#"。例:
                     #define STR(x) #x //相当于将x的值用引号引起来
       功能二:起将某字符串与宏替换内容连接作用,做法是:"字符串"#宏替换内容。例:
                     #define CONECT(x) "Name: "#x //作用是把字符串"Name: "与x代表的字符串连在一起形成一个大字符串(说明:"#"仅是对字符串的操作)
        ##运算符:
        功能一:将作为参数的两个字符串(或其它类型)连接在一起。例:
                     #define cons(a,b) a##b //如 a="123";b="456";则 cons(a,b)="123456";
        功能二:参数与其它标识符连在一起,组成一个新的标识符。例:
                     #define makeVar(n) i##n //这里的作用也是连接,但连接出来的是标识符,不是字符串,具体见后例子 (说明:"##"操作的对象及结果不仅仅是字符串)
4.#define定义不定参数宏
       宏的参数事先不能确定时,可以使用不定参数宏。做法是参数表中使用 "..." 代表未确定参数,宏替换内容中用预定义宏__VA_ARGS__代表"..."对应的宏替换内容。例:
       #define 宏名(参1,参2, ... ) 参1参2对应宏替换内容,__VA_ARGS__
      说明:__VA_ARGS__对应...;宏替换内容"..."只能放在最后参数位置。
5.下面将带参宏、#和##运算符、不定参数宏用一个程序做进一步说明。代码如下:

#include<stdio.h>
int main(void)
{
//A、#define带参宏
#define MAX(x, y) ((x)>(y))?(x):(y) //使用三目表达式返回x,y中最大值//在三目表达式中多加括号,目的是防止展开式出现意外结果double x = 3.14, y = 2.71;printf("x,y较大的是%f\n", MAX(x, y));//运行结果:x,y较大的是3.140000
//B、#运算符的使用
#define Str(x) #x //给x代表的值加上双引号printf(Str(3.1415926));//相当于printf("3.1415926");printf("\n");
#define Str01(x) "No."#x //将No.与x所代表的值连接成字符串printf(Str01(001)); //运行结果:No.001
//C、##运算符
#define cons(x,y) x##yprintf("\n%f\n", cons(123.23, 4567)); //运行结果:123.4567 小数位数加长了四位
#define makeIntVar(n) int i##nmakeIntVar(1); //等价 int i1makeIntVar(2); //等价 int i2i1 = 100, i2 = 300;printf("i1+i2=%d\n", i1 + i2);
//D、不定参数宏的使用
#define PR(...) printf(__VA_ARGS__)  //相当于printf()函数PR("123456\n"); //运行结果:123456//如果这样定义宏
#define PR01(...) printf(#__VA_ARGS__) //可以省略双引号PR01(abcdefg\n); //运行结果:abcdefggetchar();return 0;
}

       说明:总的说来,宏的优点是相对简单,本质上是字符串替换,不涉及数据类型,不像函数必须定义数据类型。而且,宏将每一处都替换成实际的代码,省掉了函数调用的开销,所以性能会好一些。但宏有时候会产生意想不到的替换结果,而且往往只能写成一行,除非对换行符进行转义,但是可读性就变得很差。所以应该首先使用函数,它的功能更强、更容易理解。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 【熊猫派对】
  • vim使用技巧
  • Mysql-窗口函数一
  • Animate软件动画类型简介
  • LabVIEW水下根石监测系统
  • redis面试(四)持久化
  • Vulnhub靶场DC-9练习
  • 软件开发人员如何有效提问
  • Linux系统
  • 如何判断机器学习模型的好坏之分类模型
  • 哪个电脑桌面便签好用并且无广告弹窗?
  • org.springframework.web.client.HttpClientErrorException$NotFound异常
  • Java企业微信服务商代开发获取AccessToken示例
  • 《Linux系统开发入门定制专栏导读》
  • 白骑士的PyCharm教学高级篇 3.3 Web开发支持
  • android 一些 utils
  • Angular js 常用指令ng-if、ng-class、ng-option、ng-value、ng-click是如何使用的?
  • Sequelize 中文文档 v4 - Getting started - 入门
  • 对象引论
  • 浮动相关
  • 简单数学运算程序(不定期更新)
  • 问:在指定的JSON数据中(最外层是数组)根据指定条件拿到匹配到的结果
  • 移动互联网+智能运营体系搭建=你家有金矿啊!
  • 与 ConTeXt MkIV 官方文档的接驳
  • 智能合约Solidity教程-事件和日志(一)
  • “十年磨一剑”--有赞的HBase平台实践和应用之路 ...
  • ​​​​​​​​​​​​​​汽车网络信息安全分析方法论
  • ​​​​​​​Installing ROS on the Raspberry Pi
  • ​MPV,汽车产品里一个特殊品类的进化过程
  • ​数据结构之初始二叉树(3)
  • # wps必须要登录激活才能使用吗?
  • #LLM入门|Prompt#1.7_文本拓展_Expanding
  • #中国IT界的第一本漂流日记 传递IT正能量# 【分享得“IT漂友”勋章】
  • (1) caustics\
  • (2)STM32单片机上位机
  • (2015)JS ES6 必知的十个 特性
  • (C语言)strcpy与strcpy详解,与模拟实现
  • (function(){})()的分步解析
  • (LeetCode 49)Anagrams
  • (补充):java各种进制、原码、反码、补码和文本、图像、音频在计算机中的存储方式
  • (第9篇)大数据的的超级应用——数据挖掘-推荐系统
  • (二)延时任务篇——通过redis的key监听,实现延迟任务实战
  • (附源码)springboot车辆管理系统 毕业设计 031034
  • (黑马出品_高级篇_01)SpringCloud+RabbitMQ+Docker+Redis+搜索+分布式
  • (求助)用傲游上csdn博客时标签栏和网址栏一直显示袁萌 的头像
  • (三)Kafka离线安装 - ZooKeeper开机自启
  • (十)【Jmeter】线程(Threads(Users))之jp@gc - Stepping Thread Group (deprecated)
  • (十六)一篇文章学会Java的常用API
  • (续)使用Django搭建一个完整的项目(Centos7+Nginx)
  • (转)C语言家族扩展收藏 (转)C语言家族扩展
  • ****** 二十三 ******、软设笔记【数据库】-数据操作-常用关系操作、关系运算
  • *p=a是把a的值赋给p,p=a是把a的地址赋给p。
  • .Net 4.0并行库实用性演练
  • .net core 6 redis操作类
  • .NET Framework杂记