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


目录
相关文章
|
存储 编译器 程序员
c语言的文件操作与文件缓冲区
如果没有文件,我们写的程序的数据是存储在电脑的内存中,如果程序退出,内存回收,数据就丢失了,等再次运行程序,是看不到上次程序的数据的,如果要将数据进行持久化的保存,我们可以使用文件。磁盘(硬盘)上的文件是文件。但是在程序设计中,我们⼀般谈的⽂件有两种:程序文件、数据文件(从文件功能的角度来分类 的)。就比如说我们电脑中以.txt为后缀的就是文件的一种,他就是数据文件。.exe为后缀的就为程序文件。函数名功能适用范围fgetc字符输入函数所有输入流fputc字符输出函数所有输出流fgets。
574 0
|
人工智能 C语言
|
存储 小程序 C语言
【C语言程序设计——文件】文件操作(头歌实践教学平台习题)【合集】
本文介绍了C语言中的文件操作,分为两个关卡。第1关任务是将键盘输入的字符(以#结束)存入`file1.txt`并显示输出;第2关任务是从键盘输入若干行文本(每行不超过80个字符,用-1作为结束标志),写入`file2.txt`后再读取并显示。文中详细讲解了文件的打开、读取(使用`fgetc()`和`fgets()`)、写入(使用`fputc()`和`fputs()`)及关闭操作,并提供了示例代码和测试说明。
560 5
|
C语言
【C语言】全局搜索变量却找不到定义?原来是因为宏!
使用条件编译和 `extern` 来管理全局变量的定义和声明是一种有效的技术,但应谨慎使用。在可能的情况下,应该优先考虑使用局部变量、函数参数和返回值、静态变量或者更高级的封装技术(如结构体和类)来减少全局变量的使用。
404 5
|
算法 C语言
C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项
本文深入讲解了C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项,通过实例演示了文件操作的基本流程,帮助读者掌握这一重要技能,提升程序开发能力。
982 3
|
存储 自然语言处理 Unix
【C语言】C语言 4 个编译过程详解
编译是将源代码转换为目标代码的过程。它是在编译器的帮助下完成的。编译器检查源代码是否存在语法或结构错误,如果源代码没有错误,则生成目标代码。
819 1
|
存储 算法 C语言
【C语言程序设计——函数】素数判定(头歌实践教学平台习题)【合集】
本内容介绍了编写一个判断素数的子函数的任务,涵盖循环控制与跳转语句、算术运算符(%)、以及素数的概念。任务要求在主函数中输入整数并输出是否为素数的信息。相关知识包括 `for` 和 `while` 循环、`break` 和 `continue` 语句、取余运算符 `%` 的使用及素数定义、分布规律和应用场景。编程要求根据提示补充代码,测试说明提供了输入输出示例,最后给出通关代码和测试结果。 任务核心:编写判断素数的子函数并在主函数中调用,涉及循环结构和条件判断。
939 23
|
10月前
|
存储 C语言
`scanf`是C语言中用于按格式读取标准输入的函数
`scanf`是C语言中用于按格式读取标准输入的函数,通过格式字符串解析输入并存入指定变量。需注意输入格式严格匹配,并建议检查返回值以确保读取成功,提升程序健壮性。
1553 0
|
安全 C语言
C语言中的字符、字符串及内存操作函数详细讲解
通过这些函数的正确使用,可以有效管理字符串和内存操作,它们是C语言编程中不可或缺的工具。
509 15
|
人工智能 Java 程序员
一文彻底搞清楚C语言的函数
本文介绍C语言函数:函数是程序模块化的工具,由函数头和函数体组成,涵盖定义、调用、参数传递及声明等内容。值传递确保实参不受影响,函数声明增强代码可读性。君志所向,一往无前!
671 1
一文彻底搞清楚C语言的函数