{"id":"58954472","type":"page","status":"current","title":"05.C++模板","version":{"by":{"type":"known","username":"ethanliu","userKey":"2c90931b6cfb654f016d5c69becc0001","profilePicture":{"path":"/images/icons/profilepics/default.svg","width":48,"height":48,"isDefault":true},"displayName":"刘浩","_links":{"self":"https://wiki.suncaper.net/rest/api/user?key=2c90931b6cfb654f016d5c69becc0001"},"_expandable":{"status":""}},"when":"2022-07-09T01:51:24.000Z","message":"","number":1,"minorEdit":false,"hidden":false,"_links":{"self":"https://wiki.suncaper.net/rest/experimental/content/58954472/version/1"},"_expandable":{"content":"/rest/api/content/58954472"}},"body":{"storage":{"value":"

5.1 模板概论

c++提供了函数模板(function template.)所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体制定,用一个虚拟的类型来代表。这个通用函数就成为函数模板。凡是函数体相同的函数都可以用这个模板代替,不必定义多个函数,只需在模板中定义一次即可。在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现不同函数的功能。

  • c++提供两种模板机制:函数模板类模板
  • 类属 - 类型参数化,又称参数模板

总结:

  • 模板把函数或类要处理的数据类型参数化,表现为参数的多态性,成为类属。
  • 模板用于表达逻辑结构相同,但具体数据元素类型不同的数据对象的通用行为。

5.2 函数模板

5.2.1 什么是函数模板?

cpp\nvoid MySwap(T& a,T& b){\n\tT temp = a;\n\ta = b;\n\tb = temp;\n}\n\nvoid test01(){\n\t\n\tint a = 10;\n\tint b = 20;\n\tcout << \"a:\" << a << \" b:\" << b << endl;\n\t//1. 这里有个需要注意点,函数模板可以自动推导参数的类型\n\tMySwap(a,b);\n\tcout << \"a:\" << a << \" b:\" << b << endl;\n\n\tchar c1 = 'a';\n\tchar c2 = 'b';\n\tcout << \"c1:\" << c1 << \" c2:\" << c2 << endl;\n\t//2. 函数模板可以自动类型推导,那么也可以显式指定类型\n\tMySwap(c1, c2);\n\tcout << \"c1:\" << c1 << \" c2:\" << c2 << endl;\n}]]>

5.2.2 课堂练习

使用函数模板实现对charint类型数组进行排序?

5.3 函数模板和普通函数区别

  • 函数模板不允许自动类型转化
  • 普通函数能够自动进行类型转化
cpp\nT MyPlus(T a, T b){\n\tT ret = a + b;\n\treturn ret;\n}\n\n//普通函数\nint MyPlus(int a,char b){\n\tint ret = a + b;\n\treturn ret;\n}\n\nvoid test02(){\n\n\tint a = 10;\n\tchar b = 'a';\n\n\t//调用函数模板,严格匹配类型\n\tMyPlus(a, a);\n\tMyPlus(b, b);\n\t//调用普通函数\n\tMyPlus(a, b);\n\t//调用普通函数 普通函数可以隐式类型转换\n\tMyPlus(b, a);\n\n\t//结论:\n\t//函数模板不允许自动类型转换,必须严格匹配类型\n\t//普通函数可以进行自动类型转换\n}]]>

5.4 函数模板和普通函数在一起调用规则

  • c++编译器优先考虑普通函数
  • 可以通过空模板实参列表的语法限定编译器只能通过模板匹配
  • 函数模板可以像普通函数那样可以被重载
  • 如果函数模板可以产生一个更好的匹配,那么选择模板
cpp\nT MyPlus(T a, T b){\n\tT ret = a + b;\n\treturn ret;\n}\n\n//普通函数\nint MyPlus(int a, int b){\n\tint ret = a + b;\n\treturn ret;\n}\n\nvoid test03(){\n\tint a = 10;\n\tint b = 20;\n\tchar c = 'a';\n\tchar d = 'b';\n\t//如果函数模板和普通函数都能匹配,c++编译器优先考虑普通函数\n\tcout << MyPlus(a, b) << endl;\n\t//如果我必须要调用函数模板,那么怎么办?\n\tcout << MyPlus<>(a, b) << endl;\n\t//此时普通函数也可以匹配,因为普通函数可以自动类型转换\n\t//但是此时函数模板能够有更好的匹配\n\t//如果函数模板可以产生一个更好的匹配,那么选择模板\n\tcout << MyPlus(c,d);\n}\n\n//函数模板重载\ntemplate\nT MyPlus(T a, T b, T c){\n\tT ret = a + b + c;\n\treturn ret;\n}\n\nvoid test04(){\n\n\tint a = 10;\n\tint b = 20;\n\tint c = 30;\n\tcout << MyPlus(a, b, c) << endl;\n\t//如果函数模板和普通函数都能匹配,c++编译器优先考虑普通函数\n}]]>

5.5 模板机制剖析

思考:为什么函数模板可以和普通函数放在一起?c++编译器是如何实现函数模板机制的?

5.5.1编译过程

hello.cpp程序是高级c语言程序,这种程序易于被人读懂。为了在系统上运行hello.c程序,每一条c语句都必须转化为低级的机器指令。然后将这些机器指令打包成可执行目标文件格式,并以二进制形式存储于磁盘中。

预处理(Pre-processing) -> 编译(Compiling) ->汇编(Assembling) -> 链接(Linking)

5.5.2 模板实现机制

函数模板机制结论:

  • 编译器并不是把函数模板处理成能够处理任何类型的函数
  • 函数模板通过具体类型产生不同的函数
  • 编译器会对函数模板进行两次编译,在声明的地方对模板代码本身进行编译,在调用的地方对参数替换后的代码进行编译。

5.6模板的局限性

假设有如下模板函数:

       template<class T>

       void f(T a, T b)

       { … }

如果代码实现时定义了赋值操作 a = b,但是T为数组,这种假设就不成立了

同样,如果里面的语句为判断语句 if(a>b),T如果是结构体,该假设也不成立,另外如果是传入的数组,数组名为地址,因此它比较的是地址,而这也不是我们所希望的操作。

       总之,编写的模板函数很可能无法处理某些类型,另一方面,有时候通用化是有意义的,但C++语法不允许这样做。为了解决这种问题,可以提供模板的重载,为这些特定的类型提供具体化的模板。


cppmName = name;\n\t\tthis->mAge = age;\n\t}\n\tstring mName;\n\tint mAge;\n};\n\n//普通交换函数\ntemplate \nvoid mySwap(T &a,T &b)\n{\n\tT temp = a;\n\ta = b; \n\tb = temp;\n}\n//第三代具体化,显示具体化的原型和定意思以template<>开头,并通过名称来指出类型\n//具体化优先于常规模板\ntemplate<>void mySwap(Person &p1, Person &p2)\n{\n\tstring nameTemp;\n\tint ageTemp;\n\n\tnameTemp = p1.mName;\n\tp1.mName = p2.mName;\n\tp2.mName = nameTemp;\n\n\tageTemp = p1.mAge;\n\tp1.mAge = p2.mAge;\n\tp2.mAge = ageTemp;\n\n}\n\nvoid test()\n{\n\tPerson P1(\"Tom\", 10);\n\tPerson P2(\"Jerry\", 20);\n\n\tcout << \"P1 Name = \" << P1.mName << \" P1 Age = \" << P1.mAge << endl;\n\tcout << \"P2 Name = \" << P2.mName << \" P2 Age = \" << P2.mAge << endl;\n\tmySwap(P1, P2);\n\tcout << \"P1 Name = \" << P1.mName << \" P1 Age = \" << P1.mAge << endl;\n\tcout << \"P2 Name = \" << P2.mName << \" P2 Age = \" << P2.mAge << endl;\n}\n\n]]>


5.7 类模板

5.7.1 类模板基本概念

类模板和函数模板的定义和使用类似,我们已经进行了介绍。有时,有两个或多个类,其功能是相同的,仅仅是数据类型不同。

  • 类模板用于实现类所需数据的类型参数化
cpp\nclass Person\n{\npublic:\n\tPerson(NameType name, AgeType age)\n\t{\n\t\tthis->mName = name;\n\t\tthis->mAge = age;\n\t}\n\tvoid showPerson()\n\t{\n\t\tcout << \"name: \" << this->mName << \" age: \" << this->mAge << endl;\n\t}\npublic:\n\tNameType mName;\n\tAgeType mAge;\n};\n\nvoid test01()\n{\n\t//Person P1(\"德玛西亚\",18); // 类模板不能进行类型自动推导 \n\tPersonP1(\"德玛西亚\", 18);\n\tP1.showPerson();\n}]]>

5.7.2 类模板做函数参数

cpp\nclass Person{\npublic:\n\tPerson(NameType name, AgeType age){\n\t\tthis->mName = name;\n\t\tthis->mAge = age;\n\t}\n\tvoid PrintPerson(){\n\t\tcout << \"Name:\" << this->mName << \" Age:\" << this->mAge << endl;\n\t}\npublic:\n\tNameType mName;\n\tAgeType mAge;\n};\n\n//类模板做函数参数\nvoid DoBussiness(Person& p){\n\tp.mAge += 20;\n\tp.mName += \"_vip\";\n\tp.PrintPerson();\n}\n\nint main(){\n\n\tPerson p(\"John\", 30);\n\tDoBussiness(p);\n\n\tsystem(\"pause\");\n\treturn EXIT_SUCCESS;\n}]]>

5.7.3 类模板派生普通类

cpp\nclass MyClass{\npublic:\n\tMyClass(T property){\n\t\tthis->mProperty = property;\n\t}\npublic:\n\tT mProperty;\n};\n\n//子类实例化的时候需要具体化的父类,子类需要知道父类的具体类型是什么样的\n//这样c++编译器才能知道给子类分配多少内存\n\n//普通派生类\nclass SubClass : public MyClass{\npublic:\n\tSubClass(int b) : MyClass(20){\n\t\tthis->mB = b;\n\t}\npublic:\n\tint mB;\n};]]>

5.7.4 类模板派生类模板

cpp\nclass Base\n{\n\tT m;\n};\ntemplate\nclass Child2 : public Base //继承类模板的时候,必须要确定基类的大小\n{\npublic:\n\tT mParam;\n};\n\nvoid test02()\n{\n\tChild2 d2;\n}]]>

5.7.5 类模板类内实现

cpp\nclass Person\n{\npublic:\n\tPerson(NameType name, AgeType age)\n\t{\n\t\tthis->mName = name;\n\t\tthis->mAge = age;\n\t}\n\tvoid showPerson()\n\t{\n\t\tcout << \"name: \" << this->mName << \" age: \" << this->mAge << endl;\n\t}\npublic:\n\tNameType mName;\n\tAgeType mAge;\n};\n\nvoid test01()\n{\n\t//Person P1(\"德玛西亚\",18); // 类模板不能进行类型自动推导 \n\tPersonP1(\"德玛西亚\", 18);\n\tP1.showPerson();\n}]]>

5.7.6 类模板类外实现

cpp\n#include\nusing namespace std;\n\ntemplate\nclass Person{\npublic:\n\tPerson(T1 name, T2 age);\n\tvoid showPerson();\n\npublic:\n\tT1 mName;\n\tT2 mAge;\n};\n\n\n//类外实现\ntemplate\nPerson::Person(T1 name, T2 age){\n\tthis->mName = name;\n\tthis->mAge = age;\n}\n\n\ntemplate\nvoid Person::showPerson(){\n\tcout << \"Name:\" << this->mName << \" Age:\" << this->mAge << endl;\n}\n\nvoid test()\n{\n\tPerson p(\"Obama\", 20);\n\tp.showPerson();\n}\n\nint main(){\n\n\ttest();\n\n\tsystem(\"pause\");\n\treturn EXIT_SUCCESS;\n}]]>

5.7.7    模板类碰到友元函数

cpp\nusing namespace std;\n#include \n\ntemplate class Person;\n//告诉编译器这个函数模板是存在\ntemplate void PrintPerson2(Person& p);\n\n//友元函数在类内实现\ntemplate\nclass Person{\n\t//1. 友元函数在类内实现\n\tfriend void PrintPerson(Person& p){\n\t\tcout << \"Name:\" << p.mName << \" Age:\" << p.mAge << endl;\n\t}\n\n\t//2.友元函数类外实现\n\t//告诉编译器这个函数模板是存在\n\tfriend void PrintPerson2<>(Person& p);\n\n\t//3. 类模板碰到友元函数模板\n\ttemplate\n\tfriend void PrintPerson(Person& p);\n\npublic:\n\tPerson(T1 name, T2 age){\n\t\tthis->mName = name;\n\t\tthis->mAge = age;\n\t}\n\tvoid showPerson(){\n\t\tcout << \"Name:\" << this->mName << \" Age:\" << this->mAge << endl;\n\t}\nprivate:\n\tT1 mName;\n\tT2 mAge;\n};\n\nvoid test01()\n{\n\tPerson p(\"Jerry\", 20);\n\tPrintPerson(p);\n}\n\n\n// 类模板碰到友元函数\n//友元函数类外实现 加上<>空参数列表,告诉编译去匹配函数模板\ntemplate\nvoid PrintPerson2(Person& p)\n{\n\tcout << \"Name2:\" << p.mName << \" Age2:\" << p.mAge << endl;\n}\n\nvoid test02()\n{\n\tPerson p(\"Jerry\", 20);\n\tPrintPerson2(p); //不写可以编译通过,写了之后,会找PrintPerson2的普通函数调用,因为写了普通函数PrintPerson2的声明\t\n}\n\nint main(){\n\n\t//test01();\n\ttest02();\n\tsystem(\"pause\");\n\treturn EXIT_SUCCESS;\n}]]>

5.8         类模板的应用

设计一个数组模板类(MyArray),完成对不同类型元素的管理

cpp\nclass MyArray\n{\npublic:\n\texplicit MyArray(int capacity)\n\t{\n\t\tthis->m_Capacity = capacity;\n\t\tthis->m_Size = 0;\n\t\t// 如果T是对象,那么这个对象必须提供默认的构造函数\n\t\tpAddress = new T[this->m_Capacity];\n\t}\n\n\t//拷贝构造\n\tMyArray(const MyArray & arr)\n\t{\n\t\tthis->m_Capacity = arr.m_Capacity;\n\t\tthis->m_Size = arr.m_Size;\n\t\tthis->pAddress = new T[this->m_Capacity];\n\t\tfor (int i = 0; i < this->m_Size;i++)\n\t\t{\n\t\t\tthis->pAddress[i] = arr.pAddress[i];\n\t\t}\n\t}\n\n\t//重载[] 操作符 arr[0]\n\tT& operator [](int index)\n\t{\n\t\treturn this->pAddress[index]; \n\t}\n\t//尾插法\n\tvoid Push_back(const T & val)\n\t{\n\t\tif (this->m_Capacity == this->m_Size)\n\t\t{\n\t\t\treturn;\n\t\t}\n\t\tthis->pAddress[this->m_Size] = val;\n\t\tthis->m_Size++;\n\t}\n\tvoid Pop_back()\n\t{\n\t\tif (this->m_Size == 0)\n\t\t{\n\t\t\treturn;\n\t\t}\n\t\tthis->m_Size--;\n\t}\n\tint\tgetSize()\n\t{\n\t\treturn this->m_Size;\n\t}\n\t//析构\n\t~MyArray()\n\t{\n\t\tif (this->pAddress != NULL)\n\t\t{\n\t\t\tdelete[] this->pAddress;\n\t\t\tthis->pAddress = NULL;\n\t\t\tthis->m_Capacity = 0; \n\t\t\tthis->m_Size = 0;\n\t\t}\n\t}\n\nprivate:\n\tT * pAddress; //指向一个堆空间,这个空间存储真正的数据\n\tint m_Capacity; //容量\n\tint m_Size; // 大小\n};\n\n测试代码:\n\n\nclass Person{\npublic:\n\tPerson(){}\n\tPerson(string name, int age){\n\t\tthis->mName = name;\n\t\tthis->mAge = age;\n\t}\npublic:\n\tstring mName;\n\tint mAge;\n};\n\n\nvoid PrintMyArrayInt(MyArray& arr){\n\tfor (int i = 0; i < arr.getSize(); i++){\n\t\tcout << arr[i] << \" \";\n\t}\n\tcout << endl;\n}\n\nvoid PrintMyPerson(MyArray& personArr)\n{\n\tfor (int i = 0; i < personArr.getSize(); i++){\n\t\tcout << \"姓名:\" << personArr[i].mName << \" 年龄: \" << personArr[i].mAge << endl;\n\t}\n\t\n}\n\t\n\tMyArray myArrayInt(10);\n\tfor (int i = 0; i < 9; i++)\n\t{\n\t\tmyArrayInt.Push_back(i);\n\t}\n\tmyArrayInt.Push_back(100);\n\tPrintMyArrayInt(myArrayInt);\n\nMyArray myArrayPerson(10);\n\tPerson p1(\"德玛西亚\", 30);\n\tPerson p2(\"提莫\", 20);\n\tPerson p3(\"孙悟空\",18);\n\tPerson p4(\"赵信\", 15);\n\tPerson p5(\"赵云\", 24);\n\tmyArrayPerson.Push_back(p1);\n\tmyArrayPerson.Push_back(p2);\n\tmyArrayPerson.Push_back(p3);\n\tmyArrayPerson.Push_back(p4);\n\tmyArrayPerson.Push_back(p5);]]>




","representation":"storage","_expandable":{"content":"/rest/api/content/58954472"}},"_expandable":{"editor":"","view":"","export_view":"","styled_view":"","anonymous_export_view":""}},"extensions":{"position":"none"},"_links":{"webui":"/pages/viewpage.action?pageId=58954472","edit":"/pages/resumedraft.action?draftId=58954472","tinyui":"/x/6JKDAw","collection":"/rest/api/content","base":"https://wiki.suncaper.net","context":"","self":"https://wiki.suncaper.net/rest/api/content/58954472"},"_expandable":{"container":"/rest/api/space/2CT","metadata":"","operations":"","children":"/rest/api/content/58954472/child","restrictions":"/rest/api/content/58954472/restriction/byOperation","history":"/rest/api/content/58954472/history","ancestors":"","descendants":"/rest/api/content/58954472/descendant","space":"/rest/api/space/2CT"}}