googleTest 源码主线框架性分析

news2024/11/14 4:15:56

本文备忘一个主题的分析过程和结论,即,googleTest框架中是如何调用相关的测试宏的?

TEST TEST_F TEST_P 等等

1,googleTest 环境与简单示例

1.1 下载 googletest 并编译

下载:

$ git clone https://github.com/google/googletest.git
$ git checkout release-1.10.0

编译:

$ mkdir build
$ cd build/
$ export CXXFLAGS="-Wno-error=maybe-uninitialized"
$ cmake ..
$ make -j
$ ls lib/

 默认为 release,若debug版本则须:

$ cmake .. -DCMAKE_BUILD_TYPE=Debug

成果:

1.2 示例1 验证函数 add

源码

#include <iostream>
#include "gtest/gtest.h"

int add_int_int(int a, int b){
	return a+b;
}

TEST(SumFuncTest, twoNumbers){
	EXPECT_EQ(add_int_int(3,4),7);
	EXPECT_EQ(27, add_int_int(9, 18));
}

GTEST_API_ int main(int argc, char** argv) {
	printf("Running main() from %s\n", __FILE__);
	testing::InitGoogleTest(&argc, argv);
	return RUN_ALL_TESTS();
}

运行:

1.3 示例 2

#include <gtest/gtest.h>

int Foo(int a, int b)
{
    if (a == 0 || b == 0)
    {
        throw "don't do that";
    }

    int c = a % b;

    if (c == 0)
        return b;

    return Foo(b, c);
}


TEST(FooTest, HandleNoneZeroInput)
{
    EXPECT_EQ(2, Foo(4, 10));
    EXPECT_EQ(6, Foo(30, 18));
}

g++ foo.cpp -I ../../googletest/googletest/include -L ../../googletest/build_dbg/lib -lgtest -lgtest_main

编译运行:

1.4 示例3

源码:

#include <iostream>
#include "gtest/gtest.h"


// add_util.cc

float add_from_left(float a, float b, float c, float d,	float e)
{
	float sum = 0.0;
	sum += c;
	sum += a;
	sum += b;
	//sum += c;
	sum += d;
	sum += e;
/*
	sum += a;
	sum += b;
	sum += c;
	sum += d;
	sum += e;
*/
	printf("add_from_left: sum = %f\n", sum);
	return sum;
}


float add_from_right(float a, float b, float c,	float d, float e)
{
	float sum = 0.0;

	sum += e;
	sum += d;
	sum += c;
	sum += b;
	sum += a;

	printf("add_from_right: sum = %f\n", sum);
	return sum;
}

int sum(int a, int b){
	return a+b;
}




TEST(AddFuncTest, floatVSfloat) {
	printf("AddFuncTest: float  sum = %f\n", 1.238f + 3.7f + 0.000353265f + 7898.3f + 12.23209f);
	printf("AddFuncTest: double sum = %f\n", 12.23209 + 7898.3 + 0.000353265 + 3.7 + 1.238);
	EXPECT_EQ(1.238f + 3.7f + 0.000353265f + 7898.3f + 12.23209f, add_from_left(1.238, 3.7, 0.000353265, 7898.3, 12.23209));
	EXPECT_EQ(1.238f + 3.7f + 0.000353265f + 7898.3f + 12.23209f, add_from_right(1.238, 3.7, 0.000353265, 7898.3, 12.23209));
//
}

TEST(AddFuncTest, doubleVSfloat) {
	printf("AddFuncTest: float  sum = %f\n", 1.238f + 3.7f + 0.000353265f + 7898.3f + 12.23209f);
	printf("AddFuncTest: double sum = %f\n", 12.23209 + 7898.3 + 0.000353265 + 3.7 + 1.238);
	EXPECT_EQ(1.238f + 3.7f + 0.000353265f + 7898.3f + 12.23209f, add_from_left(1.238, 3.7, 0.000353265, 7898.3, 12.23209));
	EXPECT_EQ(1.238 + 3.7 + 0.000353265 + 7898.3 + 12.23209, add_from_right(1.238, 3.7, 0.000353265, 7898.3, 12.23209));
//
}

TEST(SumFuncTest, twoNumbers){
	EXPECT_EQ(sum(3,4),7);
	EXPECT_EQ(27, sum(9, 18));
}

GTEST_API_ int main(int argc, char** argv) {
	printf("Running main() from %s\n", __FILE__);
	testing::InitGoogleTest(&argc, argv);
	return RUN_ALL_TESTS();
}

Makefile

EXE := hello_gtest_ex hello_gtest_add_int_int
all: $(EXE)

%: %.cpp
	g++ -O0 -fno-toplevel-reorder $< -o $@ $(INC) $(LD_FLAGS)

INC := -I../googletest/googletest/include/
LD_FLAGS := -L../googletest/build/lib/ -lgtest -lgtest_main


.PHONY: clean
clean:
	-rm -rf $(EXE)

2,示例与源码分析

使用最简单的测试示例,聚焦googletest本身的代码逻辑

观察点,main 函数如何调用到 TEST(...){...} 这种结构中的代码

两种方式互相印证:

方式1,通过编译器的预编译指令 g++ -E ... 生成展开代码;

方式2,通过跟踪源代码,来份些TEST等的展开结果

2.1 TEST

示例代码如上:

simple_gtest.cpp


#include "gtest/gtest.h"

int add_int_int(int a, int b){
	return a+b;
}

TEST(SumFuncTest, twoNumbers){
	EXPECT_EQ(add_int_int(3,4),7);
}

方式1:

g++ -E simple_gtest.cpp -o simple_gtest.i

展开后,simple_gtest.i文件有8W多行,但是其中对我们理解有意义的也就最尾巴上的几行:


int add_int_int(int a, int b){
 return a+b;
}

static_assert(sizeof("SumFuncTest") > 1, "test_suite_name must not be empty"); 
static_assert(sizeof("twoNumbers") > 1, "test_name must not be empty"); 

class SumFuncTest_twoNumbers_Test : public ::testing::Test { 
public: 
        SumFuncTest_twoNumbers_Test() {} 
private:
        virtual void TestBody(); 
        static ::testing::TestInfo* const test_info_ __attribute__ ((unused)); 
        SumFuncTest_twoNumbers_Test(SumFuncTest_twoNumbers_Test const &) = delete; void operator=(SumFuncTest_twoNumbers_Test const &) = delete; 
}; 

::testing::TestInfo* const SumFuncTest_twoNumbers_Test::test_info_ = 
                ::testing::internal::MakeAndRegisterTestInfo( "SumFuncTest", "twoNumbers", nullptr, nullptr, ::testing::internal::CodeLocation("simple_gtest.cpp", 8), (::testing::internal::GetTestTypeId()), ::testing::internal::SuiteApiResolver< ::testing::Test>::GetSetUpCaseOrSuite("simple_gtest.cpp", 8), ::testing::internal::SuiteApiResolver< ::testing::Test>::GetTearDownCaseOrSuite("simple_gtest.cpp", 8), new ::testing::internal::TestFactoryImpl<SumFuncTest_twoNumbers_Test>); 

void SumFuncTest_twoNumbers_Test::TestBody()
{
        switch (0) 
          case 0: 
          default: 
              if (const ::testing::AssertionResult gtest_ar = (::testing::internal::EqHelper::Compare("add_int_int(3,4)", "7", add_int_int(3,4), 7))) 
                  ; 
              else 
                  ::testing::internal::AssertHelper(::testing::TestPartResult::kNonFatalFailure, "simple_gtest.cpp", 9, gtest_ar.failure_message()) = ::testing::Message();
}

分析这段代码会发现,

TEST被展开成为了一个 class SumFuncTest_twoNumbers_Test

它有一个成员函数 TestBody(){....}

观察上述代码中最后一个函数体:void SumFuncTest_twoNumbers_Test::TestBody()

其中出现了被测试的函数等。

这说明,这个函数体中的代码才是是被测试内容,而其外围都是框架。

框架部分只需要把这中类的一个实例添加到某个链表中,然后依次迭代执行每个类的 TestBody成员函数,既可以完成测试任务。

本例中的 class 如下:

通过方法2.来验证一下展开的结果:

第一部分,class 宏

关联TEST宏,我们可以找到如下内容:


#define TEST(test_suite_name, test_name) GTEST_TEST(test_suite_name, test_name)


#define GTEST_TEST(test_suite_name, test_name)             \
  GTEST_TEST_(test_suite_name, test_name, ::testing::Test, \
              ::testing::internal::GetTestTypeId())




// Expands to the name of the class that implements the given test.
#define GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) \
  test_suite_name##_##test_name##_Test

// Helper macro for defining tests.
#define GTEST_TEST_(test_suite_name, test_name, parent_class, parent_id)       \
  static_assert(sizeof(GTEST_STRINGIFY_(test_suite_name)) > 1,                 \
                "test_suite_name must not be empty");                          \
  static_assert(sizeof(GTEST_STRINGIFY_(test_name)) > 1,                       \
                "test_name must not be empty");                                \
  class GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)                     \
      : public parent_class {                                                  \
   public:                                                                     \
    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)() = default;            \
    ~GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)() override = default;  \
    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)                         \
    (const GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) &) = delete;     \
    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) & operator=(            \
        const GTEST_TEST_CLASS_NAME_(test_suite_name,                          \
                                     test_name) &) = delete; /* NOLINT */      \
    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)                         \
    (GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) &&) noexcept = delete; \
    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) & operator=(            \
        GTEST_TEST_CLASS_NAME_(test_suite_name,                                \
                               test_name) &&) noexcept = delete; /* NOLINT */  \
                                                                               \
   private:                                                                    \
    void TestBody() override;                                                  \
    static ::testing::TestInfo* const test_info_ GTEST_ATTRIBUTE_UNUSED_;      \
  };                                                                           \
                                                                               \
  ::testing::TestInfo* const GTEST_TEST_CLASS_NAME_(test_suite_name,           \
                                                    test_name)::test_info_ =   \
      ::testing::internal::MakeAndRegisterTestInfo(                            \
          #test_suite_name, #test_name, nullptr, nullptr,                      \
          ::testing::internal::CodeLocation(__FILE__, __LINE__), (parent_id),  \
          ::testing::internal::SuiteApiResolver<                               \
              parent_class>::GetSetUpCaseOrSuite(__FILE__, __LINE__),          \
          ::testing::internal::SuiteApiResolver<                               \
              parent_class>::GetTearDownCaseOrSuite(__FILE__, __LINE__),       \
          new ::testing::internal::TestFactoryImpl<GTEST_TEST_CLASS_NAME_(     \
              test_suite_name, test_name)>);                                   \
  void GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)::TestBody()

其中的如下两行:

class GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) \

: public parent_class { \

TEST 的宏充分展开后,会根据TEST(X,Y) 括号中的X、Y字串定义一个完整的类,并且包含成员函数:
  TestBody()

但是展开的内容中,没有这个函数的函数体。

这个函数体正好就是TEST(X,Y){Z} 中,{Z}的这个部分,即,
 
  TestBody(){Z}

只需要在整个测试系统中,讲上面展开生成的class的一个实例,insert进一个链表中,并依次迭代执行链表的每一对象的成员函数 TestBody(){Z},即可达到测试目的。

第二部分,函数体中的宏

关于 EXPECT_EQ,我们会发现如下定义:

#define EXPECT_EQ(val1, val2) \
  EXPECT_PRED_FORMAT2(::testing::internal::EqHelper::Compare, val1, val2)

而其中的 EXPECT_PRED_FORMAT2 又被展开为如下:

// Binary predicate assertion macros.
#define EXPECT_PRED_FORMAT2(pred_format, v1, v2) \
  GTEST_PRED_FORMAT2_(pred_format, v1, v2, GTEST_NONFATAL_FAILURE_)

又 GTEST_PRED_FORMAT2_ 被定义为:

#define GTEST_PRED_FORMAT2_(pred_format, v1, v2, on_failure) \
  GTEST_ASSERT_(pred_format(#v1, #v2, v1, v2), on_failure)

而且其中的 GTEST_ASSERT_  被展开为:

#define GTEST_ASSERT_(expression, on_failure)                   \
  GTEST_AMBIGUOUS_ELSE_BLOCKER_                                 \
  if (const ::testing::AssertionResult gtest_ar = (expression)) \
    ;                                                           \
  else                                                          \
    on_failure(gtest_ar.failure_message())

其中 GTEST_AMBIGUOUS_ELSE_BLOCKER_ 展开为:

#define GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
  switch (0)                          \
  case 0:                             \
  default:  // NOLINT

于是得到函数体为:

总之,只需要调用这个 TestBody() 函数,即可完成测试任务。

2.2 TEST_F

2.2.1 小示例编译与运行

保持关于 TEST 宏分析的记忆,我们以一个简单的示例来分析 TEST_F 宏,

#include <gtest/gtest.h>

class SampleTestWithFixture : public ::testing::Test {
protected:
  void SetUp() override {
    a_ = 1;
    b_ = 2;
  }
  int a_;
  int b_;
};

TEST_F(SampleTestWithFixture, Case2) {
  a_ = 3;
  EXPECT_EQ(a_ + b_, 5);
}

Makefile:

EXE := hello_gtest_f
all: $(EXE)

%: %.cpp
	g++ -g -fno-toplevel-reorder $< -o $@ $(INC) $(LD_FLAGS)


# g++ -E hello_gtest_f.cpp  -I ../googletest/googletest/include/ -o hello_gtest_f.i
#g++ -g -fno-toplevel-reorder $< -o $@ $(INC) $(LD_FLAGS)

INC := -I../../googletest/googletest/include/
LD_FLAGS := -L../../googletest/build_dbg/lib/ -lgtest -lgtest_main


.PHONY: clean
clean:
	-rm -rf $(EXE)

编译,确保能够正确运行:

$ make

$ ./hello_gtest_f

2.2.2 TEST_F 宏展开分析

$ g++ -E hello_gtest_f.cpp  -I ../googletest/googletest/include/ -o hello_gtest_f.i

生成的预处理后的文件 hello_gtest_f.i 主要内容还是在文件的尾巴上,摘录调整格式如下:

class SampleTestWithFixture : public ::testing::Test {
protected:
  void SetUp() override {
    a_ = 1;
    b_ = 2;
  }
  int a_;
  int b_;
};

static_assert(sizeof("SampleTestWithFixture") > 1, "test_suite_name must not be empty");
static_assert(sizeof("Case2") > 1, "test_name must not be empty");

class SampleTestWithFixture_Case2_Test : public SampleTestWithFixture{
public:
  SampleTestWithFixture_Case2_Test() = default;
  ~SampleTestWithFixture_Case2_Test() override = default;
  SampleTestWithFixture_Case2_Test(const SampleTestWithFixture_Case2_Test&) = delete;
  SampleTestWithFixture_Case2_Test& operator=( const SampleTestWithFixture_Case2_Test&) = delete;
  SampleTestWithFixture_Case2_Test(SampleTestWithFixture_Case2_Test &&) noexcept = delete;
  SampleTestWithFixture_Case2_Test& operator=(SampleTestWithFixture_Case2_Test &&) noexcept = delete;

  private: void TestBody() override;
  static ::testing::TestInfo* const test_info_ __attribute__((unused));
};

::testing::TestInfo* const SampleTestWithFixture_Case2_Test::test_info_
    =::testing::internal::MakeAndRegisterTestInfo("SampleTestWithFixture",
                                                  "Case2",
                                                  nullptr,
                                                  nullptr,
                                                  ::testing::internal::CodeLocation("hello_gtest_f.cpp", 27),
                                                  (::testing::internal::GetTypeId<SampleTestWithFixture>()),
                                                  ::testing::internal::SuiteApiResolver< SampleTestWithFixture>::GetSetUpCaseOrSuite("hello_gtest_f.cpp", 27),
                                                  ::testing::internal::SuiteApiResolver< SampleTestWithFixture>::GetTearDownCaseOrSuite("hello_gtest_f.cpp", 27),
                                                  new ::testing::internal::TestFactoryImpl<SampleTestWithFixture_Case2_Test>);

void SampleTestWithFixture_Case2_Test::TestBody()
{
  a_ = 3;
  switch (0)
    case 0:
    default:
      if (const ::testing::AssertionResult gtest_ar = (::testing::internal::EqHelper::Compare( "a_ + b_" ,  "5" ,  a_ + b_ ,  5 )))
        ;
      else
        ::testing::internal::AssertHelper(::testing::TestPartResult::kNonFatalFailure, "hello_gtest_f.cpp", 29, gtest_ar.failure_message())
            = ::testing::Message();
}

 

跟 TEST 宏的展开类似,组合 TEST_F(X,Y) 的两个参数,构成一个新的类

class X_Y_Test :public SampleTestWithFixture{

...

... TestBody()

}

宏展开的新类中也有一个成员函数 TestBody();

其中 SampleTestWithFixture 是自己定义的类,会被 X_Y_Test 类共有继承走。

而 TEST_F(...){body} 的类似函数体的部分 {body},也同样被安排成为了 TestBody函数的函数体。

接下来,gtest框架会通过成员 X_Y_Test::test_info_ 的静态赋值过程,将本测试用例挂进系统的代运行链表,届时依次迭代 调用 X_Y_Test::TestBody(); 实现测试感兴趣代码的目的。

2.2.3 总结 TEST_F

TEST_F的意图:

TEST_F的目的是为了把关系密切的测试问题汇总到一个class中来进行测试,可以共用同一个类的对象的上下文成员数据。

TEST_F 中,成员函数的执行顺序:

那么,成员函数 Setup( ) 在什么时候执行呢?

先说答案:

  1      X_Y_Test() 构造函数;//c++ 语法

  2      Setup();                      //数据预备,资源申请

  3      TestBody();                //测试部分

  4      TearDown();               //资源释放

  5      X_Y_Test() 析构函数;//c++ 语法

改造刚才的示例:

#include <gtest/gtest.h>

class SampleTestWithFixture : public ::testing::Test {
public:
SampleTestWithFixture(){std::cout<<"construct_STWF"<<std::endl;}
~SampleTestWithFixture(){std::cout<<"destruct_STWF"<<std::endl;}
protected:
  void SetUp() override {
    std::cout <<"Hello setupupup()000"<<std::endl;
    a_ = 1;
    b_ = 2;
    std::cout <<"Hello setupupup()111"<<std::endl;
  }
  void TearDown() override {
    std::cout <<"Hello teardownnn()000"<<std::endl;
    a_ = 4;
    b_ = 5;
    std::cout <<"Hello teardownnn()111"<<std::endl;
  }
  int a_;
  int b_;
};

TEST_F(SampleTestWithFixture, Case2) {
  std::cout <<"test_f Casess222"<<std::endl;
  a_ = 3;
  EXPECT_EQ(a_ + b_, 5);
}

编译运行:

TEST_F函数体中的部分的一些宏,跟TEST中的一样,展开成为一些比较语句。

2.3 TEST_P

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

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

相关文章

5 C 语言数组与字符串的全面解析

目录 1 数组的概念与特性 1.1 什么是数组 1.2 数组的特点 1.3 数组的用途 2 一维数组的定义与初始化 2.1 一维数组的定义 2.2 声明与定义的区别 2.3 一维数组的多种初始化 3 数组名的命名规则与作用 3.1 数组名的命名规则 3.2 数组名的作用 4 一维数组在内存中的存…

实战篇(十二):如何使用 Processing 创建一个多功能的简易吃豆人游戏

如何使用 Processing 创建一个多功能的简易吃豆人游戏 文章目录 如何使用 Processing 创建一个多功能的==简易==吃豆人游戏引言准备工作第一步:设置基本框架第二步:创建 Pacman 类第三步:创建 Obstacle 类第四步:添加分数系统第五步:运行游戏完整代码结论参考资料引言 吃…

Python基础知识——(005)

文章目录 P21——20. 比较运算符 P22——21. 逻辑运算符 P23——22. 位运算和运算符的优先级 P24——23. 本章总结和章节习题 P21——20. 比较运算符 示例3-17—比较运算符的使用&#xff1a; P22——21. 逻辑运算符 示例3-18—逻辑运算符的使用&#xff1a; print(True and T…

van-dialog 组件调用报错

报错截图 报错原因 这个警告表明 vue 在渲染页面时遇到了一个未知的自定义组件 <van-dialog>&#xff0c;并且提示可能是由于未正确注册该组件导致的。在 vue 中&#xff0c;当我们使用自定义组件时&#xff0c;需要先在 vue 实例中注册这些组件&#xff0c;以便 vue 能…

基于关键字驱动设计Web UI自动化测试框架!

引言 在自动化测试领域&#xff0c;关键字驱动测试&#xff08;Keyword-Driven Testing, KDT&#xff09;是一种高效且灵活的方法&#xff0c;它通过抽象测试用例中的操作为关键字&#xff0c;实现了测试用例与测试代码的分离&#xff0c;从而提高了测试脚本的可维护性和可扩展…

5.Fabric的共识机制

在Fabric中,有以下3中典型共识机制。 Solo共识 solo共识机制只能用于单节点模式,即只能有一个Orderer节点,因此,其共识过程很简单,每接收到一个交易信息,就在共识模块的控制下产生区块并广播给节点存储到账本中。 Solo 模式下的共识只适用于一个Orderer节点,所以可以在…

AI 驱动下的一体化分布式数据库:滴滴、快手、中国恩菲、好未来、翼鸥教育共话创新应用实践|OceanBase Meetup 精彩回顾

7月6日&#xff0c;OceanBase Meetup 北京站——“AI 驱动下的一体化分布式数据库&#xff1a;跨行业多场景的创新应用与实战”举办。来自滴滴、快手、中国恩菲、好未来、翼鸥教育、蚂蚁集团及OceanBase等众多行业技术专家与资深用户&#xff0c;围绕众多用户关注的AI 与数据库…

Performance Metrics in Evaluating Stable Diffusion Models

1.Performance Metrics in Evaluating Stable Diffusion Models 笔记来源&#xff1a; 1.Performance Metrics in Evaluating Stable Diffusion Models 2.Denoising Diffusion Probabilistic Models 3.A simple explanation of the Inception Score 4.What is the inception s…

【LLM】-05-提示工程-部署Langchain-Chat

目录 1、软硬件要求 1.1、软件要求 1.2、硬件要求 1.3、个人配置参考 2、创建cuda环境 3、下载源码及模型 4、配置文件修改 5、初始化知识库 5.1、训练自己的知识库 6、启动 7、API接口调用 7.1、使用openai 参考官方wiki&#xff0c;本文以Ubuntu20.04_x64&#xf…

揭秘!电源炼成记:从基础原理到高端设计的全面解析

文章目录 初始构想&#xff1a;需求驱动设计原理探索&#xff1a;选择适合的拓扑结构精细设计&#xff1a;元器件选型与布局环路稳定&#xff1a;控制策略与补偿网络严格测试&#xff1a;验证与优化持续改进&#xff1a;创新与技术迭代《硬件十万个为什么&#xff08;电源是怎样…

云计算实训11——web服务器的搭建、nfs服务器的搭建、备份静态文件、基于linux和windows实现文件共享

一、搭建web服务器 1.关闭firewall和selinux 关闭防火墙 systemctl stop firewalld systemctl disable firewalld 停用selinux setenforce 0 配置文件中让sellinux不再启动 vim /etc/selinux/config SELINUXpermissive 2.编辑dns配置文件 vim /etc/resolv.conf nameserver 114.…

Sql Server缓冲池、连接池等基本知识(附Demo)

目录 前言1. 缓存池2. 连接池3. 彩蛋 前言 基本的知识推荐阅读&#xff1a; java框架 零基础从入门到精通的学习路线 附开源项目面经等&#xff08;超全&#xff09;Mysql优化高级篇&#xff08;全&#xff09;Mysql底层原理详细剖析常见面试题&#xff08;全&#xff09; 1…

【深度学习入门篇 ⑪】自注意力机制

【&#x1f34a;易编橙&#xff1a;一个帮助编程小伙伴少走弯路的终身成长社群&#x1f34a;】 大家好&#xff0c;我是小森( &#xfe61;ˆoˆ&#xfe61; ) &#xff01; 易编橙终身成长社群创始团队嘉宾&#xff0c;橙似锦计划领衔成员、阿里云专家博主、腾讯云内容共创官…

基于微信小程序+SpringBoot+Vue的大学生科技竞赛管理系统(带1w+文档)

基于微信小程序SpringBootVue的大学生科技竞赛管理系统(带1w文档) 基于微信小程序SpringBootVue的大学生科技竞赛管理系统(带1w文档) 本系统中采用的开发工具包括软件工具和硬件工具&#xff0c;软件采用了Java语言和MySQL数据库&#xff0c;利用微信小程序技术&#xff0c;框架…

从零训练一个多模态LLM:预训练+指令微调+对齐+融合多模态+链接外部系统

本文尝试梳理一个完整的多模态LLM的训练流程。包括模型结构选择、数据预处理、模型预训练、指令微调、对齐、融合多模态以及链接外部系统等环节。 01 准备阶段 1 模型结构 目前主要有三种模型架构&#xff0c;基于Transformer解码器&#xff0c;基于General Language Model&a…

51单片机嵌入式开发:16、STC89C52RC 嵌入式之 步进电机28BYJ48、四拍八拍操作

STC89C52RC 嵌入式之 步进电机28BYJ48、四拍八拍操作 STC89C52RC 之 步进电机28BYJ48操作1 概述1.1 步进电机概述1.2 28BYJ48概述 2 步进电机工作原理2.1 基本原理2.2 28BYJ48工作原理2.3 28BYJ48控制原理 3 电路及软件代码实现4 步进电机市场价值 STC89C52RC 之 步进电机28BYJ…

英语(二)-我的学习方式

章节章节汇总我的学习方式历年真题作文&范文 目录 1、背单词 2、学语法 3、做真题 4、胶囊助学计划 写在最前&#xff1a;我是零基础&#xff0c;初二就听天书的那种。 本专栏持续更新学习资料 1、背单词 单词是基础&#xff0c;一定要背单词&#xff01;考纲要求要…

瑞吉外卖学习(一)

pom文件的导入中 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.6.6</version><relativePath/> <!-- lookup parent from repository --></…

【STM32 HAL库】DMA+串口

DMA 直接存储器访问 DMA传输&#xff0c;将数据从一个地址空间复制到另一个地址空间。-----“数据搬运工”。 DMA传输无需CPU直接控制传输&#xff0c;也没有中断处理方式那样保留现场和恢复现场&#xff0c;它是通过硬件为RAM和IO设备开辟一条直接传输数据的通道&#xff0c…

构建网络安全之盾:应对“微软蓝屏”教训的全面策略

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…