1.6.1 变换

news2024/10/7 10:24:00
我们要想改变物体的位置,现有解决办法是,每一帧改变物体的顶点并且重配置缓冲区从而使物体移动,但是这样太繁琐,更好的解决方式是使用矩阵(Matrix)来更好的变换(Transform)一个物体。

一、向量

向量是有方向的量,向量有一个方向(Direction)和大小(Magnitude,也叫强度或长度)。
数学家喜欢在字母上面加一横表示向量,比如说 。当用在公式中时它们通常是这样的:

1、向量与标量运算
标量只是一个数字(或者说是仅有一个分量的向量)。当把一个向量加、减、乘、除一个标量时,可以把向量的每个分量分别与这个标量进行运算。如下是向量与标量的加法运算:

2、向量取反
对一个向量取反会将其方向逆转,一个指向东北的向量取反后就指向西南方向了。在一个向量的每个分量前加负号就可以实现取反:

3、向量加减
向量的加法可以被定义为分量的相加,即将一个向量中的每个分量加上另一个向量对应的分量:

两个向量想加的结果向量是从第一个向量(v=(4,2))起点指向第二个向量(k=(1,2))的终点

向量的减法等于第一个向量加上第二个向量的相反向量:

两个向量相减会得到这两个向量指向位置的差:从第二个向量(v=(3,2))的终点指向第一个向量(v=(0.5,3.5))的终点,再把起点位置移动到(0,0)处,最终向量是(-2.5,1.5)

4、长度
使用勾股定理来获取向量的长度/大小,如果你把向量的x与y分量画出来,该向量会和x与y分量为边形成一个三角形:

因为两条边(x和y)是已知的,如果希望知道斜边的长度,可以直接通过勾股定理计算:

||v¯||表示向量的长度,例子中向量(4,2)的长度等于4.47:

有一个特殊类型的向量叫做单位向量(Unit Vector)。单位向量有一个特别的性质:长度是1。可以用任意向量的每个分量除以向量的长度得到它的单位向量:

这种方法叫做一个向量的标准化,单位向量头上有一个^样子的记号。

二、向量相乘

普通的乘法在向量上时没有定义的,因为它在视觉上没有意义。但是在相乘的时候有两种特定情况可以选择:一个是点乘,另一个是叉乘。
1、点乘
点乘是通过将对应分量逐个相乘,然后再把所有得积相加来计算的:

同时,两个向量的点乘等于它们的数乘结果乘以两个向量之间夹角的余弦值,公式如下:

它们之间的夹角记作 θ。当两个向量都是单位向量时,公式简化为:

现在点乘只定义了两个向量的夹角,0度的余弦值是1,90度的余弦值是0,使用点乘可以很容易测试两个向量是否是正交或平行(想了解更多关于正弦或余弦函数的知识,推荐看可汗学院的基础三角学视频: https://www.khanacademy.org/math/geometry-home/right-triangles-topic/intro-to-the-trig-ratios-geo/v/basic-trigonometry )。
也可以通过点乘的结果计算两个非单位向量的夹角,点乘的结果除以两个向量的长度之积,得到的结果就是夹角的余弦值,即cos θ:

2、叉乘
叉乘需要两个不平行向量作为输入,生成一个正交与两个输入向量的第三个向量。
如果输入的两个向量也是正交的,那么叉乘之后将会产生3个相互正交的向量:

叉乘公式如下:

三、矩阵

简单来说矩阵就是一个矩形的数字、符号或表达式数组。矩阵中每一项叫做矩阵的元素(Element)。
下面是一个2 × 3的矩阵:

矩阵可以通过(i,j)进行索引,i是行,j是列,这就是上面的矩阵叫做 2 × 3的矩阵的原因(2行3列,也叫做矩阵的维度)。
1、矩阵的加减
矩阵与标量之间的加减定义如下,标量值要加减到矩阵的每一个元素上:

矩阵与矩阵之间的加减就是两个矩阵对应元素的加减运算,只有同维度的矩阵才能加减:

2、矩阵的数乘
矩阵与标量的乘法是矩阵的每一个元素分别乘以该标量:

四、矩阵相乘

矩阵相乘有一些限制:
  • 只有当左侧矩阵的列数与右侧矩阵的行数相等时,才能相乘;
  • 矩阵相乘不遵守交换律,也就是说A·B≠B·A;
下面是两个2×2矩阵相乘的例子:

  • 首先用左侧矩阵的第1行的第1个数乘以右侧矩阵的第1列的第1个数加上左侧矩阵的第1行第2个数乘以右侧矩阵的第1列第2个数,得到的结果放在第1行第1列上;
  • 然后用左侧矩阵的第1行的第1个数乘以右侧矩阵的第2列的第1个数加上左侧矩阵的第1行第2个数乘以右侧矩阵的第2列第2个数,得到的结果放在第1行第2列上;
  • 然后用左侧矩阵的第2行的第1个数乘以右侧矩阵的第1列的第1个数加上左侧矩阵的第2行第2个数乘以右侧矩阵的第1列第2个数,得到的结果放在第2行第1列上;
  • 最后用左侧矩阵的第2行的第1个数乘以右侧矩阵的第2列的第1个数加上左侧矩阵的第2行第2个数乘以右侧矩阵的第2列第2个数,得到的结果放在第2行第2列上;
  • 结果得到一个维度是(n,m)的矩阵,n等于左侧矩阵的行数,m等于右侧矩阵的列数(n×k矩阵 乘以 k×m矩阵得到一个n×m矩阵)。
(想了解更多的矩阵知识,推荐看可汗学院的矩阵教程: https://www.khanacademy.org/math/algebra2/algebra-matrices)

五、矩阵与向量相乘

向量可以看成是N×1的矩阵,N表示向量分量的个数。如果我们有一个M×N矩阵,可以用这个矩阵乘以N×1向量,
因为这个矩阵的列数等于这个向量的行数,所以他们能相乘。
1、单位矩阵
在OpenGL中,由于某些原因我们通常使用4 ×4的变换矩阵,其中最重要的原因就是大部分的向量都是4分量的。
最简单的变换矩阵就是单位矩阵,单位矩阵是一个除了对角线以外都是0的N ×N矩阵。
下面可以看到这个变换矩阵使一个向量完全不变:

2、缩放矩阵
对一个向量进行缩放就是对这个向量的长度进行缩放,它的方向保持不变。
先尝试缩放向量 v ¯ = ( 3 , 2 ), 把向量沿着x轴缩放0.5,使它的宽度缩小为原来的二分之一, 沿着y轴把向量的高度缩放为原来的两倍,得到向量 s ¯

OpenGL通常在3D空间进行操作,对于2D的情况我们当做把z轴缩放1倍,这样z轴的值保持不变。
刚刚的缩放操作时, 每个轴的缩放因子都不一样,所以 是不均匀缩放;如果每个轴的缩放因子都一样就叫均匀缩放。
下图为向量(x,y,z)定义一个缩放矩阵:

S1、S2、S3分别表示对轴x、y、z的缩放倍数。注意,第四个缩放向量(w分量)仍然是1,这个w分量有其他用途。
3、位移矩阵
位移是在原来向量的基础上加上另一个向量从而获得一个不同位置的新向量的过程。
4 ×4矩阵上的第四列最上面的3个值用来实现向量的位移操作,位移矩阵定义如下:

齐次坐标:
向量的w分量也叫齐次坐标,想要从齐次向量得到3D向量,我们可以把x、y、z坐标分别除以w坐标。使用齐次坐标的好处:它允许我们在3D向量上进行移动(如果没有w分量我们不能位移向量),而且下一章我们会用w值创建3D视觉效果。
如果一个向量的齐次坐标是0,这个坐标就是方向向量,这个向量不能移动。
4、旋转矩阵
如果想了解旋转矩阵是如何构造出来的,推荐看可汗学院线性代数的视频: https://www.khanacademy.org/math/linear-algebra/matrix_transformations
2D或3D空间中旋转用角来表示。角可以是角度制或弧度制的,周角是360角度或2PI弧度。
大多数旋转函数需要用弧度制的角,角度制的角可以和弧度制相互转化:
  • 弧度制角度:角度 = 弧度 * (180.0f / PI);
  • 角度制弧度:弧度 = 角度 * (PI / 180.0f);
PI约等于3.14159265359。
转半圈会旋转360/2 = 180度,向右旋转1/5圈表示向右旋转360/5 = 72度。
在3D空间中旋转需要定义一个角和一个旋转轴,物体会沿着给定的旋转轴旋转特定角度。
使用三角学,给定一个角度,可以把一个向量变换为一个经过旋转的新向量,这通常使用一系列正弦和余弦函数进行巧妙的组合得到(如何生成旋转矩阵超出本教程的范围,直接看旋转矩阵即可)。
旋转矩阵在3D空间中每个单位轴上有不同定义,旋转角度用 θ表示,沿x轴旋转:

沿y轴旋转:

沿z轴旋转:

利用旋转矩阵可以把任意向量沿着一个单位轴进行旋转,也可以将多个矩阵复合,比如先沿着x轴旋转再沿着y轴旋转,但是这会导致万向节死锁(万向节死锁的知识推荐看这个视频: https://www.youtube.com/watch?v=zc8b2Jo7mno )。一个更好的模型是沿着任意的一个轴进行旋转,而不是一系列旋转矩阵进行复合,这样的矩阵是存在的,见下面的公式,其中 ( R x , R y , R z ) 代表任意旋转轴:

在数学上讨论如何生成这样的矩阵仍然超过本节内容,但是,这样一个矩阵也只是会极大地避免万向节死锁的问题,不能完全的解决 万向节死锁的问题。避免 万向节死锁的真正解决方案是使用四元数(关于四元数,推荐看这个: https://krasjet.github.io/quaternion/quaternion.pdf ),它不仅更安全,而且计算会更有效率。
5、矩阵的组合
使用矩阵进行变换的真正力量在于,根据矩阵之间的乘法,可以把多个变换组合到一个矩阵中。
假设有一个顶点(x,y,z),我们希望将其缩放2倍,然后位移(1,2,3)个单位,我们需要一个位移和缩放矩阵来完成这些变换,结果的变换矩阵像这样:

注意,当矩阵相乘时我们先写位移再写缩放变换,矩阵乘法是不遵守交换律的,这意味这它们的顺序很重要。当矩阵相乘时,在最右边的矩阵是第一个向量相乘的,所以你应该从右向左读这个乘法。建议在组合矩阵时先进行缩放操作,然后旋转,最后才是位移(位移一般要放在最后;假设现在需要旋转和位移,如果是先旋转再位移,旋转的中心就是物体的中点点; 如果是先位移再旋转,旋转的中心是原点 )。
最终的变换矩阵左乘向量得到以下结果:

这样,向量先缩放2倍,然后位移了(1,2,3)个单位。
上面的矩阵可以写成如下样式,这样的话向量会和第二个矩阵进行运算,运算结果再和第一个矩阵进行运算,这样的计算结果是先缩放再位移:

如果写成下面这样,计算结果是先位移再缩放(下一节的练习1中展示这种效果)

六、实践

更改程序,实现矩形位移和旋转功能。
更改顶点着色器代码,创建一个 uniform mat4 RotationMatrix变量,用于接收变换矩阵
#version 330 core

layout (location = 0) in vec3 aPos; //位置变量的属性位置值为0
layout (location = 1) in vec3 aColor; //颜色变量的属性位置值为1
layout (location = 2) in vec2 aTexture; //纹理变量的属性位置值为2

out vec3 ourColor; //向片段着色器输出一个颜色坐标
out vec2 ourTexture; //向片段着色器输出一个纹理坐标

uniform mat4 RotationMatrix; //变换矩阵

void main()
{
    //矩阵与向量相乘,使向量进行旋转、缩放、位移等
    gl_Position = RotationMatrix * vec4(aPos, 1.0);
    ourColor = aColor;
    ourTexture = aTexture;
}

在myopenglwidget.h文件中创建定时器对象m_timer和定时器响应槽函数函数onTimeout

#ifndef MYOPENGLWIDGET_H
#define MYOPENGLWIDGET_H
#include <QOpenGLWidget>
#include <QOpenGLFunctions_3_3_Core>
#include <QOpenGLShaderProgram>
#include <QOpenGLTexture>
#include <QTimer>

class MyOpenGLWidget : public QOpenGLWidget,QOpenGLFunctions_3_3_Core
{
    Q_OBJECT

public:
    explicit MyOpenGLWidget(QWidget *parent = nullptr);
    ~MyOpenGLWidget();

protected:
    virtual void initializeGL();
    virtual void resizeGL(int w, int h);
    virtual void paintGL();

    void keyPressEvent(QKeyEvent *event);

private slots:
    void onTimeout();

private:
    QOpenGLShaderProgram m_shaderProgram;
    QOpenGLTexture *m_textureWall;
    QOpenGLTexture *m_textureSmile;
    QOpenGLTexture *m_textureSmall;

    float mixValue = 0.5;

    QTimer m_timer;
};

#endif // MYOPENGLWIDGET_H

在myopenglwidget.h文件中构造函数进行定时器的信号与槽函数的绑定,并启动100ms的定时器;析构函数中停止定时器:

#include "myopenglwidget.h"
#include <QDebug>
#include <QKeyEvent>
#include <QTime>

unsigned int VBO; //顶点缓冲对象
unsigned int VAO; //顶点数组对象
unsigned int EBO; //元素缓冲对象

MyOpenGLWidget::MyOpenGLWidget(QWidget *parent) : QOpenGLWidget(parent)
{
    setFocusPolicy(Qt::StrongFocus);

    connect(&m_timer, &QTimer::timeout, this, &MyOpenGLWidget::onTimeout);
    m_timer.start(100); //100ms
}

MyOpenGLWidget::~MyOpenGLWidget()
{
    if(m_timer.isActive())
        m_timer.stop();

    makeCurrent();
    glDeleteBuffers(1, &VBO);
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &EBO);
    doneCurrent();
}

void MyOpenGLWidget::onTimeout()
{
    update();
}

void MyOpenGLWidget::initializeGL()
{
    //初始化OpenGL函数
    initializeOpenGLFunctions();

    //创建VBO,并赋予ID
    glGenBuffers(1, &VBO);
    //绑定VBO对象
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    //顶点数据
    float vertices[] = {
        //位置              //颜色             //纹理
        0.5f,  0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, //右上角
        0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, //右下角
       -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, //左下角
       -0.5f,  0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f //左上角
    };
    //把顶点数据复制到显存中
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    //创建VAO对象,并赋予ID
    glGenVertexArrays(1, &VAO);
    //绑定VAO对象
    glBindVertexArray(VAO);

    //创建EBO对象,并赋予ID
    glGenBuffers(1, &EBO);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    unsigned int indices[] = {
                               0, 1, 3, //第一个三角形
                               1, 2, 3 //第二个三角形
                             };
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    //位置属性
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0); //开启VAO管理的第一个属性值

    //颜色属性
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1); //开启VAO管理的第二个属性值

    //纹理属性
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
    glEnableVertexAttribArray(2); //开启VAO管理的第三个属性值

    //解绑VBO
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    //解绑VAO
    glBindVertexArray(0);

    //创建一个程序对象
    m_shaderProgram.addShaderFromSourceFile(QOpenGLShader::Vertex,":/shaders/shapes.vert");
    m_shaderProgram.addShaderFromSourceFile(QOpenGLShader::Fragment,":/shaders/shapes.frag");
    bool success = m_shaderProgram.link();
    if(!success)
        qDebug()<<"ERR:" << m_shaderProgram.log();

    m_shaderProgram.bind();
    m_shaderProgram.setUniformValue("vertexColor", 0.0, 1.0, 0.0, 1.0);
    
    m_textureWall = new QOpenGLTexture(QImage(":/images/wall.jpg").mirrored()); //mirrored消除镜像
    m_shaderProgram.setUniformValue("textureWall", 0); //把纹理单元传给片段着色器中的采样器

    m_textureSmile = new QOpenGLTexture(QImage(":/images/awesomeface.png").mirrored()); //mirrored消除镜像
    m_shaderProgram.setUniformValue("textureSmile", 1); //把纹理单元传给片段着色器中的采样器

    m_textureSmall = new QOpenGLTexture(QImage(":/images/small.png").mirrored()); //mirrored消除镜像
    m_shaderProgram.setUniformValue("textureSmall", 2); //把纹理单元传给片段着色器中的采样器

    m_shaderProgram.setUniformValue("mixValue", mixValue);
}

void MyOpenGLWidget::resizeGL(int w, int h)
{
    Q_UNUSED(w);

    Q_UNUSED(h);
    //glViewport(0, 0, w, h);
}

void MyOpenGLWidget::paintGL()
{
    //设置墨绿色背景
    glClearColor(0.2f, 0.3f, 0.3f, 1.0f); //状态设置
    glClear(GL_COLOR_BUFFER_BIT); //状态使用

    //变换矩阵
    QMatrix4x4 matrix; //创建单位矩阵
    unsigned int time = QTime::currentTime().msec();
    matrix.translate(0.5, -0.5, 0.0); //位移
    matrix.rotate(time, 0.0f, 0.0f, 1.0f); //旋转
    m_shaderProgram.setUniformValue("RotationMatrix", matrix);

    //绘制
    m_shaderProgram.bind(); //激活程序对象
    glBindVertexArray(VAO); //绑定VAO
    m_textureWall->bind(0); //绑定激活纹理单元0
    m_textureSmile->bind(1); //绑定激活纹理单元1
    m_textureSmall->bind(2); //绑定激活纹理单元2
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, NULL); //绘图
}

void MyOpenGLWidget::keyPressEvent(QKeyEvent *event)
{
    if(event->key() == Qt::Key_Up)
        mixValue+=0.1;
    else if(event->key() == Qt::Key_Down)
        mixValue-=0.1;
    else
        return;

    if(mixValue > 1.0)
        mixValue = 1.0;

    if(mixValue < 0.0)
        mixValue = 0.0;

    makeCurrent();
    m_shaderProgram.setUniformValue("mixValue", mixValue);
    doneCurrent();
    update();

    QOpenGLWidget::keyPressEvent(event);
}

定时器的槽函数中进行界面更新

void MyOpenGLWidget::onTimeout()
{
    update();
}

更改一下顶点数据中位置数据,使矩形变小(此处不是要实现的缩放功能,只是为展示效果更看)

//顶点数据
float vertices[] = {
        //位置              //颜色             //纹理
        0.5f,  0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, //右上角
        0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, //右下角
       -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, //左下角
       -0.5f,  0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f //左上角
};

在paintGL()函数中创建matrix变量,对matrix进行旋转和位移操作,之后把值传递给顶点着色器的RotationMatrix变量。

    //变换矩阵
    QMatrix4x4 matrix;
    unsigned int time = QTime::currentTime().msec();
    matrix.translate(0.5, -0.5, 0.0); //位移
    matrix.rotate(time, 0.0f, 0.0f, 1.0f); //旋转
    m_shaderProgram.setUniformValue("RotationMatrix", matrix);

在上面的代码中旋转代码在位移代码的下一行,变量matrix会先和旋转矩阵进行运算然后再和位移矩阵进行计算,达到先旋转后位移的效果。

运行结果如下,矩形会移动到界面的右下角,同时每隔100ms进行一次旋转

注:观看OpenGL中文官网(https://learnopengl-cn.github.io/)和阿西拜的现代OpenGL入门(https://ke.qq.com/course/3999604#term_id=104150693)学习OpenGL

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

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

相关文章

Python更改Word文档的页面大小

页面大小确定文档中每个页面的尺寸和布局。在某些情况下&#xff0c;您可能需要自定义页面大小以满足特定要求。在这种情况下&#xff0c;Python可以帮助您。通过利用Python&#xff0c;您可以自动化更改Word文档中页面大小的过程&#xff0c;节省时间和精力。本文将介绍如何使…

每日一题 --- 删除链表的倒数第 N 个结点[力扣][Go]

删除链表的倒数第 N 个结点 题目&#xff1a;19. 删除链表的倒数第 N 个结点 给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], n 2 输出&#xff1a;[1,2,3,5]示例 2&#x…

Python中lambda函数使用方法

在Python中&#xff0c;lambda 关键字用于创建匿名函数&#xff08;无名函数&#xff09;&#xff0c;这些函数的特点是简洁、一次性使用&#xff0c;并且通常用于只需要一行表达式的简单场景。下面是lambda函数的基本结构和使用方法&#xff1a; 基本语法&#xff1a; lambd…

代码随想录算法训练营DAY7| C++哈希表Part.2|LeetCode:454.四数相加II、383.赎金信、15. 三数之和、18.四数之和

文章目录 454.四数相加II思路C代码 383.赎金信C 代码 15. 三数之和排序哈希法思路C代码 排序双指针法思路去重C代码 18.四数之和前言剪枝C代码 454.四数相加II 力扣题目链接 文章链接&#xff1a;454.四数相加II 视频链接&#xff1a;学透哈希表&#xff0c;map使用有技巧&…

STL的基本概念

一、STL的诞生 长久以来&#xff0c;软件界一直希望建立一种可重复利用的东西 C的面向对象和泛型编程思想&#xff0c;目的就是复用性的提升 面向对象的三大特性(简单理解) 封装&#xff1a;把属性和行为抽象出来作为一个整体来实现事和物 继承&#xff1a;子类继承父类&a…

linux下docker容器的使用

1、根据已有镜像images创建容器 1.1、查看镜像 如果是接手的别人的项目&#xff0c;需要从以往的images镜像中创建新容器&#xff0c;使用命令查看当前机器上的docker镜像&#xff1a; docker images1.2、创建容器 使用docker run 根据images镜像名创建容器&#xff0c;命令…

电阻的妙用:限流、分压、滤波,助力电路设计!

电阻可以降低电压&#xff0c;这是通过电阻的分压来实现的。事实上&#xff0c;利用电阻来降低电压只是电阻的多种功能之一。电路中的电阻与其他元件&#xff08;电容、电感&#xff09;结合用于限流、滤波等。&#xff08;本文素材来源&#xff1a;https://www.icdhs.com/news…

SV-7045V网络草坪音箱 室外网络广播POE供电石头音箱

SV-7045V网络草坪音箱 室外网络广播POE供电石头音箱 描述 IP网络广播草坪音箱 SV-7045V是深圳锐科达电子有限公司的一款防水网络草坪音箱&#xff0c;具有10/100M以太网接口&#xff0c;可将网络音源通过自带的功放和喇叭输出播放&#xff0c;可达到功率20W。用在公园&#…

Kotlin高效App爬取工具:利用HttpClient与代理服务器的技巧

在当今数字化时代&#xff0c;移动应用&#xff08;App&#xff09;数据的价值日益凸显&#xff0c;而为了获取并分析这些数据&#xff0c;开发高效的数据爬取工具变得至关重要。Kotlin作为一种现代化、功能强大的编程语言&#xff0c;与HttpClient等强大工具的结合&#xff0c…

day53 动态规划part10

121. 买卖股票的最佳时机 简单 给定一个数组 prices &#xff0c;它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 你只能选择 某一天 买入这只股票&#xff0c;并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。 返回你可…

蓝桥杯学习笔记 单词分析

试题 G: 单词分析 时间限制: 1.0s 内存限制: 512.0MB 本题总分:20 分 [问题描述] 小蓝正在学习一门神奇的语言&#xff0c;这门语言中的单词都是由小写英文字母组成&#xff0c;有些单词很长&#xff0c;远远超过正常英文单词的长度。小蓝学了很长时间也记不住一些单词&#xf…

C语言实现顺序表(增,删,改,查)

目录 一.概念&#xff1a; 1.静态顺序表&#xff1a;使用定长数组存储元素。 2.动态顺序表&#xff1a;使用动态开辟的数组存储。 二.顺序表的实现: 1.顺序表增加元素 1.检查顺序表 2.头插 3.尾插 2.顺序表删除元素 1.头删 2.尾删 3.指定位置删 3.顺序表查找元素 …

就业班 第二阶段 2401--3.25 day5 mycat读写分离

[TOC] 启动并更改临时密码 [rootmysql1~]# systemctl start mysqld && passwdgrep password /var/log/mysqld.log | awk END{ print $NF} && mysqladmin -p"$passwd" password Qwer123..; MyCAT读写分离 Mycat 是一个开源的数据库系统&#xff0c;但…

线程安全集合类原理

一、ConcurrentHashMap (一)、HashMap 1、JDK7 并发死链 采用头插法 扩容源码(扩容时并没有创建新的节点&#xff0c;只是将引用挂在不同的地方) void transfer(Entry[] newTable, boolean rehash) {int newCapacity newTable.length;for (Entry<K,V> e : table) {…

俚语加密漫谈

俚语加密是一种古老而有效的通信方式&#xff0c;将特定词语或短语在群体内赋予特殊含义&#xff0c;从而隐藏真实信息。类似于方言&#xff0c;它在历史上的应用不可忽视。随着计算机时代的到来&#xff0c;现代密码学通过数学运算编织密语&#xff0c;使得加密变得更加高深莫…

大数据开发(离线实时音乐数仓)

大数据开发&#xff08;离线实时音乐数仓&#xff09; 一、数据库与ER建模1、数据库三范式2、ER实体关系模型 二、数据仓库与维度建模1、数据仓库&#xff08;Data Warehouse、DW、DWH&#xff09;1、关系型数据库很难将这些数据转换成企业真正需要的决策信息&#xff0c;原因如…

【Java多线程】1——多线程知识回顾

1 多线程知识回顾 ⭐⭐⭐⭐⭐⭐ Github主页&#x1f449;https://github.com/A-BigTree 笔记仓库&#x1f449;https://github.com/A-BigTree/tree-learning-notes 个人主页&#x1f449;https://www.abigtree.top ⭐⭐⭐⭐⭐⭐ 如果可以&#xff0c;麻烦各位看官顺手点个star…

web 技术中前端和后端交互过程

1、客户端服务器交互过程 客户端:上网过程中,负责浏览资源的电脑,叫客户端服务器:在因特网中,负责存放和对外提供资源的电脑叫服务器 服务器的本质: 就是一台电脑,只不过相比个人电脑它的性能高很多,个人电脑中可以通过安装浏览器的形式,访问服务器对外提供的各种资源。 个人…

【每日一题】2580. 统计将重叠区间合并成组的方案数-2024.3.27

题目&#xff1a; 2580. 统计将重叠区间合并成组的方案数 给你一个二维整数数组 ranges &#xff0c;其中 ranges[i] [starti, endi] 表示 starti 到 endi 之间&#xff08;包括二者&#xff09;的所有整数都包含在第 i 个区间中。 你需要将 ranges 分成 两个 组&#xff0…

【数据结构】 HashMap源码分析(常量+构造方法+方法)

文章目录 HashMap源码分析一、成员常量二、构造方法三、方法1.此时假定为进行了无参构造&#xff0c;没有分配内存2.当发生有参构造时&#xff0c;完成对容量的大小判断后&#xff0c;将容量大小&#xff0c;传进tableSizeFor方法中&#xff1a; HashMap源码分析 一、成员常量…