【C++】模板(函数模板和类模板) 您所在的位置:网站首页 接口测试用例模板和例子模板区别大吗 【C++】模板(函数模板和类模板)

【C++】模板(函数模板和类模板)

2024-07-05 07:40| 来源: 网络整理| 查看: 265

📝我的个人主页 🎁欢迎各位→点赞👍 + 收藏⭐️ + 留言📝​ 💬总结:希望你看完之后,能对你有所帮助,不足请指正!共同学习交流 🖊 ✉️今天你做别人不想做的事,明天你就能做别人做不到的事♐

相关视频——黑马程序员匠心之作|C++教程从0到1入门编程,学习编程不再难(167-184)

上一篇笔记:

(99-146)笔记——【黑马程序员】C++核心编程2 -类与对象(封装、继承和多态)-this指针-友元-运算重载符-文本操作(附测试用例源码、测试结果图及详细注释)

本篇文章详细的介绍了C++中模板的概念与相关应用,包含了函数模板中的语法、注意事项、案例、与普通函数的区别、调用规则和其局限性等相关知识点,以及类模板中的语法、与函数模板的区别、类模板中成员函数创建时机、 类模板对象做函数参数等相关知识点。

在记录了视频中所讲内容的同时也在一些难点上加入了我自己的理解,并附上了测试用例源码和测试结果图,以此帮助大家理解每个知识点。 只要你认真看完这篇笔记,一定能够掌握模板的概念和应用! \color{red}{只要你认真看完这篇笔记,一定能够掌握模板的概念和应用!} 只要你认真看完这篇笔记,一定能够掌握模板的概念和应用!

C++提高编程 1 模板1.1 模板的概念1.2 函数模板1.2.1 函数模板语法1.2.2 函数模板注意事项1.2.3 函数模板案例1.2.4 普通函数与函数模板的区别1.2.5 普通函数与函数模板的调用规则1.2.6 模板的局限性 1.3 类模板1.3.1 类模板语法1.3.2 类模板与函数模板区别1.3.3 类模板中成员函数创建时机1.3.4 类模板对象做函数参数(即类模板对象作为某个函数的参数传递进去)1.3.5 类模板与继承3.6 类模板成员函数类外实现1.3.7 类模板分文件编写1.3.8 类模板与友元

1 模板 1.1 模板的概念

模板就是建立通用的模具,大大提高复用性

模板的特点:

模板不可以直接使用,它只是一个框架模板的通用并不是万能的 1.2 函数模板

C++另一种编程思想称为 泛型编程 ,主要利用的技术就是模板

C++提供两种模板机制:函数模板和类模板

1.2.1 函数模板语法

函数模板作用:

建立一个通用函数,其函数返回值类型和形参类型可以不具体制定,用一个虚拟的类型来代表。

语法:

template 函数声明或定义

解释:

template — 声明创建模板

typename — 表面其后面的符号是一种数据类型,可以用class代替

T — 通用的数据类型,名称可以替换,通常为大写字母

示例:

//交换整型函数 void swapInt(int& a, int& b) { int temp = a; a = b; b = temp; } //交换浮点型函数 void swapDouble(double& a, double& b) { double temp = a; a = b; b = temp; } //利用模板提供通用的交换函数 template void mySwap(T& a, T& b) { T temp = a; a = b; b = temp; } void test01() { int a = 10; int b = 20; //swapInt(a, b); //利用模板实现交换 //1、自动类型推导 mySwap(a, b); //2、显示指定类型 mySwap(a, b); cout T temp = a; a = b; b = temp; } // 1、自动类型推导,必须推导出一致的数据类型T,才可以使用 void test01() { int a = 10; int b = 20; char c = 'c'; mySwap(a, b); // 正确,可以推导出一致的T //mySwap(a, c); // 错误,推导不出一致的T类型 } // 2、模板必须要确定出T的数据类型,才可以使用 template void func() { cout test01(); test02(); system("pause"); return 0; }

在这里插入图片描述 总结:

使用模板时必须确定出通用数据类型T,并且能够推导出一致的类型 1.2.3 函数模板案例

案例描述:

利用函数模板封装一个排序的函数,可以对不同数据类型数组进行排序排序规则从大到小,排序算法为选择排序分别利用char数组和int数组进行测试

示例:

//交换的函数模板 template void mySwap(T &a, T&b) { T temp = a; a = b; b = temp; } template // 也可以替换成typename //利用选择排序,进行对数组从大到小的排序 void mySort(T arr[], int len) { for (int i = 0; i if (arr[max] mySwap(arr[max], arr[i]); } } } template void printArray(T arr[], int len) { for (int i = 0; i //测试char数组 char charArr[] = "bdcfeagh"; int num = sizeof(charArr) / sizeof(char); mySort(charArr, num); printArray(charArr, num); } void test02() { //测试int数组 int intArr[] = { 7, 5, 8, 1, 3, 9, 2, 4, 6 }; int num = sizeof(intArr) / sizeof(int); mySort(intArr, num); printArray(intArr, num); } int main() { test01(); test02(); system("pause"); return 0; }

在这里插入图片描述 总结:模板可以提高代码复用,需要熟练掌握

1.2.4 普通函数与函数模板的区别

普通函数与函数模板区别:

普通函数调用时可以发生自动类型转换(隐式类型转换)函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换如果利用显示指定类型的方式,可以发生隐式类型转换

示例:

//普通函数 int myAdd01(int a, int b) { return a + b; } //函数模板 template T myAdd02(T a, T b) { return a + b; } //使用函数模板时,如果用自动类型推导,不会发生自动类型转换,即隐式类型转换 void test01() { int a = 10; int b = 20; char c = 'c'; cout cout cout test01(); system("pause"); return 0; }

在这里插入图片描述

总结:既然提供了函数模板,最好就不要提供普通函数,否则容易出现二义性

1.2.6 模板的局限性

局限性:

模板的通用性并不是万能的

例如:

template void f(T a, T b) { a = b; }

在上述代码中提供的赋值操作,如果传入的a和b是一个数组,就无法实现了

再例如:

template void f(T a, T b) { if(a > b) { ... } }

在上述代码中,如果T的数据类型传入的是像Person这样的自定义数据类型,也无法正常运行

因此C++为了解决这种问题,提供模板的重载,可以为这些特定的类型提供具体化的模板

示例:

#include using namespace std; #include class Person { public: Person(string name, int age) { this->m_Name = name; this->m_Age = age; } string m_Name; int m_Age; }; //普通函数模板 template bool myCompare(T& a, T& b) { if (a == b) { return true; } else { return false; } } //具体化,显示具体化的原型和定意思以template开头,并通过名称来指出类型 //具体化优先于常规模板 template bool myCompare(Person &p1, Person &p2) { if ( p1.m_Name == p2.m_Name && p1.m_Age == p2.m_Age) { return true; } else { return false; } } void test01() { int a = 10; int b = 20; //内置数据类型可以直接使用通用的函数模板 bool ret = myCompare(a, b); if (ret) { cout Person p1("Tom", 10); Person p2("Tom", 10); //自定义数据类型,不会调用普通的函数模板 //可以创建具体化的Person数据类型的模板,用于特殊处理这个类型 bool ret = myCompare(p1, p2); if (ret) { cout test01(); test02(); system("pause"); return 0; }

在这里插入图片描述 总结:

利用具体化的模板,可以解决自定义类型的通用化学习模板并不是为了写模板,而是在STL能够运用系统提供的模板 1.3 类模板 1.3.1 类模板语法

类模板作用:

建立一个通用类,类中的成员 数据类型可以不具体制定,用一个虚拟的类型来代表。

语法:

template 类

解释:

template — 声明创建模板

typename — 表面其后面的符号是一种数据类型,可以用class代替

T — 通用的数据类型,名称可以替换,通常为大写字母

示例:

#include //类模板,因为年龄和姓名是两种类型的数据,所以定义了NameType和AgeType template class Person { public: Person(NameType name, AgeType age) { this->mName = name; this->mAge = age; } void showPerson() { cout test01(); system("pause"); return 0; }

在这里插入图片描述 总结:类模板和函数模板语法相似,在声明模板template后面加类,此类称为类模板

1.3.2 类模板与函数模板区别

类模板与函数模板区别主要有两点:

类模板没有自动类型推导的使用方式类模板在模板参数列表中可以有默认参数

关于第一点可以在下面例子中的第26行得到验证,当创建类对象并通过构造函数进行初始化时,必须指定好传入的参数的数据类型,如,否则就会报错。

关于第二点,可以综合第3行和第33行来看,因为我在模板参数列表中已经默认了AgeType = int,所以在通过构造函数进行初始化并传入参数时可以不指定有默认参数的数据,我已经默认了模板参数的第二个参数是int类型,所以可以不用指定第2个传入的参数的数据类型。

示例:

#include //类模板 template class Person { public: Person(NameType name, AgeType age) { this->mName = name; this->mAge = age; } void showPerson() { cout Person p("猪八戒", 999); //类模板中的模板参数列表 可以指定默认参数 p.showPerson(); } int main() { test01(); test02(); system("pause"); return 0; }

在这里插入图片描述 总结:

类模板使用只能用显示指定类型方式类模板中的模板参数列表可以有默认参数 1.3.3 类模板中成员函数创建时机

类模板中成员函数和普通类中成员函数创建时机是有区别的:

普通类中的成员函数一开始就可以创建类模板中的成员函数在调用时才创建

如下图的示例中,我分别创建了person1和person2两个类,接着创建了一个类模板,以T obj创建了一个对象,但由于T是未知的,所以没办法确定obj是哪个类的对象。所以在27行和28行,我分别创建了两个成员函数,在这两个成员函数中,分别用obj调用了类person1中的成员函数showPerson1()和类person2中的成员函数showPerson2(),按理说obj应该是固定的一个类对象,怎么能在两个成员函数中分别调用两个类的成员函数呢?然而这样程序也是可以运行的,就是因为类模板中的成员函数在调用时才创建。

所以在第34行创建MyClass类的对象m时,给定了T的数据类型就是person1,所以obj就相当于person1的类对象,可以调用fun1,但fun2就没法调用了。因为在调用的时候成员函数就会被创建,而我给定了obj的类型就是person1,person1中是没有showPerson2成员函数的,就会报错。

示例:

class Person1 { public: void showPerson1() { cout cout obj.showPerson1(); } void fun2() { obj.showPerson2(); } }; void test01() { MyClass m; m.fun1(); //m.fun2();//编译会出错,因为fun2是Person2里的成员函数 } int main() { test01(); system("pause"); return 0; }

在这里插入图片描述

总结:类模板中的成员函数并不是一开始就创建的,在调用时才去创建

1.3.4 类模板对象做函数参数(即类模板对象作为某个函数的参数传递进去)

学习目标:

类模板实例化出的对象,向函数传参的方式

一共有三种传入方式:

指定传入的类型 — 直接显示对象的数据类型(最常用)参数模板化 — 将对象中的参数变为模板进行传递(是将函数变成了函数模板,目的就是为了使用函数模板的自动类型推导)整个类模板化 — 将这个对象类型 模板化进行传递

如第22行的printPerson1函数,我想是使第4行的类模板所创建出的实例对象作为形参传递进入该函数,该怎么做呢?

指定传入的类型 — 直接显示对象的数据类型。如第28行,在实例化出一个对象之后,直接指定传入的类型void printPerson1(Person &p) ,这样我们就可以调用该对象中的成员函数之类的参数模板化 — 将对象中的参数变为模板进行传递。如第33和第34行。同样是在printPerson1里传递参数,但这次传递的不是Person &p,而是Person&p,而函数是认不出来T1和T2是什么,所以要在前面声明template 整个类模板化 — 将这个对象类型 模板化进行传递。如第47和48行,这次传递参数直接将整个类模板化了。void printPerson3(T & p)。然后在前面声明template

示例:

#include //类模板 template class Person { public: Person(NameType name, AgeType age) { this->mName = name; this->mAge = age; } void showPerson() { cout Person p("孙悟空", 100); //实例化一个Person对象 printPerson1(p); } //2、参数模板化 template void printPerson2(Person&p) { p.showPerson(); cout cout test01(); test02(); test03(); system("pause"); return 0; }

在这里插入图片描述 总结:

通过类模板创建的对象,可以有三种方式向函数中进行传参使用比较广泛是第一种:指定传入的类型 1.3.5 类模板与继承

当类模板碰到继承时,需要注意一下几点:

当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型如果不指定,编译器无法给子类分配内存如果想灵活指定出父类中T的类型,子类也需变为类模板

我首先在第1-5行创建了一个类模板,T m定义了一个变量,但T是什么数据类型是未知的。第7行给我们展示了一个错误,即当父类是一个类模板的时候,其继承的子类应该在声明的时候指定出父类中T的类型。因为不指定的话,比如在这个例子中,系统在给子类分配内存空间的时候,就没法确定子类应该占多少内存空间,因为T m数据类型是不确定的。 所以有2种办法使子类继承父类时制定出父类中数据类型的:

硬指定;如第8行,直接在继承的时候进行指定灵活指定;如第17行和18行,实际上就是指定了父类中的T类型为子类中的T2类型,后续子类在实例化创建对象的时候,会指定好T1和T2的数据类型,实际上这时父类的T类型也就被指定了

示例:

template class Base { T m; }; //class Son:public Base //错误,c++编译需要给子类分配内存,必须知道父类中T的类型才可以向下继承 class Son :public Base //必须指定一个类型 { }; void test01() { Son c; } //类模板继承类模板 ,可以用T2指定父类中的T类型 template class Son2 :public Base { public: Son2() { cout test01(); test02(); system("pause"); return 0; }

在这里插入图片描述 总结:如果父类是类模板,子类需要指定出父类中T的数据类型

3.6 类模板成员函数类外实现

第17到21行是构造函数的类外实现,主要注意三个点,1、Person::;这是声明了该函数的作用域为Person。2、template;这是为了告诉编译器(T1 name, T2 age)中T1和T2到底是什么。3、;如果仅仅是Person::Person(T1 name, T2 age),这就只是一个普通的构造函数的类外实现。所以要加上,让编译器知道这是一个类模板的类外实现 。

成员函数类外实现与构造函数的差不多,注意加上即可

示例:

#include //类模板中成员函数类外实现 template class Person { public: //成员函数类内声明 Person(T1 name, T2 age); void showPerson(); public: T1 m_Name; T2 m_Age; }; //构造函数 类外实现 template Person::Person(T1 name, T2 age) { this->m_Name = name; this->m_Age = age; } //成员函数 类外实现 template void Person::showPerson() { cout test01(); system("pause"); return 0; }

在这里插入图片描述 总结:类模板中成员函数类外实现时,需要加上模板参数列表

1.3.7 类模板分文件编写

学习目标:

掌握类模板成员函数分文件编写产生的问题以及解决方式

正常来说,分文件编写是要有三个文件夹,一个是在头文件中创建的Person.h文件,内容是类的声明:

#pragma once //防止头文件重复包含 #include using namespace std; #include template class Person { public: Person(T1 name, T2 age); void showPerson(); public: T1 m_Name; T2 m_Age; };

一个是在源文件Person.cpp中是类的内容:

#include "person.h" //构造函数 类外实现 template Person::Person(T1 name, T2 age) { this->m_Name = name; this->m_Age = age; } //成员函数 类外实现 template void Person::showPerson() { cout this->m_Name = name; this->m_Age = age; } //成员函数 类外实现 template void Person::showPerson() { cout test01(); system("pause"); return 0; }

总结:主流的解决方式是第二种,将类模板成员函数写到一起,并将后缀名改为.hpp

1.3.8 类模板与友元

学习目标:

掌握类模板配合友元函数的类内和类外实现

全局函数类内实现 - 直接在类内声明友元即可

全局函数类外实现 - 需要提前让编译器知道全局函数的存在

示例:

全局函数类内实现:

template class Person { //1、全局函数配合友元 类内实现 friend void printPerson(Person & p) { cout Person p("Tom", 20); printPerson(p); } int main() { test01(); system("pause"); return 0; }

在这里插入图片描述 全局函数类外实现:

//因为在声明函数模板时用到了Person,所以要先声明下类模板让编译器知道它的存在 template class Person; //如果声明了函数模板,可以将实现写到后面,否则需要将实现体写到类的前面让编译器提前看到 //template //void printPerson2(Person & p); //2、全局函数配合友元 类外实现 - 先做函数模板声明,下方再做函数模板定义,再做友元 template void printPerson2(Person & p) { cout this->m_Name = name; this->m_Age = age; } private: T1 m_Name; T2 m_Age; }; //2、全局函数在类外实现 void test02() { Person p("Jerry", 30); printPerson2(p); } int main() { test02(); system("pause"); return 0; }

在这里插入图片描述



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有