目录
一、预定义符号
在C语言中有一些内置的预定义符号,如下:
__FILE__ // 进行编译的源文件
__LINE__ // 文件当前的行号
__DATE__ // 文件被编译的日期
__TIME__ // 文件被编译的时间
__STDC__ // 若编译器遵循的时ANSI C,值为1,否则未定义
__FUNCTION__// 当前所在的函数
举例如下:
源文件内容:
#include <stdio.h> // 1
// 2
int main() // 3
{ // 4
printf("path:%s\n", __FILE__);// 5
printf("line:%d\n", __LINE__);// 6
return 0;
}
运行如下:
二、#define
1.#define 定义标识符
使用define定义标识符是很常见的,可以直接更改标识符所设定的值而改变程序的执行结果,省去了去代码中逐个修改的繁杂过程。
举个栗子:
#include <stdio.h>
#define M 10
int mian()
{
int a = M;
printf("a is %d\n", a);
return 0;
}
需要 特别注意的是,在使用#define 定义标识符时,应避免在结尾加上分号
虽然支持这样的写法,但是在程序的调用中并不总是会将标识符放在行末以省略分号。故强烈建议不要加上;,这样容易导致程序出现问题或者语法错误。
2.#define 定义宏
宏:#define机制的一个规定,允许把参数替换到文本中,这种实现通常称为宏或者定义宏
宏的申明方式如下:
#define name(parament-list) stuff
其中的parament-list 是指有逗号隔开的符号表,可能会出现在stuff中
注意:左括号必须与定义的name相邻,否则参数列表将会被解释成为stuff的一部分
关于其他注意事项通过例子进行说明会更加明了:
例1;
#include <stdio.h>
#define SQUARE1(x) x*x
#define SQUARE2(x) (x)*(x)
int main()
{
int a = 5;
printf("SQUARE1 is %d\n", SQUARE1(a));
printf("SQUARE2 is %d\n", SQUARE2(a));
printf("SQUARE1 is %d\n", SQUARE1(a + 1));
printf("SQUARE2 is %d\n", SQUARE2(a + 1));
return 0;
}
结果如下:
例2:
#include <stdio.h>
#define ADD1(x,y) (x)+(y)
#define ADD2(x,y) ((x)+(y))
int main()
{
int a = 5;
int b = 6;
printf("ADD1 is %d\n", ADD1(a,b));
printf("ADD1 is %d\n", 2 * ADD1(a,b));
printf("ADD2 is %d\n", 2 * ADD2(a,b));
return 0;
}
结果如下:
结合两个例子,可以得出,在宏定义的表达式中,只有当每个变量和整个表达式都是用括号括起来时,得出的才会是正确的结果。
结论:对于所有数值表达式进行求值的宏定义都应该给数值和表达式加上括号,避免在使用宏时由于参数中的操作符或者时临近的操作符之间不可预料的相互作用而得出错误得结果。
3.替换规则
在调用宏时,遵循以下规则:
a.首先检查参数,是否包含任何由#define定义的符号,有就进行替换
b.替换文本被插入到程序原来文本的位置,对于宏,则是将参数名替换为调用时的值
c.替换完成后,再次扫描,若还存在#define定义符号,就重复整个过程
需要注意的是:#define定义的符号和宏中可以出现其他#define定义的符号。但是宏不能出现递归。当预处理器搜索#define定义的符号时,位于字符串常量中的内容并不会被搜索和替换。
4. #和##
在C语言中,位于英文双引号之间的内容我们称为字符串,字符串具有自动连接的特点。如下:
#include <stdio.h>
int main()
{
printf("hello world\n");
printf("hello ""world\n");
return 0;
}
当一行中的内容太长时,可以使用"\"作为续行符,使得显示效果更好。
(1)#
使用#,把一个宏参数编程对应的字符:
(2)##
使用##,将两个字符串连接称为一个新的字符串,若存在该名称的变量,则使用改变量的值
举例如下:
#include <stdio.h>
#define PRINT1(RET, VALUE) printf("the value is "RET"\n", VALUE);
#define PRINT2(RET, VALUE) printf("the value of "#VALUE" is "RET"\n", VALUE);
#define PRINT3(RET, VALUE) RET##VALUE
int mian()
{
int i = 10;
int helloworld = 100;
PRINT1("%d\n", 10);
PRINT2("%d\n", i + 10);
printf("%d\n", PRINT3(hello,world));
return 0;
}
结果如下:
5.带有副作用的宏参数
举例说明:
// x+1; // 不带副作用
// x++; // 带有副作用
#include <stdio.h>
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int main()
{
int x = 5;
int y = 8;
int z1 = MAX(x, y);
printf("x is %d\ny is %d\nz1 is %d\n", x, y, z1);
x = 5;
y = 8;
int z2 = MAX(x++, y++);
printf("x is %d\ny is %d\nz2 is %d\n", x, y, z2);
return 0;
}
结果如图:
可得,在调用宏时,使用自增(或自减等)类型的参数会导致输出结果与预期不符。
6. 宏和函数的对比
宏通常被应用于执行简单的运算
不使用函数来完成的原因:
a.调用函数和函数返回值的时间比宏更久——宏比函数在程序的规模和速度方面更胜一筹
b.函数的参数必须声明为特定的类型——宏和类型是无关的
缺点:
a.每次使用宏,一份宏定义得代码将插入到程序中,除非宏比较短,否则将大幅增加程序的长度
b.宏是无法调试的
c.宏由于类型无关,即不够严谨
d.宏可能会带来运算符优先级的问题,导致程序出错
e.宏是无法递归的
7. 命名约定
通常定义宏时,名称全部大写。
通常定义函数时,仅单词首字母大写,多单词连接的函数名通常每个单词首字母大写以便区分。
8. #undef
#undef用来移除一个宏定义,举例如下:
如果已有的名字的宏需要被重新定义,那么这个宏名应当先移除。
9. 命令行定义
命令行定义即是在执行可执行文件的命令行中对代码内的内容进行定义。
创建test.c,内容如下:
#include <stdio.h>
int main()
{
int arr[MAX];
int i = 0;
for(i = 0; i < MAX; i++)
{
arr[i] = i;
}
for(i = 0;i < MAX; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
在命令行中定义MAX为9,则只循环0~8
10. 条件编译
条件编译即选择性的编译。
常见的条件编译指令:
(1)#if
#include <stdio.h>
int main()
{
// 当if后表达式为真时
// 编译范围内的内容,可以为传递的别的预定义的值——非0为真
#if 0
printf("hello\n"); // if 为0,不执行输出语句
#endif
return 0;
}
(2)#ifdef 和 #ifndef
#include <stdio.h>
#define TEST 0
#define HEHE 1
int main()
{
// 如果TEST定义了,就会被编译,哪怕定义TEST为0也会被执行
#ifdef TEST
printf("nihao\n");
#endif
// 如果TEST被定义,就参与编译
#if defined(TEST)
printf("nihaoya\n");
#endif
// ifndef——若HEHE不被定义,就执行;定义了就不执行
#ifndef HEHE
printf("wohao\n");
#endif
// 如果HEHE不被定义,就参与编译
#if !defined(HEHE)
printf("wohaone\n");
#endif
return 0;
}
(3)嵌套指令
#include <stdio.h>
#define M1
#define M2
int main()
{
#ifdef M1
#ifdef M2
// 只有当M1和M2都被定义时,才会执行该语句
printf("M2 has been defined\n");
#endif
// 只要当M1被定义,就执行该语句
printf("M1 has been defined\n");
#endif
return 0;
}
三、文件包含
1. 头文件包含
常用的主要为库文件包含和本地头文件包含
库文件包含——#include <stdio.h>(仅以stdio.h为例)
本地头文件包含——#include "add.h"
两者的主要区别是查找策略的区别
使用“”时,首先在工程目录下查找,若没有就再去标准位置进行查找
使用<>时,直接去库函数所在的目录(标准位置)下查找
使用""引用库函数时可行的,但会造成程序执行时间的增加,不建议这样做。
2. 嵌套文件包含
在有时候编写代码时,会导致头文件被重复包含,会造成代码冗余,不利于程序的快速执行。
介绍两个小方法
a. 使用#pragma once ,使得不论头文件引用多少次,都只会被包含一次
b. 使用#ifdef xxx #define xxx ... #endif 进行判断
#ifndef __TEST_H__
#define __TEST_H__
#include <stdio.h>
#endif
只有当stdio.h没有被包含时,才会对该头文件进行包含,若已存在该头文件,就不会再次进行包含。
还有其他预处理指令,在等待你的发现~~~