当前位置: 首页 > news >正文

【代码大全2 选读】看看骨灰级高手消灭 if-else 逻辑的瑞士军刀长啥样

文章目录

    • 1 【写在前面】
    • 2 【心法】这把瑞士军刀长啥样
    • 3 【示例1】确定某个月份的天数(Days-in-Month Example)
    • 4 【示例2】确定保险费率(Insurance Rates Example)
    • 5 【示例3】灵活的消息格式(Flexible-Message-Format Example)
    • 6 【结语】

1 【写在前面】

随手一翻吃灰多年的《代码大全2》,偶然看到一篇讲重构 if-else / case 的三个案例,很有启发,便有了今天这篇分享。

在这里插入图片描述

2 【心法】这把瑞士军刀长啥样

它就是大名鼎鼎的 表驱动方法(table-driven method)。原文是这样说的:

A table-driven method is a scheme that allows you to look up information in a table rather than using logic statements (if and case) to figure it out.

直译:表驱动法是一种 编程模式——从 里面查找信息,而不使用 逻辑语句ifcase

实际开发中,凡是能通过逻辑语句来实现的功能,都可以通过查表来实现(这个语气是不是似曾相识?)。对于简单情况,用逻辑语句更容易也更直白;随着逻辑链越来越复杂,查表法的作用就愈发凸显出来了。例如,要把字符分为 字母标点数字 三类,若用逻辑语句来实现,可能写成:

if ( (('a' <= inputChar) && (inputChar <= 'z')) ||(('A' <= inputChar) && (inputChar <= 'Z'))) {charType = CharacterType.Letter;
} else if ( (inputChar == ' ') || (inputChar == ',') ||(inputChar == '.') || (inputChar == '!') || (inputChar == '(') ||(inputChar == ')') || (inputChar == ':') || (inputChar == ';') ||(inputChar == '?') || (inputChar == '-')) {charType = CharacterType.Punctuation;
} else if ( ('0' <= inputChar) && (inputChar <= '9') ) {charType = CharacterType.Digit;
}

而如果用查表法,上述代码也就一句搞定:

charType = charTypeTable[ inputChar ];

前提是数组 charTypeTable 提前建好。这里的核心在于,把程序中的信息存到 数据 里,而非 逻辑 里;放到表中,而非 if 检测中。(… put your program’s knowledge into its data rather than into its logic — in the table instead of in the if tests.)

使用表驱动法必须回答的两方面问题:

  • 怎样从表中查到数据?推荐方法有三——
    • 直接访问(Direct access)
    • 索引访问(Indexed access)
    • 阶梯访问(Stair-step access)
  • 应该在表里存什么内容?即查出的结果是数据(data)还是操作(action)?
    • 存数据:直接放表里即可;
    • 存操作:要么存一段代码逻辑(想想 Lambda 表达式);要么存某个引用(如 Java8 的方法引用)

心法讲完了,来看三个具体案例。

3 【示例1】确定某个月份的天数(Days-in-Month Example)

先不考虑闰年,逻辑语句法可以写成(Visual Basic 版):

If ( month = 1 ) Thendays = 31
ElseIf ( month = 2 ) Thendays = 28
ElseIf ( month = 3 ) Thendays = 31
ElseIf ( month = 4 ) Thendays = 30
ElseIf ( month = 5 ) Thendays = 31
ElseIf ( month = 6 ) Thendays = 30
ElseIf ( month = 7 ) Thendays = 31
ElseIf ( month = 8 ) Thendays = 31
ElseIf ( month = 9 ) Thendays = 30
ElseIf ( month = 10 ) Thendays = 31
ElseIf ( month = 11 ) Thendays = 30
ElseIf ( month = 12 ) Thendays = 31
End If

这是一段如假包换的经典屎山代码。

再来看看查表法怎么重构的:

' Initialize Table of "Days Per Month" Data
Dim daysPerMonth() As Integer = _{ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
days = daysPerMonth( month-1 )

考虑闰年的话,改造查表法也很简单,引入一个标志位变成二维数组就行了(如函数 LeapYearIndex),闰年取 1 否则取 0

days = daysPerMonth( month-1, LeapYearIndex() )

可以想象一下,if-else 逻辑语句法的闰年版长啥样……(此处省略 25,000 字)

4 【示例2】确定保险费率(Insurance Rates Example)

写一个算医保费率的程序,该费率随年龄、性别、婚姻状况及吸烟状况的不同而变化。逻辑语句版实现如下:

// Java Example of a Clumsy Way to Determine an Insurance Rate
if ( gender == Gender.Female ) {if ( maritalStatus == MaritalStatus.Single ) {if ( smokingStatus == SmokingStatus.NonSmoking ) {if ( age < 18 ) {rate = 200.00;} else if ( age == 18 ) {rate = 250.00;} else if ( age == 19 ) {rate = 300.00;}
// ...else if ( 65 < age ) {rate = 450.00;}else {if ( age < 18 ) {rate = 250.00;} else if ( age == 18 ) {rate = 300.00;} else if ( age == 19 ) {rate = 350.00;}
// ...else if ( 65 < age ) {rate = 575.00;}}else if ( maritalStatus == MaritalStatus.Married )
//  ...
}

这还只是其中一小部分情况,还没包含已婚女士、所有男士、或者 18 ~ 65 岁人群。按这个思路写下去,写成屎山中的“珠穆朗玛”也只是时间的问题。

那么,换成查表法呢?

书中还是用 VB 来实现(当时的 VB 正如日中天,也可能是因为 VB 对多维数组的支持更友好吧):

首先构建数据表:

' Visual Basic Example of Declaring Data to Set Up an Insurance Rates Table
Public Enum SmokingStatusSmokingStatus_First = 0SmokingStatus_Smoking = 0SmokingStatus_NonSmoking = 1SmokingStatus_Last = 1
End EnumPublic Enum GenderGender_First = 0Gender_Male = 0Gender_Female = 1Gender_Last = 1
End EnumPublic Enum MaritalStatusMaritalStatus_First = 0MaritalStatus_Single = 0MaritalStatus_Married = 1MaritalStatus_Last = 1
End EnumConst MAX_AGE As Integer = 125
Dim rateTable ( SmokingStatus_Last, Gender_Last, MaritalStatus_Last, _MAX_AGE ) As Double

然后调用该数据表:

' Visual Basic Example of an Elegant Way to Determine an Insurance Rate
rate = rateTable( smokingStatus, gender, maritalStatus, age )

5 【示例3】灵活的消息格式(Flexible-Message-Format Example)

如果前两个示例觉得没啥挑战,那你就太小看写《代码大全2》的这帮大宗师了。下面这个案例要解决的,就是针对查询表中的 key 值(或 index 索引值)复杂到难以用代码完全硬编码的情况:

问题描述

编写一个消息打印程序,所有的消息都存放在某类文件中,通常每个文件大概有 500 条这样的消息,涉及大概 20 种消息类型。这些数据采集自一些水上浮标(buoy),提供水温、漂移、方位等信息。

每条消息都包含一个消息头和一段消息正文。消息头有一个标识消息类型的 ID;消息正文由数量不等的字段构成,每一类消息的格式也不尽相同,如图1、图2所示:

图1
图1 浮标信息没有特定顺序,每条消息用 ID 标识

图2
图2 除了消息 ID 外,每类消息有各自的格式

为了突出重点,具体的输出格式就省略了,书中用伪代码一笔带过,最终的一条关于浮标温度的消息,输出结果大概长这样:

Print "Buoy Temperature Message"Read a floating-point value
Print "Average Temperature"
Print the floating-point valueRead a floating-point value
Print "Temperature Range"
Print the floating-point valueRead an integer value
Print "Number of Samples"
Print the integer valueRead a character string
Print "Location"
Print the character stringRead a time of day
Print "Time of Measurement"
Print the time of day

拿到这样的需求,映入你脑海的第一方案是怎样的?

是不是也像写逻辑分支那样,分 20 多种情况,写到 20 多个 if-elseselect-case 分支,然后每个分支再实现对应的输出逻辑……是这样吗:(伪代码形式)

While more messages to readRead a message headerDecode the message ID from the message headerIf the message header is type 1 thenPrint a type 1 messageElse if the message header is type 2 thenPrint a type 2 message
...Else if the message header is type 19 thenPrint a type 19 messageElse if the message header is type 20 thenPrint a type 20 message
End While

或者用面向对象的思想来解决:(伪代码形式)

While more messages to readRead a message headerDecode the message ID from the message headerIf the message header is type 1 thenInstantiate a type 1 message objectElse if the message header is type 2 thenInstantiate a type 2 message object
// ...Else if the message header is type 19 thenInstantiate a type 19 message objectElse if the message header is type 20 thenInstantiate a type 20 message objectEnd if
End While

作者鄙视道:不管是直接写一堆语句、还是抽成 20 多个子类去分别调用公共接口的方法,本质都一样;而且像这样照搬的面向对象编程,写了还不如不写:写成父级接口+子类实现的多态形式,只会让问题更加复杂,后续扩展看似简单,实则比直接写 if-else 还要麻烦。也要改代码、重新编译打包、重新更新发布……

这种情况下,瑞士军刀怎样证明自己呢?

首先,绕过那 20 多种消息类型,直接看消息正文中每个字段出现过的数据类型,并收集到一起(枚举):

// C++ Example of Defining Message Data Types
enum FieldType {FieldType_FloatingPoint,FieldType_Integer,FieldType_String,FieldType_TimeOfDay,FieldType_Boolean,FieldType_BitField,FieldType_Last = FieldType_BitField
};

然后把每类消息按如下形式抽象出来,变成一组配置:

// Example of Defining a Message Table Entry
Message BeginNumFields 5MessageName "Buoy Temperature Message"Field 1, FloatingPoint, "Average Temperature"Field 2, FloatingPoint, "Temperature Range"Field 3, Integer, "Number of Samples"Field 4, String, "Location"Field 5, TimeOfDay, "Time of Measurement"
Message End

然后,主程序只需要写成下面的伪代码形式即可:

While more messages to readRead a message headerDecode the message ID from the message headerLook up the message description in the message-description tableRead the message fields and print them based on the message description
End While

然后,第 4 行用心法中的那三个方法解决检索的问题。

第 5 行输出模块用下面的伪代码实现:

While more fields to printGet the field type from the message descriptioncase ( field type )of ( floating point )read a floating-point valueprint the field labelprint the floating-point valueof ( integer )read an integer valueprint the field labelprint the integer valueof ( character string )read a character stringprint the field labelprint the character stringof ( time of day )read a time of dayprint the field labelprint the time of dayof ( boolean )read a single flagprint the field labelprint the single flagof ( bit field )read a bit fieldprint the field labelprint the bit fieldEnd Case
End While

这样,20 多个消息类型的实现,就转变成了 6 个基本数据类型的实现,并且后者还无视了消息类型数量的变化——无非是修改配置的活,代码可以一直沿用(只要基本类型一直是这 6 个)。

这样改造后,再写成父接口+子类实现的形式,才是面向对象编程的正确打开方式

所谓好人做到底,送佛送到西,作者还是把大致流程用 C++ 过了一遍(大宗师级别还需要手把手敲代码吗?开什么玩笑):

先声明接口和子类实现:

// C++ Example of Setting Up Object Types
class AbstractField {public:virtual void ReadAndPrint( string, FileStatus & ) = 0;
};
class FloatingPointField : public AbstractField {public:virtual void ReadAndPrint( string, FileStatus & ) {// ...
}
};
class IntegerField // ...
class StringField // ...
// ...

再声明一个容纳所有六种情况的数组并初始化:

// C++ Example of Setting Up a Table to Hold an Object of Each Type
AbstractField* field[ Field_Last+1];// C++ Example of Setting Up a List of Objects
field[ Field_FloatingPoint ] = new FloatingPointField();
field[ Field_Integer ] = new IntegerField();
field[ Field_String ] = new StringField();
field[ Field_TimeOfDay ] = new TimeOfDayField();
field[ Field_Boolean ] = new BooleanField();
field[ Field_BitField ] = new BitFieldField();

最后是放到主程序模块:

// C++ Example of Looking Up Objects and Member Routines in a Table
fieldIdx = 1;
while ( (fieldIdx <= numFieldsInMessage) && (fileStatus == OK) ) {fieldType = fieldDescription[ fieldIdx ].FieldType;fieldName = fieldDescription[ fieldIdx ].FieldName;/* This is the table lookup that calls a routine depending on the type of the field just by looking it up in a table of objects. */field[ fieldType ].ReadAndPrint( fieldName, fileStatus );fieldIdx++;
}

后面的检索实现和瑞士军刀无关,就不介绍了。

6 【结语】

我是看到第三个示例,才下决心写一写这个心得的,因为之前的工作就刚好遇到了类似的情况,自己自认为十分牛逼的写法,放到看过这一段或真正理解了表驱动法的开发者眼里,也不过是另一堆屎山罢了。拿到需求的第一时间,可能面临的很多因素都是非技术层面的,比如“时间紧、任务重”,或者“立等可取”,“明早出一版”,“这是上面的死命令”……要想甩锅,在职场打拼多年的老油条们谁又不会呢?但我想说的是,自己的代码写成什么样,说到底还是你对自己整个职业生涯的定位决定的。不求代码流芳百世,但求尽量别遗臭万年,被后来接盘的某某骂到春秋战国时期就行。

关键的关键,是要明白,这样的问题其实是有瑞士军刀级的解决方案的。

P.S.:对《代码大全2》感兴趣的朋友,可以自行感受一下作者那大宗师级别的气场——详见原书第 18 章《表驱动方法》(Chapter 18:Table-Driven Methods

相关文章:

【代码大全2 选读】看看骨灰级高手消灭 if-else 逻辑的瑞士军刀长啥样

文章目录 1 【写在前面】2 【心法】这把瑞士军刀长啥样3 【示例1】确定某个月份的天数&#xff08;Days-in-Month Example&#xff09;4 【示例2】确定保险费率&#xff08;Insurance Rates Example&#xff09;5 【示例3】灵活的消息格式&#xff08;Flexible-Message-Format …...

深度学习 --- stanford cs231学习笔记八(训练神经网络之dropout)

6&#xff0c;dropout 6&#xff0c;1 线性分类器中的正则化 在线性分类器中&#xff0c;我们提到过正则化&#xff0c;其目的就是为了防止过度拟合。例如&#xff0c;当我们要用一条curve去拟合一些散点的数据时&#xff0c;常常是不希望训练出来的curve过所有的点&#xff0c…...

【C++】 解决 C++ 语言报错:Undefined Reference

文章目录 引言 未定义引用&#xff08;Undefined Reference&#xff09;是 C 编程中常见的错误之一&#xff0c;通常在链接阶段出现。当编译器无法找到函数或变量的定义时&#xff0c;就会引发未定义引用错误。这种错误会阻止生成可执行文件&#xff0c;影响程序的正常构建。本…...

【博士每天一篇文献-算法】Adult neurogenesis acts as a neural regularizer

阅读时间&#xff1a;2023-12-20 1 介绍 年份&#xff1a;2022 作者&#xff1a;Lina M. Tran&#xff0c;Adam Santoro&#xff0c;谷歌DeepMind 期刊&#xff1a; Proceedings of the National Academy of Sciences 引用量&#xff1a;13 代码&#xff1a;https://github.c…...

在Spring Boot项目中引入本地JAR包的步骤和配置

在Spring Boot项目中&#xff0c;有时需要引入本地JAR包以便重用已有的代码库或者第三方库。本文将详细介绍如何在Spring Boot项目中引入本地JAR包的步骤和配置&#xff0c;并提供相应的代码示例。 1. 为什么需要本地JAR包 在开发过程中&#xff0c;可能会遇到以下情况需要使…...

Android Studio中使用命令行gradle查看签名信息

Android Studio中使用命令行gradle查看签名信息&#xff1a; 使用 Gradle 插件生成签名报告 打开 Android Studio 的 Terminal。 运行以下命令&#xff1a;./gradlew signingReport 将生成一个签名报告&#xff0c;其中包含 MD5、SHA1 和 SHA-256 的信息。 如果失败&#xf…...

昇思25天学习打卡营第5天 | 神经网络构建

1. 神经网络构建 神经网络模型是由神经网络层和Tensor操作构成的&#xff0c;mindspore.nn提供了常见神经网络层的实现&#xff0c;在MindSpore中&#xff0c;Cell类是构建所有网络的基类&#xff0c;也是网络的基本单元。一个神经网络模型表示为一个Cell&#xff0c;它由不同…...

Web缓存—Nginx和CDN应用

目录 一、代理的工作机制 二、概念 三、作用 四、常用的代理服务器 二.Nginx缓存代理服务器部署 1.在三台服务器上部署nginx 此处yum安装 2.准备测试界面 三、CDN概念及作用 1.CDN的工作过程 一、代理的工作机制 &#xff08;1&#xff09;代替客户机向网站请求数据…...

Linux 端口

什么是虚拟端口 计算机程序之间的通讯&#xff0c;通过IP只能锁定计算机&#xff0c;但是无法锁定具体的程序。通过端口可以锁定计算机上具体的程序&#xff0c;确保程序之间进行沟通。 IP地址相当于小区地址&#xff0c;在小区内可以有许多用户&#xff08;程序&#xff09;&…...

菜鸡的原地踏步史02(◐‿◑)

每日一念 改掉自己想到哪写哪的坏习惯 二叉树 二叉树的中序遍历 class Solution {/**中序遍历左 - 中 - 右*/private List<Integer> res new ArrayList<>();public List<Integer> inorderTraversal(TreeNode root) {if(root null) {return res;}tranve…...

实现Java应用的数据加密与解密技术

实现Java应用的数据加密与解密技术 大家好&#xff0c;我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01; 1. 数据加密与解密的重要性 数据安全是当今互联网应用开发中的重要问题之…...

赛博解压板

目录 开头程序程序的流程图程序的解压效果(暂无&#xff0c;但可以运行一下上面的代码)结尾 开头 大家好&#xff0c;我叫这是我58。今天&#xff0c;我们要看关于赛博解压板的一些东西。 程序 #define _CRT_SECURE_NO_WARNINGS 1 #define ROW 6//ROW表示行数&#xff0c;可…...

微信小程序常用的事件

1.点击事件 WXML 中绑定点击事件&#xff1a; <!-- index.wxml --> <button bindtap"handleTap">点击我</button> 对应的 JS 文件中编写点击事件处理函数&#xff1a; // index.js Page({handleTap: function() {console.log(按钮被点击了);} }…...

js时间转成xx前

// 时间戳转多少分钟之前 export default function getDateDiff(dateTimeStamp) {// console.log(dateTimeStamp,dateTimeStamp)// 时间字符串转时间戳var timestamp new Date(dateTimeStamp).getTime();var minute 1000 * 60;var hour minute * 60;var day hour * 24;var …...

iOS 锁总结(cc)

iOS中atomic和synchrosize锁的本质是什么? 在iOS中,atomic和@synchronized锁的本质涉及底层的多线程同步机制。以下是关于这两者本质的详细解释: atomic 定义与用途: atomic是Objective-C属性修饰符的一种,用于指示属性的存取方法是线程安全的。当一个属性被声明为ato…...

【CSAPP】-binarybomb实验

目录 实验目的与要求 实验原理与内容 实验设备与软件环境 实验过程与结果&#xff08;可贴图&#xff09; 操作异常问题与解决方案 实验总结 实验目的与要求 1. 增强学生对于程序的机器级表示、汇编语言、调试器和逆向工程等方面原理与技能的掌握。 2. 掌握使用gdb调试器…...

SpringBoot实战:轻松实现XSS攻击防御(注解和过滤器)

文章目录 引言一、XSS攻击概述1.1 XSS攻击的定义1.2 XSS攻击的类型1.3 XSS攻击的攻击原理及示例 二、Spring Boot中的XSS防御手段2.1 使用注解进行XSS防御2.1.1 引入相关依赖2.1.2 使用XSS注解进行参数校验2.1.3 实现自定义注解处理器2.1.4 使用注解 2.2 使用过滤器进行XSS防御…...

如何改善提示词,让 GPT-4 更高效准确地把视频内容整体转换成文章?

&#xff08;注&#xff1a;本文为小报童精选文章。已订阅小报童或加入知识星球「玉树芝兰」用户请勿重复付费&#xff09; 让我们来讨论一下大语言模型应用中的一个重要原则 ——「欲速则不达」。 作为一个自认为懒惰的人&#xff0c;我一直有一个愿望&#xff1a;完成视频制作…...

TensorBoard进阶

文章目录 TensorBoard进阶1.设置TensorBoard2.图像数据在TensorBoard中可视化3.模型结构在TensorBoard中可视化&#xff08;重点✅&#xff09;4.高维数据在TensorBoard中低维可视化5.利用TensorBoard跟踪模型的训练过程&#xff08;重点✅&#xff09;6.利用TensorBoard给每个…...

使用AES加密数据传输的iOS客户端实现方案

在现代应用开发中&#xff0c;确保数据传输的安全性是至关重要的。本文将介绍如何在iOS客户端中使用AES加密数据传输&#xff0c;并与服务器端保持加密解密的一致性。本文不会包含服务器端代码&#xff0c;但会解释其实现原理。 加密与解密的基本原理 AES&#xff08;Advance…...

业务系统对接大模型的基础方案:架构设计与关键步骤

业务系统对接大模型&#xff1a;架构设计与关键步骤 在当今数字化转型的浪潮中&#xff0c;大语言模型&#xff08;LLM&#xff09;已成为企业提升业务效率和创新能力的关键技术之一。将大模型集成到业务系统中&#xff0c;不仅可以优化用户体验&#xff0c;还能为业务决策提供…...

(十)学生端搭建

本次旨在将之前的已完成的部分功能进行拼装到学生端&#xff0c;同时完善学生端的构建。本次工作主要包括&#xff1a; 1.学生端整体界面布局 2.模拟考场与部分个人画像流程的串联 3.整体学生端逻辑 一、学生端 在主界面可以选择自己的用户角色 选择学生则进入学生登录界面…...

UE5 学习系列(三)创建和移动物体

这篇博客是该系列的第三篇&#xff0c;是在之前两篇博客的基础上展开&#xff0c;主要介绍如何在操作界面中创建和拖动物体&#xff0c;这篇博客跟随的视频链接如下&#xff1a; B 站视频&#xff1a;s03-创建和移动物体 如果你不打算开之前的博客并且对UE5 比较熟的话按照以…...

在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module

1、为什么要修改 CONNECT 报文&#xff1f; 多租户隔离&#xff1a;自动为接入设备追加租户前缀&#xff0c;后端按 ClientID 拆分队列。零代码鉴权&#xff1a;将入站用户名替换为 OAuth Access-Token&#xff0c;后端 Broker 统一校验。灰度发布&#xff1a;根据 IP/地理位写…...

UR 协作机器人「三剑客」:精密轻量担当(UR7e)、全能协作主力(UR12e)、重型任务专家(UR15)

UR协作机器人正以其卓越性能在现代制造业自动化中扮演重要角色。UR7e、UR12e和UR15通过创新技术和精准设计满足了不同行业的多样化需求。其中&#xff0c;UR15以其速度、精度及人工智能准备能力成为自动化领域的重要突破。UR7e和UR12e则在负载规格和市场定位上不断优化&#xf…...

docker 部署发现spring.profiles.active 问题

报错&#xff1a; org.springframework.boot.context.config.InvalidConfigDataPropertyException: Property spring.profiles.active imported from location class path resource [application-test.yml] is invalid in a profile specific resource [origin: class path re…...

#Uniapp篇:chrome调试unapp适配

chrome调试设备----使用Android模拟机开发调试移动端页面 Chrome://inspect/#devices MuMu模拟器Edge浏览器&#xff1a;Android原生APP嵌入的H5页面元素定位 chrome://inspect/#devices uniapp单位适配 根路径下 postcss.config.js 需要装这些插件 “postcss”: “^8.5.…...

让回归模型不再被异常值“带跑偏“,MSE和Cauchy损失函数在噪声数据环境下的实战对比

在机器学习的回归分析中&#xff0c;损失函数的选择对模型性能具有决定性影响。均方误差&#xff08;MSE&#xff09;作为经典的损失函数&#xff0c;在处理干净数据时表现优异&#xff0c;但在面对包含异常值的噪声数据时&#xff0c;其对大误差的二次惩罚机制往往导致模型参数…...

【JavaSE】多线程基础学习笔记

多线程基础 -线程相关概念 程序&#xff08;Program&#xff09; 是为完成特定任务、用某种语言编写的一组指令的集合简单的说:就是我们写的代码 进程 进程是指运行中的程序&#xff0c;比如我们使用QQ&#xff0c;就启动了一个进程&#xff0c;操作系统就会为该进程分配内存…...

【Linux】自动化构建-Make/Makefile

前言 上文我们讲到了Linux中的编译器gcc/g 【Linux】编译器gcc/g及其库的详细介绍-CSDN博客 本来我们将一个对于编译来说很重要的工具&#xff1a;make/makfile 1.背景 在一个工程中源文件不计其数&#xff0c;其按类型、功能、模块分别放在若干个目录中&#xff0c;mak…...