【C++篇】引领C++模板初体验:泛型编程的力量与妙用

文章目录

  • C++模板编程
    • 前言
      • 第一章: 初始模板与函数模版
        • 1.1 什么是泛型编程?
          • 1.1.1 为什么要有泛型编程?
          • 1.1.1 泛型编程的优势
        • 1.2 函数模板的基础
          • 1.2.1 什么是函数模板?
          • 1.2.2 函数模板的定义格式
          • 1.2.3 示例:通用的交换函数
            • 输出示例:
          • 1.2.4 模板中的`typename`与`class`
        • 1.3 函数模板的原理
          • 1.3.1 函数模板的实例化
          • 1.3.2 隐式实例化与显式实例化
      • 第二章: 类模板
        • 2.1 类模板概念
          • 2.1.1 类模板的定义格式
          • 2.1.2 示例:简单的类模板
            • 输出示例:
        • 2.2 类模板的实例化
        • 2.3 类模板中的成员函数定义
        • 2.4 为什么不建议类模板的定义和声明分离?
          • 2.4.1 模板的编译时行为
          • 2.4.2 链接器无法找到定义
          • 2.4.3 无法预编译模板
          • 2.4.4 解决方案:将声明和定义放在同一个头文件中
      • 第三章: 模板的匹配原则
        • 3.1 模板的匹配原则
  • 写在最后

C++模板编程

💬 欢迎讨论:如果你在学习过程中有任何问题或想法,欢迎在评论区留言,我们一起交流学习。你的支持是我继续创作的动力!

👍 点赞、收藏与分享:觉得这篇文章对你有帮助吗?别忘了点赞、收藏并分享给更多的小伙伴哦!你们的支持是我不断进步的动力!
🚀 分享给更多人:如果你觉得这篇文章对你有帮助,欢迎分享给更多对C++感兴趣的朋友,让我们一起进步!

前言

C++作为一门强大的编程语言,以其丰富的功能和灵活的设计著称。模板编程是C++中非常重要的一个特性,通过模板可以实现泛型编程,编写与数据类型无关的代码,极大地提高了代码的复用性和可维护性。本文将从泛型编程、函数模板、类模板等几个方面详细讲解C++模板的使用,并结合实际的代码示例进行分析,帮助大家全面掌握模板编程的知识。

本篇文章将包含以下几个部分:

  1. 泛型编程的基本概念
  2. 函数模板的定义与使用
  3. 类模板的实现
  4. 模板的匹配原则

通过阅读本文,你将能够掌握C++模板编程的基础知识,理解其背后的工作原理,并学会如何在实际项目中应用这些技术。


第一章: 初始模板与函数模版

1.1 什么是泛型编程?

泛型编程(Generic Programming)是C++中的一种编程范式,旨在编写与数据类型无关的通用代码。这意味着你可以编写一次代码,并通过不同的数据类型进行复用。C++通过模板(Template)来实现泛型编程,模板是泛型编程的核心工具。

1.1.1 为什么要有泛型编程?
  • 问题提出:如何实现一个通用的交换函数呢?
void Swap(int& left, int& right)
{
    int temp = left;
    left = right;
    right = temp;
}
void Swap(double& left, double& right)
{
    double temp = left;
    left = right;
    right = temp;
}
void Swap(char& left, char& right)
{
    char temp = left;
    left = right;
    right = temp;
}
//....等等

使用函数重载虽然可以实现,但是有一下几个不好的地方:

  1. 重载的函数仅仅是类型不同,代码复用率比较低,只要有新类型出现时,就需要用户自己增加对应的函数
  2. 代码的可维护性比较低,一个出错可能所有的重载均出错

那能否告诉编译器一个模子,让编译器根据不同的类型利用该模子来生成代码呢?

在这里插入图片描述
如果在C++中,也能够存在这样一个模具,通过给这个模具中填充不同材料(类型),来获得不同
材料的铸件(即生成具体类型的代码),那将会节省许多头发。巧的是前人早已将树栽好,我们只需在此乘凉。这就是泛型编程

泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础

1.1.1 泛型编程的优势

泛型编程的主要优势包括:

  • 代码复用性强:通过模板,你可以避免为每个数据类型单独编写相同功能的代码。
  • 提高代码的可维护性:代码只需编写一次,减少了冗余代码,后续如果需要修改或修复,只需在一处进行。
  • 减少编写错误:重复编写代码时容易出错,而模板可以让编译器自动生成所需代码,减少人为失误。

在这里插入图片描述


1.2 函数模板的基础
1.2.1 什么是函数模板?

函数模板(Function Template)是一个与类型无关的函数“蓝图”。通过模板参数,编译器在编译期间会根据实际的数据类型生成相应的函数版本。

1.2.2 函数模板的定义格式

我们可以通过以下格式来定义一个函数模板:

template<typename T> 
返回类型 函数名(参数列表) {
    // 函数体
}
  • template:告诉编译器接下来的内容是模板。
  • typename T:定义一个模板参数T,可以用来表示任何类型。
  • 返回类型参数列表可以使用T作为数据类型。

注意:typename是用来定义模板参数关键字,也可以使用class(切记:不能使用struct代替
class),下文会讲到

1.2.3 示例:通用的交换函数

那我们就可以使用模板来编写交换函数,它可以交换任意类型的数据:

/**
 * @brief 通用的交换函数
 * @tparam T 通用的类型参数,由编译器根据实参推断
 * @param left 左侧变量
 * @param right 右侧变量
 */
template<typename T>
void Swap(T& left, T& right) {
    T temp = left;
    left = right;
    right = temp;
}

int main() {
    int a = 10, b = 20;
    double x = 1.1, y = 2.2;
    char c1 = 'A', c2 = 'B';

    // 使用模板函数进行交换
    Swap(a, b);    
    Swap(x, y);    
    Swap(c1, c2);  

    std::cout << "交换后的整数: " << a << " " << b << std::endl;
    std::cout << "交换后的浮点数: " << x << " " << y << std::endl;
    std::cout << "交换后的字符: " << c1 << " " << c2 << std::endl;

    return 0;
}
输出示例:
交换后的整数: 20 10
交换后的浮点数: 2.2 1.1
交换后的字符: B A
1.2.4 模板中的typenameclass

在定义模板时,typenameclass是可以互换的。你可以选择以下两种方式:

template<typename T>  // 使用 typename
template<class T>     // 使用 class

虽然两者功能相同,但推荐使用typename,因为它能够更好地表达该参数是一个类型参数,避免与类的定义产生混淆。


1.3 函数模板的原理

函数模板的核心在于它不是一个真正的函数,而是一个编译器用来生成特定类型函数的蓝图。编译器根据模板的使用情况推导出具体的类型,并生成相应的代码。这一过程称为模板的实例化

1.3.1 函数模板的实例化

当我们调用模板函数时,编译器会根据实际的参数类型生成对应的函数版本。比如:

template<class T>
T Add(const T& left, const T& right) {
    return left + right;
}

int main() {
    int a = 10, b = 20;
    double x = 1.1, y = 2.2;

    Add(a, b);  // 生成 Add<int> 版本
    Add(x, y);  // 生成 Add<double> 版本

    return 0;
}

在这里插入图片描述

在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应
类型的函数以供调用。比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,
将T确定为double类型,然后产生一份专门处理double类型的代码,对于字符类型也是如此。

1.3.2 隐式实例化与显式实例化
template<class T>
    T Add(const T& left, const T& right)
{
    return left + right;
}
int main()
{
    int a1 = 10, a2 = 20;
    double d1 = 10.0, d2 = 20.0;
    Add(a1, a2);
    Add(d1, d2);

    Add(a1, d1);
    /*
该语句不能通过编译,因为在编译期间,当编译器看到该实例化时,需要推演其实参类型
通过实参a1将T推演为int,通过实参d1将T推演为double类型,但模板参数列表中只有
一个T,
编译器无法确定此处到底该将T确定为int 或者 double类型而报错
注意:在模板中,编译器一般不会进行类型转换操作,因为一旦转化出问题,编译器就需要
背黑锅

*/
    // 此时有两种处理方式:1. 用户自己来强制转化 2. 使用显式实例化
    Add(a1, (int)d1);//自己强制转换
    return 0;
}

模板实例化分为两种:

  1. 隐式实例化:编译器根据实参推导出模板参数,并自动生成函数。例如上面的Add函数就是隐式实例化。
  2. 显式实例化:如果想要强制指定模板参数,可以使用显式实例化:

就是第二种处理方式

int main(void)
{
    int a = 10;
    double b = 20.0;
    // 显式实例化
    Add<int>(a, b);
    return 0;
};

第二章: 类模板

2.1 类模板概念

类模板(Class Template)是用于定义与类型无关的类,它允许我们在类的定义中使用模板参数,编译时再根据实际类型进行类的实例化。类模板与函数模板类似,只不过它是用来生成类的。

2.1.1 类模板的定义格式

定义类模板的语法格式如下:

template<class T>
class 类名 {
    // 类的成员变量和方法
};
  • template<class T>:告诉编译器这是一个模板类,T是一个类型参数。
  • 类的成员和方法可以使用T作为数据类型,编译时由用户提供的类型来替代T
2.1.2 示例:简单的类模板

下面是一个简单的栈(Stack)类模板,用于存储任意类型的数据:

#include<iostream>

using namespace std;

/**
 * @brief 栈的类模板
 * @tparam T 通用的类型参数
 */
template<class T>
class Stack {
public:
    Stack(size_t capacity = 4) {
        _array = new T[capacity];
        _capacity = capacity;
        _size = 0;
    }

    ~Stack() {
        delete[] _array;
    }

    /// @brief 将元素压入栈中
    void Push(const T& data) {
        if (_size == _capacity) {
            Expand();  // 扩容
        }
        _array[_size++] = data;
    }

    /// @brief 弹出栈顶元素
    void Pop() {
        if (_size > 0) {
            --_size;
        }
    }

    /// @brief 返回栈顶元素
    T& Top() {
        if (_size > 0) {
            return _array[_size - 1];
        }
        throw out_of_range("栈为空!");
    }

    /// @brief 检查栈是否为空
    bool IsEmpty() const {
        return _size == 0;
    }

    /// @brief 获取栈中元素的数量
    size_t Size() const {
        return _size;
    }

private:
    T* _array;
    size_t _capacity;
    size_t _size;

    /// @brief 扩展栈的容量
    void Expand() {
        size_t newCapacity = _capacity * 2;
        T* newArray = new T[newCapacity];
        for (size_t i = 0; i < _size; ++i) {
            newArray[i] = _array[i];
        }
        delete[] _array;
        _array = newArray;
        _capacity = newCapacity;
    }
};

int main() {
    Stack<int> st;  // 创建存储整数的栈
    st.Push(10);
    st.Push(20);
    st.Push(30);

    cout << "栈顶元素: " << st.Top() << endl;
    st.Pop();
    cout << "弹出后栈顶元素: " << st.Top() << endl;

    return 0;
}
输出示例:
栈顶元素: 30
弹出后栈顶元素: 20

在这个类模板中,T是一个通用类型参数。Stack<int>Stack类模板的一个实例化,表示它是一个存储int类型数据的栈。编译器会根据实际使用的类型自动生成相应的类。

2.2 类模板的实例化

与函数模板不同,类模板在使用时必须显示地提供类型参数。实例化类模板时,必须在类名后面的尖括号<>中指定实际的类型参数。例如:

Stack<int> st1;    // 实例化为处理 int 类型的栈
Stack<double> st2; // 实例化为处理 double 类型的栈

这里的Stack<int>Stack<double>分别表示不同的类型,即不同的模板实例。编译器会根据模板参数生成相应的类代码。

2.3 类模板中的成员函数定义

对于类模板,成员函数可以在类定义内或定义外实现。类模板的成员函数定义外置时,需要在函数名之前加上模板声明和模板参数。例如:

template<class T>
void Stack<T>::Push(const T& data) {
    if (_size == _capacity) {
        Expand();  // 扩容
    }
    _array[_size++] = data;
}
2.4 为什么不建议类模板的定义和声明分离?

在C++中,类模板的实现与普通类有一个显著的区别:模板是在编译时根据实际类型实例化的,而不是像普通的类那样在编译期和链接期处理。这导致了一个很重要的问题:如果将类模板的声明和定义分离到不同的文件中,可能会导致链接错误。以下是详细原因:

2.4.1 模板的编译时行为

类模板的本质是一个“蓝图”,它并不是一个完整的类,而是一个在需要时根据实际类型生成代码的模式。因此,模板只有在实际使用(实例化)时,编译器才会生成对应的类型的代码。编译器无法预先知道你会使用哪些类型来实例化模板,因此它不会为模板生成实际的代码。

2.4.2 链接器无法找到定义

当你将类模板的声明放在头文件中,而把定义放在.cpp文件中时,模板实例化的过程可能发生在不同的编译单元中。因为模板只有在编译期被实例化,链接器在链接时无法看到模板的定义,除非在编译时所有模板的实例化代码都可见。如果定义在.cpp文件中,其他使用模板的编译单元无法找到这个定义,导致链接器报错

2.4.3 无法预编译模板

与普通类不同,类模板无法被预编译或只在一个编译单元中定义然后供其他单元使用。普通的类在编译过程中,编译器会生成目标代码并储存在.obj文件中,链接时其他编译单元可以引用这些已生成的代码。而类模板无法这样做,因为它需要知道使用时的类型才能生成实际的代码。

2.4.4 解决方案:将声明和定义放在同一个头文件中

为了避免上述问题,C++的惯用方法是将类模板的声明和定义都放在同一个头文件中。这使得每个使用模板的编译单元在实例化模板时,编译器能够访问到模板的定义,并根据需要生成实际的代码。这种方式确保了编译器能够在编译期处理模板的实例化,而不会在链接时出现找不到定义的问题。

错误用法:

// Stack.h
template<typename T>
class Stack {
public:
    void Push(const T& value);
    // 声明在头文件中
};

// Stack.cpp
template<typename T>
void Stack<T>::Push(const T& value) {
    // 定义在.cpp文件中
}

在这种情况下,如果不同编译单元使用了Stack<int>Stack<double>,链接器可能会报错,因为它无法找到模板的定义。

正确用法:

// Stack.h
template<typename T>
class Stack {
public:
    void Push(const T& value);
};

template<typename T>
void Stack<T>::Push(const T& value) {
    // 声明和定义都在头文件中
}

这种方法确保每个编译单元都能访问到模板的完整定义,避免链接时的错误。

总结:

类模板的代码只有在实例化时才生成,因此类模板的定义必须在每个使用它的编译单元中可见。将模板的声明和定义放在同一个头文件中,可以确保模板实例化时能够访问到其定义,避免链接错误。这也是为什么大多数C++开发者在编写模板时会将模板的实现放在头文件中的原因。

注意:

函数模板与类模板不同,当代大多数编译器支持函数模板的声明和定义分离,这是因为函数模板的实例化往往只涉及函数的具体调用,不像类模板这么复杂,具体之后的博客会更详细的讲解此处的内容,敬请期待哦💕


第三章: 模板的匹配原则

3.1 模板的匹配原则

C++编译器在调用模板时,会根据实参类型和函数参数类型进行匹配。模板的匹配规则如下:

  1. 优先调用非模板函数:如果存在一个与实参完全匹配的非模板函数,编译器将优先调用非模板函数,而不是通过模板生成一个实例。

    例如:

    int Add(int a, int b) {
        return a + b;
    }
    
    template<typename T>
    T Add(T a, T b) {
        return a + b;
    }
    
    int main() {
        int a = 10, b = 20;
        cout << Add(a, b) << endl;  // 调用非模板版本
    }
    
  2. 如果非模板函数没有匹配,则调用模板实例:如果模板函数比非模板函数更能匹配参数类型,编译器将生成模板实例。

    例如:

    double Add(double a, double b) {
        return a + b;
    }
    
    template<typename T>
    T Add(T a, T b) {
        return a + b;
    }
    
    int main() {
        int a = 10, b = 20;
        double x = 1.1, y = 2.2;
    
        cout << Add(a, b) << endl;  // 调用模板实例 Add<int>
        cout << Add(x, y) << endl;  // 调用非模板版本 Add(double, double)
    }
    

写在最后

本文基础的讲解了C++模板编程的基础知识,涵盖了泛型编程、函数模板、类模板、模板匹配原则等概念。通过这些模板功能,C++开发者可以编写更加灵活和可复用的代码,大幅提高编程效率。

模板编程虽然强大,但使用时也需要谨慎,尤其是在处理模板特化和匹配规则时。如果能够合理地使用模板技术,相信你的代码质量将会有显著提升。


以上就是关于【C++篇】引领C++模板初体验:泛型编程的力量与妙用的内容啦,在之后会有另一篇博客来讲解有关模板的更多进阶内容,敬请期待哦,然后各位大佬有什么问题欢迎在评论区指正,或者私信我也是可以的啦,您的支持是我创作的最大动力!❤️

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Redis面试真题总结(四)

文章收录在网站&#xff1a;http://hardyfish.top/ 文章收录在网站&#xff1a;http://hardyfish.top/ 文章收录在网站&#xff1a;http://hardyfish.top/ 文章收录在网站&#xff1a;http://hardyfish.top/ AOF 持久化&#xff1f; AOF&#xff08;Append Only File&#x…

ETCD学习使用

一、介绍 etcd&#xff08;分布式键值存储&#xff09;是一个开源的分布式系统工具&#xff0c;用于可靠地存储和提供键值对数据。etcd 通常通过 HTTP 或 gRPC 提供 API&#xff0c;允许应用程序通过简单的接口与其交互。由于其可靠性和稳定性&#xff0c;etcd 在构建可扩展、分…

【OpenAI o1背后技术】Sef-play RL:LLM通过博弈实现进化

【OpenAI o1背后技术】Sef-play RL&#xff1a;LLM通过博弈实现进化 OpenAI o1是经过强化学习训练来执行复杂推理任务的新型语言模型。特点就是&#xff0c;o1在回答之前会思考——它可以在响应用户之前产生一个很长的内部思维链。也就是该模型在作出反应之前&#xff0c;需要…

k8s中pod的创建过程和阶段状态

管理k8s集群 kubectl k8s中有两种用户 一种是登录的 一种是/sbin/nologin linux可以用密码登录&#xff0c;也可以用证书登录 k8s只能用证书登录 谁拿到这个证书&#xff0c;谁就可以管理集群 在k8s中&#xff0c;所有节点都被网络组件calico设置了路由和通信 所以pod的ip是可以…

WebRTC编译后替换libwebrtc.aar时提示找不到libjingle_peerconnection_so.so库

Loading native library: jingle_peerconnection_so 问题原因&#xff1a;编译的时候只编译了armeabi-v7a的版本&#xff0c;但是应用程序是arm64-v8a&#xff0c;所以无法运行 解决方法&#xff1a;更新编译脚本&#xff0c;加上arm64-v8a进行编译 ./tools_webrtc/android/bu…

【漏洞复现】用友 NC-Cloud queryStaffByName Sql注入漏洞

免责声明&#xff1a; 本文内容旨在提供有关特定漏洞或安全漏洞的信息&#xff0c;以帮助用户更好地了解可能存在的风险。公布此类信息的目的在于促进网络安全意识和技术进步&#xff0c;并非出于任何恶意目的。阅读者应该明白&#xff0c;在利用本文提到的漏洞信息或进行相关测…

windows 驱动实例分析系列-COM驱动案例讲解

COM也被称之为串口,这是一种非常简单的通讯接口,这种结构简单的接口被广泛的应用在开发中,几乎所有系统都能支持这种通讯接口,它有RS232和RS485等分支,但一般我们都会使用RS232作为常见的串口,因为它足够简单和高效。 几乎所有的开发板,都会提供用于烧录、调试、日志的…

常见中间件漏洞(Apache)

CVE-2021-41773 搭建环境 docker pull blueteamsteve/cve-2021-41773:no-cgid curl http://192.168.10.190:8080/cgi-bin/.%2e/.%2e/.%2e/.%2e/etc/passwd 工具 验证

银河麒麟高级服务器操作系统V10:提升普通用户操作权限

银河麒麟高级服务器操作系统V10&#xff1a;提升普通用户操作权限 1. 打开终端2. 切换到root用户&#xff08;可选&#xff09;3. 将用户加入到wheel组4. 验证用户组变更5. 使用sudo执行命令结论 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f4…

神经网络面试题目

1. 批规范化(Batch Normalization)的好处都有啥&#xff1f;、 A. 让每一层的输入的范围都大致固定 B. 它将权重的归一化平均值和标准差 C. 它是一种非常有效的反向传播(BP)方法 D. 这些均不是 正确答案是&#xff1a;A 解析&#xff1a; ‌‌‌‌  batch normalization 就…

ChatGPT 在国内使用的方法

AI如今很强大&#xff0c;聊聊天、写论文、搞翻译、写代码、写文案、审合同等等&#xff0c;ChatGPT 真是无所不能~ 作为一款出色的大语言模型&#xff0c;ChatGPT 实现了人类般的对话交流&#xff0c;最主要是能根据上下文进行互动。 接下来&#xff0c;我将介绍 ChatGPT 在国…

docker|Oracle数据库|docker快速部署Oracle11g和数据库的持久化(可用于生产环境)

一、 容器数据持久化的概念 docker做为容器化的领先技术&#xff0c;现在广泛应用于各个平台中&#xff0c;但不知道什么时候有一个说法是docker并不适用容器化数据库&#xff0c;说容器化的数据库性能不稳定&#xff0c;其实&#xff0c;这个说法主要是因为对docker的数据持…

电气设备施工现场风险状态判断ai模型训练数据集

电气设备施工现场风险状态判断ai模型训练数据集 id:18 电气设备施工现场工人人工智能学习数据和工作环境安全数据&#xff0c;建立系统化管理体系&#xff0c;改变全球EHS范式&#xff0c;预防工业事故。数据集记录了387709例子电力设施建设以及施工现场相关的灾害安全环境数据…

Python进阶学习笔记(一)对象

1.对象模型 在面向对象理论中类和对象是不同的概念&#xff0c;而在python中类也是对象&#xff0c;叫做类型对象。 所以python中的类&#xff0c;实例对象&#xff0c;类型都是对象。 元类型&#xff1a; 在python中实例对象的类型为对应类型的对象&#xff0c;而类型的对象…

FastAPI 的隐藏宝石:自动生成 TypeScript 客户端

在现代 Web 开发中&#xff0c;前后端分离已成为标准做法。这种架构允许前端和后端独立开发和扩展&#xff0c;但同时也带来了如何高效交互的问题。FastAPI&#xff0c;作为一个新兴的 Python Web 框架&#xff0c;提供了一个优雅的解决方案&#xff1a;自动生成客户端代码。本…

Java笔试面试题AI答之设计模式(3)

文章目录 11. Spring开发中的哪里使用了工厂设计模式 &#xff1f;1. BeanFactory2. 工厂方法模式3. 抽象工厂模式4. 示例说明总结 12. 什么是代理模式 &#xff1f;13. 请列举代理模式的应用场景 &#xff1f;14. 什么是原型模式 &#xff1f;15. 请简述Java中原型模式的使用方…

c++二叉搜索树

⼆叉搜索树的概念 ⼆叉搜索树又称⼆叉排序树&#xff0c;它或者是⼀棵空树&#xff0c;或者是具有以下性质的⼆叉树: • 若它的左子树不为空&#xff0c;则左子树上所有结点的值都小于等于根结点的值 • 若它的右子树不为空&#xff0c;则右子树上所有结点的值都大于等于根结…

Contact Form 7最新5.9.8版错误修复方案

最近有多位用户反应Contact Form 7最新5.9.8版的管理页面有错误如下图所示 具体错误文件的路径为wp-content\plugins\contact-form-7\admin\includes\welcome-panel.php on line 153 找到welcome-panel.php这个文件编辑它&#xff0c;将如下图选中的部分删除 删除以后&#xf…

洛谷P5740——结构体运用

简单的结构体&#xff0c;但是要注意这个排序还有求和重复 时的特判 AC代码附在后面 #include<bits/stdc.h> using namespace std; struct Node{string name;int a,b,c,sum;//语文&#xff0c;数学&#xff0c;英语 }node[1000]; bool cmp(Node a,Node b){return a.sum…

软件测试之测试用例

1. 测试用例的基本要素 测试用例&#xff08;Test Case&#xff09;是为了实施测试而向被测试的系统提供的一组集合&#xff0c;这组集合包含&#xff1a;测试环 境、操作步骤、测试数据、预期结果等要素。 好的测试用例是一个不熟悉业务的人也能依据用例来很快的进行测试 评价…