OpenGL编译用户着色器shader
shader相信很多朋友们都听说过,shader就是运行再GPU上的程序。虽然是这么说,但是我们发现,很多IDE开发工具比如说visual studio 没有办法直接去运行shader代码。这是因为,许多编译器不会自动将shader文件编译成可执行的代码然后发送给GPU,shader代码的编译需要开发人员手动动用GPU的shader编译接口,将对应的代码编译成可执行的程序。在这里,笔者就为大家介绍,如何使用OpenGL提供的API编译用户自己的shader程序。
Shader
OpenGL渲染管线
这里先为大家介绍一下OpenGL渲染管线如下图所示

事实上在一众着色器当中,只有顶点着色器和片元着色器是必须的,其他都是可选的。具体想要了解更多,可以去看一些资料或者书籍,比如笔者手上的这本《OpenGL编程指南》
OpenGL shader
OpenGL 支持的 shader 语法是 OpenGL shader language 简称是glsl。这个shader语法和C++基本上是大同小异,很快就可以轻松上手。
话说到这里,让我们回顾一下上一篇文章OpenGL渲染结果移至ImGui窗口上,有细心的本有不难发现,我并没有去编译用户着色器,而且必要的顶点着色器和片元着色器都没有进行编写,但是我们仍然得到我们想要的渲染结果。其原因是,OpenGL自带默认的着色器,所以对于初学者来说,可以尽可能使用少的代码去实现自己想要的结果,这就是为什么图形接口的学习一般都是从OpenGL开始学起。
OpenGL着色器编译
Shader 类
这里笔者写了一个Shader类,整体的代码如下
Shader.h
#pragma once#include<unordered_map>
typedef unsigned int GLenum;class Shader {
public:Shader(const std::string& filePath);~Shader();void Bind();void UBind();void UploadUniformFloat4(const std::string& name, float* value);private:std::string ReadFile(const std::string& filePath);std::unordered_map<GLenum, std::string> PreProcess(const std::string& source);void Compile(const std::unordered_map<GLenum, std::string>& shaderSources);
private:uint32_t m_ShaderID;std::string m_Name;
};
Shader.cpp
#include<glad/glad.h>
#include<string>
#include<array>
#include<fstream>
#include<iostream>#include"Shader.h"static GLenum ShaderTypeFromString(const std::string& type) {if (type == "vertex")return GL_VERTEX_SHADER;else if (type == "fragment" || type == "pixel")return GL_FRAGMENT_SHADER;std::cout << "Unknown shader type" << std::endl;return 0;
}Shader::Shader(const std::string& filePath) :m_ShaderID(0) {std::string source = ReadFile(filePath);auto shaderSource = PreProcess(source);Compile(shaderSource);auto lastSlash = filePath.find_last_of("/\\");lastSlash = lastSlash == std::string::npos ? 0 : lastSlash + 1;auto lastDot = filePath.rfind('.');auto count = lastDot == std::string::npos ? filePath.size() - lastSlash : lastDot - lastSlash;m_Name = filePath.substr(lastSlash, count);
}Shader::~Shader() {glDeleteProgram(m_ShaderID);
}void Shader::Bind(){glUseProgram(m_ShaderID);
}void Shader::UBind(){glUseProgram(0);
}void Shader::UploadUniformFloat4(const std::string& name, float* value) {int location = glGetUniformLocation(m_ShaderID, name.c_str());glUniform4f(location, value[0], value[1], value[2], value[3]);
}std::string Shader::ReadFile(const std::string& filePath) {std::string result;std::ifstream in(filePath, std::ios::in | std::ios::binary);if (in) {in.seekg(0, std::ios::end);result.resize(in.tellg());in.seekg(0, std::ios::beg);in.read(&result[0], result.size());in.close();}else {std::cout << "着色器文件没有正常打开" << std::endl;__debugbreak();}return result;
}std::unordered_map<GLenum, std::string> Shader::PreProcess(const std::string& source) {std::unordered_map<GLenum, std::string> shaderSources;const char* typeToken = "#type";size_t typeTokenLength = strlen(typeToken);size_t pos = source.find(typeToken,0);while (pos != std::string::npos) {size_t eol = source.find_first_of("\r\n", pos);if (eol == std::string::npos) {std::cout << "着色器语法出错" << std::endl;__debugbreak();}size_t begin = pos + typeTokenLength + 1;std::string type = source.substr(begin, eol - begin);if (!ShaderTypeFromString(type)) {std::cout << "这是一个不合法的着色器类型" << std::endl;__debugbreak();}size_t nextLinePos = source.find_first_of("\r\n", eol);pos = source.find(typeToken, nextLinePos);shaderSources[ShaderTypeFromString(type)] = source.substr(nextLinePos, pos - (nextLinePos == std::string::npos ? source.size() - 1 : nextLinePos));}return shaderSources;
}void Shader::Compile(const std::unordered_map<GLenum, std::string>& shaderSources) {unsigned int program = glCreateProgram();//一次性至多编译两种着色器if (shaderSources.size() < 2) {std::cout << "一次性至多编译两种着色器" << std::endl;__debugbreak();}std::array<GLenum, 2> glShaderIDs;int glShaderIDIndex = 0;for (auto& kv : shaderSources) {GLenum type = kv.first;const std::string& source = kv.second;unsigned int shader = glCreateShader(type);const char* sourceCStr = source.c_str();glShaderSource(shader, 1, &sourceCStr, 0);glCompileShader(shader);int isCompiled = 0;glGetShaderiv(shader, GL_COMPILE_STATUS, &isCompiled);if (isCompiled == GL_FALSE) {int maxLength = 0;glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength);std::vector<char> infoLog(maxLength);glGetShaderInfoLog(shader, maxLength, &maxLength, &infoLog[0]);glDeleteShader(shader);std::cout << "着色器编译出错:" << infoLog.data() << std::endl;__debugbreak();break;}glAttachShader(program, shader);glShaderIDs[glShaderIDIndex++] = shader;}m_ShaderID = program;// Link our programglLinkProgram(program);// Note the different functions here: glGetProgram* instead of glGetShader*.int isLinked = 0;glGetProgramiv(program, GL_LINK_STATUS, (int*)&isLinked);if (isLinked == GL_FALSE) {int maxLength = 0;glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength);// The maxLength includes the NULL characterstd::vector<char> infoLog(maxLength);glGetProgramInfoLog(program, maxLength, &maxLength, &infoLog[0]);// We don't need the program anymore.glDeleteProgram(program);for (auto id : glShaderIDs)glDeleteShader(id);std::cout << "用户着色器链接失败:" << infoLog.data() << std::endl;__debugbreak();return;}for (auto id : glShaderIDs)glDetachShader(program, id);
}
着色器代码
TextureShader.glsl
#type vertex
#version 450 core
//标记为0的内存位置输入一个有两个分量的向量,这是顶点的位置
layout(location = 0) in vec2 v_Position;void main(){//顶点位置的数据进行赋值,需要转换为齐次向量gl_Position = vec4(v_Position,0.0f,1.0f);
}#type fragment
#version 450 core
//标记为0的内存位置输出一个有四个分量的向量,这是像素的颜色
layout(location = 0) out vec4 o_Color;void main(){o_Color = vec4(0.8f,0.2f,0.3f,1.0f);
}
着色器介绍
上面虽然是一个着色器文件,其实这里面写了两个着色器,一个是顶点着色器,一个是片元着色器。#type vertex 下面的是顶点着色器,#type fragment 下面的是片元着色器。为什么这两个要一起写了?前面也介绍了,这两个着色器是必需要有的,所以笔者推荐这个着色器最好就是一起写。顶点着色器必须要有输入数据,片元着色器必须要有输出数据,不然屏幕上就看不到任何东西。
着色器编译
笔者将其分成了3个步骤进行
1、读取对应的文件内容
std::string Shader::ReadFile(const std::string& filePath) {std::string result;std::ifstream in(filePath, std::ios::in | std::ios::binary);if (in) {in.seekg(0, std::ios::end);result.resize(in.tellg());in.seekg(0, std::ios::beg);in.read(&result[0], result.size());in.close();}else {std::cout << "着色器文件没有正常打开" << std::endl;__debugbreak();}return result;
}
将TextureShader.glsl当中的文本信息全部转换成一个string类型当中进行存储。
2、确定着色器的类型,以及每个着色器的代码
std::unordered_map<GLenum, std::string> Shader::PreProcess(const std::string& source) {std::unordered_map<GLenum, std::string> shaderSources;const char* typeToken = "#type";size_t typeTokenLength = strlen(typeToken);size_t pos = source.find(typeToken,0);while (pos != std::string::npos) {size_t eol = source.find_first_of("\r\n", pos);if (eol == std::string::npos) {std::cout << "着色器语法出错" << std::endl;__debugbreak();}size_t begin = pos + typeTokenLength + 1;std::string type = source.substr(begin, eol - begin);if (!ShaderTypeFromString(type)) {std::cout << "这是一个不合法的着色器类型" << std::endl;__debugbreak();}size_t nextLinePos = source.find_first_of("\r\n", eol);pos = source.find(typeToken, nextLinePos);shaderSources[ShaderTypeFromString(type)] = source.substr(nextLinePos, pos - (nextLinePos == std::string::npos ? source.size() - 1 : nextLinePos));}return shaderSources;
}
对于OpenGL来说我们不光要告诉它需要编译的代码,还要告诉它编译的着色器代码是什么类型的着色器代码。在前面可以看到TextureShader.glsl当中有 #type vertex 这样的语句,这个并不是glsl语法,我们在进行文本处理的时候需要省略掉才行,不然的话编译会失败,这个只是用来告诉程序下面着色器代码是什么类型着色器的,所以这里选择返回了一个字典,用来存储着色器的类型和需要编译的程序。能够编译的是下面两段
#version 450 core
//标记为0的内存位置输入一个有两个分量的向量,这是顶点的位置
layout(location = 0) in vec2 v_Position;void main(){//顶点位置的数据进行赋值,需要转换为齐次向量gl_Position = vec4(v_Position,0.0f,1.0f);
}
#version 450 core
//标记为0的内存位置输出一个有四个分量的向量,这是像素的颜色
layout(location = 0) out vec4 o_Color;void main(){o_Color = vec4(0.8f,0.2f,0.3f,1.0f);
}
他们已经被分开存储了。
3、编译链接着色器
void Shader::Compile(const std::unordered_map<GLenum, std::string>& shaderSources) {//注册使用下面两个着色器的程序号unsigned int program = glCreateProgram();//一次性至多编译两种着色器if (shaderSources.size() < 2) {std::cout << "一次性至多编译两种着色器" << std::endl;__debugbreak();}std::array<GLenum, 2> glShaderIDs;int glShaderIDIndex = 0;for (auto& kv : shaderSources) {GLenum type = kv.first;const std::string& source = kv.second;//注册对饮类型的着色器unsigned int shader = glCreateShader(type);const char* sourceCStr = source.c_str();glShaderSource(shader, 1, &sourceCStr, 0);//编译着色器源码glCompileShader(shader);int isCompiled = 0;glGetShaderiv(shader, GL_COMPILE_STATUS, &isCompiled);//检查着色器是否编译失败if (isCompiled == GL_FALSE) {int maxLength = 0;glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength);std::vector<char> infoLog(maxLength);glGetShaderInfoLog(shader, maxLength, &maxLength, &infoLog[0]);glDeleteShader(shader);std::cout << "着色器编译出错:" << infoLog.data() << std::endl;__debugbreak();break;}//将着色器加入到这个程序当中glAttachShader(program, shader);glShaderIDs[glShaderIDIndex++] = shader;}m_ShaderID = program;// Link our programglLinkProgram(program);// Note the different functions here: glGetProgram* instead of glGetShader*.int isLinked = 0;glGetProgramiv(program, GL_LINK_STATUS, (int*)&isLinked);//检查程序是否能够链接成功if (isLinked == GL_FALSE) {int maxLength = 0;glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength);// The maxLength includes the NULL characterstd::vector<char> infoLog(maxLength);glGetProgramInfoLog(program, maxLength, &maxLength, &infoLog[0]);// We don't need the program anymore.glDeleteProgram(program);for (auto id : glShaderIDs)glDeleteShader(id);std::cout << "用户着色器链接失败:" << infoLog.data() << std::endl;__debugbreak();return;}for (auto id : glShaderIDs)glDetachShader(program, id);
}
上面大致流程就是,注册程序的编号,创建对应类型的着色器,根据下面的代码
static GLenum ShaderTypeFromString(const std::string& type) {if (type == "vertex")return GL_VERTEX_SHADER;else if (type == "fragment" || type == "pixel")return GL_FRAGMENT_SHADER;std::cout << "Unknown shader type" << std::endl;return 0;
}
可以知道 #type vertex 对应的着色器类型就是GL_VERTEX_SHADER,#type fragment 对应的着色器类型就是GL_FRAGMENT_SHADER。创建了对应的着色器类型过后就是对源码进行编译,放入到程序当中,检查这个程序能否顺利接入管线当中,隔离开然后等待被调用。
使用用户自定义着色器
着色器使用,主函数代码如下
#include<glad/glad.h>
#include<GLFW/glfw3.h>#include "imgui.h"
#include "imgui_impl_glfw.h"
#include "imgui_impl_opengl3.h"#include<iostream>#include"FrameBuffer.h"
#include"Shader.h"int main() {glfwInit();GLFWwindow* window = glfwCreateWindow(640, 480, "Triangles", NULL, NULL);glfwMakeContextCurrent(window);glfwSwapInterval(1); // Enable vsync// Setup Dear ImGui contextIMGUI_CHECKVERSION();ImGui::CreateContext();ImGuiIO& io = ImGui::GetIO(); (void)io;io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controlsio.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controlsio.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Dockingio.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport / Platform Windows//io.ConfigViewportsNoAutoMerge = true;//io.ConfigViewportsNoTaskBarIcon = true;// Setup Dear ImGui styleImGui::StyleColorsDark();//ImGui::StyleColorsLight();// When viewports are enabled we tweak WindowRounding/WindowBg so platform windows can look identical to regular ones.ImGuiStyle& style = ImGui::GetStyle();if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable){style.WindowRounding = 0.0f;style.Colors[ImGuiCol_WindowBg].w = 1.0f;}// Setup Platform/Renderer backendsImGui_ImplGlfw_InitForOpenGL(window, true);ImGui_ImplOpenGL3_Init("#version 130");//需要初始化GLADif (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {std::cout << "Failed to initialize GLAD" << std::endl;return -1;}float positions[6] = {-0.5f, -0.5,0.0f, 0.5f,0.5f, -0.5f};GLuint buffer = 0;glGenBuffers(1, &buffer);glBindBuffer(GL_ARRAY_BUFFER, buffer);glBufferData(GL_ARRAY_BUFFER, sizeof(positions), positions, GL_STATIC_DRAW);glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), NULL);glEnableVertexAttribArray(0);bool show_demo_window = true;ImVec2 viewPortSize(640,480);float colorEditor[4] = {1.0f, 1.0f, 1.0f, 1.0f};FrameBuffer *pFrameBuffer = new FrameBuffer(640, 480);Shader* pShader = new Shader("assets/shaders/TextureShader.glsl");pShader->UBind();while (!glfwWindowShouldClose(window)) {pFrameBuffer->Bind();pShader->Bind();glClear(GL_COLOR_BUFFER_BIT);glDrawArrays(GL_TRIANGLES, 0, 3);pFrameBuffer->UBind();// Start the Dear ImGui frameImGui_ImplOpenGL3_NewFrame();ImGui_ImplGlfw_NewFrame();ImGui::NewFrame();ImGui::DockSpaceOverViewport(0, ImGui::GetMainViewport());ImGui::Begin("ViewPort");viewPortSize = ImGui::GetContentRegionAvail();if (viewPortSize.x * viewPortSize.y > 0 && (viewPortSize.x != pFrameBuffer->GetWidth() || viewPortSize.y != pFrameBuffer->GetHeight())) {pFrameBuffer->Resize(viewPortSize.x, viewPortSize.y);glViewport(0, 0, viewPortSize.x, viewPortSize.y);}uint32_t textureID = pFrameBuffer->GetColorAttachment();ImGui::Image(reinterpret_cast<void*>(textureID), viewPortSize, { 0,1 }, { 1,0 });ImGui::End();ImGui::Begin("ColorEditor");ImGui::ColorEdit4("##colorEditor", colorEditor);ImGui::End();/*if(show_demo_window)ImGui::ShowDemoWindow(&show_demo_window);*/// RenderingImGui::Render();ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable){GLFWwindow* backup_current_context = glfwGetCurrentContext();ImGui::UpdatePlatformWindows();ImGui::RenderPlatformWindowsDefault();glfwMakeContextCurrent(backup_current_context);}glfwSwapBuffers(window);glfwPollEvents();}// CleanupImGui_ImplOpenGL3_Shutdown();ImGui_ImplGlfw_Shutdown();ImGui::DestroyContext();delete pFrameBuffer;delete pShader;glfwDestroyWindow(window);glfwTerminate();
}
得到的结果是

三角形被顺利染成了红色,有人可能会说这也有点费了这么大的劲,就把颜色改成了红色,实在是有点无聊,那让我们来做一些比较Cool的事。
我们修改一下着色器
#type vertex
#version 450 corelayout(location = 0) in vec2 v_Position;void main(){gl_Position = vec4(v_Position,0.0f,1.0f);
}#type fragment
#version 450 corelayout(location = 0) out vec4 o_Color;
//增加的片段
uniform vec4 u_Color;void main(){o_Color = u_Color;
}
主函数也修改一下
pShader->UBind();while (!glfwWindowShouldClose(window)) {pFrameBuffer->Bind();pShader->Bind();//新增片段pShader->UploadUniformFloat4("u_Color", colorEditor);glClear(GL_COLOR_BUFFER_BIT);
展示一下结果


我们现在可以通过ImGui上面的控件对三角形的颜色进行实时修改了,不用去改动程序,是不是很棒了。下面还是把整个主函数放出来,如果对里面的FrameBuffer类不了解的可以看笔者的OpenGL渲染结果移至ImGui窗口上这篇文章,同样有源代码,希望对大家能有帮助。
#include<glad/glad.h>
#include<GLFW/glfw3.h>#include "imgui.h"
#include "imgui_impl_glfw.h"
#include "imgui_impl_opengl3.h"#include<iostream>#include"FrameBuffer.h"
#include"Shader.h"int main() {glfwInit();GLFWwindow* window = glfwCreateWindow(640, 480, "Triangles", NULL, NULL);glfwMakeContextCurrent(window);glfwSwapInterval(1); // Enable vsync// Setup Dear ImGui contextIMGUI_CHECKVERSION();ImGui::CreateContext();ImGuiIO& io = ImGui::GetIO(); (void)io;io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controlsio.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controlsio.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Dockingio.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport / Platform Windows//io.ConfigViewportsNoAutoMerge = true;//io.ConfigViewportsNoTaskBarIcon = true;// Setup Dear ImGui styleImGui::StyleColorsDark();//ImGui::StyleColorsLight();// When viewports are enabled we tweak WindowRounding/WindowBg so platform windows can look identical to regular ones.ImGuiStyle& style = ImGui::GetStyle();if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable){style.WindowRounding = 0.0f;style.Colors[ImGuiCol_WindowBg].w = 1.0f;}// Setup Platform/Renderer backendsImGui_ImplGlfw_InitForOpenGL(window, true);ImGui_ImplOpenGL3_Init("#version 130");//需要初始化GLADif (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {std::cout << "Failed to initialize GLAD" << std::endl;return -1;}float positions[6] = {-0.5f, -0.5,0.0f, 0.5f,0.5f, -0.5f};GLuint buffer = 0;glGenBuffers(1, &buffer);glBindBuffer(GL_ARRAY_BUFFER, buffer);glBufferData(GL_ARRAY_BUFFER, sizeof(positions), positions, GL_STATIC_DRAW);glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), NULL);glEnableVertexAttribArray(0);bool show_demo_window = true;ImVec2 viewPortSize(640,480);float colorEditor[4] = {1.0f, 1.0f, 1.0f, 1.0f};FrameBuffer *pFrameBuffer = new FrameBuffer(640, 480);Shader* pShader = new Shader("assets/shaders/TextureShader.glsl");pShader->UBind();while (!glfwWindowShouldClose(window)) {pFrameBuffer->Bind();pShader->Bind();pShader->UploadUniformFloat4("u_Color", colorEditor);glClear(GL_COLOR_BUFFER_BIT);glDrawArrays(GL_TRIANGLES, 0, 3);pFrameBuffer->UBind();// Start the Dear ImGui frameImGui_ImplOpenGL3_NewFrame();ImGui_ImplGlfw_NewFrame();ImGui::NewFrame();ImGui::DockSpaceOverViewport(0, ImGui::GetMainViewport());ImGui::Begin("ViewPort");viewPortSize = ImGui::GetContentRegionAvail();if (viewPortSize.x * viewPortSize.y > 0 && (viewPortSize.x != pFrameBuffer->GetWidth() || viewPortSize.y != pFrameBuffer->GetHeight())) {pFrameBuffer->Resize(viewPortSize.x, viewPortSize.y);glViewport(0, 0, viewPortSize.x, viewPortSize.y);}uint32_t textureID = pFrameBuffer->GetColorAttachment();ImGui::Image(reinterpret_cast<void*>(textureID), viewPortSize, { 0,1 }, { 1,0 });ImGui::End();ImGui::Begin("ColorEditor");ImGui::ColorEdit4("##colorEditor", colorEditor);ImGui::End();/*if(show_demo_window)ImGui::ShowDemoWindow(&show_demo_window);*/// RenderingImGui::Render();ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable){GLFWwindow* backup_current_context = glfwGetCurrentContext();ImGui::UpdatePlatformWindows();ImGui::RenderPlatformWindowsDefault();glfwMakeContextCurrent(backup_current_context);}glfwSwapBuffers(window);glfwPollEvents();}// CleanupImGui_ImplOpenGL3_Shutdown();ImGui_ImplGlfw_Shutdown();ImGui::DestroyContext();delete pFrameBuffer;delete pShader;glfwDestroyWindow(window);glfwTerminate();
}
相关文章:
OpenGL编译用户着色器shader
shader相信很多朋友们都听说过,shader就是运行再GPU上的程序。虽然是这么说,但是我们发现,很多IDE开发工具比如说visual studio 没有办法直接去运行shader代码。这是因为,许多编译器不会自动将shader文件编译成可执行的代码然后发…...
过期策略、内存淘汰机制
1.过期策略:请求时删除 定期删除 请求时删除:使用key之前,检查是否过期,属于一种被动的处理方式。 因此,过期时间到了不表示这个key真的被删除了 定期删除:Redis默认每隔100ms检查,有过期ke…...
Scala的正则表达式
package hfdobject Test35_3 {def main(args: Array[String]): Unit {println("a\tb")//定义一个规则 正则表达式//1. .表示除了换行之外的其他的任意单个字符//2. \d等于[0-9] 匹配一个数字//3. \D除了\d之外的其他的任意字符,表示非数字//4. \w等价于[…...
关于睡懒觉
我们经常听到一个词:睡懒觉。 我认为,睡懒觉这个词,是错误的。 人,是需要睡眠的,睡不够,就不会醒。睡够了,自然会醒,也不想继续睡。不信你试试,睡够了,你…...
【算法day10】栈与队列:拓展与应用
题目引用 逆波兰表达式求值滑动窗口最大值前k个高频元素 1.逆波兰表达式求值 给你一个字符串数组 tokens ,表示一个根据 逆波兰表示法 表示的算术表达式。 请你计算该表达式。返回一个表示表达式值的整数。 注意: 有效的算符为 ‘’、‘-’、‘*’ 和…...
爆肝Android JNI - 延展Android蓝牙JNI学习
零. 前言 由于Bluedroid的介绍文档有限,以及对Android的一些基本的知识需要了(Android 四大组件/AIDL/Framework/Binder机制/JNI/HIDL等),加上需要掌握的语言包括Java/C/C++等,加上网络上其实没有一个完整的介绍Bluedroid系列的文档,所以不管是蓝牙初学者还是蓝牙从业人员…...
总篇:Python3+Request+Pytest+Allure+Jenkins接口自动化框架设计思路
1、技术选型 Python3 Python 是一种广泛使用的高级编程语言,具有简洁、易读、易维护的特点。 Python 拥有丰富的第三方库,可以方便地进行接口测试的开发。 Request Request 是一个强大的 HTTP 库,用于发送 HTTP 请求和处理响应。 Request 支持多种 HTTP 方法,如 GET、P…...
Java的Map介绍以及常见方法和三种遍历方式
Java的Map介绍以及常见方法和三种遍历方式 1 Java 中的 Map 介绍 在 Java 中,Map 是一个接口,它提供了一种存储键值对(key-value pairs)的方式。每个键(key)都关联着一个值(value)…...
C/C++基础知识复习(39)
1) 什么是封装性?C中如何实现封装? 封装性(Encapsulation)是面向对象编程中的一个重要概念,它指的是将对象的状态(数据)和行为(方法)绑定在一起,并且通过访问…...
自建服务器,数据安全有保障
在远程桌面工具的选择上,向日葵和TeamViewer功能强大,但都存在收费昂贵、依赖第三方服务器、数据隐私难以完全掌控等问题。相比之下,RustDesk 凭借开源免费、自建服务的特性脱颖而出!用户可以在自己的服务器上部署RustDesk服务端&…...
CCF-GESP 编程能力认证 C++ 七级 2024年9月份判断题详细解析
链接:CCF-GESP 编程能力认证 C 七级 2024年9月份选择题详细解析-CSDN博客 目录 第 1 题 第 2 题 第 3 题 第 4 题 第 5 题 第 6 题 第 7 题 第 8 题 第 9 题 第 10 题 第 1 题 表达式 a << 1 的结果为 a(错误) 【a是字符常…...
使用Vue3+Echarts实现加载中国地图,点击省份地图下钻(完整教程)
一. 前言 在众多 ECharts 图表类型中,开发者始终绕不开的有各种各样的地图开发,关于地图开发,可能比其他图表相对繁琐一些,其实说简单也简单,说复杂也复杂,其中不乏有层级地图、3D 地图等,感觉…...
NUMA-非统一内存访问架构
NUMA(Non-Uniform Memory Access) 是一种计算机内存架构,主要用于多处理器系统。NUMA架构中的每个处理器都连接到自己的本地内存,并且可以访问其他处理器的内存,但访问其他处理器的内存速度较慢。 内核通过调度优化进…...
初识交换机和路由器
目录 初识交换机和路由器交换机路由器主要区别工作流程如果是交换机:如果是路由器 初识交换机和路由器 左为路由器,右为交换机 交换机 交换机的前身是集线器,集线器是物理层的设备,有很多接口,当一台计算机A想发消息…...
SQL面试题——滴滴SQL面试题 取出累计值与1000差值最小的记录
滴滴SQL面试题 取出累计值与1000差值最小的记录 今天的题目来自滴滴出行 已知有表cost_detail包含id和money两列,id为自增,请累加计算money值,并求出累加值与1000差值最小的记录。 +-----+--------+ | id | money | +-----+--------+ | 1 | 200 | | 2 | 300 …...
openEuler 22.03 使用cephadm安装部署ceph集群
目录 目的步骤规格步骤ceph部署前准备工作安装部署ceph集群ceph集群添加node与osdceph集群一些操作组件服务操作集群进程操作 目的 使用ceph官网的cephadm无法正常安装,会报错ERROR: Distro openeuler version 22.03 not supported 在openEuler上实现以cephadm安装部…...
C++哈希(一)
1.底层结构 顺序结构以及平衡中,元素关键码与其存储位置之间没有相对应的关系,因此在查找一个元素时,要经过关键码的多次比较。顺序查找的时间复杂度为O(N)。 理想的搜索方法:可以不经过比较,依次直接从表中直接搜索…...
阿拉丁论文助手:一键点亮学术之路
在学术研究的海洋中,每一位学者都渴望拥有一盏能够照亮前行道路的神灯。阿拉丁论文助手,正是这样一盏神奇的灯,它以其先进的人工智能技术和丰富的学术资源,为学者们的学术写作提供了全方位的支持。 一、阿拉丁论文助手简介 阿拉丁…...
视频码率到底是什么?详细说明
视频码率(Video Bitrate)是指在单位时间内(通常是每秒)传输或处理的视频数据量,用比特(bit)表示。它通常用来衡量视频文件的压缩程度和质量,码率越高,视频质量越好&#…...
嵌入式学习(17)-stm32F407串口使用注意事项
一、概述 配置串口时串口的接收一直不好使,对比例程发现了问题: 在网上也找了一些资料供参考“STM32F4的串口RX引脚不能被设置为输入是因为串口的接收(RX)功能是由硬件电路实现的,无法通过软件配置来控制。串口接收功…...
css实现圆环展示百分比,根据值动态展示所占比例
代码如下 <view class""><view class"circle-chart"><view v-if"!!num" class"pie-item" :style"{background: conic-gradient(var(--one-color) 0%,#E9E6F1 ${num}%),}"></view><view v-else …...
YSYX学习记录(八)
C语言,练习0: 先创建一个文件夹,我用的是物理机: 安装build-essential 练习1: 我注释掉了 #include <stdio.h> 出现下面错误 在你的文本编辑器中打开ex1文件,随机修改或删除一部分,之后…...
测试markdown--肇兴
day1: 1、去程:7:04 --11:32高铁 高铁右转上售票大厅2楼,穿过候车厅下一楼,上大巴车 ¥10/人 **2、到达:**12点多到达寨子,买门票,美团/抖音:¥78人 3、中饭&a…...
linux 下常用变更-8
1、删除普通用户 查询用户初始UID和GIDls -l /home/ ###家目录中查看UID cat /etc/group ###此文件查看GID删除用户1.编辑文件 /etc/passwd 找到对应的行,YW343:x:0:0::/home/YW343:/bin/bash 2.将标红的位置修改为用户对应初始UID和GID: YW3…...
Spring AI 入门:Java 开发者的生成式 AI 实践之路
一、Spring AI 简介 在人工智能技术快速迭代的今天,Spring AI 作为 Spring 生态系统的新生力量,正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务(如 OpenAI、Anthropic)的无缝对接&…...
html css js网页制作成品——HTML+CSS榴莲商城网页设计(4页)附源码
目录 一、👨🎓网站题目 二、✍️网站描述 三、📚网站介绍 四、🌐网站效果 五、🪓 代码实现 🧱HTML 六、🥇 如何让学习不再盲目 七、🎁更多干货 一、👨…...
2025季度云服务器排行榜
在全球云服务器市场,各厂商的排名和地位并非一成不变,而是由其独特的优势、战略布局和市场适应性共同决定的。以下是根据2025年市场趋势,对主要云服务器厂商在排行榜中占据重要位置的原因和优势进行深度分析: 一、全球“三巨头”…...
C++使用 new 来创建动态数组
问题: 不能使用变量定义数组大小 原因: 这是因为数组在内存中是连续存储的,编译器需要在编译阶段就确定数组的大小,以便正确地分配内存空间。如果允许使用变量来定义数组的大小,那么编译器就无法在编译时确定数组的大…...
使用Spring AI和MCP协议构建图片搜索服务
目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式(本地调用) SSE模式(远程调用) 4. 注册工具提…...
【JVM】Java虚拟机(二)——垃圾回收
目录 一、如何判断对象可以回收 (一)引用计数法 (二)可达性分析算法 二、垃圾回收算法 (一)标记清除 (二)标记整理 (三)复制 (四ÿ…...
