C语言进阶第十一节 --------程序环境和预处理(包含宏的解释)-2

简介: C语言进阶第十一节 --------程序环境和预处理(包含宏的解释)

C语言进阶第十一节 --------程序环境和预处理(包含宏的解释)-1

https://developer.aliyun.com/article/1499018


运行环境

程序执行的过程:

  1. 程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。
  2. 程序的执行便开始。接着便调用main函数。
  3. 开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。
  4. 终止程序。正常终止main函数;也有可能是意外终止。


预处理详解

__FILE__    //进行编译的源文件
__LINE__   //文件当前的行号
__DATE__   //文件被编译的日期
__TIME__   //文件被编译的时间
__STDC__   //如果编译器遵循ANSI C,其值为1,否则未定义


在vs2019中__STDC__ 不支持

#include<stdio.h>
#define M 100
int main()
{
  printf("%s\n", __FILE__);//查看当前的文件路径
  printf("%d\n", __LINE__);//查看这一行是第几行
  printf("%s\n", __DATE__);//查看当前日期
  printf("%s\n", __TIME__);//查看当前时间


  return 0;
}


在linux的gcc可以

#define

(1)定义常量

#define M 100

(2)定义宏


#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义

宏(define macro)。


#define name( parament-list ) stuff

其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中。

#define M(a,b) (a+b)

我们尽量在写宏的时候给替代的数值加个()

#define 替换规则

在程序中扩展#define定义符号和宏时,需要涉及几个步骤。

  1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先
    被替换。
#define a 100
#define M(a,b) (a+b)
int main()
{
  printf("%d", M(a, 2));
  return 0;
}

2.替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。

3.最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。

注意:

4.宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。

5.当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。


#和##

我们知道在字符串中有#define定义的符号是不会替换的

#

使用 # ,把一个宏参数变成对应的字符串

#define print(a, format) printf("the value of " #a " is " format "\n", a)
#include<stdio.h>
#define PRINT(a, format) printf("the value of " #a " is " format "\n", a)
int main()
{
  int  b= 10;
  PRINT(b, "%d");
  return 0;
}

#会把宏参数变成一个字符串,不会进行转换

##

##可以把位于它两边的符号合成一个符号。

它允许宏定义从分离的文本片段创建标识符。

#include<stdio.h>
//#define A 100
//#define M(a,b) (a+b)

#define ADD(num, value) int sum##num = value
#define print(num)  printf("%d ", sum##num)
int main()
{
  int i = 0;
  for (i = 0; i < 5; i++)
  {
    ADD(i, i);
    print(i);
  }
  return 0;
}

我们可以使用于创建一些文件名的地方,或者创建一些变量

带有副作用的宏参数

#include<stdio.h>
//#define a 100
//#define M(a,b) (a+b)
#define ADD(a,b) (a >= b? a + b : 1)
int main()
{
  int a = 10;
  int b = 10;
  printf("%d\n", ADD(a++, b));
  printf("a = %d", a);

  return 0;
}

可以看到我们写了一段代码里面有宏,传入的值是a++,下面预处理的代码如下

可以简单明了的看到a的值变化了两次

注意:我们传入参数要思考好,不然就会引起一些不必要的后果

宏和函数的比较

宏:

1.通常被应用于执行简单的运算

宏仅仅只有运算,

如果使用函数,不仅要为函数创建栈帧,参数的传递,还有运算, 最后函数返回,这个就会很费时间

2.宏比函数在程序运算的规模和速度更胜一筹

3.函数的参数要声明类型,而宏不是


函数:

1.宏每调用一次就会替换一次,如果宏很长,即不好写,也不好直观

2.宏不能调试,

3.宏的类型无关,也会不严谨

4.宏可能会造成一些运算顺序问题,戴上()频繁

建议:逻辑简单,使用宏,逻辑复杂使用函数


函数和宏的命名规则

把宏名全部大写

函数名不要全部大写


undef

移除一个宏定义

#include<stdio.h>
#define M 100
int main()
{
#undef M
  printf("%d", M);
  return 0;
}

M不存在了,这里就会报错.

命令行定义

在linux系统中

#include<stdio.h>
int main()
{
  int arr[sz];
  int i = 0;
  for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
  {
    printf("%d", arr[i]);
  }

  return 0;
}

条件编译

我们在写了一段代码发现,某些代码是不需要的,但是删除了可惜,因此我们在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的。因为我们有条件编译指令。

#if … #endif

#if 常量表达式
//...
#endif

相当于我们的if判断 ,没有else

#include<stdio.h>
#define M 3
int main()
{
  int a = 1;
  scanf("%d", &a);
#if M==3
  printf("%d", a);
#endif
  printf("跳过");


  return 0;
}

如果M==3就执行printf(“%d”, a);,不是就不执行,

多分支语句

#if 常量表达式
//...
#elif 常量表达式
//...
#else
//...
#endif
#include<stdio.h>
//#define a 100
//#define M(a,b) (a+b)
#define M  10
int main()
{
#if M == 1
  printf("1 ");
#elif M == 2
  printf("2 ");
#else
  printf("3 ");
#endif
  printf("运行结束");

  return 0;
}

这个语句相当于我们的if的多分支语句

判断是否被定义

#include<stdio.h>
//#define a 100
#define M 0
int main()
{
#if defined(M)
  printf("定义了");
#endif
  printf("哈哈哈");

  return 0;
}
#include<stdio.h>
//#define a 100
#define M 0
int main()
{
#ifdef M
  printf("定义了");
#endif
  printf("哈哈哈");

  return 0;
}

#if defined(M) 等同于 #ifdef M

#include<stdio.h>
//#define a 100
//#define M 0
int main()
{
#ifdef M
  printf("定义了");
#elif !defined(M)
  printf("没有定义");
#endif
  printf("哈哈哈");

  return 0;
}

#if !defined(M) 等同于 #ifndef M

defined() 函数用于检查某个标识符是否已经被 #define 定义

文件包含

我们知道#include可以引入头文件

有两种表示形式

(1)包含本地文件(自己写的文件,或者别人写的)

#include"xxx.h"

会先源文件的目录下查找头文件,如果找不到就会到标准位置找(库函数的目录)(标准库位置)

(2)包含标准库的方式

#include<xxx.h>

直接到标准库里面找,找不到就报错

Linux环境的标准头文件的路径:

/usr/include


嵌套文件包含

我们在引入头文件可能会引入多次相同的文件

为了防止重复引用

#ifndef __TEST_H__
#define __TEST_H__
//头文件的内容
#endif  //__TEST_H__

这段代码是经典的头文件保护机制,用于防止同一头文件被多次包含。当 TEST_H 没有被定义时,会定义它并包含头文件内容,否则直接跳过。

或者

#pragma once
相关文章
|
1月前
|
编译器 C语言
C语言--预处理详解(1)
【10月更文挑战第3天】
|
1月前
|
编译器 Linux C语言
C语言--预处理详解(3)
【10月更文挑战第3天】
|
18天前
|
C语言
【c语言】你绝对没见过的预处理技巧
本文介绍了C语言中预处理(预编译)的相关知识和指令,包括预定义符号、`#define`定义常量和宏、宏与函数的对比、`#`和`##`操作符、`#undef`撤销宏定义、条件编译以及头文件的包含方式。通过具体示例详细解释了各指令的使用方法和注意事项,帮助读者更好地理解和应用预处理技术。
20 2
|
1月前
|
C语言
C语言--预处理详解(2)
【10月更文挑战第3天】
|
1月前
|
存储 文件存储 C语言
深入C语言:文件操作实现局外影响程序
深入C语言:文件操作实现局外影响程序
|
1月前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
34 3
|
7天前
|
C语言
c语言调用的函数的声明
被调用的函数的声明: 一个函数调用另一个函数需具备的条件: 首先被调用的函数必须是已经存在的函数,即头文件中存在或已经定义过; 如果使用库函数,一般应该在本文件开头用#include命令将调用有关库函数时在所需要用到的信息“包含”到本文件中。.h文件是头文件所用的后缀。 如果使用用户自己定义的函数,而且该函数与使用它的函数在同一个文件中,一般还应该在主调函数中对被调用的函数做声明。 如果被调用的函数定义出现在主调函数之前可以不必声明。 如果已在所有函数定义之前,在函数的外部已做了函数声明,则在各个主调函数中不必多所调用的函数在做声明
23 6
|
27天前
|
存储 缓存 C语言
【c语言】简单的算术操作符、输入输出函数
本文介绍了C语言中的算术操作符、赋值操作符、单目操作符以及输入输出函数 `printf` 和 `scanf` 的基本用法。算术操作符包括加、减、乘、除和求余,其中除法和求余运算有特殊规则。赋值操作符用于给变量赋值,并支持复合赋值。单目操作符包括自增自减、正负号和强制类型转换。输入输出函数 `printf` 和 `scanf` 用于格式化输入和输出,支持多种占位符和格式控制。通过示例代码详细解释了这些操作符和函数的使用方法。
34 10
|
20天前
|
存储 算法 程序员
C语言:库函数
C语言的库函数是预定义的函数,用于执行常见的编程任务,如输入输出、字符串处理、数学运算等。使用库函数可以简化编程工作,提高开发效率。C标准库提供了丰富的函数,满足各种需求。
|
26天前
|
机器学习/深度学习 C语言
【c语言】一篇文章搞懂函数递归
本文详细介绍了函数递归的概念、思想及其限制条件,并通过求阶乘、打印整数每一位和求斐波那契数等实例,展示了递归的应用。递归的核心在于将大问题分解为小问题,但需注意递归可能导致效率低下和栈溢出的问题。文章最后总结了递归的优缺点,提醒读者在实际编程中合理使用递归。
53 7