C语言-程序环境和预处理(2)

文章目录

  • 预处理详解
  • 1.预定义符号
  • 2.#define
    • 2.1#define定义的标识符
    • 2.2#define定义宏
    • 2.3#define替换规则
      • 注意事项:
    • 2.4#和##
      • #的作用
    • ##的作用
    • 2.5带副作用的宏参数
    • 2.6宏和函数的对比
      • 宏的优势:
      • 宏的劣势:
      • 宏和函数的一个对比
      • 命名约定
  • 3.undef
  • 4.条件编译
  • 5.文件包含
    • 5.1文件包含的方式
    • 5.2嵌套文件的包含

预处理详解

1.预定义符号

__FILE__ //进行编译的源文件
__LINE__ //文件当前的行号
__DATE__ //文件被编译的日期
__TIME__ //文件被编译的时间
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义
#include<stdio.h>
int main()
{
	printf("file:%s\nline:%d\n", __FILE__, __LINE__);
	printf("date:%s\ntime:%s\n", __DATE__, __TIME__);
	return 0;
}

在这里插入图片描述
注:输出的第一行是我这个文件的路径。

2.#define

2.1#define定义的标识符

#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__ )

注:不要在标识符后面加","容易出事

2.2#define定义宏

#define MUL(x,y) x*y
int main()
{
	int a = 10;
	int b = 20;
	int c = MUL(a, b);
	return 0;
}

结果c是200
但若是这样

#define MUL(x,y) x*y
int main()
{
	int a = 10;
	int b = 20;
	int c = MUL(a+1, b);
	return 0;
}

结果就是30,而不是220
原因是置于程序中,预处理器就会将MUL(a+1,b)替换为a+1*b
这里还有一个宏定义

#define DOUBLE(x) (x) + (x)

定义中我们使用了括号,想避免之前的问题,但是这个宏可能会出现新的错误

int a = 5;
printf("%d\n" ,10 * DOUBLE(a));

这将打印什么值呢?

printf ("%d\n",10 * (5) + (5));

这个问题的解决办法是在宏定义表达式两边加上一对括号就可以了

#define DOUBLE( x) ( ( x ) + ( x ) )

看上去,好像打印100,但事实上打印的是55.
我们发现替换之后:

所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,
避免在使用宏时由于参数中 的操作符或邻近操作符之间不可预料的相互作用。

2.3#define替换规则

在程序中扩展#define定义符号和宏时,需要涉及几个步骤。

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

注意事项:

  1. 宏参数和#define 定义中可以出现其他#define定义的变量。但是对于宏,不能出现递归。
  2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。
#define MAX 100
printf("MAX:%d\n", MAX);

在这个中字符串MAX不会被替换

2.4#和##

#的作用

如何把参数插入到字符串中?
首先我们看看这样的代码:

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

都输出hello world
我们发现字符串是有自动连接的特点的

使用 # ,可以把一个宏参数变成对应的字符串

#define PRINT(X) printf("the value of " #X " is %d\n",X)


int main()
{
	int a = 10;
	int b = 20;
	PRINT(a);
	PRINT(b);
	return 0;
}

在这里插入图片描述

##的作用

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

#define CAT(X,Y) X##Y

int main()
{
	int abc123 = 100;
	printf("%d",CAT(abc, 123));
	return 0;
}

在这里插入图片描述

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

2.5带副作用的宏参数

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

例如:

x+1;//不带副作用
x++;//带有副作用
#define MAX(a, b) ( (a) > (b) ? (a) : (b) )
...
x = 5;
y = 8;
z = MAX(x++, y++);
printf("x=%d y=%d z=%d\n", x, y, z);//输出的结果是什么?

这里我们得知道预处理器处理之后的结果是什么:

z = ( (x++) > (y++) ? (x++) : (y++));

所以输出的结果是:

x=6 y=10 z=9

2.6宏和函数的对比

宏通常被应用于执行简单的运算。比如在两个数中找出较大的一个。
那为什么不用函数来完成这个任务?

原因有二:

宏的优势:

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

宏的劣势:

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

宏有时候可以做函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到。

#define MALLOC(num, type)\
(type *)malloc(num * sizeof(type))
...
//使用
MALLOC(10, int);//类型作为参数
//预处理器替换之后:
(int *)malloc(10 * sizeof(int));

宏和函数的一个对比

命名约定

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

3.undef

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

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

4.条件编译

在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的。因为我们有条件编译指令
比如说:
调试性的代码,删除可惜,保留又碍事,所以我们可以选择性的编译。

#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;
}

常见的条件编译指令:

1.
#if 常量表达式(如果为0,则不执行下面的语句,如果是非零则执行)
//...
#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

5.文件包含

我们已经知道, #include 指令可以使另外一个文件被编译。就像它实际出现于 #include 指令的地方
一样。
这种替换的方式很简单:
预处理器先删除这条指令,并用包含文件的内容替换。
这样一个源文件被包含10次,那就实际被编译10次。

5.1文件包含的方式

本地文件包含

#include "filename"

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

#include <filename.h>

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

5.2嵌套文件的包含

如果出现这样的场景:
在这里插入图片描述
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

或者:

pragma once

就可以避免头文件的重复引入。

以上就是本篇文章的内容了,很感谢你能看到这里
如果觉得内容对你有帮助的话,不妨点个关注
我会继续更新更高质量的内容,我们一同学习,一同进步!

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

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

相关文章

centos系统/dev/mapper/centos-root目录被占满的解决方式

最近在做虚拟机部署docker微服务时&#xff0c;发现磁盘内存占满&#xff0c;无法进行操作。open /var/lib/dpkg/info/libc6:amd64.templates: no space left on device接下来就写下我在备份虚拟机上如何解决根目录被占满的问题&#xff1a;1、查看虚拟机磁盘使用情况df -h可以…

D - 统计子矩阵 (双指针+前缀和+降维处理)

D - 统计子矩阵 &#xff08;双指针前缀和降维处理&#xff09; 1、问题 D - 统计子矩阵 2、分析 代码 &#xff08;1&#xff09;纯暴力做法&#xff1a; 这个做法就很简单了&#xff0c;我们直接枚举所有的子矩阵&#xff0c;然后在对每一个子矩阵内部的元素逐一累加起…

【计算机二级】综合题目

计算机二级python真题 文章目录计算机二级python真题一、《大学慕课 两问 》二、综合应用题——价值链三、基本操作题 ——信息输出一、《大学慕课 两问 》 附件中的文件data.txt 是教育部爱课程网中国大学MOOC平台的某个 HTML页面源文件,里面包含了我国参与MOOC建设的一批大学…

STM32之点亮一个LED小灯(轮询法)

目录 一、初始化GPIO口 二、按键点亮LED灯&#xff08;轮询法&#xff09; 一、初始化GPIO口 1、点亮LED小灯前&#xff0c;需要先初始化GPIO口 HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init) GPIO_TypeDef *GPIOx&#xff1a; //指初始化GPIO…

libvirt零知识学习5 —— libvirt源码编译安装(3)

接前一篇文章libvirt零知识学习4 —— libvirt源码编译安装&#xff08;2&#xff09; 在上篇文章及上上篇文章中构建libvirt的时候遇到了一个问题“ERROR: Problem encountered: YAJL 2 is required to build QEMU driver”。上篇文章讲到即使安装了相应的YAJL库仍然不能解决问…

HC小区管理系统window系统安装教程

实操视频 HC小区管理系统局域网window物理机部署教程_哔哩哔哩_bilibili 一、下载安装包 百度网盘&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1XAjxtpeBjHIQUZs4M7TsRg 提取码&#xff1a;hchc 或者 123盘 hc-window.zip官方版下载丨最新版下载丨绿色版下…

12个 Python 装饰器让代码cleaner

0 — 引言装饰器能在在不影响质量的情况下用更少的代码做更多的事情。Python 装饰器是强大的工具&#xff0c;可帮助你生成干净、可重用和可维护的代码。我等了很久才想了解这些抽象&#xff0c;现在我已经有了扎实的理解&#xff0c;本篇是为了帮助你也掌握这些对象背后的概念…

uni-app+uView如何轮播图滑动时改变背景颜色和导航栏颜色

今儿的创作欲很高涨哈 &#x1f604; 这也是在群里看到的&#xff0c;群友问如何在滑动&#xff08;或者自动滑动&#xff09;的时候背景颜色能跟着变 正好之前做过这个需求&#xff0c;也分享一下 首先&#xff0c;页面的组成分为三部分&#xff1a; 自定义navbar 页面背景轮…

JavaSE进阶之(十六)枚举

十六、枚举16.1 背景16.2 枚举类型16.3 EnumSet 和 EnumMap01、EnumSet02、EnumMap16.1 背景 在 Java 语言中还没有引入枚举类型之前&#xff0c;表示枚举类型的常用模式是声明一组 int 类型的常量&#xff0c;常常用的就是&#xff1a; public static final int SPRING 1; …

ElementUI学习笔记

目录 一、简单介绍 二、安装 1、下载 2、引入 三、布局 1、简介 2、使用 3、好处 四、布局容器 1、常见排布 2、调整样式 五、按钮 1、简单引用 2、改变样式 3、加载中效果 六、表格 1、简单使用 2、样式修改 七、对话框 1、简单使用 2、添加自定义内容 3、…

7个最受瞩目的 Python 库,提升你的开发效率

当今时代&#xff0c;数据分析和处理已经成为了各行各业中不可或缺的一环。Python作为一种非常流行的编程语言&#xff0c;为我们提供了许多强大的工具和库来处理不同类型的数据。 在这篇文章中&#xff0c;我将向您介绍七个非常有用的Python库&#xff0c;这些库各自有着独特…

js调用gpt3.5

参考链接&#xff1a;直接在前端调用 GPT-3 API 效果图&#xff1a; <!DOCTYPE html> <html><head><meta charset"UTF-8" /><title>ChatGPT Web Example</title><style>body {font-family: "Helvetica Neue"…

TimeQuest时序路径详解

&#x1f4a1; 基于TimeQuest软件来查看时序报告和分析时序路径 分析最坏传输路径 根据[FPGA典型时序路径的分析可知&#xff0c;最坏传输路径对应的建立时间&#xff08;setup time&#xff09;余量最小。所以&#xff0c;查看最坏传输路径也就是查看建立时间余量最小的路径。…

【Linux】安装DHCP服务器

1、先检测网络是否通 get dhcp.txt rpm -qa //查看软件包 rpm -qa |grep dhcp //确定是否安装 yum install dhcp //进行安装 安装完成后 查询 rpm -ql dhcp 进行配置 cd /etc/dhcp 查看是否有遗留dhcpd.conf.rpmsave 删除该文件 cp /usr/share/doc/dhcp-4.1.1/dhcpd.conf.sampl…

ChatGPT能代替Oracle DBA吗?用Oracle OCP(1z0-083)的真题测试一下。

让我们来看看ChatGPT不能通过Oracle OCP的考试&#xff1f; 文章目录引言测试过程总结和分析关于博主&#xff0c;姚远&#xff1a;Oracle ACE&#xff08;Oracle和MySQL数据库方向&#xff09;。Oracle MAA 大师。华为云MVP。《MySQL 8.0运维与优化》的作者。拥有 Oracle 10g和…

mysql和mysql2模块的区别!!(nodejs中的模块)

mysql 和 mysql2 都是 Node.js 中常用的操作 MySQL 数据库的模块&#xff0c;它们的主要区别是在实现方式上略有不同。 mysql&#xff1a;是 Node.js 中比较早期的 MySQL 操作模块&#xff0c;该模块底层使用的是回调函数&#xff08;callback&#xff09;来实现异步操作。在处…

ESP32设备驱动-DHT12温湿度传感器驱动

DHT12温湿度传感器驱动 文章目录DHT12温湿度传感器驱动1、DHT12介绍2、硬件准备3、软件准备4、驱动实现1、DHT12介绍 DHt12是经典DHT11温湿度传感器的升级版&#xff0c;完全向下兼容&#xff0c;精度更高&#xff0c;增加了I2C接口。 DHT12 具有单总线和标准 I 2C 两种通讯&…

一文7个步骤从0到1教你搭建Selenium 自动化测试环境

【导语】Selenium是一个用于Web应用程序测试的工具。Selenium测试直接运行在浏览器中&#xff0c;就像真正的用户在操作一样。支持自动录制动作和自动生成 .Net、Java、Perl等不同语言的测试脚本。本文详细介绍了搭建自动化测试环境所需的工具&#xff0c;让你学习自动化测试不…

不用科学上网,免费的GPT-4 IDE工具Cursor保姆级使用教程

大家好&#xff0c;我是可乐。 过去的一周&#xff0c;真是疯狂的一周。 GPT-4 震撼发布&#xff0c;拥有了多模态能力&#xff0c;不仅能和GPT3一样进行文字对话&#xff0c;还能读懂图片&#xff1b; 然后斯坦福大学发布 Alpaca 7 B&#xff0c;性能匹敌 GPT-3.5&#xff…

[图像识别]关于cv2库无法安装的故障问题解决,全网最全解决方案!本人亲身测试,参考了stackoverflow、51CTO等博客文章总结而成

本文范畴&#xff1a;故障排查 cv2 技术 库安装 Linux/Unix 笔记本系统&#xff1a;win10 python版本&#xff1a;3.10 故障问题&#xff1a;无法安装cv2库 适应对象&#xff1a;程序员新手、运维程序员、大学生、青少年对系统感兴趣的爱好者等等 文章目录前言一、cv2库是什么&…