黑马C++笔记——模板(CPP)
2023-09-14 09:14:58 时间
模板的概念
模板就是建立通用的模具,这样可以极大地提高复用性
特点:
- 模板不可以直接使用,他只是一个架子
- 模板的通用并不是万能的,他依然有局限性
1.函数模板
- C++的另一种编程思想被称为泛型编程,其主要利用的技术就是模板
- C++提供两种模板机制 函数模板 和 类模板
1.函数模板的语法
作用:可以建立一个通用的函数,其函数返回值类型 和 形参类型可以不具体指定,用一个虚拟的类型 T 来代表
template<typename T>
函数声明或者定义
template<class T>
函数声明或者定义
//上面两者是一样的
举例:实现一个交换两个元素的函数模板
#include<iostream>
using namespace std;
template<typename T>
void mySwap(T& a, T& b) {
T temp = a;
a = b;
b = temp;
}
int main() {
int a = 20;
int b = 10;
//自动类型推导
mySwap(a, b);
cout << "a = " << a << endl; //a = 10
cout << "b = " << b << endl; //b = 20
double c = 1.1;
double d = 2.2;
//显式指定类型
mySwap<double>(c, d);
cout << "c = " << c << endl; // c = 2.2
cout << "d = " << d << endl; // d = 1.1
system("pause");
return 0;
}
2.函数模板注意事项
一般使用函数模板有两种方式:
- 自动类型推导
- 显式指定类型
#include<iostream>
using namespace std;
template<typename T>
void myswap(T& a, T& b) {
T temp = a;
a = b;
b = temp;
}
template<typename T>
void fun() {
cout << "感觉不如原神...画质" << endl;
}
int main() {
int a = 10;
int b = 20;
char c = 'c';
//myswap(a, b); 成功执行
//myswap(a, c); 报错。自动类型推导 这里要求两个参数的类型必须是一致的
//fun(); 报错。这里只是普通函数调用
fun<int>(); //成功执行。这里是显式指定类型
cout << "a = " << a << endl;
cout << "b = " << b << endl;
system("pause");
return 0;
}
案例:用模板实现对不同内置类型数组的排序(以下代码只测试了char数组 和 int数组)
#include<iostream>
using namespace std;
template<typename T>
void myswap(T& a, T& b) {
T temp = a;
a = b;
b = temp;
}
template<typename T>
void mysort(T arr[], int len) {
for (int i = 0; i < len; i++) {
int pos = i;
for (int j = i; j < len; j++) {
if (arr[j] < arr[pos]) pos = j;
}
if (i != pos) myswap(arr[i], arr[pos]);
}
}
template<typename T>
void printArray(T arr[], int len) {
for (int i = 0; i < len; i++) {
cout << arr[i] << " ";
}
cout << endl;
}
void test01() {
char arr[] = "fghbnabcdffaa";
int len = sizeof(arr) / sizeof(char);
mysort(arr, len);
printArray(arr, len); // a a a b b c d f f f g h n
}
void test02() {
int arr[] = { 2,12,41,3,6,7,90,11,1,2,3,4,5 };
int len = sizeof(arr) / sizeof(int);
mysort(arr, len);
printArray(arr, len); // 1 2 2 3 3 4 5 6 7 11 12 41 90
}
int main() {
test01();
//test02();
system("pause");
return 0;
}
3.普通函数与函数模板的区别
- 普通函数调用时可以发生自动类型转换(隐式类型转换)
- 函数模板调用时,如果使用自动类型推导,则不会发生自动类型转换
- 如果使用显式指定类型的方式,可以发生自动类型转换
#include<iostream>
using namespace std;
template<typename T>
T myadd(T a, T b) {
return a + b;
}
int add(int a, int b) {
return a + b;
}
int main() {
int a = 10;
int b = 20;
char c = 'c';
//1.普通函数调用时是可以发生自动类型转换的
cout << add(a, b) << endl; //30
cout << add(a, c) << endl; //答案是 c 的ascii码值 + a = 109
//2.模板函数调用时,如果利用自动类型推导,是不会发生隐式类型转换的
cout << myadd(a, b) << endl; //30
//cout << myadd(a, c) << endl; 报错
//3.如果使用显式指定类型的方式,可以发生隐式类型转换
cout << myadd<int>(a,c) << endl; //答案是 c 的ascii码值 + a = 109
system("pause");
return 0;
}
4.普通函数与函数模板的调用规则
- 如果函数模板 和 普通函数都能匹配,优先调用普通函数
- 可以通过添加空模板参数列表强制使用函数模板
- 函数模板也可以发生重载
- 如果函数模板可以更好的匹配,优先使用函数模板
#include<iostream>
using namespace std;
void myadd(int a, int b) {
cout << "普通函数调用" << endl;
}
template<typename T>
void myadd(T a, T b) {
cout << "函数模板调用" << endl;
}
template<typename T>
void myadd(T a, T b,T c) {
cout << "重载函数模板调用" << endl;
}
int main() {
//1.如果普通函数和函数模板都能实现,优先使用普通函数
int a = 10;
int b = 20;
myadd(a, b);//普通函数调用
//2.可以通过空模板参数列表来强制调用函数模板
myadd<>(a, b);//函数模板调用
//3.函数模板也可以发生重载
myadd(a, b, 100);//函数模板重载
//4.如果函数模板可以更好地匹配,优先调用函数模板
char c = 'c';
char d = 'd';
myadd(c, d);//函数模板调用。因为调用普通函数会发生自动类型转换(char -> int),所以编译器选择了函数模板
system("pause");
return 0;
}
5.函数模板的局限性
- 针对复杂的自定义类型,函数模板就很难处理
解决方案:
可以利用具体化的模板,来适用自定义类型(不推荐)
#include<iostream>
#include<string>
using namespace std;
class Person {
public:
int age;
string name;
Person(int age, string name) {
this->age = age;
this->name = name;
}
};
//1
template<typename T>
bool mycmp(T& a, T& b) {
if (a == b) return true;
else return false;
}
//2
//利用具体化的模板解决自定义类型的比较
template<>bool mycmp(Person& p1, Person& p2) {
if (p1.age == p2.age && p1.name == p2.name) return true;
else return false;
}
int main() {
Person p1(20, "wurusai");
Person p2(20, "wurusai");
//在调用mycmp(p1, p2)时,不会调用1,只会调用2这个函数模板
if (mycmp(p1, p2)) cout << "p1 == p2" << endl; //输出 p1 == p2
else cout << "p1 != p2" << endl;
system("pause");
return 0;
}
2.类模板
- 建立一个通用的类,类中成员的数据类型可以不具体指定,先用一个虚拟的类型 T 指定。
示例:
#include<iostream>
using namespace std;
template<typename NameType,typename AgeType>
class Person {
public:
NameType name;
AgeType age;
Person(NameType name, AgeType age) {
this->name = name;
this->age = age;
}
void show() {
cout << "name : " << this->name << " age : " << this->age << endl;
}
};
void test() {
Person<string, int> p1("wurusai", 20);
p1.show();
}
int main() {
test();
system("pause");
return 0;
}
1.类模板与函数模板的区别
- 类模板没有自动类型推导
- 类模板在参数列表中可以有默认参数
#include<iostream>
using namespace std;
template<typename NameType,typename AgeType>
class Person {
public:
NameType name;
AgeType age;
Person(NameType name, AgeType age) {
this->name = name;
this->age = age;
}
void show() {
cout << "name : " << this->name << " age : " << this->age << endl;
}
};
void test01() {
//1.类模板没有自动类型推导
Person p1("wurusai", 20);//报错
}
int main() {
test01();
system("pause");
return 0;
}
#include<iostream>
using namespace std;
template<typename NameType = string,typename AgeType = int>
class Person {
public:
NameType name;
AgeType age;
Person(NameType name, AgeType age) {
this->name = name;
this->age = age;
}
void show() {
cout << "name : " << this->name << " age : " << this->age << endl;
}
};
void test01() {
//1.类模板没有自动类型推导
//Person p1("wurusai", 20);//错误
//2.类模板在参数列表中可以有默认参数
Person<>p2("wurusai", 20);//运行成功
p2.show();
}
int main() {
test01();
system("pause");
return 0;
}
2.类模板中成员函数的创建时机
- 普通类的成员函数在一开始的时候就被创建
- 类模板中的成员函数在调用时才被创建(类似懒加载)
#include<iostream>
using namespace std;
class Person1 {
public:
void showPerson1() {
cout << "show Person1" << endl;
}
};
class Person2 {
public:
void showPerson2() {
cout << "show Person2" << endl;
}
};
template<typename T>
class Person {
public:
T obj;
//obj 要么是Person1 要么是Persona2
//不可能 既能调用showPerson1 又能调用showPerson2
//但是编译没有报错,说明这两个成员函数都还没有被创建
void fun1() {
obj.showPerson1();
}
void fun2() {
obj.showPerson2();
}
};
void test() {
//1.类模板中的成员函数在调用的时候才会创建(类似于懒加载)
Person<Person1> p;
}
int main() {
test();
system("pause");
return 0;
}
#include<iostream>
using namespace std;
class Person1 {
public:
void showPerson1() {
cout << "show Person1" << endl;
}
};
class Person2 {
public:
void showPerson2() {
cout << "show Person2" << endl;
}
};
template<typename T>
class Person {
public:
T obj;
void fun1() {
obj.showPerson1();
}
void fun2() {
obj.showPerson2();
}
};
void test() {
//1.类模板中的成员函数在调用的时候才会创建(类似于懒加载)
Person<Person1> p;
p.fun1();
//p.fun2(); 报错
Person<Person2> q;
//q.fun1(); 报错
q.fun2();
}
int main() {
test();
system("pause");
return 0;
}
3.类模板对象作为函数参数
- 传入指定的类型(直接显示对象的数据类型)推荐使用
- 参数模板化(将对象中参数变为模板进行传递)
- 将整个类模板化(将这个对象类型模板化进行传递)
#include<iostream>
using namespace std;
template<typename T1,typename T2>
class Person {
public:
T1 name;
T2 age;
Person(T1 name, T2 age) {
this->name = name;
this->age = age;
}
void showPerson() {
cout << "name : " << this->name << " age : " << this->age << endl;
}
};
//1.直接指定类对象的数据类型 这一种方式最常用
void fun1(Person<string, int> &p) {
p.showPerson();
}
void test01() {
Person<string, int> p("wurusai", 20);
fun1(p);
}
//2.将参数模板化
template<typename T1,typename T2>
void fun2(Person<T1,T2>& p) {
p.showPerson();
cout << "T1 的类型为 : " << typeid(T1).name() << endl;
cout << "T2 的类型为 : " << typeid(T2).name() << endl;
}
void test02() {
Person<string, int> p("wurusai", 20);
fun2(p);
}
//3.直接将整个类模板化
template<typename T>
void fun3(T &p) {
p.showPerson();
cout << "T 的类型为 : " << typeid(T).name() << endl;
}
void test03() {
Person<string, int> p("wurusai", 20);
fun3(p);
}
int main() {
//test01();
//test02();
test03();
system("pause");
return 0;
}
4.类模板与继承
- 当子类继承的父类是一个类模板时,子类在声明的时候,需要指定出父类T的类型,否则编译器无法给子类分配内存,报错
- 如果想灵活指定出父类中的 T 类型,子类也需要变为类模板
#include<iostream>
using namespace std;
template<typename T>
class Base {
public:
T m;
};
//class Son1 : public Base { 父类是类模板时,子类必须指定 T 的类型,否则编译器无法给子类分配内存,报错
class Son1 : public Base<int>{
};
//2.如果想灵活的指定父类中 T 的类型,子类也需要变为类模板
template<typename T1,typename T2>
class Son2 : public Base<T2> {
public:
T1 b;
Son2() {
cout << "T1 的类型为 : " << typeid(T1).name() << endl;
cout << "T2 的类型为 : " << typeid(T2).name() << endl;
}
};
void test() {
Son2<string, int> s2;
}
int main() {
test();
system("pause");
return 0;
}
5.类模板成员函数的类外实现
#include<iostream>
using namespace std;
template<typename T1,typename T2>
class Person {
public:
T1 name;
T2 age;
Person(T1 name, T2 age);
void showPerson();
};
//类模板成员函数的类外实现
template<typename T1,typename T2>
Person<T1, T2>::Person(T1 name, T2 age) {
this->name = name;
this->age = age;
}
template<typename T1, typename T2>
void Person<T1, T2>::showPerson() {
cout << "name : " << this->name << " age: " << this->age << endl;
}
void test() {
Person<string, int> p("wurusai", 20);
p.showPerson();
}
int main() {
test();
system("pause");
return 0;
}
6.类模板分文件编写
问题:
类模板中成员函数被创建时是在调用阶段,这可能会导致分文件编写后链接不到
解决方案:
- 直接include .cpp源文件 不推荐
- 将模板的声明和实现写到同一个文件中,文件的后缀名为.hpp。(hpp是约定的规范命名) 推荐使用
person.h
#pragma once
#include<iostream>
using namespace std;
template<typename T1, typename T2>
class Person {
public:
T1 name;
T2 age;
Person(T1 name, T2 age);
void showPerson();
};
person.cpp
#include"person.h"
using namespace std;
template<typename T1,typename T2>
Person<T1, T2>::Person(T1 name, T2 age) {
this->name = name;
this->age = age;
}
template<typename T1, typename T2>
void Person<T1, T2>::showPerson() {
cout << "name : " << this->name << " age: " << this->age << endl;
}
主函数
#include"person.h"
using namespace std;
void test() {
//运行会报错
//类模板中成员函数是在调用时才会创建。分文件编写会导致链接时出问题
Person<string, int> p("wurusai", 20);
p.showPerson();
}
int main() {
test();
system("pause");
return 0;
}
主函数1
#include"person.cpp"
using namespace std;
void test() {
//引入.cpp源文件 运行成功
Person<string, int> p("wurusai", 20);
p.showPerson();
}
int main() {
test();
system("pause");
return 0;
}
person.hpp
#pragma once
#include<iostream>
using namespace std;
template<typename T1,typename T2>
class Person {
public:
T1 name;
T2 age;
Person(T1 name, T2 age);
void showPerson();
};
template<typename T1, typename T2>
Person<T1, T2>::Person(T1 name, T2 age) {
this->name = name;
this->age = age;
}
template<typename T1, typename T2>
void Person<T1, T2>::showPerson() {
cout << "name : " << this->name << " age: " << this->age << endl;
}
主函数3(最终版)
#include"person.hpp"
using namespace std;
void test() {
//类模板中成员函数是在调用时才会创建。分文件编写会导致链接时出问题
/*
* 解决方案:
* 1.直接包含 .cpp文件(不推荐)
* 2.将模板声明 和 实现都写到头文件中,并更改为.hpp文件(这是约定俗成的规范)
*/
Person<string, int> p("wurusai", 20);
p.showPerson();
}
int main() {
test();
system("pause");
return 0;
}
7.类模板与友元
- 全局函数类内实现——直接在类内部声明实现友元即可
- 全局函数类外实现——需要提前让编译器知道全局函数的存在(比较麻烦)
#include<iostream>
using namespace std;
//提前声明
template<typename T1,typename T2>
class Person;
template<typename T1, typename T2>
void showPerson2(Person<T1, T2> &p) {
cout << "全局友元函数(类外)实现—— name : " << p.name << " age: " << p.age << endl;
}
template<typename T1,typename T2>
class Person {
//全局友元函数 类内实现
friend void showPerson(Person<T1, T2> &p) {
cout << "全局友元函数类内实现—— name : " << p.name << " age: " << p.age << endl;
}
//全局友元函数 类外实现
friend void showPerson2<>(Person<T1, T2> &p);
public:
Person(T1 name, T2 age) {
this->name = name;
this->age = age;
}
private:
T1 name;
T2 age;
};
void test() {
Person<string, int> p("tom", 20);
showPerson(p);
}
//类外实现
void test2() {
Person<string, int> p("tom", 20);
showPerson2(p);
}
int main() {
//test();
test2();
system("pause");
return 0;
}
8.案例实现一个通用的数组类
功能:
myArray.hpp
#pragma once
#include<iostream>
using namespace std;
template<typename T>
class MyArray {
public:
MyArray(int capacity) {
cout << "MyArray 构造函数调用。。。" << endl;
this->m_capacity = capacity;
this->m_size = 0;
this->pAddress = new T[capacity];
}
MyArray(const MyArray& arr) {
cout << "MyArray 拷贝构造函数调用。。。" << endl;
this->m_capacity = arr.m_capacity;
this->m_size = arr.m_size;
this->pAddress = new T[this->m_capacity];
for (int i = 0; i < this->m_size; i++) {
this->pAddress[i] = arr.pAddress[i];
}
}
//尾插法
void push_back(const T& value) {
if (this->m_capacity == this->m_size) {
cout << "数组容量已经满了" << endl;
return;
}
this->pAddress[this->m_size] = value;
this->m_size++;
}
//尾删法
void pop_back() {
if (this->m_size == 0) {
cout << "数组已经为空" << endl;
return;
}
this->m_size--;
}
//返回元素个数
int getSize() {
return this->m_size;
}
//返回容量
int getCapacity() {
return this->m_capacity;
}
//利用下标返回元素
T& operator[] (int index) {
return this->pAddress[index];
}
MyArray& operator= (const MyArray& arr) {
cout << "MyArray operator = 函数调用。。。" << endl;
if (this->pAddress != nullptr) {
delete[] this->pAddress;
this->pAddress = nullptr;
this->m_capacity = 0;
this->m_size = 0;
}
this->m_capacity = arr.m_capacity;
this->m_size = arr.m_size;
this->pAddress = new T[this->m_capacity];
for (int i = 0; i < this->m_size; i++) {
this->pAddress[i] = arr.pAddress[i];
}
return *this;
}
~MyArray() {
if (this->pAddress != nullptr) {
cout << "MyArray 析构函数调用。。。" << endl;
delete[] this->pAddress;
this->pAddress = nullptr;
}
}
private:
//指向数组
T* pAddress;
//容量
int m_capacity;
//元素个数
int m_size;
};
主函数
#include<iostream>
#include"myArray.hpp"
using namespace std;
class Person {
public:
string name;
int age;
Person() {
}
Person(string name, int age) {
this->name = name;
this->age = age;
}
};
void printArray(MyArray<int> &a) {
for (int i = 0; i < a.getSize(); i++) {
cout << a[i] << " ";
}
cout << endl;
}
//测试内置数据类型
void test1() {
MyArray<int> a(5);
/*MyArray<int> b(a);
MyArray<int> c(20);*/
//尾插法
for (int i = 0; i < 5; i++) {
a.push_back(i + 1);
}
cout << "a 的容量为 :" << a.getCapacity() << endl;
cout << "a 的元素个数为 :" << a.getSize() << endl;
printArray(a);
//尾删法
a.pop_back();
a.pop_back();
cout << "删除之后的情况为:" << endl;
cout << "a 的容量为 :" << a.getCapacity() << endl;
cout << "a 的元素个数为 :" << a.getSize() << endl;
printArray(a);
}
void printPersonArray(MyArray<Person>& p) {
for (int i = 0; i < p.getSize(); i++) {
cout << "name : " << p[i].name << "\t age : " << p[i].age << endl;
}
}
//测试自定义数据类型
void test2() {
MyArray<Person> p(10);
/*MyArray<int> b(a);
MyArray<int> c(20);*/
//尾插法
Person p1("wurusai", 20);
Person p2("zed", 40);
Person p3("yassuo", 40);
Person p4("fizz", 20);
Person p5("jax", 50);
Person p6("leesin", 30);
p.push_back(p1);
p.push_back(p2);
p.push_back(p3);
p.push_back(p4);
p.push_back(p5);
p.push_back(p6);
cout << "a 的容量为 :" << p.getCapacity() << endl;
cout << "a 的元素个数为 :" << p.getSize() << endl;
printPersonArray(p);
//尾删法
p.pop_back();
p.pop_back();
cout << "删除之后的情况为:" << endl;
cout << "a 的容量为 :" << p.getCapacity() << endl;
cout << "a 的元素个数为 :" << p.getSize() << endl;
printPersonArray(p);
}
int main() {
//test1();
test2();
system("pause");
return 0;
}
相关文章
- EasyC++42,模板函数显式实例化
- C++基本概念_c语言 c++区别
- 【C++408考研必备】模板函数、内存分配、标准模板
- 编写第一个 C++ 程序:Hello World 示例
- C++ 调用 Halcon 时大尺寸操作无效问题的解决方案
- C/C++ 使用Socket模拟远程CMD
- C/C++ 字符串拷贝处理
- C/C++ ShellCode 常用加密方式
- 【C++ 语言】面向对象 ( 模板编程 | 函数模板 | 类模板 )
- C++模板扩展
- C++引用
- 【错误记录】Visual Studio 编译 C++ 代码报错 ( To disable deprecation, use _CRT_SECURE_NO_WARNINGS. )
- C++模板(初阶)
- 彩色的Hello World到彩色的Donuts甜甜圈-蓝桥ROS云课-C++案例
- 【C++】你想要的——印刷模板儿
- c++基础篇之C++ 模板
- C++ STL之min_element()与max_element()(取容器中的最大最小值)详解编程语言
- C++函数或函数模板的匹配顺序
- C++关系运算符(超详细)
- C++模板类继承
- C++类模板用法详解
- C++标准模板库函数sort的那些事儿
- C++类模板与模板类深入详解
- VC++基于Dx实现的截图程序示例代码
- C++常用的#include头文件总结