本专栏记录C++学习过程包括C++基础以及数据结构和算法,其中第一部分计划时间一个月,主要跟着黑马视频教程,学习路线如下,不定时更新,欢迎关注 。
当前章节处于:
---------第1阶段-C++基础入门
---------第2阶段实战-通讯录管理系统,
---------第3阶段-C++核心编程,
---------第4阶段实战-基于多态的企业职工系统
=====>第5阶段-C++提高编程
---------第6阶段实战-基于STL泛化编程的演讲比赛
---------第7阶段-C++实战项目机房预约管理系统
文章目录
- 一、概念
- 二、函数模板
-
- [2.1 基本语法](#2.1 基本语法)
- [2.2 注意事项](#2.2 注意事项)
- [2.3 普通函数和模板函数的区别](#2.3 普通函数和模板函数的区别)
- [2.4 普通函数和函数模板调用规则](#2.4 普通函数和函数模板调用规则)
- [2.5 模板的局限性](#2.5 模板的局限性)
- 三、类模板
-
- [3.1 基本语法](#3.1 基本语法)
- [3.2 类模板中成员函数创建时机](#3.2 类模板中成员函数创建时机)
- [3.3 类模板对象做函数参数](#3.3 类模板对象做函数参数)
- [3.4 类模板与继承](#3.4 类模板与继承)
- [3.5 类模板成员函数类外实现](#3.5 类模板成员函数类外实现)
- [3.6 类模板分文件编写](#3.6 类模板分文件编写)
- [3.7 类模板和友元](#3.7 类模板和友元)
一、概念
模板就是建立通用的模具,大大提高复用性。模板不可以直接使用,只是一个框架,并且模板的通用并不是万能的。使用模板的目的是提高复用性,将类型参数化。
二、函数模板
2.1 基本语法
template
函数声明或定义
- template:声明创建模板
- typename:表明其后面的符号是一种数据类型,可以用class代替
- T:通用的数据类型,名称可以替换,通常为大写字母,可以定义多个
cpp
#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;
}
cpp
a=20
b=10
c=d
d=c
请按任意键继续. . .
2.2 注意事项
- 自动类型推导,必须推导出一致的数据类型,才可以使用
- 模板必须要确定出T的数据类型,才可以使用
第一个好理解,比如上面的Myswap,如果传入的两个实参类型不一致时,则会导致推导出来的T的类型不一致,则无法使用。下面对于第二点进行代码讲解:
cpp
#include <iostream>
using namespace std;
// 定义模板
template <class T>
void func() {
cout << "调用函数模板" << endl;
}
void test() {
// func(); // 错误
func<int>(); // 正确
}
int main() {
test();
system("pause");
return 0;
}
cpp
调用函数模板
请按任意键继续. . .
实战案例
- 利用函数模板封装一个排序的函数,可以对不同数据类型数组进行排序
- 排序规则从大到小,排序算法为选择排序
- 分别利用char数组和int数组进行排序
cpp
#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;
}
cpp
9 8 7 6 5 4 3 2 1 0
g f e d c b a
请按任意键继续. . .
2.3 普通函数和模板函数的区别
- 普通函数调用时可以发生自动类型转化(隐式类型转化)
- 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
- 如果利用显示指定类型的方式,可以发生隐式类型转化
cpp
#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;
}
cpp
调用的是函数模板!
30
调用的是普通函数!
109
调用的是函数模板!
109
请按任意键继续. . .
2.4 普通函数和函数模板调用规则
- 如果函数模板和普通函数都可以实现,优先调用普通函数
- 可以通过空模板参数列表来强制调用函数模板
- 函数模板也可以发生重载
- 如果函数模板可以更好的匹配,优先调用函数模板
下面通过具体代码进行逐个讲解
cpp
#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;
}
cpp
调用的是普通函数
调用的是模板函数
调用重载的模板函数
调用的是模板函数
请按任意键继续. . .
总结:如果提供了函数模板,最好就不要再提供对应的普通函数,否则容易出现二义性。
2.5 模板的局限性
如果T的数据类型传入的是像Person这样的自定义数据类型,也无法正常进行比较运算,C++为了解决这种问题,提供模板的重载,为这些特定类型提供具体化模板。
cpp
#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;
}
cpp
1
请按任意键继续. . .
利用具体化的模板,可以解决自定义类型的通用化,学习模板并不是为了写模板,而是再STL中能够运用系统提供的模板。
三、类模板
类似于函数模板,类有也对应的类模板,类模板的作用是建立一个通用类,类中的成员,数据类型可以不具体制定,用一个虚拟的类型来代表,语法为
template<typename T>
类
- template --- 声明创建模板
- typename --- 表面其后面的符号是一种数据类型,可以用class代替
- T --- 通用的数据类型,名称可以替换,通常为大写字母
3.1 基本语法
cpp
#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;
}
cpp
姓名:孙悟空 年龄:999
姓名:猪八戒 年龄:888
请按任意键继续. . .
3.2 类模板中成员函数创建时机
类模板中成员函数和普通类中成员函数创建时机是有区别的:
- 普通类中的成员函数一开始就可以创建
- 类模板中的成员函数再调用时才创建
cpp
#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;
}
cpp
Person1 show
请按任意键继续. . .
3.3 类模板对象做函数参数
类模板实例化出的对象,向函数传参的方式
一共有三种传入方式:
- 指定传入的形式:直接显示对象的数据类型,这是用的最多的一种方式
- 参数模板化:将对象中的参数变为模板进行传递
- 整个类模板化:将这个对象类型,模板化进行传递
下面用代码进行讲解:
cpp
#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;
}
cpp
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的类型,子类也需变成类模板
cpp
#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;
}
cpp
char
请按任意键继续. . .
3.5 类模板成员函数类外实现
cpp
#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;
}
cpp
name: 张三 age: 13
请按任意键继续. . .
3.6 类模板分文件编写
由于类模板中成员函数创建时机是在调用阶段,导致分文件编写时连接不到,有两种解决方案:
- 直接包含cpp源文件
- 将声明和实现写到同一个文件中,并更改后缀名为.hpp (更常用)
Person.hpp
cpp
#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
cpp
#include <iostream>
using namespace std;
#include "Person.hpp"
int main() {
Person<string, int> p("张三", 13);
p.showPerson();
system("pause");
return 0;
}
cpp
name: 张三 age: 13
请按任意键继续. . .
3.7 类模板和友元
cpp
#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;
}
cpp
姓名:Tom 年龄:12
请按任意键继续. . .
实战案例
实现一个通用的数组类
- 可以对内置数据类型以及自定义数据类型的数据进行存储
- 将数组中的数据存储到堆区
- 构造函数中可以川入数组的容量
- 提供对应的拷贝构造函数以及operator=防止浅拷贝问题
- 提供尾插法和尾删法对数组中的数据进行增加和删除
- 可以通过下标的方式访问数组中的元素
- 可以获取数组中当前元素个数和数组的容量
main.cpp
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
cpp
#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() {
// 判断是否为0
if (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; // 大小
};
cpp
调用有参构造函数
0 1 2 3 4 5 6 7 8 9
调用析构函数
调用有参构造函数
姓名:张三 年龄:12
姓名:李四 年龄:21
调用析构函数
调用析构函数
调用析构函数
请按任意键继续. . .