019 C语言基础:C预处理

一:概述

前面各章中,已经多次使用过#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有两种使用方式:

  1. #include <stdio.h>
  2. #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,然后再进行编译。

对宏定义的几点说明:

  1. 宏定义是用宏名来表示一个字符串,在宏展开时又以该字符串取代宏名,这只是一种简单粗暴的替换。字符串中可以含任何字符,它可以是常数、表达式、if 语句、函数等,预处理程序对它不作任何检查,如有错误,只能在编译已被宏展开后的源程序时发现。

  2. 宏定义不是说明或语句,在行末不必加分号,如加上分号则连分号也一起替换。

  3. 宏定义必须写在函数之外,其作用域为宏定义命令起到源程序结束。如要终止其作用域可使用#undef命令。例如:

     #define PI 3.14159
     int main(){
     	// Code
     	return 0;
     }
     #undef PI
     void func(){
     	// Code
     }
     表示 PI 只在 main() 函数中有效,在 func() 中无效。
    
  4. 代码中的宏名如果被引号包围,那么预处理程序不对其作宏代替,例如:

     #include <stdio.h>
     #define OK 100
     int main(){
     	printf("OK\n");
     	return 0;
     }
     运行结果:
     OK
     该例中定义宏名 OK 表示 100,但在 printf 语句中 OK 被引号括起来,因此不作宏替换,而作为字符串处理。
    
  5. 宏定义允许嵌套,在宏定义的字符串中可以使用已经定义的宏名,在宏展开时由预处理程序层层代换。例如:

     #define PI 3.1415926
     #define S PI*y*y    /* PI是已定义的宏名*/
     对语句:
     printf("%f", S);
     在宏代换后变为:
     printf("%f", 3.1415926*y*y);
    
  6. 习惯上宏名用大写字母表示,以便于与变量区别。但也允许用小写字母。

  7. 可用宏定义表示数据类型,使书写方便。例如:#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;
}
结果:
	┌──(root💀kali)-[~/Desktop/c_test]
	└─# ./hong
	please input your number: 1
	2
	max = 2 

对带参宏定义的说明:

  1. 带参宏定义中,形参之间可以出现空格,但是宏名和形参列表之间不能有空格出现。例如把:#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);这显然是错误的。
  2. 在带参宏定义中,不会为形式参数分配内存,因此不必指明数据类型。而在宏调用中,实参包含了具体的数据,要用它们去替换形参,因此实参必须要指明数据类型。
  3. 在宏定义中,字符串内的形参通常要用括号括起来以避免出错。#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);
}
结果:
	┌──(root💀kali)-[~/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);
	}
	结果:
		┌──(root💀kali)-[~/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;
}
结果:
	┌──(root💀kali)-[~/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 的功能正好相反。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值