当前位置:网站首页>019 C语言基础:C预处理
019 C语言基础:C预处理
2022-06-27 04:04:00 【入狱计划进度50%】
一:概述
前面各章中,已经多次使用过#include命令。使用库函数之前,应该用#include引入对应的头文件。这种以#号开头的命令称为预处理命令。在编译之前对源文件进行简单加工的过程,就称为预处理(即预先处理、提前处理)。
预处理主要是处理以#开头的命令,例如#include <stdio.h>等。预处理命令要放在所有函数之外,而且一般都放在源文件的前面。我们把 C 预处理器(C Preprocessor)简写为 CPP。所有的预处理器命令都是以井号(#)开头。它必须是第一个非空字符,为了增强可读性,预处理器指令应从第一列开始。
下面列出了所有重要的预处理器指令:
指令 描述
#define 定义宏
#include 包含一个源代码文件
#undef 取消已定义的宏
#ifdef 如果宏已经定义,则返回真
#ifndef 如果宏没有定义,则返回真
#if 如果给定条件为真,则编译下面代码
#else #if 的替代方案
#elif 如果前面的 #if 给定条件不为真,当前条件为真,则编译下面代码
#endif 结束一个 #if……#else 条件编译块
#error 当遇到标准错误时,输出错误消息
#pragma 使用标准化方法,向编译器发布特殊的命令到编译器中
二:#include命令
文件包含命令,主要用来引入对应的头文件。#include 的处理过程很简单,就是将头文件的内容插入到该命令所在的位置,从而把头文件和当前源文件连接成一个源文件,这与复制粘贴的效果相同。
#include有两种使用方式:
- #include <stdio.h>
- #include “myHeader.h”
使用尖括号<>和双引号""的区别在于头文件的搜索路径不同,包含标准库的头文件建议用尖括号,包含自定义的头文件建议用双引号。
说明:
一个#include命令只能包含一个头文件,多个头文件需要多个#include命令。
文件包含允许嵌套,也就是说在一个被包含的文件中又可以包含另一个文件。
三:C语言宏定义
宏(Macro)是预处理命令的一种,它允许用一个标识符来表示一个字符串。
先看一个例子:
#include <stdio.h>
#define N 100
int main(){
int sum = 20 + N;
printf("%d \n", sum);
return 0;
}
运行结果:120
该示例中的语句int sum = 20 + N;,N被100代替了。
#define N 100就是宏定义,N为宏名,100是宏的内容。在预处理阶段,对程序中所有出现的“宏名”,预处理器都会用宏定义中的字符串去代换,这称为“宏替换”或“宏展开”。宏定义是由源程序中的宏定义命令#define完成的,宏替换是由预处理程序完成的。
宏定义的一般形式为:#define 宏名 字符串
程序中反复使用的表达式就可以使用宏定义,例如:#define M (n*n+3*n)
它的作用是指定标识符M来表示(yy+3y)这个表达式。在编写代码时,所有出现 (yy+3y) 的地方都可以用 M 来表示,而对源程序编译时,将先由预处理程序进行宏代替,即用 (yy+3y) 去替换所有的宏名 M,然后再进行编译。
对宏定义的几点说明:
宏定义是用宏名来表示一个字符串,在宏展开时又以该字符串取代宏名,这只是一种简单粗暴的替换。字符串中可以含任何字符,它可以是常数、表达式、if 语句、函数等,预处理程序对它不作任何检查,如有错误,只能在编译已被宏展开后的源程序时发现。
宏定义不是说明或语句,在行末不必加分号,如加上分号则连分号也一起替换。
宏定义必须写在函数之外,其作用域为宏定义命令起到源程序结束。如要终止其作用域可使用#undef命令。例如:
#define PI 3.14159 int main(){ // Code return 0; } #undef PI void func(){ // Code } 表示 PI 只在 main() 函数中有效,在 func() 中无效。代码中的宏名如果被引号包围,那么预处理程序不对其作宏代替,例如:
#include <stdio.h> #define OK 100 int main(){ printf("OK\n"); return 0; } 运行结果: OK 该例中定义宏名 OK 表示 100,但在 printf 语句中 OK 被引号括起来,因此不作宏替换,而作为字符串处理。宏定义允许嵌套,在宏定义的字符串中可以使用已经定义的宏名,在宏展开时由预处理程序层层代换。例如:
#define PI 3.1415926 #define S PI*y*y /* PI是已定义的宏名*/ 对语句: printf("%f", S); 在宏代换后变为: printf("%f", 3.1415926*y*y);习惯上宏名用大写字母表示,以便于与变量区别。但也允许用小写字母。
可用宏定义表示数据类型,使书写方便。例如:
#define UINT unsigned int在程序中可用 UINT 作变量说明:UINT a, b;应注意用宏定义表示数据类型和用 typedef 定义数据说明符的区别。宏定义只是简单的字符串替换,由预处理器来处理;而 typedef 是在编译阶段由编译器处理的,它并不是简单的字符串替换,而给原有的数据类型起一个新的名字,将它作为一种新的数据类型。
请看下面的例子:#define PIN1 int * typedef int *PIN2; //也可以写作typedef int (*PIN2); 从形式上看这两者相似, 但在实际使用中却不相同。下面用 PIN1,PIN2 说明变量时就可以看出它们的区别:
PIN1 a, b;在宏代换后变成:int * a, b;
表示 a 是指向整型的指针变量,而 b 是整型变量。然而:PIN2 a,b;表示 a、b 都是指向整型的指针变量。因为 PIN2 是一个新的、完整的数据类型。由这个例子可见,宏定义虽然也可表示数据类型, 但毕竟只是简单的字符串替换。在使用时要格外小心,以避出错。
四:C语言带参数宏定义
C语言允许宏带有参数。在宏定义中的参数称为“形式参数”,在宏调用中的参数称为“实际参数”,这点和函数有些类似。对带参数的宏,在展开过程中不仅要进行字符串替换,还要用实参去替换形参。
带参宏定义的一般形式为:#define 宏名(形参列表) 字符串 ,在字符串中可以含有各个形参。
带参宏调用的一般形式为:宏名(实参列表);
例如:
#define M(y) y*y+3*y // 宏定义
k = M(5); // 宏调用
实例:输出两个数中的较大的数
#include <stdio.h>
#define MAX(a,b) (a>b) ? a : b
int main(){
int x, y, max;
printf("please input your number: ");
scanf("%d %d", &x, &y);
max = MAX(x, y);
printf("max = %d \n", max);
return 0;
}
结果:
┌──(rootkali)-[~/Desktop/c_test]
└─# ./hong
please input your number: 1
2
max = 2
对带参宏定义的说明:
- 带参宏定义中,形参之间可以出现空格,但是宏名和形参列表之间不能有空格出现。例如把:
#define MAX(a,b) (a>b)?a:b写为:#define MAX (a,b) (a>b)?a:b将被认为是无参宏定义,宏名 MAX 代表字符串(a,b) (a>b)?a:b。宏展开时,宏调用语句:max = MAX(x,y);将变为:max = (a,b)(a>b)?a:b(x,y);这显然是错误的。 - 在带参宏定义中,不会为形式参数分配内存,因此不必指明数据类型。而在宏调用中,实参包含了具体的数据,要用它们去替换形参,因此实参必须要指明数据类型。
- 在宏定义中,字符串内的形参通常要用括号括起来以避免出错。
#define SQ(y) (y)*(y)
C语言带参宏定义和函数的区别:
带参数的宏和函数很相似,但有本质上的区别:宏展开仅仅是字符串的替换,不会对表达式进行计算,宏在编译之前就被处理掉了,没有机会参与编译,也不会占用内存。而函数是一段可以重复使用的代码,会被编译,会给它分配内存,每次调用函数,就是执行这块内存中的代码。
实例:
1)用函数计算平方值
#include <stdio.h>
int SQ(int y){
return (y*y);
}
int main(){
int x;
int i;
printf("input your number: ");
scanf("%d", &x);
i = SQ(x);
printf("%d 的平方是: %d", x, i);
}
结果:
┌──(rootkali)-[~/Desktop/c_test]
└─# ./pingfanghanshu
input your number: 2
2 的平方是: 4
2)用宏计算平方值
#include <stdio.h>
#define SQ(y) (y*y)
int main(){
int x;
printf("input your number: ");
scanf("%d", &x);
int i;
i = SQ(x);
printf("%d 的平方是: %d", x, i);
}
结果:
┌──(rootkali)-[~/Desktop/c_test]
└─# ./pingfanghong
input your number: 3
3 的平方是: 9
C语言条件编译:
能够根据不同情况编译不同代码、产生不同目标文件的机制,称为条件编译。条件编译是预处理程序的功能,不是编译器的功能。
假如现在要开发一个C语言程序,让它输出红色的文字,并且要求跨平台,在 Windows 和 Linux 下都能运行,怎么办呢?
这个程序的难点在于,不同平台下控制文字颜色的代码不一样,我们必须要能够识别出不同的平台。Windows 有专有的宏_WIN32,Linux 有专有的宏__linux__,以现有的知识,我们很容易就想到了 if else
实例:
#include <stdio.h>
int main(){
if(_WIN32){
system("color 0c");
printf("www.qq.com \n");
}else if (__linux__)
{
printf("\033[22;31mwww.qq.com \n\033[22;30m");
}else{
printf("www.qq.com \n");
}
return 0;
}
但这段代码是错误的,在windows下报错error: '__linux__' undeclared,在Linux下报错error: ‘_WIN32’ undeclared
所以需要进行改进:
#include <stdio.h>
int main(){
#if _WIN32
system("color 0c");
printf("www.qq.com \n");
#elif __linux__
printf("\033[22;31mwww.qq.com \n\033[22;30m");
#else
printf("www.qq.com \n");
#endif
return 0;
}
结果:
┌──(rootkali)-[~/Desktop/c_test]
└─# ./tiaojianbianyi2
www.qq.com // 红色字体
#if 命令
#if 命令的完整格式为:
#if 整型常量表达式1
程序段1
#elif 整型常量表达式2
程序段2
#elif 整型常量表达式3
程序段3
#else
程序段4
#endif
#ifdef 命令
#ifdef 宏名
程序段1
#else
程序段2
#endif
它的意思是,如果当前的宏已被定义过,则对“程序段1”进行编译,否则对“程序段2”进行编译。
#ifndef 命令
#ifndef 宏名
程序段1
#else
程序段2
#endif
与 #ifdef 相比,仅仅是将 #ifdef 改为了 #ifndef。它的意思是,如果当前的宏未被定义,则对“程序段1”进行编译,否则对“程序段2”进行编译,这与 #ifdef 的功能正好相反。
边栏推荐
- Products change the world
- 1.5 conda的使用
- 1.5 use of CONDA
- IOS development: understanding of dynamic library shared cache (dyld)
- Description of replacement with STM32 or gd32
- PostgreSQL基础命令教程:创建新用户admin来访问PostgreSQL
- 2022-06-26:以下golang代码输出什么?A:true;B:false;C:编译错误。 package main import “fmt“ func main() { type
- Fplan powerplan instance
- math_数集(数集符号)和集合论
- Promise [II. Promise source code] [detailed code comments / complete test cases]
猜你喜欢

Resnet152 pepper pest image recognition 1.0

乐得瑞LDR6035 USB-C接口设备支持可充电可OTG传输数据方案。

MATLAB | 三个趣的圆相关的数理性质可视化

基于MobileNet-Yolov4搭建轻量化目标检测

iOS开发:对于动态库共享缓存(dyld)的了解

JMeter distributed pressure measurement
![[promise I] introduction of promise and key issues of hand rolling](/img/14/94bd986d3ac8a0db35c83b4234fa8a.png)
[promise I] introduction of promise and key issues of hand rolling

为什么 C# 访问 null 字段会抛异常?

静态时序分析-OCV和time derate

CVPR2021:Separating Skills and Concepts for Novel Visual Question Answering将技巧与概念分开的新视觉问答
随机推荐
[数组]BM94 接雨水问题-较难
Basic functions of promise [IV. promise source code]
Anaconda3安装过程及安装后缺失大量文件,没有scripts等目录
Fplan powerplan instance
mysql数据库基础:DQL数据查询语言
Kotlin compose compositionlocalof and staticcompositionlocalof
Knowledge of iPhone certificate structure
手机新领域用法知识
MobileNet系列(4):MobileNetv3网络详解
Matlab | drawing of three ordinate diagram based on block diagram layout
Anaconda3 is missing a large number of files during and after installation, and there are no scripts and other directories
如何让 EF Core 6 支持 DateOnly 类型
Facing the "industry, University and research" gap in AI talent training, how can shengteng AI enrich the black land of industrial talents?
JMeter takes the result of the previous request as the parameter of the next request
2021:Beyond Question-Based Biases:Assessing Multimodal Shortcut Learning in Visual Question Answeri
PostgreSQL basic command tutorial: create a new user admin to access PostgreSQL
静态时序分析-OCV和time derate
低代码开发平台NocoBase的安装
Fplan power planning
2021:AdaVQA: Overcoming Language Priors with Adapted Margin Cosine Loss∗自适应的边缘余弦损失解决语言先验