【C语言进阶深度学习记录】十七 宏定义的使用与分析

文章目录

    • 1 C语言中的宏定义
      • 1.1 定义宏常量
      • 1.2 宏定义表达式
      • 1.3 宏表达式与函数的对比
      • 1.4 宏表达式的作用域
    • 2 C语言中的内置宏
    • 3 宏定义的代码综合示例
    • 4 总结

1 C语言中的宏定义

  • #define 是预处理器处理的单元实体之一
  • #define 定义的宏,可以出现在程序的任意位置
  • #define 定义之后的代码,都可以使用这个宏

因为宏定义是在预处理器处理的,所以如果怀疑是宏定义出错,可以单步编译到预处理阶段,查看预处理后的代码。参考文章:编译的过程

1.1 定义宏常量

  • #define 定义的宏常量可以直接使用。常量也可以是字符和字符串
  • #define 定义的宏常量本质为字面量(字面量到底是什么,存在哪里?后期还会学习记录)
  • 在预处理阶段直接将定义的宏进行文本替换

如下面的代码(先不管这个代码有什么问题):

  • 21-1.c
#define ERROR -1
#define PATH1 "D:\test\test.c"
#define PATH2 D:\test\test.c
#define PATH3 D:\test\
test.cint main()
{int err = ERROR;char* p1 = PATH1;char* p2 = PATH2;char* p3 = PATH3;
}
  • 使用预编译指令进行编译:gcc -E 21-1.c -o 21-1.i
  • 查看生成的预处理文件21-1.i 如下:
  • 21-1.i
# 1 "21-1.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "21-1.c"int main()
{int err = -1;char* p1 = "D:\test\test.c";char* p2 = D:\test\test.c;char* p3 = D:\testtest.c;
}
  • 可以看到定义的宏常量,是直接在编译器的预处理阶段被文本替换了。
  • 上述代码是有问题的,如果直接编译的话,会出现编译错误。从替换后的16,17行可以看出错误之处。

1.2 宏定义表达式

  • #define 定义的表达式类似函数调用
  • 并且#define 定义的表达式 在有些时候比函数更加强大
  • 但是 #define 表达式也更容易出错,就像上面宏定义常量出错一样。

比如下面的代码:

  • 21-2.c
#include <stdio.h>#define _SUM_(a, b) (a) + (b)
#define _MIN_(a, b) ((a) < (b) ? (a) : (b))
#define _DIM_(a) sizeof(a)/sizeof(*a)int main()
{int a = 1;int b = 2;int c[4] = {0};int s1 = _SUM_(a, b);int s2 = _SUM_(a, b) * _SUM_(a, b);int m = _MIN_(a++, b);int d = _DIM_(c);printf("s1 = %d\n", s1);printf("s2 = %d\n", s2);printf("m = %d\n", m);printf("d = %d\n", d);return 0;
}
  • 编译上面的代码结果为:

s1 = 3
s2 = 5
m = 2
d = 4

很明显与我们所期望的结果不一样。实际上还是宏定义表达式没有定义好,在预处理阶段,直接将宏定义表达式替换的原因。

将上述代码的第1、19、20、21、22注释掉,然后使用预处理编译指令进行单步编译上面的代码,gcc -E 21-2.c -o 21-2.i 得到的结果如下:

  • 21-2.i
# 1 "21-2.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "21-2.c"int main()
{int a = 1;int b = 2;int c[4] = {0};int s1 = (a) + (b);int s2 = (a) + (b) * (a) + (b);int m = ((a++) < (b) ? (a++) : (b));int d = sizeof(c)/sizeof(*c);return 0;
}
  • 很明显,宏替换之后,第19行很明显不是我们想要的那样。

1.3 宏表达式与函数的对比

  • 宏表达式被预处理器处理,编译器不知道宏表达式的存在
  • 宏表达式用实参完全替代形参,不进行任何运算
  • 宏表达式没有任何的调用开销
  • 宏表达式中不能出现递归定义

1.4 宏表达式的作用域

一个问题:宏定义的常量或者表达式是否有作用域限制?

在回答这个问题之前,先看看下面的代码:

  • 21-3.c
#include <stdio.h>void def()
{#define PI 3.1415926#define AREA(r) r * r * PI
}double area(double r)
{return AREA(r);
}int main()
{double r = area(5);printf("PI = %f\n", PI);printf("d = 5; a = %f\n", r);return 0;
}
  • 编译运行上述代码结果如下:

PI = 3.141593
d = 5; a = 78.539815

  • 这说明,上述宏定义是有效的。并没有因为函数的结束而销毁。
  • 其实,因为宏定义在预处理阶段就被处理替换了,所以作用域不会影响宏定义。
  • 只要定义完宏之后,它后面的代码就可以使用
  • 作用域是在编译阶段针对变量和函数,不影响已经在预处理阶段替换了的宏。

2 C语言中的内置宏

在C语言中,有很多内置宏,可以供我们使用,这样节省了我们的开发时间。下面罗列一些常用的:

在这里插入图片描述

注意,上面的宏,都是代表当前编译时的xxx

3 宏定义的代码综合示例

可以使用下面的综合例子来说明本文宏的使用:

  • 21-4.c
#include <stdio.h>
#include <malloc.h>#define MALLOC(type, x) (type*)malloc(sizeof(type)*x)#define FREE(p) (free(p), p=NULL)#define LOG(s) printf("[%s] {%s:%d} %s \n", __DATE__, __FILE__, __LINE__, s)#define FOREACH(i, m) for(i=0; i<m; i++)
#define BEGIN {
#define END   }int main()
{int x = 0;int* p = MALLOC(int, 5);LOG("Begin to run main code...");FOREACH(x, 5)BEGINp[x] = x;ENDFOREACH(x, 5)BEGINprintf("%d\n", p[x]);ENDFREE(p);LOG("End");return 0;
}
  • 编译运行的结果为:

在这里插入图片描述

  • 上述的代码也是比较简单的,但是同时将本片文章的所有内容都囊括在内了,是一个非常不错的例子

4 总结

  • 预处理器直接对宏进行替换
  • 预处理器不会对宏定义进行语法检查
  • 宏定义时出现的语法错误,只能被编译器检测到
  • 宏定义的效率高于函数调用
  • 宏定义容易出错

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/422813.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【C语言进阶深度学习记录】十八 条件编译的使用与分析

文章目录1 基本概念1.1 代码分析1.2 通过命令行定义宏2 #include 的本质2.1 解决重复包含头文件的问题3 条件编译的应用4 总结1 基本概念 条件编译的行为类似于C语言中的if … else…条件编译是预编译指示指令&#xff0c;用于控制是否编译某段代码 比如下图的代码&#xff1…

【C语言进阶深度学习记录】十九 #pragma使用与分析

文章目录1 #pragma 概念简介1.1 #pragma message 的用法1.2 #pragma once 的用法1.3 #pragma pack 的用法1.31 struct占用的内存大小如何计算2 总结在学习 #pragma 之前 &#xff0c;我们首先要明白一点&#xff0c; #pragma 的实现&#xff0c;在不同的编译器之间是不同的&…

【C语言进阶深度学习记录】二十一 # 和 ## 号操作符的使用与分析

文章目录1 # 运算符1.1 # 运算符的基本用法1.2 # 运算符的妙用2 ## 运算符2.1 ##运算符的基本用法2.2 ##运算符的工程运用3 总结1 # 运算符 #运算符用于在预处理期将宏参数转换为字符串#的转换是在预处理期间完成的&#xff0c;因此&#xff0c;只在宏定义中有效编译器并不知道…

生命游戏

生命游戏其实是一个零玩家游戏。在一个二维世界中&#xff0c;每一个格子看作一个细胞&#xff0c;每个细胞都有生和死两种状态。 每个细胞周围有8个邻居&#xff0c;这个细胞时刻关注着这些邻居的状态。一个细胞在下一个时刻生死取决于相邻八个方格中活着的或死了的细胞的数量…

【C语言进阶深度学习记录】二十 结构体大小计算与结构体内存布局的详细方法

结构体大小的计算往往是面试笔试常考的知识。对于简单的结构体&#xff0c;可以一眼看出来&#xff0c;对于复杂的结构体&#xff0c;该如何计算结构体占用内存的大小呢?本文学习所使用的编译器是gcc 4.4.5 使用其他编译器或者使用Windows 上的编译器有可能不太一样。 文章目录…

【C语言进阶深度学习记录】二十二 指针的本质分析

在C语言中&#xff0c;最难的也就是指针了。如果我们了解了指针的本质&#xff0c;它就会变得简单 文章目录1 回顾&#xff1a;什么是变量&#xff1f;1.1 *号的意义1.2 指针使用示例2 传值调用与传址调用2.1 利用指针交换两个变量3 const与指针的配合3.1 const指针代码分析4 总…

前端学习(284):纯css实现翻书效果

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>纯css3实现翻书效果</title><link href"reset.css" rel"stylesheet" type"text/css"><style>body {…

【C语言进阶深度学习记录】二十三 数组的本质分析

文章目录1 数组的概念1.1 数组的大小1.2 数组的初始化2 数组的地址与数组名3 数组名不能作为左值使用4 总结1 数组的概念 数组是相同类型的变量的有序集合数组中的元素没有名字 如下图是一个数组&#xff1a; 1.1 数组的大小 数组的大小是数组元素个数乘以元素的数据类型数组…

【C语言进阶深度学习记录】二十四 指针与数组的本质分析一

文章目录1 回顾--数组的本质2 指针的运算2.1 指针运算代码案例分析3 指针的比较3.1 指针运算的应用代码案例分析4 总结1 回顾–数组的本质 在之前的文章&#xff0c;已经学习了数组的本质分析。下面再来回顾一下&#xff1a; 数组是一段连续的内存空间数组名可以看做是指向数…

【C语言进阶深度学习记录】二十五 指针与数组的本质分析二

文章目录1 数组的访问方式1.1 数组的访问方式代码分析2 数组和指针不同3 a 和 &a 的区别3.1 指针运算的经典代码案例分析4 数组作为函数的参数4.1 数组作为函数参数的代码案例分析5 总结开题问&#xff1a;数组名可以当做常量指针使用。那么指针是否可以当做数组来使用? …

设计模式_第二篇_策略模式

本文是我通过学习《Head First 设计模式》而写。 作为我要描述的第一个模式&#xff0c;首先要说什么是设计模式&#xff0c;然后&#xff0c;用一个实例&#xff0c;并对这个实例不断的改进&#xff0c;引出策略模式。 与其空泛地给出一堆描述&#xff0c;倒不如给出通过一个实…

【C语言进阶深度学习记录】二十六 C语言中的字符串与字符数组的详细分析

之前有一篇文章是学习了字符和字符串的&#xff0c;可以与之结合学习&#xff1a;【C语言进阶深度学习记录】十二 C语言中的&#xff1a;字符和字符串 文章目录1 字符串的概念1.1 字符串与字符数组1.2 字符数组与字符串代码分析2 字符串字面量2.1 字符串字面量的本质的代码分析…

3天搞定的小型B/S内部管理类软件定制开发项目【软件开发实战10步骤详解】

2010-10-07 21:39 by 通用权限组件源码, 16580 visits, 收藏, 编辑 十一休假&#xff0c;杭州西湖边逛了一圈只能用人山人海来形容&#xff0c;浙大紫金港校区也逛了一圈风景如画&#xff0c;建设得真不错很棒&#xff0c;假期就去了这2个地方&#xff0c;然后在家里陪老婆、看…

【C语言进阶深度学习记录】二十八 数组指针与指针数组的分析

数组指针与指针数是非常重要的概念。面试中也是经常会被问到的 文章目录1 数组的类型1.1 定义数组的类型2 数组指针2.1 数组类型和数组指针的代码分析3 指针数组3.1 指针数组代码案例分析4 总结1 数组的类型 C语言中的数组有自己特定的类型。比如 int a[5]&#xff1b; 数组a…

【C语言进阶深度学习记录】二十九 main函数与命令行参数

文章目录1 main函数的返回值2 main函数的参数2.1 main函数的参数的代码案例分析3 main函数不一定是程序中第一个执行的函数4 总结1 main函数的返回值 main函数是操作系统调用的函数操作系统总是将main函数的返回值作为程序的退出状态main函数的返回值正常来说是0&#xff0c;如…

【C语言进阶深度学习记录】三十 二维数组与二维指针

文章目录1 二维指针&#xff08;指向指针的指针&#xff09;2 二维数组3 二维数组的类型3.2 如何动态申请二维数组4 总结1 二维指针&#xff08;指向指针的指针&#xff09; 指针的本质是变量指针的指针是保存指针变量的地址。如下面的代码&#xff1a; 为什么需要指向指针的存…

【C语言进阶深度学习记录】三十一 数组作为函数参数时退化为指针

之前的学习数组的文章中&#xff0c;已经知道一维数组作为函数参数的时候&#xff0c;最终会被编译器编译为指针。今天来看看二维数组的情形 文章目录1 为什么C语言中的数组作为函数参数会退化为指针&#xff1f;2 二维数组作为函数参数如何退化2.1 代码案例分析&#xff08;传…

使用HTMLParser模块解析HTML页面

HTMLParser是python用来解析html和xhtml文件格式的模块。它可以分析出html里面的标签、数据等等&#xff0c;是一种处理html的简便途径。HTMLParser采用的是一种事件驱动的模式&#xff0c;当HTMLParser找到一个特定的标记时&#xff0c;它会去调用一个用户定义的函数&#xff…

前端学习(294):rem小实例

altz转换为rem <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><meta http-equiv"X-UA-Compatible"…