C语言进阶⑳(程序环境和预处理)(#define定义宏+编译+文件包含)(中)

简介: C语言进阶⑳(程序环境和预处理)(#define定义宏+编译+文件包含)

2.2.3 # 和 ##

我们知道,宏是把参数替换到文本中。那么如何把参数插入到字符串中呢?

比如这种情况,如果只规定传一个参数,使用函数是根本做不到的:

函数传两个参数:

 
#include <stdio.h>
void print(char x,int y) 
{
    printf("变量%c的值是%d\n", x,y);
}
int main() 
{
    int a = 10;
    // 打印内容:变量a的值是10
    print('a',a);
 
    int b = 20;
    // 打印内容:变量b的值是20
    print('b',b);
 
    int c = 30;
    // 打印内容:变量c的值是30
    print('c',c);
 
    return 0;
}

这种情况,就可以用 传一个参数来实现。

介绍:# 把一个宏参数变成对应的字符串。

使用 # 解决上面的问题:

 
#include <stdio.h>
#define PRINT(X) printf("变量"#X"的值是%d\n", X);
// #X 就会变成 X内容所定义的字符串
 
int main() 
{
    // 打印内容:变量a的值是10
    int a = 10;
    PRINT(a); // printf("变量""a""的值是%d\n", a);
 
    // 打印内容:变量b的值是20
    int b = 20;
    PRINT(b); // printf("变量""b"的值是%d\n", b);
 
    // 打印内容:变量c的值是30
    int c = 30;
    PRINT(c); // printf("变量""c""的值是%d\n", c);
 
    return 0;
}

改进:让程序不仅仅支持打印整数,还可以打印其他类型的数(比如浮点数):

 
#include <stdio.h>
#define PRINT(X, FORMAT) printf("变量"#X"的值是 "FORMAT"\n", X);//format格式(格式化参数)
int main() 
{
    // 打印内容:变量a的值是10
    int a = 10;
    PRINT(a, "%d");
 
    // 打印内容:变量f的值是5.5
    float f = 5.5f;
    PRINT(f, "%.1f"); //替换成printf("变量""f""的值是 ""%.1f""\n", f);
 
    return 0;
}

这操作是不是很奇葩?还有更奇葩的呢:

介绍:## 可以把位于它两边的符号融合成一个符号。它允许宏定义从分离的文本片段创建标识符。

使用 ## 将两边的符号缝合成一个符号:

 
#include <stdio.h>
#define CAT(X,Y) X##Y    //发现连3个也行
int main()
{
    int vs2022 = 100;  //下面会打印出100
    printf("%d\n", CAT(vs, 2022)); //替换成printf("%d\n", vs2022); 
    return 0;
}

2.2.4带 "副作用" 的宏参数

什么是副作用?副作用就是表达式求值的时候出现的永久性效果,例如:

 
// 不带有副作用
x + 1;
// 带有副作用
x++;  
 
int a = 1;
// 不带有副作用
int b = a + 1; // b=2, a=1
 
// 带有副作用
int b = ++a; // b=2, a=2

当宏参数在宏的定义中出现超过一次的情况下,如果参数带有副作用(后遗症),

那么在使用这个宏的时候就可能出现危险,导致不可预料的后果。

这种带有副作用的宏参数如果传到宏体内,这种副作用一直会延续到宏体内。

举个例子:

 
#include <stdio.h>
#define MAX(X,Y) ((X)>(Y)?(X):(Y))
int main(void){
    int a = 5;
    int b = 8;
    int m = MAX(a++, b++);
 
    printf("m = %d\n", m);//9
    printf("a=%d, b=%d\n", a, b);//6  10
    return 0;
}

所以写宏的时候要尽量避免使用这种带副作用的参数。


2.2.5 宏和函数对比

举个例子:在两数中找较大值

① 用宏:

 
#include <stdio.h>
#define MAX(X,Y) ((X)>(Y)?(X):(Y))
int main() 
{
    int a = 10;
    int b = 20;
    int m = MAX(a, b); // int m = ((a)>(b) ? (a):(b))
    printf("%d\n", m);
    return 0;
}

② 用函数:

 
#include <stdio.h>
int Max(int x, int y)
{
    return x > y ? x : y;
}
int main() 
{
    int a = 10;
    int b = 20;
    int m = Max(a, b);
    printf("%d\n", m);
    return 0;
}

那么问题来了,宏和函数那种更好呢?

这题用宏更好,宏的优势:

用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多,

所以宏比函数在程序的规模和速度方面更胜一筹。 更为重要的是函数的参数必须声明为特定的类型。

所以函数只能在类型合适的表达式上使用。反之,宏可以适用于整型、长整型、浮点型等可以用于

比较的类型。因为宏是类型无关的。

当然,宏也有劣势的地方:

① 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,

否则可能大幅度增加程序的长度。

② 宏不能调试。

③ 宏由于类型无关,因为没有类型检查,所以不够严谨。

④ 宏可能会带来运算符优先级的问题,导致程容易出现错。

宏有时候可以做函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到:

 
#include <stdio.h>
#include <stdlib.h>
#define MALLOC(num, type) (type*)malloc(num*sizeof(type))
int main() 
{
    // 原本的写法:malloc(10*sizeof(int));
    // 但我想这么写:malloc(10, int);
    int* p = MALLOC(10, int); // (int*)malloc(10*sizeof(int))  
    return 0;    
}

宏和函数的对比:

内联函数inline(C99)结合了宏和函数两者的优点(后面C++专栏会讲)


2.2.6宏和函数命名约定

命名约定,一般来讲函数的宏的使用语法很相似,所以语言本身没法区分二者。


约定俗成的一个习惯是: 宏名全部大写,函数名不要全部大写。


不过这也不是绝对的,比如我有时候就是想把一个宏伪装成函数来使用,那么我就全小写给宏取名。并不强制,但是这个约定是每个C/C++程序员大家的一种 "约定俗成" 。

2.3 #undef移除宏定义

#undef NAME 用于移除一个宏定义。(也不用在后面加分号)

代码演示:用完 M 之后移除该定义

 
#include <stdio.h>
#define M 100
int main(void) 
{
    int a = M;
    printf("%d\n", M);
#undef M// 移除宏定义
    return 0;
}

2.4命令行编译

什么是命令行编译?

在编译的时候通过命令行的方式对其进行相关的定义,叫做命令行编译。

介绍:许多C的编译器提供的一种能力,允许在命令行中定义符号。用于启动编译过程。

当我们根据同一个源文件要编译出不同的一个程序的不同版本的时,可以用到这种特性,增加灵活性。

例子:假如某个程序中声明了一个某个长度的数组,假如机器甲内存有限,我们需要一个很小的数据,但是机器丙的内存较大,我们需要一个大点的数组。

 
#include <stdio.h>
int main() 
{
    int arr[ARR_SIZE];
    int i = 0;
    for (i = 0; i < ARR_SIZE; i++) 
    {
        arr[i] = i;
    }
    for (i = 0; i < ARR_SIZE; i++) 
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
    return 0;
}

gcc 环境下测试:(VS 里面不太好演示)

gcc test.c -D ARR_SIZE=5

ls

a.out test.c

./a.out

0 1 2 3 4 5

gcc test.c -D ARR_SIZE=20

./a.out

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

2.5条件编译

条件编译介绍

在编译一个程序时,通过条件编译指令将一条语句(一组语句)编译或者放弃是很方便的。

调试用的代码删除了可惜,保留了又碍事。我们就可以使用条件编译来选择性地编译

 
#include <stdio.h>
#define __DEBUG__ // 就像一个开关一样
int main()
{
    int arr[10] = { 0 };
    int i = 0;
    for (i = 0; i < 10; i++) 
    {
        arr[i] = i;
#ifdef __DEBUG__ // 因为__DEBUG__被定义了,所以为真
        printf("%d ", arr[i]); // 就打印数组    
#endif // 包尾
    }
    return 0;
}

如果不想用了,就把 #define __DEBUG__ 注释掉:代码运行后就不打印数组了。

C语言进阶⑳(程序环境和预处理)(#define定义宏+编译+文件包含)(下):https://developer.aliyun.com/article/1513284


目录
相关文章
|
4月前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
115 5
|
4月前
|
C语言
C语言编程中,错误处理至关重要,能提升程序的健壮性和可靠性
C语言编程中,错误处理至关重要,能提升程序的健壮性和可靠性。本文探讨了C语言中的错误类型(如语法错误、运行时错误)、基本处理方法(如返回值、全局变量、自定义异常处理)、常见策略(如检查返回值、设置标志位、记录错误信息)及错误处理函数(如perror、strerror)。强调了不忽略错误、保持处理一致性及避免过度处理的重要性,并通过文件操作和网络编程实例展示了错误处理的应用。
122 4
|
4月前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
110 1
|
4月前
|
网络协议 物联网 数据处理
C语言在网络通信程序实现中的应用,介绍了网络通信的基本概念、C语言的特点及其在网络通信中的优势
本文探讨了C语言在网络通信程序实现中的应用,介绍了网络通信的基本概念、C语言的特点及其在网络通信中的优势。文章详细讲解了使用C语言实现网络通信程序的基本步骤,包括TCP和UDP通信程序的实现,并讨论了关键技术、优化方法及未来发展趋势,旨在帮助读者掌握C语言在网络通信中的应用技巧。
102 2
|
4月前
|
程序员 C语言
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门。本文深入探讨了指针的基本概念、声明方式、动态内存分配、函数参数传递、指针运算及与数组和函数的关系,强调了正确使用指针的重要性,并鼓励读者通过实践掌握这一关键技能。
88 1
|
4月前
|
C语言
【c语言】你绝对没见过的预处理技巧
本文介绍了C语言中预处理(预编译)的相关知识和指令,包括预定义符号、`#define`定义常量和宏、宏与函数的对比、`#`和`##`操作符、`#undef`撤销宏定义、条件编译以及头文件的包含方式。通过具体示例详细解释了各指令的使用方法和注意事项,帮助读者更好地理解和应用预处理技术。
73 2
|
2月前
|
存储 算法 C语言
【C语言程序设计——函数】素数判定(头歌实践教学平台习题)【合集】
本内容介绍了编写一个判断素数的子函数的任务,涵盖循环控制与跳转语句、算术运算符(%)、以及素数的概念。任务要求在主函数中输入整数并输出是否为素数的信息。相关知识包括 `for` 和 `while` 循环、`break` 和 `continue` 语句、取余运算符 `%` 的使用及素数定义、分布规律和应用场景。编程要求根据提示补充代码,测试说明提供了输入输出示例,最后给出通关代码和测试结果。 任务核心:编写判断素数的子函数并在主函数中调用,涉及循环结构和条件判断。
93 23
|
22天前
|
人工智能 Java 程序员
一文彻底搞清楚C语言的函数
本文介绍C语言函数:函数是程序模块化的工具,由函数头和函数体组成,涵盖定义、调用、参数传递及声明等内容。值传递确保实参不受影响,函数声明增强代码可读性。君志所向,一往无前!
20 1
一文彻底搞清楚C语言的函数
|
2月前
|
算法 C语言
【C语言程序设计——函数】利用函数求解最大公约数和最小公倍数(头歌实践教学平台习题)【合集】
本文档介绍了如何编写两个子函数,分别求任意两个整数的最大公约数和最小公倍数。内容涵盖循环控制与跳转语句的使用、最大公约数的求法(包括辗转相除法和更相减损术),以及基于最大公约数求最小公倍数的方法。通过示例代码和测试说明,帮助读者理解和实现相关算法。最终提供了完整的通关代码及测试结果,确保编程任务的成功完成。
110 15