100字范文,内容丰富有趣,生活中的好帮手!
100字范文 > C++笔记7:C++提高编程1:模板—[函数模板和类模板]

C++笔记7:C++提高编程1:模板—[函数模板和类模板]

时间:2021-08-19 10:21:52

相关推荐

C++笔记7:C++提高编程1:模板—[函数模板和类模板]

0820

C++提高编程:

1、模板—[函数模板和类模板]

2、初识STL

3、STL—常用容器

4、STL—函数对象

5、STL—常用算法

C++提高编程引言:

C++除了面向对象编程思想,还有泛型编程思想。

泛型编程主要是利用模板技术来实现的。

1、模板

1.1 模板的概念

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

模板的特点:

①模板不可以直接使用,它只是一个框架而已;

②模板不是万能的。

1.2 函数模板

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

1.2.1 语法

template<typename T> 函数声明或定义

其中:

template — 声明创建模板

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

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

示例:

template <typename T>//这里的T就可以是各种类型了int float double等void mySwap(T& a, T& b) {//这里的a和b必须是同一数据类型T temp = a;a = b;b = temp;}int main() {int a = 10, b = 20;cout << "a = " << a << ";b = " << b << endl;//10 20//使用`函数模板`有两种方式:自动类型推导、显示指定类型//1、自动类型推导mySwap(a, b);cout << "a = " << a << ";b = " << b << endl;//20 10//2、显示指定类型mySwap<int>(a, b);//mySwap<double>(a, b);//报错cout << "a = " << a << ";b = " << b << endl;//10 20system("pause");return 0;}

1.2.2 注意事项

使用函数模板有两种方式:自动类型推导、显示指定类型(见上面的程实例)

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

1.2.3 案例

对数组进行选择排序模板+打印

#include<iostream>#include<string>using namespace std;//函数模板案例:排序+打印//排序模板:template<class T>//typename可以用从class代替void SelectionSort1(T arr[], int length) {//注意,不要写成T[] arrfor (int i = 0; i < length - 1; i++) {int min = i;for (int j = i + 1; j < length; j++) {if (arr[j] < arr[min])min = j;}if (min != i) {T temp = arr[i];//注意,这里是Tarr[i] = arr[min];arr[min] = temp;}}}//打印模板:template <class T>void printArr1(T arr[],int length) {//不是T[] arrfor (int i = 0; i < length; i++) cout << arr[i] << ",";cout << '\n' << endl;}int main(){//函数模板案例测试:cout << "开始测试函数模板案例:" << endl;//测试int数组int intArr[] = { 7, 5, 8, 1, 3, 9, 2, 4, 6 };int length1 = sizeof(intArr) / sizeof(intArr[0]);cout << "length = " << length1 << endl;SelectionSort1(intArr, length1);printArr1(intArr,length1);//测试字符数组char charArr[] = "bdcfeagh";length1 = sizeof(charArr) / sizeof(charArr[0]);//cout << length1 << endl;//这个字符数组占9个字节length1 = strlen(charArr);cout << length1 << endl;//但这个字符串中包含8个字符,不算结束符‘\0’SelectionSort1(charArr, length1);printArr1(charArr, length1);//字符数组的长度???见/weixin_38665351/article/details/119868537//测试string//会报错,因为模板中的参数是(T arr[],int len),但string不是一个数组string str = "bhdceafg";length1 = str.length();cout << length1 << endl;//但这个字符串中包含8个字符,不算结束符‘\0’//SelectionSort1(str, length1);//printArr1(str, length1);system("pause");return 0;}

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

使用函数模板有两种方式:自动类型推导、显示指定类型(见1.2.2)

//1、自动类型推导mySwap(a, b);//2、显示指定类型mySwap<int>(a, b);

区别:

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

示例:

//普通函数int myAdd01(int a, int b){return a + b;}//函数模板template<class T>T myAdd02(T a, T b) {return a + b;}int main() {int a = 10;int b = 20;char c = 'c';cout << myAdd01(a, c) << endl; //正确,将char类型的'c'隐式转换为int类型 'c' 对应 ASCII码 99//myAdd02(a, c); // 报错,使用自动类型推导时,不会发生隐式类型转换myAdd02<int>(a, c); //正确,如果用显示指定类型,可以发生隐式类型转换system("pause");return 0;}

总结:建议使用显示指定类型的方式,调用函数模板,因为可以自己确定通用类型T

1.2.5 普通函数与函数模板的调用规则

调用规则如下:

如果函数模板和普通函数都可以实现,优先调用普通函数可以通过空模板参数列表来强制调用函数模板

myPrint<>(a, b); //调用函数模板

函数模板也可以发生重载

template<typename T>void myPrint(T a, T b) { cout << "调用的模板" << endl;}template<typename T>void myPrint(T a, T b, T c) { cout << "调用重载的模板" << endl; }

如果函数模板可以产生更好的匹配,优先调用函数模板

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

1.2.6 模板的重载—具体化的模板

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

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

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

示例:

//具体化的模板:class Person {public:string name;int age;void setInfo(string name, int age) {this->name = name;this->age = age;}};template<class T>bool isEqual(T &a,T &b) {if (a == b)return 1;elsereturn 0;}//假如把Person类作为参数传入模板,模板的局限性就体现出来了//所以要为Person类专门提供一个具体化的模板template<> bool isEqual(Person& p1, Person& p2) {if ((p1.age == p2.age) && (p1.name == p2.name))return 1;elsereturn 0;}int main() {//具体化的模板---测试:cout << "开始具体化模板的测试:" << endl;int m, n;m = 10;n = 9;cout << isEqual(m, n) << endl;//Person p1;p1.setInfo("Tom",10);Person p2;p2.setInfo("Tom", 10);cout << isEqual(p1, p2) << endl;//system("pause");return 0;}

总结:

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

1.3 类模板

1.3.1 语法

1.3.2 类模板与函数模板的区别

1.3.3 类模板中成员函数创建时机

1.3.4 类模板实例化的对象做函数参数

1.3.5 类模板与继承

1.3.6 类模板成员函数在类外实现

1.3.7 类模板分文件编写

1.3.8 类模板与友元

1.3.1 语法

template<typename T>//typename可以用class代替类

示例:

//类模板template<class NameType, class AgeType>//多个参数(成员变量)class Person {public:NameType name;//NameType原本是stringAgeType age;//AgeType原本是intpublic://构造函数Person() {}void setInfo(NameType name, AgeType age) {this->name = name;this->age = age;}void showInfo() {cout << name << " " << age << endl;}};int main() {Person<string, int>p1;//类模板只有显示指定类型;函数模板的使用方式:自动类型推导、显示指定类型;; //即类模板在实例化对象时,需要指明参数类型!!!p1.setInfo("Tom", 25);p1.showInfo();system("pause");return 0;}

1.3.2 类模板与函数模板的区别

使用方式:

类模板:只有显示指定类型(没有自动类型推导的使用方式)

函数模板:自动类型推导、显示指定类型

1.3.3 类模板中成员函数创建时机

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

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

示例:

//类模板中的成员函数在调用时才创建class Person1{public:void showPerson1(){cout << "Person1 show" << endl;}};class Person2{public:void showPerson2(){cout << "Person2 show" << endl;}};template<class T>class MyClass{public:T obj;//类模板中的成员函数,并不是一开始就创建的,而是在模板调用时再生成void fun1() { obj.showPerson1(); }void fun2() { obj.showPerson2(); }};int main(){//类模板中的成员函数在调用时才创建MyClass<Person1> m1;//m1是Person1类型m1.fun1();//m1.fun2();//错误C2039"showPerson2": 不是 "Person1" 的成员;//说明类模板中的成员函数,并不是一开始就创建的,而是在模板调用时再生成MyClass<Person2> m2;//m2是Person2类型//m2.fun1();//错误C2039"showPerson1": 不是 "Person2" 的成员;m2.fun2();system("pause");return 0;}

1.3.4 类模板实例化的对象做函数参数

类模板实例化的对象,向函数传参的方式,一共有三种传入方式:

指定传入的类型 — 直接显示对象的数据类型参数模板化 — 将对象中的参数变为模板进行传递整个类模板化 — 将这个对象类型 模板化进行传递

template<class NameType, class AgeType = int> class Person{public:Person(NameType name, AgeType age){this->mName = name;this->mAge = age;}void showPerson(){cout << "name: " << this->mName << " age: " << this->mAge << endl;}public:NameType mName;AgeType mAge;};//1、指定传入的类型void printPerson1(Person<string, int> &p) //直接指明形参中T的类型{p.showPerson();}void test01(){Person <string, int >p("孙悟空", 100);//类模板实例化对象要指明T的类型printPerson1(p);}//2、参数模板化template <class T1, class T2>void printPerson2(Person<T1, T2>&p)//形参是模板的形式:Person<T1, T2>&p{p.showPerson();cout << "T1的类型为: " << typeid(T1).name() << endl;cout << "T2的类型为: " << typeid(T2).name() << endl;}void test02(){Person <string, int >p("猪八戒", 90);//类模板实例化对象要指明T的类型printPerson2(p);}//3、整个类模板化template<class T>void printPerson3(T & p)//把整个Person类模板化{cout << "T的类型为: " << typeid(T).name() << endl;p.showPerson();}void test03(){Person <string, int >p("唐僧", 30);//类模板实例化对象要指明T的类型printPerson3(p);}int main() {test01();test02();test03();system("pause");return 0;}

1.3.5 类模板与继承

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

普通子类继承父类模板时,子类在声明的时候(也即继承的时候),就要指定出父类中T的类型;

模板子类继承父类模板时,

template<class nameType, class ageType,class genderType>//①要把父类模板中的所有参数也包括进来

class Dogs :public Animals<nameType, ageType> { //②这样就可以灵活指定父类中T的类型,在实例化子类模板的对象时一次性指明(父类和子类模板中的)所有的T

public:

//成员变量

};

int main()

{

Dogs<string,int,char>dog;//③实例化子类模板的对象时一次性指明(父类和子类模板中的)所有的T

}

示例:

//类模板与继承template<class nameType,class ageType>class Animals {public://成员变量nameType name;ageType age;public://成员函数void setInfo(nameType name, ageType age) {this->name = name;this->age = age;}void showInfo() {cout << this->name << " " << this->age << endl;}};//子类不是模板类class Cats :public Animals<string,int>{//普通子类在继承时就必须要指定父类模板中T的类型};//子类是模板类template<class nameType, class ageType,class genderType>//要把父类模板中的所有参数也包括进来class Dogs :public Animals<nameType, ageType> {//这样就可以灵活指定父类中T的类型,在实例化子类模板的对象时一次性指明(父类和子类模板中的)所有的Tpublic:genderType gender;public:void setInfo(nameType name, ageType age,genderType gender){this->name = name;this->age = age;this->gender = gender;} void showInfo() {cout << this->name << " " << this->age << " " << this->gender << endl;}};int main() {//类模板与继承//普通子类继承父类模板Cats cat;//普通类就正常实例化对象cat.setInfo("咪咪", 2);cat.showInfo();//子类模板继承父类模板Dogs<string,int,char>dog;//类模板在实例化对象的时候需要指明参数类型//在实例化子类模板的对象时一次性指明(父类和子类模板中的)所有的Tdog.setInfo("米奇", 2, 'm');dog.showInfo();system("pause");return 0;}

1.3.6 类模板成员函数在类外实现

类模板中的构造函数和成员函数在类外实现,加作用域的方式有所不同:

①类模板声明不能少;template <class nameType, class yearType>②需要加上模板参数列表;Books<nameType, yearType>::showInfo() { ...}//成员函数类外实现Books<nameType, yearType>::Books(nameType name, yearType year) {...}//构造函数类外实现

示例:

//类模板成员函数类外实现template <class nameType,class yearType>class Books {public://成员变量nameType bookName;yearType publishYear;public://构造函数Books(nameType name,yearType year);//成员函数void showInfo();};//类外实现:template <class nameType, class yearType>//①类模板声明不能少Books<nameType, yearType>::Books(nameType name, yearType year) {//②记得加上模板参数列表this->bookName = name;this->publishYear = year;}template <class nameType, class yearType>void Books<nameType, yearType>::showInfo() {cout << this->bookName << " " << this->publishYear << endl;}int main(){//类模板成员函数类外实现Books<string, int> book("The Little Prince",1983);book.showInfo();system("pause");return 0;}

1.3.7 类模板分文件编写

把类的声明(.h)和实现(.cpp)写在一起放到.hpp文件中,一看.hpp文件就知道是类模板文件

示例:

Person.hpp中代码:

#pragma once#include <iostream>using namespace std;#include <string>template<class T1, class T2>class Person {public:Person(T1 name, T2 age);void showPerson();public:T1 m_Name;T2 m_Age;};//构造函数 类外实现template<class T1, class T2>Person<T1, T2>::Person(T1 name, T2 age) {this->m_Name = name;this->m_Age = age;}//成员函数 类外实现template<class T1, class T2>void Person<T1, T2>::showPerson() {cout << "姓名: " << this->m_Name << " 年龄:" << this->m_Age << endl;}

类模板分文件编写.cpp中代码

#include<iostream>using namespace std;#include "person.hpp"//将声明和实现写到一起,文件后缀名改为.hppint main() {Person<string, int> p("Tom", 10);p.showPerson();system("pause")return 0;}

1.3.8 类模板与友元

友元:关键字friend,一个函数或者类 访问另一个类中私有成员(private)

(补充)全局函数和成员函数的区别:见运算符重载

全局函数需要传入两个参数,二类内成员函数只需传入一个参数即可。

// 通过调用类成员函数,返回一个类的实例Test t3 = t1.add(t2);cout << t3.getA() << " " << t3.getB() << endl;// 通过调用全局函数,返回一个类的实例Test t4 = add(t1, t2);cout << t4.getA() << " " << t4.getB() << endl;

示例:

//类模板与友元//全局函数(在类外实现)template<class nameType, class ageType> class Person5;//要先声明模板类,让编译器知道类模板的存在template<class nameType,class ageType>//要先声明T,让编译器知道整两个T的存在void printPerson555(Person5<nameType, ageType>& p) {cout << "全局函数(在类外实现):" << p.name << " " << p.age << endl;}template<class nameType, class ageType>class Person5 {//全局函数(在类内实现)friend void printPerson5(Person5<nameType,ageType>& p) {//加一个关键字friend,函数printPerson5()便可访问类内的私有成员变量cout << "全局函数(在类内实现):" << p.name << " " << p.age << endl;}//全局函数(在类外实现,类内做声明)friend void printPerson555<>(Person5<nameType, ageType>& p);private://私有成员变量nameType name;ageType age;public://构造函数Person5(nameType name, ageType age) {this->name = name;this->age = age;}//类内成员函数void printPerson55() {cout << "类内成员函数:" << this->name << " " << this->age << endl;}};int main() {//类模板与友元Person5<string, int> p55("Amelie", 28);p55.printPerson55();//类内成员函数:Amelie 28Person5<string, int> p5("Sherly", 25);printPerson5(p5);//全局函数(在类内实现):Amelie 28Person5<string, int> p555("Simon", 23);printPerson555(p555);//全局函数(在类外实现):Amelie 28system("pause");return 0;}

总结:

全局函数在类内实现比较简单,直接在函数前加一个关键字friend即可,推荐使用类内实现全局函数的方法

类外实现比较复杂:

首先要在类内声明(//注意:在类内声明的时候要加个空参数列表<>)

//全局函数(在类外实现,类内做声明)friend void printPerson555<>(Person5<nameType, ageType>& p);

然后要在类外声明模板类和类型T

template<class nameType, class ageType> class Person5;//要先声明模板类,让编译器知道类模板的存在template<class nameType,class ageType>//要先声明T,让编译器知道整两个T的存在

最后才是函数的实现

void printPerson555(Person5<nameType, ageType>& p) {cout << "全局函数(在类外实现):" << p.name << " " << p.age << endl;}

1.3.9 类模板案例

预备知识1:深拷贝与浅拷贝

预备知识2:运算符重载

实现一个通用的数组类,要求如下:

可以对内置数据类型以及自定义数据类型的数据进行存储—T类型,模板将数组中的数据存储到堆区—new,用一个指针接收new的数组地址构造函数中可以传入数组的容量—构造函数(int capacity)提供对应的拷贝构造函数以及(operator=)赋值运算符重载,防止浅拷贝问题—拷贝构造函数,(operator=)赋值运算符重载,深拷贝提供尾插法和尾删法对数组中的数据进行增加和删除—?可以通过下标的方式访问数组中的元素—?可以获取数组中当前元素个数和数组的容量—容量Capacity,个数num

弹幕提出的问题:

这个等号重载的代码有问题:会导致自己=自己,然后数据被清空了

自己等于自己,不会报错,但把自己堆区的数据清空了!!!

operator=函数这里要判断一下,是不是自身给自身赋值

需要注意的几点地方:

1.拷贝构造函数&(operator=)赋值运算符重载

①拷贝构造函数中要用深拷贝;

②在赋值形参数组中的数据时,for循环的判断条件为i < arr.num;如果写成i < arr.capacity;就有点多余了;

③为什么有了拷贝构造函数还要再写赋值=运算符重载呢?因为拷贝构造函数使用时机:1.用已有对象初始化新对象。 2,传参。3,函数返回值。当=的左侧非新对象时,无法调用拷函,此时要拷贝需重载“=”运算符(见4.2.3 拷贝构造函数调用时机)

④赋值运算符重载中为什么要先判断原来堆区是否有数据,如果有先释放

因为此拷贝(赋值运算符重载)是对应在已建立成员的情况下,在构造时已经开辟空间,如果赋值号=拷贝的话要防止堆区开辟空间不同,比如我要把20容量的数组用等号拷贝到一个10容量的数组,就需要清除原有的10空间,再开辟对应的20个空间。

2.通过下标法访问数组元素:

普通数组可以直接通过下标访问数组元素,但这个MyArray[T]数组中装的是类型T,所以要重载中括号[],即T& operator[](int index){ }

3.打印数组内容:

对于内置的如int型数据,可以直接cout << arrayAddress[i] << " ";

但对于自定义数据类型如Person类,就要重载左移运算符<<了,这样才可以保证MyArray类中的打印函数同时适用于内置的和自定义的数据类型。

同样地,(赋值运算符=)

对于内置的如int型数据,可以直接this->arrayAddress[i] = arr.arrayAddress[i];

但对于自定义数据类型如Person类,如果涉及到深拷贝,就要重载赋值运算符<<见4.赋值运算符(=)重载;如果Person类的成员变量不涉及拷贝堆区的数据,即仅涉及浅拷贝,就不需要重载赋值运算符,因为编译器内置的拷贝构造函数就可以完成简单的值拷贝操作。

类模板案例_第二遍:

1.有参构造的时候this->arrayAddress = new T[capacity];//①NULL;就要在堆区把空间开辟出来;

2.拷贝构造函数MyArray(const MyArray& arr) { },没有返回值

3.通过下标访问数组元素,//可以加个判断:下标index是否>=元素个数arrayNum

4.尾插法和尾删法也加个判断:判断数组是否为空或者是满数组

5.内置数据类型测试:利用尾插法给输入输入数据arr1.weicha(i);// + 1,而不是arr1[i] = i;

6.赋值运算符重载:数组元素进行拷贝的时候,this->arrayAddress[i] = arrs[i];是错的,应该是this->arrayAddress[i] = arr.arrayAddress[i];

7.尾插法的形参前加个const,表示指向固定,值可变,这样在内置数据类型测试时arr1.weicha(i+1);这里的形参就可以写i、3、和i+1了。

8.自定义类型Person类中 加个默认构造函数???否则类模板文件中59行会报错 Person() { name = “”; age = 0; }

9.自定义类型Person类中 赋值运算符=重载的形参中加const???

Person& operator=(constPerson& p) { }

—带引用的形参都加上const吗?

10.自定义类型Person类中 左移运算符<<重载的形参也要加const,并且out要加引用&???

friend ostream& operator<<(ostream&out,constPerson& p) { }

这里的const不加也可以

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。