项目gtest测试框架 - GoogleTest(十)

article/2025/9/10 16:00:44

精简版本的C++单元测试框架 ,通过编写这个简单的测试框架,将有助于我们理解gtest。

1. 目录

类型文件说明
文件./CMakeLists.txt整体项目工程文件
目录./debiandeb包打包脚本目录,未实现
目录./rpmrpm打包目录,rpm打包的详细内容可以看链接
目录./src源码目录,所有的源码都放在该目录下。
文件./src/arvapi.h动态链接库的头文件。
文件./src/arvapi.cpp 动态链接库的源文件。
文件./src/CMakeLists.txt动态链接库的g工程文件
目录./tests所有的测试文件都放入这个目录中
文件./tests/CMakeLists.txt测试工程文件,其中有可能包含模糊测试等。
目录 ./tests/UnitTest单元测试目录
目录./tests/UnitTest/src与源程序对应的目录
文件./tests/UnitTest/src/CMakeLists.txt单元测试项目文件
文件./tests/UnitTest/main.cpp单元测试主程序
文件./tests/UnitTest/src/ut_arvapi.cpp与原程序中的源文件一致。以ut开头一一对应源文件。例如:源文件名称为test.cpp,单元测试的源文件命名为ut_test.cpp

 文件内容

源程序文件列表及说明
./CMakeLists.txt

该工程的文件的目的为了包含各个子工程文件,对于通用的环境变量测试也可以写在这里。

cmake_minimum_required(VERSION 3.9.5)
 
add_subdirectory(src)
 
add_subdirectory(tests)
./src/CMakeLists.txt  

源程序项目工程文件。

# 设置变量,给LIB_NAME赋值为arv
set(LIB_NAME arv)
 
# 设定项目名称
project(${LIB_NAME})
 
# 设置编译源文件。
file(GLOB_RECURSE c_files RELATIVE ${PROJECT_SOURCE_DIR}  *.cpp)
# 设置编译头文件
file(GLOB_RECURSE h_files RELATIVE ${PROJECT_SOURCE_DIR}  *.h)
 
# 将源文件和头文件编译成共享库文件。${LIB_NAME}为库文件的文件名。
add_library(${LIB_NAME} SHARED ${h_files} ${c_files})
./src/arvapi.h

动态链接库的头文件。该文件定义了动态链接库的头文件。

#ifndef ARVAPI_H
#define ARVAPI_H
 
#ifdef __cplusplus
extern "C"{
#endif
 
int arv_add(int a,int b);
 
#ifdef __cplusplus
}
#endif
 
#endif // ARVAPI_H
./src/arvapi.cpp

源程序文件。

#include "arvapi.h"
 
#ifdef __cplusplus
extern "C"{
#endif
 
int arv_add(int a,int b){
 
    return a+b;
}
 
#ifdef __cplusplus
}
#endif
单元测试测试文件列表及说明
./tests/CMakeLists.txt

该工程文件包含了所有的测试工程,本工程中只有单元测试,所以只是添加了单元测试,如果是还有模糊测试等其他类型的测试可以在tests目录下新建更多的测试工程。

add_subdirectory(UnitTest)
./tests/UnitTest/main.cpp

main函数包含文件,其实该文件不写gtest也可以运行。

#include <gtest/gtest.h>
 
int main(int argc,char *  argv[]){
    testing::InitGoogleTest(&argc,argv);
    return  RUN_ALL_TESTS();
}
RUN_ALL_TESTS宏定义,表示执行所有的单元测试,执行循序如下:

1. UnitTest::Run()
2. UnitTestImpl::RunAllTests()
3. TestCase::Run()
4. Test::Run()
5. Test::TestBody()
如果main函数不适用return RUN_ALL_TESTS(); 直接使用RUN_ALL_TESTS();可能会出现警告如下图。

6: warning: ignoring return value of ‘int RUN_ALL_TESTS()’, declared with attribute warn_unused_result [-Wunused-result]
     RUN_ALL_TESTS();
     ~~~~~~~~~~~~~^~
如果testing::InitGoogleTest(&argc,argv);没有调用会出现一下错误提示. 

IMPORTANT NOTICE - DO NOT IGNORE:
This test program did NOT call testing::InitGoogleTest() before calling RUN_ALL_TESTS(). This is INVALID. Soon Google Test will start to enforce the valid usage. Please fix it ASAP, or IT WILL START TO FAIL.


 ./tests/UnitTest/CMakeLists.txt

单元测试项目文件。

# 设置变量,给EXE_NAME 赋值为arv-test
set(EXE_NAME arv-test)
# 设置项目名称为${EXE_NAME}
project(${EXE_NAME})
 
# 引用Gtest项目
find_package(GTest REQUIRED)
# 引入Gtest项目的头文件
include_directories(${GTEST_INCLUDE_DIRS})
 
# 设置代码覆盖率的相关参数
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread -fprofile-arcs -ftest-coverage -lgcov")
 
# 设置源文件目录地址,对于本项目地址的相对地址
set(SRCELATIVEPATH ../../)
include_directories(${PROJECT_SOURCE_DIR})
include_directories(${PROJECT_SOURCE_DIR}/${SRCELATIVEPATH}/src)
 
 
# 设置编译源文件,其中包含了源程序文件,以及单元测试的源文件
file(GLOB_RECURSE c_files RELATIVE ${PROJECT_SOURCE_DIR}  main.cpp src/*.cpp ${SRCELATIVEPATH}/src/*.cpp)
# 设置编译头文件,其中包含了源程序的文件,以及单元测试的头文件。
file(GLOB_RECURSE h_files RELATIVE ${PROJECT_SOURCE_DIR}  ${SRCELATIVEPATH}/src/*.h)
 
# 生成单元测试克执行程序
add_executable(${EXE_NAME} ${h_files} ${c_files})
 
# gtest需要依赖于gtest动态链接库。
target_link_libraries(
    ${EXE_NAME}
    ${GTEST_LIBRARIES}
    ${GTEST_MAIN_LIBRARIES}
)
./tests/UnitTest/src/ut_arv.cpp

单元测试文件,该文件针对与arv.cpp文件的单元测试用例集合。

如果还有其他的源文件可以按照命名要求进行设置。例如,XXX.cpp的源文件,单元测试文件可以为ut_XXX.cpp

#include "arvapi.h"
#include <gtest/gtest.h>
 
TEST(arv_add,add_h_a){
    int res =arv_add(5,6);
    EXPECT_EQ(res, 11);
}
 

单元测试理论
宏测试
1.1 TEST
TEST宏的作用是创建一个简单测试。他的源码如下:

#define GTEST_TEST(test_case_name, test_name)\
  GTEST_TEST_(test_case_name, test_name, \
              ::testing::Test, ::testing::internal::GetTestTypeId())
 
// Define this macro to 1 to omit the definition of TEST(), which
// is a generic name and clashes with some other libraries.
#if !GTEST_DONT_DEFINE_TEST
# define TEST(test_case_name, test_name) GTEST_TEST(test_case_name, test_name)
#endif
test_case_name:测试套件,通常用来定义一个行数名称。因为一个函数的测试可能不止一个那么我们可以通过定义套件的方式进行区分
test_name:测试名称。
例如:
我们测试一下函数 strcp(char * str1,char* str2);

// 测试字符串1为空的情况。
TEST(strcp,str1_is_null)
{
    char * str1= nullptr;
    char * str2 = "asdf";
    strcp(str1,str2);
}
// 测试字符串2为空的情况。
TEST(strcp,str2_is_null)
{
    char * str1=  "asdf";
    char * str2 = nullptr;
    strcp(str1,str2);
}
密码套件将测试进行分类。 

最终的测试名称为

strcp.str1_is_null

strcp.str2_is_null

1.2 TEST_F
TEST_F(test_fixture, test_name)
test_fixture:为测试的类名,并且他也会作为测试套件的名字,该类必须继承 testing::Test。

test_name:测试名称。

// Defines a test that uses a test fixture.
//
// The first parameter is the name of the test fixture class, which
// also doubles as the test case name.  The second parameter is the
// name of the test within the test case.
//
// A test fixture class must be declared earlier.  The user should put
// the test code between braces after using this macro.  Example:
//
//   class FooTest : public testing::Test {
//    protected:
//     虚函数SetUp将在所有测试用例之前调用,当有变量需要初始化时,应当定义这个函数
//     virtual void SetUp() { b_.AddElement(3); }
//
//     Foo a_;
//     Foo b_;
//   };
//
//   TEST_F(FooTest, InitializesCorrectly) {
//     EXPECT_TRUE(a_.StatusIsOK());
//   }
//
//   TEST_F(FooTest, ReturnsElementCountCorrectly) {
//     EXPECT_EQ(a_.size(), 0);
//     EXPECT_EQ(b_.size(), 1);
//   }
 
#define TEST_F(test_fixture, test_name)\
  GTEST_TEST_(test_fixture, test_name, test_fixture, \
              ::testing::internal::GetTypeId<test_fixture>())
testing::Test提供接口SetUp,虚函数SetUp将在所有测试用例之前调用,当有变量需要初始化时,应当定义这个函数
断言
Gtest中,断言的宏可以理解为分为两类,一类是ASSERT系列,一类是EXPECT系列。

ASSERT_* 系列的断言(致命的断言),当检查点失败时,退出当前函数(注意:并非退出当前案例)。

EXPECT_* 系列的断言(非致命性断言),当检查点失败时,继续执行下一个检查点(每一个断言表示一个测试点)。
 

1.1 布尔型检查
致命的断言    非致命性断言    条件
ASSERT_TRUE(condition);    EXPECT_TRUE(condition);    true
ASSERT_FALSE(condition);    EXPECT_FALSE(condition);    false
1.2 二值检查
致命的断言    非致命性断言    条件
ASSERT_EQ(v1, v2);    EXPECT_EQ(v1, v2);    v1== v2
ASSERT_NE(v1, v2);    EXPECT_NE(v1, v2);    v1 != v2
ASSERT_LT(v1, v2);    EXPECT_LT(v1, v2);    v1 < v2
ASSERT_LE(v1, v2);    EXPECT_LE(v1, v2);    v1 <= v2
ASSERT_GT(v1, v2);    EXPECT_GT(v1, v2);    v1 > v2
ASSERT_GE(v1, v2);    EXPECT_GE(v1, v2);    v1 >= v2
1.3字符串检查
致命的断言    非致命性断言    条件
ASSERT_STREQ(str1, str2);    EXPECT_STREQ(str1,str2);    
str1和str2两个C字符串有相同的内容

ASSERT_STRNE(str1, str2);    EXPECT_STRNE(str1, str2);    str1和str2两个C字符串有不同的内容
ASSERT_STRCASEEQ(str1, str2);    EXPECT_STRCASEEQ(str1, str2);    两个内容在忽略大小写的前提下相等。
ASSERT_STRCASENE(str1, str2);    EXPECT_STRCASENE(str1, str2);    两个内容在不忽略的大小写的前提下不相等。
*STREQ*和*STRNE*同时支持char*和wchar_t*类型的,*STRCASEEQ*和*STRCASENE*却只接收char*

1.4异常检查
致命的断言    非致命性断言    条件
ASSERT_THROW(statement, exception_type);    EXPECT_THROW(statement, exception_type);    statement throws an exception of the given type
ASSERT_ANY_THROW(statement);    EXPECT_ANY_THROW(statement);    statement throws an exception of any type
ASSERT_NO_THROW(statement);    EXPECT_NO_THROW(statement);    statement doesn't throw any exception
1.5浮点检查
expected: 期望的浮点值

actual:最终的浮点值

致命的断言    非致命性断言    条件
ASSERT_FLOAT_EQ(expected, actual);    EXPECT_FLOAT_EQ(expected, actual);    两浮点数相等
ASSERT_DOUBLE_EQ(expected, actual);    EXPECT_DOUBLE_EQ(expected, actual);    想浮点数不等
在对比数据方面,我们往往会讨论到浮点数的对比。因为在一些情况下,浮点数的计算精度将影响对比结果,所以这块都会单独拿出来说。GTest对于浮点数的对比也是单独的

1.6相近值检查
致命的断言    非致命性断言    条件
ASSERT_NEAR(val1, val2, abs_error);    EXPECT_NEAR(val1, val2, abs_error);    val1和val2之间的差值不超过给定的绝对误差


代码覆盖率
编译参数

# 设置代码覆盖率的相关参数
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread -fprofile-arcs -ftest-coverage -lgcov")

统计脚本

#!/bin/bash
build_dir=$1
if [ -z "$build_dir" ] ;thenecho "error!请输入build目录,如果没有编译请先编译。"echo "例如:$0 /home/uos/Desktop/build-tools-unknown-Debug"exit 0
fi
cd $build_dir
lcov -d ./ -c -o init.info 
lcov -a init.info -o total.info
lcov --remove total.info '*/usr/include/*' '*/usr/lib/*' '*/usr/lib64/*' '*/src/log/*' '*/tests/*' '*/usr/local/include/*' '*/usr/local/lib/*' '*/usr/local/lib64/*' '*/third/*' 'testa.cpp' -o final.info
genhtml -o cover_report --legend --title "lcov"  --prefix=./ final.info
browser cover_report/index.html &


注意 : browser为浏览器的命令模式启动。
 

1. 整体设计

使用最精简的设计,我们就用两个类,够简单吧:

1. TestCase类
包含单个测试案例的信息。

2. UnitTest类

负责所有测试案例的执行,管理。

1.1 TestCase类

TestCase类包含一个测试案例的基本信息,包括:测试案例名称,测试案例执行结果,同时还提供了测试案例执行的方法。我们编写的测试案例都继承自TestCase类。

class TestCase
{
public:TestCase(const char* case_name) : testcase_name(case_name){}// 执行测试案例的方法virtual void Run() = 0;int nTestResult; // 测试案例的执行结果 const char* testcase_name; // 测试案例名称
};

1.2 UnitTest类

我们的UnitTest类和gtest的一样,是一个单件。我们的UnitTest类的逻辑非常简单:

1. 整个进程空间保存一个UnitTest 的单例。

2. 通过RegisterTestCase()将测试案例添加到测试案例集合testcases_中。

3. 执行测试案例时,调用UnitTest::Run(),遍历测试案例集合testcases_,调用案例的Run()方法

class UnitTest
{
public:// 获取单例static UnitTest* GetInstance(); // 注册测试案例TestCase* RegisterTestCase(TestCase* testcase);// 执行单元测试int Run();TestCase* CurrentTestCase; // 记录当前执行的测试案例int nTestResult; // 总的执行结果int nPassed; // 通过案例数int nFailed; // 失败案例数
protected:std::vector<TestCase*> testcases_; // 案例集合
};

2. 实现

下面是UnitTest类的实现:

UnitTest* UnitTest::GetInstance()
{static UnitTest instance;return &instance;
}TestCase* UnitTest::RegisterTestCase(TestCase* testcase)
{testcases_.push_back(testcase);return testcase;
}int UnitTest::Run()
{nTestResult = 1;for (std::vector<TestCase*>::iterator it = testcases_.begin();it != testcases_.end(); ++it){TestCase* testcase = *it;CurrentTestCase = testcase;std::cout << green << "======================================" << std::endl;std::cout << green << "Run TestCase:" << testcase->testcase_name << std::endl;testcase->Run();std::cout << green << "End TestCase:" << testcase->testcase_name << std::endl;if (testcase->nTestResult){nPassed++;}else{nFailed++;nTestResult = 0;}}std::cout << green << "======================================" << std::endl;std::cout << green << "Total TestCase : " << nPassed + nFailed << std::endl;std::cout << green << "Passed : " << nPassed << std::endl;std::cout << red << "Failed : " << nFailed << std::endl;return nTestResult;
}

五、NTEST宏

接下来定一个宏NTEST,方便我们写我们的测试案例的类。

#define TESTCASE_NAME(testcase_name) \testcase_name##_TEST#define NANCY_TEST_(testcase_name) \
class TESTCASE_NAME(testcase_name) : public TestCase \
{ \
public: \TESTCASE_NAME(testcase_name)(const char* case_name) : TestCase(case_name){}; \virtual void Run(); \
private: \static TestCase* const testcase_; \
}; \
\
TestCase* const TESTCASE_NAME(testcase_name) \::testcase_ = UnitTest::GetInstance()->RegisterTestCase( \new TESTCASE_NAME(testcase_name)(#testcase_name)); \
void TESTCASE_NAME(testcase_name)::Run()#define NTEST(testcase_name) \NANCY_TEST_(testcase_name)

六、RUN_ALL_TEST宏

然后是执行所有测试案例的一个宏:

#define RUN_ALL_TESTS() \UnitTest::GetInstance()->Run();

七、断言的宏EXPECT_EQ

这里,我只写一个简单的EXPECT_EQ :

#define EXPECT_EQ(m, n) \if (m != n) \{ \UnitTest::GetInstance()->CurrentTestCase->nTestResult = 0; \std::cout << red << "Failed" << std::endl; \std::cout << red << "Expect:" << m << std::endl; \std::cout << red << "Actual:" << n << std::endl; \}

八、案例Demo

够简单吧,再来看看案例怎么写:

#include "nancytest.h"int Foo(int a, int b)
{return a + b;
}NTEST(FooTest_PassDemo)
{EXPECT_EQ(3, Foo(1, 2));EXPECT_EQ(2, Foo(1, 1));
}NTEST(FooTest_FailDemo)
{EXPECT_EQ(4, Foo(1, 2));EXPECT_EQ(2, Foo(1, 2));
}int _tmain(int argc, _TCHAR* argv[])
{return RUN_ALL_TESTS();
}


整个一山寨版gtest,呵。执行一下,看看结果怎么样:

2.性能测试

可运行文件内容:

static void benchmark_slamopttest(benchmark::State& state)
{SlamOptBoundaries slamtest(15, 4, 4, "/home/usrname/slamtest/CameraRoadFrame", "/home/usrname/slamtest/result.json");size_t index = state.range(0);for(auto _: state){slamtest.SaveSameBoundaries(index);}
}
BENCHMARK(benchmark_slamopttest)->Arg(10);BENCHMARK_MAIN();


测试函数1:

void SlamOptBoundaries::SaveSameBoundaries(size_t const index_b)
{VecLocalBoundary vec_same_boundary_l;size_t           index_bl = index_b;for (; index_bl < vec_boundaries_in_frames_.size(); index_bl += b_){// for(int i = 0; i < 10; ++i)// {//     int j = i;// }if(vec_boundaries_in_frames_[index_bl].size() == 1){continue;}vec_same_boundary_l.push_back(vec_boundaries_in_frames_[index_bl]);}
}


测试函数2:

void SlamOptBoundaries::SaveSameBoundaries(size_t const index_b)
{VecLocalBoundary vec_same_boundary_l;size_t           index_bl = index_b;for (; index_bl < vec_boundaries_in_frames_.size(); index_bl += b_){for(int i = 0; i < 10; ++i){int j = i;}if(vec_boundaries_in_frames_[index_bl].size() == 1){continue;}vec_same_boundary_l.push_back(vec_boundaries_in_frames_[index_bl]);}
}


测试结果
测试函数1:

2022-01-25T16:13:26+08:00
Running ./run
Run on (16 X 4800 MHz CPU s)
CPU Caches:L1 Data 32 KiB (x8)L1 Instruction 32 KiB (x8)L2 Unified 256 KiB (x8)L3 Unified 16384 KiB (x1)
Load Average: 0.28, 0.47, 0.51
***WARNING*** CPU scaling is enabled, the benchmark real time measurements may be noisy and will incur extra overhead.
***WARNING*** Library was built as DEBUG. Timings may be affected.
-------------------------------------------------------------------
Benchmark                         Time             CPU   Iterations
-------------------------------------------------------------------
benchmark_slamopttest/50        152 ns          152 ns      4207340



152ns内执行了4207340次,平均1ns执行27679次。

测试函数2:

2022-01-25T16:14:00+08:00
Running ./run
Run on (16 X 4800 MHz CPU s)
CPU Caches:L1 Data 32 KiB (x8)L1 Instruction 32 KiB (x8)L2 Unified 256 KiB (x8)L3 Unified 16384 KiB (x1)
Load Average: 0.21, 0.43, 0.50
***WARNING*** CPU scaling is enabled, the benchmark real time measurements may be noisy and will incur extra overhead.
***WARNING*** Library was built as DEBUG. Timings may be affected.
-------------------------------------------------------------------
Benchmark                         Time             CPU   Iterations
-------------------------------------------------------------------
benchmark_slamopttest/50        151 ns          151 ns      4321895


151ns内执行了4321895次,平均1ns执行28621次。

结论
函数1性能更好,虽然结果肉眼可见。

九、总结

本篇介绍性的文字比较少,主要是我们在上一篇深入解析gtest时已经将整个流程弄清楚了,而现在编写的nancytest又是其非常的精简版本,所有直接看代码就可以完全理解。希望通过这个Demo,能够让大家对gtest有更加直观的了解。回到开篇时所说的,我们没有必要每个人都造一个轮子,因为gtest已经非常出色的为我们做好了这一切。如果我们每个人都写一个自己的框架的话,一方面我们要付出大量的维护成本,一方面,这个框架也许只能对你有用,无法让大家从中受益。


http://chatgpt.dhexx.cn/article/gPGk7QfJ.shtml

相关文章

Geetest极验+VUE把验证码绑定到自己的按钮(例如获取验证码)

极验流程 客户端gt.js里调用initGeetest发起初始化&#xff0c;会向后端获取gt等参数&#xff08;后端会跟极验通信&#xff09;&#xff0c;然后前端会根据传回的数据去决定用什么做验证&#xff0c;然后验证通过之后会有三个参数提供给后端进行二次校验的。具体接入看极验官…

【日常】Geetest滑动验证码(三代canvas版)处理小结(以B站登录验证为例)

问题描述 这个问题确实让我困扰了太长时间&#xff0c;今天花了半天时间&#xff0c;并没有找到非常完满的解决方案&#xff0c;只是在解决问题的过程中学会了一些其他知识&#xff0c;我最后还是要通过人工来判断大致的移动距离&#xff0c;然后根据误差做微调。大致做个总结…

geetest极验空间推理验证码破解与研究

看了很多的破解滑动验证码&#xff0c;决定破解一下空间推理验证码。破解思路&#xff0c;通过分析接口请求&#xff0c;对图片物体进行定位分类&#xff0c;通过模拟请求破解验证码。 研究的网站为 https://www.geetest.com/show 一、极验请求分析 请求详细 一、register-s…

googletest简介

googletest是由谷歌的测试技术团队开发的测试框架&#xff0c;使用c实现&#xff0c;具有跨平台等特性。 好的测试框架 引用谷歌给出的文档&#xff0c;好的测试应当具备以下特征&#xff1a; 测试应该是独立的和可重复的。调试一个由于其他测试而成功或失败的测试是一件痛苦…

破解极验(geetest)验证码

最近在搞爬虫的时候在好几个网站都碰到了一种叫做geetest的滑动条验证码,一直没有太好的办法只能在触发这个验证码后发个报警去手动处理一下。http://www.geetest.com/exp_embed是他们官网的样例。 后来研究了下觉得要破解这个验证码有这么几个问题: 无法直接通过发送…

破解滑块验证码最新版(GEETEST 95%以上通过率)

一、滑块验证码简述 有爬虫&#xff0c;自然就有反爬虫&#xff0c;就像病毒和杀毒软件一样&#xff0c;有攻就有防&#xff0c;两者彼此推进发展。而目前最流行的反爬技术验证码&#xff0c;为了防止爬虫自动注册&#xff0c;批量生成垃圾账号&#xff0c;几乎所有网站的注册页…

极验GeeTest简单demo

概述 人机验证 3.0 解决方案(基于生物行为与人工智能) 2012 年极验将人机验证从1.0时代推动到了 2.0 时代。在 5 年时间中&#xff0c;超过千亿次数据学习与优化&#xff0c;极验利用三角防护理论和 AI 智能决策引擎&#xff0c;全面更新安全架构。2017 年&#xff0c;正式推出…

极验geetest的使用

项目中会遇到 滑块验证的需求&#xff1a; 前端vue里 1.新建/utils/gt3.js "v0.4.8 Geetest Inc.";(function (window) {"use strict";if (typeof window undefined) {throw new Error(Geetest requires browser environment);}var document window.do…

爬虫进阶教程:极验(GEETEST)验证码破解教程

摘要: 爬虫最大的敌人之一是什么&#xff1f;没错&#xff0c;验证码&#xff01;Geetest作为提供验证码服务的行家&#xff0c;市场占有率还是蛮高的。遇到Geetest提供的滑动验证码怎么破&#xff1f;授人予鱼不如授人予渔&#xff0c;接下来就为大家呈现本教程的精彩内容。 一…

【已解决】安卓手机的GeeTest文件夹是什么

网上关于安卓系统手机的GeeTest目录是什么的文章和帖子&#xff0c;绝大部分打着GeeTest的标题&#xff0c;内容都是牛头不对马嘴&#xff0c;答非所问&#xff0c;没一个能解释清楚。 我刚刚找到了正式的答复如下&#xff1a; Android 手机上的“geetest”目录与名为“极验”的…

正定二次型与半正定二次型

对于实二次型其中A是实对称的&#xff0c;下列条件等价&#xff1a; 正定的 &#xff08;1&#xff09;是正定的. &#xff08;2&#xff09;它的正惯性指数p等于n. &#xff08;3&#xff09;有可逆实矩阵C&#xff0c;使得其中 &#xff08;4&#xff09;实对称矩阵A是正…

怎么对document.write写出来的内容调整对齐方式_【求职技巧】给少数人:硅谷BAT级别的简历这么写...

我什么都不会”,“我什么都没干”,“这个项目很水”,这是我在帮别人修改简历时听到的最多的几句话。难道你真的什么都不会吗?真的什么都没干吗?真的很水吗?其实很多情况下,是这样的。 但是很水就放弃治疗了吗?不会的。放下无谓的抱怨和遗憾,好好梳理自己,认真编…

c语言八皇后问题经典算法,经典算法之八皇后问题

八皇后问题是一个古老而又著名的问题&#xff0c;是学习回溯算法的一个经典案例。今天我们就一起来探究一下吧&#xff01; 时间退回到1848年&#xff0c;国际西洋棋棋手马克斯贝瑟尔提出了这样的一个问题&#xff0c; 在88格的国际象棋上摆放八个皇后&#xff0c;使其不能互相…

从八皇后问题思考回溯法

一、八皇后问题 八皇后是经典的回溯法问题&#xff0c;题目是说将八个皇后&#xff0c;放到88的国际象棋棋盘中中&#xff0c;使得任意两个皇后都不能在同一行、同一列以及同一条对角线上。下图是一个四皇后的搜索示意图。 八皇后问题可以通过暴力法求解&#xff0c;代码也很…

八皇后问题(Python)

一.问题简介 八皇后问题&#xff1a; 如何能在 8*8 的国际象棋棋盘上放置八个皇后&#xff0c;使得任何一个皇后都无法直接吃掉其他的皇后&#xff1f;为了到达此目的&#xff0c;任两个皇后都不能处于同一条横行、纵行或斜线上。 二.几种思路和方法 1.回溯法递归思想 如图所…

八皇后问题详解(四种解法)

所有源码都在github上(https://github.com/seasonyao/eight_queen_question) 如果你去百度百科八皇后这个问题,你会发现人家也是历史上有头有脸的一个问题,最后一句“计算机发明后就有一万种方式解决这个问题”读起来也让程序猿们很快活。闲话少说,开始阐述我的思路: 最…

八皇后问题

八皇后问题 八皇后问题(英文:Eight queens)&#xff0c;是由国际西洋棋棋手马克斯贝瑟尔于1848年提出的问题&#xff0c;是回溯算法的典型案例。 问题表述为:在88格的国际象棋上摆放8个皇后&#xff0c;使其不能互相攻击&#xff0c;即任意两个皇后都不能处于同一行、同一列或…

八皇后问题(适合初学者的写法)C语言

什么是八皇后问题&#xff1a; 八皇后问题&#xff0c;是一个古老而著名的问题&#xff0c;是回溯算法的典型案例。该问题是国际西洋棋棋手马克斯贝瑟尔于1848年提出&#xff1a;在88格的国际象棋上摆放八个皇后&#xff0c;使其不能互相攻击&#xff0c;即任意两个皇后都不能…

八皇后问题,秒懂递归回溯(有图详解|c语言)

目录 &#x1f478;&#x1f3fb;前言 &#x1f478;&#x1f3fb;题目介绍 &#x1f478;&#x1f3fb;引入&#xff1a; &#x1f478;&#x1f3fb;解决思路&#xff1a; &#x1f478;&#x1f3fb;理论存在&#xff0c;实践开始&#xff01; &#x1f478;&#x1f…

利用ngrok实现域名映射局域网ip

前言 相信很多开发者都有这样的需求&#xff0c;需要让外网访问你本地的服务器&#xff0c;方便调试本地代码&#xff0c;或者让别人体验到自己做的应用。那么这时&#xff0c;我们需要做的就是将我们本地的端口映射到一个外网的端口上&#xff0c;也就是内网穿透。常见的解决…