C++_进阶之函数模板_类模板
第一部分
前言
c++提供了函数模板(function template.)所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具*定,用一个虚拟的类型来代表。这个通用函数就成为函数模板。凡是函数体相同的函数都可以用这个模板代替,不必定义多个函数,只需在模板中定义一次即可。在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现不同函
数的功能。
1)c++提供两种模板机制:函数模板和类模板
2)类属 - 类型参数化,又称参数模板
使得程序(算法)可以从逻辑上抽象,把被处理的对象(数据)类型作为参数传递。
总结:
1)模板把函数或类要处理的数据类型参数化,表现为参数的多态性,成为类属。
2)模板用于表达逻辑结构相同,但具体数据元素类型不同的数据对象的通用行为。
第二部分
1.函数模板
1.1为什么要有函数模板
需求:写n个函数,交换char类型、int类型、double类型变量的值。
案例:
#include <iostream> using namespace std; /* void myswap(int &a, int &b) { int t = a; a = b; b = t; } void myswap(char &a, char &b) { char t = a; a = b; b = t; } */ //template 关键字告诉C++编译器 我要开始泛型了.你不要随便报错 //数据类型T 参数化数据类型 template <typename T> void myswap(T &a, T &b) { T t; t = a; a = b; b = t; } void main() { //char a = 'c'; ; ; myswap(x, y); //自动数据类型 推导的方式 float a = 2.0; float b = 3.0; myswap(a, b); //自动数据类型 推导的方式 myswap<float>(a, b); //显示类型调用 cout<<"hello..."<<endl; system("pause"); return ; }
1.2函数模板语法
函数模板定义形式
template < 类型形式参数表 >
类型形式参数的形式为:
typename T1 , typename T2 , …… , typename Tn
或 class T1 , class T2 , …… , class Tn
函数模板调用
myswap<float>(a, b); //显示类型调用
myswap(a, b); //自动数据类型推导
1.3函数模板和模板函数
转自:函数模板和模板函数
1.4函数模板做函数参数
#include<iostream> using namespace std; /* 让你对int行数组 和字符数组排序 函数模板本质:类型参数化 */ template <typename T, typename T2> int mySort(T *array, int size) { if (array == NULL) { ; } ;i<size; i++) { ; j<size; j++) { if (array[i] > array[j]) { T temp; temp = array[i]; array[i] = array[j]; array[j] = temp; } } } ; } template <typename T, typename T2> int myPrintf(T *array, T2 size) { ; i<size; i++) { cout << array[i] << endl; } ; } void main21() { {//int类型 , ,, , , , , }; int size = sizeof(myarray) / sizeof(*myarray); mySort<int, int>(myarray, size); printf("排序之后:\n"); myPrintf<int, int>(myarray, size); } { //char类型 char buf[] = "ggggggghhhhhjjjdfffzzzzvvv"; int len = strlen(buf); mySort<char, int>(buf, len); myPrintf<char, int>(buf, len); } system("pause"); }
1.5函数模板遇上函数重载
函数模板和普通函数区别结论:
(1)函数模板不允许自动类型转化
(2)普通函数能够进行自动类型转换
函数模板和普通函数在一起,调用规则:
1 函数模板可以像普通函数一样被重载
2 C++编译器优先考虑普通函数
3 如果函数模板可以产生一个更好的匹配,那么选择模板
4 可以通过空模板实参列表的语法限定编译器只通过模板匹配
以下代码对上面文字进行说明:
案例1:
#include <iostream> using namespace std; template <typename T> void myswap(T &a, T &b) { T t; t = a; a = b; b = t; cout<<"myswap 模板函数do"<<endl; } void myswap(char &a, int &b) { int t; t = a; a = b; b = t; cout<<"myswap 普通函数do"<<endl; } void main() { char cData = 'a'; ; //myswap<int>(cData, iData); //结论 函数模板不提供隐式的数据类型转换 必须是严格的匹配 myswap(cData, iData); //myswap(iData, cData); cout<<"hello..."<<endl; system("pause"); return ; }
案例2:
#include<iostream> using namespace std; //让类型参数化---》方便程序员进行编码 //泛型编程 //template告诉C++编译器,开始泛型编程,不要随便报错 template <typename T> void myswap(T &a, T &b) { T c; c = a; a = b; b = c; cout << "我是模板函数-----》" << endl; } void myswap(int a, char c) { cout << "a:" << "c:" << c << endl; cout << "我是普通函数-----》" << endl; } void main31() { ; char c = 'z'; myswap(a,c);//当普通函数调用,可以进行隐式的类型转化 myswap(c, a); myswap(a, a);//调用函数模板,(本质:类型参数化) 将严格进行类型匹配,不会进行类型转化 }
案例3:
#include "iostream" using namespace std; int Max(int a, int b) { cout<<"int Max(int a, int b)"<<endl; return a > b ? a : b; } template<typename T> T Max(T a, T b) { cout<<"T Max(T a, T b)"<<endl; return a > b ? a : b; } template<typename T> T Max(T a, T b, T c) { cout<<"T Max(T a, T b, T c)"<<endl; return Max(Max(a, b), c); } void main() { ; ; cout<<Max(a, b)<<endl; //当函数模板和普通函数都符合调用时,优先选择普通函数 cout<<Max<>(a, b)<<endl; //若显示使用函数模板,则使用<> 类型列表 cout<<Max(3.0, 4.0)<<endl; //如果 函数模板产生更好的匹配 使用函数模板 cout<<Max(5.0, 6.0, 7.0)<<endl; //重载 cout<<Max()<<endl; //调用普通函数 可以隐式类型转换 system("pause"); return ; }
案例4:
/* 函数模板和普通函数区别结论: 函数模板不允许自动类型转化 普通函数能够进行自动类型转换 */ /*函数模板和普通函数在一起,调用规则: 1 函数模板可以像普通函数一样被重载 2 C++编译器优先考虑普通函数 3 如果函数模板可以产生一个更好的匹配,那么选择模板 4 可以通过空模板实参列表的语法限定编译器只通过模板匹配 */ #include "iostream" using namespace std; int Max(int a, int b) { cout << "int Max(int a, int b)" << endl; return a > b ? a : b; } template<typename T> T Max(T a, T b) { cout << "T Max(T a, T b)" << endl; return a > b ? a : b; } template<typename T> T Max(T a, T b, T c) { cout << "T Max(T a, T b, T c)" << endl; return Max(Max(a, b), c); } void main41() { ; ; cout << Max(a, b) << endl; //当函数模板和普通函数都符合调用时,优先选择普通函数 cout << Max<>(a, b) << endl; //若显示使用函数模板,则使用<> 类型列表 cout << Max(3.0, 4.0) << endl; //如果 函数模板产生更好的匹配 使用函数模板 cout << Max(5.0, 6.0, 7.0) << endl; //重载 cout << Max() << endl; //调用普通函数 可以隐式类型转换 system("pause"); return; }
1.6C++编译器模板机制剖析
思考:为什么函数模板可以和函数重载放在一块。C++编译器是如何提供函数模板机制的?
#include<iostream> using namespace std; //1.cpp //g++ -S 1.cpp -o 1.s 变成汇编语言 template <typename T> void myswap(T &a, T &b) { T c; c = a; a = b; b = c; cout << "hello------" << endl; } //函数模板的调用,显示类型调用,自动类型推倒 void main51() { { ; ; myswap<int>(x, y);//函数模板的显示类型调用 printf("x:%d y:%d \n", x, y); } { char a = 'a'; char b = 'b'; myswap<char>(a, b);//函数模板的显示类型调用 printf("x:%d y:%d \n", a, b); } } /* 原理: C++编译器会根据你的调用来产生函数,如果是int型的会产生int型的函数 ,如果是char会产生,char型的函数,如果有的话,就不会产生了。 C++编译器帮我们写了一个函数,经过两次编译,形成的 */ /* 函数模板机制结论 编译器并不是把函数模板处理成能够处理任意类的函数 编译器从函数模板通过具体类型产生不同的函数 编译器会对函数模板进行两次编译 在声明的地方对模板代码本身进行编译;在调用的地方对参数替换后的代码进行编译。 */
首先补充一些知识:
编译器编译原理:
什么是gcc
gcc(GNU C Compiler)编译器的作者是Richard Stallman,也是GNU项目的奠基者。 |
什么是gcc:gcc是GNU Compiler Collection的缩写。最初是作为C语言的编译器(GNU C Compiler),现在已经支持多种语言了,如C、C++、Java、Pascal、Ada、COBOL语言等。 |
gcc支持多种硬件平台,甚至对Don Knuth 设计的 MMIX 这类不常见的计算机都提供了完善的支持 |
gcc主要特征
1)gcc是一个可移植的编译器,支持多种硬件平台 2)gcc不仅仅是个本地编译器,它还能跨平台交叉编译。 3)gcc有多种语言前端,用于解析不同的语言。 4)gcc是按模块化设计的,可以加入新语言和新CPU架构的支持 5)gcc是*软件 |
gcc编译过程
预处理(Pre-Processing) 编译(Compiling) 汇编(Assembling) 链接(Linking) Gcc *.c –o 1exe (总的编译步骤) Gcc –E 1.c –o 1.i //宏定义 宏展开 Gcc –S 1.i –o 1.s Gcc –c 1.s –o 1.o Gcc 1.o –o 1exe 结论:gcc编译工具是一个工具链。。。。 |
hello程序是一个高级C语言程序,这种形式容易被人读懂。为了在系统上运行hello.c程序,每条C语句都必须转化为低级机器指令。然后将这些指令打包成可执行目标文件格式,并以二进制形式存储器于磁盘中。 |
gcc常用编译选项
选项 |
作用 |
-o |
产生目标(.i、.s、.o、可执行文件等) |
-c |
通知gcc取消链接步骤,即编译源码并在最后生成目标文件 |
-E |
只运行C预编译器 |
-S |
告诉编译器产生汇编语言文件后停止编译,产生的汇编语言文件扩展名为.s |
-Wall |
使gcc对源文件的代码有问题的地方发出警告 |
-Idir |
将dir目录加入搜索头文件的目录路径 |
-Ldir |
将dir目录加入搜索库的目录路径 |
-llib |
链接lib库 |
-g |
在目标文件中嵌入调试信息,以便gdb之类的调试程序调试 |
练习
gcc -E hello.c -o hello.i(预处理) gcc -S hello.i -o hello.s(编译) gcc -c hello.s -o hello.o(汇编) gcc hello.o -o hello(链接) 以上四个步骤,可合成一个步骤 gcc hello.c -o hello(直接编译链接成可执行目标文件) gcc -c hello.c或gcc -c hello.c -o hello.o(编译生成可重定位目标文件) |
建议初学都加这个选项。下面这个例子如果不加-Wall选项编译器不报任何错误,但是得到的结果却不是预期的。 #include <stdio.h> int main(void) { printf("2+1 is %f", 3); return 0; } |
Gcc编译多个.c |
hello_1.h hello_1.c main.c 一次性编译 gcc hello_1.c main.c –o newhello 独立编译 gcc -Wall -c main.c -o main.o gcc -Wall -c hello_1.c -o hello_fn.o gcc -Wall main.o hello_1.o -o newhello |
模板函数反汇编观察
命令:g++ -S 7.cpp -o 7.s
汇编语言:略过
.file "7.cpp" .text .def __ZL6printfPKcz; .scl ; .type ; .endef __ZL6printfPKcz: LFB264: .cfi_startproc pushl %ebp .cfi_def_cfa_offset .cfi_offset , - movl %esp, %ebp .cfi_def_cfa_register pushl %ebx subl $, %esp .cfi_offset , - leal (%ebp), %eax movl %eax, -(%ebp) movl -(%ebp), %eax movl %eax, (%esp) movl (%ebp), %eax movl %eax, (%esp) call ___mingw_vprintf movl %eax, %ebx movl %ebx, %eax addl $, %esp popl %ebx .cfi_restore popl %ebp .cfi_restore .cfi_def_cfa , ret .cfi_endproc LFE264: .lcomm __ZStL8__ioinit,, .def ___main; .scl ; .type ; .endef .section .rdata,"dr" LC0: .ascii "a:%d b:%d \12\0" LC1: .ascii "c1:%c c2:%c \12\0" LC2: .ascii "pause\0" .text .globl _main .def _main; .scl ; .type ; .endef _main: LFB1023: .cfi_startproc .cfi_personality ,___gxx_personality_v0 .cfi_lsda ,LLSDA1023 pushl %ebp .cfi_def_cfa_offset .cfi_offset , - movl %esp, %ebp .cfi_def_cfa_register andl $-, %esp subl $, %esp call ___main movl $, (%esp) movl $, (%esp) movb $, (%esp) movb $, (%esp) leal (%esp), %eax movl %eax, (%esp) leal (%esp), %eax movl %eax, (%esp) call __Z6myswapIiEvRT_S1_ //66 ===>126 movl (%esp), %edx movl (%esp), %eax movl %edx, (%esp) movl %eax, (%esp) movl $LC0, (%esp) call __ZL6printfPKcz leal (%esp), %eax movl %eax, (%esp) leal (%esp), %eax movl %eax, (%esp) call __Z6myswapIcEvRT_S1_ //77 ===>155 movzbl (%esp), %eax movsbl %al, %edx movzbl (%esp), %eax movsbl %al, %eax movl %edx, (%esp) movl %eax, (%esp) movl $LC1, (%esp) call __ZL6printfPKcz movl $LC2, (%esp) LEHB0: call _system LEHE0: movl $, %eax jmp L7 L6: movl %eax, (%esp) LEHB1: call __Unwind_Resume LEHE1: L7: leave .cfi_restore .cfi_def_cfa , ret .cfi_endproc LFE1023: .def ___gxx_personality_v0; .scl ; .type ; .endef .section .gcc_except_table,"w" LLSDA1023: .byte 0xff .byte 0xff .byte 0x1 .uleb128 LLSDACSE1023-LLSDACSB1023 LLSDACSB1023: .uleb128 LEHB0-LFB1023 .uleb128 LEHE0-LEHB0 .uleb128 L6-LFB1023 .uleb128 .uleb128 LEHB1-LFB1023 .uleb128 LEHE1-LEHB1 .uleb128 .uleb128 LLSDACSE1023: .text .section .text$_Z6myswapIiEvRT_S1_,"x" .linkonce discard .globl __Z6myswapIiEvRT_S1_ .def __Z6myswapIiEvRT_S1_; .scl ; .type ; .endef __Z6myswapIiEvRT_S1_: //126 LFB1024: .cfi_startproc pushl %ebp .cfi_def_cfa_offset .cfi_offset , - movl %esp, %ebp .cfi_def_cfa_register subl $, %esp movl (%ebp), %eax movl (%eax), %eax movl %eax, -(%ebp) movl (%ebp), %eax movl (%eax), %edx movl (%ebp), %eax movl %edx, (%eax) movl (%ebp), %eax movl -(%ebp), %edx movl %edx, (%eax) leave .cfi_restore .cfi_def_cfa , ret .cfi_endproc LFE1024: .section .text$_Z6myswapIcEvRT_S1_,"x" .linkonce discard .globl __Z6myswapIcEvRT_S1_ .def __Z6myswapIcEvRT_S1_; .scl ; .type ; .endef __Z6myswapIcEvRT_S1_: //155 LFB1025: .cfi_startproc pushl %ebp .cfi_def_cfa_offset .cfi_offset , - movl %esp, %ebp .cfi_def_cfa_register subl $, %esp movl (%ebp), %eax movzbl (%eax), %eax movb %al, -(%ebp) movl (%ebp), %eax movzbl (%eax), %edx movl (%ebp), %eax movb %dl, (%eax) movl (%ebp), %eax movzbl -(%ebp), %edx movb %dl, (%eax) leave .cfi_restore .cfi_def_cfa , ret .cfi_endproc LFE1025: .text .def ___tcf_0; .scl ; .type ; .endef ___tcf_0: LFB1027: .cfi_startproc pushl %ebp .cfi_def_cfa_offset .cfi_offset , - movl %esp, %ebp .cfi_def_cfa_register subl $, %esp movl $__ZStL8__ioinit, %ecx call __ZNSt8ios_base4InitD1Ev leave .cfi_restore .cfi_def_cfa , ret .cfi_endproc LFE1027: .def __Z41__static_initialization_and_destruction_0ii; .scl ; .type ; .endef __Z41__static_initialization_and_destruction_0ii: LFB1026: .cfi_startproc pushl %ebp .cfi_def_cfa_offset .cfi_offset , - movl %esp, %ebp .cfi_def_cfa_register subl $, %esp cmpl $, (%ebp) jne L11 cmpl $, (%ebp) jne L11 movl $__ZStL8__ioinit, %ecx call __ZNSt8ios_base4InitC1Ev movl $___tcf_0, (%esp) call _atexit L11: leave .cfi_restore .cfi_def_cfa , ret .cfi_endproc LFE1026: .def __GLOBAL__sub_I_main; .scl ; .type ; .endef __GLOBAL__sub_I_main: LFB1028: .cfi_startproc pushl %ebp .cfi_def_cfa_offset .cfi_offset , - movl %esp, %ebp .cfi_def_cfa_register subl $, %esp movl $, (%esp) movl $, (%esp) call __Z41__static_initialization_and_destruction_0ii leave .cfi_restore .cfi_def_cfa , ret .cfi_endproc LFE1028: .section .ctors,"w" .align .long __GLOBAL__sub_I_main .ident "GCC: (rev2, Built by MinGW-builds project) 4.8.0" .def ___mingw_vprintf; .scl ; .type ; .endef .def _system; .scl ; .type ; .endef .def __Unwind_Resume; .scl ; .type ; .endef .def __ZNSt8ios_base4InitD1Ev; .scl ; .type ; .endef .def __ZNSt8ios_base4InitC1Ev; .scl ; .type ; .endef .def _atexit; .scl ; .type ; .endef
1.7函数模板机制结论
编译器并不是把函数模板处理成能够处理任意类的函数
编译器从函数模板通过具体类型产生不同的函数
编译器会对函数模板进行两次编译
在声明的地方对模板代码本身进行编译;在调用的地方对参数替换后的代码进行编译。
2.类模板
2.1为什么需要类模板
类模板与函数模板的定义和使用类似,我们已经进行了介绍。 有时,有两个或多个类,其功能是相同的,仅仅是数据类型不同,如下面语句声明了一个类:
- 类模板用于实现类所需数据的类型参数化
- 类模板在表示如数组、表、图等数据结构显得特别重要,
这些数据结构的表示和算法不受所包含的元素类型的影响
2.2单个类模板语法
//类的类型参数化 抽象的类 //单个类模板 template<typename T> class A { public: A(T t) { this->t = t; } T &getT() { return t; } protected: public: T t; }; void main() { //模板了中如果使用了构造函数,则遵守以前的类的构造函数的调用规则 A<); a.getT(); printAA(a); return ; }
2.3继承中的类模板语法
案例1:
//结论: 子类从模板类继承的时候,需要让编译器知道 父类的数据类型具体是什么(数据类型的本质:固定大小内存块的别名)A<int> // class B : public A<int> { public: B(int i) : A<int>(i) { } void printB() { cout<<"A:"<<t<<endl; } protected: private: }; //模板与上继承 //怎么样从基类继承 //若基类只有一个带参数的构造函数,子类是如何启动父类的构造函数 void pintBB(B &b) { b.printB(); } void printAA(A<int> &a) //类模板做函数参数 { // a.getT(); } void main() { A<); //模板了中如果使用了构造函数,则遵守以前的类的构造函数的调用规则 a.getT(); printAA(a); B b(); b.printB(); cout<<"hello..."<<endl; system("pause"); return ; }
案例2:
#include<iostream> using namespace std; //A编程模板类--类型参数化 /* 类模板的定义 类模板的使用 类模板做函数参数 */ template <typename T> class A { public: A(T a = ) { this->a = a; } public: void printA() { cout << "a:" << a << endl; } protected: T a; private: }; //从模板类派生时,需要具体化模板类,C++编译器需要知道父类的数据类型是什么样子的 //要知道父类所占的内存多少 class B :public A<int> { public: B(, ):A<int>(a) { this->b = b; } void printB() { cout << "a:" << a << "b:" << b << endl; } protected: private: int b; }; //从模板类派生模板类 template <typename T> class C :public A<T> { public: C(T c,T a) : A<T>(a) { this->c = c; } void printC() { cout << "c:" << c << endl; } protected: T c; private: }; void main() { //B b1(1, 2); //b1.printB(); C<,); c1.printC(); }
2.4类模板的基础语法
#include<iostream> using namespace std; //A编程模板类--类型参数化 /* 类模板的定义 类模板的使用 类模板做函数参数 */ template <typename T> class A { public: A(T a = ) { this->a = a; } public: void printA() { cout << "a:" << a << endl; } protected: private: T a; }; //参数 C++编译器具体的类 void UseA(A<int> &a) { a.printA(); } void main() { //模板类本身就是抽象的,具体的类,具体的变量 A<),a2(),a3();//模板类是抽象的, 需要类型具体化 //a1.printA(); UseA(a1); UseA(a2); UseA(a3); }
2.5类模板语法知识体系梳理
1.所有的类模板函数写在类的内部
代码:
复数类:
#include<iostream> using namespace std; template <typename T> class Complex { public: friend Complex MySub(Complex &c1, Complex &c2) { Complex tmp(c1.a-c2.a, c1.b-c2.b); return tmp; } friend ostream & operator<< (ostream &out, Complex &c3) { out << c3.a << "+" << c3.b <<"i"<< endl; return out; } Complex(T a, T b) { this->a = a; this->b = b; } Complex operator+(Complex &c2) { Complex tmp(a + c2.a, b + c2.b); return tmp; } void printCom() { cout << "a:" << a << " b:" << b << endl; } protected: private: T a; T b; }; /* 重载运算符的正规写法: 重载左移<< 右移>> 只能用友元函数,其他的运算符重载都要用成员函数,不要滥用友元函数 */ //ostream & operator<< (ostream &out, Complex &c3) //{ // out<< "a:" << c3.a << " b:" << c3.b << endl; // return out; //} void main() { Complex<,); Complex<, ); Complex<int> c3 = c1 + c2;//重载加号运算符 c3.printCom(); //重载左移运算符 cout << c3 << endl; { Complex<int> c4 = MySub(c1 , c2); cout << c4 << endl; } system("pause"); }
2.所有的类模板函数写在类的外部,在一个cpp中
注意:
//构造函数 没有问题 //普通函数 没有问题 //友元函数:用友元函数重载 << >> // friend ostream& operator<< <T> (ostream &out, Complex<T> &c3) ; //友元函数:友元函数不是实现函数重载(非 << >>) //1)需要在类前增加 类的前置声明 函数的前置声明
template<typename T> class Complex; template<typename T> Complex<T> mySub(Complex<T> &c1, Complex<T> &c2); //2)类的内部声明 必须写成: friend Complex<T> mySub <T> (Complex<T> &c1, Complex<T> &c2); //3)友元函数实现 必须写成:
template<typename T> Complex<T> mySub(Complex<T> &c1, Complex<T> &c2) { Complex<T> tmp(c1.a - c2.a, c1.b-c2.b); return tmp; } //4)友元函数调用 必须写成 Complex<int> c4 = mySub<int>(c1, c2); cout<<c4;
结论:友元函数只用来进行 左移 友移操作符重载。
复数类:
代码:
#include<iostream> using namespace std; template<typename T> class Complex; template<typename T> Complex<T> mySub(Complex<T> &c1, Complex<T> &c2); template <typename T> class Complex { public: friend Complex<T> mySub <T>(Complex<T> &c1, Complex<T> &c2); friend ostream & operator<< <T>(ostream &out, Complex &c3); Complex(T a, T b); void printCom(); Complex operator+(Complex &c2); Complex operator-(Complex &c2); protected: private: T a; T b; }; //构造函数的实现,写在了外部 template <typename T> Complex<T>::Complex(T a, T b) { this->a = a; this->b = b; } template <typename T> void Complex<T>::printCom() { cout << "a:" << a << " b:" << b << endl; } //成员函数实现加号运算符重载 template <typename T> Complex<T> Complex<T>::operator+(Complex<T> &c2) { Complex tmp(a + c2.a, b + c2.b); return tmp; } template <typename T> Complex<T> Complex<T>::operator-(Complex<T> &c2) { Complex(a-c2.a,a-c2.b); return tmp; } //友元函数实现<<左移运算符重载 /* 严重性 代码 说明 项目 文件 行 禁止显示状态 错误 C2768 “operator <<”: 非法使用显式模板参数 泛型编程课堂操练 错误的本质:两次编译的函数头,第一次编译的函数头,和第二次编译的函数有不一样 */ template <typename T> ostream & operator<< (ostream &out, Complex<T> &c3)//不加T { out << c3.a << "+" << c3.b << "i" << endl; return out; } ////////////////////////////////////////////////// template <typename T> Complex<T> mySub(Complex<T> &c1, Complex<T> &c2) { Complex<T> tmp(c1.a - c2.a, c1.b - c2.b); return tmp; } void main() { Complex<, ); Complex<, ); Complex<int> c3 = c1 + c2;//重载加号运算符 c3.printCom(); //重载左移运算符 cout << c3 << endl; { Complex<int> c4 = mySub<int>(c1, c2); cout << c4 << endl; } system("pause"); }
所有的类模板函数写在类的外部,在不同的.h和.cpp中
也就是类模板函数说明和类模板实现分开
//类模板函数
构造函数
普通成员函数
友元函数
用友元函数重载<<>>;
用友元函数重载非<< >>
//要包含.cpp
demo_09complex.cpp
#include"demo_09complex.h" #include<iostream> using namespace std; template <typename T> Complex<T>::Complex(T a, T b) { this->a = a; this->b = b; } template <typename T> void Complex<T>::printCom() { cout << "a:" << a << " b:" << b << endl; } //成员函数实现加号运算符重载 template <typename T> Complex<T> Complex<T>::operator+(Complex<T> &c2) { Complex tmp(a + c2.a, b + c2.b); return tmp; } //template <typename T> //Complex<T> Complex<T>::operator-(Complex<T> &c2) //{ // Complex(a - c2.a, a - c2.b); // return tmp; //} template <typename T> ostream & operator<< (ostream &out, Complex<T> &c3)//不加T { out << c3.a << "+" << c3.b << "i" << endl; return out; } ////////////////////////////////////////////////// //template <typename T> //Complex<T> mySub(Complex<T> &c1, Complex<T> &c2) //{ // Complex<T> tmp(c1.a - c2.a, c1.b - c2.b); // return tmp; //}
demo_09complex.h
#pragma once #include<iostream> using namespace std; template <typename T> class Complex { public: //friend Complex<T> mySub <T>(Complex<T> &c1, Complex<T> &c2); friend ostream & operator<< <T>(ostream &out, Complex &c3); Complex(T a, T b); void printCom(); Complex operator+(Complex &c2); //Complex operator-(Complex &c2); protected: private: T a; T b; };
demo_09complex_text.cpp
#include"demo_09complex.h" #include"demo_09complex.cpp" #include<iostream> using namespace std; void main() { Complex<, ); Complex<, ); Complex<int> c3 = c1 + c2;//重载加号运算符 c3.printCom(); //重载左移运算符 cout << c3 << endl; /*{ Complex<int> c4 = mySub<int>(c1, c2); cout << c4 << endl; }*/ system("pause"); }
2.5总结
归纳以上的介绍,可以这样声明和使用类模板:
1) 先写出一个实际的类。由于其语义明确,含义清楚,一般不会出错。
2) 将此类中准备改变的类型名(如int要改变为float或char)改用一个自己指定的虚拟类型名(如上例中的numtype)。
3) 在类声明前面加入一行,格式为:
template <class 虚拟类型参数>
如:
template <class numtype> //注意本行末尾无分号
class Compare
{…}; //类体
4) 用类模板定义对象时用以下形式:
类模板名<实际类型名> 对象名;
类模板名<实际类型名> 对象名(实参表列);
如:
Compare<int> cmp;
Compare<int> cmp(3,7);
5) 如果在类模板外定义成员函数,应写成类模板形式:
template <class 虚拟类型参数>
函数类型 类模板名<虚拟类型参数>::成员函数名(函数形参表列) {…}
关于类模板的几点说明:
1) 类模板的类型参数可以有一个或多个,每个类型前面都必须加class,如:
template <class T1,class T2>
class someclass
{…};
在定义对象时分别代入实际的类型名,如:
someclass<int,double> obj;
2) 和使用类一样,使用类模板时要注意其作用域,只能在其有效作用域内用它定义对象。
3) 模板可以有层次,一个类模板可以作为基类,派生出派生模板类。
2.6类模板中的static关键字
- 从类模板实例化的每个模板类有自己的类模板数据成员,该模板类的所有对象共享一个static数据成员
- 和非模板类的static数据成员一样,模板类的static数据成员也应该在文件范围定义和初始化
- 每个模板类有自己的类模板的static数据成员副本
#include<iostream> using namespace std; template <typename T> class AA { public: static T m_a; protected: private: }; template <typename T> T AA<T>::m_a =; void main() { AA<int> a1, a2, a3; a1.m_a = ; a2.m_a++; a3.m_a++; cout << AA<int>::m_a << endl; AA<char> b1, b2, b3; b1.m_a = 'a'; b2.m_a++; b3.m_a++; cout << AA<char>::m_a << endl; //m_a是每个类型的类,去使用,手工写两个类 int char system("pause"); }
案例2:以下来自:C++类模板遇上static关键字
#include <iostream> using namespace std; template<typename T> class Obj{ public: static T m_t; }; template<typename T> T Obj<T>::m_t = ; int main04(){ Obj<int> i1,i2,i3; i1.m_t = ; i2.m_t++; i3.m_t++; cout << Obj<int>::m_t<<endl; Obj<float> f1,f2,f3; f1.m_t = ; f2.m_t++; f3.m_t++; cout << Obj<float>::m_t<<endl; Obj<char> c1,c2,c3; c1.m_t = 'a'; c2.m_t++; c3.m_t++; cout << Obj<char>::m_t<<endl; }
当类模板中出现static修饰的静态类成员的时候,我们只要按照正常理解就可以了。static的作用是将类的成员修饰成静态的,所谓的静态类成员就是指类的成员为类级别的,不需要实例化对象就可以使用,而且类的所有对象都共享同一个静态类成员,因为类静态成员是属于类而不是对象。那么,类模板的实现机制是通过二次编译原理实现的。c++编译器并不是在第一个编译类模板的时候就把所有可能出现的类型都分别编译出对应的类(太多组合了),而是在第一个编译的时候编译一部分,遇到泛型不会替换成具体的类型(这个时候编译器还不知道具体的类型),而是在第二次编译的时候再将泛型替换成具体的类型(这个时候编译器知道了具体的类型了)。由于类模板的二次编译原理再加上static关键字修饰的成员,当它们在一起的时候实际上一个类模板会被编译成多个具体类型的类,所以,不同类型的类模板对应的static成员也是不同的(不同的类),但相同类型的类模板的static成员是共享的(同一个类)。
相关连接:
C++--类模板中的static关键字 - CSDN博客
2.7类模板在项目开发中的应用
小结
- 模板是C++类型参数化的多态工具。C++提供函数模板和类模板。
- 模板定义以模板说明开始。类属参数必须在模板定义中至少出现一次。
- 同一个类属参数可以用于多个模板。
- 类属参数可用于函数的参数类型、返回类型和声明函数中的变量。
- 模板由编译器根据实际数据类型实例化,生成可执行代码。实例化的函数。
模板称为模板函数;实例化的类模板称为模板类。
- 函数模板可以用多种方式重载。
- 类模板可以在类层次中使用 。
训练题
1) 请设计一个数组模板类( MyVector ),完成对int、char、Teacher类型元素的管理。
需求
设计:
类模板 构造函数 拷贝构造函数 << [] 重载=操作符
a2=a1
实现
2) 请仔细思考:
a) 如果数组模板类中的元素是Teacher元素时,需要Teacher类做什么工作
b) 如果数组模板类中的元素是Teacher元素时,Teacher类含有指针属性哪?
class Teacher { friend ostream & operator<<(ostream &out, const Teacher &obj); public: Teacher(char *name, int age) { this->age = age; strcpy(this->name, name); } Teacher() { ; strcpy(this->name, ""); } private: int age; ]; }; class Teacher { friend ostream & operator<<(ostream &out, const Teacher &obj); public: Teacher(char *name, int age) { this->age = age; strcpy(this->name, name); } Teacher() { ; strcpy(this->name, ""); } private: int age; char *pname; };
结论1: 如果把Teacher放入到MyVector数组中,并且Teacher类的属性含有指针,就是出现深拷贝和浅拷贝的问题。
结论2:需要Teacher封装的函数有:
1) 重写拷贝构造函数
2) 重载等号操作符
3) 重载左移操作符。
理论提高:
所有容器提供的都是值(value)语意,而非引用(reference)语意。容器执行插入元素的操作时,内部实施拷贝动作。所以STL容器内存储的元素必须能够被拷贝(必须提供拷贝构造函数)。
3) 请从数组模板中进行派生
//演示从模板类 派生 一般类 #include "MyVector.cpp" class MyArray01 : public MyVector<double> { public: MyArray01(int len) : MyVector<double>(len) { ; } protected: private: }; //演示从模板类 派生 模板类 //BoundArray template <typename T> class MyArray02 : public MyVector<T> { public: MyArray02(int len) : MyVector<double>(len) { ; } protected: private: }; 测试案例: //演示 从模板类 继承 模板类 void main() { MyArray02<); dArray2[] = 3.15; } //演示 从模板类 继承 一般类 void main11() { MyArray01 d_array(); ; i<d_array.getLen(); i++) { d_array[i] = 3.15; } ; i<d_array.getLen(); i++) { cout << d_array[i] << " "; } cout<<"hello..."<<endl; system("pause"); return ; }
作业:
封装你自己的数组类;设计被存储的元素为类对象;
思考:类对象的类,应该实现的功能。
//1 优化Teacher类, 属性变成 char *panme, 构造函数里面 分配内存
//2 优化Teacher类,析构函数 释放panme指向的内存空间
//3 优化Teacher类,避免浅拷贝 重载= 重写拷贝构造函数
//4 优化Teacher类,在Teacher增加 <<
//5 在模板数组类中,存int char Teacher Teacher*(指针类型)
zuoye.h
#pragma once template <typename T> class MyVector { //friend ostream & operator<< <T>(ostream &out, const MyVector &obj); public: MyVector();//构造函数 MyVector(const MyVector &obj);//copy构造函数 ~MyVector(); public: T& operator [](int index); MyVector &operator=(const MyVector &obj); int getLen() { return m_len; } protected: private: T *m_space; int m_len; };
zuoye_test12.cpp
#include"zuoye.h" #include"zuoye12.cpp" #include<iostream> using namespace std; class Teacher { public: Teacher() { age = ; m_p = ]; strcpy(m_p, " "); } Teacher(char *name, int age) { this->age = age; m_p = ]; strcpy(this->m_p, name); } Teacher(const Teacher &obj) { m_p = ]; strcpy(this->m_p, obj.m_p); age = obj.age; } ~Teacher() { if (m_p!=NULL) { delete[] m_p; m_p = NULL; } } void printT() { cout << m_p << ", " << age; } public: //重载<< == friend ostream & operator<<(ostream &out,Teacher &t); Teacher & operator=(const Teacher &obj) { if (m_p!=NULL) { delete[] m_p; m_p = NULL; age = ; } m_p = ]; age = obj.age; strcpy(this->m_p, obj.m_p); return *this; } protected: private: int age; //char name[32]; char *m_p; }; ostream & operator<<(ostream &out, Teacher &t) { out << t.m_p << ", " << t.age << endl; return out; } void main() { Teacher t1(), t2(); MyVector<Teacher *> Tarray(); Tarray[] = &t1; Tarray[] = &t2; ; i < ; i++) { Teacher *tmp = Tarray[i]; tmp->printT(); } system("pause"); } void main123() { Teacher t1(), t2(); MyVector<Teacher> Tarray(); Tarray[] = t1; Tarray[] = t2; ; i < ; i++) { Teacher tmp = Tarray[i]; tmp.printT(); } system("pause"); } void main112() { MyVector<); myv1[] = 'a'; myv1[] = 'b'; myv1[] = 'c'; myv1[] = 'd'; myv1[] = 'e'; //cout << myv1; MyVector<int> myv2 = myv1; } void main111() { MyVector<); ; i < myv1.getLen(); i++) { myv1[i] = i + ; cout << myv1[i] << " "; } MyVector<int> myv2 = myv1; ; i < myv2.getLen(); i++) { myv2[i] = i + ; cout << myv2[i] << " "; } //cout << myv2 << endl;//重载左移运算符 system("pause"); }
zuoye12.cpp
#include"zuoye.h" #include<iostream> using namespace std; template <typename T> ostream & operator<<(ostream &out, const MyVector<T> &obj) { ; i<obj.m_len; i++) { out << obj.m_space[i] << " "; } out << endl; return out; } //构造函数 template <typename T> MyVector<T>::MyVector() { m_space = new T[size]; m_len = size; } //MyVector<int> myv2 = myv1; template <typename T> MyVector<T>::MyVector(const MyVector &obj) { //根据大小分配内存 m_len = obj.m_len; m_space = new T[m_len]; //copy数据 ; i<m_len; i++) { m_space[i] = obj.m_space[i]; } } template <typename T> MyVector<T>::~MyVector() { if (m_space != NULL) { delete[] m_space; m_space = NULL; m_len = ; } } template <typename T> T& MyVector<T>::operator [](int index) { return m_space[index]; } template <typename T> MyVector<T> & MyVector<T>::operator=(const MyVector<T> &obj) { //先把a2的内存释放掉 if (m_space != NULL) { delete[] m_space; m_space = NULL; m_len = ; } //根据a1分配内存 m_len = obj.m_len; m_space = new T[m_len]; //copy数据 ; i<m_len; i += ) { m_space[i] = obj.m_space[i]; } return *this;//a2= a1 返回a2的自身 }
参考一些资料,加上一些见解,如果有雷同,纯属巧合。