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
相关文章
|
29天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
55 5
|
29天前
|
C语言
C语言编程中,错误处理至关重要,能提升程序的健壮性和可靠性
C语言编程中,错误处理至关重要,能提升程序的健壮性和可靠性。本文探讨了C语言中的错误类型(如语法错误、运行时错误)、基本处理方法(如返回值、全局变量、自定义异常处理)、常见策略(如检查返回值、设置标志位、记录错误信息)及错误处理函数(如perror、strerror)。强调了不忽略错误、保持处理一致性及避免过度处理的重要性,并通过文件操作和网络编程实例展示了错误处理的应用。
64 4
|
28天前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
61 1
|
29天前
|
网络协议 物联网 数据处理
C语言在网络通信程序实现中的应用,介绍了网络通信的基本概念、C语言的特点及其在网络通信中的优势
本文探讨了C语言在网络通信程序实现中的应用,介绍了网络通信的基本概念、C语言的特点及其在网络通信中的优势。文章详细讲解了使用C语言实现网络通信程序的基本步骤,包括TCP和UDP通信程序的实现,并讨论了关键技术、优化方法及未来发展趋势,旨在帮助读者掌握C语言在网络通信中的应用技巧。
46 2
|
25天前
|
存储 C语言 开发者
【C语言】字符串操作函数详解
这些字符串操作函数在C语言中提供了强大的功能,帮助开发者有效地处理字符串数据。通过对每个函数的详细讲解、示例代码和表格说明,可以更好地理解如何使用这些函数进行各种字符串操作。如果在实际编程中遇到特定的字符串处理需求,可以参考这些函数和示例,灵活运用。
51 10
|
25天前
|
存储 程序员 C语言
【C语言】文件操作函数详解
C语言提供了一组标准库函数来处理文件操作,这些函数定义在 `<stdio.h>` 头文件中。文件操作包括文件的打开、读写、关闭以及文件属性的查询等。以下是常用文件操作函数的详细讲解,包括函数原型、参数说明、返回值说明、示例代码和表格汇总。
43 9
|
25天前
|
存储 Unix Serverless
【C语言】常用函数汇总表
本文总结了C语言中常用的函数,涵盖输入/输出、字符串操作、内存管理、数学运算、时间处理、文件操作及布尔类型等多个方面。每类函数均以表格形式列出其功能和使用示例,便于快速查阅和学习。通过综合示例代码,展示了这些函数的实际应用,帮助读者更好地理解和掌握C语言的基本功能和标准库函数的使用方法。感谢阅读,希望对你有所帮助!
34 8
|
25天前
|
C语言 开发者
【C语言】数学函数详解
在C语言中,数学函数是由标准库 `math.h` 提供的。使用这些函数时,需要包含 `#include <math.h>` 头文件。以下是一些常用的数学函数的详细讲解,包括函数原型、参数说明、返回值说明以及示例代码和表格汇总。
43 6
|
25天前
|
存储 C语言
【C语言】输入/输出函数详解
在C语言中,输入/输出操作是通过标准库函数来实现的。这些函数分为两类:标准输入输出函数和文件输入输出函数。
179 6
|
25天前
|
存储 缓存 算法
【C语言】内存管理函数详细讲解
在C语言编程中,内存管理是至关重要的。动态内存分配函数允许程序在运行时请求和释放内存,这对于处理不确定大小的数据结构至关重要。以下是C语言内存管理函数的详细讲解,包括每个函数的功能、标准格式、示例代码、代码解释及其输出。
55 6