宏定义

无参宏定义

#define 标识符 字符串
// 标识符是符号常量(宏名),字符串是常数、表达式、格式串等

注意事项:

  • 宏名一般用大写
  • 不可以重复定义同名的宏
  • 宏名末尾不加分号,不然连着分号一起替换了
  • 宏定义可以嵌套
  • 可以使用#undef命令终止宏定义的作用域
  • 预处理是编译之前的处理,预编译不做语法检查
  • 宏定义卸载函数的花括号外面,作用于为后面的程序
  • 字符串""中永远不包含宏名,否则该宏名当做字符串处理
  • 宏定义不分配内存,变量定义分配内存

有参宏定义

#define 宏名(形参表) 字符串
// 调用方式:宏名(实参表)

注意:

  • 宏定义中的形参是标识符
  • 宏调用中的实参是表达式
  • 宏定义中的字符串是表达式的话,要把每个参数用括号括起来,避免出错
  • 宏名和形参表的括号间不能有空格
  • 宏替换只作替换,不做计算,不做表达式求解
  • 函数调用在程序运行时进行,并且分配内存,宏替换在编译前进行,不分配内存
  • 宏可以有多个返回值,而函数只有一个
  • 当宏调用自身时,不会继续展开
  • 宏定义在换行时加上反斜杠 "\"
#include <stdio.h>
#define MULT(x) (x)*(x)

int Mult(int x){
    return x*x;
}

int main(){
    int i = 0;
    while(i <= 5){
        printf("i = %d, MULT(i) = %d\t", i, MULT(i++));
    }
    printf("\n");
    i = 0;
    while(i <= 5){
        printf("i = %d, Mult(i) = %d\t", i, Mult(i++));
    }

    return 0;
}

/*
输出:
i = 2, MULT(i) = 0      i = 4, MULT(i) = 6      i = 6, MULT(i) = 20
i = 1, Mult(i) = 0      i = 2, Mult(i) = 1      i = 3, Mult(i) = 4      i = 4, Mult(i) = 9      i = 5, Mult(i) = 16     i = 6, Mult(i) = 25
*/

利用宏定义使用双目运算求最大值

#include <stdio.h>
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
int main(){
    float a = 5.6;
    float b = 6.7;
    printf("%f", MAX(a,b));
    return 0;
}
// 输出:6.700000

利用宏定义求指针的地址

#include <stdio.h>
#define MEM_I(x) (*((int *)(x)))

int main(){
    int a = 10;
    int* p;
    p = &a;
    printf("%d\n", *p);
    printf("%d", MEM_I(&p));
    return 0;
}

求某个结构体占用的字节数

#include <stdio.h>
#define FSIZE(type, x) sizeof((type).x)

struct Student
{
    char name[10];
    int age;
};

int main(){
    struct Student stu = {"BOY", 21};
    printf("%s : %d\n", stu.name, stu.age);
    printf("%d\n", FSIZE(stu, name));
    printf("%d", FSIZE(stu, age));
    return 0;
}
/*
输出:
BOY : 21
10
4
*/

ANSI标准说明的预定义的宏名

__LINE__:语句所在行
__FILE__:语句所在文件
__DATA__:文件编译的时间(月:日:年)
__TIME__:文件编译的时间(时:分:秒)
__STDC__:

可以使用宏来输出语句所在文件、行

#include <stdio.h>
#define DEBUG(msg) printf("%s\n文件名 : [%s]\n行数 : [%d]", msg, __FILE__, __LINE__)

int main(){
    char buf[] = "AAAA";
    DEBUG(buf);
    return 0;
}
/*
输出:
AAAA
文件名 : [5.c]
行数 : [6]
*/

特殊用法

:将传入的参数作为字符串

#include <stdio.h>
#define MYPRINT1(msg) printf("%s", msg)
#define MYPRINT2(msg) printf("%s", #msg)
int main(){
    char buf[] = "AAAA";
    MYPRINT1(buf);
    printf("\n");
    MYPRINT2(buf);
    return 0;
}
/*
输出:
AAAA
buf
*/

:符号连接操作符

包含头文件

可以使用尖括号或者双引号包括头文件

双引号是指从当前源文件目录中查找,若未找到则去包含目录找

尖括号是在包含目录寻找

条件编译

ifdef

#ifdef 标识符(或#if defined标识符)
    程序1
#else
    程序2
#endif
// 如果标识符被define定义过则走程序1,否则走程序2
#include <stdio.h>
#define FLAG 1
int main(){
    #ifdef FLAG
        printf("FLAG 被宏定义了");
    #else
        printf("FLAG 未被宏定义");
    #endif
    return 0;
}
// 输出:FLAG 被宏定义了

ifndef

#ifndef 标识符
    程序1
#else
    程序2
#endif
// 如果标识符没有被define定义过,则走程序1,否则走程序2,和#ifdef相反
#include <stdio.h>
#define FLAG 1
int main(){
    #ifndef FLAG
        printf("FLAG 未被宏定义了");
    #else
        printf("FLAG 被宏定义了");
    #endif
    return 0;
}
// 输出:FLAG 被宏定义了

if

#if 常量表达式
    程序1
#else
    程序2
#endif
// 如果常量表达式的值是真则走程序1,否则走程序2
#include <stdio.h>
#define FLAG 0
int main(){
    #if FLAG
        printf("FLAG是真");
    #else
        printf("FLAG是假");
    #endif
    return 0;
}
// 输出:FLAG是假

用处

屏蔽跨平台的差异

包含程序功能模块

开关调试信息

避开硬件的限制

防止头文件被重复包含

在#ifndef中定义变量出现的问题

类型定义符typedef

#include <stdio.h>
typedef int my_int;
int main(){
    my_int a = 1;
    printf("%d", a);
    return 0;
}