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
相关文章
|
6天前
|
C语言
第一章 C语言知识点(程序)
第一章 C语言知识点(程序)
19 0
|
6天前
|
编译器 C语言
C语言:预处理
C语言:预处理
11 1
C语言:预处理
|
6天前
|
存储 自然语言处理 编译器
“ Hello world ”中的秘密之【C语言程序编译和链接】
作为C语言最经典的代码,大家都可以轻易写出。但是代码的运行过程却很少有人清楚,接下来我将介绍代码运行的奥秘。
17 0
|
6天前
|
自然语言处理 编译器 C语言
C语言程序编译和链接
在ANSI C的任何⼀种实现中,存在两个不同的环境。 第1种是翻译环境,在这个环境中源代码被转换为可执⾏的机器指令(⼆进制指令)。 第2种是执⾏环境,它⽤于实际执⾏代码。
|
6天前
|
网络协议 物联网 数据处理
【C 言专栏】C 语言实现网络通信程序
【5月更文挑战第4天】本文探讨了使用C语言实现网络通信程序的方法,包括理解网络通信基本概念如协议和套接字,以及TCP/UDP通信的实现步骤。通过创建套接字、绑定端口、监听连接、数据传输和错误处理等关键环节,阐述了C语言在网络通信中的优势。文中还提到了实际应用案例、程序优化策略及未来发展趋势,旨在帮助读者掌握C语言在网络通信领域的应用技巧。
【C 言专栏】C 语言实现网络通信程序
|
6天前
|
C语言
C语言:内存函数(memcpy memmove memset memcmp使用)
C语言:内存函数(memcpy memmove memset memcmp使用)
|
4天前
|
C语言
C语言—内存函数的实现和模拟实现(内存函数的丝绸之路)
C语言—内存函数的实现和模拟实现(内存函数的丝绸之路)
18 0
|
4天前
|
C语言
C语言—字符函数与字符串函数(字符问题变简单的关键之技)
C语言—字符函数与字符串函数(字符问题变简单的关键之技)
6 0
|
20小时前
|
C语言 C++
C语言进阶⑭(内存函数_以字节操作)momcpy+mommove+memcmp+memset
C语言进阶⑭(内存函数_以字节操作)momcpy+mommove+memcmp+memset
5 0
|
4天前
|
C语言
C语言——函数递归
C语言——函数递归
7 0