文件包含指令
文件包含处理是指一个源文件可以将另一个文件的全部包含进来.c提供了#include命令用来实现文件包含
#include<>和#include ""区别
- “”表示系统先在file1.c所在的当前目录找file1.h,如果找不到,再按系统指定的目录检索
<>表示系统直接按系统指定的目录检索
注意:
#include<>常用于包含库函数的头文件#include ""常用于包含自定义的头文件理论上
#include可以包含任意格式的文件(.c .h等),但一般用于头文件的包含宏参数的字符串化
#的用法
#用来将宏参数转换为字符串,也就是在宏参数的开头和末尾添加引号。例如有如下宏定义:#define STR(s) #s
那么:printf("%s", STR(c.biancheng.net));printf("%s", STR("c.biancheng.net"));
分别被展开为:
printf("%s", "c.biancheng.net");printf("%s", "\"c.biancheng.net\"");
注意: 对空格处理
当传入参数名间存在空格时,编译器将会自动连接各个子字符串,用每个子字符串中只以一个空格连接,忽略其中多余一个的空格, 如果是带引号就当做一个整体"你好啊 hello 1 22"printf("%s\n", STR(abc 123));
输出
abc 123宏参数的连接
##的用法##称为连接符,用来将宏参数或其他的串连接起来。例如有如下的宏定义:#define CON1(a, b) a##e##b #define CON2(a, b) a##b##00那么:
printf("%f\n", CON1(8.5, 2)); printf("%d\n", CON2(12, 34));将被展开为:
printf("%f\n", 8.5e2); printf("%d\n", 123400);注意: 当用
**##**连接形参时,**##**前后的空格可有可无
另外,如果**##**后的参数本身也是一个宏的话,**##**会阻止这个宏的展开宏定义
无参数的宏定义(宏常量)
如果在程序中大量用到了100这个值,那么为了方便管理,我们可以将其定义为:
const int num = 100;
但是如果我们使用num定义一个数组,在不支持c99标准的编译器上是不支持的,因为num不是一个编译器常量,如果想得到一个编译器常量,那么可以使用:#define NUM 100在编译预处理时,将程序中在该语句以后出现的所有的num都用100代替.这种写法使用户能以一个简单的名字代替一个长的字符串,在预处理时将宏名替换成字符串的过程称为宏展开.宏定义,只在宏定义的文件中起作用
说明:宏名一般用于大写,以便于与变量区别
- 宏定义可以是常数,表达式等
- 宏定义不作语法检查,只有在编译被宏展开后的源程序才会报错
- 宏定义不是c语言,不在行末加分号
- 宏名有效范围为从定义到本源文件结束
- 可以用
#undef命令终止宏定义的作用域带参数的宏定义(宏函数)
在项目中,经常把一些短小又频繁使用的函数写成宏函数,这是由于宏函数没有普通函数参数压栈,跳转,返回等开销,可以调高程序的效率
宏通过使用参数,可以创建外形和作用都与函数类似的类函数宏(function-like macro)
宏的参数也用园括号括起来 ```cppdefine SUM(x, y) ((x) + (y))
void test() { //仅仅只是做文本替换,下边替换为int ret = ((10) + (20)); //不进行计算 int ret = SUM(10, 20); printf(“ret:%d\n”, ret); }
注意:
1. **宏的名字中不能有空格,但是在替换的字符串中可以有空格.ANSIC允许在参数列表中使用空格**
1. **用括号括住每一个参数,并括住宏的整体定义**
1. **用大写字母表示宏的函数名**
1. **如果打算宏代替函数来加快程序运行速度.假如在程序中只使用一次宏对程序的运行时间没有太大提高**
宏名与形参表之间也不能有空格,而形参表中形参之间可以出现空格
```java
#define SUM (a,b) a + b //定义有参宏
printf("SUM = %d\n", SUM(1,2)); //调用有参宏。Build Failed!
因为 SUM 被替换为:(a,b) a + b
宏函数
//宏函数,不是一个真正的函数(只是预处理器进行简单的文本转换)
#define MYADD(x, y) ((x) + (y))
int main() {
int a = 10;
int b = 20;
//宏函数在一定场景下效率要比函数高
printf("a + b = %d\n", MYADD(a, b));
printf("a + b = %d\n", ((a) + (b)));
getchar();
return 0;
}
多行
如果要写宏不止一行,则在结尾加反斜线符号使得多行能连接上,如:
#define HELLO "hello \
the world"
注意第二行要对齐,否则,如:
#define HELLO "hello the wo\
rld"
printf("HELLO is %s\n", HELLO);
//输出结果为: HELLO is hello the wo rld
也就是行与行之间的空格也会被作为替换文本的一部分
而且由这个例子也可以看出:宏名如果出现在源程序中的“”内,则不会被当做宏来进行宏代换。
嵌套
宏可以嵌套,但不参与运算:
#define M 5 // 宏定义
#define MM M * M // 宏的嵌套
printf("MM = %d\n", MM); // MM 被替换为: MM = M * M, 然后又变成 MM = 5 * 5
宏代换的过程在上句已经结束,实际的 5 * 5 相乘过程则在编译阶段完成,而不是在预处理器工作阶段完成,所以宏不进行运算,它只是按照指令进行文字的替换操作。再强调下,宏进行简单的文本替换,无论替换文本中是常数、表达式或者字符串等,预处理程序都不做任何检查,如果出现错误,只能是被宏代换之后的程序在编译阶段发现。
定义结构体
用宏定义表示数据类型,可以使代码简便:
#define STU struct Student // 宏定义STU
struct Student{ // 定义结构体Student
char *name;
int sNo;
};
STU stu = {"Jack", 20}; // 被替换为:struct Student stu = {"Jack", 20};
printf("name: %s, sNo: %d\n", stu.name, stu.sNo);
一些特殊的预定义宏
c编译器,提供了几个特定形式的预定义宏,在实际编程中可以直接使用,很方便
//__FILE__ 宏所在文件的源文件名
//__LINE__ 宏所在行的行号
//__DATE__ 代码编译的日期
//__TIME__ 代码编译的时间
int main() {
printf("%s\n", __FILE__);
printf("%d\n", __LINE__);
printf("%s\n", __DATE__);
printf("%s\n", __TIME__);
getchar();
return 0;
}
输出
/Users/jdxia/Desktop/study/studyc/main.c
7
Oct 19 2019
22:35:14
可变宏… 和 VA_ARGS
我们经常要输出结果时要多次使用 prinf(“…”, …); 如果用上面例子
#define SUM(a,b) printf(#a " + "#b" = %d\n",((a) + (b))) //宏定义,运用 # 运算符
SUM(1 + 2, 3 + 4); //宏调用
//输出结果:1 + 2 + 3 + 4 = 10
则格式比较固定,不能用于输出其他格式。
这时我们可以考虑用可变宏(Variadic Macros)。用法是:
#define PR(...) printf(__VA_ARGS__) //宏定义
PR("hello\n"); //宏调用
//输出结果:hello
在宏定义中,形参列表的最后一个参数为省略号”…”,而”VA_ARGS“就可以被用在替换文本中,来表示省略号”…”代表了什么。而上面例子宏代换之后为: printf(“hello\n”);
还有个例子如:
#define PR2(X, ...) printf("Message"#X":"__VA_ARGS__) //宏定义
double msg = 10;
PR2(1, "msg = %.2f\n", msg); //宏调用
//输出结果:Message1:msg = 10.00
在宏调用中,X的值为10,所以 #X 被替换为”1”。宏代换后为:
printf("Message""1"":""msg = %.2f\n", msg);
接着这4个字符串连接成一个:
printf("Message1:msg = %.2f\n", msg);
要注意的是:省略号“…”只能用来替换宏的形参列表中最后一个!
条件编译
一般情况下,源程序中所有的行都参加编译.但是有时希望对部分源程序行只在满足一定条件下才编译,即对这部分源程序行指定编译条件
//#define FLAG
#define FLAG
#ifdef FLAG
void func(long a) {
}
#else
void func(int a) {
}
#endif
存在 #ifdef
不存在 #ifndef
#if的用法
#include <stdio.h>
int main(){
#if _WIN32
printf("This is Windows!\n");
#else
printf("Unknown platform!\n");
#endif
#if __linux__
printf("This is Linux!\n");
#endif
//mac这2个都可以 __APPLE__ __MACH__
#if __APPLE__
printf("This is apple!\n");
#endif
#if __MACH__
printf("This is mac!\n");
#endif
return 0;
}
#ifdef 的用法
ifdef 用法的一般格式为:
#ifdef 宏名
程序段1
#else
程序段2
#endif
它的意思是,如果当前的宏已被定义过,则对“程序段1”进行编译,否则对“程序段2”进行编译。
也可以省略 #else:
#ifdef 宏名
程序段
#endif
debug和Release模式
VS/VC 有两种编译模式,Debug 和 Release。在学习过程中,我们通常使用 Debug 模式,这样便于程序的调试;而最终发布的程序,要使用 Release 模式,这样编译器会进行很多优化,提高程序运行效率,删除冗余信息。
为了能够清楚地看到当前程序的编译模式,我们不妨在程序中增加提示,请看下面的代码:
#include <stdio.h>
#include <stdlib.h>
int main(){
#ifdef _DEBUG
printf("正在使用 Debug 模式编译程序...\n");
#else
printf("正在使用 Release 模式编译程序...\n");
#endif
system("pause");
return 0;
}
当以 Debug 模式编译程序时,宏 _DEBUG 会被定义,预处器会保留第 5 行代码,删除第 7 行代码。反之会删除第 5 行,保留第 7 行``
#ifndef的用法
#ifndef 用法的一般格式为:
#ifndef 宏名
程序段1
#else
程序段2
#endif
与 #ifdef 相比,仅仅是将 #ifdef 改为了 #ifndef。它的意思是,如果当前的宏未被定义,则对“程序段1”进行编译,否则对“程序段2”进行编译,这与 #ifdef 的功能正好相反
三者之间的区别
最后需要注意的是,#if 后面跟的是“整型常量表达式”,而 #ifdef 和 #ifndef 后面跟的只能是一个宏名,不能是其他的。
例如,下面的形式只能用于 #if
#include <stdio.h>
#define NUM 10
int main(){
#if NUM == 10 || NUM == 20
printf("NUM: %d\n", NUM);
#else
printf("NUM Error\n");
#endif
return 0;
}
运行结果:NUM: 10
再如,两个宏都存在时编译代码A,否则编译代码B:
#include <stdio.h>
#define NUM1 10
#define NUM2 20
int main(){
#if (defined NUM1 && defined NUM2)
//代码A
printf("NUM1: %d, NUM2: %d\n", NUM1, NUM2);
#else
//代码B
printf("Error\n");
#endif
return 0;
}
运行结果:NUM1: 10, NUM2: 20#ifdef 可以认为是 #if defined 的缩写
#error命令
error 指令用于在编译期间产生错误信息,并阻止程序的编译,其形式如下:
#error error_message
例如,我们的程序针对 Linux 编写,不保证兼容 Windows,那么可以这样做:
#ifdef WIN32
#error This programme cannot compile at Windows Platform
#endif
WIN32 是 Windows 下的预定义宏。当用户在 Windows 下编译该程序时,由于定义了 WIN32 这个宏,所以会执行 #error命令,提示用户发生了编译错误,错误信息是:
This programme cannot compile at Windows Platform
这和发生语法错误的效果是一样的,程序编译失败。请看下面的截图:
需要注意的是:报错信息不需要加引号" ",如果加上,引号会被一起输出。例如将上面的 #error 命令改为:
#error "This programme cannot compile at Windows Platform"
那么错误信息如下:
再如,当我们希望以 C++ 的方式来编译程序时,可以这样做:
#ifndef __cplusplus
#error 当前程序必须以C++方式编译
#endif
判断不同平台
1. Windows
_WIN32 32位和64位系统都有定义
_WIN64 仅64位系统有定义
2. Unix
unix
__unix
__unix__
3. Mac OS X
__APPLE__
__MACH__
4. Linux
__linux__
linux
__linux
5. FreeBSD
__FreeBSD__
