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语言预处理及宏和函数的区别与各自优劣点的详解

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


相关文章
|
3天前
|
程序员 C语言 开发者
pymalloc 和系统的 malloc 有什么区别
pymalloc 和系统的 malloc 有什么区别
|
6天前
|
C语言
【c语言】你绝对没见过的预处理技巧
本文介绍了C语言中预处理(预编译)的相关知识和指令,包括预定义符号、`#define`定义常量和宏、宏与函数的对比、`#`和`##`操作符、`#undef`撤销宏定义、条件编译以及头文件的包含方式。通过具体示例详细解释了各指令的使用方法和注意事项,帮助读者更好地理解和应用预处理技术。
14 2
|
8天前
|
存储 算法 程序员
C语言:库函数
C语言的库函数是预定义的函数,用于执行常见的编程任务,如输入输出、字符串处理、数学运算等。使用库函数可以简化编程工作,提高开发效率。C标准库提供了丰富的函数,满足各种需求。
|
13天前
|
机器学习/深度学习 C语言
【c语言】一篇文章搞懂函数递归
本文详细介绍了函数递归的概念、思想及其限制条件,并通过求阶乘、打印整数每一位和求斐波那契数等实例,展示了递归的应用。递归的核心在于将大问题分解为小问题,但需注意递归可能导致效率低下和栈溢出的问题。文章最后总结了递归的优缺点,提醒读者在实际编程中合理使用递归。
37 7
|
11天前
|
存储 C语言
【c语言】字符串函数和内存函数
本文介绍了C语言中常用的字符串函数和内存函数,包括`strlen`、`strcpy`、`strcat`、`strcmp`、`strstr`、`strncpy`、`strncat`、`strncmp`、`strtok`、`memcpy`、`memmove`和`memset`等函数的使用方法及模拟实现。文章详细讲解了每个函数的功能、参数、返回值,并提供了具体的代码示例,帮助读者更好地理解和掌握这些函数的应用。
13 0
|
11天前
|
C语言
【c语言】qsort函数及泛型冒泡排序的模拟实现
本文介绍了C语言中的`qsort`函数及其背后的回调函数概念。`qsort`函数用于对任意类型的数据进行排序,其核心在于通过函数指针调用用户自定义的比较函数。文章还详细讲解了如何实现一个泛型冒泡排序,包括比较函数、交换函数和排序函数的编写,并展示了完整的代码示例。最后,通过实际运行验证了排序的正确性,展示了泛型编程的优势。
11 0
|
24天前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
31 3
|
14天前
|
存储 缓存 C语言
【c语言】简单的算术操作符、输入输出函数
本文介绍了C语言中的算术操作符、赋值操作符、单目操作符以及输入输出函数 `printf` 和 `scanf` 的基本用法。算术操作符包括加、减、乘、除和求余,其中除法和求余运算有特殊规则。赋值操作符用于给变量赋值,并支持复合赋值。单目操作符包括自增自减、正负号和强制类型转换。输入输出函数 `printf` 和 `scanf` 用于格式化输入和输出,支持多种占位符和格式控制。通过示例代码详细解释了这些操作符和函数的使用方法。
30 10
|
13天前
|
存储 编译器 程序员
【c语言】函数
本文介绍了C语言中函数的基本概念,包括库函数和自定义函数的定义、使用及示例。库函数如`printf`和`scanf`,通过包含相应的头文件即可使用。自定义函数需指定返回类型、函数名、形式参数等。文中还探讨了函数的调用、形参与实参的区别、return语句的用法、函数嵌套调用、链式访问以及static关键字对变量和函数的影响,强调了static如何改变变量的生命周期和作用域,以及函数的可见性。
25 4