C语言预处理及宏和函数的区别与各自优劣点的详解(下)

简介: C语言预处理及宏和函数的区别与各自优劣点的详解(下)

4:使用#和##

4.1:#

#:作用:把参数插入到字符串中

使用方法:

int main()
{
  int a = 20;
  printf("the value of a is %d\n",a);
  int b = 15;
  printf("the value of b is %d\n",b);
  float f = 4.5f;
  printf("the value of f is %f\n",f);
  return 0;
}
我们有这么一个需求,可不可以定义一个函数print完成a,b,f三者的打印任务呢?
答案是:不可以,
因为函数的参数必须声明为特定的类型。
所以函数只能在类型合适的表达式上使用。
也就是说:
函数的参数是与类型有关的,如果参数的类型不同,就需要不同的函数,即使他们执行的任务是相同的。

不过宏可以完成这个任务

我们先来介绍关于字符串的一个很容易被忽视的细节

int main()
{
  char* p = "hello ""world\n";
  printf("hello ""world\n");
  printf("%s", p);
}

从中我们发现字符串是有自动连接的特点的。

printf("the value of a is %d\n",a);
  printf("the value of b is %d\n",b);
  printf("the value of f is %f\n",f);
  这三个printf函数中这三者的不同点就是
  1:字符串中的a,b,f字符
  2:占位符%d,%d,%f不同
  又因为:字符串是有自动连接的特点的
  所以我们想:
  printf("the value of" "a" "is" "%d" "\n",a);
  printf("the value of" "b" "is" "%d" "\n",b);
  printf("the value of" "f" "is" "%f" "\n",f);
  能不能将这两个不同点当成参数传入呢?

所以我们这样去写

从下面的代码中我们就能够看出#的作用来

也就是:把参数插入到字符串中

#define PRINT(n,format) printf("the value of "#n" is " format "\n",n)
// 当n为a时:  #n:"a"
printf("the value of ""a"" is " "%d" "\n",a);
// 当n为b时:  #n:"b"
printf("the value of ""b"" is " "%d" "\n",b);
// 当n为f时:  #n:"f"
printf("the value of ""f"" is " "%f" "\n",f);
int main()
{
  int a = 20;
  PRINT(a,"%d");
  int b = 15;
  PRINT(b,"%d");
  float f = 4.5f;
  PRINT(f, "%f");
  return 0;
}

不过,这里只有当字符串作为宏参数的时候才可以把字符串放在字符串中

4.2:##

##可以把位于它两边的符号合成一个符号。
它允许宏定义从分离的文本片段创建标识符。
注:
这样的连接必须产生一个合法的标识符。否则其结果就是未定义的。

例子:

#define PT(x,y) x##y
int main()
{
  int Hello0716 = 2024;
  printf("%d\n", PT(Hello,0716));
  return 0;
}

5.带副作用的宏参数

当宏参数在宏的定义中出现超过一次的时候,
如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果。
副作用就是表达式求值的时候出现的永久性效果。
例如:
int a = 10;
int b = ++a;//给b赋值时改变了a,也就是产生了副作用
int b = a + 1;//无副作用
x + 1;//不带副作用
x++;//带有副作用

例子:

#define MAX(a,b) ((a)>(b)?(a):(b))
int c = ((a++) > (b++) ? (a++) : (b++));
问:a,b,c分别等于什么?
int main()
{
  int a = 5;
  int b = 6;
  //int c = MAX(a++,b++);
  int c = ((a++) > (b++) ? (a++) : (b++));
  //         5       6               7
  //这个最后位置的b++这个整体表达式的值是7,这个7赋值给c
  //  7      6                       8
  printf("c=%d\n", c);//7  注意c不是有歧义
  printf("a=%d\n", a);//6
  printf("b=%d\n", b);//8
  return 0;
}

6:宏和函数对比

宏通常被应用于执行简单的运算。
比如在两个数中找出较大的一个。
#define MAX(a,b) ((a)>(b)?(a):(b))
那为什么不用函数来完成这个任务?
原因有二:
1. 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。
所以宏比函数在程序的规模和速度方面更胜一筹。
2. 更为重要的是函数的参数必须声明为特定的类型。
所以函数只能在类型合适的表达式上使用。反之这个宏却可以适用于整形、长整型、浮点型等可以
用于 > 来比较的类型。
宏是类型无关的。
宏的缺点:当然和函数相比宏也有劣势的地方:
1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序
的长度。
2. 宏是没法调试的。
3. 宏由于类型无关,没有类型检查,也就不够严谨。
4. 宏可能会带来运算符优先级的问题,导致程序容易出现错。
宏有些能力是函数绝对没有的
宏有时候可以做函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到。
#define MALLOC(num,type) (type*)malloc(num*sizeof(type))
int main094()
{
  //int* p = (int*)malloc(126 * sizeof(int));
  int* p = MALLOC(126, int);//传参方便
  return 0;
}

7:#undef及命名约定

7.1:#undef

#undef
这条指令用于移除一个宏定义
#undef NAME
如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除。

7.2:命名约定

一般来讲函数的宏的使用语法很相似。

所以语言本身没法帮我们区分二者。

那我们平时的一个习惯是:

把宏名全部大写

函数名不要全部大写

二:条件编译

在编译一个程序的时候

我们如果要将一条语句(一组语句)编译或者放弃是很方便的。

因为我们有条件编译指令。 比如说:

调试性的代码,删除可惜,保留又碍事,所以我们可以选择性地编译。

条件编译在预编译时执行

1:单分支与多分支的条件编译

1.1 单分支

#define M 1
int main()
{
#if M
  printf("hello");
#endif
  return 0;
}
//等同于注释:
#if 0
int main()
{
  return 0;
}
#endif

1.2 多分支

#define I 2
int main()
{
  #if (I==1)
    printf("(1)执行");
  #elif (I==2)
    printf("(2)执行");
  #else
    printf("(3)执行");
  #endif
  return 0;
}

注意:

1.#if后面必须有#endif

2.#endif前面必须有#if

2:判断是否被定义

判断是否被定义(只关心是否被定义过,而不关心具体的真/假)
 一定不要忘记#endif
被定义过则执行
#if defined(symbol)   #endif
#ifdef symbol        #endif
 未被定义过才执行
#if !defined(symbol)     #endif
#ifndef symbol         #endif

3:嵌套指令

#define FIRST 0
#define SECOND 0
#define OPTION1 1
#define OPTION2 2
#define OPTION3 3
void do_option1()
{
  printf("do_option1\n");
}
void do_option2()
{
  printf("do_option2\n");
}
void do_option3()
{
  printf("do_option3\n");
}
int main()
{
  #if defined(FIRST)
    #ifdef OPTION1
      do_option1();
    #endif
    #ifdef OPTION2
      do_option2();
    #endif
  #elif defined(SECOND)
    #ifdef OPTION3
      do_option3();
    #endif
  #endif
}

三:文件包含

1:#include<>与""

本地文件包含
#include "filename"
查找策略:先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件。
如果找不到就提示编译错误。
库文件包含
#include <filename>
查找头文件直接去标准路径下去查找,如果找不到就提示编译错误。
这样是不是可以说,对于库文件也可以使用 “” 的形式包含?
答案是肯定的,可以。
但是这样做查找的效率就低些,当然这样也不容易区分是库文件还是本地文件了。

2:嵌套文件包含

在头文件中写入这两种代码中的任意一种都可以防止头文件被重复引用

//防止头文件被重复引用
#ifndef __TEST_H__
#define __TEST_H__
//头文件的内容
#endif
//或者
#pragma once
头文件中的 ifndef / define / endif的用处:
防止头文件被重复引用
#include <filename.h> 和 #include "filename.h"的区别:
<>:只在库的标准目录下进行查找
"":先在当前目录下进行查找,如果查找不到,就去库的标准目录下进行查找

四:预定义符号

这些预定义符号都是C语言内置的。

__FILE__      //进行编译的源文件
__LINE__     //文件当前的行号
__DATE__    //文件被编译的日期
__TIME__    //文件被编译的时间
__STDC__    //如果编译器遵循ANSI C,其值为1,否则未定义
int main()
{
  printf("%s\n", __FILE__);
  printf("%d\n", __LINE__);
  printf("%s\n", __DATE__);
  printf("%s\n", __TIME__);
  //printf("%d\n",__STDC__);//当前VS不遵守ANSI_C(标准C)
  //C:\Users\86157\Desktop\gitee总\c - code\C_preprocessing_compilation\C_preprocessing_compilation\test.c
  //90
  //Jul 18 2023
  //13:55 : 48
  return 0;
}

以上就是C语言预处理及宏和函数的区别与各自优劣点的详解

的全部内容,希望能对大家有所帮助


相关文章
|
1月前
|
存储 C语言
【C语言程序设计——函数】递归求斐波那契数列的前n项(头歌实践教学平台习题)【合集】
本关任务是编写递归函数求斐波那契数列的前n项。主要内容包括: 1. **递归的概念**:递归是一种函数直接或间接调用自身的编程技巧,通过“俄罗斯套娃”的方式解决问题。 2. **边界条件的确定**:边界条件是递归停止的条件,确保递归不会无限进行。例如,计算阶乘时,当n为0或1时返回1。 3. **循环控制与跳转语句**:介绍`for`、`while`循环及`break`、`continue`语句的使用方法。 编程要求是在右侧编辑器Begin--End之间补充代码,测试输入分别为3和5,预期输出为斐波那契数列的前几项。通关代码已给出,需确保正确实现递归逻辑并处理好边界条件,以避免栈溢出或结果
66 16
|
1月前
|
存储 编译器 C语言
【C语言程序设计——函数】分数数列求和2(头歌实践教学平台习题)【合集】
函数首部:按照 C 语言语法,函数的定义首部表明这是一个自定义函数,函数名为fun,它接收一个整型参数n,用于指定要求阶乘的那个数,并且函数的返回值类型为float(在实际中如果阶乘结果数值较大,用float可能会有精度损失,也可以考虑使用double等更合适的数据类型,这里以float为例)。例如:// 函数体代码将放在这里函数体内部变量定义:在函数体中,首先需要定义一些变量来辅助完成阶乘的计算。比如需要定义一个变量(通常为float或double类型,这里假设用float。
37 3
|
1月前
|
存储 算法 安全
【C语言程序设计——函数】分数数列求和1(头歌实践教学平台习题)【合集】
if 语句是最基础的形式,当条件为真时执行其内部的语句块;switch 语句则适用于针对一个表达式的多个固定值进行判断,根据表达式的值与各个 case 后的常量值匹配情况,执行相应 case 分支下的语句,直到遇到 break 语句跳出 switch 结构,若没有匹配值则执行 default 分支(可选)。例如,在判断一个数是否大于 10 的场景中,条件表达式为 “num> 10”,这里的 “num” 是程序中的变量,通过比较其值与 10 的大小关系来确定条件的真假。常量的值必须是唯一的,且在同一个。
20 2
|
1月前
|
存储 编译器 C语言
【C语言程序设计——函数】回文数判定(头歌实践教学平台习题)【合集】
算术运算于 C 语言仿若精密 “齿轮组”,驱动着数值处理流程。编写函数求区间[100,500]中所有的回文数,要求每行打印10个数。根据提示在右侧编辑器Begin--End之间的区域内补充必要的代码。如果操作数是浮点数,在 C 语言中是不允许直接进行。的结果是 -1,因为 -7 除以 3 商为 -2,余数为 -1;注意:每一个数据输出格式为 printf("%4d", i);的结果是 1,因为 7 除以 -3 商为 -2,余数为 1。取余运算要求两个操作数必须是整数类型,包括。开始你的任务吧,祝你成功!
53 1
|
C语言 网络协议
C语言及程序设计进阶例程-8 预处理之宏定义
贺老师教学链接  C语言及程序设计进阶 本课讲解 宏定义 #include &lt;stdio.h&gt; #define PI 3.1415926 int main ( ) { float r,l,s,sq,vq; printf("please enter r:"); scanf("%f", &amp;r); l=2 * PI *r; s=r * r * PI;
1017 0
|
C语言
C语言预处理之二-----宏定义那点事儿
1、关于宏的副作用,请看下面代码:   #include stdio.h> #define GOODDEF (input+3) #define POORDEF input+3   //这里是宏的副作用最经典的例子,不穿裤子!!!如果你这样用,下面你就知错!! ...
1011 0
|
1月前
|
存储 算法 C语言
【C语言程序设计——函数】素数判定(头歌实践教学平台习题)【合集】
本内容介绍了编写一个判断素数的子函数的任务,涵盖循环控制与跳转语句、算术运算符(%)、以及素数的概念。任务要求在主函数中输入整数并输出是否为素数的信息。相关知识包括 `for` 和 `while` 循环、`break` 和 `continue` 语句、取余运算符 `%` 的使用及素数定义、分布规律和应用场景。编程要求根据提示补充代码,测试说明提供了输入输出示例,最后给出通关代码和测试结果。 任务核心:编写判断素数的子函数并在主函数中调用,涉及循环结构和条件判断。
63 23
|
1月前
|
算法 C语言
【C语言程序设计——函数】利用函数求解最大公约数和最小公倍数(头歌实践教学平台习题)【合集】
本文档介绍了如何编写两个子函数,分别求任意两个整数的最大公约数和最小公倍数。内容涵盖循环控制与跳转语句的使用、最大公约数的求法(包括辗转相除法和更相减损术),以及基于最大公约数求最小公倍数的方法。通过示例代码和测试说明,帮助读者理解和实现相关算法。最终提供了完整的通关代码及测试结果,确保编程任务的成功完成。
68 15
|
1月前
|
C语言
【C语言程序设计——函数】亲密数判定(头歌实践教学平台习题)【合集】
本文介绍了通过编程实现打印3000以内的全部亲密数的任务。主要内容包括: 1. **任务描述**:实现函数打印3000以内的全部亲密数。 2. **相关知识**: - 循环控制和跳转语句(for、while循环,break、continue语句)的使用。 - 亲密数的概念及历史背景。 - 判断亲密数的方法:计算数A的因子和存于B,再计算B的因子和存于sum,最后比较sum与A是否相等。 3. **编程要求**:根据提示在指定区域内补充代码。 4. **测试说明**:平台对代码进行测试,预期输出如220和284是一组亲密数。 5. **通关代码**:提供了完整的C语言代码实现
61 24
|
2月前
|
存储 C语言 开发者
【C语言】字符串操作函数详解
这些字符串操作函数在C语言中提供了强大的功能,帮助开发者有效地处理字符串数据。通过对每个函数的详细讲解、示例代码和表格说明,可以更好地理解如何使用这些函数进行各种字符串操作。如果在实际编程中遇到特定的字符串处理需求,可以参考这些函数和示例,灵活运用。
94 10

热门文章

最新文章