五仁wr - 2022年3月
type: channel
link: https://www.orzzz.cn/index.php/2022/03
description: 五仁のBLOG
items: 【五仁图形学】Unity Shader 来个最简单的顶点片元着色器 Helloworld!
items: 【五仁科技】高动态范围（HDR）成像——debevec算法
items: 【五仁图形学】Unity Shader 基础
items: 【五仁python】来给你的朋友做一个随机涩图程序
items: 【五仁python】用简单代码压缩图片
items: 【五仁C++】静态库？动态库？.a.so.lib.dll的生成与使用(g++版)

【五仁图形学】Unity Shader 来个最简单的顶点片元着色器 Helloworld!
type: item
date: 2022-03-30T00:40:16+08:00
description: 任何的程序，任何的语言，都有 Helloworld，它是初学者迈入新世界的大门，Unity Shader 也不例外。在实现光彩夺目的画面之前，我们首先来实现一个最简单的 Unity Shader。Helloworld以我以往的经验，此时应该直接写出所有的步骤，而不涉及任何的原理。也就是说，只动手，不动脑。（1）在 Unity 中新建一个场景，操作是点击菜单栏中的 File → New Scene，然后点击 Create，即可创建一个含有摄像头和平行光的场景。（2）新建一个 Unity Shader，操作是在项目面板中单机创建按钮，然后选择 shader，选任意一个模板都可以。（3）新建一个材质，类似于上一步，在项目面板中创建。然后把第 2 步创建的 Unity Shader 赋给这个材质，操作是在项目面板中把 Unity Shader 文件拖拽到材质文件上。 （4）在场景中新建一个球体，操作是在层级面板中创建。然后把第 3 步创建的材质赋给这个球体，操作是把项目面板中的材质文件拖拽到层级面板的球体上。（5）双击打开第 2 步创建的 Unity Shader。删除里面的所有代码，把下面的代码粘帖进去。Shader "Wuren/Helloworld" { SubShader { Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag float4 vert(float4 v : POSITION) : SV_POSITION { return UnityObjectToClipPos(v); } fixed4 frag() : SV_Target { return fixed4(1.0, 1.0, 1.0, 1.0); } ENDCG } } }保存之后，返回 Unity 会自动编译，就能查看结果了。这段 shader 代码的效果如下图所示：首先，代码的第一行通过 Shader 语义定义了这个 Unity Shader 的名字。正如 C++ 的函数不一定要传入参数，Unity Shader 也不一定需要 Properties 语义块，我们可以选择不声明任何材质属性。然后，我们声明了 SubShader 和 Pass 语义块，没有进行任何的渲染设置和标签设置。接着，就是由 CGPROGRAM 和 ENDCG 所包围的 CG 代码片段，这是我们的重点，在 Unity Shader，被 CGPROGRAM 和 ENDCG 包围的部分才是我们真正用于计算的代码，在其中可以发挥我们无穷无尽的创意。在 CG 代码中，首先是两行非常重要的编译指令:#pragma vertex vert #pragma fragment frag它们将告诉 Unity，哪个函数包含了顶点着色器的代码，哪个函数包含了片元着色器的代码，上面这两句对应了后面的 vert 函数和 frag 函数。当然，想要个性化也可以写 #pragma vertex QwQ 并且后面定义一个 QwQ 函数作为顶点着色器。接下来，我们看一下 vert 函数的定义：float4 vert(float4 v : POSITION) : SV_POSITION { return UnityObjectToClipPos(v); }它跟 C 语言很像，函数的输入是 float4 类型，即 4 维向量，变量名是 v，返回值也是 float4 类型的变量。特别的地方在于函数中的 POSITION 和 SV_POSITION，它们是 CG/HLSL 语言中的语义（semantics），这是不可省略的，它们的存在，给变量赋予了一个身份。在图形学的渲染管线中我们了解到，着色器是有特定的输入输出的，比如顶点的模型空间的坐标会作为顶点着色器的输入，且顶点着色器的输出至少包含顶点的裁剪空间的坐标。我们可以定义顶点着色器的输入和输出包含很多变量，那么哪个变量作为那些特定的输入输出呢？于是，我们用 CG/HLSL 语言中的语义来给变量赋予身份，比如这里 POSITION 将告诉 Unity，变量 v 的身份就是模型的顶点坐标， 于是 Shader 运行时就会把模型的顶点坐标填充到输入参数 v 中，SV_POSITION 将告诉 Unity，顶点着色器的输出是裁剪空间中的顶点坐标。而 UnityObjectToClipPos(*) 函数是 Unity Shader 的内置函数，我们用英文来理解它，就是把顶点在模型空间中的坐标变换成裁剪空间中的坐标。在以前，这种操作是通过 mul(UNITY_MATRIX_MVP,*) 这句代码来实现的，也就是左乘变换矩阵，UNITY_MATRIX_MVP 在以前是内置变量。现在只不过是换了种形式，原理都是一样的。像 Un…

【五仁科技】高动态范围（HDR）成像——debevec算法
type: item
date: 2022-03-23T19:52:10+08:00
description: 首先简述问题和任务：正常情况下拍的一张照片无法同时将明亮的部分和阴暗的部分都显示清楚，我们需要将曝光时间长的照片（用于看清暗部）和曝光时间短的照片（用于看清亮部）进行融合，得到一张不论明亮都非常清晰的照片。组合在不同曝光设置下获取的同一场景的不同图像的过程称为高动态范围 (HDR)成像。前置知识（1） 我们平时所看到的图片文件，比如jpg、png那些，像素值是0~255的整数，我们称为 LDR（低动态范围）图片；而文件后缀名为 .hdr 的图片的像素值是小数，不是整数，在正常情况下是不能直接打开的，需要通过某个算法将小数的像素值转化为整数，才能在计算机中正常打开，这个算法称为色调映射算法。而某些能直接打开.hdr文件的软件，如PS（Photoshop），其实是有一个自己内置的色调映射算法。（2） 辐射强度的概念：单位时间内物体单位表面积辐射出某特定波长射线的能量，称为单色波长的辐射强度。常用单位为：W/m2，瓦特/平方米。以下将辐射强度称为辐照度。（3） 响应曲线的概念：当我们拍照的时候，现实场景中的光照会转化照片中的像素值，这可看作是一种映射关系，即函数。我们用字母表示物理量，辐照度为 E，曝光时间为 Δt，曝光为 X，那么照片的像素值（整数）对曝光X的响应是一个函数，将现实场景映射到照片中，这个函数就称为相机的响应函数（CRF），也可以叫做响应曲线。在曝光时间 Δt 不变的前提下，当某个光源变成两倍亮时（辐照度变为两倍），照片中的像素值往往不会变成原来的两倍，所以说响应曲线是一个非线性函数。任务分解我们的目的是将多张不同曝光时间的照片合成一张照片，合成之后的那张照片既能看清楚很亮的区域，也能看清楚很暗的区域。我们的步骤一共有三步，请记住：① 生成相机响应曲线② 将多张照片融合，得到一个后缀为 .hdr 的图片文件，像素值为小数③ 色调映射，将上一步得到的 hdr 图片转化成能查看的 jpg、png 图片文件在计算机 opencv 库中，上述的三个步骤可以对应三个函数。需要注意的是，由于计算机的函数可以重载，所以同名函数可以传入不同的参数。为什么要说这个呢，因为上述的第 ② 步可以不依赖于第 ① 步。正常情况下，确实应该先求出响应曲线，然后第 ② 步利用这个响应曲线来融合照片。但是，如果没有求出第 ① 步的响应曲线，也不妨碍第 ② 步的执行，前面提到，真实的响应曲线一般是非线性的，换而言之，我们可以假设一个最理想但不真实的响应曲线，他是线性的，然后用这个线性响应曲线来融合图片。简单的说，就是第 ② 步的融合函数有两个重载，一个需要传入响应曲线后进行融合，这个响应曲线是第 ① 步生成的；另一个重载不需要传入响应曲线，它直接使用一个固定的线性响应曲线，当然，这种固定的线性响应曲线一般不符合真实场景，所以融合出的图片也容易不符合预期。Debevec算法下面我们来介绍一下 Debevec 算法：这个算法包含两个部分，对应第 ① 步和第 ② 步，即生成响应曲线和生成 hdr 图片。如何求相机响应曲线。已有的材料是几张有亮有暗的照片，已知是这几张照片的各个像素的像素值，以及这几张照片的曝光时间 Δt。各符号的意义：曝光 X，辐照度 E，曝光时间 Δt，像素值 Z前提假设：X=E·Δt，请务必记住这个假设，它是Debevec算法成功的前提，以后会介绍当这个假设不成立时，Debevec 算法会如何失败。X=E·Δt 可以理解为，当辐照度 E 固定时，曝光度 X 随着曝光时间 Δt 线性增大，从人体直观的感受上来看，就是曝光时间越久的照片越亮。事实上，严格满足 X=E·Δt 几乎是不可能的，但是，越接近这个式子，Debevec 算法的效果就越好。前面说过，相机响应曲线是曝光 X 到像素值 Z 的函数，我们设为 f，即 $Z = f(X) = f(E·Δt)$，我们对应到具体的某一张照片上，即 $Z_{ij} = f(E_i·Δt_j)$，其中 $Z_{ij}$ 为第 j 张照片的第 i 个像素，$E_i$ 为照片中第 i 个像素所对应的位置在现实世界中的辐照度，$Δt_j$ 为第 j 张照片的曝光时间。这里可能会有疑问，一张照片不是有长和宽吗，这明明需要 2 个坐标才能表示一个像素的位置，为什么下标 i 可以表示照片某个像素的位置呢？虽然图片是二维像素矩阵，但实际上可以压成一维，比如一张分辨率 2×5 的图片，实际上也可以表示为一个 10 维的向量，这样我们就能用一个下标来表示任意位置的像素了。第 6 个像素即对应于分辨率 2×5 图片中第 2 行第 1 列的像素。公式推演下面我们对 $Z_{ij} = f(E_i·Δt_j)$ 这条式子作变换，首先是初始公式：$$Z_{ij} = f(E_i·Δt_j) \tag{1}$$因为我们假设 f …

【五仁图形学】Unity Shader 基础
type: item
date: 2022-03-15T13:47:00+08:00
description: Shader 的中文翻译是着色器，它的作用是，将场景中的物体经过一些列计算，最终呈现在屏幕上的一个个像素点。要学会编写 Unity Shader，需要一些图形学的前置基础知识，包括渲染管线、线性代数、一点编程经验。在 Unity 中，如果想要呈现绚丽的画面，我们就要学会写 Unity Shader，本篇主要讲述了 Unity Shader 中的一些关键基础概念。一个单独的 Unity Shader 是无法发挥任何作用的，它必须和材质结合起来。Unity Shader 支持多种 shader 语言，但我们一般使用 CG 语言来写，即 C for Graphic。ShaderLabUnity 提供了一种专门为 Unity Shader 服务的语言——ShaderLab。在传统的编写着色器方式中，程序员往往要处理很多文件和设置，才能让画面呈现出想要的效果，比如载入模型、根据平台选择不同接口、设置着色器的输入、把资源加载到GPU、设置渲染状态、对渲染状态进行排序等等，而使用 ShaderLab 语言编写的 Unity Shader 可以完成这些所有的工作。ShaderLab 是一种说明性语言，它使用了一些嵌套在花括号内部的语义（syntax）来描述一个 Unity Shader 文件的结构Unity Shader 的结构Unity Shader 里有多种语义，比如 Properties、SubShader、Fallback等。这些语义定义了 Unity Shader 的结构，从而帮助 Unity 分析该 Unity Shader 文件，以便进行正确的编译。名字每个 Unity Shader 文件的第一行都需要通过 Shader 语义来指定该 Unity Shader 的名字。这个名字由一个字符串来定义，例如“QwQ”。当为材质选择使用的 Unity Shader 时，这些名称就会出现在材质面板的下拉列表里。通过在字符串中添加斜杠“/”，可以控制 Unity Shader 在材质面板出现的位置。例如：Shader "wuren/QwQ" { }材质和 Unity Shader 的桥梁: Properties在编写 Shader 时，我们往往需要一些“外来”的属性，比如场景中出现了两个球，我们想要它们使用同一种光照算法，那自然而然地，就会只写一个 Unity Shader，然后绑定到那两个球上。而如果我们想要两个球分别是红色和蓝色的，那么显然，“红”和“蓝”就要作为材质传给 Shader 的一个变量，然后 Shader 再根据这个变量进行一些列的运算。从 C++、Python 那些高级编程语言的角度来看，我们可以把 Unity Shader 看成一个大函数，Properties 就是这个函数的传入形参，只不过在 Unity Shader 中我们将其称为属性。Properties 语义块的定义通常如下：Properties{ Name ("display name", PropertyType) = DefaultValue Name ("display name", PropertyType) = DefaultValue }在上面的 Properties 语义块中，Name 指的是属性名，也是我们在 Shader 代码中使用的变量名；display name 指的是出现在材质面板上的名称，也就是说，一个属性其实有两个名，一个对内使用，一个对外作描述作用；PropertyType 指的是属性的类型，DefaultValue 指的是属性的默认值。如果对应 C++ 的话，C++ 函数的1个传入形参有变量类型、变量名和默认值，就像 void print (int a = 1) 这样，那么 Properties 语义块的 PropertyType 就对应了变量类型，Name 对应了变量名，DefaultValue 对应了默认值。下面是 Properties 语义块支持的属性类型：属性类型默认值的定义语法IntnumberFloatnumberRange(min,max)numberColor(number,number,number,number)Vector(number,number,number,number)2D"defaulttexture"{}Cube"defaulttexture"{}3D"defaulttexture"{}Int、Float、Range 是数字类型的属性，它们的值就是一个单独的数字；Color 和 Vector的值是四维向量，也就是 4 个数字；2D、Cube、3D 这 3 种是纹理类型，默认值是一个字符串后跟一个花括号，字符串要么是空的，要么是内置的纹理名称，如white、bump等，花括号本来用于指定纹理属性，…

【五仁python】来给你的朋友做一个随机涩图程序
type: item
date: 2022-03-08T23:59:00+08:00
description: 本篇仅供学习，请自觉遵守我国法律法规。科学上网，从我做起。偶然发现，有一个可以返回随机涩图的API https://api.lolicon.app/#/setu，不仅可以涩涩，还涉及了比较有意思的网络知识。这就又能水一篇博客了。 本篇有涉及 json 格式的文本，但没有对 json 作详细介绍，有兴趣的可以了解一下，不知道也不影响阅读。什么是API首先要知道什么是API，我从维基百科里找了一段解释：应用程序接口（英語：application programming interface），缩写为API，是一种计算接口，它定义多个软件中介之间的交互，以及可以进行的调用（call）或请求（request）的种类，如何进行调用或发出请求，应使用的数据格式，应遵循的惯例等。简单的来说，API 就是一种看不见的工具，它会告诉你怎么去使用这个 API，以及使用 API 后会得到什么样的结果，而我们并不知道其内部是怎么实现的。就好比我们都知道按下电饭煲的按钮就能煮好一锅饭，而不需要知道电饭煲内部的电路结构、电阻电压之类的具体实现。于是，对于一个 API 来说，我们只关心两个东西：用法和结果。API 可以分很多大类，我们将要用到的属于 Web API。可以试着选择下面的一个链接，复制粘贴到浏览器上，并进行访问，看看会的到什么结果。然后重复操作，重新粘贴再访问（无重定向的情况可以直接刷新），看看得到的结果有什么不同。https://v1.hitokoto.cn/https://api.9jojo.cn/hitokotohttps://api.9jojo.cn/acgpichttps://api.9jojo.cn/acgmp3这里以 https://v1.hitokoto.cn/ 为例，我们用浏览器访问这个链接之后，得到了如下所示的内容：不难看出，这是一段符合 JSON 语法的文本，将其展开如下：{ "id":476, "uuid":"7601bbef-a8a5-4d04-929e-05d07d0e793d", "hitokoto":"君生我未生，我生君已老。 君恨我生迟，我恨君生早。", "type":"g", "from":"全唐诗续拾", "from_who":null, "creator":"Sai", "creator_uid":0, "reviewer":0, "commit_from":"web", "created_at":"1468950842", "length":25 }https://v1.hitokoto.cn/ 是 一言 的 API 接口，随便百度搜索“一言”就能搜到，这是一个可以返回随机句子的 API，前面说过，我们只关心 API 的用法和结果，那么用浏览器访问这个链接就是一种用法，所返回的 json 对象就是结果，其中包括了句子的内容、长度、来源和其他多种属性。再具体一点，用浏览器访问可以看作是一个 get 请求，那么这就可以运用到我们的程序设计语言中，比如 python，我们只需向这个链接发送一个 get 请求，然后将返回的 json 对象进行解析，就能在程序运行时得到随机句子。用 python 发 get 请求并解析 json 对象我们要使用的随机涩图 API 的接口链接是 https://api.lolicon.app/setu/v2，跟前面一样，直接用浏览器访问就能看到效果，返回的也是一个 json 对象，我们先用 python 进行第一步处理:import requests response = requests.get("https://api.lolicon.app/setu/v2") datas = response.json() print(datas)在上述代码中，我们使用了 python 的 requests 库，如果没有的话可以使用命令行 pip install requests 进行安装。库如其名，这个库的主要功能就是发送 HTTP 请求和接收其响应，requests.get()函数传入一个链接，响起发送一个 get 请求，然后返回一个 response 对象。我们用浏览器访问时已经知道返回的是一个 json 格式的文本，于是可以调用 json() 函数将其解析，该函数返回一个 python 字典，可以直接用 print() 看到其内容。这里就不得不吐槽 python 的机制了，字典里可以套列表，列表里面又能套字典，print 又不分行，里面那么多个东西，一眼看过去是很难知道怎么用下标访问的，可以使用数括号的方法，也可以格式化上图中的字典：{ 'error': '', 'data': [{ 'pid': 85163311, 'p': 0, 'uid': 9009855, 'title': '…

【五仁python】用简单代码压缩图片
type: item
date: 2022-03-04T18:23:00+08:00
description: 这是我在写博客时遇到的问题，网页加载图片的速度并不快，想要网页显示图片又不拖慢速度的话，就要限制图片的大小。我们平时为了追求高质量图片，对于自己一见钟情的图片，通常都会选择保存原图，而原图大小几乎都是以 Mb 为单位的，肯定是不能直接放到网页上的，否则就会加载非常非常非常久。于是我百度了一下“压缩图片”，出来的全是广告，又要下载这个那个的，还不如自己写个程序，于是就有了这篇文章。不过，本篇只讨论函数接口的调用，并不打算详细研究其实现，也不深入探究各种图像压缩算法，也就是说，只讲使用，不讲原理。使用 python 的原因很简单：环境配置容易，代码简洁，有成熟的函数直接供我们使用。环境准备（安装python包）我们将会用到两个库，一个是 opencv，这个是著名的计算机视觉库了，另一个是 pillow，也就是 PIL(Python Image Library)，这个也是经典的 python 图像处理库。执行下面两句命令行，利用 pip 工具安装两个工具库：pip install opencv-python==3.4.17.61pip install pillow压缩分辨率以 .jpg 图片为例，图片文件的大小由很多因素决定，其中比较重要的有两个，一个是图像的分辨率，另一个是图像的质量。图像的分辨率决定了像素的总量，而图像的质量往往跟色彩有关。我们可以从这两方面去压缩图片的大小，下面先介绍如何改分辨率，先上代码：import cv2 img = cv2.imread("img.jpg") # 读取原图 print(img.shape) # 查看原图分辨率 new_img = cv2.resize(img, (1080, 1920)) # 修改图片分辨率 print(new_img.shape) # 查看修改后的图片分辨率 cv2.imwrite("new.jpg", new_img) # 保存修改后的图片上面的代码非常简单，只有三个步骤：读取原图→改分辨率→保存新图。python 中的 opencv 在使用时叫 cv2，所以在使用 opencv 的函数前，要先写一句 import cv2 导入 opencv 库。imread 函数用于读取图片，传入的是图片文件名（后缀名记得写对），返回一个多维数组，其中存储了图片的像素值。需要注意的是，我们平时描述图片的分辨率习惯用行数×列数的形式，比如 1920×1080、1280×720 之类的都是行×列的形式，但是 opencv 读取图片后的多维数组是列数×行数的形式，所以，在调用 resize 函数来修改分辨率时，我们要小心行数和列数的位置，不要写错。resize 函数的第一个传入参数是原图，第二个参数是一个元组，以（列，行）的形式表示新图的分辨率，返回新图的多维数组。imwrite 函数用于写入图片，第一个参数是文件名，第二个参数是多维数组，其中保存了图片的像素值。上面的代码执行完毕后，会把 img.jpg 修改成 1920×1080 的分辨率然后保存在 new.jpg 中。但是这显然有一个问题，因为 1920×1080 的图片的横纵比是 16:9，但是并不是所有的图片都是 16:9 ，如果每张图片都按这样的方法压缩的话，就会改变图片的横纵比，使图片变形。想要保持图片的横纵比的话，我们可以等比例压缩，使得新图的横纵比跟原图保持一致。实现等比例压缩有两种方法，一是我们自己手动算出最终的分辨率，然后代入上面的代码，比如上面的 resize 函数调用改成 new_img = cv2.resize(img, (img.shape[0]*0.5, img.shape[1]*0.5))，这样就把图片的行数和列数都变成原来的一半。前面所说的方法，都是以固定分辨率的形式进行传参，而 resize 函数有更直接的方式让我们按比例改变图片分辨率，我们将代码修改为下面的形式：import cv2 img = cv2.imread("img.jpg") # 读取原图 print(img.shape) # 查看原图分辨率 rate = 0.5 # 修改分辨率的比例 new_img = cv2.resize(img, (0, 0), fx = rate, fy = rate) # 修改图片分辨率 print(new_img.shape) # 查看修改后的图片分辨率 cv2.imwrite("new.jpg", new_img) # 保存修改后的图片可以看出，resize 函数允许我们用 fx 参数和 fy 参数来用百分比的形式修改图片的分辨率，这两个参数会分别乘上原图的行数和列数来得到新图的分辨率，当给 fx 和 fy 赋值之后，前面的第二项参数就失效了，所以可以直接用 (0,0) 来传参。压缩图片质量jpg 图片是…

【五仁C++】静态库？动态库？.a.so.lib.dll的生成与使用(g++版)
type: item
date: 2022-03-03T19:54:00+08:00
description: 如果你的计算机用的是 windows 系统，那么库文件将遍布电脑的每一个角落。在 windows 上安装的任何一款软件，其中都一定包含有大量的 .dll 文件（下图为原神目录的部分截图）。.dll 文件称为动态链接库，库包含了写好的，现有的，成熟的，可以复用的代码。作为程序员，需要懂得什么是库，如何生成和使用库，下面让我来一一讲解。g++编译基础也许你用过若干种 C++ 编译器，比如 Dev-C++，Visual Studio，这些编译器可以帮我们把写好的代码 .cpp 编译成可执行文件 .exe ，但事实上编译器只是帮我们输入了一句或多句命令，而这些命令就是我们理解文件编译和执行的关键。我们将一句命令称为命令行，命令行可以在很多地方上执行，比如命令提示符cmd，比如VS-code的终端。如果想要把自己写好的 main.cpp 文件用命令行编译的话，只需要输入下列语句就可以了（记得切换到 main.cpp 的目录）:g++ main.cpp -o main执行上面这条语句之后，就会生成 main.exe 文件，这跟点击 Dev-C++ 的“编译”按钮是一样的。对于这条语句来说，g++ 表示执行 g++.exe 文件，它是用来编译 c++ 文件的；main.cpp 是要被编译的文件；-o 是一个选项，它可以指定生成的文件的名字，后面跟着的 main 就是指定的名字，g++ 会自动补全后面的 .exe，所以最后生成 main.exe，当然写 -o main.exe 也没问题。想要执行 main.exe ，既可以双击执行，也可以用命令行 ./main 执行。不过，相信大家多多少少有听过程序编译的一些规范和方法。从代码文件 .cpp 到可执行文件 .exe 其实会经过多个步骤，也会产生多个中间文件。我们重点考虑两个大步骤——编译和链接。无论是C、C++，首先要把源文件编译成中间代码文件，因为 cpp 文件里面是字符，人能看懂，计算机可看不懂，首先要生成一个对应该 cpp 的能让计算机看得懂的二进制文件，即 Object File ，这个动作叫做编译（compile），然后再把大量的 Object File 合成可执行文件，这个动作叫作链接（link）。 于是，上面的g++ main.cpp -o main就可以拆成两句命令：g++ -c main.cpp和g++ main.o -o main这两句命令分别对应了编译和链接，前者的-c选项就是让g++生成中间代码文件 main.o，后者则是把多个 .o 文件链接起来，这里只有一个 main.o，下文的介绍将会出现 3 个 .o 文件。上面这两句命令是本文的核心，全文都会围绕这两句命令进行扩展。后缀的解释正常来说，我们将会接触到 6 种文件的后缀：.o，.a，.so，.obj，.lib，.dll，但其实只有 3 种文件，分别是中间代码文件、静态库文件、动态库文件，只不过这 3 种文件在 windows 系统和 linux 系统上的后缀不一样而已，在 windows 系统上可以兼容所有的 6 种后缀格式，但 linux 系统上只能用 3 种，这 6 种后缀的解释如下：|windows|linux|文件类型||:-:|:-:|:-:||.obj|.o|中间代码文件||.lib|.a|静态库文件||.dll|.so|动态库文件|简述静态库和动态库的异同静态库和动态库都是“库”，库指的是多个中间代码文件的集合，也就是说，一个库包含多个 .o 或 .obj 文件，这些 .o 文件里面有可执行的代码。静态库和动态库的区别在于链接步骤，静态库会在链接时全身心地参与构成可执行文件，链接静态库其实从某种意义上来说也是一种粘贴复制，因为静态库被链接后库就直接嵌入可执行文件中了，整个库的所有代码都会共同构成可执行文件 .exe，假如使用 1 个库生成了 100 个可执行文件，那么就会产生这个库的 100 个副本，这显然非常浪费空间，并且难以修改：一旦发现这个库中的某段代码有 bug，那么那 100 个副本都要修改，即重新生成所有的文件，非常麻烦。而动态库是在程序运行时被链接的，而只在可执行文件中留下接口的信息（比如函数名），当可执行文件调用接口时，会执行动态库中的代码。这就好像超人给每个人留下了一张名片，上面有电话号码，当有人需要超人的帮助时，就拨打电话呼叫超人，这样既能让每个人都能随时得到超人的帮助，也不需要复制很多个超人，而大家只需要在自己家里放留一张名片，而不是留一个超人，非常的节省空间。静态库和动态库各有优劣，复制多个副本不见得是坏事，比如你给不懂计算机的同学写一个很简单的程序，那使用静态库的话就可以只发一份可执行文件 .exe 到他电脑上，但如果使用动态库的话还需要把相关的动态库文件发过去，这是其中一…