C语言16--宏定义和关键字

预处理

在C语言程序源码中,凡是以井号(#)开头的语句被称为预处理语句,这些语句严格意义上并不属于C语言语法的范畴,它们在编译的阶段统一由所谓预处理器(cc1)来处理。所谓预处理,顾名思义,指的是真正的C程序编译之前预先进行的一些处理步骤,这些预处理指令包括:

  1. 头文件:#include
  2. 定义宏:#define
  3. 取消宏:#undef
  4. 条件编译:#if、#ifdef、#ifndef、#else、#elif、#endif
  5. 显示错误:#error
  6. 修改当前文件名和行号:#line
  7. 向编译器传送特定指令:#progma
  • 基本语法
    • 一个逻辑行只能出现一条预处理指令,多个物理行需要用反斜杠 \ 连接成一个逻辑行
    • 预处理是整个编译全过程的第一步:预处理 - 编译 - 汇编 - 链接
    • 可以通过如下编译选项 ( -E )来指定来限定编译器只进行预处理操作:
cpp 复制代码
gcc example.c -o example.i -E

宏的概念

宏(macro)实际上就是一段特定的字串,在源码中用以替换(无脑)为指定的表达式。例如:

cpp 复制代码
#define PI 3.14

此处,PI 就是宏(宏一般习惯用大写字母表达,以区分于变量和函数,但这并不是语法规定,只是一种习惯),是一段特定的字串,这个字串在源码中出现时,将被替换为3.14。例如:

cpp 复制代码
int main()
{
    printf("圆周率: %f\n", PI); 
    // 此语句将被替换为:printf("圆周率: %f\n", 3.14);
}
  • 宏的作用:
    • 使得程序更具可读性:字串单词一般比纯数字更容易让人理解其含义。
    • 使得程序修改更易:修改宏定义,即修改了所有该宏替换的表达式。
    • 提高程序的运行效率:程序的执行不再需要函数切换开销,而是就地展开。

无值宏定义

定义宏的时候,不一定需要带值,无值的宏定义经常在条件编译中作为判断条件出现,例如:

cpp 复制代码
#define BIG_ENDIAN
#define __cplusplus

无参(参数)宏

无参宏意味着使用宏的时候,无需指定任何参数,比如:

cpp 复制代码
#define PI          3.14
#define SCREEN_SIZE 800*480*4 
int main()
{
    // 在代码中,可以随时使用以上无参宏,来替代其所代表的表达式:
    printf("圆周率: %f\n", PI); 
    mmap(NULL, SCREEN_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, ...);
}

注意到,上述代码中,除了有自定义的宏,还有系统预定义的宏:

cpp 复制代码
// 自定义宏:
#define PI          3.14
#define SCREEN_SIZE 800*480*4 

// 系统预定义宏
#define NULL ((void *)0)
#define PROT_READ    0x1    /* Page can be read.  */
#define PROT_WRITE    0x2    /* Page can be written.  */
#define MAP_SHARED    0x01    /* Share changes.  */

宏的最基本特征是进行直接文本替换,以上代码被替换之后的结果是:

cpp 复制代码
int main()
{
    printf("圆周率: %f\n", 3.14); 
    mmap(((void *)0), 800*480*4, 0x1|0x2, 0x01, ...);
}

带参宏

带参宏意味着宏定义可以携带"参数",从形式上看跟函数很像,例如:

cpp 复制代码
#define MAX(a, b)   a>b ? a : b
#define MIN(a, b)   a<b ? a : b

以上的MAX(a,b) 和 MIN(a,b) 都是带参宏,不管是否带参,宏都遵循最初的规则,即宏是一段待替换的文本,例如在以下代码中,宏在预处理阶段都将被替换掉:

cpp 复制代码
int main()
{
    int x = 100, y = 200;
    printf("最大值:%d\n", MAX(x, y));
    printf("最小值:%d\n", MIN(x, y));
    // 以上代码等价于:
    // printf("最大值:%d\n", x>y ? x : y);
    // printf("最小值:%d\n", x<y ? x : y);
}

带参宏的特点:

    1. 直接文本替换,不做任何语法判断,更不做任何中间运算。
    2. 宏在编译的第一个阶段就被替换掉,运行中不存在宏。
    3. 宏将在所有出现它的地方展开,这一方面牺牲了内存空间,另一方面有节约了切换时间。

带参宏的副作用

由于宏仅仅做文本替换,中间不涉及任何语法检查、类型匹配、数值运算,因此用起来相对函数要麻烦很多。例如:

cpp 复制代码
#define MAX(a, b) a>b ? a : b

int main()
{
    int x = 100, y = 200;
    printf("最大值:%d\n", MAX(x, y==200?888:999));
}

直观上看,无论 y 的取值是多少,表达式 y==200?888:999 的值一定比 x 要大,但由于宏定义仅仅是文本替换,中间不涉及任何运算,因此等价于:

cpp 复制代码
printf("最大值:%d\n", x>y==200?888:999 ? x : y==200?888:999);

可见,带参宏的参数不能像函数参数那样视为一个整体,整个宏定义也不能视为一个单一的数据,事实上,不管是宏参数还是宏本身,都应被视为一个字串,或者一个表达式,或者一段文本,因此最基本的原则是:

cpp 复制代码
#define MAX(a, b) ((a)>(b) ? (a) : (b))

typedef、extern和define关键字的详细总结:

static关键字

  • 作用:在C语言中,static关键字有多种用途,它可以用于函数、变量和全局变量。
  • static修饰局部变量:使之由栈内存临时数据,变成了静态数据(存储与数据段)。
  • static修饰全局变量:使之由各文件可见的静态数据,变成了本文件可见(缩小可见范围、降低同名冲突的概率)的静态数据。
  • static修饰函数:使之由各文件可见的函数,变成了本文件可见(缩小可见范围、降低同名冲突的概率)的静态函数。

const关键字

  • const型指针有两种形式:①常指针 ②常目标指针
  1. 常指针:const修饰指针本身,表示指针变量本身无法修改。
  2. 常目标指针:const修饰指针的目标,表示无法通过该指针修改其目标。
  3. const修饰变量,变量的值修饰为常量不可修改

typedef关键字

  • 作用:typedef关键字用于创建类型的别名,可以为现有的数据类型定义一个新的名字。
    • typedef int int32_t; // 将类型 int 取个别名,称为 int32_t
    • typedef long int64_t;// 将类型 long 取个别名,称为 int64_t

extern关键字

  • 作用:extern关键字用于声明一个变量或函数是在其他文件中定义的。
  • 使用方法:在一个文件中使用extern关键字声明一个变量或函数,表示该变量或函数是在其他文件中定义的。
  • 注意事项:使用extern关键字声明的变量或函数在当前文件中并不分配存储空间,只是告诉编译器该变量或函数在其他文件中定义。

结语:

在这篇博客中,我们深入探讨了 C 语言中的宏定义与关键字的运用和特点。宏定义作为预处理器指令,为代码提供了灵活性与可维护性,使得程序员能够轻松地创建复杂的表达式、常量以及代码块。同时,C 语言的关键字则是我们构建程序逻辑的基石,它们定义了语言的语法规则和结构,确保代码的正确性和可读性。

理解宏定义与关键字不仅有助于提高我们的编程技巧,还能让我们更有效地优化和简化代码。作为 C 语言程序员,掌握这些元素使我们能更加高效地应对各种编程挑战,并提升解决问题的能力。

相关推荐
CV金科6 分钟前
蓝桥杯-STM32G431RBT6(UART解析字符串sscanf和解决串口BUG)
c语言·stm32·单片机·嵌入式硬件·mcu·算法·bug
不悔哥2 小时前
openwrt wsdd模块介绍
linux·c语言·网络·tcp/ip·智能路由器
星迹日2 小时前
C语言:结构体
c语言·开发语言·经验分享·笔记
jyan_敬言2 小时前
虚拟机centos_7 配置教程(镜像源、配置centos、静态ip地址、Finalshell远程操控使用)
linux·运维·服务器·c语言·数据结构·tcp/ip·centos
fhvyxyci6 小时前
【数据结构初阶】顺序结构二叉树(堆)接口实现超详解
c语言·数据结构
m0_6312704011 小时前
高级c语言(三)
c语言·算法
铁松溜达py11 小时前
C 语言中表示对象大小的标准数据类型size_t
c语言·开发语言·算法
黒井深11 小时前
Linux从入门到开发实战(C/C++)Day13-线程池
linux·c语言·c++
2401_8582861112 小时前
51.【C语言】字符函数和字符串函数(strcpy函数)
c语言·开发语言·汇编
半路程序员13 小时前
c语言二分法解算复杂函数值
c语言·开发语言·算法