四、C语言进阶:宏定义和头文件

4 宏定义和头文件

4.1 什么是宏定义?

宏是用来表示一段代码的标识符。

4.2 宏怎么用?

三种用法:

  • 当作常量使用;
  • 当作函数使用;
  • 编译预处理。

4.2.1 宏定义常量

  • 预定义宏
    ANSI C标准定义有些定义好的宏定义,称为预定义宏。这些宏定义以双下划线__开头结尾。
预定义宏 作用
FILE 表示当前源文件
LINE 当前所在文件的行号
DATE 文件被编译的日期
TIME 文件被编译的时间

实例

#include 
int main() {
    printf("%s:%d\n",__FILE__,__LINE__); //当前源文件:当前所在文件的行号
    printf("%s:%s\n",__DATE__,__TIME__); //文件被编译的日期:文件被编译的时间
}
  • 自定义宏
    除了使用标准定义的宏,可以使用#define指令用来自定义一个宏。
    语法:#define 标识符 值
    实例:#define PI 3.1415926

4.2.2 带参数的宏

带参数的宏:宏可以带参数,使用上有些像函数。
语法:#define 标识符(参数...) 代码
实例:#define square(x) ((x)*(x))

#include 
#define square(x) x*x
int main() {
    printf("%d\n",square(10));
    printf("%d\n",square(10+1));
}
100
21   <==  10+1*10+1

注:宏定义缺少括号导致宏定义边际效应,所以带参数的宏需要在以下两个位置加上括号:
1、参数出现的每个地方都要加括号。
2、整个值要加括号。

  • 宏定义的一些应用:交换数字、求最大值、求平方等
#include 
#define HELLO_WORLD \
"Hello World"
#define NUM 5
#define square(x) ((x)*(x))
#define SWAP(a,b) \
    int t = a; \
    a = b; \
    b = t;
#define MAX(a,b) ((a)>(b)?(a):(b))
int main() {
    char* s = HELLO_WORLD;
    printf("%s\n",s);
    int arr[NUM];

    int a = 10,b = 20;
    printf("a:%d\tb:%d\n",a,b);
    SWAP(a,b);
    printf("a:%d\tb:%d\n",a,b);
    printf("%d\n",MAX(a,b));

    printf("%d\n",square(10));
    printf("%d\n",square(10+1));
}

4.3 编译预处理

有时我们会使用没有值的宏,这种宏用于条件编译的,#ifdef #ifndef用于检查宏是否被定义过。控制代码的编译。
实例:

#include 
#define TEST
int main(){
#ifdef TEST
    printf("123\n");
#else
    printf("abc\n");
#endif
}
123

#开头的都是编译预处理指令。除了宏定义,还有文件包含#include和条件编译指令#if、#ifdef #ifndef、#else、#elif

  • 文件包含#include
    把文件内容包含到代码中
  • 条件编译指令#if、#ifdef #ifndef、#else、#elif
    根据编译条件,选择编译或者编译某段代码。

4.4 什么是头文件

  • #include指令
    把函数原型放到一个头文件.h中,在需要调用这个函数的源代码文件.c时,使用#include指令包含这个头文件,使编译器在编译的时候知道函数的原型。
  • 头文件作用
    头文件主要用于编译器的编译阶段,告诉编译器在代码中使用了这么一个函数。只是告诉编译器函数的原型,保证调用时参数类型和个数正确。

4.5 头文件怎么用?

在使用和定义函数的地方都要#include头文件。例如我们常用的stdio.h
#include指令不一定要放在.c文件的最前面,但是通常习惯这样做。

  • #include指令分类
    1、#include <>:编译器到指定目录查找,主要用于查找标准库的头文件。
    2、#include "":编译器先到当前目录查找(.c文件所在目录),如果没有再到指定目录查找。

注:#include指令是一个编译预处理指令,和宏一样,在编译之前就处理了。它会把指定的文件原封不动的插入到它所在的地方。

4.6 头文件怎么写?

头文件通常用来存放所有对外公开的函数的原型全局变量的声明
通常任何.c文件都有对应同名的.h文件。

4.6.1 声明

常见的声明:

函数声明
变量声明
结构声明
宏声明
枚举声明
类型声明

注:声明通常只能可以放在头文件中,否则,编译器连接会出现重名函数错误。

  • 重复声明
    在一个编译单元中,不允许重复声明,尤其是结构体声明。为了防止头文件不被多次#include导致重复声明。
  • 定义与声明
int i;// 变量的定义
extern int i; // 变量的声明

区别:声明是不产生代码的语句。定义是产生代码的语句。

4.6.2 标准头文件结构

避免头文件多次包含,必须使用标准头文件结构。

#ifndef _文件名_H__
#define _文件名_H__
// 声明
#endif

使用条件编译和宏,保证头文件在一个编译单元中只会#include一次。
注:#pragma once指令与上述代码作用相同,但是并不是所有编译器支持。

误区:
1、#include不是用来引入库的
2、头文件只有函数原型,函数实现在.a(Unix)或者.lib(Windows)中。
3、现代的C语言编译器会默认引入所有的标准库。

  • 实例:实现数组的打印和降序排序

1、 PrintArr.cPrintArr.h

#include 
#include "PrintArr.h"
void PrintArr(int* arr,int n){
    for(int i = 0;i < n;++i){
    	printf("%d ",arr[i]);
    }
    printf("\n");
}
void PrintArr(int* arr,int n);

2、 DesSort.cDesSort.h

#include 
#include "DesSort.h"
#include "PrintArr.h"
int cmp(const void* a,const void* b){
    return *(int*)a<*(int*)b ? 1:-1;
}
void DesSort(int* arr,int n){
    qsort(arr,n,sizeof(int),cmp);
    PrintArr(arr,n);
}
int cmp(const void* a,const void* b);
void DesSort(int* arr,int n);

3、test.c

#include 
#include "PrintArr.h"
#include "DesSort.h"
int main(){
    int arr[] = {1,2,3,4,5};
    PrintArr(arr,5);
    DesSort(arr,5);
}

编译:gcc test.c PrintArr.c DesSort.c -o main

你可能感兴趣的:(C语言进阶,c语言,linux)