OpenGL入门最后一章观察矩阵(照相机)
前面的一篇文章笔者向大家介绍了模型变化矩阵,投影矩阵。现在只剩下最后一个观察矩阵没有和大家讲了。此片文章就为大家介绍OpenGL入门篇的最后一个内容。
观察矩阵
前面的篇章当中,我们看到了即使没有观察矩阵,我们也能对绘制出来的模型有一个很好的控制,那么观察矩阵存在的意义又是什么了?那么我们现在来想象一下这样一件事,我们的屏幕上绘制了特别多的方块,现在我们要把它们全部向屏幕的右边移动一定的距离,如果是前面的做法的话所有的模型都要再乘上一个向右平移的矩阵,这样做完全没有问题,只是这会让程序的效率变得很差,而且你的GPU除了计算一下像素颜色之外基本上就没有做其他任何事,把绝大部分的任务都交给了CPU,这样的任务分配是不合理的,所以我们需要上传一个观察矩阵ViewMatrix给GPU,让他把所有的顶点按照某一个方向进行平移,这样我们的任务就合理分配了。(什么样的任务应该交给CPU,什么样的任务又交给GPU这里也有不少东西需要研究)
看过资料的朋友们都知道,在OpenGL当中想用创建一个观察矩阵只需要使用glm::lookAt函数即可
glm::lookAt(m_Position, m_Forward, m_Up);
//glm::vec3 m_Position 照相机的位置
//glm::vec3 m_Forward 观察的位置
//glm::vec3 m_Up 相机屏幕的上方的方向
这样我们就得到了一个观察矩阵了,看起来也没什么好说的对不对。但是困难的往往是处理与之相关的数学问题,前面说我们要让屏幕上的所有的方块全部向右移动一段距离,那么我们应该怎么设置这个观察矩阵了?直接给答案
glm::vec3 m_Position(-0.3f,0.0f,3.0f)
glm::vec3 m_Forward(-0.3f,0.0f,0.0f);
glm::vec3 m_Up(0.0f,1.0f,0.0f);glm::lookAt(m_Position, m_Forward, m_Up);
可能有的朋友们就要问了,相机为什么要往左边移动,并且看向左边了?要回答这个问题也很简单,大家现在把手机拿出来打开照相机,然后手机屏幕与电脑屏幕保持水平,现在把手机水平向左开始移动,你就会发现手机屏幕中的物体朝着反方向进行移动了,如果再把手机对准刚才位置,你就会发现所有的物体都行了旋转拉绳,这当然不是我们想要的结果,所以我们要让手机继续朝向正前方。观察矩阵和我们的照相机的工作原理一摸一样,所以观察矩阵也可以叫作相机矩阵。
制作一个可以观察物体的矩阵
观察矩阵其实介绍到这里也就算是结束了,我们现在来做一个可以实际使用的相机。比如说我现在制作了一个立方体,每一个面的颜色都不一样,它刚被绘制出来的时候我只能看见一两个面,但是我想观察其他的面,如果说我去修改模型让它自己转过来,这也有点太蠢了!所以我们要做一个进行鼠标控制的照相机。
我的打算是,让照相机沿着一个球面进行运动,鼠标坐标的变化来确定照相机在球体坐标上的夹角。
鼠标x方向上的偏移量决定角的大小,鼠标y方向上的偏移量决定
角的大小,有了理论的基础过后我们看一下具体如何实现。
照相机类:
Camera.h
#pragma once
#include<glm/glm.hpp>class Camera {
public:Camera(glm::vec3 position = glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f), float yam = 0.0f, float pitch =90.0f);Camera(float posX, float posY, float posZ, float upX, float upY, float upZ, float yaw, float pitch);glm::mat4 GetViewMatrix();void ProcessMouseMovement(float xoffset, float yoffset, bool constrainPitch = true);void ProcessMouseScroll(float yoffset);inline float GetCameraZoom() const { return m_Zoom; }
private:void UpdateCameraVectors();private:glm::vec3 m_Position, m_Front, m_Up, m_Right, m_WorldUp;float m_Yaw, m_Pitch;float m_MouseSensitivity;float m_Zoom;
};
Camera.cpp
#include<glm/gtc/matrix_transform.hpp>
#include<iostream>#include"Camera.h"Camera::Camera(glm::vec3 position ,glm::vec3 up,float yam,float pitch):m_Front(glm::vec3(0.0f,0.0f,-1.0f)),m_MouseSensitivity(0.1f),m_Zoom(45.0f) {m_Position = position;m_WorldUp = up;m_Yaw = yam;m_Pitch = pitch;UpdateCameraVectors();
}Camera::Camera(float posX, float posY, float posZ, float upX, float upY, float upZ, float yaw, float pitch):m_Front(glm::vec3(0.0f,0.0f,-1.0f)),m_MouseSensitivity(0.1f),m_Zoom(45.0f) {m_Position = glm::vec3(posX, posY, posZ);m_WorldUp = glm::vec3(upX, upY, upZ);m_Yaw = yaw;m_Pitch = pitch;UpdateCameraVectors();
}glm::mat4 Camera::GetViewMatrix() {return glm::lookAt(m_Position, glm::vec3(0.0f, 0.0f, 0.0f), m_Up);
}void Camera::ProcessMouseMovement(float xoffset, float yoffset, bool constrainPitch){xoffset *= m_MouseSensitivity;yoffset *= m_MouseSensitivity;m_Yaw += xoffset;m_Pitch += yoffset;if (constrainPitch) {m_Pitch = m_Pitch > 179.0f ? 179.0f : m_Pitch;m_Pitch = m_Pitch < 0.1f ? 0.1f : m_Pitch;}UpdateCameraVectors();
}void Camera::ProcessMouseScroll(float yoffset) {m_Zoom -= yoffset;m_Zoom = m_Zoom < 1.0f ? 1.0f : m_Zoom;m_Zoom = m_Zoom > 45.0f ? 45.0f : m_Zoom;
}void Camera::UpdateCameraVectors() {glm::vec3 position(0.0f,0.0f,0.0f);position.x = 5.0f * sinf(glm::radians(m_Yaw)) * sinf(glm::radians(m_Pitch));position.y = 5.0f * cosf(glm::radians(m_Pitch));position.z = 5.0f * cosf(glm::radians(m_Yaw)) * sinf(glm::radians(m_Pitch));m_Front = glm::normalize(-position);m_Position = position;m_Right = glm::normalize(glm::cross(m_Front, m_WorldUp));m_Up = glm::normalize(glm::cross(m_Right, m_Front));
}
除此以外,我们还需要处理鼠标输入函数
void mouse_callback(GLFWwindow* window, double xposIn, double yposIn) {static float lastX = 320.0f, lastY = 240.0f;static bool firstMouse = true;float xpos = static_cast<float>(xposIn);float ypos = static_cast<float>(yposIn);if (firstMouse) {lastX = xpos;lastY = ypos;firstMouse = false;}float xoffset = xpos - lastX;float yoffset = lastY - ypos;lastX = xpos, lastY = ypos;camera.ProcessMouseMovement(xoffset, yoffset);
}void scroll_callback(GLFWwindow* window, double xoffsetIn, double yoffsetIn) {camera.ProcessMouseScroll(static_cast<float>(yoffsetIn));
}void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) {if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)run = false;
}
好的主函数,贴在下面,我们就得到了一个可以绕着物体圆转的照相了,因为窗口隐藏了光标,想要推出的画按Esc键即可。
#include<glad/glad.h>
#include<GLFW/glfw3.h>#include<iostream>
#include<glm/gtc/matrix_transform.hpp>#include"Shader.h"
#include"Camera.h"static Camera camera(glm::vec3(0.0f, 0.0f, 5.0f));
static bool run = true;
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods);
void mouse_callback(GLFWwindow* window, double xposIn, double yposIn);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);int main() {glfwInit();GLFWwindow* window = glfwCreateWindow(640, 480, "Triangles", NULL, NULL);glfwMakeContextCurrent(window);glfwSetCursorPosCallback(window, mouse_callback);glfwSetScrollCallback(window, scroll_callback);glfwSetKeyCallback(window, key_callback);glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);//需要初始化GLADif (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {std::cout << "Failed to initialize GLAD" << std::endl;return -1;}float vertexes[] = {//front surface-0.5f, -0.5f, 0.5f, 1.0f, 1.0f,0.588f,0.2784f, //00.5f, -0.5f, 0.5f, 1.0f, 1.0f,0.588f,0.2784f, //10.5f, 0.5f, 0.5f, 1.0f, 1.0f,0.588f,0.2784f, //2-0.5f, 0.5f, 0.5f, 1.0f, 1.0f,0.588f,0.2784f, //3//back surface-0.5f, -0.5f, -0.5f, 1.0f, 0.933f,0.9098f,0.6666f, //40.5f, -0.5f, -0.5f, 1.0f, 0.933f,0.9098f,0.6666f, //50.5f, 0.5f, -0.5f, 1.0f, 0.933f,0.9098f,0.6666f, //6-0.5f, 0.5f, -0.5f, 1.0f, 0.933f,0.9098f,0.6666f, //7//up surface-0.5f, 0.5f, 0.5f, 1.0f, 0.0f,0.749f,1.0f, //80.5f, 0.5f, 0.5f, 1.0f, 0.0f,0.749f,1.0f, //90.5f, 0.5f, -0.5f, 1.0f, 0.0f,0.749f,1.0f, //10-0.5f, 0.5f, -0.5f, 1.0f, 0.0f,0.749f,1.0f, //11//down surface-0.5f, -0.5f, 0.5f, 1.0f, 0.498f,1.0f,0.0f, //120.5f, -0.5f, 0.5f, 1.0f, 0.498f,1.0f,0.0f, //130.5f, -0.5f, -0.5f, 1.0f, 0.498f,1.0f,0.0f, //14-0.5f, -0.5f, -0.5f, 1.0f, 0.498f,1.0f,0.0f, //15//left surface-0.5f, -0.5f, -0.5f, 1.0f, 0.5f,0.5f,0.145f, //16-0.5f, -0.5f, 0.5f, 1.0f, 0.5f,0.5f,0.145f, //17-0.5f, 0.5f, 0.5f, 1.0f, 0.5f,0.5f,0.145f, //18-0.5f, 0.5f, -0.5f, 1.0f, 0.5f,0.5f,0.145f, //19//right surface0.5f, -0.5f, -0.5f, 1.0f, 1.0f,0.8941f,0.7686f, //200.5f, -0.5f, 0.5f, 1.0f, 1.0f,0.8941f,0.7686f, //210.5f, 0.5f, 0.5f, 1.0f, 1.0f,0.8941f,0.7686f, //220.5f, 0.5f, -0.5f, 1.0f, 1.0f,0.8941f,0.7686f //24}; unsigned int indexes[] = {//front surface0,1,2,2,3,0,//back surface4,5,6,6,7,4,//up surface8,9,10,10,11,8,//down surface12,13,14,14,15,12,//left surface16,17,18,18,19,16,//right surface20,21,22,22,23,20};glEnable(GL_DEPTH_TEST);unsigned int buffer = 0, vertexArray = 0, indexBuffer = 0;glCreateVertexArrays(1, &vertexArray);glBindVertexArray(vertexArray);glCreateBuffers(1, &buffer);glBindBuffer(GL_ARRAY_BUFFER, buffer);glBufferData(GL_ARRAY_BUFFER, sizeof(vertexes), vertexes, GL_STATIC_DRAW);glEnableVertexAttribArray(0);glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 7 * sizeof(float), NULL);glEnableVertexAttribArray(1);glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 7 * sizeof(float), (const void*)(4 * sizeof(float)));glCreateBuffers(1, &indexBuffer);glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indexes), indexes, GL_STATIC_DRAW);Shader* pShader = new Shader("assets/shaders/TextureShader.glsl");while (!glfwWindowShouldClose(window) && run) {glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);glm::mat4 project = glm::perspective(glm::radians(camera.GetCameraZoom()), 640.0f / 480.0f, 0.1f, 100.0f);glm::mat4 view = camera.GetViewMatrix();glm::mat4 ViewProject = project * view;pShader->Bind();pShader->UploadUniformat4("u_ViewProject", ViewProject);glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, NULL);glfwSwapBuffers(window);glfwPollEvents();}delete pShader;glfwDestroyWindow(window);glfwTerminate();
}void mouse_callback(GLFWwindow* window, double xposIn, double yposIn) {static float lastX = 320.0f, lastY = 240.0f;static bool firstMouse = true;float xpos = static_cast<float>(xposIn);float ypos = static_cast<float>(yposIn);if (firstMouse) {lastX = xpos;lastY = ypos;firstMouse = false;}float xoffset = xpos - lastX;float yoffset = lastY - ypos;lastX = xpos, lastY = ypos;camera.ProcessMouseMovement(xoffset, yoffset);
}void scroll_callback(GLFWwindow* window, double xoffsetIn, double yoffsetIn) {camera.ProcessMouseScroll(static_cast<float>(yoffsetIn));
}void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) {if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)run = false;
}
注意
你可能注意到了,相机是不能绕过Y轴顶点的,但这里要声明的是并不是笔者的代码写错了,这是因为,如果绕道Y轴顶点,那么你看向的方向和世界向上的方向就平行了,这两个向量叉乘的结果是一个0向量,也就是根本没办法计算相机的右侧是那一边,相机的上边也同样没办法计算。这是纯数学问题导致的,纯数学问题也是最难解决的问题,现在多软件的相机使用都是四元数来解决这个问,有兴趣的朋友可以去查找相关资料。
总结
此片文章看完OpenGL入门基础篇已经写完了,相信你也可以做一个像样的游戏出来了,比如以前红白机上的一些游戏,可能觉得很Low,但凡事都要一步一个脚印,游戏也是一路发展过来的,没有多少人生来就特别牛。笔者在这里感谢大家的支持。
相关文章:
OpenGL入门最后一章观察矩阵(照相机)
前面的一篇文章笔者向大家介绍了模型变化矩阵,投影矩阵。现在只剩下最后一个观察矩阵没有和大家讲了。此片文章就为大家介绍OpenGL入门篇的最后一个内容。 观察矩阵 前面的篇章当中,我们看到了即使没有观察矩阵,我们也能对绘制出来的模型有一…...

ES6中定义私有属性详解
在ES6中,定义私有属性的方式相对传统的JavaScript有所不同。ES6并没有提供直接的语法来定义私有属性,但可以通过几种方法间接实现私有属性。 1. 使用Symbol来模拟私有属性 Symbol是一种新的数据类型,可以作为对象的键,并且它的值…...

工业5G路由器让无人机数据传输 “飞” 起来
无人机上搭载5G通信模块,该模块与工业5G路由器通过5G网络建立连接。无人机的飞控系统、传感器以及摄像头等设备采集到的数据,如飞行姿态、高度、速度、环境图像、温度湿度等,经过编码、加密、调制等处理后转换为适合5G网络传输的信号形式。 …...

面试经典150题——滑动窗口
文章目录 1、长度最小的子数组1.1 题目链接1.2 题目描述1.3 解题代码1.4 解题思路 2、无重复字符的最长子串2.1 题目链接2.2 题目描述2.3 解题代码2.4 解题思路 3、串联所有单词的子串3.1 题目链接3.2 题目描述3.3 解题代码3.4 解题思路 4、最小覆盖子串4.1 题目链接4.2 题目描…...

MiFlash 线刷工具下载合集
MiFlash 线刷工具下载合集 MiFlash 线刷工具下载合集 – MIUI历史版本相较于小米助手的刷机功能,线刷还是偏好使用 MiFlash。特点是界面简单纯粹,有自定义高级选项,可以选择刷机不上 BL 锁,自定义刷机脚本,EDL 刷机模…...

【MySQL高级】第1-4章
第1章 存储过程 1.1 什么是存储过程? 存储过程可称为过程化SQL语言,是在普通SQL语句的基础上增加了编程语言的特点,把数据操作语句(DML)和查询语句(DQL)组织在过程化代码中,通过逻辑判断、循环等操作实现复杂计算的程序语言。 换…...

课程设计项目之基于Python实现围棋游戏代码
项目介绍 游戏进去默认为九路玩法,当然也可以选择十三路或是十九路玩法 使用pycharam打开项目,pip安装模块并引用,然后运行即可, 代码每行都有详细的注释,可以做课程设计或者毕业设计项目参考 效果预览 源码下载 h…...

uni-app tab 双击事件监听
1、data中定义属性,用于临时记录点击次数 tabClick: {touchNum: 0 },2、添加页面事件监听方法 onTabItemTap(e) {this.tabClick.touchNumsetTimeout(()>{if(this.tabClick.touchNum > 2){// 双击执行代码区}this.tabClick.touchNum 0}, 250) },个人博客&am…...

如何在Maxscript脚本中检查磁盘可用空间?
在我们实际工作中,有时需要在工作开始之前检查磁盘的可用空间,比如渲染。 当然,我们可以人工很容易查看电脑中各个磁盘的空间使用情况,但是,如果是Maxscript插件完成的工作,那么如何才能实现其工作之前对磁…...

pytorch梯度上下文管理器介绍
PyTorch 提供了多种梯度上下文管理器,用于控制自动梯度计算 (autograd) 的行为。这些管理器在训练、推理和特殊需求场景中非常有用,可以通过显式地启用或禁用梯度计算,优化性能和内存使用。 主要梯度上下文管理器 torch.no_grad(): 功能&am…...

Redis Stream:实时数据处理的高效解决方案
Redis Stream:实时数据处理的高效解决方案 引言 在当今这个数据驱动的时代,实时数据处理对于各种应用场景都至关重要。Redis,作为一个高性能的键值存储系统,自然也紧跟这一趋势,推出了Redis Stream——一种用于处理实…...

使用交换机构建简单局域网
创建交换机SW-1 交换机接口规划 序号交换机名接口连接设备接口类型1SW-1Ethernet 0/0/1Host-1默认2SW-1Ethernet 0/0/2Host-2默认3SW-1Ethernet 0/0/5Host-3默认4SW-1Ethernet 0/0/6Host-4默认 主机IP规划 Host-1:192.168.64.11/24,接入SW-1 Ethernet…...

基于MATLAB的冰箱水果保鲜识别系统
摘要:本作品旨在研究和实现基于MATLAB软件的冰箱水果保鲜识别系统,针对多种常见水果混合的图像进行处理和识别。首先,根据水果与背景的差异选择合适的阈值,对图像进行去噪和对比度增强,然后进行二值化处理。接下来&…...

Flink源码解析之:Flink On Yarn模式任务提交部署过程解析
Flink源码解析之:Flink On Yarn模式任务提交部署过程解析 一、Flink on Yarn部署模式概述 Apache Hadoop YARN 在许多数据处理框架中都很流行。 Flink 服务提交给 YARN 的 ResourceManager,后者会在 YARN NodeManagers 管理的机器上生成容器。 Flink 将…...

吊舱激光测距核心技术详解!
一、核心技术 吊舱激光测距的核心技术主要体现在激光发射与接收、信号处理与距离计算、以及数据校正与优化等方面。 激光发射与接收: 激光发射器:产生经过调制的激光束,该激光束具有特定的频率和波形。这些激光束被投射到目标物体上。 光…...

[ZJCTF 2019]NiZhuanSiWei
检查通过 file_get_contents 函数读取 $text 变量指定的文件内容是否等于字符串 "welcome to the zjctf"。 测试了一下直接传参,然后进入下一阶段,通过php伪协议读取useless.php发现不行,我们使用data协议将其输入进去试试 读取到…...

Kafka配置公网或NLB访问(TCP代理)
这套配置适用于TCP代理和公网访问 分几种场景,正常来说我们直接使用kafka IP地址访问就行,考虑到网络架构和环境安全,需要使用公网或代理访问kafka时就需要对kafka进行一些额外配置 EXTERNAL这个地址需要监听本地地址,之后kafka…...

大模型推理:vllm多机多卡分布式本地部署
文章目录 1、vLLM分布式部署 docker镜像构建通信环境配置 2、其他大模型部署工具3、问题记录参考文献 单台机器GPU资源不足以执行推理任务时,一个方法是模型蒸馏量化,结果就是会牺牲些效果。另一种方式是采用多台机器多个GPU进行推理,资源不…...

clickhouse-backup配置及使用(Linux)
一、下载地址 Releases Altinity/clickhouse-backup GitHub 二、上传到服务器解压安装 自行上传至服务器,解压命令: tar xvf clickhouse-backup-linux-amd64.tar.gz 三、创建软连接 sudo ln -sv build/linux/amd64/clickhouse-backup /usr/local/bin/…...

【YashanDB知识库】启动yasom时报错:sqlite connection error
本文内容来自YashanDB官网,原文内容请见 https://www.yashandb.com/newsinfo/7817893.html?templateId1718516 【标题】启动yasom时报错:sqlite connection error 【问题分类】安装部署 【关键字】错误码sqlite3.Error、yasom启动失败、共享集群 、u…...

JAVA学习笔记_Redis进阶
文章目录 初识redisredis简介windows启动redis服务器linux启动redis服务器图形用户界面客户端RDM redis命令常用数据类型特殊类型字符串操作命令Key的层级格式哈希操作命令列表操作命令集合操作命令有序集合操作命令通用命令 java客户端Jedisjedis连接池SpringDataRedis序列化手…...

LabVIEW手部运动机能实验系统
在运动科学、人机交互和康复训练等领域,手部运动功能的研究具有重要的应用价值。开发了一个基于LabVIEW的手部运动机能实验系统设计,该系统利用力量作为关键参数,通过实时数据采集和反馈帮助受试者完成精确的手部动作,同时为研究人…...

SpringBoot的注解@SpringBootApplication及自动装配
目录 一、pom文件 二、SpringBootApplication注解 1.SpringBootApplication 2.Configuration 3.这个启动类也可以被看成是一个配置类 三、SpringBootApplication注解2 1.SpringBootConfiguration 2.Configuration 3.EnableAutoConfiguration!!&…...

STM32学习之EXTI外部中断(以对外式红外传感器 / 旋转编码器为例)
中断:在主程序运行过程中,出现了特定的中断触发条件(中断源),使得CPU暂停当前正在运行的程序,转而去处理中断程序处理完成后又返回原来被暂停的位置继续运行 中断优先级:当有多个中断源同时申请中断时,CPU会根据中断源的轻重缓急…...

数字赋能:制造企业如何靠“数字能力”实现可持续“超车”?
如今,制造业数字化转型可是个热门话题,全球都在积极推进。我国更是出台了一系列给力的政策来助力制造业数字化转型,像《中国制造 2025》就明确提出要加快制造业数字化、网络化、智能化发展,各省市也纷纷响应,从资金、税…...

.NET在中国的就业前景:开源与跨平台带来的新机遇
随着技术的不断发展和市场需求的变化,.NET在中国的就业前景正变得愈加广阔。尤其是在开源和跨平台的推动下,越来越多的中国中小型企业选择了.NET技术作为其开发平台,进一步提升了.NET技术人才的市场需求。尽管在中国市场,.NET的市…...

【基础篇】一、MySQL数据库基础知识
文章目录 Ⅰ. 什么是数据库1、普通文件的缺点2、数据库的概念3、主流数据库4、MySQL Ⅱ. MySQL中客户端、服务端、数据库的关系Ⅲ. 见一见数据库1、数据库文件存放的位置2、创建数据库3、使用数据库4、创建数据库表结构5、表中插入数据6、查询表中数据7、数据的存储逻辑 &#…...

预训练深度双向 Transformers 做语言理解
大家读完觉得有意义记得关注和点赞!!! 与 GPT 一样,BERT 也基于 transformer 架构, 从诞生时间来说,它位于 GPT-1 和 GPT-2 之间,是有代表性的现代 transformer 之一, 现在仍然在很多…...

理解js闭包,原型,原型链
闭包 一个函数嵌套了另一个函数,内部函数引用了外部函数的变量,这样,当外部函数在执行环境中执行完毕后,因为某个变量被引用就无法被GC回收,导致这个变量会一直保持在内存中不能被释放。因此可以用来封装一个私有变量…...

linux tar 文件解压压缩
文件压缩和解压 tar -c: 建立压缩档案 -x:解压 -t:查看内容 -r:向压缩归档文件末尾追加文件 -u:更新原压缩包中的文件 -z:有gzip属性的 -j:有bz2属性的 -v:显示所有过程 -O:…...