【C语言】宏

news2024/12/23 14:18:08

在这里插入图片描述

🚀write in front🚀
📜所属专栏:> c语言学习
🛰️博客主页:睿睿的博客主页
🛰️代码仓库:🎉VS2022_C语言仓库
🎡您的点赞、关注、收藏、评论,是对我最大的激励和支持!!!
关注我,关注我,关注我你们将会看到更多的优质内容!!

在这里插入图片描述

文章目录

  • 前言:
  • 一.宏:
    • #define:
        • ①. #define 定义标识符:
        • ②.#define 定义宏:
        • ③. #define 替换规则:
        • ④#与##的使用:
          • #的使用:
          • ##的使用:
  • 二.宏与函数对比:
  • 三. 宏与函数的命名约定:
  • 四.#undef:
  • 五.条件编译:
    • 常见的条件编译指令:
      • ①. 单分支条件编译指令:
      • ②. 多分枝条件编译指令:
      • ③. 判断是否被定义:
      • ④.嵌套指令:
  • 六.文件包含
    • 1.头文件被包含的方式:
    • 2.嵌套文件包含
  • 总结:

前言:

  在上一篇博客中,我们学习了程序环境和预处理相关的知识。我们在预处理阶段就是在对宏进行替换,今天我就来给大家详细讲解于宏相关的知识!

一.宏:

#define:

  #define 的用处非常多,就比如我们常用的定义标识符常量、定义宏等等,而在这个过程中,也有一些细节值得我们去注意。

①. #define 定义标识符:

我们常常会使用 #define 去定义一些标识符来方便我们的代码书写:

语法:
 #define name stuff

定义了之后,只有我们看到 name 这个东西,就给他一比一替换成 stuff这个东西就行即可

举个栗子:

#define MAX 1000
#define reg register
//为 register这个关键字,创建一个简短的名字
#define do_forever for(;;)
//用更形象的符号来替换一种实现
#define CASE break;case
//在写case语句的时候自动把 break写上。
 
// 如果定义的 stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。
#define DEBUG_PRINT printf("file:%s\tline:%d\t \
                           date:%s\ttime:%s\n",\
						__FILE__,__LINE__,\
						__DATE__,__TIME__) 

注意:
  在 #define 的最后,最好不要加上分号 “ ; ” 加上可能会出现两个;号,可能会导致语法错误:

②.#define 定义宏:

  在#define 的机制中,包括了一个规定,这个规定允许把参数替换到文本中,这种实现通常称为定义宏(或简称为宏)。

宏的申明方式为:

#define name( parament-list ) stuff
其中的 parament-list 是一个由逗号隔开的符号列表,且可能出现在 stuff 中

注意:
注意:
参数列表的左括号必须与name紧邻
如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分

易错点:

初学者在学习宏时很容易出现以下问题:
举个栗子:

#define SQUARE( x ) x * x
int main
{
int a = 5;
printf("%d\n" ,SQUARE( a + 1) );
}

乍一看,你可能觉得这段代码将打印36这个值。
事实上,它将打印11.
为什么?

我们前面说过了,标识符的替换是一比一替换
替换文本时,参数x被替换成a + 1,所以这条语句实际上变成了:
printf (“%d\n”,a + 1 * a + 1 ),所以结果是11。

所以我们可以对这个宏进行修改:

#define SQUARE(x) (x) * (x)

由此可见,我们在进行宏定义的时候,一定要舍得加括号避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用。

③. #define 替换规则:

#define 在进行符号替换时,遵循以下规则

  1. 调用宏时首先对参数进行检查,检查是否包含任何由 #define 定义的符号。如果有,它们将首先被替换
  2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。
  3. 最后,再次对结果文件进行扫描,看看它是否包含任何由 #define 定义的符号。如果有,就重复上述处理过程。

注意:
4. 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归
5. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索

④#与##的使用:

#的使用:

  上面我们说到字符串常量的内容不会被#define定义的情况下替换掉。那么,我们如何将参数插入字符串中?

首先我们看看这样的代码:

char* p = "hello ""bit\n";
printf("hello"" bit\n");
printf("%s", p);

这里输出的是不是
hello bit ?
答案是确定的:是。
我们发现字符串是有自动连接的特点的

方法一:
我们可以将带有" "(双引号)的字符串替换掉原字符串中

举个栗子:

#define PRINT(FORMAT, VALUE)\
 printf("the value is "FORMAT"\n", VALUE);
...
PRINT("%d", 10)

这里的FORMAT替换为"%d",正好完成拼接在这里我们就可以正常打印出结果了!这里只有当字符串作为宏参数的时候才可以把字符串放在字符串中。
方法二:
对于没有" "(双引号)的标识符怎么替换字符串中的标识符呢?
其实我们只要在要替换的标识符前加一个#即可,此时就把一个宏参数变成对应的字符串

举个栗子:

int i = 10;
#define PRINT(FORMAT, VALUE)\
 printf("the value of " #VALUE "is "FORMAT "\n", VALUE);
...
PRINT("%d", i+3);

代码中的 #VALUE 会预处理器处理为:
"VALUE"
最终的输出的结果应该是

the value of i+3 is 13
##的使用:

##可以把位于它两边的符号合成一个符号
它允许宏定义从分离的文本片段创建标识符。

举个栗子:

#define ADD_TO_SUM(num, value) \
 sum##num += value;
...
ADD_TO_SUM(5, 10);//作用是:给sum5增加10

注:
这样的连接必须产生一个合法的标识符。否则其结果就是未定义的。

二.宏与函数对比:

宏通常被应用于执行简单的运算。
比如在两个数中找出较大的一个。

#define MAX(a, b) ((a)>(b)?(a):(b))

为什么不用函数来完成这个任务
原因有二

  1. 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。
    所以宏比函数在程序的规模和速度方面更胜一筹
  2. 更为重要的是函数的参数必须声明为特定的类型。
    所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点型等可以用于>来比较的类型。
    宏是类型无关的。

当然宏和函数相比宏也有劣势的地方,

原因如下:

  1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度
  2. 宏是没法调试的
  3. 宏由于类型无关,也就不够严谨
  4. 宏可能会带来运算符优先级的问题,导致程容易出现错(记得加括号)

当然,宏也可以做到函数做不到的事情,比如:宏的参数可以出现类型,但是函数做不到

、#define MALLOC(num, type)\
 (type *)malloc(num * sizeof(type))
...
//使用
MALLOC(10, int);//类型作为参数


//预处理器替换之后:
(int *)malloc(10 * sizeof(int));

三. 宏与函数的命名约定:

一般来讲,我们使用的宏定义函数,与普通函数的使用语法很相似,这就导致语言本身无法帮我们区分二者。所以我们通常对二者的命名进行约定:

宏名全部大写
函数名不要全部大写

四.#undef:

这条指令用于移除一个宏定义。

#undef NAME
如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除

五.条件编译:

  通过使用条件编译指令,我们在编译一段代码时如果要将一条(或一组)语句编译或舍弃是很方便的。例如一些调试性的代码,删除可惜,保留碍事,于是我们就可以通过使用条件编译指令来实现选择性的进行编译。

举个栗子:

#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 //__DEBUG__
 }
 return 0;
}

当我们发现赋值成功的时候,只要把#define __DEBUG__这句代码删掉就行。

常见的条件编译指令:

①. 单分支条件编译指令:

#if 常量表达式
 //...
#endif
//常量表达式由预处理器求值。

如:
#define __DEBUG__ 1
#if __DEBUG__
 //..
#endif

②. 多分枝条件编译指令:

2.多个分支的条件编译
#if 常量表达式
 //...
#elif 常量表达式
 //...
#else
 //...
#endif

③. 判断是否被定义:

3.判断是否被定义
#if defined(symbol)
等价于:#ifdef symbol

#if !defined(symbol)
等价于:#ifndef symbol

④.嵌套指令:

#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"

查找策略:先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在找
准位置查找头文件。
如果找不到就提示编译错误。

  • 库文件包含
#include <filename.h>

查找头文件直接去标准路径下去查找,如果找不到就提示编译错误。
这样是不是可以说,对于库文件也可以使用 “” 的形式包含?

答案是肯定的,可以,但是这样做查找的效率就低些,当然这样也不容易区分是库文件还是本地文件了。

2.嵌套文件包含

如果出现这样的场景:
在这里插入图片描述
我们会发现:最终程序中就会出现两份comm.h的内容。这样就造成了文件内容的重复。当程序越复杂,程序就和同时打开很多一模一样的文件。导致预处理之后的代码很冗长。

如何解决这个问题?
答案:条件编译。
每个头文件的开头写:

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

或者:

#pragma once

总结:

  c语言的相关知识到这里就结束了!之后我会出一个总结的思维导图!更新不易,辛苦各位小伙伴们动动小手,👍三连走一走💕💕 ~ ~ ~ 你们真的对我很重要!最后,本文仍有许多不足之处,欢迎各位认真读完文章的小伙伴们随时私信交流、批评指正!

专栏订阅:
每日一题
c语言学习
算法
智力题
更新不易,辛苦各位小伙伴们动动小手,👍三连走一走💕💕 ~ ~ ~ 你们真的对我很重要!最后,本文仍有许多不足之处,欢迎各位认真读完文章的小伙伴们随时私信交流、批评指正!

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

打造Ai作图studio需要哪些工具

这篇文章依然是比较轻松的方式跟大家介绍Ai生成会使用到的一些工具&#xff0c;希望这些工具可以帮助到你更好的更稳定的快捷的生成高质量图片。说来轻松其实也不算轻松&#xff0c;虽然我已经按照生成的链路对工具做了规整。但是里面涉及到的工具其实确实不算少&#xff0c;并…

2023-02-20 Qt 5.13.1 + OpenCV 4.5.4环境编译

引言 OpenCV图像处理在Qt中编译记录。 之前一直是在Python中使用OpenCV&#xff0c;Python中使用某些模块使用pip工具很容易将对应的模块安装在系统中。根据项目需求项目都要转移在国产化中使用&#xff0c;为了适应国产化需求&#xff0c;将代码转移到Qt开发环境中&#xff0c…

django项目实战四(django+bootstrap实现增删改查)进阶时间控件

接上一篇《django项目实战三&#xff08;djangobootstrap实现增删改查&#xff09;进阶分页》 知识点&#xff1a; 使用bootstrap-datepicker实现时间控件 一、优化layout.html模版 主要新增2个块 {% block css %}{% endblock %}{% block js %}{% endblock %} {% load static…

nginx.conf配置方法详细介绍

从前面的内容学习中&#xff0c;我们知道Nginx的核心配置文件默认是放在/usr/local/nginx/conf/nginx.conf&#xff0c;这一节&#xff0c;我们就来学习下nginx.conf的内容和基本配置方法。读取Nginx自带的Nginx配置文件&#xff0c;我们将其中的注释部分【学习一个技术点就是在…

第20讲:Python列表、元组、字符串使用自定义排序规则

文章目录1.自定义排序方法2.常用作自定义排序的函数、方法3.列表、元组、字符串自定义排序方法3.1.当列表、元组中元素为字符串的排序规则3.2.三者采用str.lower方法实现自定义排序3.2.三者采用len函数实现自定义排序1.自定义排序方法 列表、元组、字符串都可以进行排序&#…

友元的学习

&#x1f601;友元的简介类的主要特点之一是数据隐藏&#xff0c;即类的私有成员无法在类的外部作用域之外访问&#xff0c;但是&#xff0c;有时候需要在类的外部访问类的私有成员&#xff0c;这个时候就需要使用友元函数。友元函数是一种特权函数&#xff0c;c允许这哥特权函…

分享在线预约系统制作步骤_在线预约链接怎么做

在微信小程序上进行在线预约&#xff0c;不管是商家还是顾客&#xff0c;都可以自由选择时间&#xff0c;顾客还可以通过预约小程序&#xff0c;了解到所选服务的详情和功能特色&#xff0c;不必等到去店内听介绍&#xff0c;顾客能节省等候时间&#xff0c;商家能解放招待人力…

解决:Vmware Workstation 和 Vmware ESXI 创建虚拟机Ubuntu20.04时界面显示不全,无法点击Continue进行下一步

目录 Vmware Workstation Vmware ESXI Vmware Workstation 1.如下图&#xff0c;到了这一步可以按 CTRL ALT T 调出命令终端 2. 终端输入 xrandr --size 1280x800 此命令是调整屏幕大小 3.此时已经显示屏幕完整信息 Vmware ESXI 安装workstation时那种调整界面大小的方…

每日学术速递2.21

CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 Subjects: cs.CV 1.T2I-Adapter: Learning Adapters to Dig out More Controllable Ability for Text-to-Image Diffusion Models 标题&#xff1a;T2I-Adapter&#xff1a;学习Adapter&#xff0c;为…

2022年网络安全政策态势分析与2023年立法趋势

近日&#xff0c;公安部第三研究所网络安全法律研究中心与 360 集团法务中心联合共同发布了《全球网络安全政策法律发展年度报告&#xff08;2022&#xff09;》。《报告》概览2022年全球网络安全形势与政策法律态势&#xff0c;并对2023年及后续短期内网络安全政策、立法趋势进…

【微信小程序】原生微信小程序ts模板下引入vant weapp

之前一直是在普通项目下使用 vant weapp&#xff0c;这不最近学了ts&#xff0c;使用微信开发工具的tsless初始化项目&#xff0c;再引入 vant 时踩了好久坑&#xff0c;特来记录一下 前言 本文章适合微信开发工具的ts项目&#xff0c;指的是项目目录结构如下图 总结 从上图…

Leetcode.1401 圆和矩形是否有重叠

题目链接 Leetcode.1401 圆和矩形是否有重叠 Rating &#xff1a; 1709 题目描述 给你一个以 (radius, xCenter, yCenter)表示的圆和一个与坐标轴平行的矩形 (x1, y1, x2, y2)&#xff0c;其中 (x1, y1)是矩形左下角的坐标&#xff0c;而 (x2, y2)是右上角的坐标。 如果圆和矩…

【重点掌握】Java基础之Javaweb核心技术详解

都说一入Java深似海&#xff0c;从此代码是爱人&#xff0c;但是学习的过程却从来都不轻松。当下&#xff0c;越来越多的互联网企业&#xff0c;招聘Java工程师时&#xff0c;明确写道需熟练掌握JavaWeb技术。作为衔接前后端的重要一环&#xff0c;JavaWeb技术已成为程序员向大…

Linux线程调度实验

Linux线程调度实验 1.获取线程属性 #include <stdio.h> #include <sys/types.h> #include <unistd.h> #include <pthread.h> #include <time.h> #include <stdlib.h> #include <errno.h> #define _GNU_SOURCE#define handle_error…

Power Apps 中判断用户的SharePoint Online Group权限

前言 最近&#xff0c;碰到一个棘手的问题&#xff0c;就是用户有个需求&#xff0c;就是想在Power Apps中判断一个用户是不是在某些AD安全组中。 通常&#xff0c;这样的情况&#xff0c;都需要去AAD中进行判断&#xff0c;判断这个人在不在某些组中&#xff0c;有Graph API可…

双因素方差分析全流程

上篇文章讲述了“单因素方差分析全流程总结”&#xff0c;单因素方差分析只是考虑了一个自变量&#xff08;定类&#xff09;与一个因变量&#xff08;定量&#xff09;之间的关系&#xff0c;但是在实际问题研究中可能研究两个或者几个因素与因变量之间的关系&#xff0c;例如…

监管持续,医疗卫生机构如何守好“涉疫”数据安全?

肆虐三年的新冠疫情&#xff0c;影响着全球经济发展、社会正常运行&#xff0c;也成为网络攻击、勒索软件攻击快速增长的温床&#xff0c;“滋生”了一系列网络、数据安全问题&#xff0c;受到各界关注。最近&#xff0c;上线运行三年的 “粤康码”发布公告、官宣部分服务下线&…

C++——map和set的应用总结

目录1. 关联式容器2. 键值对3. 树形结构的关联式容器3.1 set3.1.1 set的介绍3.1.2 set的使用3.2 multiset3.2.1 multiset的介绍3.2.2 multiset的使用3.3 map3.3.1 map的介绍3.3.2 map的使用operator[]3.4 multimap3.4.1 multimap的介绍3.4.2 multimap的使用3.5 map和set在OJ中的…

想知道车牌号码里都有什么秘密吗?

当我们看到一辆车时你会优先关注的是什么呢&#xff1f;关注它的外形还是LOGO?这个需要看你的角色定位&#xff0c;如果你是商人可能关注的方向是车的品牌&#xff0c;如果是警察可能关注的就是车牌号码。 因为&#xff0c;车牌号里的信息是很丰富的&#xff0c;可以通过查询车…

万物皆可集成资源包!低代码集成系列一网打尽

如何花最短的时间、用最少的成本解决客户的企业级应用定制问题&#xff1f; 如何满足数据库集成、Web API集成、第三方软件集成等需求&#xff0c;在如今万物皆可盘的当下&#xff0c;低代码如何用积木大玩具的方式快速构建各种应用&#xff0c;实现“万物皆可集成”&#xff…