文件包含指令

文件包含处理是指一个源文件可以将另一个文件的全部包含进来.c提供了#include命令用来实现文件包含
image.png

#include<>#include ""区别

  • “”表示系统先在file1.c所在的当前目录找file1.h,如果找不到,再按系统指定的目录检索
  • <>表示系统直接按系统指定的目录检索

注意:

  1. #include<>常用于包含库函数的头文件
  2. #include ""常用于包含自定义的头文件
  3. 理论上#include可以包含任意格式的文件(.c .h等),但一般用于头文件的包含

    宏参数的字符串化

    # 的用法
    #用来将宏参数转换为字符串,也就是在宏参数的开头和末尾添加引号。例如有如下宏定义:
    #define STR(s) #s
    那么:

    1. printf("%s", STR(c.biancheng.net));
    2. printf("%s", STR("c.biancheng.net"));

    分别被展开为:

    1. printf("%s", "c.biancheng.net");
    2. printf("%s", "\"c.biancheng.net\"");

    注意: 对空格处理
    当传入参数名间存在空格时,编译器将会自动连接各个子字符串,用每个子字符串中只以一个空格连接,忽略其中多余一个的空格, 如果是带引号就当做一个整体 "你好啊 hello 1 22"

    1. 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代替.这种写法使用户能以一个简单的名字代替一个长的字符串,在预处理时将宏名替换成字符串的过程称为宏展开.宏定义,只在宏定义的文件中起作用
    说明:

  4. 宏名一般用于大写,以便于与变量区别

  5. 宏定义可以是常数,表达式等
  6. 宏定义不作语法检查,只有在编译被宏展开后的源程序才会报错
  7. 宏定义不是c语言,不在行末加分号
  8. 宏名有效范围为从定义到本源文件结束
  9. 可以用#undef命令终止宏定义的作用域

    带参数的宏定义(宏函数)

    在项目中,经常把一些短小又频繁使用的函数写成宏函数,这是由于宏函数没有普通函数参数压栈,跳转,返回等开销,可以调高程序的效率
    宏通过使用参数,可以创建外形和作用都与函数类似的类函数宏(function-like macro)
    宏的参数也用园括号括起来 ```cpp

    define 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

这和发生语法错误的效果是一样的,程序编译失败。请看下面的截图:
image.png
需要注意的是:报错信息不需要加引号" ",如果加上,引号会被一起输出。例如将上面的 #error 命令改为:

#error "This programme cannot compile at Windows Platform"

那么错误信息如下:
image.png
再如,当我们希望以 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__