【C语言进阶】系统测试与调试
1. 引言
在开始本教程的深度学习之前,我们需要了解整个教程的目标及其结构,以及为何进阶学习是提升C语言技能的关键。
-
目标和结构:
- 教程目标:本教程旨在通过系统化的学习,从单元测试、系统集成测试到调试技巧,为学习者提供全面的知识,以增强在C语言中的熟练程度和实际应用能力。
- 教学结构:本教程将采用由浅入深的方式,分阶段地讲解各个主题,适合从初学者到有经验的开发者,以确保学习者能够逐渐提高,并能够快速定位重要内容。
-
进阶学习的重要性:
- 学习进阶内容的必要性:C语言作为众多系统软件的基础语言,其高性能和高效率对软件的功能性和维护性至关重要。深入学习的目的是为了提高编写代码的效率和稳定性。
- 应对复杂项目:在现实项目中实现系统的稳定性、安全性和高性能需要更深层次的知识掌握。
- 职业发展:对于希望在专业领域中取得进一步成就的程序员,深入掌握C语言将为高性能计算和复杂应用的开发打下坚实的基础。
通过本引言,我们将准备好投入到C语言的进阶学习,并为解决实际问题和参与复杂项目做好准备。
2. 测试和调试概述
进入测试和调试之前,我们需要明确这些概念在软件开发中的特定角色及其重要性。
-
什么是测试?什么是调试?:
- 测试:测试是在项目开发过程中通过用例来验证软件是否按预期工作,通常包括单元测试、集成测试、系统测试等。它的目的是发现代码中的错误和缺陷。
- 调试:调试是当测试发现错误后,对代码进行分析、查找问题所在并进行修正的过程。调试通常依赖于工具如GDB、LLDB等来定位和修复程序错误。
-
测试和调试在软件开发生命周期中的角色:
- 测试和调试是软件开发周期的关键环节,从需求分析、设计到实现、测试、部署和运维,贯穿于整个生命周期。
- 在开发阶段,单元测试帮助检测和修正早期错误,确保代码基础的可靠性。
- 在集成阶段,集成测试帮助验证不同模块接口的正确性,提高系统的整体协调性。
- 在维护阶段,调试是定位和解决因新功能或环境变化而引入的问题所必不可少的过程。
-
测试的重要性:
- 减少项目风险:通过系统化的测试流程可以识别和减少潜在风险,提高代码质量。
- 提高开发效率:自动化测试工具和框架的使用可以大幅提高工作效率,缩短软件的上线时间。
- 增强代码的可维护性:完善的测试用例作为程序的一部分提供了代码的正确性保证,有助于后期维护和二次开发。
通过这两个部分的引入,读者将会为整个教程中更为深入的内容打下认知和理解的基础。
3. 单元测试
3.1 单元测试概念
单元测试是软件测试中一种用于验证软件各个独立“单元”功能的测试方法。在C语言开发中,一个“单元”通常指的是一个函数或者一个模块。这种测试类型的目的是确保每一个单元在与其他模块隔离的情况下可以按照设计的功能正常运行。
-
单元测试的目的:
- 验证功能:确保代码的每个部分按预期工作,减少未来集成时出现问题的可能性。
- 提高代码质量:在代码初期就发现和修改错误,使得代码更加稳健。
- 简化调试过程:提供对问题显现的快捷途径,在问题集中到一个小的单元时更容易调试和修复。
- 文档化代码:单元测试代码本身也可以作为代码功能的文档,帮助理解代码设计思想和逻辑。
-
单元测试的特点:
- 自动化:通常通过自动化工具运行测试,确保每次代码更改后可以快速验证功能。
- 独立性:每个测试都应该完全独立,能在所有环境下单独执行,并产生一致结果。
- 快速执行:单元测试应该快速执行,以支持频繁的代码修改和不断的反馈循环。
- 可重复:测试结果应在多次执行中保持一致,而不依赖于测试次序或外部因素。
-
单元测试与其他类型测试的区别:
- 范围及粒度:单元测试关注最小粒度的“单元”(如函数),而集成测试、系统测试等则关注多个模块的协作和整个应用系统的运行情况。
- 执行时机:单元测试通常在开发初期和每次代码变更后尽早执行,而后续的测试则可能在代码完成后的较晚阶段进行。
- 依赖程度:单元测试应该独立于外部依赖,而集成测试可能需要依赖模块间的通信和系统配置。
单元测试的关键在于其独立性和自动化特性,不仅让开发人员及早发现与修复错误,还减少了代码维护的复杂度。在C语言中,利用这种方式测试可以显著提高工程效率和代码的稳健性。
3.2 基础的单元测试框架
在软件开发过程中,单元测试对于确保单个代码单元(通常是函数)的功能符合预期至关重要。C语言并不像Junit之于Java或unittest之于Python那样有内置的单元测试框架,但也有一些常用的第三方单元测试框架可供选择。
-
CUnit
CUnit是一个轻量级的单元测试框架,遵循xUnit架构。它简单易用,适合C语言编写的项目。
- 特点:
- 提供基本的测试功能和断言。
- 生成XML和文本格式的测试报告,便于后续分析。
- 支持自动化构建工具的集成。
- 特点:
-
Check
Check是一个功能丰富的C语言单元测试框架,支持多种操作系统。它提供了一系列工具来帮助进行单元测试。
- 特点:
- 支持fork以在隔离的环境中执行测试,防止测试对主进程的影响。
- 提供详细的报告功能,包括通过率、失败案例等数据。
- 支持测试套件的并行执行,提高测试效率。
- 特点:
-
Unity
Unity是一款用于嵌入式开发的轻量级框架,适用于资源受限的系统。
- 特点:
- 小巧的代码库,适合嵌入式系统。
- 提供多种断言功能,支持格式化输出。
- 集成容易,适用于与其它测试框架联合使用。
- 特点:
选择单元测试框架的考虑因素
在选择单元测试框架时,需要考虑以下几个因素,以确保选择的框架最适合你的项目需求:
-
项目规模和复杂性:对于小型或简单项目,轻量级的框架如CUnit或Unity可能更合适;而对于复杂项目,可能需要更丰富功能的框架如Check。
-
操作系统和工具链支持:确保框架与开发环境兼容。例如,Check对多种操作系统的支持较好,而Unity专为嵌入式系统设计。
-
测试报告和自动化需求:如果需要详细的测试报告和集成到CI/CD流水线中,选择支持这些功能的框架很重要。
-
团队熟悉度:选择团队成员熟悉或者学习成本较低的框架,可提高生产力和效率。
-
社区和文档支持:拥有良好社区支持和文档的框架更容易排除故障,并能获得可靠的帮助。
以上是关于选择C语言单元测试框架时的一些指导建议和常见选项。每个项目和团队都有独特的需求,进行合适的选择可以极大地帮助开发和测试过程。
3.3 CUnit单元测试入门
CUnit是一个轻量级的、适用于C语言的单元测试框架,它提供了简单的API来帮助开发者编写和组织测试代码。
安装和配置CUnit
要开始使用CUnit,你需要先在开发环境中安装它。你可以通过软件包管理器(如apt、yum等)安装CUnit,或者从其官方网站下载源代码进行编译安装。
# 在Debian/Ubuntu系统上
sudo apt-get install libcunit1 libcunit1-doc libcunit1-dev# 在Red Hat/CentOS系统上
sudo yum install cunit cunit-devel
基本示例:编写第一个测试
一旦CUnit安装完毕,你可以开始编写测试。CUnit测试主要由测试用例(Test Case)和测试套件(Test Suite)组成。
以下是一个简单的CUnit测试例子,它演示了如何编写和运行一个基本的单元测试:
#include <CUnit/CUnit.h>
#include <CUnit/Basic.h>
#include "math_functions.h" // 假设你要测试的函数位于此头文件// 测试用例:测试加法
void test_addition(void) {CU_ASSERT(2 + 2 == 4); // 测试直接加法 [1]CU_ASSERT(add(2, 2) == 4); // 测试自定义加法函数 [2]
}int main() {// 初始化和清理CUnit测试注册表if (CUE_SUCCESS != CU_initialize_registry()) {return CU_get_error();}CU_pSuite pSuite = NULL;// 添加测试套件pSuite = CU_add_suite("TestSuite_1", 0, 0);if (NULL == pSuite) {CU_cleanup_registry();return CU_get_error();}// 添加测试用例到套件if ((NULL == CU_add_test(pSuite, "test of addition", test_addition))) {CU_cleanup_registry();return CU_get_error();}// 运行测试CU_basic_set_mode(CU_BRM_VERBOSE);CU_basic_run_tests();CU_cleanup_registry();return CU_get_error();
}
-
[1] 测试直接加法:
CU_ASSERT(2 + 2 == 4)
用于测试基本的加法运算,验证在逻辑上2 + 2
是否等于4
。 -
[2] 测试自定义加法函数:
CU_ASSERT(add(2, 2) == 4)
调用用户自定义的add
函数,验证其返回的结果是否符合预期。 -
初始化注册表:在运行CUnit测试框架之前,首先需要初始化一个测试注册表。使用
CU_initialize_registry()
来初始化注册表,如果初始化失败,则程序返回错误。 -
添加测试套件和测试用例:
- 测试套件:使用
CU_add_suite()
函数添加一个新的测试套件。在此例中,测试套件命名为"TestSuite_1"
。初始化和清理函数在此例中为空指针(NULL)。 - 测试用例:用
CU_add_test()
把具体的测试用例加入到测试套件中。此代码段中,测试用例包括test_addition()
,其目的是测试数学函数。
- 测试套件:使用
-
运行测试:通过
CU_basic_run_tests()
来执行所有的测试用例。可以使用CU_basic_set_mode(CU_BRM_VERBOSE)
来设置运行模式为详细模式,以显示测试过程的详细信息。所有的结果和状态都将在测试结束后清理注册表来一并处理。
通过这种方式,可以快速实现对C语言项目中不同部分模块进行单元测试,确保功能性可靠。
测试用例和测试套件
-
测试用例:一个测试用例是对一个功能的一个具体测试实例,比如
test_addition
测试特定的加法功能。可以使用多种断言进行条件检查,例如CU_ASSERT
。 -
测试套件:测试用例可以被组织到测试套件中。测试套件是一组相关测试用例的集合,通常用于测试同一模块的不同方面。
CUnit中的测试用例和套件帮助开发者组织和执行大量测试,并在代码修改后快速验证代码行为一致性。此外,在编写测试时,关注代码的主要逻辑路径和边界情况是很重要的,以确保代码的鲁棒性和可靠性。
3.4 进阶CUnit使用技巧
在了解了基本的CUnit用法之后,下面将探讨更高级的CUnit使用技巧,以帮助你提升测试效率和质量。
自动化测试集成
-
作用:减少手动测试的重复劳动,提升软件测试覆盖率和一致性。
-
实现方法:
- 使用脚本或构建工具(如Makefile、CMake)自动运行CUnit测试。
- 配置CI/CD(持续集成/持续部署)环境(如GitHub Actions, Jenkins),自动执行测试并反馈结果。
- 例如,在Makefile中可添加类似以下的命令来执行所有测试:
test:./tests/my_cunit_tests
-
优势:能够在代码提交时自动检测错误,确保代码库始终处于健康状态。
使用断言与验证
- 作用:确保程序行为符合预期,通过断言捕获测试失败的具体原因。
- 常用断言函数:
CU_ASSERT
:检查表达式是否为真。CU_ASSERT_EQUAL
:检查两个值是否相等。CU_ASSERT_PTR_NULL
:检查指针是否为空。
void test_function() {int result = my_function();CU_ASSERT_EQUAL(result, expected_value); // 验证函数返回是否符合预期
}
- 好处:断言能够清晰地记录测试意图,并在测试失败时提供具体的错误信息以便调试。
生成测试报告
-
作用:提供测试执行情况的详细报告,便于审阅和分析。
-
报告类型:文本、XML、HTML等格式,选择适合的格式进行记录和展示。
-
实现方式:在CUnit中,可以通过不同的运行接口设置不同格式的报告生成。
- 使用
CU_BasicRunTests()
进行简单的文本报告。 - 使用
CU_automated_run_tests()
生成XML报告。
- 使用
CU_set_output_filename("cunit_report");
CU_automated_run_tests(); // 生成'cunit_report.xml'
- 好处:便于追踪质量问题、识别缺陷趋势,尤其在团队开发时能提高沟通效率。
内存泄漏和错误检测
-
作用:在进行单元测试的同时检查内存使用,及时发现内存泄漏等潜在问题。
-
工具推荐:
- Valgrind:在Linux下广泛使用的内存检测工具。
- AddressSanitizer:内置于GCC和Clang中的工具,用于检测内存错误。
-
集成方法:
- 使用Valgrind运行CUnit测试:
valgrind --leak-check=full ./tests/my_cunit_tests
- 在编译时启用AddressSanitizer:
gcc -o test_binary test.c -fsanitize=address -g
- 使用Valgrind运行CUnit测试:
-
好处:保证代码的健壮性,减少潜伏故障导致的意外崩溃和安全漏洞。
通过掌握这些高级技巧,你可以将CUnit用得更为得心应手,在开发、测试和部署过程中创建一种高标准化和高效率的工作流。
4. 系统集成测试
系统集成测试是软件开发中不可或缺的一部分,通过对各模块的结合和功能连通性的验证,确保整个系统高效且无缝地运行。
4.1 系统集成测试概念
集成测试是一种将已经单独测试过的模块组合起来进行测试的方法,旨在验证这些模块在整体环境中的协同工作能力。
-
集成测试定义:
集成测试主要关注模块之间的交互。尽管单个模块可能在单元测试中表现良好,但在集成到一个更大的系统时可能会出现意想不到的问题。集成测试就是为了在系统的早期阶段捕获这些问题,确保各个模块能够正确进行数据交换和交互。 -
集成测试与单元测试的关系:
单元测试和集成测试在测试流程中有着不同的职责:- 单元测试:通常关注于单个模块或函数的正确性,确保其逻辑符合预期。通常由开发者在编写代码后立即进行。
- 集成测试:则是将多个模块或单元结合在一起进行测试,主要考察模块之间的接口是否在实际环境中正常工作。
- 单元测试为集成测试打下坚实基础,但集成测试更贴近于用户操作和真实使用环境。
-
集成环境和实际环境的区别:
- 集成环境:通常是一个在开发过程中专门配置的测试环境,包含所有必要的硬件和软件配置来模拟生产环境。它是用来进行集成测试的场所,通常与实际环境非常相似。
- 实际环境(生产环境):是真实用户使用的系统环境,包含真实用户和真实数据。
- 在进行集成测试时,集成环境中的配置和条件应尽量模拟实际环境,以便更好地预测在真实使用中的表现。然而,由于硬件、网络、数据量等条件的差异,可能无法完全一致。
通过有效的系统集成测试,开发人员能够确保整个系统的各个部分能够顺畅地工作,从而更接近用户期望的行为。当集成环境恰当地设置和利用时,集成测试将极大地降低上线后系统出现重大问题的风险。
4.2 集成测试场景
集成测试是用于验证多个软件模块或系统组件之间的交互是否正常进行的测试阶段。在这一阶段,目标是通过真实或接近真实的环境来检验不同模块之间的接口、数据流以及系统性能。下面详细讲解几个关键的集成测试场景。
模块间接口测试
-
作用:模块间接口测试旨在确保不同模块之间的接口(API)按照设计进行交互,而不产生错误。
-
特点:
- 接口验证:测试每个模块的输入和输出,确保其符合预期的功能规格。
- 异常处理:不仅验证正常数据流,还包括处理异常、边界情况,以确保接口具有健壮性。
- 兼容性:检查接口在不同环境、版本下的兼容性,确保即便软件版本升级,接口也能够正常工作。
-
示例代码概念:
- 在多模块程序中,例如模块A需要调用模块B的某个函数。在接口测试中,需要确保模块A传送的参数类型正确,同时,模块B返回的结果符合模块A的预期。
数据流测试
-
作用:数据流测试检查在系统内部,数据从一个模块流向另一个模块时是否正确传递。
-
特点:
- 数据完整性:确保数据在传输过程中不被损坏或丢失。
- 路径评估:通过模拟所有可能的数据流路径,评估数据流生命周期中的逻辑错误。
- 数据依赖:识别模块之间的数据依赖关系,以防止潜在的依赖缺失或者错误传递。
-
示例代码概念:
- 考虑一个系统中,一个模块产生的数据被下一个模块处理。在数据流测试中,需要确保第一个模块传递的数据保持格式与精度不变,并且正确传到下一个接收的模块。
性能测试
-
作用:性能测试评估系统的表现能力,尤其是在高负载、极限条件下的响应速度和稳定性。
-
特点:
- 负载测试:检测系统在正常和峰值负载下的表现,评估其承载能力和响应效率。
- 压力测试:施加超出最大负载的压力来测试系统的极限,并确定崩溃点。
- 响应时间和吞吐量分析:测量系统处理请求的时间及可处理的最大事务数。
-
示例场景:
- 在一个web应用中,性能测试可能涉及请求数逐步增加,检查服务器的负载响应时间,确保即便在高峰期,用户体验仍然稳定。
通过不同场景的集成测试,开发者可以在软件集成的早期阶段捕获潜在的问题,提高整体系统的可用性、稳定性和可维护性。
4.3 实践中的集成测试
在软件开发过程中,集成测试是验证各个软件模块之间接口和交互是否符合预期的重要环节。特别是在C语言开发中,由于不同模块可能由不同团队在不同环境下开发,所以在集成阶段,面临的问题和挑战尤为复杂。因此,做好集成测试的规划与设计是非常重要的。
集成测试的规划与设计
- 作用:确保各独立开发的模块能够在规定的接口和规范下共同工作,实现预期的功能。
- 特点:
- 早期设计与规划:及早进行集成测试计划是保证测试过程流畅的关键,包括确定测试策略、资源需求、划分测试阶段等。
- 确定测试规则和数据:精确定义不同模块间的接口、数据格式以及通讯协议,准备必要的测试数据与环境设置。
- 接口测试准备:对跨模块接口的边界及数据流抽象出测试场景。
使用模拟对象与桩件(Mocks and Stubs)
在集成测试中,为了独立测试某一模块,有时需要模拟其与尚未开发完毕或不可用模块之间的交互,此时Mocks和Stubs提供了一种解决途径。
- 作用:在不损害模块功能的前提下,降低对真实外部环境和未开发模块的依赖。
- 特点:
- 模拟对象(Mocks):用于模拟真实对象的行为,检查某操作是否被调用,调用次数是否正确等。
- 桩件(Stubs):对未完成的模块或外部系统接口进行简单实现,返回固定的响应或结果。
- 优点和局限性:提升测试效率,但需小心真实环境中遗漏的数据流或交互情况。
这段代码演示了如何使用模拟(mock)技术来进行简化的网络请求测试。在没有真实网络连接或网络接口环境下,模拟技术可以帮助我们测试相关功能,确保软件的逻辑正确。下面是代码的详细解析:
#include <stdio.h>// 模拟网络请求函数
int mock_network_request(const char* request) {printf("Mocking network request: %s\n", request); // [1]return 200; // 返回模拟的 HTTP 响应码 200 OK [2]
}// 测试网络模块
void test_network_module() {int response = mock_network_request("GET /"); // [3]if (response == 200) { // [4]printf("Test passed.\n");} else {printf("Test failed.\n");}
}int main() {test_network_module(); // [5]return 0;
}
- [1] 打印模拟请求信息:在
mock_network_request
函数中,打印传入的请求信息,以表明这只是一次模拟请求。 - [2] 返回模拟响应码:返回数字
200
来代表一个成功的 HTTP 请求响应码(200 OK),用于模拟正常的网络请求结果。 - [3] 调用模拟请求:在
test_network_module
函数中,通过调用mock_network_request
来进行测试。此处使用"GET /"
表示一个简单的 GET 请求。 - [4] 检查响应码:检查
mock_network_request
返回的响应码是否为200
,以此来确认测试是否通过。 - [5] 执行测试:在
main
函数中调用test_network_module
,触发测试逻辑,输出测试结果。
该示例演示了一个基础的单元测试场景,通过模拟函数行为来验证代码逻辑在无真实外部依赖条件下的正确性。这种方式常用于测试需要与外部系统交互的部分,例如网络请求、数据库操作等。
交叉编译和测试
在嵌入式系统开发中,目标软件运行的平台通常与开发平台不同,因此需要在开发平台上编译目标平台可执行程序,这就是交叉编译。
- 作用:生成可在目标硬件上运行的程序,以在受限环境下测试模块。
- 特点:
- 不同平台支持:使用交叉编译工具链如GCC编译器支持多个目标平台。
- 硬件在环(Hardware in the Loop, HIL)测试:与物理目标硬件相结合,验证软件与硬件的接口和集成情况。
- 环境隔离:通过模拟真实硬件环境,隔离测试中的外部环境影响。
# 示例:ARM平台交叉编译
$ arm-linux-gcc -o test_sample test_sample.c
通过以上方法,集成测试中的挑战得以有效解决,确保软件系统在多个模块集成后正常运行。通过做好规划与设计,使用模拟工具以及适当的交叉编译策略,集成测试能够为软件的高质量交付提供有力保障。
5. 调试技巧
在软件开发过程中,调试是一个关键的步骤。调试的目的是发现并修正代码中的错误,并最终提高程序的稳定性和性能。本节将讨论一些常见的C语言调试工具,包括通用的调试工具和专注于内存调试的工具。
5.1 调试工具
调试工具可以极大地帮助开发者查找和修复代码中的问题。以下是一些常见的C语言调试工具:
常见的C语言调试工具
-
GDB(GNU Debugger)
- 作用:GDB是一款功能强大的命令行调试工具,适用于Unix系统上开发的C/C++程序。它允许开发者查看和控制被调试程序的执行过程。
- 特点:
- 支持断点设置,帮助开发者在程序执行时对特定代码段进行检查。
- 能查看变量的值和程序的堆栈信息。
- 支持逐步执行程序代码,有助于查找错误发生的确切位置。
- 示例使用:
gdb a.out (gdb) break main (gdb) run (gdb) print variable_name (gdb) continue
-
LLDB (LLVM Debugger)
- 作用:LLDB是适用于LLVM项目的调试工具,与GDB类似,它也支持对程序的调试操作。
- 特点:
- 具有更好的性能和更快的启动时间。
- 提供对现代C++标准的支持。
- 具有增强的命令行体验和丰富的Python API。
- 示例使用:
lldb a.out (lldb) breakpoint set --name main (lldb) run (lldb) frame variable (lldb) continue
-
Visual Studio调试工具
- 作用:Visual Studio 提供了图形化界面的调试工具,适用于Windows系统上的C/C++程序开发。
- 特点:
- 集成开发环境(IDE),支持便捷的项目管理及调试。
- 支持通过点击界面设置断点、观察变量值和程序执行流。
- 包含内置的内存检查工具来检测内存泄漏和访问错误。
内存调试工具
-
Valgrind
- 作用:Valgrind是一个强大的内存调试和分析工具,用于检测内存泄漏和无效的内存访问。
- 特点:
- 错误报告详尽,并指出具体的错误位置和原因。
- 可以检测未初始化内存使用、无效指针引用和双重释放等常见内存错误。
- 使用方式:
valgrind --leak-check=yes ./a.out
-
AddressSanitizer
- 作用:AddressSanitizer是一个快速的内存错误检测工具,适用于编译器(如Clang和GCC)生成的二进制文件。
- 特点:
- 特别适合发现缓冲区溢出、悬空指针、堆栈溢出等问题。
- 相较于Valgrind,具有更少的性能开销和更快的执行速度。
- 启用方式:
在编译过程中添加以下选项:gcc -fsanitize=address -g -o outputfile sourcefile.c
通过理解和应用这些工具,开发者可以有效地提高程序的健壮性,并减少调试和修复错误所需的时间。
5.2 使用GDB调试程序
GDB(GNU Debugger)是一个功能强大的调试工具,广泛应用于C语言程序的调试中。它可以帮助你跟踪程序的执行流程,查看程序中的变量值,以及快速定位错误。
GDB基本命令
GDB提供了一系列命令,用于控制程序的运行、查看变量、设置断点和监视点等。
- 启动调试:
gdb <program>
启动GDB并加载可执行文件。 - 运行程序:
run
或r
命令开始执行程序。 - 查看变量值:
print <variable>
或p <variable>
显示指定变量的当前值。 - 查看调用栈:
backtrace
或bt
显示当前程序的调用栈,帮助追踪函数调用路径。
$ gdb my_program
(gdb) run
(gdb) print my_variable
(gdb) backtrace
设置断点和监视点
-
断点(Breakpoint):在程序的特定位置设置暂停点,以便检查程序状态。
- 设置断点:
break <line_number>
或b <line_number>
在指定行设置断点。 - 删除断点:
delete <breakpoint_number>
删除某个断点。 - 查看断点:
info breakpoints
显示当前所有断点信息。
- 设置断点:
-
监视点(Watchpoint):当某个变量的值发生变化时暂停程序。
- 设置监视点:
watch <variable>
设置变量监视点。 - 删除监视点:
delete <watchpoint_number>
删除监视点。
- 设置监视点:
$ gdb my_program
(gdb) break 42
(gdb) run
(gdb) watch my_variable
调试运行时错误和段错误
运行时错误和段错误是程序开发中常见的问题。GDB可以协助在这些情况下进行调试。
-
调试运行时错误:
- 使用
run
命令运行程序,如果程序异常中止,GDB会显示出错位置的详细信息。 - 使用
backtrace
查看错误函数调用栈,帮助定位出错函数及代码位置。
- 使用
-
调试段错误(Segmentation Fault):
- 使用
set args <arguments>
设置程序需要的命令行参数,然后使用run
命令重现段错误。 - 当程序崩溃时,GDB会暂停在错误位置,使用
backtrace
查看调用栈,可以定位是在哪个函数或哪一行代码上引发了段错误。
- 使用
$ gdb my_program
(gdb) set args arg1 arg2
(gdb) run
(gdb) backtrace
通过对这些命令及功能的灵活运用,开发者可以有效地对程序进行调试,迅速找到问题所在,并进行修复。
5.3 高级调试技巧
在调试复杂C语言项目时,掌握一些高级调试技巧是非常必要的。它们不仅可以帮助快速定位问题,还能提供深入程序内部运作的理解。
栈回溯和堆栈分析
-
作用:在程序异常终止或出现非法操作(如段错误,Segmentation Fault)时,栈回溯能够帮助我们查看函数调用的历史记录,从而识别到底是哪个函数、哪一行代码导致了错误。
-
特性:
- 调用栈:跟踪程序执行中函数调用顺序的结构。每发生一次函数调用,栈上就加一层,返回时去掉一层。
- 栈帧:每个函数调用都会在栈中创建一个栈帧,用于存储函数返回地址、参数和局部变量。
-
使用方法 :使用调试器(如GDB)命令,如
backtrace
或者bt
,可以显示当前线程的调用栈信息。
#include <stdio.h>void func2() {printf("In func2\n");
}void func1() {func2();
}int main() {func1();return 0;
}
运行调试器时,如果我们中断在func2
中,backtrace
命令会显示函数是如何被调用的路径。
动态库调试
-
作用:调试程序与动态链库(Shared Library)的交互。
-
特性:
- 动态库在程序运行时加载,可以减少可执行文件的大小和内存使用。
- 调试动态库时,需要确保源代码匹配和符号可用。
-
使用方法:
- 在GDB中使用
set environment LD_LIBRARY_PATH
设置库路径。 - 使用
break
命令在动态库的特定函数中设置断点。
- 在GDB中使用
(gdb) set environment LD_LIBRARY_PATH ./libs
(gdb) break my_dynamic_function
多线程调试策略
-
作用:分析和解决多线程程序中可能出现的竞争条件、死锁等问题。
-
特性:
- 多线程编程中常常会导致复杂的同步问题。
- 理解线程间的交互对于稳定且高效的软件至关重要。
-
使用方法:
- 使用GDB中的
info threads
命令查看当前线程列表。 - 使用
thread apply all bt
命令来查看所有线程的栈回溯信息。 - 分析线程间的锁顺序和访问冲突。
- 使用GDB中的
(gdb) info threads
(gdb) thread apply all bt
通过这些高级调试技巧,开发者能够在复杂项目中更快地识别和解决问题,提高编码效率和软件质量。
6. 实践和项目
6.1 实践案例
在实际的软件开发过程中,实践是提高编程技能的关键途径。本节将指导你如何选择适合自己的项目并进行配置,以及如何编写详细的测试计划书。这些步骤都有助于确保项目开发的顺利进行和软件质量的提高。
实践项目选择与配置
选择一个合适的项目是成功的一半。在选择项目时,考虑以下几点:
- 项目难度:选择一个与当前技能水平稍有挑战的项目有助于技能提升。
- 项目相关性:项目内容应与个人发展方向或行业需求相符。
- 可扩展性:选择一个可以长期扩展和维护的项目。例如,一个个人博客系统、一个基本的游戏或一个简单的实时数据分析工具。
一旦选择了项目,接下来是配置阶段:
-
环境准备:
- 开发环境:确保你使用的开发工具已经配置完毕,如IDE或者文本编辑器(例如,Visual Studio Code)。
- 版本控制:设置 Git 或其他版本控制系统来管理代码变化。
- 依赖库:确保所有需要的库和工具已安装,比如通过包管理器(如 Homebrew 或 apt-get)。
-
项目结构:
- 目录划分:对项目进行合理的目录划分,例如
src
目录用于源代码,include
目录用于头文件,tests
目录用于测试代码。 - 编译脚本:准备 Makefile 或其他自动化编译工具来简化构建过程。
- 目录划分:对项目进行合理的目录划分,例如
编写测试计划书
测试计划书是确保软件质量的核心文档。它定义了测试目标、方式和资源。本质上,它为测试过程提供了一个系统化的方法。主要内容包括:
- 测试目标:明确测试的目标是什么,比如功能正常运行、性能稳定等。
- 测试范围:定义需要测试的模块或者功能范围,避免遗漏。
- 测试类型:
- 单元测试:针对单个模块的细粒度测试。
- 集成测试:验证模块之间的接口和数据交互。
- 系统测试:整个系统在实际环境中的测试。
- 测试用例:为每个测试类型编写具体的测试用例,明确输入和期望输出。
- 测试环境:描述测试将在哪些硬件和软件环境下进行。
- 资源分配:列出测试人员及所需的工具与设备。
编写测试计划书的过程能帮助你理清思路,为项目的每个开发阶段做好充分准备。而系统地进行项目选择与配置,加之详细的测试计划,将为你的项目开发提供坚实的基础。
6.2 实践项目
在本节中,我们将通过一个实际项目的演练,帮助你整合所学的单元测试、集成测试和调试技巧。以下步骤将引导你如何下载示例代码、设置环境、进行自动化测试与持续集成,并最终解析调试实例。
示例代码下载和设置
-
下载示例代码:访问项目的代码仓库,通常在GitHub或其他版本控制平台上。使用如下命令克隆代码库:
git clone https://github.com/example/repository.git
这样可以获得包含所有示例代码的本地副本。
-
设置项目环境:进入项目目录并根据
README
文件中的指示进行环境配置。可能需要安装相关的编译器和依赖库。 -
构建项目:确保项目能够正常编译和构建。通常可以通过以下命令完成:
make all
自动化测试和持续集成配置
-
配置测试脚本:在项目中引入自动化测试脚本,使测试过程变得更加可重复和便于管理。
make test
脚本应运行所有单元和集成测试,并输出测试结果。
-
持续集成设置:利用持续集成工具(如Jenkins、Travis CI、GitHub Actions),配置自动化构建和测试流水线。这些工具将在代码提交或合并请求时自动运行测试。
完整单元测试编写
-
定义测试用例:根据功能需求定义详细的测试用例,确保每个功能模块都被妥善测试。
-
实现测试代码:编写相应的测试代码,实现单元测试逻辑,例如使用CUnit框架。
void test_functionality() {CU_ASSERT_EQUAL(expected, actual); }
以上代码确认实际输出与预期一致。
系统集成测试演练
-
设计集成测试场景:确定各模块之间的交互关系以及数据流程。
-
执行集成测试:运行集成测试,确认不同模块间能够有效协同工作。
调试实例解析
-
定位问题:使用调试工具(如GDB)分析程序执行过程,设置断点,检查变量以及运行时错误。
gdb ./executable
-
解析错误源:通过调试信息查找错误的根本原因,例如段错误或者逻辑错误。
-
解决问题和优化:根据调试结果修改代码,优化性能,并再次进行构建测试,确保问题解决。
这个实践项目通过例行的步骤,展示了C语言项目中测试与调试环节的全面流程,从而为项目的质量和稳定性提供保障。
7. 常见问题与解决方案
在C语言的项目开发中,我们常常会遇到各种各样的问题,尤其是在测试和调试阶段。及时识别和解决这些问题对于提高项目的稳定性和效率至关重要。本节将详细讨论单元测试和系统集成测试中常见的问题,并提供改善调试效率的有效策略。
单元测试中常见的错误调试
单元测试在验证代码正确性方面至关重要,但编写和执行单元测试时可能会遇到一些常见问题:
-
测试数据不充分
-
问题表现:测试用例仅覆盖了少量场景,导致程序中存在的较多可能的错误未被及时发现。
-
解决方案:确保测试覆盖率,设计更多元的测试场景,包括边界条件和异常输入。
-
-
断言松散或遗漏
-
问题表现:使用的断言过于宽松,未能验证代码执行的准确性。
-
解决方案:编写严格的断言,检查具体状态和输出,对重要逻辑路径多做验证。
-
-
搭建测试环境错误
-
问题表现:测试依赖的环境不正确,导致测试结果偏差。
-
解决方案:保持环境一致性,使用CI(持续集成)工具,并定期更新和验证环境配置。
-
集成测试失败的处理方法
在系统集成测试过程中,不同模块联合后进行的测试可能会暴露出一些未曾预料的问题:
-
接口契约不匹配
-
问题表现:模块之间的接口并未遵循统一约定,导致错误数据传递。
-
解决方案:在设计和开发阶段保持良好的沟通,明确接口定义,利用模拟对象来测试接口一致性。
-
-
数据流错误
-
问题表现:数据在模块间传输过程中丢失或变形。
-
解决方案:使用工具监视和验证模块间的数据流,并对数据传递进行详细日志记录。
-
-
性能问题
-
问题表现:系统集成后,响应慢或资源消耗异常。
-
解决方案:使用性能分析工具监测系统行为,识别瓶颈并优化相关代码片段。
-
调试中的常见错觉和纠正方法
调试是软件开发中至关重要的环节,但调试过程中我们可能会陷入一些思维误区:
-
过度依赖输出来定位问题
-
问题表现:过于依赖输出(尤其是调试信息和日志)定位问题,而忽略细致的代码检查。
-
纠正方法:结合代码审查和逻辑分析,确保在了解程序功能的前提下进行调试。
-
-
忽视竞态条件
-
问题表现:多线程程序中出现的不确定行为常被误认为其他问题。
-
纠正方法:仔细检查线程间同步问题,利用合适的工具(如Valgrind)发现和调试竞态条件。
-
-
未能追踪代码变更
-
问题表现:修复一个bug的同时引入了其他bug。
-
纠正方法:使用版本控制工具(例如Git)记录每次变更,确保任何代码修改发生后都有明确的日志记录。
-
通过以上方法可以更有效地处理测试和调试中遇到的问题,从而提高C语言项目开发的整体效率和质量。
8. 总结和下一步
在本章节中,我们将对前面各部分进行总结并探讨下一步的学习和发展方向,帮助你在C语言进阶的道路上稳步前行。
通过本教程学到的主要知识点
-
测试与调试的重要性:
- 掌握了测试和调试在软件开发生命周期中的角色及其重要性。
- 学习了如何正确进行单元测试和系统集成测试,以及如何利用调试工具提升代码质量。
-
单元测试的基础与进阶:
- 理解了单元测试的概念、目的及特点。
- 通过CUnit等常见单元测试框架的基础及进阶用法,掌握了如何编写单元测试,集成自动化测试。
-
系统集成测试的策略:
- 认识了集成测试和单元测试的关系以及其在项目中的应用。
- 实践了使用模拟对象与桩件进行接口测试、数据流测试和性能测试。
-
调试技巧与工具的运用:
- 掌握了使用GDB、LLDB、Valgrind等工具进行高效调试的方法。
- 掌握了多线程调试和动态库调试等高级调试技巧。
-
实践项目训练:
- 综合运用所学知识,通过项目选择、测试计划编写、自动化测试配置等实践培养实际动手能力。
进一步学习的方向和资源推荐
-
深入学习编译原理和链接机制:
- 理解编译过程、链接过程、动态加载等,以更好地优化代码性能和可维护性。
-
掌握其他高级调试和性能分析工具:
- 例如使用
perf
、strace
等工具分析程序性能和系统调用,提升整体性能优化的能力。
- 例如使用
-
代码质量和安全性:
- 学习如何编写安全和高效的代码,关注资源释放、内存管理、以及防止缓冲区溢出等安全性问题。
-
持续集成与持续交付(CI/CD):
- 通过实践使用工具(如Jenkins, GitHub Actions)实现项目自动化构建、测试和部署。
-
贡献和参与开源项目:
- 加入开源社区,通过贡献代码和参与项目实战,不断提高自己的技能。
社区和开源项目参与
-
参与开源社区:
- 可以通过贡献代码、修复bug、完善文档等方式参与到如GNU、LLVM等社区中。
-
技术论坛和学习平台:
- 积极参加C语言相关的技术论坛(如Reddit的C语言版块)、学习平台(如LeetCode、Stack Overflow),交流心得、共享经验。
-
代码审查和合作开发:
- 进行代码审查实践,学习其他开发者的代码风格和解决方案,以提升自身代码编写的规范性和效率性。
通过本教程的学习,相信你已经积累了一定的C语言进阶知识和项目实践能力。接下去,持续学习与实践,并积极参与社区与开源项目,将使你的专业水平更上一层楼。
附录
附录部分提供了在C语言编程过程中遇到的常见问题及其解决方案、推荐的参考资料和书籍,以及测试和调试工具的安装指南。这些内容旨在帮助开发人员更好地理解和解决在特定环境下C语言应用开发过程中可能面临的挑战。
常见错误代码及诊断
在C语言编程中,错误可能来自不同的代码区域,如语法错误、链接错误、运行时错误等。以下是一些典型的错误代码及其可能的原因和解决方法:
-
Segmentation Fault (段错误)
- 原因:通常由访问非法内存地址引起,例如解引用空指针或访问超出数组范围的索引。
- 诊断方法:
- 使用GDB设置断点来跟踪代码执行,找到段错误发生的位置。
- 检查指针初始化和数组索引。
-
Undefined Reference (未定义引用)
- 原因:可能因为链接器找不到某个函数或变量的定义,通常发生在函数声明和实现错位、多文件项目中的未正确链接。
- 诊断方法:
- 确保所有文件都包含在编译过程中。
- 检查函数和变量是否在正确的作用域或链接中。
-
Compile Errors (编译错误)
- 原因:语法错误、类型不匹配、缺少头文件等。
- 诊断方法:
- 阅读编译器给出的错误信息,查看出错的具体行。
- 确保使用正确的数据类型和函数签名。
参考资料和推荐阅读
学习C语言及其测试和调试领域的书籍和文章有助于深入理解复杂概念和最佳实践:
-
《C程序设计语言》(The C Programming Language) - 作者:Brian W. Kernighan 和 Dennis M. Ritchie
- 经典的C语言教材,详细介绍了C语言的基本特性。
-
《C陷阱与缺陷》
- 涵盖C语言的各类常见错误,帮助开发者避免陷坑。
-
官方文档和社区资源
- glibc手册、GDB/LLDB调试器的官方文档
- 各类C语言社区论坛和在线资源,如Stack Overflow和GitHub项目。
测试和调试工具安装指南
为了在实践中有效地测试和调试C程序,安装合适的工具是关键。下面提供了一些常用工具的安装指南:
-
GDB(GNU Debugger)
- Linux/MacOS:大多数系统自带,或者可以通过包管理器(例如apt、brew)安装。
# Ubuntu sudo apt-get install gdb# MacOS (using Homebrew) brew install gdb
- Linux/MacOS:大多数系统自带,或者可以通过包管理器(例如apt、brew)安装。
-
Valgrind
- Linux:通常通过包管理工具安装。
sudo apt-get install valgrind
- Linux:通常通过包管理工具安装。
-
CUnit
- 下载最新版本的CUnit源码,然后编译安装。
wget http://downloads.sourceforge.net/cunit/cunit-2.1-3.tar.bz2 tar -xvf cunit-2.1-3.tar.bz2 cd cunit-2.1-3 ./configure make sudo make install
- 下载最新版本的CUnit源码,然后编译安装。
通过这篇附录,您可以找到有关测试和调试C语言程序的有用信息和工具,并增强解决问题的能力。
相关文章:

【C语言进阶】系统测试与调试
1. 引言 在开始本教程的深度学习之前,我们需要了解整个教程的目标及其结构,以及为何进阶学习是提升C语言技能的关键。 目标和结构: 教程目标:本教程旨在通过系统化的学习,从单元测试、系统集成测试到调试技巧…...

多个单链表的合成
建立两个非递减有序单链表,然后合并成一个非递增有序的单链表。 注意:建立非递减有序的单链表,需要采用创建单链表的算法 输入格式: 1 9 5 7 3 0 2 8 4 6 0 输出格式: 9 8 7 6 5 4 3 2 1 输入样例: 在这里给出一组输入。例如…...

『建议收藏』ChatGPT Canvas功能进阶使用指南!
大家好,我是木易,一个持续关注AI领域的互联网技术产品经理,国内Top2本科,美国Top10 CS研究生,MBA。我坚信AI是普通人变强的“外挂”,专注于分享AI全维度知识,包括但不限于AI科普,AI工…...

Ollama 运行视觉语言模型LLaVA
Ollama的LLaVA(大型语言和视觉助手)模型集已更新至 1.6 版,支持: 更高的图像分辨率:支持高达 4 倍的像素,使模型能够掌握更多细节。改进的文本识别和推理能力:在附加文档、图表和图表数据集上进…...

gdb 调试 linux 应用程序的技巧介绍
使用 gdb 来调试 Linux 应用程序时,可以显著提高开发和调试的效率。gdb(GNU 调试器)是一款功能强大的调试工具,适用于调试各类 C、C 程序。它允许我们在运行程序时检查其状态,设置断点,跟踪变量值的变化&am…...

Java项目实战II基于Java+Spring Boot+MySQL的房产销售系统(源码+数据库+文档)
目录 一、前言 二、技术介绍 三、系统实现 四、文档参考 五、核心代码 六、源码获取 全栈码农以及毕业设计实战开发,CSDN平台Java领域新星创作者 一、前言 随着房地产市场的蓬勃发展,房产销售业务日益复杂,传统的手工管理方式已难以满…...

aws(学习笔记第一课) AWS CLI,创建ec2 server以及drawio进行aws画图
aws(学习笔记第一课) 使用AWS CLI 学习内容: 使用AWS CLI配置密钥对创建ec2 server使用drawio(vscode插件)进行AWS的画图 1. 使用AWS CLI 注册AWS账号 AWS是通用的云计算平台,可以提供ec2,vpc,SNS以及clo…...

【Python】Eventlet 异步网络库简介
Eventlet 是一个 Python 的异步网络库,它使用协程(green threads)来简化并发编程。通过非阻塞的 I/O 操作,Eventlet 使得你可以轻松编写高性能的网络应用程序,而无需处理复杂的回调逻辑或编写多线程代码。它广泛应用于…...

【JNI】数组的基本使用
在上一期讲了基本类型的基本使用,这期来说一说数组的基本使用 HelloJNI.java:实现myArray函数,把一个整型数组转换为双精度型数组 public class HelloJNI { static {System.loadLibrary("hello"); }private native String HelloW…...

React跨平台
React的跨平台应用开发详解如下: 一、跨平台能力 React本身是一个用于构建用户界面的JavaScript库,但它通过React Native等框架实现了跨平台应用开发的能力。React Native允许开发者使用JavaScript和React来编写原生应用,这些应用可以在iOS和…...

如何在 SQL 中更新表中的记录?
当你需要修改数据库中已存在的数据时,UPDATE 语句是你的首选工具。 这允许你更改表中一条或多条记录的特定字段值。 下面我将详细介绍如何使用 UPDATE 语句,并提供一些开发建议和注意事项。 基础用法 假设我们有一个名为 employees 的表,…...

宠物饮水机的水箱低液位提醒如何实现?
ICMAN液位检测芯片轻松实现宠物饮水机的水箱低液位提醒功能! 工作原理 : 基于双通道电容式单点液位检测原理 方案特点: 液位检测精度高达1mm,超强抗干扰,动态CS 10V 为家用电器水位提醒的应用提供了一种简单而又有…...

EXCEL_光标百分比
Public Sub InitCells()Dim iSheet As LongFor iSheet Sheets.Count To 1 Step -1Sheets(iSheet).ActivateActiveWindow.Zoom 85ActiveWindow.ScrollRow 1ActiveWindow.ScrollColumn 1Sheets(iSheet).Range("A1").ActivateNext iSheetEnd Sub对日项目中的文档满天…...

(一)Web 网站服务之 Apache
一、Apache 的作用和特点 作用:Apache 是一款开源的网站服务器端软件,为网站的运行提供了稳定的基础。特点: 开源免费:这使得任何人都可以免费使用和修改它。模块化设计:具有高度的灵活性,可以根据需求选择…...

英语词汇小程序小程序|英语词汇小程序系统|基于java的四六级词汇小程序设计与实现(源码+数据库+文档)
英语词汇小程序 目录 基于java的四六级词汇小程序设计与实现 一、前言 二、系统功能设计 三、系统实现 四、数据库设计 1、实体ER图 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取: 博主介绍:✌️大厂码农|毕设布道师&a…...

AI学习指南深度学习篇-学习率衰减的实现机制
AI学习指南深度学习篇-学习率衰减的实现机制 前言 在深度学习中,学习率是影响模型训练的重要超参数之一。合理的学习率设置不仅可以加速模型收敛,还可以避免训练过程中出现各种问题,如过拟合或训练不收敛。学习率衰减是一种动态调整学习率的…...

My_qsort() -自己写的 qsort 函数
2024 - 10 - 05 - 笔记 - 21 作者(Author):郑龙浩 / 仟濹(网名) My_qsort()- 自己写的qsort函数 My_qsort为自己写的qsort函数,但是采用的不是快速排序,而是冒泡排序,是为了模仿qsort函数而尝试写出来的函数。 思路:…...

《向量数据库指南》——Mlivus Cloud打造生产级AI应用利器
哈哈,各位向量数据库和AI应用领域的朋友们,大家好!我是大禹智库的向量数据库高级研究员王帅旭,也是《向量数据库指南》的作者。今天,我要和大家聊聊如何使用Mlivus Cloud来搭建生产级AI应用。这可是个热门话题哦,相信大家都非常感兴趣! 《向量数据库指南》 使用Mlivus …...

Electron 进程通信
预加载(preload)脚本只能访问部分 Node.js API,但是主进程可以访问全部API。此时,需要使用进程通信。 比如,在preload.js中,不能访问__dirname,不能使用 Node 中的 fs 模块,但主进程…...

Kubernetes资源详解
华子目录 1.Kubernetes中的资源1.1资源管理介绍1.2资源管理方式1.2.1命令式对象管理1.2.2kubectl常见command命令1.2.3资源类型1.2.4常用资源类型 基本命令示例运行和调试命令示例高级命令示例总结 其他命令示例create和apply区别案例显示命名空间查看命名空间中的pod如何对外暴…...

C++11之线程
编译环境:Qt join:阻塞当前线程,直到线程函数退出 detach:将线程对象与线程函数分离,线程不依赖线程对象管理 注:join和detach两者必选其一,否则线程对象的回收会影响线程的回收,导致…...

界星空科技漆包线行业称重系统
万界星空科技为漆包线行业提供的称重系统是其MES制造执行系统解决方案中的一个重要组成部分。以下是对该系统的详细介绍: 一、系统概述 万界星空科技漆包线行业称重系统,是集成在MES系统中的一个功能模块,专门用于漆包线生产过程中的重量检…...

RabbitMQ的高级特性-事务
事务:RabbitMQ是基于AMQP协议实现的, 该协议实现了事务机制, 因此RabbitMQ也⽀持事务机制. SpringAMQP也提供了对事务相关的操作. RabbitMQ事务允许开发者确保消息的发送和接收是原⼦性的, 要么全部成功, 要么全部失败 配置事务管理器: Bean public Ra…...

Qt Linguist手册
概述 Qt 为将 Qt C 和 Qt Quick 应用程序翻译成当地语言提供了出色的支持。发布经理、翻译和开发人员可以使用 Qt 工具来完成他们的任务。 发布经理对应用程序的发布负总责。通常,他们负责协调开发人员和翻译人员的工作。他们可以使用 lupdate 工具同步源代码和翻…...

【简介Sentinel-1】
Sentinel-1是欧洲航天局哥白尼计划(GMES)中的地球观测卫星,由Sentinel-1A和Sentinel-1B两颗卫星组成。以下是对Sentinel-1的详细介绍: 一、基本信息 卫星名称:Sentinel-1 所属计划:欧洲航天局哥白尼计划…...

第 17 场小白入门赛蓝桥杯
第 17 场小白入门赛 2 北伐军费 发现每次选大的更优,所以可以排序之后,先手取右边,后手取左边。 实际发现,对于 A − B A-B A−B 的结果来说,后手对于这个式子的贡献是 − − a i --a_i −−ai ,也就…...

@antv/x6 导出图片下载,或者导出图片为base64由后端去处理。
1、导出为文件的格式,比如 PNG graph.exportPNG(function (dataURL) {console.log(dataURL);let img document.getElementById(img) as HTMLImageElement;img.src dataURL;},{backgroundColor: #fff,padding: [20, 20, 20, 20],quality: 1,width: graph.options.w…...

从零到精通:AI大模型的全方位学习路径解析,非常详细收藏我这一篇就够了
一、初聊大模型 1、什么是大模型? 大模型,通常指的是在人工智能领域中的大型预训练模型。你可以把它们想象成非常聪明的大脑,这些大脑通过阅读大量的文本、图片、声音等信息,学习到了世界的知识。这些大脑(模型&…...

PowerShell脚本在自动化Windows开发工作流程中的应用
PowerShell脚本在自动化Windows开发工作流程中的应用 在当今快速迭代的软件开发环境中,自动化已成为提高开发效率、减少人为错误、保障项目稳定性的重要手段。特别是在Windows平台上,PowerShell以其强大的脚本编写能力和对系统管理的深度集成࿰…...

【力扣 | SQL题 | 每日四题】力扣1783,1757,1747,1623,1468,1661
昨天晚上睡着了,今天把昨天的每日一题给补上。 1. 力扣1783:大满贯数量 1.1 题目: 表:Players ------------------------- | Column Name | Type | ------------------------- | player_id | int | | player_na…...