C语言-宏的应用

定义宏名:

#define 宏名 会被替换的内容

使用宏名:

printf("%d\n",宏名);

注意:在预处理阶段,预处理器会把代码使用的宏名替换成宏名后面的那段内容。

宏常量:

#define 宏名 字面值数据

给没有意义的字面值数据,取一个有意义名字,代替它,这样可以提高代码的可读性、可扩展性,还可以方便代码扩展。

#include <stdio.h>
#include <stdlib.h>
​
#define ARR_LEN 20                                                                                      
​
void show_arr(int arr[],size_t len)
{
    for(int i=0; i<len; i++)
    {   
        printf("%d ",arr[i]);
    }   
}
​
int main(int argc,const char* argv[])
{
    int arr[ARR_LEN];
    for(int i=0; i<ARR_LEN; i++)
    {   
        arr[i] = rand() % 100;
    }   
​
    show_arr(arr,ARR_LEN);
}

宏表达式:

#define 宏名 表达式、操作、更复杂的标识符

#define STU_FORMAT "%s %c %hd %d"
#define sf scanf
#define YEAR_SEC (3600*24*365ul)
#include <stdio.h>
​
typedef struct Student
{
    int id; 
    char name[20];
    char sex;
    float score;
}Student;
​
#define STU_FORMAT "%d %s %c %g"
​
int main(int argc,const char* argv[])
{
    Student stu;
    printf("请输入学生的学号、姓名、性别、成绩:");
    scanf(STU_FORMAT,&stu.id,stu.name,&stu.sex,&stu.score);
    printf(STU_FORMAT"\n",stu.id,stu.name,stu.sex,stu.score);
}

定义宏常量和宏表达式要注意的问题:

由于宏常量和宏表达式可能使用在表达式中,因此在定义宏常量和宏表达式的末尾不要加分号。

枚举常量与宏常量的区别:

提前:它们都可以提高代码的可读性,给没有意义字面值数据取一个有意义的名字。

1、从安全角度来说枚举常量要比宏常量安全,因为宏常量是通过替换实现的,在替换过程中可能会导致新的错误,如:有与宏名重名和函数或变量。

2、但枚举常量没有宏常量方便,枚举常量只能是整型数据,而宏常量可以是任何类型的,甚至是复杂的表达式,宏常量的使用范围更广。

总结:如果是大量的整型字面值数据建议定义为枚举常量,如果少量的或整型以外的类型的字面值数据建议定义为宏常量。

预定义的宏:

编译器预定义的宏:

__FILE__ 获取当前文件名
__func__ 获取当前函数名
__LINE__ 获取当前行号
__DATE__ 获取当前日期
__TIME__ 获取当前时间,编译时当前时间
__WORDSIZE 获取当前编译器的位数
// 适合用来显示警告、错误信息。
#include <stdio.h>
​
int main(int argc,const char* argv[])
{
    printf("%s\n",__FILE__);
    printf("%s\n",__func__);
    printf("%d\n",__LINE__);
    printf("%s\n",__DATE__);
    printf("%s\n",__TIME__);                 
}

标准库预定义的宏:

// limits.h 头文件中定义的所有整数类型最大值、最小值
#define SCHAR_MIN (-128)
#define SCHAR_MAX 127 
#define UCHAR_MAX 255
​
#define SHRT_MIN  (-32768)
#define SHRT_MAX  32767
#define USHRT_MAX 65535
​
#define INT_MIN  (-INT_MAX - 1)
#define INT_MAX  2147483647
#define UINT_MAX  4294967295U
​
#define LLONG_MAX   9223372036854775807LL
#define LLONG_MIN   (-LLONG_MAX - 1LL)
#define ULLONG_MAX  18446744073709551615ULL
​
// stdlib.h 头文件定义两个结标志
#define EXIT_SUCCESS (0)
#define EXIT_FAILURE (-1)
​
// stdbool.h 头文件定义了bool、true、false
#define bool    _Bool
#define true    1
#define false   0
​
// libio.h 头文件定义了NULL
#define NULL ((void*)0) 

宏函数:

什么是宏函数:

宏函数不是真正的函数,而是带参数的宏替换,只是使用方法像函数而已。

在代码中使用宏函数,预处理时会经历两次替换,第一次把宏函数替换成它后面的一串代码、表达式,第二次把宏函数中的参数替换到表达式中。

        

#define 宏名(a,b,c,...) a+b*c

#include <stdio.h>
#define ARR_SIZE(a) sizeof(a)/sizeof(a[0])
​
int main(int argc,const char* argv[])
{
    int arr[] = {1,2,3,4,4,3,2,1,5,5,6,6,8,7,9,9};
    // 第一次是把ARR_SIZE(arr)替换成了sizeof(a)/sizeof(a[0]),第二次替换是把a替换成了arr,最终ARR_SIZE(arr)变成了sizeof(arr)/sizeof(arr[0]);
    int len = ARR_SIZE(arr);
    for(int i=0; i<len; i++)
    {   
        printf("%d ",arr[i]);
    }   
}

定义宏函数要注意的问题:

1、如果宏函数后面的代码有多行,可以使用大括号包括,进行保护。

#define 宏名(a,b,c,...) {代码1; 代码2; ...}
#include <stdio.h>
#define swap(a,b) {int t=a; a=b; b=t;}
​
int main(int argc,const char* argv[])
{
    int num1 = 3 , num2 = 7;
    swap(num1,num2);
    printf("%d %d\n",num1,num2);
}  

2、宏函数后面的代码不能直接换行,如果代码确定太长,可以使用续行符换行。

#define 宏名(a,b,c,...) {  \
    代码1; \
    代码2; \
     ...   \
}
#include <stdio.h>
​
#define swap(a,b) { \
    int t=a;        \      
    a=b;            \
    b=t;            \
}
​
int main(int argc,const char* argv[])
{
    int num1 = 3 , num2 = 7;
    swap(num1,num2);
    printf("%d %d\n",num1,num2);
}

3、使用了大括号保护的宏函数,在使用时末尾可以不加分号,为了统一语法的一致性,强制使用者在宏函数末尾加分号,可以使用do while语句来实现。

#define 宏名(a,b,c,...) do{  \
    代码1; \
    代码2; \
    ...   \
}while(0)
#include <stdio.h>
​
#define swap(a,b) do{ \
    int t=a;        \
    a=b;            \
    b=t;            \
}while(0)
​
int main(int argc,const char* argv[])
{
    int num1 = 3 , num2 = 7;
    swap(num1,num2);     
    printf("%d %d\n",num1,num2);
}

4、为了防止宏函数出现二义性,对宏参数要尽量多加小括号。

二义性:就是使用宏函数的环境不同、参数不同,造成宏函数有多执行规则,会出现出乎意料的执行结果,这种宏函数的二义性,设计宏函数时要尽量杜绝。

#include <stdio.h>
​
// #define SUM(a,b) a+b 这种写法就会造成二义性
#define SUM(a,b) (a+b)
​
int main(int argc,const char* argv[])
{
    printf("%d\n",SUM(5,4));
  
    int num = SUM(3,4)*12;
    printf("%d\n",num); 
}
​
#include <stdio.h>
// #define MUT(a,b) (a*b) 这种写法就会造成二义性
#define MUT(a,b) ((a)*(b))
int main(int argc,const char* argv[])
{
    printf("%d\n",MUT(3,4));
    printf("%d\n",MUT(3+2,4));
}
​

调用宏函数要注意的问题:

1、传递给宏函数的参数不能使用自变运算符,因为我们无法知道参数在宏代码中会被替换多少次。

#include <stdio.h>              
​
#define MUT(a,b) ((a)*(a)*(b))
​
int main(int argc,const char* argv[])
{
    int num = 1;
    printf("%d\n",MUT(num++,4));
    printf("%d\n",num);
}

2、宏函数没有返回值,只是个别宏函数表达式有计算结果。

普通函数与宏函数的优缺点?

宏函数的优点:

1、执行速度快,它不是真正的函数调用,而是代码替换,不会经历传参、跳转、返回值。

2、不会检查参数的类型,因此通用性强。

宏函数的缺点:

1、由于它不是真正的函数调用,而是代码替换,每使用一次,就会替换出一份代码,会造成代码冗余、编译速度慢、可执行文件变大。

2、没有返回值,最多可以有个执行结果。

3、类型检查不严格,安全性低。

4、无法进行递归调用。

普通函数的优点:

1、不存在代码冗余的情况,函数的代码只会在代码段中存储一份,使用时跳转过去执行,执行结束后再返回,还可以附加返回值。

2、安全性高,会对参数进行类型检查。

3、可以进行递归调用,实现分治算法。

函数的缺点:

1、相比宏函数它的执行速度慢,调用时会经历传参、跳转、返回等过程,该过程耗费大量的时间。

2、类型专用,形参什么类型,实参必须是什么类型,无法通用。

什么样的代码适合封装成宏函数?

1、代码量少,即使多次使用也不会造成代码段过度冗余。

2、调用次数少,但执行次数多,也就是宏函数会在循环语句中调用。

3、函数的功能对返回值没有要求,也就是函数的功能不是通过返回值达到的。

条件编译:

条件语句(if、switch、for、while、do while)会根据条件选择执行哪些代码,条件编译就是预处理器根据条件选择哪些代码参与下一步的编译。

负责条件编译的预处理指令有:

#if #ifdef #ifndef #elif #else #endif

头文件卫士:

这种固定写法,在头文件中使用,它能防止头文件被重复包含,所有的头文件都要遵循这个规则。

#ifndef FILE_H // 判断FILE_H宏是否正在,不存在则条件为真
#define FILE_H // 定义FILE_H宏
​
// 头文件卫士能保证此处不重复出现
​
#endif//FILE_H // #ifndef的结尾

注意:头文件只能解决重复包含的问题,不能解决循环包含,互相包含的问题。

注释代码:

// 只能注释单行代码,早期的编译器不支持该用
​
/* 多行注释,但不能嵌套 */
​
#if 0|1
可注释大块代码,可以嵌套    
#endif 

版本、环境判断:

#if __WORDSIZE == 64
    typedef long int        int64_t;
#else
    typedef long long int       int64_t;
#endif
​
​
// 判断是否是Linux操作系统:
#if __linux__
​
#endif 
​
// 判断是否是Windows操作系统:
#if __WIN32 | __WIN32__ | __WIN64__
​
#endif 
​
int main(int argc,const char* argv[])
{
#if __linux__
    system("clear");
#elif ____WIN32 | __WIN32__ | __WIN64__
    system("cls");
#endif
    printf("hello world!\n");   
}
​
​
// 判断gcc还是g++:
int main(int argc,const char* argv[])
{
#if __cplusplus
    printf("你使用是g++编译器\n");
#else
    printf("你使用是gcc编译器\n");
#endif
}
​

DEBUG宏:

专门用于调试程序的宏函数,这种宏函数在程序测试、调试、试运行阶段执行,在程序正式上线阶段不执行,这类函数会根据DEBUG宏是否定义确定执行的流程。

#include <stdio.h>                                                             
#include <stdlib.h>
​
#ifdef DEBUG
void* _my_malloc(const char* file,const char* func,int line,size_t size)
{
    void* ptr = malloc(size);
    printf("%s %s %d malloc %p\n",__FILE__,__func__,__LINE__,ptr);
    return ptr;
}
​
#define my_malloc(size) _my_malloc(__FILE__,__func__,__LINE__,size)
​
#define my_free(ptr) do{ \
    printf("%s %s %d free %p\n",__FILE__,__func__,__LINE__,ptr); \
    free(ptr); \
}while(0)
​
#else
​
#define my_malloc malloc
#define my_free free
​
#endif
​
int main(int argc,const char* argv[])
{
    int* ptr = my_malloc(40);
    *ptr = 123456;
    printf("%d\n",*ptr);
    my_free(ptr);
}

不常用的预处理指令:

#line <常整数> 设置当前代码的行号,目前没有发现它有什么用

#error "在预处理阶段提示错误信息",一旦预处理遇到它,将不再继续编译,它不能单独使用必须与条件判断系列语句配合使用 

#warning "在预处理阶段提示警告信息" 不能建议单独使用,最好与条件判断系列语句配合使用。

#pragma GCC poison <标识符> 把标识符设置病毒,禁止在代码中使用

#pragma pack(n)  设置最大对齐和补齐字节数
  每个系统在进行对齐和补齐都有一个最大对齐和补齐字节数n,也就是超出n字节按n字节计算,例如:linux32系统n=4,windows32 n=8
设置要求:
	1、n < 系统默认的最大对齐、补齐字节数,往大了调整没有意义,速度不会提升还会导致内存浪费。
	2、n必须是2的x次方,也就是必须是1、2、4、8、16这一类的整数

宏函数的变长参数:

#define func(...) __VA_ARGS__

注意:这种用法必须配合,printf/fprintf/sprintf系列支持变长参数的函数使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值