本专栏记录C++学习过程包括C++基础以及数据结构和算法,其中第一部分计划时间一个月,主要跟着黑马视频教程,学习路线如下,不定时更新,欢迎关注。
当前章节处于:
---------第1阶段-C++基础入门
---------第2阶段实战-通讯录管理系统,
---------第3阶段-C++核心编程,
---------第4阶段实战-基于多态的企业职工系统
=====>第5阶段-C++提高编程
---------第6阶段实战-基于STL泛化编程的演讲比赛
---------第7阶段-C++实战项目机房预约管理系统
文章目录
- 一、概念
- 二、函数模板
- 2.1 基本语法
- 2.2 注意事项
- 2.3 普通函数和模板函数的区别
- 2.4 普通函数和函数模板调用规则
- 2.5 模板的局限性
- 三、类模板
- 3.1 基本语法
- 3.2 类模板中成员函数创建时机
- 3.3 类模板对象做函数参数
- 3.4 类模板与继承
- 3.5 类模板成员函数类外实现
- 3.6 类模板分文件编写
- 3.7 类模板和友元
一、概念
模板就是建立通用的模具,大大提高复用性。模板不可以直接使用,只是一个框架,并且模板的通用并不是万能的。使用模板的目的是提高复用性,将类型参数化。
二、函数模板
2.1 基本语法
template
函数声明或定义
- template:声明创建模板
- typename:表明其后面的符号是一种数据类型,可以用class代替
- T:通用的数据类型,名称可以替换,通常为大写字母,可以定义多个
#include <iostream>
using namespace std;// 定义一个交换的函数模板
template <typename T>
void Myswap(T& a, T& b) {T temp = a;a = b;b = temp;
}void test() {int a = 10;int b = 20;char c = 'c';char d = 'd';// 1. 自动推导型Myswap(a, b);cout << "a=" << a << endl;cout << "b=" << b << endl;// 2. 显示推导型Myswap<char>(c, d);cout << "c=" << c << endl;cout << "d=" << d << endl;}int main() {test();system("pause");return 0;}
a=20
b=10
c=d
d=c
请按任意键继续. . .
2.2 注意事项
- 自动类型推导,必须推导出一致的数据类型,才可以使用
- 模板必须要确定出T的数据类型,才可以使用
第一个好理解,比如上面的Myswap,如果传入的两个实参类型不一致时,则会导致推导出来的T的类型不一致,则无法使用。下面对于第二点进行代码讲解:
#include <iostream>
using namespace std;// 定义模板
template <class T>
void func() {cout << "调用函数模板" << endl;
}void test() {// func(); // 错误func<int>(); // 正确
}int main() {test();system("pause");return 0;}
调用函数模板
请按任意键继续. . .
实战案例
- 利用函数模板封装一个排序的函数,可以对不同数据类型数组进行排序
- 排序规则从大到小,排序算法为选择排序
- 分别利用char数组和int数组进行排序
#include <iostream>
using namespace std;// 排序函数模板
template<class T>
void func(T arr[],int length) {// 选择排序for (int i = 0; i < length; i++) {int max = i; // 认为当前的是最大值for (int j = i+1; j < length; j++) {if (arr[max] < arr[j]) {max = j;}}// 调换两个值T temp = arr[i];arr[i] = arr[max];arr[max] = temp;}
}// 打印函数模板
template <class T>
void printArr(T arr[],int length) {for (int i = 0; i < length; i++) {cout << arr[i] << " ";}cout << endl;
}// 测试整型
void test01() {int arr[10] = { 1,4,3,2,6,7,5,9,8,0 };func(arr, 10);printArr(arr, 10);
}
void test02() {char arr[] = "acbdfeg";int length = sizeof(arr) / sizeof(arr[0]);func(arr,length);printArr(arr, length);
}
int main() {test01();test02();system("pause");return 0;}
9 8 7 6 5 4 3 2 1 0
g f e d c b a
请按任意键继续. . .
2.3 普通函数和模板函数的区别
- 普通函数调用时可以发生自动类型转化(隐式类型转化)
- 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
- 如果利用显示指定类型的方式,可以发生隐式类型转化
#include <iostream>
using namespace std;// 定义一个函数模板
template<class T>
T func1(T a, T b) {cout << "调用的是函数模板!" << endl;return a + b;
}// 定义一个普通函数
int func2(int a,int b) {cout << "调用的是普通函数!" << endl;return a + b;
}// 测试案例
void test() {int a = 10;int b = 20;char c = 'c';cout << func1(a, b) << endl;cout << func2(a, c) << endl;//cout << func1(a, c) << endl;// 报错cout << func1<int>(a, c) << endl;// 不报错
}
int main() {test();system("pause");return 0;}
调用的是函数模板!
30
调用的是普通函数!
109
调用的是函数模板!
109
请按任意键继续. . .
2.4 普通函数和函数模板调用规则
- 如果函数模板和普通函数都可以实现,优先调用普通函数
- 可以通过空模板参数列表来强制调用函数模板
- 函数模板也可以发生重载
- 如果函数模板可以更好的匹配,优先调用函数模板
下面通过具体代码进行逐个讲解
#include <iostream>
using namespace std;
// 定义普通函数
void MyPrint(int a,int b) {cout << "调用的是普通函数" << endl;
}// 定义模板函数
template<class T>
void MyPrint(T a, T b) {cout << "调用的是模板函数" << endl;
}
// 重载模板函数
template<typename T>
void MyPrint(T a, T b, T c)
{cout << "调用重载的模板函数" << endl;
}int main() {// 1. 如果函数模板和普通函数都可以实现,优先调用普通函数int a = 10; int b = 20;MyPrint(a, b);// 2.可以通过空模板参数列表来强制调用函数模板MyPrint<>(a, b);// 3.函数模板也可以发生重载MyPrint(a, b, 30);// 4.如果函数模板可以产生更好的匹配,优先调用函数模板char c1 = 'a';char c2 = 'b';MyPrint(c1, c2);system("pause");return 0;}
调用的是普通函数
调用的是模板函数
调用重载的模板函数
调用的是模板函数
请按任意键继续. . .
总结:如果提供了函数模板,最好就不要再提供对应的普通函数,否则容易出现二义性。
2.5 模板的局限性
如果T的数据类型传入的是像Person这样的自定义数据类型,也无法正常进行比较运算,C++为了解决这种问题,提供模板的重载,为这些特定类型提供具体化模板。
#include <iostream>
using namespace std;
class Person {
public:Person(string name,int age) {m_Name = name;m_Age = age;}string m_Name;int m_Age;
};
// 普通函数模板
template<class T>
bool myCompare(T& a, T& b) {return a == b;
}
// 具体化 优先于常规模板
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 test() {Person p1("Tom", 10);Person p2("Tom", 10);cout << myCompare(p1, p2) << endl;
}
int main() {test();system("pause");return 0;}
1
请按任意键继续. . .
利用具体化的模板,可以解决自定义类型的通用化,学习模板并不是为了写模板,而是再STL中能够运用系统提供的模板。
三、类模板
类似于函数模板,类有也对应的类模板,类模板的作用是建立一个通用类,类中的成员,数据类型可以不具体制定,用一个虚拟的类型来代表,语法为
template<typename T>
类
- template — 声明创建模板
- typename — 表面其后面的符号是一种数据类型,可以用class代替
- T — 通用的数据类型,名称可以替换,通常为大写字母
3.1 基本语法
#include <iostream>
using namespace std;// 定义一个交换的函数模板
template <typename T>
void Myswap(T& a, T& b) {T temp = a;a = b;b = temp;
}void test() {int a = 10;int b = 20;char c = 'c';char d = 'd';// 1. 自动推导型Myswap(a, b);cout << "a=" << a << endl;cout << "b=" << b << endl;// 2. 显示推导型Myswap<char>(c, d);cout << "c=" << c << endl;cout << "d=" << d << endl;}int main() {test();system("pause");return 0;}
姓名:孙悟空 年龄:999
姓名:猪八戒 年龄:888
请按任意键继续. . .
3.2 类模板中成员函数创建时机
类模板中成员函数和普通类中成员函数创建时机是有区别的:
- 普通类中的成员函数一开始就可以创建
- 类模板中的成员函数再调用时才创建
#include <iostream>
using namespace std;
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(); }};
void test01()
{MyClass<Person1> m;m.fun1();//m.fun2();//编译会出错,说明函数调用才会去创建成员函数
}
int main() {test01();system("pause");return 0;}
Person1 show
请按任意键继续. . .
3.3 类模板对象做函数参数
类模板实例化出的对象,向函数传参的方式
一共有三种传入方式:
- 指定传入的形式:直接显示对象的数据类型,这是用的最多的一种方式
- 参数模板化:将对象中的参数变为模板进行传递
- 整个类模板化:将这个对象类型,模板化进行传递
下面用代码进行讲解:
#include <iostream>
using namespace std;
// 创建一个类模板
template<class NameType, class AgeType = int> // 默认参数为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)
{p.showPerson();
}
void test01()
{Person <string, int >p("孙悟空", 100);printPerson1(p);
}//2、参数模板化
template <class T1, class T2>
void printPerson2(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);printPerson2(p);
}//3、整个类模板化
template<class T>
void printPerson3(T& p)
{cout << "T的类型为: " << typeid(T).name() << endl;p.showPerson();}
void test03()
{Person <string, int >p("唐僧", 30);printPerson3(p);
}int main() {test01();test02();test03();system("pause");return 0;
}
int main() {system("pause");return 0;}
name: 孙悟空 age: 100
name: 猪八戒 age: 90
T1的类型为: class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >
T2的类型为: int
T的类型为: class Person<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,int>
name: 唐僧 age: 30
请按任意键继续. . .
3.4 类模板与继承
当类模板碰到继承时,需要注意一下几点:
- 当子类继承的父类是一个类模板时,子类在声明的时候,要制定出父类中T的类型
- 如果不指定,编译器无法给子类分配内存
- 如果想灵活指定出父类中T的类型,子类也需变成类模板
#include <iostream>
// 父类类模板
using namespace std;
template<class T>
class Base
{T m;
};// 子类继承父类
// class Son :public Base {}; // 错误,必须要确定父类中的T
class Son :public Base<int> {};
void test01()
{Son c;// 实例化对象
}
template<class T2>
class Son2 :public Base<T2>
{
public:Son2(){cout << typeid(T2).name() << endl;}
};void test02()
{Son2<char> child1;
}
int main() {test01();test02();system("pause");return 0;}
char
请按任意键继续. . .
3.5 类模板成员函数类外实现
#include <iostream>
using namespace std;template<class NameType, class AgeType = int> // 默认参数为int
class Person
{
public:Person(NameType name, AgeType age);void showPerson();
public:NameType mName;AgeType mAge;
};
// 构造函数 类外实现
template<class NameType, class AgeType>
Person<NameType, AgeType>::Person(NameType name, AgeType age)
{this->mName = name;this->mAge = age;
}
// 成员函数类外实现
template<class NameType, class AgeType>
void Person<NameType, AgeType>::showPerson()
{cout << "name: " << this->mName << " age: " << this->mAge << endl;
}int main() {Person<string, int> p("张三", 13);p.showPerson();system("pause");return 0;}
name: 张三 age: 13
请按任意键继续. . .
3.6 类模板分文件编写
由于类模板中成员函数创建时机是在调用阶段,导致分文件编写时连接不到,有两种解决方案:
- 直接包含cpp源文件
- 将声明和实现写到同一个文件中,并更改后缀名为.hpp (更常用)
Person.hpp
#pragma once
#include <iostream>
using namespace std;template<class NameType, class AgeType = int> // 默认参数为int
class Person
{
public:Person(NameType name, AgeType age);void showPerson();
public:NameType mName;AgeType mAge;
};
// 构造函数 类外实现
template<class NameType, class AgeType>
Person<NameType, AgeType>::Person(NameType name, AgeType age)
{this->mName = name;this->mAge = age;
}
// 成员函数类外实现
template<class NameType, class AgeType>
void Person<NameType, AgeType>::showPerson()
{cout << "name: " << this->mName << " age: " << this->mAge << endl;
}
类模板分文件编写.cpp
#include <iostream>
using namespace std;#include "Person.hpp"int main() {Person<string, int> p("张三", 13);p.showPerson();system("pause");return 0;}
name: 张三 age: 13
请按任意键继续. . .
3.7 类模板和友元
#include <iostream>
using namespace std;
template <class T1,class T2>
class Person;// 2. 全局函数类外实现
template <class T1, class T2>
void showPerson(Person<T1, T2> p) {cout << "姓名:" << p.m_Name << " 年龄:" << p.m_Age << endl;
}template <class T1, class T2>
class Person {// 1. 全局函数类内实现// 加上friend变为全局函数,如果不加的话就是成员函数//friend void showPerson(Person<T1,T2> p) {// cout << "姓名:" << p.m_Name << " 年龄:" << p.m_Age << endl;//}// 如果全局函数是类外实现,需要让编译器提前知道这个函数存在friend void showPerson<>(Person<T1, T2> p);
public:Person(T1 name, T2 age) {this->m_Name = name;this->m_Age = age;}
private:T1 m_Name;T2 m_Age;
};int main() {Person<string, int> p("Tom", 12);showPerson(p);system("pause");return 0;}
姓名:Tom 年龄:12
请按任意键继续. . .
实战案例
实现一个通用的数组类
- 可以对内置数据类型以及自定义数据类型的数据进行存储
- 将数组中的数据存储到堆区
- 构造函数中可以川入数组的容量
- 提供对应的拷贝构造函数以及operator=防止浅拷贝问题
- 提供尾插法和尾删法对数组中的数据进行增加和删除
- 可以通过下标的方式访问数组中的元素
- 可以获取数组中当前元素个数和数组的容量
main.cpp
#include <iostream>
using namespace std;
#include "MyArray.hpp"
class Person {
public:Person() {}Person(string name, int age) {m_name = name;m_age = age;}string getName() {return m_name;}int getAge() {return m_age;}
private:string m_name;int m_age=0;
};void showPerson(Myarray<Person> array,int num) {for (int i = 0; i < num; i++) {cout << "姓名:" << array[i].getName() << " 年龄:"<<array[i].getAge()<<endl;}
}
void showInt(Myarray<int> arr, int num) {for (int i = 0; i < num; i++) {cout << arr[i] << " ";}cout << endl;
}void test() {Myarray<int> arr(10);for (int i = 0; i < 10; i++) {arr.insert_Back(i);}showInt(arr, 10);//Myarray<int> arr2(arr);//Myarray<int> arr3(20);//arr3 = arr;//cout <<"arr3的容量为"<< arr3.get_Mcap() << endl;Myarray<Person> PersonArr(2);Person p1("张三", 12);Person p2("李四", 21);PersonArr.insert_Back(p1);PersonArr.insert_Back(p2);showPerson(PersonArr,2);
}
int main() {test();system("pause");return 0;}
MyArray.hpp
#pragma once
#include <iostream>
using namespace std;template <class T>
class Myarray {
public:// 有参构造Myarray(int cap) {cout << "调用有参构造函数" << endl;//cout << "hello" << endl;this->m_cap = cap;this->pArray = new T[this->m_cap]; // 在堆区开辟this->size = 0;}// 拷贝构造Myarray(Myarray& arr) {//cout << "调用拷贝构造函数" << endl;this->size = arr.size;this->m_cap = arr.m_cap;this->pArray = new T[arr.m_cap];// 将arr中的数据拿过来for (int i = 0; i < arr.size; i++) {this->pArray[i] = arr.pArray[i];}}// 析构函数~Myarray() {if (this->pArray!= NULL) {cout << "调用析构函数" << endl;this->m_cap = 0;this->size = 0;// pArray是个数组 删除时要注意格式delete[] pArray;pArray = NULL;//cout << "删除完成" << endl;}}// 重载=操作运算符Myarray& operator=(const Myarray& arr) {//cout << "调用operator=函数" << endl;// 先判断原来栈区是否有数据if (this->pArray != NULL) {delete[] pArray;pArray = NULL;this->m_cap = 0;this->size = 0;}this->m_cap = arr.m_cap;this->size = arr.size;this->pArray = new T[arr.m_cap];return *this;}// 重载[]操作运算符T& operator[](int index) {return pArray[index];}// 获取数组容量int get_Mcap() {return this->m_cap;}// 获取数组大小int get_Size() {return this->size;}// 获取// 尾插法void insert_Back(T value) {// 判断还有无空间if (this->size == this->m_cap) {cout << "达到插入上限!" << endl;return ;}this->pArray[this->size] = value;this->size++;//return *this;}// 尾删法void pop_Back() {// 判断是否为0if (this->size == 0) {cout << "已达删除上限!" << endl;return;}this->size--;}// 通过下标方式访问元素T& get_index(int index) {if (index<0 || index>this.size) {cout << "索引有无!" << endl;return;}return this->pArray[index];}
private:T* pArray; // 头指针int m_cap; // 容量int size; // 大小
};
调用有参构造函数
0 1 2 3 4 5 6 7 8 9
调用析构函数
调用有参构造函数
姓名:张三 年龄:12
姓名:李四 年龄:21
调用析构函数
调用析构函数
调用析构函数
请按任意键继续. . .