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


目录
相关文章
|
1月前
|
存储 自然语言处理 编译器
【C语言】编译与链接:深入理解程序构建过程
【C语言】编译与链接:深入理解程序构建过程
|
1月前
|
存储 文件存储 C语言
深入C语言:文件操作实现局外影响程序
深入C语言:文件操作实现局外影响程序
|
2月前
|
存储 编译器 程序员
C语言程序的基本结构
C语言程序的基本结构包括:1)预处理指令,如 `#include` 和 `#define`;2)主函数 `main()`,程序从这里开始执行;3)函数声明与定义,执行特定任务的代码块;4)变量声明与初始化,用于存储数据;5)语句和表达式,构成程序基本执行单位;6)注释,解释代码功能。示例代码展示了这些组成部分的应用。
95 10
|
1月前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
34 3
|
C语言
C语言及程序设计初步例程-4 C语言程序初体验
贺老师教学链接  C语言及程序设计初步 本课讲解 让程序会计算:求a和b两个数之和 #include &lt;stdio.h&gt; int main( ) { int a,b,sum; scanf("%d %d", &amp;a, &amp;b); sum=a+b; printf("%d\n", sum); return 0; } 用户界面友好(或罗
1087 0
|
C语言 数据处理
《C语言及程序设计》实践项目——C语言程序初体验
返回:贺老师课程教学链接  C语言及程序设计初步   【项目1-输出点阵图】编一个程序,用你的姓名读音首字母,组成类似的趣图提示:printf("……\n");语句会输出双引号中的内容,'\n'完成换行[参考解答]【项目2-完成简单计算】(1)编程序,输入长方形的两边长a和b,输出长方形的周长和面积 提示:边长可以是整数也可以是小数;实现乘法的运算符是*[参考解答] (2)编程序,输入两个电
1267 0
|
7天前
|
C语言
c语言调用的函数的声明
被调用的函数的声明: 一个函数调用另一个函数需具备的条件: 首先被调用的函数必须是已经存在的函数,即头文件中存在或已经定义过; 如果使用库函数,一般应该在本文件开头用#include命令将调用有关库函数时在所需要用到的信息“包含”到本文件中。.h文件是头文件所用的后缀。 如果使用用户自己定义的函数,而且该函数与使用它的函数在同一个文件中,一般还应该在主调函数中对被调用的函数做声明。 如果被调用的函数定义出现在主调函数之前可以不必声明。 如果已在所有函数定义之前,在函数的外部已做了函数声明,则在各个主调函数中不必多所调用的函数在做声明
22 6
|
27天前
|
存储 缓存 C语言
【c语言】简单的算术操作符、输入输出函数
本文介绍了C语言中的算术操作符、赋值操作符、单目操作符以及输入输出函数 `printf` 和 `scanf` 的基本用法。算术操作符包括加、减、乘、除和求余,其中除法和求余运算有特殊规则。赋值操作符用于给变量赋值,并支持复合赋值。单目操作符包括自增自减、正负号和强制类型转换。输入输出函数 `printf` 和 `scanf` 用于格式化输入和输出,支持多种占位符和格式控制。通过示例代码详细解释了这些操作符和函数的使用方法。
34 10
|
20天前
|
存储 算法 程序员
C语言:库函数
C语言的库函数是预定义的函数,用于执行常见的编程任务,如输入输出、字符串处理、数学运算等。使用库函数可以简化编程工作,提高开发效率。C标准库提供了丰富的函数,满足各种需求。
|
25天前
|
机器学习/深度学习 C语言
【c语言】一篇文章搞懂函数递归
本文详细介绍了函数递归的概念、思想及其限制条件,并通过求阶乘、打印整数每一位和求斐波那契数等实例,展示了递归的应用。递归的核心在于将大问题分解为小问题,但需注意递归可能导致效率低下和栈溢出的问题。文章最后总结了递归的优缺点,提醒读者在实际编程中合理使用递归。
53 7