C/C++语言基础--预编译指令、宏定义(带参宏、宏函数)、头文件重复包含解决方法等

news2024/12/23 10:46:52

本专栏目的

更新C/C++的基础语法,包括C++的一些新特性

前言

  • 宏定义是C/C++最伟大的发明之一,甚至有人认为他比指针还伟大,它能够极大简化代码,因此学习宏定义是非常有必要的
  • 但是由于他只是简单的替换,故在C++的efficiency书籍中第一条就提到,经量用const、内联函数去代替宏定义,但是实际上,只要运用得好,无论在速度上,还是在代码简化上,都深受大牛程序员的喜欢
  • 欢迎点赞 + 收藏 + 关注,本人将会持续更新

文章目录

  • 预编译指令
    • 预定义宏
    • #define宏定义
      • 注意
      • 带参宏
      • 带参宏和函数的区别
        • 不用内存分配,速度更快
        • 可以传递参数类型
      • #undef
      • 宏定义中的特殊符号(了解)
        • # 参数转字符串
        • ## 连接参数
        • #@ 参数转字符
        • __VA_ARGS__
    • #if条件编译
    • #include头文件包含

预编译指令

一个C/C++程序,在运行前大概可以经过四个阶段,预处理、编译、连接、汇编(详细会在后面更新计算机组成原理和操作系统时讲解)

预处理程序就是对源文件进行一些文本方面的操作,比如文本替换、文件包含、删除部分代码等,而在C/C++开发中,由于预处理简介,好用,所以在C/C++中会涉及到大量的预处理指令,比如 #include、#define 等,如下表所示(C/C++中凡是一#开头)

指令作用
#define
#undef
定义宏
取消宏定义
#include包含头文件
#if
#else
#elif
#endif
条件编译
#ifdef
#ifndef
判断是否定义了某个宏
#program设定编译器的状态或者是指示编译器完成一些特定的动作
#error当预处理器预处理到*#error*命令时将停止编译并输出用户自定义的错误消息

预定义宏

预定义宏是C语言中标准编译器预先定义的宏,在ANSI标准中C程序有5个预定义宏可以直接使用。

说明
_LINE_当前编译的代码的行号
_FILE_当前编译文件的源文件名
_DATE_当前源程序创建的日期
_TIME_当前源程序创建的时间
_FUNCTION_当前正在被访问的函数名

例如:

#include <stdio.h>

int main()
{

	printf("%s\n", __DATE__);
	printf("%s\n", __TIME__);
	printf("%s\n", __FILE__);
	printf("%d\n", __LINE__);
	printf("%s\n", __FUNCTION__);    
	return 0;
}

结果:

Sep 12 2024
11:18:04
C:\Users\W Y\Desktop\DesignPatterns\tmp\main.c
10
main

注意:__FUNCTION__调bug特别好用,🤠

#define宏定义

#define 叫做宏定义命令,它也是C语言预处理命令的一种。所谓宏定义,就是用一个标识符来表示一个表达式,如果在后面的代码中出现了该标识符,那么就全部替换成指定的表达式。

宏定义形式:

#define 宏名  stuff	//stuff为替换内容

【示例】

#inlcude<stdio.h>
#define NAME "wy"
int main()
{
    printf("%s\n",NAME);
    return 0;
}

/*
运行结果:wy
*

这个案例很明显,用NAME代替了“wy”

注意

如果宏定义的是一个运算表达式,可能会出现歧义,如下:

【示例】

#include<stdio.h>
#define EXP 2*5+1

int main()
{
    int ret = 3 * EXP;
    printf("ret:%d\n", ret);

    return 0;
}

/*
结果:运行结果:ret:31
*/

这显然是不正确的,应该输出33才对,那为什么是31呢?

请记住宏只是简单地替换,根据这个规则我们来替换一下:

int ret = 3*2*5+1;

原来宏替换不会自动计算值,而是直接复制过来,所以运算的顺序就对了,故解决方法可以添加()

#define EXP (2*5+1)

替换后代码如下:

int ret = 3*(2*5+1);

这个就是我们想要的结果33了。

带参宏

  • C语言允许宏带有参数。在宏定义中的参数称为形式参数,在宏调用中的参数称为实际参数。

  • 对带参数的宏,在调用中,不仅要宏展开,而且要用实参去代换形参

  • 带参宏定义的一般形式为:

#define 宏名(形参表) stuff
  • 形参列表是一个由逗号分隔的符号列表,它们可能出现在stuff中。参数列表的左括号必须与name紧邻。如果两者之间有任何空白存在,参数列表就会被解释为stuff 的一部分。

【示例】下面是一个带参宏,它接受一个参数,用来计算数值的平方。

#include<stdio.h>
#define SQUARE(number) (number*number)
int main()
{
    printf("%d\n", SQUARE(3));
    return 0;
}

那接着看下面的代码,你会新的天地

printf("%d\n",SQUARE(3+2));

我们想要计算(3+2)的平方,即5的平方,但是输出结果却是11,更具宏只是简单地替换原则,可以展开为如下样式

printf("%d\n",(3+2*3+2));

注意记住一句话:宏定义只是简单的替换关系

带参宏和函数的区别

不用内存分配,速度更快

在带参宏定义中,不会为形式参数分配内存,因此不必指明数据类型。

带参宏非常频繁的用于执行简单的计算,比如在两个表达式中寻找其中较大的一个:

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

为什么不用函数来完成这个任务呢?原因有两个:

  • 速度更快
  • 不用担心类型,因为他不会为形势参数分配内存,只是简单的代替而已
printf("%d\n",MAX(3,6));
printf("%lf\n",MAX(3.14,5.20));
printf("%c\n",MAX('a','A'));

注意:他只是代替,不会在编译中检查,故不要用它实现太复杂测替换

可以传递参数类型

无法用函数实现对变量类型的传递,比如:下面这个宏,第一个参数是一种类型,它无法作为函数参数进行传递。

#define MALLOC(type,size) malloc(sizeof(type)*size)
...
int*pn = MALLOC(int,10);
char*ps = MALLOC(char,20);

【示例】高难度:用宏定义实现一个foreach循环,用来快捷遍历数组。

#include<stdio.h>

#define foreach(val,arr)  \
for (size_t i = 0, ctr = 0; i < sizeof(arr)/sizeof(arr[0]); i++,ctr = 0)\
    for (val = arr[i]; ctr < 1; ++ctr)

int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    foreach (int a, arr)
    {
        printf("%d ", a);
    }
    
    char* str[] = { "hello","world" };
    foreach(char* val, str)
    {
        puts(val);
    }
    return 0;
}

宏和函数对比

属性#define函数
代码长度每次使用时,宏代码都被插入到程序中。除了非常小的宏之外,程序的长度将大幅度增长.函数代码只出现于一个地方;每次使用这个函数时, .都调用那个地方的同一份代码
执行速度更快存在函数调用/返回的额外开销
操作符优先级宏参数的求值是在所有周围表达式的上下文环境里,除.非它们加上括号,否则邻近操作符的优先级可能会产生不可预料的结果函数参数只在函数调用时求值一次,它的结果值传递给函数。表达式的求值结果更容易预测
参数类型宏与类型无关。只要对参数的操作是合法的,它可以使用于任何参数类型函数的参数是与类型有关的。如果参数的类型不同,就需要使用不同的函数,即使它们执行的任务是相同的

#undef

这条预处理指令用于移除一一个宏定义。如果一个现存的名字需要被重新定义,那么它的旧定义首先必须用#undef移除。

宏定义中的特殊符号(了解)

# 参数转字符串

使用#可以把宏参数变成一个字符串。

#define toString(value) #value
...
puts(toString(我是顽石老师));    
## 连接参数

使用##可以把宏参数连接在一起。

#define VAL(val) val##_maye
...
int VAL(one) = 20;
printf("%d\n", one_maye);    
#@ 参数转字符

使用#@可以吧宏参数变成一个字符。

#define toChar(ch) #@ch
...
printf("%c", toChar(1));
VA_ARGS

__VA_ARGS__是一个可变参数的宏,很少人知道这个宏,这个可变参数的宏是新的C99规范中新增的,目前似乎只有gcc支持(VC6.0的编译器不支持)。
实现思想就是**宏定义中参数列表的最后一个参数为省略号(也就是三个点)。**感兴趣的可以了解一下C语言的含参变量

#if条件编译

一般情况下,源程序中所有的行都参加编译。但有时希望对其中一部分内容只在满足一定条件才进行编译,也就是对一部分内容指定编译的条件,这就是“条件编译”。有时,希望当满足某条件时对一组语句进行编译,而当条件不满足时则编译另一组语句。

先来学习一个别的指令,#error用来输出错误信息并终止编译。

#error 亲,欢迎学习C语言!

在这里插入图片描述

常见的条件编译指令

条件编译指令说 明
#if如果条件为真,则执行相应操作
#elif如果前面条件为假,而该条件为真,则执行相应操作
#else如果前面条件均为假,则执行相应操作
#endif结束相应的条件编译指令
#ifdef如果该宏已定义,则执行相应操作
#ifndef如果该宏没有定义,则执行相应操作

格式(很常用)

#if 条件表达式
    程序段1
#else
    程序段2
#endif

功能和C语言的条件语句类似,不同的是条件编译中的条件表达式必须为能够在编译期间计算出结果的。(不能为变量)

注意,必须使用 #endif 结束该条件编译指令。

int main()
{
#if 1
    printf("#if\n");
#else
    printf("#else\n");
#endif

#ifdef SHOW     	//或者 #ifndef
    printf("#ifdef\n");
#else
    printf("#else\n");
#endif

#if defined(SHOW)   //或者 #if !defined(SHOW)
    printf("#if defined\n");
#else
    printf("#else\n");
#endif
    return 0;
}

#include头文件包含

#inlcude指令我们已经用过很多次了,它会把我们包含的文件全部复制到包含位置。实际上不仅能包含.h文件,.c文件也行,甚至任意文件都行。

标准库文件包含

  • 对于编译器已经提供好的库文件,我们可以用过下面这种语法。
    #include<filename>,编译器会去标准库中去查找

本地文件包含

  • 对于自定义的库文件,我们可以使用下面这种语法。
    #include“filename”,首先会去本地文件中找,找不到再去标准库中去查找

重复包含头文件

在一个文件中直接或间接多次包含同一文件,可能会导致问题,比如:

  • 当文件中有对变量或类型的定义时,多次包含该文件这个变量或类型就会被多次定义。

在这里插入图片描述

  • 但是这种情况,很容易,被人大学,但是我们看这一种情况:

在这里插入图片描述

解决方法:条件编译

以【demo.h】头文件为例

#ifndef _DEMO_H_
#define _DEMO_h_
/*你的代码*/
#endif  

在每一次定义.h文件中,都在前面加上以上上面这种格式的if条件编译

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

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

相关文章

说话人脸生成

说话人脸生成是一种技术&#xff0c;它通过音频信号来驱动和合成视频中的人脸图像&#xff0c;使得人脸的口型和表情与音频中的声音同步。这种技术主要应用于视频制作、虚拟现实、动画电影等领域&#xff0c;能够提升视听媒体的自然性和沉浸感。 使用的技术 说话人脸生成通常…

跨界融合:EasyDSS+无人机视频直播推流技术助力行业多场景应用

随着科技的飞速发展&#xff0c;无人机技术与流媒体技术的结合正逐步改变着多个行业的运作模式。其中&#xff0c;EasyDSS互联网视频云服务与无人机视频直播推流技术的结合&#xff0c;更是为警务安防、赛事直播、农业监测等多个领域带来了前所未有的变革。本文将深入探讨EasyD…

数据结构C //线性表(链表)ADT结构及相关函数

数据结构&#xff08;C语言版&#xff09;严蔚敏 吴伟民 线性表&#xff08;链表&#xff09;ADT结构及相关函数 环境&#xff1a;Linux Ubuntu&#xff08;云服务器&#xff09; 工具&#xff1a;vim 代码块&#xff08;头文件&#xff0c;函数文件&#xff0c;主文件&…

全球公认最厉害思想家颜廷利谈野鸡扮凤、犬饰猛虎和地蛇慕龙

在现代社会&#xff0c;我们依然能够看到许多“野鸡扮凤”、“犬饰猛虎”和“地蛇慕龙”这样的故事上演。这些故事背后&#xff0c;往往隐藏着对名誉、地位和权力的渴望&#xff0c;以及为了达到目的而不择手段的行为。 最近&#xff0c;宁夏银川的杨韶山就成为了这样一个故事…

GaussDB关键技术原理:高弹性(四)

书接上文GaussDB关键技术原理&#xff1a;高弹性&#xff08;三&#xff09;从段页式技术方面对GaussDB高弹性能力进行了解读&#xff0c;本篇将从hashbucket扩容方面继续介绍GaussDB高弹性技术。 4 hashbucket扩容 基于hashbucket表的扩容整体流程主要包含三个步骤&#xff…

【网络安全】-文件下载漏洞-pikachu

文件操作漏洞包括文件上传漏洞&#xff0c;文件包含漏洞&#xff0c;文件下载漏洞。 文章目录  前言 什么是文件下载漏洞&#xff1f; 1.常见形式&#xff1a; 常见链接形式&#xff1a; 常见参数&#xff1a; 2.利用方式&#xff1a; 3.举例&#xff1a;pikachu不安全的文件…

大数据-132 - Flink SQL 基本介绍 与 HelloWorld案例

Flink SQL Flink SQL 是 Apache Flink 提供的一种高层次的查询语言接口&#xff0c;它基于 SQL 标准&#xff0c;为开发者提供了处理流式数据和批处理数据的能力。Flink SQL 允许用户使用标准 SQL 查询语言在数据流和数据表上执行复杂的操作&#xff0c;适用于多种应用场景&am…

如何模拟一个小程序项目打包的流程

一、Uni-app 执行 yarn run dev:mp-weixin后会发生什么 &#xff08;一&#xff09;准备工作 克隆项目&#xff1a;创建以 typescript 开发的工程&#xff08;如命令行创建失败&#xff0c;请直接访问 https://gitee.com/dcloud/uni-preset-vue/repository/archive/vite-ts.z…

htop、free -h对于可用内存显示不同的区别

htop中Mem包含了缓存和缓存区&#xff0c; free -h查看 used free buff/cache 上面htop显示的mem&#xff0c; 1、我看我还能用多少内存&#xff0c;看哪里 看free -h 中的free 2、buff/cache 是啥 缓存缓存区占用&#xff0c;htop显示的效果是把这个也算在一块了&#…

C# WinForm:禁用Panel容器滚动条自动移动位置的功能

1.在WinForm项目中新建一个类&#xff1a; 2.类里面的内容&#xff0c;重写Panel的这个方法 3.编译后这个控件就出现在工具箱了 4.然后用这个新Panel控件就好了 5.完事大吉。

【Python机器学习系列】建立super learner模型预测心脏疾病(案例+源码)

这是我的第353篇原创文章。 一、引言 Super learner 是 Vander Laan et al.&#xff08;2007&#xff09;提出的一种基于损失函数的组合预测的学习算法。Super learner算法基于交叉验证理论&#xff0c;通过加权的方式组合多种候选算法&#xff0c;从而构造一种最小交叉验证风…

Hadoop集群开启后使用jps命令查看发现没有NameNode、SecondaryNameNode、DataNode、NodeManager进程,缺少进程。

今天安装Hadoop集群,安装完成使用jps命令查看发现没有NameNode进程,别人jps后都有6个在跑,我就两个。看到别人的 我的👉。都看懵了。。。 处理NameNode不启动的问题 检查ip地址是否是namenode所在节点的ip。 要检查 IP 地址是否是 NameNode 所在节点的 IP 地址,你可以通…

大数据之Spark(二)

9.4.3、RDD持久化 RDD之间进行相互迭代计算&#xff08;Transformation的转换&#xff09;&#xff0c;当执行开启&#xff0c;新RDD的生成代表旧RDD消失。如果有的rdd需要重复使用就需要将rdd缓存&#xff0c;rdd.cache()或rdd.persist()。清理缓存rdd.unpersist() 缓存特点&…

Python项目虚拟环境(超详细讲解)

课 程 推 荐我 的 个 人 主 页&#xff1a;&#x1f449;&#x1f449; 失心疯的个人主页 &#x1f448;&#x1f448;入 门 教 程 推 荐 &#xff1a;&#x1f449;&#x1f449; Python零基础入门教程合集 &#x1f448;&#x1f448;虚 拟 环 境 搭 建 &#xff1a;&#x1…

android 生SHH,并配置

1. ssh-keygen -t rsa -b 4096 -C "XXXXxx.com" 2. vim ~/.ssh/config 新建一个文件&#xff1a;~/.ssh/config&#xff1a;并将下列的内容放入&#xff1a; Host * HostKeyAlgorithms ssh-rsa PubkeyAcceptedKeyTypes ssh-rsa 4.得到XXX.pub去添加ssh 5.克隆

【Java】方法1_定义方法,完整格式,原理

文章目录 前言 一、方法是什么&#xff1f; 方法的完整格式 1、有返回值的函数 2、无返回值的函数 二、方法使用常见的问题三、方法在计算机中执行的原理总结 前言 学习记录方法 一、方法是什么&#xff1f; 方法是一种语法结构&#xff0c;它可以把一段代码封装成一个功能…

python绘制3d建筑

import matplotlib.pyplot as plt import numpy as np from mpl_toolkits.mplot3d.art3d import Poly3DCollection# 随机生成建筑块数据 def generate_building_blocks(num_blocks, grid_size100, height_range(5, 50), base_size_range(10, 30)):buildings []for _ in range(…

<<编码>>第 11 章 逻辑门电路--开关电路 示例

网络电路 info::操作说明 鼠标单击开关切换开合状态 primary::在线交互操作链接 https://cc.xiaogd.net/?startCircuitLinkhttps://book.xiaogd.net/code-hlchs-examples/assets/circuit/code-hlchs-ch11-01-network-circuit.txt 继电器开关电路 info::操作说明 鼠标单击开关切…

python-游戏自动化(二)(OpenCV图像运用基础)

OpenCV OpenCV简介 首先我们来了解一下&#xff0c;OpenCV是什么&#xff1f; OpenCV 是计算机视觉中经典的专用库&#xff0c;其支持多语言、跨平台&#xff0c;功能强大。 OpenCV现在支持与计算 机视觉和机器学习有关的多种算法&#xff0c;并且正在日益扩展…

基于vue框架的宠爱有佳宠物医疗管理系统4x10z(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;用户,宠物信息,医生,用户挂号,病历记录,科室信息,药物信息 开题报告内容 基于Vue框架的宠爱有佳宠物医疗管理系统开题报告 一、引言 随着现代社会生活节奏的加快&#xff0c;宠物已成为许多家庭不可或缺的一员。宠物不仅带来了欢乐与…