- 一、模板的概念
- 二、函数模板
- 2.1 函数模板的基本语法
- 2.2函数模板的注意事项
- 2.3 函数模板案例-数组的选择排序
- 2.4 普通模板与函数模板的区别
- 2.5 普通函数和函数模板的调用规则
- 2.6 函数模板的局限性
- 三、类模板
- 3.1 类模板语法
- 3.2 类模板与函数模板的区别
- 3.3 类模板成员函数创建时机
- 3.4 类模板对象做函数参数
- 3.5 类模板与继承
- 3.6 类模板成员类外实现
- 3.7 类模板分文件编写
- 3.8 类模板与友元
本博文是学习黑马程序员C++视频时做的笔记,记录一下只是方便温故知新,不做其他用途。 一、模板的概念
1、模板的特点:
(1)不可以直接使用,它只是一个模板;
(2)模板的通用不是万能的。
2、C++提供两种模板机制:
(1)函数模板;
(2)类模板。
语法:
template
模板有两种使用方法:
(1)自动推导类型,例如myswap(a,b);
(2)显示指定类型,例如myswap(a,b);
#include#include using namespace std; //模板的基本语法 template //声明模板 void myswap(T &a,T &b) { T temp = a; a = b; b = temp; } void test01() { int a = 10; int b = 20; // 两种方式使用函数模板 // 1 自动推导类型 // myswap(a,b); // cout<<"a = "< // cout<<"b = "<(a,b); cout<<"a = "< 2.2函数模板的注意事项 (1)自动推导类型,必须推导出一致的数据类型T才可以使用
(2)模板必须确定出T的数据类型才可以使用。#include2.3 函数模板案例-数组的选择排序#include using namespace std; //模板的注意事项 template //声明模板 void myswap(T &a,T &b) { T temp = a; a = b; b = temp; } //1、自动推导类型,必须推导出一致的数据类型T才可以使用 void test01() { int a = 10; int b = 20; char c = 'c'; // 两种方式使用函数模板 // 1 自动推导类型 myswap(a,b);//正确 // myswap(a,c);//报错,类型不一致 cout<<"a = "< void func() { cout<<"func 调用"< ();//正确,指定函数模板的数据类型为int。 } int main() { test01(); test02(); return 0; } #include2.4 普通模板与函数模板的区别#include using namespace std; //交换的函数模板 template void mySwap(T &a, T&b) { T temp = a; a = b; b = temp; } template //利用选择排序,进行对数组从大到小的排序 void mySort(T arr[], int len) { for (int i = 0; i < len; i++) { int max = i; //最大数的下标 for (int j = i + 1; j < len; j++) { if (arr[max] < arr[j]) { max = j; } } if (max != i) //如果最大数的下标不是i,交换两者 { mySwap(arr[max], arr[i]); } } } template void printArray(T arr[], int len) { for (int i = 0; i < len; i++) { cout << arr[i] << " "; } cout << endl; } void test01() { //测试char数组 char charArr[] = "bdcfeagh"; int num = sizeof(charArr) / sizeof(char); mySort(charArr, num); printArray(charArr, num); } void test02() { //测试int数组 int intArr[] = {1,3,5,7,9,2,4,6,8}; int num = sizeof(intArr) / sizeof(int); mySort(intArr, num); printArray(intArr, num); } int main() { test01(); test02(); return 0; } 区别:
(1)普通函数调用时,可以发生自动类型转换(隐式类型转换);
(2)函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换;
(3)如果利用显示指定类型的方式,可以发生隐式类型转换。#include#include using namespace std; //普通函数与函数模板区别 //1 普通函数 int myAdd01(int a, int b) { return a + b; } //2 函数模板 template T myAdd02(T a, T b) { return a + b; } //使用函数模板时,如果用自动类型推导,不会发生自动类型转换,即隐式类型转换 void test01() { int a = 10; int b = 20; char c = 'c'; // 普通函数,将char类型的'c'隐式转换为int类型 'c' 对应 ASCII码 99 cout << myAdd01(a, c) << endl; //109,正确, // 使用自动类型推导时,不会发生隐式类型转换 // myAdd02(a, c); // 报错, // 显示指定类型,可以发生隐式类型转换 cout< (b, c)< 2.5 普通函数和函数模板的调用规则 规则:
(1)如果普通函数和函数模板都可以实现,优先调用普通函数;
(2)可以通过空模板参数列表来强制调用函数模板;
(3)函数模板也可以发生重载;
(4)如果函数模板可以产生更好的匹配,优先调用函数模板。#include2.6 函数模板的局限性#include using namespace std; //普通函数与函数模板调用规则 void myPrint(int a, int b) { cout << "调用的普通函数" << endl; } template void myPrint(T a, T b) { cout << "调用的模板" << endl; } template void myPrint(T a, T b, T c) { cout << "调用重载的模板" << endl; } void test01() { //1、如果函数模板和普通函数都可以实现,优先调用普通函数 // 注意 如果告诉编译器 普通函数是有的,但只是声明没有实现,或者不在当前文件内实现,就会报错找不到 int a = 10; int b = 20; myPrint(a, b); //调用普通函数 //2、可以通过空模板参数列表来强制调用函数模板 myPrint<>(a, b); //<>,实现强制调用函数模板 //3、函数模板也可以发生重载 int c = 30; myPrint(a, b, c); //调用重载的函数模板 //4、 如果函数模板可以产生更好的匹配,优先调用函数模板 char c1 = 'a'; char c2 = 'b'; myPrint(c1, c2); //普通函数和重载函数的匹配度不如函数模板,调用函数模板 } int main() { test01(); return 0; } 模板的通用性不是万能的。
#include三、类模板#include using namespace std; //模板的局限性 //模板并不是万能的,有些特定数据类型,需要具体化方式特殊实现 //自定义类 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<>开头,并通过名称来指出类型 //可以创建具体化的Person数据类型的模板,用于特殊处理这个类型,具体化优先调用 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 << "a == b " << endl; } else { cout << "a != b " << endl; } } void test02() { Person p1("Tom", 10); Person p2("Tom", 10); //自定义数据类型,不会调用普通的函数模板,调用具体化实现模板 bool ret = myCompare(p1, p2); if (ret) { cout << "p1 == p2 " << endl; } else { cout << "p1 != p2 " << endl; } } int main() { test01(); test02(); return 0; } 类模板作用:建立一个通用类,类中的成员 数据类型可以不具体制定,用一个虚拟的类型来代表。
3.1 类模板语法template类 其中,
● template — 声明创建模板;
● typename — 表面其后面的符号是一种数据类型,可以用class代替;
● T — 通用的数据类型,名称可以替换,通常为大写字母。#include3.2 类模板与函数模板的区别#include using namespace std; //类模板 template class Person { public: Person(NameType name, AgeType age) { this->m_Name = name; this->m_Age = age; } void ShowPerson() { cout<<"naem:"<< this->m_Name<<"age:"<< this->m_Age< p1("孙悟空",999); p1.ShowPerson(); } int main() { test01(); return 0; } 区别:
1、类模板没有自动类型推导的使用方式;
2、类模板在模板参数列表中可以有默认参数。#include3.3 类模板成员函数创建时机#include using namespace std; //类模板与函数模板区别 template //类模板在模板声明中可以指定默认参数,如class AgeType = int class Person { public: Person(NameType name, AgeType age) { this->m_Name = name; this->m_Age = age; } void ShowPerson() { cout<<"naem:"<< this->m_Name<<"age:"<< this->m_Age< p1("孙悟空",999); p1.ShowPerson(); } //2、类模板在模板参数列表中可以有默认参数。 void test02() { Person p2("猪八戒",1000); p2.ShowPerson(); } int main() { test01(); test02(); return 0; } 类模板中成员函数和普通类中成员函数创建时机是有区别的:
1、普通类中的成员函数一开始就可以创建;
2、类模板中的成员函数在调用时才创建。#include3.4 类模板对象做函数参数#include using namespace std; class Person1 { public: void ShowPerson1() { cout << "Person1 show" << endl; } }; class Person2 { public: void ShowPerson2() { cout << "Person2 show" << endl; } }; template class MyClass { public: T obj; //类模板中的成员函数,并不是一开始就创建的,而是在模板调用时再生成 void fun1() { obj.ShowPerson1(); } void fun2() { obj.ShowPerson2(); } }; void test01() { MyClass m; m.fun1(); // m.fun2();//编译会出错,说明函数调用才会去创建成员函数 } int main() { test01(); return 0; } 一共有三种传入方式:
1、指定传入类型——直接显示对象的数据类型;
2、参数模板化——将对象中的参数变为模板进行传递;
3、整个参数类模板化——将这个对象模板化进行传递。#include3.5 类模板与继承#include #include using namespace std; //类模板对象做函数参数 //1、指定传入类型——直接显示对象的数据类型; //2、参数模板化——将对象中的参数变为模板进行传递; //3、整个参数类模板化——将这个对象模板化进行传递。 template class Person { public: Person(T1 name,T2 age) { this->m_Name = name; this->m_Age = age; } void ShowPerson() { cout<<"姓名:"<< this->m_Name<<"年龄"<< this->m_Age< &p) { p.ShowPerson(); } void test01() { Person p("孙悟空",100); printPerson01(p); } //2 参数模板化 template void printPerson02(Person &p) { p.ShowPerson(); // cout<<"T1的类型为:"< p("猪八戒",101); printPerson02(p); } //3 整个类模板化 template void printPerson03(T3 &p) { p.ShowPerson(); // cout<<"T3的类型为:"< p("唐僧",33); printPerson03(p); } int main() { test01(); test02(); test03(); return 0; } 当类模板碰到继承时,需要注意一下几点:
1、当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型;
2、如果不指定,编译器无法给子类分配内存;
3、如果想灵活指定出父类中T的类型,子类也需变为类模板。#include3.6 类模板成员类外实现#include #include using namespace std; //类模板与继承 template class base { T m; }; //1 必须知道父类中的T类型,才能继承给子类 //class Son: public base//报错,如果不指定,编译器无法给子类分配内存; class Son: public base { }; void test01() { Son s1; } //2 如果想灵活地指定父类中T数据类型,子类也需要变为类模板 template class Son2: public base { public: Son2() { cout<<"T1的类型为:"< s2; } int main() { test01(); test02(); return 0; } #include3.7 类模板分文件编写#include using namespace std; //类模板成员函数类外实现 template class Person { public: // 成员函数类内声明 Person(T1 name,T2 age); void showPerson(); // Person(T1 name,T2 age) // { // this->m_Name = name; // this->m_Age = age; // } // void showPerson() // { // cout<<"Name:"<< this->m_Name<<"Age:"<< this->m_Age< Person ::Person(T1 name, T2 age) { this->m_Name = name; this->m_Age = age; } //成员函数类外实现 template void Person ::showPerson() { cout<<"Name:"<< this->m_Name<<"Age:"<< this->m_Age< p("Tom",20); p.showPerson(); } int main() { test01(); return 0; } ● 存在问题:
类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到。
● 解决方法:
解决方式1:直接包含.cpp源文件;
解决方式2:将声明和实现写到同一个文件中,并更改后缀名为.hpp,hpp是约定的名称,并不是强制。person.hpp文件中内容
// Created by chenhong on 2021/11/24. // #ifndef CLIONPROJECTS_PERSON_H #define CLIONPROJECTS_PERSON_H #endif //CLIONPROJECTS_PERSON_H #pragma once #includeusing namespace std; #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 << "姓名: " << this->m_Name << " 年龄:" << this->m_Age << endl; } 类模板分文件中的.cpp代码
#include3.8 类模板与友元#include using namespace std; //#include "person.h" //#include "person.cpp" //解决方式1,包含cpp源文件(不常用) //解决方式2,将声明和实现写到一起,文件后缀名改为.hpp(常用) #include "person.hpp" void test01() { Person p("Jerry",18); p.showPerson(); } int main() { test01(); return 0; } 1、全局函数类内实现 - 直接在类内声明友元即可;
2、全局函数类外实现 - 需要提前让编译器知道全局函数的存在。#includeusing namespace std; //2.3局函数类外实现的声明,提前让编译器知道Person类 template class Person; //2.2全局函数的类外实现 template void printPerson2(Person p) { cout<<"全局函数类外实现——姓名:"< class Person { // 1.1 全局函数配合友元 类内实现 friend void printPerson(Person p) { cout<<"姓名:"< (Person p); public: Person(T1 name,T2 age) { this->m_Name = name; this->m_Age = age; } private: T1 m_Name; T2 m_Age; }; //1.2 测试 void test01() { Person p1("Tom",20); printPerson(p1); } //2.4 测试 void test02() { Person p2("Tim",22); printPerson2(p2); } int main() { test01(); test02(); return 0; }



