C语言-程序环境和预处理(2)--带副作用的宏参数,宏与函数的对比,#undef,条件编译,文件包含

news2025/3/13 6:33:13

前言

上一篇文章–《C语言-程序环境和预处理(1)》讲述了程序的翻译环境和执行环境,编译、连接,预定义符号,#define,#符号和##符号的相关知识。
链接: 《C语言-程序环境和预处理(1)》
本篇文章,讲述带副作用的宏参数,宏与函数的对比,#undef,条件编译,文件包含的相关知识。

文章目录

  • 前言
  • 1.带副作用的宏参数
  • 2.宏与函数的对比
    • 2.1 宏的命名约定
    • 2.2 命令行定义
  • 3.#undef宏讲解
  • 4.条件编译
    • 4.1 #if #endif
    • 4.2 多个分支的条件编译
    • 4.3 判断是否被定义
    • 4.4 嵌套指令
  • 5.文件包含
    • 5.1 头文件的包含
    • 5.1 嵌套文件的包含
  • 6.其他预处理指令


1.带副作用的宏参数

我们来看一个代码:
最后输出的a,b,m分别是多少?

#define MAX(x,y) ((x)>(y)?(x):(y))
int main()
{
	int a = 3;
	int b = 5;
	int m = MAX(a++, b++);
	printf("%d\n", m);
	printf("%d\n", a);
	printf("%d\n", b);
	return 0;
}

你第一次做的时候是否和我一样,最后输出的m是5,a是4,b是6。如果是这样的,那么恭喜你和我一样,做错了。

正确的解析如下:

	int m = ((a++) > (b++) ? (a++) : (b++));
	          3    >  5    
	        a=4     b=6     no        6
	  m=6                             b=7

正确答案应该是a=4,b=7,m=6.

其运算步骤应该是这个样子的,不是一开始我们想当然的那样,这就是带有副作用的宏参数。

当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。

2.宏与函数的对比

宏和函数运行时的步骤对比如下:
在这里插入图片描述

函数的缺点:

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

宏的缺点:

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

除此之外,宏可以完成一件函数永远做不到的事情:
我们如果某些时候想给某个东西传类型,那么函数显然无法做到,比如:
开辟空间:

int*p = (int*)malloc(10 * sizeof(int));
//我们想要便捷的写malloc(10,int),这样函数显然做不到,但是宏可以做到

 #define MALLOC(num, type)   (type*)malloc(num * sizeof(type))
int*p = MALLOC(10, int);//这样就可以了

提示:宏是不能递归的!!

2.1 宏的命名约定

一般来讲函数的宏的使用语法很相似。所以语言本身没法帮我们区分二者。

那我们平时的一个习惯是:
把宏名全部大写
函数名不要全部大写

2.2 命令行定义

许多C 的编译器提供了一种能力,允许在命令行中定义符号。用于启动编译过程。

例如:当我们根据同一个源文件要编译出不同的一个程序的不同版本的时候,这个特性有点用处。(假定某个程序中声明了一个某个长度的数组,如果机器内存有限,我们需要一个很小的数组,但是另外一个机器内存大写,我们需要一个数组能够大写。)

比如在Linux,gcc编译器中就可以这样使用。
这个功能很少见,不常使用,只需知道即可!

3.#undef宏讲解

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

#define	M 100

int main()
{
	int m = M;
	printf("m = %d\n", m);

#undef M
	int m = M;//错误,宏M已被删除不可使用
#define M 1000
	//删除后还可以重新定义M
	return 0;
}

4.条件编译

在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的。因为我们有条件编译指令。

比如说: 调试性的代码,删除可惜,保留又碍事,所以我们可以选择性的编译。

常见的条件编译有如下:

4.1 #if #endif

int main()
{
#if 1==2
	printf("hehe\n");
#endif
	return 0;
}

在这里插入图片描述

4.2 多个分支的条件编译

int main()
{
#if 1==1
	printf("我是帅哥\n");
#elif 2==1
	printf("我是帅人\n");
#elif 3==1
	printf("我太帅了\n");
#else
	printf("帅不可挡\n");
#endif
	return 0;
}

在这里插入图片描述

4.3 判断是否被定义

int main()
{
#if defined(M)
	printf("snan");
#endif
	return 0;
}

在这里插入图片描述
其还有一种写法:

int main()
{
//#if defined(M)
//	printf("snan");
//#endif

#ifdef M
	printf("snan");
#endif

	return 0;

}

当然还有#if !defined(M)表示如果没有定义就怎么怎么样
#ifndef M便是第二种写法的如果未定义怎么怎么样

4.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

就是#if套#if,就是稍微复杂,一条一条理清楚是不难的。

5.文件包含

5.1 头文件的包含

头文件的包含有两种形式:
1.包含本地文件(自己的.h文件)
#include “xxx.h”

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

2.包含标准库的头文件
#include <stdio.h>

查找策略:查找头文件直接去标准路径下去查找,如果找不到就提示编译错误。

提示:#include “stdio.h”这样写也是没问题的,但是会拉低代码的运行速率,降低查找头文件的效率!!

5.1 嵌套文件的包含

在工作中往往避免不了这样一种情况:
在这里插入图片描述
comm.h和comm.c是公共模块。
test1.h和test1.c使用了公共模块。
test2.h和test2.c使用了公共模块。
test.h和test.c使用了test1模块和test2模块。
这样最终程序中就会出现两份comm.h的内容。这样就造成了文件内容的重复。

如何避免这样的问题,提高编译器的工作效率呢?
每个头文件的开头写

#ifndef __TEST_H__
#define __TEST_H__
//这部分是头文件的内容
#endif //__TEST_H__

TEST_H是根据头文件的名字来的(不是只能写TEST_H)

或者:

#pragma once

就可以避免头文件的重复引入。我们在VS上创建一个头文件时,它会自动输入这句话。

6.其他预处理指令

1.#error

编译程序时,只要遇到#error就会生成一个编译错误的提示消息,并停止编译

2.#pragma

可以设定编译程序完成一些特定的动作(可以通过编译程序的菜单设定,也可以直接写在源代码中),它允许向编译程序传送各种指令。例如:编译程序可能有一种选择,它支持对程序执行的跟踪,可用#pragma语句指定一个跟踪选项

3.#line

可以改变当前行数和文件名称,他们时在编译程序中预先定义的标识符命令的基本形式
#line number[“filename”]

4.#pragma pack()

C语言预处理指令#pragma pack()用于控制结构体、联合体和类成员的对齐方式。在C语言中,编译器通常会根据特定的对齐规则将结构体、联合体和类成员对齐到特定的边界上,以提高内存访问效率。#pragmapack()指令通过设置对齐边界值,控制编译器对结构体、联合体和类成员的对齐方式。该指令的参数是一个非负整数,默认情况下通常是4或8,表示对齐边界值为4字节或8字节。具体来说,当设置对齐边界值为n时,编译器会将结构体、联合体和类成员按照n字节对齐,即每个成员的起始地址必须是n的倍数。这可以防止因为内存对齐不当而导致的性能下降或错误,尤其在与硬件交互或与其他系统进行通信时很重要。

当然还有更多的预处理指令,大家可以自己学习。

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

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

相关文章

【Linux】详解Linux中的Makefile文件

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; &#x1f525;c系列专栏&#xff1a;C/C零基础到精通 &#x1f525; 给大…

idea无法通过vpn连接到数据库

本人之前遇到情况当打开vpn时&#xff0c;使用工具navicat可以连接到数据库&#xff0c;但是IDEA连接不到。这就很奇怪了&#xff0c;于是在网上大量搜寻解决方案&#xff0c;终于找到&#xff1a; 连接异常&#xff1a; 因为是Springboot项目&#xff0c;可以在启动类的配置…

SLAM从入门到精通(dwa速度规划算法)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 要说搜路算法&#xff0c;这个大家都比较好理解。毕竟从一个地点走到另外一个地点&#xff0c;这个都是直觉上可以感受到的事情。但是这条道路上机…

入侵检测代码

在人工智能中有个入侵检测&#xff1a;当检测到的目标位于指定区域内才算是入侵&#xff0c;思路很简单&#xff0c;判断相关坐标即可&#xff1a; from matplotlib import pyplot as plt, patches from shapely.geometry import Polygon, Pointdef is_intrusion(target_box, …

HTML基础入门03

1.表单标签 表单是让用户输入信息的重要途径. 分成两个部分: 表单域: 包含表单元素的区域. 重点是 form 标签. 表单控件: 输入框, 提交按钮等. 重点是 input 标签. 1.1form标签 <form action"test08.html">hello world </form> 描述了要把数据按照什…

特斯拉pre-test (Go)

特斯拉pre-test &#xff08;Go&#xff09; 1 Q12 Q23 Q3 1 Q1 原文&#xff1a; You are given an implementation of a function Solution that, given a positive integer N, prints to standard output another integer, which was formed by reversing a decimal repres…

互联网Java工程师面试题·Java 总结篇·第五弹

目录 47、Java 语言如何进行异常处理&#xff0c;关键字&#xff1a;throws、throw、try、catch、finally 分别如何使用&#xff1f; 48、运行时异常与受检异常有何异同&#xff1f; 49、列出一些你常见的运行时异常&#xff1f; 50、阐述 final、finally、finalize 的区别…

【Java学习之道】指引篇:从入门到入世

引言 你是否曾为找不到适合自己的Java学习之路而烦恼&#xff1f;是否想摆脱混乱的Java知识体系&#xff0c;找到一条从入门到精通的捷径&#xff1f;来《Java学习之道》吧&#xff0c;本专栏为你量身打造&#xff0c;让我们一起轻松踏上Java学习之旅&#xff01; 第一章、Jav…

AD620A运算放大器的原理、应用和性能特点 | 百能云芯

在电子领域&#xff0c;AD620A是一种广泛应用的运算放大器&#xff0c;也被称为运放。它在信号放大、传感器接口和测量应用中扮演着重要的角色。接下来云芯将带您深入探讨AD620A运放的原理、应用领域以及性能特点&#xff0c;以帮助您更好地理解它的作用和价值。 AD620A是一种精…

CentOS有IP地址,连接不上Xshell或使用Xshell时突然断开

问题原因&#xff1a;未在电脑主机的网络中进行IP地址配置 解决办法&#xff1a; 1.打开控制面板&#xff0c;选择‘网络与共享中心’ 2.选择“更改适配器设置” 3.右键点击以太网3“属性” 4.选择协议版本4&#xff0c;点击属性 5.IP地址填写CentOS的IP地址&#xff1a;192.…

微信小程序进阶——Flex弹性布局轮播图会议OA项目(首页)

目录 一、Flex弹性布局 1.1 什么是Flex弹性布局 1.1.1 详解 1.1.2 图解 1.1.3 代码演示效果 1.2 Flex弹性布局的核心概念 1.3 Flex 弹性布局的常见属性 1.4 Flex弹性布局部分属性详解 1.4.1 flex-direction属性 1.4.2 flex-wrap属性 1.4.3 flex-flow属性 1.4.4 ju…

Redis数据结构之quicklist

前言 为了节省内存&#xff0c;Redis 推出了 ziplist 数据类型&#xff0c;采用一种更加紧凑的方式来存储 hash、zset 元素。因为查找的时间复杂度是 O(N)&#xff0c;且写入需要重新分配内存&#xff0c;所以它仅适用于小数据量的存储&#xff0c;而且它还存在 连锁更新 的风…

Redis AOF持久化和ReWrite

前言 Redis 的 RDB 持久化机制简单直接&#xff0c;把某一时刻的所有键值对以二进制的方式写入到磁盘&#xff0c;特点是恢复速度快&#xff0c;尤其适合数据备份、主从复制场景。但如果你的目的是要保证数据可靠性&#xff0c;RDB 就不太适合了&#xff0c;因为 RDB 持久化不…

Epoch、批量大小、迭代次数

梯度下降 它是 机器学习中使用的迭代 优化算法&#xff0c;用于找到最佳结果&#xff08;曲线的最小值&#xff09;。 坡度 是指 斜坡的倾斜度或倾斜度 梯度下降有一个称为 学习率的参数。 正如您在上图&#xff08;左&#xff09;中看到的&#xff0c;最初步长较大&#…

2023年中国半导体缺陷检测设备市场规模及发展趋势分析[图]

前道检测设备帮助晶圆厂在更快时间内提升芯片良率&#xff0c;按功能可分为参数量测、缺陷检测。前道检测设备按功能可分为参数量测、缺陷检测。 半导体缺陷检测设备分类 资料来源&#xff1a;共研产业咨询&#xff08;共研网&#xff09; 2023-2029年中国半导体缺陷检测设备行…

libcurl库使用

libcurl介绍 libcurl是一个跨平台的网络协议库&#xff0c;支持http, https,ftp, gopher, telnet, dict, file, 和ldap 协议。libcurl同样支持HTTPS证书授权&#xff0c;HTTP POST,HTTP PUT, FTP 上传, HTTP基本表单上传&#xff0c;代理&#xff0c;cookies和用户认证。 基本…

linux加密和安全

sudo实现授权 添加 vim /etc/sudoers luo ALL(root) /usr/bin/mount /deb/cdrom /mnt/ 切换luo用户使用 sudo mount /dev/cdrom /mnt %sudo ALL(ALL:ALL) ALL %sudo 表示该规则适用于sudo用户组中的所有成员。 ALL(ALL:ALL) 表示可以在任何主机上&#xff0c;以任何用户身份来…

2023.10.17 关于 wait 和 notify 的使用

目录 引言 方法的使用 引入实例&#xff08;wait 不带参数版本&#xff09; wait 方法执行流程 wait 和 notify 组合实例 wait 带参数版本 notify 和 notifyAll 的区别 经典例题 总结 引言 线程最大的问题是抢占式执行&#xff0c;随机调度虽然线程在内核里的调度是随…

UITesting 界面测试

1. 创建界面测试视图 UITestingBootcampView.swift import SwiftUI/// 界面测试 ViewModel class UITestingBootcampViewModel: ObservableObject{let placeholderText: String "Add name here..."Published var textFiledText: String ""Published var…

CVE-2021-26084 漏洞分析

基础知识 Velocity .vm 结尾的文件一般为Velocity模板文件$action $action 是 velocity 上下⽂中的⼀个变量&#xff0c;⼀般在进⾏模板渲染前会设置到 context ⾥⾯。$action 是当前访问路由对应的具体 Action 类。$action.xxx 表⽰取对应 Action 类的 xxx 属性值 ${} 和 $!…