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
相关文章
|
14天前
|
编译器 C语言
C语言--预处理详解(1)
【10月更文挑战第3天】
|
14天前
|
编译器 Linux C语言
C语言--预处理详解(3)
【10月更文挑战第3天】
|
14天前
|
C语言
C语言--预处理详解(2)
【10月更文挑战第3天】
|
11天前
|
存储 文件存储 C语言
深入C语言:文件操作实现局外影响程序
深入C语言:文件操作实现局外影响程序
|
17天前
|
编译器 C语言
C语言预处理详解
C语言预处理详解
|
10天前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
24 3
|
1天前
|
存储 缓存 C语言
【c语言】简单的算术操作符、输入输出函数
本文介绍了C语言中的算术操作符、赋值操作符、单目操作符以及输入输出函数 `printf` 和 `scanf` 的基本用法。算术操作符包括加、减、乘、除和求余,其中除法和求余运算有特殊规则。赋值操作符用于给变量赋值,并支持复合赋值。单目操作符包括自增自减、正负号和强制类型转换。输入输出函数 `printf` 和 `scanf` 用于格式化输入和输出,支持多种占位符和格式控制。通过示例代码详细解释了这些操作符和函数的使用方法。
17 10
|
4天前
|
存储 编译器 C语言
C语言函数的定义与函数的声明的区别
C语言中,函数的定义包含函数的实现,即具体执行的代码块;而函数的声明仅描述函数的名称、返回类型和参数列表,用于告知编译器函数的存在,但不包含实现细节。声明通常放在头文件中,定义则在源文件中。
|
11天前
|
C语言
c语言回顾-函数递归(上)
c语言回顾-函数递归(上)
27 2