编译和链接(2)

3. 预处理详解

3.2#define

3.2.5带副作用的宏参数

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

例如:

x+1;//不带副作用
x++;//带有副作用

MAX宏可以证明具有副作用的参数所引起的问题。

#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 

3.2.6 宏和函数对比 

宏通常被应用于执行简单的运算。

比如在两个数中找出较大的一个。

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

宏:                        函数:

1.计算                         1.函数的调用

                                    2.计算

                                    3.函数的返回

宏和函数的一个对比 

#define定义宏函数

每次使用时,宏代码都会被插入到程序中。除了非常 小的宏之外,程序的长度会大幅度增长函数代码只出现于一个地方;每 次使用这个函数时,都调用那个 地方的同一份代码

行 速 度

更快存在函数的调用和返回的额外开 销,所以相对慢一些

优 先 级

宏参数的求值是在所有周围表达式的上下文环境里, 除非加上括号,否则邻近操作符的优先级可能会产生 不可预料的后果,所以建议宏在书写的时候多些括 号。函数参数只在函数调用的时候求 值一次,它的结果值传递给函 数。表达式的求值结果更容易预 测。

作 用 的 参 数

参数可能被替换到宏体中的多个位置,所以带有副作 用的参数求值可能会产生不可预料的结果。函数参数只在传参的时候求值一 次,结果更容易控制。
参 数 类 型宏的参数与类型无关,只要对参数的操作是合法的, 它就可以使用于任何参数类型。函数的参数是与类型有关的,如 果参数的类型不同,就需要不同 的函数,即使他们执行的任务是 不同的。
调 试宏是不方便调试的函数是可以逐语句调试的
递 归宏是不能递归的函数是可以递归的

建议:

        如果逻辑比较简单,可以使用宏来实现

        但是如果计算逻辑比较复杂,就使用函数

3.2.7 命名约定

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

那我们平时的一个习惯是:

把宏名全部大写

函数名不要全部大写

3.3 #undef 

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

#undef NAME
//如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除。
#define M 100int main()
{int m = M;printf("m=%d\n", m);#undef Mint n = M;printf("%d\n", m);return 0;
}

3.4 命令行定义

许多C 的编译器提供了一种能力,允许在命令行中定义符号。用于启动编译过程。 例如:当我们根据同一个源文件要编译出不同的一个程序的不同版本的时候,这个特性有点用处。(假 定某个程序中声明了一个某个长度的数组,如果机器内存有限,我们需要一个很小的数组,但是另外一 个机器内存大写,我们需要一个数组能够大写。)

#include <stdio.h>
int main()
{int array [ARRAY_SIZE];int i = 0;for(i = 0; i< ARRAY_SIZE; i ++){array[i] = i;}for(i = 0; i< ARRAY_SIZE; i ++){printf("%d " ,array[i]);}printf("\n" );return 0;
}

编译指令:

//linux 环境

gcc -D ARRAY_SIZE=10 programe.c 

3.5 条件编译

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

比如说:        

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

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

常见的条件编译指令:

1.
#if 常量表达式//...
#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 OPTION1unix_version_option1();#endif#ifdef OPTION2unix_version_option2();#endif
#elif defined(OS_MSDOS)#ifdef OPTION2msdos_version_option2();#endif
#endif

3.6 文件包含

我们已经知道, #include 指令可以使另外一个文件被编译。就像它实际出现于 #include 指令的地方 一样。

这种替换的方式很简单: 预处理器先删除这条指令,并用包含文件的内容替换。 这样一个源文件被包含10次,那就实际被编译10次。

3.6.1 头文件被包含的方式

本地文件包含

#include "filename"

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

如果找不到就提示编译错误。 

Linux环境的标准头文件的路径:

/usr/include

VS环境的标准头文件的路径: 

C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include 

注意按照自己的安装路径去找。 

库文件包含

#include  <filename.h>

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

答案是肯定的,可以。 

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

3.6.2 嵌套文件包含 

如果出现这样的场景:

comm.h和comm.c是公共模块。

test1.h和test1.c使用了公共模块。

test2.h和test2.c使用了公共模块。

test.h和test.c使用了test1模块和test2模块。

这样最终程序中就会出现两份comm.h的内容。这样就造成了文件内容的重复。 

如何解决这个问题?

答案:条件编译。 

#error
#pragma
#line
...

每个头文件的开头写:

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

或者:

#pragma once

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

4. 其他预处理指令 

#error
#pragma
#line
...

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

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

相关文章

nmealib 库移植 - -编译报错不完全类型 error: field ‘st_atim’ has incomplete type

一、报错提示-不完全类型(has incomplete type) Compiling obj/main.o from main.c.. arm-linux-gcc -g -w -stdgnu99 -DLINUX -I./ -Inmealib/inc/ -c -o obj/main.o main.c In file included from /home/user/Desktop/nuc980-sdk/sdk/arm_linux_4.8/usr/include/sys/stat…

【c/python】用GTK实现一个带菜单的窗口

一、用python 在GTK中创建一个带菜单的窗口&#xff0c;可以通过使用Gtk.MenuBar、Gtk.Menu和Gtk.MenuItem组件来构建菜单。以下是一个基本的例子&#xff0c;展示了如何使用Python的PyGObject库创建一个简单的带菜单栏的GTK窗口。 import gi gi.require_version(Gtk, 3.0) f…

debian apt 装 mysql8

MySQL &#xff1a;&#xff1a; MySQL 8.0 参考手册 &#xff1a;&#xff1a; 2.5.5 使用来自 Oracle 的 Debian 软件包在 Linux 上安装 MySQL apt install -f lsb-release gnupg wget https://repo.mysql.com//mysql-apt-config_0.8.29-1_all.deb dpkg -i mysql-apt-config…

【软件测试】学习笔记-不同视角的软件性能与性能指标

本篇文章探讨新的测试主题&#xff1a;性能测试&#xff0c;因为性能测试的专业性很强&#xff0c;所以我会以从0到1的入门者视角&#xff0c;系统性地阐述性能测试的方法以及应用领域&#xff0c;用实例去诠释各种性能指标。 本篇文章站在全局的视角&#xff0c;帮你梳理软件性…

【Python机器学习】决策树——树的特征重要性

利用一些有用的属性来总结树的工作原理&#xff0c;其中最常用的事特征重要性&#xff0c;它为每个特征树的决策的重要性进行排序。对于每个特征来说&#xff0c;它都是介于0到1之间的数字&#xff0c;其中0代表“根本没有用到”&#xff0c;1代表“完美预测目标值”。特征重要…

【复现】网康科技-防火墙存在RCE漏洞_17

目录 一.概述 二 .漏洞影响 三.漏洞复现 1. 漏洞一&#xff1a; 四.修复建议&#xff1a; 五. 搜索语法&#xff1a; 六.免责声明 一.概述 网康下一代防火墙(NGFW)是一款可以全面应对应用层威胁的高性能防火墙。通过深入洞察网络流量中的用户、应用和内容&#xff0c;并…

Vulnhub靶机:driftingblues 2

一、介绍 运行环境&#xff1a;Virtualbox 攻击机&#xff1a;kali&#xff08;10.0.2.15&#xff09; 靶机&#xff1a;driftingblues2&#xff08;10.0.2.18&#xff09; 目标&#xff1a;获取靶机root权限和flag 靶机下载地址&#xff1a;https://www.vulnhub.com/entr…

删除排序链表中的重复元素

说在前面 &#x1f388;不知道大家对于算法的学习是一个怎样的心态呢&#xff1f;为了面试还是因为兴趣&#xff1f;不管是出于什么原因&#xff0c;算法学习需要持续保持。 题目描述 给定一个已排序的链表的头 head &#xff0c; 删除所有重复的元素&#xff0c;使每个元素只…

Vue-Awesome-Swiper基本能解决你所有的轮播需求(vue的问题)

在我们使用的很多ui库&#xff08;vant、antiUi、elementUi等&#xff09;中&#xff0c;都有轮播组件&#xff0c;对于普通的轮播效果足够了。但是&#xff0c;某些时候&#xff0c;我们的轮播效果可能比较炫&#xff0c;这时候ui库中的轮播可能就有些力不从心了。当然&#x…

Linux read命令教程:交互式读取用户输入(附案例详解和注意事项)

Linux read命令介绍 read命令是处理从键盘或其它输入设备读入字符的一个简单命令。常见用法就是在shell脚本中用来读取用户的输入。除此之外&#xff0c;也可以从文件中读取内容或处理由其它命令输出的内容。 Linux read命令适用的Linux版本 read命令在所有主流的Linux发行版…

springboot(ssm甘肃旅游管理系统 在线旅游景点管理系统 Java系统

springboot(ssm甘肃旅游管理系统 在线旅游景点管理系统 Java系统 开发语言&#xff1a;Java 框架&#xff1a;ssm/springboot vue JDK版本&#xff1a;JDK1.8&#xff08;或11&#xff09; 服务器&#xff1a;tomcat 数据库&#xff1a;mysql 5.7&#xff08;或8.0&#x…

YOLOv5改进 | 注意力篇 | CGAttention实现级联群体注意力机制 (全网首发改进)

一、本文介绍 本文给大家带来的改进机制是实现级联群体注意力机制CascadedGroupAttention,其主要思想为增强输入到注意力头的特征的多样性。与以前的自注意力不同,它为每个头提供不同的输入分割,并跨头级联输出特征。这种方法不仅减少了多头注意力中的计算冗余,而且通过增…

四、Qt 的第一个demo

在上一篇章节里《三、Qt Creator 使用》&#xff0c;我们介绍了如何使用Qt Creator创建一个简单的带窗体的demo&#xff0c;在这一章节里&#xff0c;我们详细讲解一下这个demo的文件组成&#xff0c;及主函数&#xff0c;并在UI上加一些控件&#xff0c;实现一些简单的功能。 …

Qt打包程序

添加链接描述

11Spring IoC注解式开发(下)(负责注入的注解/全注解开发)

1负责注入的注解 负责注入的注解&#xff0c;常见的包括四个&#xff1a; ValueAutowiredQualifierResource 1.1 Value 当属性的类型是简单类型时&#xff0c;可以使用Value注解进行注入。Value注解可以出现在属性上、setter方法上、以及构造方法的形参上, 方便起见,一般直…

leetcode-删除排序链表中的重复元素

83. 删除排序链表中的重复元素 题解&#xff1a; 要删除一个已排序链表中的所有重复元素&#xff0c;从而使每个元素只出现一次&#xff0c;我们可以使用一个指针来遍历这个链表&#xff0c;同时比较当前节点和它下一个节点的值。如果它们相等&#xff0c;我们就删除下一个节…

git修改历史(非最新)提交信息

二、修改最近第二次或更早之前的commit信息 当前有三次提交&#xff0c;从近到远分别为1、2、3 以修改第2次提交为例&#xff08;从最新往前数&#xff09; 1、使用命令git rebase -i HEAD~2 按i进入编辑模式&#xff0c;将对应的pick改为edit&#xff0c;然后ctrlc退出。最…

高并发场景下底层账务优化方案

在秒杀等场景中&#xff0c;经常会需要对库存、商家冻结资金做一些修改&#xff0c;而并发一旦上来后会导致这个修改动作频繁超时及失败&#xff0c;那么如何处理呢&#xff1f; 下面将以问答形式分享解决方案&#xff1a; 底层账务优化相关&#xff0c;根据实际业务进…

Linux mren命令教程:批量重命名文件(附实际操作案例和注意事项)

Linux mren命令介绍 mren&#xff08;全称multiple rename&#xff09;&#xff0c;它是用来对多个文件进行重命名的工具。这个命令在一次操作中可以批量改变多个文件的名称&#xff0c;特别是在需要对大量文件进行重命名时&#xff0c;mren将节省大量的时间和努力。 Linux m…