{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# C++的模板" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 1. 模板简介" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "① C++的一种编程思想称为泛型编程,主要利用的技术就是模板。\n", "\n", "② 模板就是建立通用的模具,大大提高复用性。\n", "\n", "③ 模板的特点:\n", "\n", "1. 模板不可以直接使用,它只是一个框架。\n", "2. 模板的通用并不是万能的。\n", "\n", "④ C++提供两种模板机制:函数模板和类模板。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 2. 函数模板" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 2.1 函数模板基本语法" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "① 函数模板作用:建立一个通用函数,其函数返回值类型和形参类型可以不具体制定,用一个虚拟的类型来代表。\n", "\n", "② 函数模板利用关键字 template。\n", "\n", "③ 函数模板声明或定义为 template\n", "\n", "1. template -- 声明创建模板\n", "2. typename -- 表明其后面的符号是一种数据类型,可以用class代替\n", "3. T -- 通用的数据类型,名称可以替换,通常为大写字母\n", "\n", "④ 使用函数模板有两种方式,自动类型推导、显示指定类型。\n", "\n", "⑤ 模板的目的是为了提高复用性,将类型也参数化。" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#include \n", "using namespace std;\n", "\n", "//函数模板\n", "\n", "//两个整型交换函数\n", "void swapInt(int& a, int& b)\n", "{\n", " int temp = a;\n", " a = b;\n", " b = temp;\n", "}\n", "\n", "//两个浮点型交换函数\n", "void swapDouble(double &a, double &b)\n", "{\n", " double temp = a;\n", " a = b;\n", " b = temp;\n", "}\n", "\n", "//函数模板\n", "template //声明一个模板,告诉编译器后面代码中紧跟着的T不要报错,T是一个通用数据类型\n", "void mySwap( T &a, T &b)\n", "{\n", " T temp = a;\n", " a = b;\n", " b = temp;\n", "}\n", "\n", "void test01()\n", "{\n", " int a = 10;\n", " int b = 20;\n", "\n", " swapInt(a, b);\n", " cout << \"a= \" << a << endl;\n", " cout << \"b= \" << b << endl;\n", "\n", " double c = 1.1;\n", " double d = 2.2;\n", " swapDouble(c, d);\n", " cout << \"c= \" << c << endl;\n", " cout << \"d= \" << d << endl;\n", "}\n", "\n", "void test02()\n", "{\n", " int m = 10;\n", " int n = 20;\n", " //利用函数模板交换\n", " //两种方式使用函数模板\n", " //1、自动类型推导\n", " mySwap(m,n); //根据m、n的数据为int,自动设置为T为int\n", " cout << \"m= \" << m << endl;\n", " cout << \"n= \" << n << endl;\n", "\n", " //2、显示指定类型\n", " mySwap(m,n); //指定模板中数据类型T为int型\n", " cout << \"m= \" << m << endl;\n", " cout << \"n= \" << n << endl;\n", "}\n", "\n", "int main()\n", "{\n", " test01();\n", " test02();\n", "\n", " system(\"pause\");\n", "\n", " return 0;\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "运行结果: \n", "- a= 20 \n", "- b= 10 \n", "- c= 2.2 \n", "- d= 1.1 \n", "- m= 20 \n", "- n= 10 \n", "- m= 10 \n", "- n= 20 \n", "- 请按任意键继续. . ." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 2.2 函数模板注意事项" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "① 注意事项一:自动类型推导,必须推导出一致的数据T才可以使用。\n", "\n", "② 注意事项二:模板必须要确定T的数据类型,才可以使用。" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#include \n", "using namespace std;\n", "\n", "//函数模板注意事项\n", "\n", "template //typename 可以替换成class\n", "void mySwap(T&a,T&b)\n", "{\n", " T temp = a;\n", " a = b;\n", " b = temp;\n", "}\n", "//1、自动类型推导,必须推导出一致的数据类型T才可以使用\n", "void test01()\n", "{\n", " int a = 10;\n", " int b = 20;\n", " char c = 'c';\n", "\n", " mySwap(a, b); //正确\n", " //mySwap(a, c); //错误,推导不出一致的T类型\n", "\n", " cout << \"a= \" << a << endl;\n", " cout << \"b= \" << b << endl;\n", "}\n", "\n", "//2、模板必须要确定出T的数据类型,才可以使用\n", "template\n", "void func()\n", "{\n", " cout << \"func 调用\" << endl;\n", "}\n", "\n", "void test02()\n", "{\n", " //func(); //错误,模板必须要确定T的数据类型才可以使用\n", " func(); //正确,指定了模板的数据类型为int\n", "}\n", "\n", "\n", "int main()\n", "{\n", " test01();\n", " test02();\n", "\n", " system(\"pause\");\n", "\n", " return 0;\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "运行结果: \n", "- a= 20\n", "- b= 10\n", "- func 调用\n", "- 请按任意键继续. . ." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 2.3 普通函数与函数模板区别" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "① 普通函数调用时可以发生自动类型转换(隐式类型转换)。\n", "\n", "② 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换。\n", "\n", "③ 如果利用显示指定类型的方式,可以发生隐式类型转换。\n", "\n", "④ 建议使用显示指定类型的方式,调用函数模板,因为可以自己确定通用类型T。" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#include \n", "using namespace std;\n", "\n", "//普通函数与函数模板区别\n", "\n", "//1、普通函数调用可以发生隐式类型转换\n", "//2、函数模板 用自动类型推导,不可以发生隐式类型转换\n", "//3、函数模板 用显示指定类型,可以发生隐式类型转换\n", "\n", "//普通函数\n", "int myAdd01(int a, int b)\n", "{\n", " return a + b;\n", "}\n", "\n", "template\n", "T myAdd02(T a, T b)\n", "{\n", " return a + b;\n", "}\n", "\n", "void test01()\n", "{\n", " int a = 10;\n", " int b = 20;\n", " char c = 'c'; // ASCII码,a - 97,c - 99\n", " cout << myAdd01(a, b) << endl;\n", " cout << myAdd01(a, c) << endl; //进行了隐式转换,把 c 转换为整型\n", "\n", " //自动类型推导\n", " //cout << myAdd02(a, c) << endl; //报错,编译器不知道把T转为整型还是字符型\n", "\n", " //显示指定类型\n", " cout << myAdd02(a, c) << endl; //明确告诉编译器T是int类型,不是int类型的,自动转换为int类型\n", "}\n", "\n", "int main()\n", "{\n", " test01();\n", "\n", " system(\"pause\");\n", "\n", " return 0;\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "运行结果: \n", "- 30 \n", "- 109 \n", "- 109 \n", "- 请按任意键继续. . ." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 2.4 普通函数与函数模板调用规则" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "① 如果函数模板和普通函数都可以实现,优先调用普通函数。\n", "\n", "② 可以通过空模板参数列表来强制调用函数模板。\n", "\n", "③ 函数模板也可以发生重载。\n", "\n", "④ 如果函数模板可以产生更好的匹配,优先调用函数模板。\n", "\n", "⑤ 既然提供了函数模板,最好就不要提供普通函数,否则容易出现二义性。" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#include \n", "using namespace std;\n", "\n", "//普通函数与函数模板调用规则\n", "\n", "//1、如果函数模板和普通函数都可以实现,优先调用普通函数。\n", "//2、可以通过空模板参数列表来强制调用函数模板。\n", "//3、函数模板也可以发生重载。\n", "//4、如果函数模板可以产生更好的匹配,优先调用函数模板。\n", "\n", "//普通函数\n", "void myPrint(int a, int b)\n", "{\n", " cout << \"调用的普通函数\" << endl;\n", "}\n", "\n", "//函数模板\n", "template\n", "void myPrint(T a, T b)\n", "{\n", " cout << \"调用的模板\" << endl;\n", "}\n", "\n", "//函数模板重载\n", "template\n", "void myPrint(T a, T b,T c)\n", "{\n", " cout << \"调用重载的模板\" << endl;\n", "}\n", "\n", "void test01()\n", "{\n", " int a = 10;\n", " int b = 20;\n", "\n", " myPrint(a,b); //如果函数模板和普通函数都可以实现,优先调用普通函数。\n", " //如果普通函数只有声明没有定义,即使有函数模板定义,会报错,也不会调用函数模板\n", "\n", " myPrint<>(a, b); //通过空模板参数列表,强制调用函数模板\n", "\n", " myPrint(a, b, 100); //三个参数,调用函数模板重载\n", "\n", " char c1 = 'a';\n", " char c2 = 'b';\n", " myPrint(c1, c2); //虽然它可以隐式的把char类型转为int,调用普通函数,但是编译器认为模板直接把T定义为char更方便,所以编译器认为函数模板比普通函数更匹配\n", "}\n", "\n", "int main()\n", "{\n", " test01();\n", "\n", " system(\"pause\");\n", "\n", " return 0;\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "运行结果: \n", "- 调用的普通函数 \n", "- 调用的模板 \n", "- 调用重载的模板 \n", "- 调用的模板 \n", "- 请按任意键继续. . ." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 2.5 函数模板案例" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "案例描述: \n", "1. 利用函数模板封装一个排序的函数,可以对不同数据类型数组进行排序。\n", "2. 排序规则从大到小、排序算法为选择排序。 \n", "3. 分别利用char数组和int数组进行测试。" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#include \n", "using namespace std;\n", "\n", "//实现通用 对数组进行排序的函数\n", "//规则 从大到小\n", "//算法 选择\n", "//测试 char 数组、int 数组\n", "\n", "//交换函数模板\n", "template\n", "void mySwap(T& a, T& b)\n", "{\n", " T temp = a;\n", " a = b;\n", " b = temp;\n", "}\n", "\n", "//排序算法\n", "template\n", "void mySort(T arr[], int len) //数组类型不确定,但是数组的长度类型肯定为int型\n", "{\n", " for (int i = 0; i < len; i++)\n", " {\n", " int max = i; //认定最大值的下标\n", " for (int j = i + 1; j < len; j++)\n", " {\n", " //认定的最大值 比 遍历出的数值 要小,说明j下标的元素才是真正的最大值\n", " if (arr[max] < arr[j])\n", " {\n", " max = j;//更新最大值下标\n", " }\n", " }\n", " if (max != i)\n", " {\n", " //交换max和i元素\n", " mySwap(arr[max], arr[i]);\n", " }\n", " }\n", "}\n", "\n", "//提供打印数组模板\n", "template\n", "void printArray(T arr[], int len)\n", "{\n", " for (int i = 0; i < len; i++)\n", " {\n", " cout << arr[i] << \" \";\n", " }\n", " cout << endl;\n", " }\n", "\n", "void test01()\n", "{\n", " //测试char数组\n", " char charArr[] = \"badcfe\";\n", " int num = sizeof(charArr) / sizeof(char);\n", " mySort(charArr, num);\n", " printArray(charArr, num);\n", "}\n", "\n", "void test02()\n", "{\n", " //测试int数组\n", " int intArr[] = { 7,5,1,3,9,2,4,6,8 };\n", " int num = sizeof(intArr) / sizeof(int);\n", " mySort(intArr, num);\n", " printArray(intArr, num);\n", "}\n", "\n", "int main()\n", "{\n", " test01();\n", " test02();\n", "\n", " system(\"pause\");\n", "\n", " return 0;\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "运行结果: \n", "- f e d c b a \n", "- 9 8 7 6 5 4 3 2 1 \n", "- 请按任意键继续. . ." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 3. 类模板" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3.1 类模板基本语法" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "① 类模板作用:建立一个通用类,类中的成员数据可以不具体制定,用一个虚拟的类型来代表。\n", "\n", "② 类模板语法如下所示。语法注释:\n", "\n", "1. template -- 声明创建模板\n", "2. typename -- 表明后面的符号是一种数据类型,可以用class代替\n", "3. T -- 通用的数据类型,名称可以替换,通常为大写字母\n", "\n", "③ 类模板和函数模板语法相似,在声明模板template后面加类,此类称为模板。" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "// 语法: \n", "\n", "template \n", "类" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#include \n", "using namespace std;\n", "#include\n", "\n", "//类模板\n", "template\n", "class Person\n", "{\n", "public:\n", " Person(NameType name, AgeType age) //构造函数赋初值\n", " {\n", " this->m_Name = name;\n", " this->m_Age = age;\n", " }\n", "\n", " void showPerson()\n", " {\n", " cout << \"name:\" << this->m_Name << \"age:\" << this->m_Age << endl;\n", " }\n", " NameType m_Name;\n", " AgeType m_Age;\n", "};\n", "\n", "void test01()\n", "{\n", " Person p1(\"孙悟空\", 999); //尖括号<>里面是模板的参数列表 \n", " p1.showPerson();\n", "}\n", "\n", "int main()\n", "{\n", " test01();\n", "\n", " system(\"pause\");\n", "\n", " return 0;\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "运行结果:\n", " - name:孙悟空age:999" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3.2 类模板与函数模板区别" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "① 类模板与函数模板区别主要有两点:\n", "\n", "1. 类模板没有自动类型推导的使用方式。\n", "2. 类模板在模板参数列表中可以有默认参数。" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#include \n", "using namespace std;\n", "#include\n", "\n", "//类模板与函数模板区别\n", "template\n", "class Person\n", "{\n", "public:\n", " Person(NameType name, AgeType age)\n", " {\n", " this->m_Name = name;\n", " this->m_Age = age;\n", " }\n", "\n", " void showPerson()\n", " {\n", " cout << \"name:\" << this->m_Name << \"age=\" << this->m_Age << endl;\n", " }\n", "\n", " NameType m_Name;\n", " AgeType m_Age;\n", "};\n", "\n", "//1、类模板没有自动类型推导的使用方式。\n", "void test01()\n", "{\n", " //Person p(\"孙悟空\", 1000); //错误,无法用自动类型推导\n", " Person p(\"孙悟空\", 1000); //正确,只能用显示指定类型\n", " p.showPerson();\n", "}\n", "\n", "//2. 类模板在模板参数列表中可以有默认参数。\n", "void test02()\n", "{\n", " Person p(\"猪八戒\", 999); //正确,只能用显示指定类型\n", " p.showPerson();\n", "}\n", "\n", "int main()\n", "{\n", " test01();\n", " test02();\n", "\n", " system(\"pause\");\n", "\n", " return 0;\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "运行结果: \n", "- name:孙悟空age=1000 \n", "- name:猪八戒age=999 \n", "- 请按任意键继续. . ." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3.3 类模板中成员函数创建时机" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "① 类模板中成员函数和普通类中成员函数创建时机是有区别的。\n", "\n", "② 普通类中的成员函数一开始就可以创建。\n", "\n", "③ 类模板中的成员函数在调用时才创建。" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#include \n", "using namespace std;\n", "#include\n", "\n", "//类模板中成员函数创建时机\n", "//类模板中成员函数在调用时才去创建\n", "class Person1\n", "{\n", "public:\n", " void showPerson1() //普通类中的成员函数一开始就可以创建。\n", " {\n", " cout << \"Person1 show\" << endl; \n", " }\n", "};\n", "\n", "class Person2\n", "{\n", "public:\n", " void showPerson2()\n", " {\n", " cout << \"Person2 show\" << endl;\n", " }\n", "};\n", "\n", "template\n", "class MyClass\n", "{\n", "public:\n", " T obj;\n", "\n", " //类模板中的成员函数\n", " void func1() // 类模板中的成员函数在调用时才创建。\n", " {\n", " obj.showPerson1();\n", " }\n", "\n", " void func2()\n", " {\n", " obj.showPerson2();\n", " }\n", "};\n", "\n", "void test01()\n", "{\n", " MyClass m;\n", " m.func1();\n", " //m.func2(); // 报错,声明了模板是Person1类型,obj.showPerson2()是调用Person1类中的showPerson2()方法,然而Person1类中没有showPerson2()方法,所以会报错 \n", " // 如果传入的是,此方法就不会报错\n", "}\n", "\n", "int main()\n", "{\n", " test01();\n", "\n", " system(\"pause\");\n", "\n", " return 0;\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "运行结果: \n", "- Person1 show \n", "- 请按任意键继续. . ." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3.4 类模板对象做函数参数" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "① 类模板实例化出的对象,向函数传参的方式,一共有三种传入方式:\n", "\n", "1. 指定传入的类型 -- 直接显示对象的数据类型\n", "2. 参数模板化 -- 将对象中的参数变为模板进行传递\n", "3. 整个类模板化 -- 将这个对象类型 模板化进行传递\n", "\n", "② 使用比较广泛的是第一种:指定传入类型。" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#include \n", "using namespace std;\n", "#include\n", "\n", "//类模板对象做函数参数\n", "template\n", "class Person\n", "{\n", "public:\n", " Person(T1 name, T2 age)\n", " {\n", " this->m_Name = name;\n", " this->m_Age = age;\n", " }\n", "\n", " void showPerson()\n", " {\n", " cout << \"姓名:\" << this->m_Name << \" 年龄:\" << this->m_Age << endl;\n", " }\n", "\n", " T1 m_Name;\n", " T2 m_Age;\n", "};\n", "\n", "\n", "//1、指定传入类型\n", "void printPerson1(Person& p)\n", "{\n", " p.showPerson();\n", "}\n", "\n", "void test01()\n", "{\n", " Person p(\"孙悟空\", 100);\n", " printPerson1(p);\n", "}\n", "\n", "//2、参数模板化\n", "template\n", "void printPerson2(Person& p)\n", "{\n", " p.showPerson();\n", " cout << \"T1的类型为:\" << typeid(T1).name() << endl; //string的名字很长\n", " cout << \"T2的类型为:\" << typeid(T2).name() << endl;\n", "}\n", "\n", "void test02()\n", "{\n", " Person p(\"猪八戒\", 90);\n", " printPerson2(p);\n", "}\n", "\n", "//3、整个类模板化\n", "template\n", "void printPerson3(T &p)\n", "{\n", " p.showPerson();\n", " cout << \"T的类型为:\" << typeid(T).name() << endl; //打印出来为:class Person,class std::allocator >,int> \n", " //会把整个类打印出来,类的类型为Person,里面有两个类型:basic_string型、int型,类似打印 Person \n", "}\n", "\n", "void test03()\n", "{\n", " Person p(\"唐僧\", 30);\n", " printPerson3(p);\n", "}\n", "\n", "int main()\n", "{\n", " test01();\n", " test02();\n", " test03();\n", "\n", " system(\"pause\");\n", "\n", " return 0;\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "运行结果: \n", "- 姓名:孙悟空 年龄:100 \n", "- 姓名:猪八戒 年龄:90 \n", "- T1的类型为:class std::basic_string,class std::allocator > \n", "- T2的类型为:int \n", "- 姓名:唐僧 年龄:30 \n", "- T的类型为:class Person,class std::allocator >,int> \n", "- 请按任意键继续. . ." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3.5 类模板与继承" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "① 当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型。\n", "\n", "② 如果不指定父类中T的类型,编译器无法给子类分配内存。\n", "\n", "③ 如果想灵活指定出父类中T的类型,子类也需要变为类模板。" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#include \n", "using namespace std;\n", "\n", "//类模板与继承\n", "template\n", "class Base\n", "{\n", " T m;\n", "};\n", "\n", "//class Son :public Base // 报错,必须要知道父类中T类型,才能继承给子类,因为编译器不知道给子类多少个内存空间,如果T是int型给1个字节,但是T是double型给4个字节\n", "class Son:public Base\n", "{\n", "\n", "};\n", "\n", "void test01()\n", "{\n", " Son s1;\n", "}\n", "\n", "//如果想灵活指定父类中T类型,子类也需要变类模板\n", "template\n", "class Son2 :public Base //T2给了父类\n", "{\n", "public:\n", " Son2()\n", " {\n", " cout << \"T1的类型为:\" << typeid(T1).name() << endl;\n", " cout << \"T2的类型为:\" << typeid(T2).name() << endl;\n", " }\n", " T1 obj; //T1 给了子类\n", "};\n", "\n", "void test02()\n", "{\n", " Son2 S2; //T1为int,即obj为int型,T2为char型,即m为char型\n", "\n", "}\n", "int main()\n", "{\n", " test01();\n", " test02();\n", "\n", " system(\"pause\");\n", "\n", " return 0;\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "运行结果: \n", "- T1的类型为:int \n", "- T2的类型为:char \n", "- 请按任意键继续. . ." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3.6 类模板成员函数类外实现" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 3.6.1 类模板成员函数类外实现规则" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "① 类模板成员函数类外实现时,需要加上模板参数列表。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 3.6.2 类模板成员函数类内实现" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#include \n", "using namespace std;\n", "#include\n", "\n", "//类模板成员函数类内实现\n", "template\n", "class Person\n", "{\n", "public:\n", " Person(T1 name, T2 age)\n", " {\n", " this->m_Name = name;\n", " this->m_Age = age;\n", " }\n", "\n", " void showPerson()\n", " {\n", " cout << \"姓名:\" << this->m_Name << \" 年龄:\" << this->m_Age << endl;\n", " }\n", "\n", " T1 m_Name;\n", " T2 m_Age;\n", "};\n", "\n", "void test01()\n", "{\n", " Persons1(\"张三\",18);\n", " s1.showPerson();\n", "}\n", "\n", "int main()\n", "{\n", " test01();\n", "\n", " system(\"pause\");\n", "\n", " return 0;\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "运行结果: \n", "- 姓名:张三 年龄:18 \n", "- 请按任意键继续. . ." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 3.6.3 类模板成员函数类外实现" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#include \n", "using namespace std;\n", "#include\n", "\n", "//类模板成员函数类内实现\n", "template\n", "class Person\n", "{\n", "public:\n", " Person(T1 name, T2 age); //构造函数声明\n", "\n", " void showPerson(); //函数声明\n", "\n", " T1 m_Name;\n", " T2 m_Age;\n", "};\n", "\n", "//构造函数类外实现\n", "template\n", "Person::Person(T1 name, T2 age) //Person说明这是一个Person类模板的类外成员函数实现,Person::Person(T1 name, T2 age)表示类的函数的类外实现,Person(T1 name, T2 age)表示构造函数\n", "{\n", " this->m_Name = name;\n", " this->m_Age = age;\n", "}\n", "\n", "//成员函数类外实现\n", "template\n", "void Person::showPerson() // 有表示是类模板的成员函数类外实现,Person表示是Person作用域的showPerson函数 \n", "{\n", " cout << \"姓名:\" << this->m_Name << \" 年龄:\" << this->m_Age <s1(\"张三\",18);\n", " s1.showPerson();\n", "}\n", "\n", "int main()\n", "{\n", " test01();\n", "\n", " system(\"pause\");\n", "\n", " return 0;\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "运行结果: \n", "- 姓名:张三 年龄:18 \n", "- 请按任意键继续. . ." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3.7 类模板分文件编写" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 3.7.1 类模板分文件编写简介" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "① 类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到。\n", "\n", "② 解决方式:\n", "\n", "1. 解决方式1:直接包含.cpp源文件。\n", "2. 解决方式2:将声明和实现写到同一个文件中,并更改后缀名为.hpp,.hpp是约定的名称,并不是强制。\n", "\n", "③ 主流的解决方式是第二种,将类模板成员函数写到一起,并将后缀名改为.hpp。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 3.7.2 类模板没有分文件编写" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "// 类模板没有分文件编写时\n", "\n", "#include \n", "using namespace std;\n", "#include \n", "//类模板分文件编写问题以及解决\n", "\n", "template\n", "class Person\n", "{\n", "public:\n", " Person(T1 name, T2 age);\n", "\n", " void showPerson();\n", "\n", " T1 m_Name;\n", " T2 m_Age;\n", "};\n", "\n", "template\n", "Person::Person(T1 name, T2 age)\n", "{\n", " this->m_Name = name;\n", " this->m_Age = age;\n", "}\n", "\n", "template\n", "void Person::showPerson()\n", "{\n", " cout << \"姓名:\" << this->m_Name << \" 年龄:\" << this->m_Age << endl;\n", "}\n", "\n", "void test01()\n", "{\n", " Personp(\"Jerry\", 18);\n", " p.showPerson();\n", "}\n", "\n", "int main()\n", "{\n", " test01();\n", "\n", " system(\"pause\");\n", "\n", " return 0;\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "运行结果: \n", "- 姓名:Jerry 年龄:18 \n", "- 请按任意键继续. . ." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 3.7.3 类模板分文件编写(方式一)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### 3.7.3.1 方式一简介" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "① 第一种解决方式,直接包含.cpp源文件" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### 3.7.3.2 person.h 文件" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "//person.h 文件\n", "#pragma once //表示防止头文件重复包含\n", "#include \n", "using namespace std;\n", "#include \n", "\n", "template\n", "class Person\n", "{\n", "public:\n", " Person(T1 name, T2 age);\n", "\n", " void showPerson();\n", "\n", " T1 m_Name;\n", " T2 m_Age;\n", "};" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### 3.7.3.3 person.cpp 文件" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#include \"person.h\"\n", "template\n", "Person::Person(T1 name, T2 age)\n", "{\n", " this->m_Name = name;\n", " this->m_Age = age;\n", "}\n", "\n", "template\n", "void Person::showPerson()\n", "{\n", " cout << \"姓名:\" << this->m_Name << \"年龄:\" << this->m_Age << endl;\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### 3.7.3.4 main.cpp 文件" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#include \n", "using namespace std;\n", "//第一种解决方式,直接包含源文件\n", "#include \"person.cpp\" //不能使用 #include \"person.h\" \n", " //person.h文件中包含的是类模板的成员函数,如果仅包含 #include \"person.h\",由于类模板中的成员函数并没有创建,编译器并不会去找 Person(T1 name, T2 name); void showPerson(); 这两个函数的定义,即编译器从来都没有见到过,所以导致test01()中运行Personp(\"Jerry\", 18);和p.showPerson();会报错 \n", " \n", " //如果包含#include \"person.cpp\"就会看到 Person(T1 name, T2 name); void showPerson(); 这两个函数的定义,由于#include \"person.cpp\"上面有#include \"person.h\"就又回看到.h文件中的声明,因此.h与.cpp文件都看到了 \n", "\n", "void test01()\n", "{\n", " Personp(\"Jerry\", 18);\n", " p.showPerson();\n", "}\n", "\n", "int main()\n", "{\n", " test01();\n", "\n", " system(\"pause\");\n", "\n", " return 0;\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 3.7.4 类模板分文件编写(方式二)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### 3.7.4.1 方式二简介" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "① 第二种解决方式,将.h和.cpp中的内容写到一起,将后缀名改为.hpp文件。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### 3.7.4.2 person.hpp文件" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "//person.hpp 文件\n", "#pragma once //表示防止头文件重复包含\n", "#include \n", "using namespace std;\n", "#include \n", "\n", "template\n", "class Person\n", "{\n", "public:\n", " Person(T1 name, T2 age);\n", "\n", " void showPerson();\n", " \n", " T1 m_Name;\n", " T2 m_Age;\n", "};\n", "\n", "//构造函数 类外实现\n", "template\n", "Person::Person(T1 name, T2 age)\n", "{\n", " this->m_Name = name;\n", " this->m_Age = age;\n", "}\n", "\n", "//成员函数 类外实现\n", "template\n", "void Person::showPerson()\n", "{\n", " cout << \"姓名:\" << this->m_Name << \"年龄:\" << this->m_Age << endl;\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### 3.7.4.3 main.cpp 文件" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#include \n", "using namespace std;\n", "#include \"person.hpp\" \n", "\n", "void test01()\n", "{\n", " Personp(\"Jerry\", 18);\n", " p.showPerson();\n", "}\n", "\n", "int main()\n", "{\n", " test01();\n", "\n", " system(\"pause\");\n", "\n", " return 0;\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3.8 类模板与友元" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "① 全局函数类内实现--直接在类内声明友元即可\n", "\n", "② 全员函数类外实现--需要提前让编译器知道全局函数的存在\n", "\n", "③ 建议全局函数做类内实现,用法简单,而且编译器可以直接识别。" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#include \n", "using namespace std;\n", "#include\n", "\n", "//通过全局函数 打印Person信息\n", "\n", "//提前声明,提前让编译器知道Person模板类存在\n", "template\n", "class Person;\n", "\n", "//全局函数 类外实现\n", "//让编译器知道Person类存在后,还需要提前让编译器知道printPerson2全局函数存在\n", "template\n", "void printPerson2(Person p) //全局函数,所以不需要加作用域\n", "{\n", " cout << \"类外实现--姓名:\" << p.m_Name << \" 年龄:\" << p.m_Age << endl;\n", "}\n", "\n", "template\n", "class Person\n", "{\n", " //全局函数 类内实现\n", " friend void printPerson(Person p)\n", " {\n", " cout << \"类内实现--姓名:\" << p.m_Name << \" 年龄:\" << p.m_Age<(Person p); //加一个空模板参数列表,表示是函数模板声明,而不是普通函数的声明 \n", "\n", " //只有前面让提前让编译器知道printPerson2存在,由于printPerson2里面有Person类,所以还需要提前让编译器知道Person类存在,才能申明全局函数类外实现:friend void printPerson2<>(Person p);\n", "public:\n", " Person(T1 name, T2 age)\n", " {\n", " this->m_Name = name;\n", " this->m_Age = age;\n", " }\n", "\n", "private:\n", " T1 m_Name;\n", " T2 m_Age;\n", "};\n", "\n", "//1、全局函数在类内实现\n", "void test01()\n", "{\n", " Personp(\"Tom\", 20);\n", " printPerson(p);\n", "}\n", "\n", "//2、全局函数在类外实现\n", "void test02()\n", "{\n", " Personp(\"Jerry\", 20);\n", " printPerson2(p);\n", "}\n", "\n", "int main()\n", "{\n", " test01();\n", " test02();\n", "\n", " system(\"pause\");\n", "\n", " return 0;\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "运行结果:\n", "- 类内实现--姓名:Tom 年龄:20 \n", "- 类外实现--姓名:Jerry 年龄:20 \n", "- 请按任意键继续. . ." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3.9 类模板案例" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 3.9.1 案例描述" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "案例描述:实现一个通用的数组类,要求如下: \n", "1. 可以对内置数据类型以及自定义数据类型的数据进行存储。\n", "2. 将数组中的数据存储到堆区。\n", "3. 构造函数中可以传入数组的容量。\n", "4. 提供对应的拷贝构造函数以及operator=防止浅拷贝问题。\n", "5. 提供尾插法和尾删法对数组中的数据进行增加和删除。\n", "6. 可以通过下标的方式访问数组中的元素。\n", "7. 可以获取数组中当前元素个数和数组的容量。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 3.9.2 案例代码" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "//自己的通用的数组类\n", "#pragma once\n", "#include \n", "using namespace std;\n", "#include\n", "\n", "template\n", "class MyArray\n", "{\n", "public:\n", " //有参构造 参数为容量\n", " MyArray(int capacity)\n", " {\n", " //cout << \"MyArray有参构造调用\" << endl;\n", " this->m_Capacity = capacity;\n", " this->m_Size = 0;\n", " this->pAddress = new T[this->m_Capacity]; //T[this->m_Capacity]表示类型为T的数组 \n", " }\n", "\n", " //拷贝构造\n", " MyArray(const MyArray& arr)\n", " {\n", " //cout << \"MyArray拷贝构造调用\" << endl;\n", " this->m_Capacity = arr.m_Capacity;\n", " this->m_Size = arr.m_Size;\n", " //this->pAddress = arr.pAddress; //浅拷贝会导致堆区数据重复释放\n", "\n", " //深拷贝\n", " this->pAddress = new T[arr.m_Capacity];\n", "\n", " //将arr中的数据都拷贝过来\n", " for (int i = 0; i < this->m_Size; i++)\n", " {\n", " this->pAddress[i] = arr.pAddress[i];\n", " }\n", " }\n", "\n", " //operator= 防止浅拷贝问题\n", " MyArray& operator = (const MyArray& arr)\n", " {\n", " //cout << \"MyArray 的 operator调用\" << endl;\n", " //先判断原来堆区是否有数据,如果有先释放\n", " if (this->pAddress != NULL)\n", " {\n", " delete[] this->pAddress;\n", " this->pAddress = NULL;\n", " this->m_Capacity = 0;\n", " this->m_Size = 0;\n", " }\n", "\n", " //深拷贝\n", " this->m_Capacity = arr.m_Capacity;\n", " this->m_Size = arr.m_Size;\n", " this->pAddress = new T[arr.m_Capacity];\n", " for (int i = 0; i < this->m_Size; i++)\n", " {\n", " this->pAddress[i] = arr.pAddress[i];\n", " }\n", " return *this;\n", " }\n", "\n", " //尾插法\n", " void Push_Back(const T& val)\n", " {\n", " //判断容量是否等于大小\n", " if (this->m_Capacity == this->m_Size)\n", " {\n", " return;\n", " }\n", " this->pAddress[this->m_Size] = val; //在数组末尾插入数据\n", " this->m_Size++; //更新数组大小\n", " }\n", "\n", " //尾删法\n", " void Pop_Back()\n", " {\n", " //让用户访问不到最后一个元素,即为尾删,逻辑删除\n", " if (this->m_Size == 0)\n", " {\n", " return;\n", " }\n", " this->m_Size--;\n", " }\n", "\n", " //通过下标方式访问数组中的元素\n", " //例如,还想arr[0]作为左值存在,要返回T的引用,把数据本身作为一个返回\n", " T& operator[](int index)\n", " {\n", " return this->pAddress[index];\n", " }\n", " \n", " //返回数组容量\n", " int getCapacity()\n", " {\n", " return this->m_Capacity;\n", " }\n", "\n", " //返回数组大小\n", " int getSize()\n", " {\n", " return this->m_Size;\n", " }\n", "\n", " //析构函数\n", " ~MyArray()\n", " {\n", " if (this->pAddress != NULL)\n", " {\n", " //cout << \"MyArray析构函数调用\" << endl;\n", " delete[] this->pAddress;\n", " this->pAddress = NULL;\n", " }\n", " }\n", "\n", "private:\n", " T* pAddress; //指针指向堆区开辟的真实数组\n", " int m_Capacity; //数组容量\n", " int m_Size; //数组大小\n", "};\n", "\n", "void printIntArray(MyArray& arr)\n", "{\n", " for (int i = 0; i < arr.getSize(); i++)\n", " {\n", " cout << arr[i] << endl;\n", " }\n", "}\n", "\n", "void test01()\n", "{\n", " MyArrayarr1(5);\n", " \n", " for (int i = 0; i < 5; i++)\n", " {\n", " //利用尾插法向数组中插入数据\n", " arr1.Push_Back(i);\n", " }\n", " cout << \"arr1的打印输出为:\" << endl;\n", "\n", " printIntArray(arr1);\n", " \n", " cout << \"arr1的容量为:\" << arr1.getCapacity() << endl;\n", " cout << \"arr1的大小为:\" << arr1.getSize() << endl;\n", "\n", " MyArrayarr2(arr1);\n", "\n", " cout << \"arr2的打印输出为:\" << endl;\n", " printIntArray(arr2);\n", "\n", " //尾删\n", " arr2.Pop_Back();\n", " cout << \"arr2尾删后:\" << endl;\n", " cout << \"arr2的容量为:\" << arr2.getCapacity() << endl;\n", " cout << \"arr2的容量为:\" << arr2.getSize() << endl;\n", "\n", " MyArrayarr3(100);\n", " arr3 = arr1;\n", "}\n", "\n", "//测试自定义数据类型\n", "class Person\n", "{\n", "public:\n", " Person() {}; //默认构造空实现\n", " Person(string name, int age)\n", " {\n", " this->m_Name = name;\n", " this->m_Age = age;\n", " }\n", "\n", " string m_Name;\n", " int m_Age;\n", "};\n", "\n", "void printPersonArray(MyArray& arr)\n", "{\n", " for (int i = 0; i < arr.getSize(); i++)\n", " {\n", " cout << \"姓名:\" << arr[i].m_Name << \"年龄:\" << arr[i].m_Age << endl;\n", " }\n", "}\n", "\n", "void test02()\n", "{\n", " MyArray arr(10);\n", "\n", " Person p1(\"孙悟空\", 999);\n", " Person p2(\"韩信\", 30);\n", " Person p3(\"妲己\", 20);\n", " Person p4(\"赵云\", 25);\n", " Person p5(\"安其拉\", 27);\n", "\n", " //将数据插入到数组中\n", " arr.Push_Back(p1);\n", " arr.Push_Back(p2);\n", " arr.Push_Back(p3);\n", " arr.Push_Back(p4);\n", " arr.Push_Back(p5);\n", "\n", " //打印数组\n", " printPersonArray(arr);\n", "\n", " //输出容量\n", " cout << \"arr的容量为:\" << arr.getCapacity() << endl;\n", "\n", " //输出大小\n", " cout << \"arr的大小为:\" << arr.getSize() << endl;\n", "}\n", "\n", "int main() {\n", "\n", " test01();\n", " test02();\n", "\n", " system(\"pause\");\n", "\n", " return 0;\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "运行结果: \n", "- arr1的打印输出为: \n", "- 0 \n", "- 1 \n", "- 2 \n", "- 3 \n", "- 4 \n", "- arr1的容量为:5 \n", "- arr1的大小为:5 \n", "- arr2的打印输出为: \n", "- 0 \n", "- 1 \n", "- 2 \n", "- 3 \n", "- 4 \n", "- arr2尾删后: \n", "- arr2的容量为:5 \n", "- arr2的容量为:4 \n", "- 姓名:孙悟空年龄:999 \n", "- 姓名:韩信年龄:30 \n", "- 姓名:妲己年龄:20 \n", "- 姓名:赵云年龄:25 \n", "- 姓名:安其拉年龄:27 \n", "- arr的容量为:10 \n", "- arr的大小为:5 \n", "- 请按任意键继续. . ." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 4. 模板局限性" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 4.1 模板局限性简介" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "① 模板的通用性并不是万能的。\n", "\n", "② 利用具体化的模板,可以解决自定义类型的通用化。\n", "\n", "③ 学习模板并不是为了写模板,而是在STL中能够运用系统提供的模板。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 4.2 局限性一" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "//下面代码提供赋值操作,如果传入的a和b是一个数组,就无法实现了\n", "//数组无法给另一个数组直接赋值\n", "\n", "template\n", "void myPrint(T a, T b)\n", "{\n", " a = b;\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 4.3 局限性二" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "//在下面代码中,如果T的数据类型传入的是像Person这样的自定义数据类型,也无法正常运行。\n", "\n", "template\n", "void myPrint(T a, T b)\n", "{\n", " if (a > b) { ... };\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 4.4 模板具体实现" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#include \n", "using namespace std;\n", "#include\n", "\n", "//模板局限性\n", "//模板并不是万能的,有些特定数据类型,需要用具体化方式做特殊实现\n", "\n", "class Person\n", "{\n", "public:\n", " Person(string name, int age)\n", " {\n", " this->m_Name = name;\n", " this->m_Age = age;\n", " }\n", " string m_Name;\n", " int m_Age;\n", "};\n", "\n", "//对比两个数据是否相等的函数\n", "template\n", "bool myCompare(T &a, T &b)\n", "{\n", " if (a == b) //(a == b)能判断整型、浮点型数据是否相等,但是没有办法判断Person类型与Person类型相等比较,但是可以通过==运算符重载,来判断Person类型的p1和Person类型的p2是否相等\n", " {\n", " return true;\n", " }\n", " else\n", " {\n", " return false;\n", " }\n", "}\n", "\n", "//利用具体化的Person(类)版本实现代码,具体化优先调用\n", "template<> bool myCompare(Person & p1, Person & p2) //template<>表示这是一个模板重载的版本,Person表示这是重载的person的模板 \n", "{\n", " if (p1.m_Name == p2.m_Name && p1.m_Age == p2.m_Age)\n", " {\n", " return true;\n", " }·\n", " else\n", " {\n", " return false;\n", " }\n", "}\n", "\n", "void test01()\n", "{\n", " int a = 10;\n", " int b = 20;\n", " \n", " bool ret = myCompare(a, b);\n", "\n", " if (ret)\n", " {\n", " cout << \"a==b\" << endl;\n", " }\n", " else\n", " {\n", " cout << \"a!=b\" << endl;\n", " }\n", "}\n", "\n", "void test02()\n", "{\n", " Person p1(\"Tom\", 10);\n", " Person p2(\"Tom\", 10);\n", "\n", " bool ret = myCompare(p1, p2);\n", " if (ret)\n", " {\n", " cout << \"p1==p2\" << endl;\n", " }\n", " else\n", " {\n", " cout << \"p1!=p2\" << endl;\n", " }\n", "}\n", "\n", "int main()\n", "{\n", " test01();\n", " test02();\n", "\n", " system(\"pause\");\n", "\n", " return 0;\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "运行结果: \n", "- a!=b \n", "- p1==p2 \n", "- 请按任意键继续. . ." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3.6.3", "language": "python", "name": "python3.6.3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.3" }, "toc": { "base_numbering": 1, "nav_menu": {}, "number_sections": false, "sideBar": true, "skip_h1_title": false, "title_cell": "Table of Contents", "title_sidebar": "Contents", "toc_cell": false, "toc_position": { "height": "calc(100% - 180px)", "left": "10px", "top": "150px", "width": "423.594px" }, "toc_section_display": true, "toc_window_display": true } }, "nbformat": 4, "nbformat_minor": 4 }