深入浅出C语言:(十)C预处理

2023-05-16

目录

一、C 语言#include 的用法(文件包含命令)

二、C 语言宏定义(#define 的用法)

1、#define 的基础用法

2、C 语言带参数的宏定义

3、对带参宏定义的说明

4、用宏参数创建字符串:#运算符

5、预处理器粘合剂:##运算符

6、变参宏:...和_ _VA_ARGS_ _

三、其他指令

1、#undef指令

2、#ifdef、#else和#endif指令

3、#ifndef指令

4、#if和#elif指令

5、预定义宏:

6、#line和#error指令

四、数学库

五、通用工具库:

1、exit()和atexit()函数

2、qsort函数

六、断言库

1、assert的用法

2、_Static_assert

七、string.h库中的memcpy()和memmove()


       在编译和链接之前,还需要对源文件进行一些文本方面的操作,比如文本替换、文件包含、删除部分代码等,这个过程叫做预处理,由预处理程序完成。
 

一、C 语言#include 的用法(文件包含命令)

       #include 叫做文件包含命令,用来引入对应的头文件(.h 文件)。 #include 也是 C 语言预处理命令的一种。

       #include 的处理过程很简单,就是将头文件的内容插入到该命令所在的位置,从而把头文件和当前源文件连接成一个源文件,这与复制粘贴的效果相同。

#include <sumjess.h>
#include "sumjess.h"

使用尖括号< >和双引号" "的区别在于头文件的搜索路径不同:

  1. 使用尖括号< >,编译器会到系统路径下查找头文件;
  2. 而使用双引号" ",编译器首先在当前目录下查找头文件,如果没有找到,再到系统路径下查找。

也就是说,使用双引号比使用尖括号多了一个查找路径,它的功能更为强大。

二、C 语言宏定义(#define 的用法)

1、#define 的基础用法

       #define 叫做宏定义命令,它也是 C 语言预处理命令的一种。所谓宏定义,就是用一个标识符来表示一个字符串,如果在后面的代码中出现了该标识符,那么就全部替换成指定的字符串。字符串可以是数字、表达式、 if 语句、函数等。
       #define 用法的几点说明
       1) 宏定义是用宏名来表示一个字符串,在宏展开时又以该字符串取代宏名,这只是一种简单粗暴的替换。字符串中可以含任何字符,它可以是常数、表达式、 if 语句、函数等,预处理程序对它不作任何检查,如有错误,只能在编译已被宏展开后的源程序时发现。
       2) 宏定义不是说明或语句,在行末不必加分号,如加上分号则连分号也一起替换。
       3) 宏定义必须写在函数之外,其作用域为宏定义命令起到源程序结束。如要终止其作用域可使用#undef 命令

#define PI 3.14159
int main(){
// Code
return 0;
}
#undef PI

       5) 宏定义允许嵌套,在宏定义的字符串中可以使用已经定义的宏名,在宏展开时由预处理程序层层代换。

       6) 习惯上宏名用大写字母表示,以便于与变量区别。但也允许用小写字母。

       7) 可用宏定义表示数据类型,使书写方便。例如:

2、C 语言带参数的宏定义

对带参数的宏,在展开过程中不仅要进行字符串替换,还要用实参去替换形参。
带参宏定义的一般形式为:

#define 宏名(形参列表) 字符串
#define MAX(a,b) (a>b) ? a : b

3、对带参宏定义的说明

        1) 带参宏定义中,形参之间可以出现空格,但是宏名和形参列表之间不能有空格出现
        2) 在带参宏定义中,不会为形式参数分配内存,因此不必指明数据类型。而在宏调用中,实参包含了具体的数据,要用它们去替换形参,因此实参必须要指明数据类型。

        3) 在宏定义中,字符串内的形参通常要用括号括起来以避免出错。

#define SQ(y) y*y
sq = SQ(a+1);

与

SQ(y) (y)*(y)
sq = SQ(a+1);

最终结果是不一样的

由此可见,对于带参宏定义不仅要在参数两侧加括号,还应该在整个字符串外加括号以防出错。

4、用宏参数创建字符串:#运算符

C允许在字符串中包含宏参数。在类函数宏的替换体中,#号作为一个预处理运算符,可以把记号转换为字符串。

例如:如果x是一个宏形参,那么#x就是转换为字符串“x”的形参名。这个过程称为字符串化

#define add2 (x) printf("the double of " #x " is %d.\n", ((x)*(x) )

int y=5 ;
add2(y);
输出结果:the double of y is 25.

add2(2+3);
输出结果:the double of 2+3 is 25.


转换流程:
printf("the double of " "y" " is %d.\n", ((y)*(y) );
printf("the double of y is %d.\n", 25 );
输出结果:the double of y is 25.

5、预处理器粘合剂:##运算符

与#运算符类似,##运算符可用于类函数宏的替换部分。而且,##还可用于对象宏的替换部分##运算符把两个记号组合成一个记号

#include <stdio.h>

#define XN(n) x ## n
#define PRINTF_XN(n) printf("x" #n " = %d\n", x ## n);

int main(void)
{
	int XN(1) = 3;  变成 int x1 = 3
	int XN(2) = 2;  变成 int x2 = 2
	int x3    = 1;  变成 int x3 = 1
	
	PRINTF_XN(1);   变成 printf("x1 = %d\n", x1);
	PRINTF_XN(2);   变成 printf("x2 = %d\n", x2);  
	PRINTF_XN(3);   变成 printf("x3 = %d\n", x3);
	  
	return 0;
	
}

结果
x1 = 3
x2 = 2
x3 = 1

6、变参宏:...和_ _VA_ARGS_ _

       通过把宏参数列表中最后的参数写成省略号(即,3个点...)来实现这一功能。这样,预定义宏_ _VA_ARGS_ _可用在替换部分中,表明省略号代表什么。

#define PR(X,...) printf("message " #x ": "_ _VA_ARGS_ _)


int x= 5, y=9;

PR(1," x= %d,y=%d \n",x,y);

结果
message 1 : x=5,y=9

三、其他指令

1、#undef指令

#undef指令用于“取消”已定义的#define指令。

#define PI 3.14159

int main()
{
 Code          此处可以使用PI

return 0;
}

#undef PI        “取消”已定义的#define指令(PI)。

void nono(void)
{

 Code          此处无法再使用PI

}

2、#ifdef、#else和#endif指令

#ifndef指令和#else、#endf搭配使用。

#ifdef MAVIS
    #include "horse.h"    如果已经用了#define定义了MAVIS,则执行下面的指令
    #define  SABLE  5
#else
    #include "cow.h"      如果没有用了#define定义了MAVIS,则执行下面的指令
    #define  SABLE  10
#endif 

3、#ifndef指令

#ifndef指令也和#else、#endf搭配使用。

#ifndef SIZE
  #defien SIZE 100  如果SIZE没定义,那么定义SIZE等于1000
#endif

在头文件中我们常使用#ifndef指令避免文件被重复包含。

4、#if和#elif指令

#if和#elif也与#endif搭配,#if和#elif可以类比if 和else if。

defined是一个预处理运算符,如果它的参数被define定义过,则返回1,否则返回0。

#if   defined (VAX)    等于 #ifdef VAX
    #include "vax.h"
#elif defined (SEE)
    #include "see.h"
#else
    #include "all.h"
#endif

5、预定义宏:

                   宏

                                                           意     义
_ _DATE_ _进行预处理的日期,“Mmm dd yyy”形式的字符串字面量,格式为 Nov 23 2013
_ _FILE_ _代表当前源代码文件名的字符串文字
_ _LINE_ _代表当前源代码文件中的行号的整数常量
_ _STDC_ _设置为1时,表示该实现遵循C标准
_ _STDC_HOSTED_ _为本机环境设置为1,否则为设为0
_ _STDC_VERSION_ _为C99时设置为199901L
_ _TIME_ _源文件编译时间,格式为“hh:mm:ss”

 

 

 

 

 

 

 

 

 

使用printf直接打印即可。

6、#line和#error指令

① #line指令重置_ _LINE_ __ _FILE_ _宏报告的行号文件名

#line 1000           把当前行号重置为1000

#line 10 “cllo.c”    把行号重置为10,把文件名重置为cllo.c

② #error指令让预处理器发出一条错误信息,该消息包含指令中的文本。

#if _ _STDC_VERSION_ _ != 201112L  如果版本号不是C11
#error Not C11                      报错Not C11 
#endif

四、数学库

使用说明:

五、通用工具库:

1、exit()和atexit()函数

       exit()函数用于在程序运行的过程中随时结束程序,exit的参数state将会返回给操作系统,返回0表示程序正常结束,非0表示程序非正常结束。main函数结束时也会隐式地调用exit函数。exit函数运行时首先会执行由atexit()函数登记的函数,然后会做一些自身的清理工作,同时刷新所有输出流、关闭所有打开的流并且关闭通过标准I/O函数tmpfile()创建的临时文件。

  • exit() 结束当前进程/当前程序/,在整个程序中,只要调用 exit ,就结束;
  • exit(1)表示进程非正常退出. 返回 1给操作系统;
  • exit(0)表示进程正常退出. 返回 0给操作系统.

atexit()函数--注册程序正常终止时要被调用的函数

       按照ISOC的规定,一个进程可以登记多达32个函数,这些函数将由exit自动调用,通常这32个函数被称为终止处理程序,并调用atexit函数来登记这些函数。

       在程序退出时经常需要做一些诸如释放资源的操作,但程序退出的方式有很多种。因此需要一种与程序退出方式无关的方法来进行程序退出时的必要处理。atexit()函数用来注册程序正常终止时要被调用的函数。      

       atexit的参数是一个函数指针,当调用此函数时无须传递任何参数,该函数也不能返回值,atexit函数称为终止处理程序注册程序,注册完成以后,当函数终止时exit函数会主动的调用前面注册的各个函数,但是exit函数调用这些函数的顺序与这些函数登记的顺序是相反的,我认为这实质上是参数压栈造成的,参数由于压栈顺序而先入后出。同时如果一个函数被多次登记,那么该函数也将多次的执行。

2、qsort函数

描述

C 库函数 void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*)) 对数组进行排序。

声明

下面是 qsort() 函数的声明。


void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*))  

参数

  • base -- 指向要排序的数组的第一个元素的指针。
  • nitems -- 由 base 指向的数组中元素的个数。
  • size -- 数组中每个元素的大小,以字节为单位。
  • compar -- 用来比较两个元素的函数。

实例

下面的实例演示了 qsort() 函数的用法。

#include <stdio.h>
#include <stdlib.h>

int values[] = { 88, 56, 100, 2, 25 };

int cmpfunc (const void * a, const void * b)
{
   return ( *(int*)a - *(int*)b );
}

int main()
{
   int n;

   printf("排序之前的列表:\n");
   for( n = 0 ; n < 5; n++ ) {
      printf("%d ", values[n]);
   }

   qsort(values, 5, sizeof(int), cmpfunc);

   printf("\n排序之后的列表:\n");
   for( n = 0 ; n < 5; n++ ) {
      printf("%d ", values[n]);
   }
  
  return(0);
}

让我们编译并运行上面的程序,这将产生以下结果:

排序之前的列表:
88 56 100 2 25 
排序之后的列表:
2 25 56 88 100

六、断言库

1、assert的用法

#include <stdio.h>
#include <assert.h>


int main(void)
{
	int a = 3;
	
	assert(a>4);	//这里有错,因为3>4是错的。 
    
	return 0;
}

当我们修改完bug后,可以在assert.h前面,加上 #define NDEBUG

2、_Static_assert

assert是编译运行后才显示错误,_Static_assert是编译的时候就显示。

#include <stdio.h>
#include <limits.h>

_Static_assert(a==16,"!!!这里有错!!!");
int main(void)
{
	int a = 2;

	return 0;
}

七、string.h库中的memcpy()和memmove()

memcpy和memmove()都是C语言中的库函数,在头文件string.h中,作用是拷贝一定长度的内存的内容,原型分别如下:
void *memcpy(void *dst, const void *src, size_t count);
void *memmove(void *dst, const void *src, size_t  count);


唯一的区别是,当内存发生局部重叠的时候,memmove保证拷贝的结果是正确的,memcpy不保证拷贝的结果的正确。

memmove在copy两个有重叠区域的内存时可以保证copy的正确,而memcopy就不行了,但memcopy比memmove的速度要快一些。

char  s[] = "1234567890";
char* p1 = s;
char* p2 = s+2;

memcpy(p2, p1, 5)与memmove(p2, p1, 5)的结果就可能是不同的,
memmove()可以将p1的头5个字符"12345"正确拷贝至p2,而memcpy()的结果就不一定正确了.

和memcpy相比,src和des有重叠的情况下,memmove可以保证数据的完整性.
memmove保证的原因很简单,就是针对重叠的情况做特殊处理,因此速度会比memcpy慢一些

具体解释见:https://blog.csdn.net/zhao_miao/article/details/82319093

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

深入浅出C语言:(十)C预处理 的相关文章

  • VINS-Mono学习(五)——闭环优化4DoF

    这里再重写一边VINS开启的新线程 xff1a 前端图像跟踪后端非线性优化闭环检测闭环优化 闭环优化是跟在闭环检测之后步骤 首先回顾闭环检测的过程 xff1a 1 pose graph node cpp开启process闭环检测线程 xff
  • C语言预定义跟踪调试

    标准C语言预处理要求定义某些对象宏 xff0c 每个预定义宏的名称一两个下划线字符开头和结尾 xff0c 这些预定义宏不能被取消定义 xff08 undef xff09 或由编程人员重新定义 下面预定义宏表 xff0c 被我抄了下来 LIN
  • 链式存储

    1 特点 线性表的链式存储结构的特点是用一组任意的存储单元存储线性表的数据元素 xff0c 这组存储单元可以是连续的 xff0c 也可以是不连续的这就意味着 xff0c 这些数据元素可以存在内存未被占用的任意位置 2 结点是什么 在数据结构
  • ROS驱动包无法连接A2M7雷达解决办法

    我在使用A2M7雷达时 xff0c 波特率是256000 xff0c 之前驱动跑A2M6和A2M8雷达时 xff0c 波特率115200 xff0c 都可以使用 xff0c 现在跑M7就不行 xff0c 显示无法绑定到串口 xff0c 刚开
  • FreeRTOS prvTaskExitError 创建任务错误

    文件port c prvTaskExitError 任务退出错误 xff0c 一个可能在任务里面写了return xff0c 另一个可能任务切换退出问题 xff0c 入栈和出栈的时候出了问题 1 static void prvTaskExi
  • Ubuntu18.04下VSCode环境编写Linux相关驱动程序时出现未定义标识符问题

    Ubuntu18 04下VSCode环境编写Linux相关驱动程序时出现未定义标识符问题 编译Linux相关驱动程序时 xff0c 出现未定义标识符问题 但是ctrl 43 鼠标左键可以找到相关定义 这是因为没有加载对应Linux内核中头文
  • postman基础使用教程

    Postman教程大全 简书 推荐一款接口测试工具 xff01 POSTMAN xff01 简单来说 xff0c 四个词 xff0c 简单实用大方美观 xff01 Postman是一款功能强大的网页调试与发送网页HTTP请求的Chrome插

随机推荐

  • Make、Makefile、CMake和CMakeLists

    一 Make 在 认识编译器和C C 43 43 编译 一文中介绍过 xff0c 一个 c cpp 文件从源文件到目标文件的过程叫做编译 xff0c 但是一个项目中不可能只存在一个文件 xff0c 这就涉及到多个文件的编译问题 xff0c
  • C++将类写在头文件中

    比如有个类ABC要在main cpp内使用 xff0c 创建两个文件 ABC h xff0c ABC cpp 把类的声明都写在h里面 xff0c 方法的实现写在cpp里面 xff0c 然后在main cpp内 include ABC h x
  • ubuntu搭建一个简单的http服务器

    使用ubuntu搭建一个简单的http服务器 安装apache2 1 sudo apt get update 2 sudo apt get install apache2 安装成功后 xff0c 再 etc apache2目录可见其配置文件
  • Postman操作基本教程

    一 xff1a 基本介绍 xff1a Postman是一款功能强大的网页调试与发送网页HTTP请求的Chrome插件 Postman背景介绍 用户在开发或者调试网络程序或者是网页B S模式的程序的时候是需要一些方法来跟踪网页请求的 xff0
  • ROS中rosserial通讯协议初探

    ROS中rosserial通讯协议初探 串行的通讯 xff0c 我们用串口模拟下通讯图 官方 http wiki ros org rosserial rosserial 1概述 标准ROS序列化message的协议 xff0c 可以让一个字
  • 使用cmake交叉编译arm平台so

    使用cmake交叉编译arm平台so 众所周知 xff0c androidStudio可以编译apk及so 具体配置此处不一一介绍 xff0c 但对于需要经常编译不同项目的小编来说 xff0c 太过重量级了 xff0c 假如在编译系统下并没
  • Linux编译C文件

    熟悉了Windows平台下编译一个C 43 43 工程后 xff0c 你是否会提出这样一个问题 xff1a 在Linux平台下又如何编译一个C 43 43 工程呢 xff1f 希望本文能给正在学习或想学习Linux C 43 43 开发的你
  • stm32 esp8266 ota升级-qt bin文件处理工具

    stm32 esp8266 ota系列文章 xff1a stm32 esp8266 ota 快速搭建web服务器之docker安装openresty stm32 esp8266 ota升级 tcp模拟http stm32 esp8266 o
  • 04.Android调用C语言的方法

    为了在Android端调用底层的驱动程序 xff0c 我们需要在Android中调用C语言 直接新建一个Native C 43 43 工程 xff0c 然后按照这篇文章的方法 xff1a JNI与NDK简析 xff08 一 xff09 St
  • gazebo教程---使用gazebo插件

    一 添加传感器插件 xff08 1 xff09 在rrbot xacro中添加 lt link gt 和 lt joint gt xff0c 内容如下 xff1a lt joint name 61 span class token stri
  • 基于加速度计与磁力计的姿态解算方法(加计补偿偏航)

    附上转载文章链接 加速度计实时输出机体坐标系下的三轴线加速度 xff0c 磁力计实时输出机体坐标系下的三轴地磁强度 xff0c 加速度计能解算出俯仰角与横滚角 xff0c 由磁力计计算出航向角 xff0c 两者相互配合可以解算三个姿态角信息
  • Xavier踩坑之-GPIO做外触发

    Xavier入门踩坑 PWM问题解决方法 GPIO问题解决方法 PWM问题 由于需要做外部传感器的触发同步 xff0c 所以需要一个方波 xff0c 考虑用Xavier的PWM xff0c 结果折腾了好久发现需要配置内部硬件 xff0c 折
  • Kalibr进行相机-IMU联合标定踩坑记录RuntimeError: Optimization failed!

    1 具体标定步骤 xff0c 跟网上别的一模一样 xff0c 此处就不列举 2 记录踩坑过程 xff1a RuntimeError Optimization failed 当执行到开始联合标定时 xff0c 也就是如下指令 xff1a ka
  • “GPG 错误导致没有公钥,无法验证签名”的问题解决

    W GPG 错误 xff1a http packages ros org ros ubuntu xenial InRelease 由于没有公钥 xff0c 无法验证下列签名 xff1a NO PUBKEY F42ED6FBAB17C654
  • 加快从github的git clone速度

    对于国内用户来说遇到clone Github速度十分缓慢的问题实在是一个令人头疼崩溃的问题 下面介绍一个简单的方法解决这个问题 xff0c 也就是先从github拉取到自己的码云帐号 xff0c 然后再从自己的帐号git clone 方法
  • 快速了解机器人操作系统ROS

    ROS xff08 Robot Operating System xff09 机器人操作系统 xff0c 由斯坦福大学人工智能实验室开发的一套提供类似操作系统服务的机器人专用开源系统 ROS包括一个类似于硬件系统的硬件抽象 xff0c 但它
  • 《大话数据结构》C++实现哈希表的创建、查找和插入

    include lt iostream gt using namespace std typedef int status constexpr auto SUCCESS 61 1 constexpr auto UNSUCCESS 61 0
  • 一文理解UART通信

    还记得当年的打印机 xff0c 鼠标和调制解调器吗 他们都有巨大笨重的连接器和粗电缆 xff0c 并且必须拧到你的电脑上 这些设备正是使用UART协议与计算机进行通信 虽然USB几乎完全取代了旧的电缆和连接器 xff0c 但UART绝对没有
  • UART串口通信协议概述

    1 UART协议介绍 UART是一种通用串行数据总线 xff0c 用于异步通信 UART能实现双向通信 xff0c 在嵌入式设计中 xff0c 常用于主机与辅助设备通信 UART包括RS232 RS449 RS423等接口标准规范和总线标准
  • 深入浅出C语言:(十)C预处理

    目录 一 C 语言 include 的用法 xff08 文件包含命令 xff09 二 C 语言宏定义 xff08 define 的用法 xff09 1 define 的基础用法 2 C 语言带参数的宏定义 3 对带参宏定义的说明 4 用宏参