LearnOpenGL-入门-你好,三角形

news2024/11/27 16:45:20

本人刚学OpenGL不久且自学,文中定有代码、术语等错误,欢迎指正

我写的项目地址:https://github.com/liujianjie/LearnOpenGLProject

LearnOpenGL中文官网:https://learnopengl-cn.github.io/

文章目录

  • 图形渲染管线
    • 基本介绍
    • 着色器阶段
  • 顶点输入
  • 着色器代码流程
  • 链接顶点属性
  • 顶点数组对象VAO
  • 绘制三角形
  • 元素(索引)缓冲对象EBO
  • 小结
    • 草稿图
    • 重复重要的流程
      • 着色器流程
      • 绘制流程

图形渲染管线

基本介绍

  • 功能

    将3D坐标变为2D坐标

    将2D坐标转换为实际的有颜色的像素

  • 图形渲染管线与着色器

    图形渲染管线分为多个阶段,多个阶段对应多个自己特定的函数(小程序),在各自特定的函数可并行执行调用显卡的成千上万的核心,这些小程序被称为着色器

着色器阶段

  • 顶点数据

    数组的形式传递3个3D坐标作为图形渲染管线的输入,用来表示一个三角形,这个数组叫做顶点数据(Vertex Data)

  • 顶点着色器

    • 它把一个单独的顶点作为输入
    • 主要的目的是把3D坐标转为另一种3D坐标
  • 形状(图元)装配

    将顶点着色器输出的所有顶点作为输入(如果是GL_POINTS,那么就是一个顶点),并所有的点装配成指定图元的形状

  • 几何着色器

    • 把图元形式的一系列顶点的集合作为输入
    • 它可以通过产生新顶点构造出新的(或是其它的)图元来生成其他形状
  • 光栅化

    • 把图元映射为最终屏幕上相应的像素,生成供片段着色器(Fragment Shader)使用的片段(Fragment)
    • 在片段着色器运行之前会执行裁切(Clipping)。裁切会丢弃超出你的视图以外的所有像素,用来提升执行效率。
  • 片段着色器

    片段着色器的主要目的是计算一个像素的最终颜色

  • Alpha测试和混合

    • 检测片段的对应的深度(和模板(Stencil))值(后面会讲),用它们来判断这个像素是其它物体的前面还是后面,决定是否应该丢弃

    • 检查alpha值(alpha值定义了一个物体的透明度)并对物体进行混合(Blend),可以认为改变片段的颜色

在现代OpenGL中,我们必须定义至少一个顶点着色器和一个片段着色器(因为GPU中没有默认的顶点/片段着色器)。

顶点输入

  • 标准化设备坐标

    • 在顶点着色器中处理过,它们就应该是标准化设备坐标,x、y和z值在-1.0到1.0的一小段空间

    • 图示

  • 顶点数据

    float vertices[] = {
        -0.5f, -0.5f, 0.0f,
         0.5f, -0.5f, 0.0f,
         0.0f,  0.5f, 0.0f
    };
    

    一个在CPU内存的数组

  • 顶点缓冲对象VBO

    由于CPU内存的顶点数据需要传入GPU内存中,就需要在GPU内存中存储同样大小的顶点数据,而顶点缓冲对象管理这个在GPU上的内存(有点模糊这个概念)

    unsigned int VBO;
    // 1.在GPU上生成一个缓冲,返回ID
    glGenBuffers(1, &VBO);
    // 2.绑定缓冲
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    // 3.CPU内存的顶点数据复制到GPU内存中
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    

着色器代码流程

  • 创建一个着色器对象

    用glCreateShader创建这个着色器

    unsigned int vertexShader;
    vertexShader = glCreateShader(GL_VERTEX_SHADER);
    
  • 着色器源码附加到着色器对象

    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
    
  • 编译着色器

    glCompileShader(vertexShader);
    

    编译完顶点着色器后,片段着色器同样这样编译

  • 两个着色器对象链接到一个用来渲染的着色器程序

    • 创建一个着色器程序对象

      unsigned int shaderProgram;
      shaderProgram = glCreateProgram();
      
    • 编译的着色器附加着色器程序对象

      glAttachShader(shaderProgram, vertexShader);
      glAttachShader(shaderProgram, fragmentShader);
      
    • glLinkProgram链接着色器程序对象

      glLinkProgram(shaderProgram);
      
  • 使用着色器程序

    glUseProgram(shaderProgram);
    
  • 着色器对象链接到着色器程序对象以后,删除着色器对象

    glDeleteShader(vertexShader); 
    glDeleteShader(fragmentShader);
    
  • 另外

    在编译着色器对象和链接时可以看是否成功

    int  success;
    char infoLog[512];
    glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
    if(!success)
    {
        glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
    }
    

链接顶点属性

  • 我们必须手动指定顶点输入数据的哪一个部分对应顶点着色器哪一个顶点属性

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    // 若第一个参数为0,对应顶点着色器的layout(location = 0) in vec3 a_Position;
    // 若第一个参数为1,对应顶点着色器的layout(location = 1) in vec4 a_Color;
    glEnableVertexAttribArray(0);// 代表启用顶点着色器location=0的输入
    

    设置好OpenGL如何解释顶点数据,但是设置的顶点数据来源于上一次将顶点缓冲对象绑定的那个VBO。

    glVertexAttribPointer参数:

    • 1:要配置的顶点属性

      layout(location = 0)

    • 2:顶点属性的大小

    • 3:数据的类型

    • 4:是否希望数据被标准化

      GL_TRUE:所有数据都会被映射到0(对于有符号型signed数据是-1)到1之间

    • 5:步长

    • 6:偏移量

  • 由此绘制的代码

    // 省略创建缓冲
    // 0. CPU内存的顶点数据复制到GPU内存中
    glBindBuffer(GL_ARRAY_BUFFER, VBO1);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    // 1. 设置顶点属性指针
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    // 2. 当我们渲染一个物体时要使用着色器程序
    glUseProgram(shaderProgram);
    // 3. 绘制物体
    someOpenGLFunctionThatDrawsOurTriangle();
    

    若有第二个不同的物体(不同的顶点数据)需要渲染

    又要写一遍这个代码
    // 0. CPU内存的顶点数据复制到GPU内存中
    glBindBuffer(GL_ARRAY_BUFFER, VBO2);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    // 1. 设置顶点属性指针
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    // 2. 当我们渲染一个物体时要使用着色器程序
    glUseProgram(shaderProgram);
    // 3. 绘制物体
    someOpenGLFunctionThatDrawsOurTriangle();
    
  • 缺点

    由上可看出,有多少个物体,就得重复写绑定的顶点缓冲区、顶点属性指针,属实麻烦,则应该使用顶点数组对象VAO

顶点数组对象VAO

  • 使用这个有什么用

    原话:

    • 当配置顶点属性指针时,你只需要将那些调用执行一次,之后再绘制物体的时候只需要绑定相应的VAO就行了。

    • 这使在不同顶点数据和属性配置之间切换变得非常简单,只需要绑定不同的VAO就行了。刚刚设置的所有状态都将存储在VAO中

    我认为:

    • 顶点数组对象VAO与顶点缓冲对象VBO一对多,一个VAO的顶点属性指针可以来源于多个不同的顶点缓冲对象,在初始化时VAO设置好顶点属性指针后,绘制的时候绑定对应的VAO就行,不需要写绑定顶点缓冲与设置顶点属性指针的代码了,可以在绘制时无关初始化设置状态的代码。
  • 代码

    // ..:: 初始化代码(只运行一次 (除非你的物体频繁改变)) :: ..
    unsigned int VAO;
    glGenVertexArrays(1, &VAO);
    // 1. 绑定VAO
    glBindVertexArray(VAO);
    // 2. 把顶点数组复制到缓冲中供OpenGL使用
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    // 3. 设置顶点属性指针
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    
    [...]
    
    // ..:: 绘制代码(渲染循环中) :: ..
    // 4. 绘制物体
    glUseProgram(shaderProgram);
    glBindVertexArray(VAO);
    someOpenGLFunctionThatDrawsOurTriangle();
    

    若有第二个物体要绘制,跟上一样,在初始化部分绑定相应顶点缓冲对象设置顶点属性后,在渲染绘制代码只要切换VAO就行

    // 4. 绘制物体
    glUseProgram(shaderProgram);
    glBindVertexArray(VAO);
    someOpenGLFunctionThatDrawsOurTriangle();
    
    glUseProgram(shaderProgram2);
    glBindVertexArray(VAO2);
    someOpenGLFunctionThatDrawsOurTriangle();
    
  • 图示

    如图:VAO与VBO一一对应,但实际上VAO的顶点属性指针可以来源于多个不同的顶点缓冲VBO,一般是一一对应

绘制三角形

  • 代码

    glsl

    version 330 core
    layout (location = 0) in vec3 aPos;
    void main()
    {
       gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
    }
    
    #version 330 core
    out vec4 FragColor;
    void main()
    {
       FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
    }
    

    cpp

    // 0.顶点数据
    float vertices[] = {
        -0.5f, -0.5f, 0.0f, // left  
        0.5f, -0.5f, 0.0f, // right 
        0.0f,  0.5f, 0.0f  // top   
    };
    unsigned int VBO, VAO;
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);
    // bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
    // 1. 绑定顶点数组对象
    glBindVertexArray(VAO);
    // 2. 把我们的CPU的顶点数据复制到GPU顶点缓冲中,供OpenGL使用
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    // 3. 设定顶点属性指针,来解释顶点缓冲中的顶点属性布局
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    while (!glfwWindowShouldClose(window))
    {
        .....
        // 4.使用着色器程序对象
        glUseProgram(shaderProgram);
        // 5.绑定顶点数组对象,并绘制
        glBindVertexArray(VAO); 
    	glDrawArrays(GL_TRIANGLES, 0, 3);// 这里
       	.....
    }
    
  • 效果

元素(索引)缓冲对象EBO

  • 简介

    绘制矩形,有重复的顶点,正确使用索引顺序绘制图形可以重复利用顶点从而减少顶点数据。

  • 使用

    和VBO同样的生成使用方法,生成EBO缓冲区返回ID、绑定ID、设置索引数据、绑定在VAO上

    绘制不同:

    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);代替glDrawArrays(GL_TRIANGLES, 0, 3);

  • 图示

    由图可见,VAO索引缓冲区的指针只有一个,且在最后

  • 代码

    glsl不变

    version 330 core
    layout (location = 0) in vec3 aPos;
    void main()
    {
       gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
    }
    
    #version 330 core
    out vec4 FragColor;
    void main()
    {
       FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
    }
    

    cpp

    // 0.1顶点数据
    float vertices[] = {
        0.5f,  0.5f, 0.0f,  // top right
        0.5f, -0.5f, 0.0f,  // bottom right
        -0.5f, -0.5f, 0.0f,  // bottom left
        -0.5f,  0.5f, 0.0f   // top left 
    };
    // 0.2索引数据
    unsigned int indices[] = {  // note that we start from 0!
        0, 1, 3,  // first Triangle
        1, 2, 3   // second Triangle
    };
    
    unsigned int VBO, VAO, EBO;
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);
    glGenBuffers(1, &EBO);
    // bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
    // 1. 绑定顶点数组对象
    glBindVertexArray(VAO);
    // 2. 把我们的CPU的顶点数据复制到GPU顶点缓冲中,供OpenGL使用
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    // 3. 复制我们的CPU的索引数组到GPU索引缓冲中,供OpenGL使用
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
    // 4. 设定顶点属性指针,来解释顶点缓冲中的顶点属性布局
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    while (!glfwWindowShouldClose(window))
    {
        .....
        // 5.使用着色器程序对象
        glUseProgram(shaderProgram);
        // 6.绑定顶点数组对象,并绘制
        glBindVertexArray(VAO); 
    	//glDrawArrays(GL_TRIANGLES, 0, 3);
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);// 这里不同
        // 由于只有一个顶点数组对象,不需要解绑
       	.....
    }
    
  • 效果

小结

草稿图

请添加图片描述

重复重要的流程

着色器流程

  • 顶点着色器

    • 创建顶点着色器对象

    • 附加源码给顶点着色器对象

    • 编译顶点着色器对象

      可以打印是否编译成功

  • 片段着色器同上

  • 着色器程序

    • 创建着色器程序对象

    • 附加着色器对象给着色器程序对象

    • 链接着色器程序对象

      可以检查是否成功

    • 删除着色器对象

const char* vShaderCode = vertexCode.c_str();
const char* fShaderCode = fragmentCode.c_str();
unsigned int vertex, fragment;
// 1.1创建顶点着色器对象
vertex = glCreateShader(GL_VERTEX_SHADER);
// 1.2附加顶点着色器源码给顶点着色器对象
glShaderSource(vertex, 1, &vShaderCode, NULL);
// 1.3编译顶点着色器对象
glCompileShader(vertex);
// 1.4检测是否编译成功
checkCompileErrors(vertex, "VERTEX");
// 2.1创建片段着色器对象
fragment = glCreateShader(GL_FRAGMENT_SHADER);
// 2.2附加片段着色器源码给片段着色器对象
glShaderSource(fragment, 1, &fShaderCode, NULL);
// 2.3编译片段着色器对象
glCompileShader(fragment);
// 2.4检测是否编译成功
checkCompileErrors(fragment, "FRAGMENT");

// 3.1创建着色器程序对象
ID = glCreateProgram();
// 3.2附加着色器对象给着色器程序对象
glAttachShader(ID, vertex);
glAttachShader(ID, fragment);
// 3.3链接着色器程序对象
glLinkProgram(ID);
checkCompileErrors(ID, "PROGRAM");// 可以检查是否成功
// 4.删除着色器对象
glDeleteShader(vertex);
glDeleteShader(fragment);

void checkCompileErrors(unsigned int shader, std::string type)
{
    int success;
    char infoLog[1024];
    if (type != "PROGRAM")
    {
        glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
        if (!success)
        {
            glGetShaderInfoLog(shader, 1024, NULL, infoLog);
            std::cout << "ERROR::SHADER_COMPILATION_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl;
        }
    }
    else
    {
        glGetProgramiv(shader, GL_LINK_STATUS, &success);
        if (!success)
        {
            glGetProgramInfoLog(shader, 1024, NULL, infoLog);
            std::cout << "ERROR::PROGRAM_LINKING_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl;
        }
    }
}

绘制流程

  • 顶点数组对象
    • 创建顶点数组对象
    • 绑定顶点数组对象
  • 顶点缓冲对象
    • 创建顶点缓冲对象
    • 绑定顶点缓冲对象
    • 将顶点数据从CPU拷贝到GPU的顶点缓冲对象中
    • 设置顶点数组里的顶点属性指针,解释此顶点缓冲区的布局
  • 索引缓冲对象
    • 创建索引缓冲对象
    • 绑定索引缓冲对象,当前绑定顶点数组对象的索引缓冲对象指针会指向当前索引缓冲对象(自己的语言)
    • 将索引数据从CPU拷贝到GPU的索引缓冲对象中
  • 绘制代码
    • 使用着色器程序对象
    • 绑定顶点数组对象
    • 绘制元素
    • 解绑顶点数组对象
// ..:: 初始化代码 :: ..
// 1. 绑定顶点数组对象
glBindVertexArray(VAO);
// 2. 把我们的CPU的顶点数据复制到GPU顶点缓冲中,供OpenGL使用
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 3. 复制我们的CPU的索引数组到GPU索引缓冲中,供OpenGL使用
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
// 4. 设定顶点属性指针,来解释顶点缓冲中的顶点属性布局
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

[...]

// ..:: 绘制代码(渲染循环中) :: ..
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glBindVertexArray(0);

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

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

相关文章

文献计量三大定律之一---洛特卡定律及普赖斯定律

科学生产率是洛特卡定律的基础&#xff0c;科学生产率”(Scientific Productivity)&#xff09;是指科学家&#xff08;科研人员&#xff09;在科学上所表现出的能力和工作效率&#xff0c;通常用其生产的科学文献的数量来衡量。 1926年&#xff0c;洛特卡在一篇论文中提出了科…

Windows作为操作系统的典型特征和主要功能

我是荔园微风&#xff0c;作为一名在IT界整整25年的老兵&#xff0c;今天我们来重新审视一下Windows这个我们熟悉的不能再熟悉的系统。我们每天都在用Windows操作系统&#xff0c;但是其实我们每天直接在打交道的并不是Windows操作系统的内核&#xff0c;而是Windows操作系统的…

Docker部署Springboot项目(含MySQL+Redis)

使用Docker部署之前写的一个博客项目&#xff0c;主要用到了MySQL和Redis&#xff0c;Redis作网站访问量统计。下面会对具体的部署方式作详细讲解 一、服务器安装Docker 1、删除docker旧版本 sudo yum remove docker \docker-client \docker-client-latest \docker-common \…

(三十五)大白话MySQL一个事务多次查询一条数据读到的都是不同的值,这就是不可重复读?

上一讲我们说完了多个事务并发执行时候&#xff0c;对MySQL的缓存页里的同一行数据同时进行更新或者查询的时候&#xff0c;可能发生的脏写和脏读的问题 我们也都理解了&#xff0c;之所以会发生脏写和脏读&#xff0c;最关键的&#xff0c;其实是因为你一个事务写或者查的是人…

黑盒测试的常用方法

这里我们先设置一个示例,后面的文章中会根据示例来进行讲解 假设有一个程序是判断一个整形数字是否属于1-100 目录 1.等价类法 2.边界值法 3.判定表法 4.场景设计法 5.错误猜测法 6.正交法 1.等价类法 概念:系统性的确定要输入的测试条件的方法可以看出概念非常抽象,那…

命令执行漏洞 | iwebsec

文章目录1 靶场环境2 命令执行漏洞介绍3 靶场练习01-命令执行漏洞02-命令执行漏洞空格绕过03-命令执行漏洞关键命令绕过04-命令执行漏洞通配符绕过05-命令执行漏洞base64编码绕过4 命令执行漏洞危害01-读写系统文件02-执行系统命令03-种植恶意木马04-反弹shellpython反弹shellp…

Android 基础知识4-3.4 ImageView(图像视图)详解

一、ImageView简介 ImageView是Android开发中最常用的组件之一&#xff0c;主要用于显示图片&#xff0c;但是它不只是能显示图片&#xff0c;任何Drawable对象都可以使用它来显示。 二、ImageView 的继承关系 ImageView的继承关系 如下&#xff1a; java.lang.Object 《-- …

生成式语言大模型压缩技术思考——以ChatGPT为例

ChatGPT引领了生成式语言大模型的应用与技术热潮&#xff0c;首先简单回顾ChatGPT应用范式&#xff1a;将其应用于指定的下游任务时&#xff08;如知识问答、翻译、编码&#xff09;&#xff0c;ChatGPT需要经历三个阶段的训练&#xff08;增强人类语境的猜想&#xff09;&…

基于nodejs+vue的平面设计课程管理系统vscode

后台由管理员&#xff0c;教师和学生三个角色&#xff0c;其主要功能包括首页&#xff0c;个人中心&#xff0c;学生管理&#xff0c;教师管理&#xff0c;课程类型管理&#xff0c;课程学习管理&#xff0c;试题讲解管理&#xff0c;作业信息管理&#xff0c;作业提交管理&…

Flutter3引用原生播放器-IOS(Swift)篇

前言由于Flutter项目中需要使用到播放器功能&#xff0c;因此对flutter中各种播放器解决方案进行了一番研究和比对&#xff0c;最后决定还是自己通过Plugin的方法去引用原生播放器符合自己的需求&#xff0c;本篇文章会对各种解决方案做一个简单的比较&#xff0c;以及讲解一下…

STM32—DMA

什么是DMA&#xff1f; DMA(Direct Memory Access&#xff0c;直接存储器访问) 提供在外设与内存、存储器和存储器、外设与外设之间的高速数据传输使用。它允许不同速度的硬件装置来沟通&#xff0c;而不需要依赖于CPU&#xff0c;在这个时间中&#xff0c;CPU对于内存的工作来…

Leetcode 剑指 Offer II 016. 不含重复字符的最长子字符串

题目难度: 中等 原题链接 今天继续更新 Leetcode 的剑指 Offer&#xff08;专项突击版&#xff09;系列, 大家在公众号 算法精选 里回复 剑指offer2 就能看到该系列当前连载的所有文章了, 记得关注哦~ 题目描述 给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的最长…

软考之操作系统知识

目录 1.进程管理-进程的概念 2.进程的三态图和五态图 3.进程的同步与互斥 4.PV操作应用 5.死锁问题 6.银行家算法 7.存储管理 8.段式存储组织 9.段页式存储组织 10.页面置换算法 11.磁盘管理 12.作业管理 13.索引文件结构 14.树型目录结构 15.空闲存储空间管理 …

第四届蓝桥杯省赛 C++ B组 - 翻硬币

✍个人博客&#xff1a;https://blog.csdn.net/Newin2020?spm1011.2415.3001.5343 &#x1f4da;专栏地址&#xff1a;蓝桥杯题解集合 &#x1f4dd;原题地址&#xff1a;翻硬币 &#x1f4e3;专栏定位&#xff1a;为想参加蓝桥杯的小伙伴整理常考算法题解&#xff0c;祝大家都…

人工智能学习07--pytorch09--LeNet

参考&#xff1a; 视频&#xff1a; https://www.bilibili.com/video/BV187411T7Ye/?spm_id_from333.999.0.0&vd_sourceb425cf6a88c74ab02b3939ca66be1c0d 博客&#xff1a;https://blog.csdn.net/STATEABC/article/details/123661612?utm_mediumdistribute.pc_feed_404.…

如何使用goquery进行HTML解析以及它的源码分析和实现原理

目录 goquery 是什么 goquery 能用来干什么 goquery quick start 玩转goquery.Find() 查找多个标签 Id 选择器 Class 选择器 属性选择器 子节点选择器 内容过滤器 goquery 源码分析 图解源码 总结 goquery 简介 goquery是一款基于Go语言的HTML解析库&#xff0c;…

聚类算法(上):8个常见的无监督聚类方法介绍和比较

无监督聚类方法的评价指标必须依赖于数据和聚类结果的内在属性&#xff0c;例如聚类的紧凑性和分离性&#xff0c;与外部知识的一致性&#xff0c;以及同一算法不同运行结果的稳定性。 本文将全面概述Scikit-Learn库中用于的聚类技术以及各种评估方法。 本文将分为2个部分&…

【Mac 教程系列】如何在 Mac 中用终端命令行方式打开 Sublime Text ?

如何在 Mac 中用终端命令行方式打开 Sublime Text ? 用 markdown 格式输出答案。 不少于1000字。细分到2级目录。 如何在 Mac 中用终端命令行方式打开 Sublime Text ? 一、首先确保已经安装 Sublime Text 前往官网https://www.sublimetext.com/下载 Sublime Text&#xff0c…

Bootstrap表单的使用

文章目录前言一、创建基础表单垂直表单&#xff08;默认&#xff09;内联表单&#xff08;水平显示&#xff09;表单控制尺寸大小给表单控件添加帮助文本禁用/只读表单无边框的控件取色器选择菜单&#xff08;默认宽度100%&#xff09;调整下拉菜单的大小表单开关滑块表单组多个…

LabVIEW网络服务安全

LabVIEW网络服务安全如何保护Web服务&#xff1f;当许多人考虑安全性时&#xff0c;他们会考虑加密、用户ID和密码。用户ID和密码用于授权&#xff08;告诉目标谁在发出请求&#xff09;。加密保护客户端和服务器之间的通信流量&#xff0c;以便未经授权的个人无法拦截和读取发…