预处理,咕咕咕
1.预定义符号
_FILE_ //编译的源文件
_LINE_ //文件行号
_DATA_ //文件编译日期
_TIME_ //文件编译时间
_STDC_ //如果文件编译遵循ANSI C,其值为一,否则未定义
printf("%d",_FILE_,_LINE_);
2.#define定义常量
#define name stuff
#define MAX 1000
#define reg register 创建简短的名字
#define do_forever for(;;)用更形象的符号实现
#define CASE break;case 写case时自动把break填上
如果定义的东西太长,分成几行写,每一行的后面加一个\,续航符
#define DEBUG_PRINT printf("file:%s\tline:%d\t \
date:%s\ttime:%s\n",\
_FILE_,_LINE_, \
_DATE_,_TIME_)
定义标识符时后面不要加;
#define MAX 1000;
if(condition)
max=MAX;
else
max=0;这里加了; if与else之间有2个语句,没有{}时if后面只能有1条语句
3.#define 定义宏
#define机制包括了一个规定,允许把文本替换到文本中,这种实现称为宏或定义宏
#define name(parament-list) stuff是一个由逗号隔开的符号表,可能出现在stuff中
参数列表的左括号必须与name紧邻,如果两者之间有任何空白存在,
参数列表会被解释为stuff的一部分
#define SQUARE(x) x*x
输入5,打印5*5;
但是有问题
int a=5;
printf("%d",SQUARE(a+1));
但它不打印36,实际上替换为
printf("%d",a+1*a+1);
打印了11
#define SQUARE(x) (x)*(x)就可以解决了
printf("%d",(a+1)*(a+1));
#define double(x) (x)+(x)
printf("%d",10*double(5)); 10*5+5
乘法先于宏定义
#define double(x) ((x)+(x))
用于数值表达式进行求值的宏定义应该用这种方法加上括号,避免在使用宏时由于参数中的操作符和临近操作符之间不可预料的相互作用
4.带有副作用的宏参数
当宏参数在宏的定义中出现超过一次后,如果参数带有副作用,那么使用时就可能有副作用
x+1没有副作用,x++有副作用
#define MAX(a,b) ((a)>(b)?(a):(b))
x=5;
y=8;
z=MAX(x++,y++); ((x++)>(y++)?(x++):(y++))
6 10 9
5.宏替换的规则
1.调用宏时,先对参数检查,看是否包含任何#define定义的符号,如果是,它们首先被替换
2.替换文本随后被插入到程序中原来文本的位置,对于宏,参数名被他们的值替换
3.最后对结果文件扫描检查是否有#define定义的符号,有就在重复
宏定义和#define定义中可以出现其他#define定义的符号,对于宏不能出现递归
当预处理器搜索#define定义的符号,字符串常量不被搜索
6.宏函数的对比
宏通常被用于执行简单的运算
#define MAX(a,b) ((a)>(b)?(a):(b))
优势;
由于调用函数和从函数返回的代码可能比实际执行这个代码工作的时间要多,宏比函数在程序的规模和速度上更胜一筹
函数的参数必须声明特定的类型,函数只能在类型合适的表达式上使用,宏不需要定义类型
劣势:
每次使用宏,一份宏定义的代码将插入到程序中,除非宏比较短,否则可能大幅度增加程序的长度
宏没法调试
宏与类型无关,不够严谨
宏可能带来运算符优先级的问题,可能有误
宏的参数可以出现类型,但是函数做不到
#define MALLOC(num,type)\
(type )malloc(num sizeof(type))
MALLOC(10,int) (int*)malloc(10 sizeof(int))
7.#和##
#运算符把宏的一个参数转换为字符串常量,它仅允许出现在带参数的宏的替换列表中
#所执行的操作可以理解为字符串化
#define PRINT(n) printf(" the "#n" is %d",n)
PRINT(a) 10
打印the a is 10
##可以把位于它两边的符号合成一个符号,它允许宏定义从分离的文本片段创建标识符,称为记号粘合,这样的链接必须产生一个合法的标识符,否则结果就是未定义的
int int_max(int x,int y)
{
return x>y?x:y:
}
float float_max(float a,float b)
{
return a>b?a:b;
}
#define GENERIC_MAX(type) \
type type##_max(type x,type y) \
{
return (x>y?x:y); \
}
一般来说有命名约定,宏名全部大写,函数名不要全部大写
#undef用于移除一个宏定义
#undef NAME
如果现存的一个名字需要被重新定义,那么它的旧名字要先移除
10.命令行定义
允许在命令行中定义符号,用于启动编译过程。当我们要根据同一个源文件编译出一个程序的不同版本就有用。
#include<stdio.h>
int main()
{
int array{ARRAY_SIZE];
for(int i=0;i<ARRAT_SIZE;i++)
{
array[i]=i;
}
for(int i=0;i<ARRATY_SIZE;i++)
{
printf("%d",array[i]);
}
return 0;
}linux环境展示
gcc -D ARRAY_SIZE=10 programe.c
11.条件编译
因为有条件编译指令,在编译一个程序时我们如果要将一些语句编译或放弃很方便
调试性的代码,删除可惜,保留碍事,可以选择性的编译
#include<stdio.h>
#define _DUBUG_
int main()
{
int arr[10]={0};
for(int i=0;i<10;i++)
{
arr[i]=i;
#ifdef _DEBUG_
printf("%d",arr[i]);//为了观察数组是否赋值成功
#endif //_DEBUG_
}
return 0;
}
1.#if 常量表达式 //条件,常量表达式由预处理器求值#endif
如
#define _DEBUG_ 1
#if _DEBUG_#endif
2.多个分支的条件编译
#if 常量表达式#elif 常量表达式#else#endif3.判断是否被定义
#if defined(symbol)//#ifdef symbol#if !defined(symbol)//#ifndef symbol4.嵌套指令
#if defined(OS_UNIX)#ifdef OPTION1unix_version_option1();#endif#ifdef OPTION2unix_version_option2();#endif
#elif defined(OS_UNIX)#ifdef OPTION2msdos_version_option2();#endif
#endif
12.头文件的包含
本地文件包含
#include"filename"Linux环境标准头文件的路径
/usr/include
VS环境标准头文件的路径2013
C:\program Files (x86)\Microsoft Visual Studio 12.0\VC\include
先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件
库文件包含
#include<filename>
查找头文件直接去标准路径下去查找,找不到就显示编译错误
用""包含也可以,查找的效率会小,不容易区分·
嵌套文件包含#include指令可以使另外一个文件被编译,就像它实际出现于#include指令的地方一样
test.h
void test();test.c
#include<test.h>
#include<test.h>
int main()
{
return 0;
}
这种替换的方式就是预处理器先删除这条指令,并用包含文件的内容替换。如果头文件被重复包含,会被重复编译,对编译的压力比较大
为了解决头文件被重复引入的问题,可以用条件编译,每个头文件的开头写:
#ifndef _TEST_H_
#define _TEST_H_
头文件内容
#endif或者#pragma once
(推荐《高质量C/C++编程指南》的附录考试试卷
停停停!!!!!咕咕咕要考考你!!!!咕咕咕咕咕咕咕咕咕咕咕咕咕咕咕咕咕咕!!!!!
(1) 头文件中ifndef/define/endif干什么的?
- 防止头文件被重复包含,避免出现重复定义的问题。
- 确保头文件在整个编译过程中只被包含一次。
- 这是 C/C++ 编程中的一种标准做法,能够有效解决大型项目里复杂的头文件包含问题。
(2)#include<filename>与#include"filename"的区别
特性 #include <filename> #include "filename"
搜索路径优先级 系统标准库目录 当前源文件目录 → 系统标准库目录
常见用途 包含系统 / 第三方库头文件 包含项目内部自定义头文件
文件位置示例 /usr/include/stdio.h ./my_header.h 或 ./utils/...
编译效率 可能更快(直接搜索系统目录) 可能稍慢(先搜索当前目录)
13.其他预处理指令
#error
在预处理阶段生成自定义错误信息,强制编译过程终止。
常用于条件编译中检查不合法的配置或依赖,防止错误代码被编译。
#progma类似上面的防止重复
提供编译器特定的指令,控制编译过程的细节行为。
与标准 C/C++ 语法不同,#pragma是编译器扩展,不同编译器的实现可能不同
#line
重新设置编译器的行号和文件名信息,主要用于调试和自动生成代码的场景。
当编译器报告错误或警告时,会使用#line指定的行号和文件名,
而非实际源代码的行号和文件名。
#progma pack()
控制结构体、联合体的内存对齐方式,影响成员变量在内存中的布局。
默认情况下,编译器会对结构体成员进行对齐(如 4 字节、8 字节对齐),
以提高内存访问效率,但可能导致内存浪费。#pragma pack()可减少这种浪费。
参考《C语言深度解剖》