预处理
预处理定义符号
__FILE__ //进行编译的源文件
__LINE__ //文件当前的行号
__DATE__ //文件被编译的日期
__TIME__ //文件被编译的时间
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义
这些符号都是语言内置的无需定义
最后__STDC__并不是所有编译器都定义了的至少在VS2019上未被定义.
#define
#define有本质上是将文本内容在编译时进行替换,又因为他可以替换参数所以就出现了宏
注意点:
#define有替换规则:
先替换参数的#define
随后插入文本将宏参数名所替换
最后对结果文件进行扫描看是否还有
宏定义格式:#define name( parament-list ) stuff
宏定义时不能吝啬括号
name和左括号直接不能有空隙如#define MAX(a,b) MAX不能和左括号有空隙
宏不可以递归 不可以一个宏套自己.
使用是宏最好不要使用带有副作用的宏参数如x++
宏一般比函数速度快一般简单的功能使用宏来实现较好
有时我们可以通过宏来实现函数无法实现的功能
宏无法调试因为在编译期间就被替换掉了
如使用宏PRINT打印无论浮点型或者整形还可以实时将我们要打印的变量名加入其中.通过#和##来实现(目录中有)
#define替换文本
语法:#define name stuff
举一个例子
#define MAX 1000 #define reg register //为 register这个关键字,创建一个简短的名字 #define do_forever for(;;) //用更形象的符号来替换一种实现 #define CASE break;case //在写case语句的时候自动把 break写上。 // 如果定义的 stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。 #define DEBUG_PRINT printf("file:%s\tline:%d\t \ date:%s\ttime:%s\n" ,\ __FILE__,__LINE__ ,\ __DATE__,__TIME__ )
我们C语言的程序员内部有默契,我们把#define定义的一般做全大写函数命名一般不是全大写一般是首字母大写或其他部分大写,当然我们宏定义假做成函数也会没有全大写.
#define定义宏
#define允许把参数替换到文本中,这种实现通常被称为宏,或定义宏
#define name(由逗号隔开的符号) stuff
举例:
#include<stdio.h> #define SQUARE(x) x*x int main() { printf("%d", SQUARE(3)); return 0; }
值得注意的是:
还有上面的宏其实有一个很大的弊病
我们用下面的代码来说明:
#include<stdio.h> #define SQUARE(x) x * x int main() { printf("%d", SQUARE(3+2)); return 0; }
这并不是我们想要的答案我们想得到5*5可是这个宏定义给的式子却给了我们11这是因为我们在定义宏时没有考虑到运算的优先级
首先我们的宏在编译阶段会直接和代码替换本次的宏就将printf函数内容进行了替换使SQUARE(3+2)替换成了3+2*3+2这样我们就得到了11的值
所以这也提醒我们在定义宏的时候一定不要吝啬我们的括号只需稍加修改我们的宏变为如下:
#include<stdio.h> #define SQUARE(x) ((x) * (x)) int main() { printf("%d", SQUARE(3+2)); return 0; }
就不会出现以上情况了.
注意:
宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。
当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。
#和##
介绍这俩之前我们需要先知道
char* p = "hello ""bit\n"; printf("hello"" bit\n"); printf("%s\n", p);
他们的打印结果都是"hello bit"这是因为字符串有自动连接的特点
首先介绍这步用到的知识点
字符串的自动连接
#放在字符串之间自动将变量转换成了字符串(放在宏定义里)
##将两个符号合成为一个符号
介绍一下#操作直接上例子:
#define PRINT(x) printf("The "#x" value is %d",x) int main() { int a = 10; PRINT(a); return 0; }
这样define里的#x和"x"一样.
很适合偷懒
包括打印其他类型也就只是多个参数
#define PRINT(x,y) printf("The "#x" value is" #y,x)
##的作用把两边的符号合成一个符号直接上例子:
#define ADD_TO_SUM(num, value) sum##num += value int main() { int sum1 = 20; int sum2 = 10; ADD_TO_SUM(1, 20); printf("%d", sum1); return 0; }
这样我们就在sum1直接加了20或把1改完2就是将sum2加20
带有副作用的宏参数
x+1;//不带副作用x++;//带有副作用
1
不要对宏使用带有副作用的参数
如下例
#define MAX(a, b) ( (a) > (b) ? (a) : (b) ) int main() { int x = 5; int y = 8; int z = MAX(x++, y++); printf("x=%d y=%d z=%d\n", x, y, z); return 0; }
不然总会带来我们不想要的结果因为y进行了两次++而x进行了一次++ z得到的是y++的值也就是9当然我们知道宏MAX是什么可以轻松反推但是未来大型项目中这样搞不知会出现什么bug
undef
移除宏定义
#undef NAME //如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除。
命令行定义^ 2
许多C 的编译器提供了一种能力,允许在命令行中定义符号。用于启动编译过程。
例如:当我们根据同一个源文件要编译出不同的一个程序的不同版本的时候,这个特性有点用处。(假 定某个程序中声明了一个某个长度的数组,如果机器内存有限,我们需要一个很小的数组,但是另外一 个机器内存大写,我们需要一个数组能够大写。)
这样我们就直接将M定义成了100直接就加入了代码中
条件编译
在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的。因为我们有条件 编译指令。
比如:
那些我们为了调试而编写的代码,删除可惜但是保留住又十分碍事.
常见的条件编译指令(使用时均包含在main函数中):
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 OPTION1 unix_version_option1(); #endif #ifdef OPTION2 unix_version_option2(); #endif #elif defined(OS_MSDOS) #ifdef OPTION2 msdos_version_option2(); #endif #endif
一个一个讲
#if 常量表达式 //... #endif
后面跟常量表达式为真就进入为假就跳过(除了0都是真)所以有时也被当做注释使用(为了装逼=-=)
#define __DEBUG__ 1 #if __DEBUG__ //.. #endif
只要这个__DEBUG__为非零就进入
#if defined(symbol)//symbol被定义了就进入 #ifdef symbol//同上 #if !defined(symbol)//没有被定义就进入 #ifndef symbol//同上
可以嵌套使用
文件包含
我们可以使用#include来使另一个文件被编译就行他实际就在#include的位置一样
这种替换的方式很简单:
预处理器先删除这条指令,并用包含文件的内容替换。
这样一个源文件被包含10次,那就实际被编译10次
包含方式
分两种
#include"Add.h"
这种包含方式我们的编译器会现在源文件的目录下查找如果没找到编译器就会想查找库函数头文件一样寻找.还没找到就报错
#include<stdio.h>
直接就在库函数的头文件找,找不到就报错
嵌套文件的包含
有时我们会不小心多次嵌套自己的头文件或者一个头文件被多次嵌套使用就浪费了内存,这时我们只需在文件开头写到
#pragma once
1
或
#ifndef __TEST_H__
#define __TEST_H__
//头文件的内容
#endif //__TEST_H_
结尾
C进阶就基本算是完成了,等我吧文件和通讯录更出来就算完成了C语言的进阶,后续会先C深剖再数据结构.数据结构穿插着题目进行.
本次测试均在Linux环境下进行。 ↩︎