【本文谢绝转载】
【泛型编程】
函数模板
为什么会有函数模板
现象:
函数的业务逻辑一样
函数的参数类型不一样
【最常用】函数模板 显式的调用
【不常用】类型推导
多个参数,参数定义了必须要用
函数模板,实现int类型数组,char字符串排序:
函数模板 与 普通函数的本质区别
函数模板 和 普通函数在一起 的调用型研究:
C++是如何支持函数模板机制的?
函数模板机制结论
类模板
类模板的定义
类模板做函数的参数
类模板的派生成普通类
模板类的派生成模板类
复数类,所有函数都写在类的内部,运算符重载热身
复数类,所有函数都写在类的内部, 类模板
【演示】滥用友元函数的后果--正常的代码准备
复数类,所有函数都写在类的内部(一个CPP中):问题抛出
当模板函数遇到友元函数,问题解决:
滥用友元函数的后果
【结论】:不需要友元函数,不要用友元函数
【结论】模板类的cpp文件 与 .h头文件分开写的时候,要把cpp文件也包含进来
当类模板中有static成员变量时;从类模板的实质分析,编译器你会自动为我们写成两个类
类模板 数组案例
类模板 结构体案例
作业:
-------------------------------------------------------------------------
【为什么会有函数模板】
[email protected]://990487026.blog.51cto.com~/c++$ cat main.cpp #include<iostream> using namespace std; void swap1(int &a,int &b) { a = a ^ b; b = a ^ b; a = a ^ b; } void swap2(char &a,char &b) { a = a ^ b; b = a ^ b; a = a ^ b; } int main() { int a = 10; int b = 20; swap1(a,b); cout <<a <<" "<< b << endl; char c = ‘A‘; char d = ‘B‘; swap2(c,d); cout <<c <<" "<< d << endl; return 0; } [email protected]://990487026.blog.51cto.com~/c++$ g++ -g -o run main.cpp && ./run 20 10 B A [email protected]://990487026.blog.51cto.com~/c++$
现象:
函数的业务逻辑一样
函数的参数类型不一样
【最常用】函数模板 显式的调用
[email protected]://990487026.blog.51cto.com~/c++$ cat main.cpp #include<iostream> using namespace std; //template //告诉编译器,我要开始泛型编程了 template <typename T> void myswap(T &a,T &b) { a = a ^ b; b = a ^ b; a = a ^ b; } int main() { int a = 10; int b = 20; cout <<a <<" "<< b << endl; myswap<int>(a,b); cout <<a <<" "<< b << endl; char c = ‘A‘; char d = ‘B‘; cout <<c <<" "<< d << endl; myswap<char>(c,d); cout <<c <<" "<< d << endl; return 0; } [email protected]://990487026.blog.51cto.com~/c++$ g++ -g -o run main.cpp && ./run 10 20 20 10 A B B A [email protected]://990487026.blog.51cto.com~/c++$
【不常用】类型推导
[email protected]://990487026.blog.51cto.com~/c++$ cat main.cpp #include<iostream> using namespace std; //template //告诉编译器,我要开始泛型编程了 template <typename T> void myswap(T &a,T &b) { a = a ^ b; b = a ^ b; a = a ^ b; } int main() { int a = 10; int b = 20; cout <<a <<" "<< b << endl; myswap(a,b); cout <<a <<" "<< b << endl; char c = ‘A‘; char d = ‘B‘; cout <<c <<" "<< d << endl; myswap(c,d); cout <<c <<" "<< d << endl; return 0; } [email protected]://990487026.blog.51cto.com~/c++$ g++ -g -o run main.cpp && ./run 10 20 20 10 A B B A [email protected]://990487026.blog.51cto.com~/c++$
多个参数,参数定义了必须要用
[email protected]://990487026.blog.51cto.com~/c++$ cat main.cpp #include<iostream> using namespace std; //template //告诉编译器,我要开始泛型编程了 template <typename T,typename str> void myswap(T &a,T &b,str s) { a = a ^ b; b = a ^ b; a = a ^ b; cout << s << endl; } int main() { int a = 10; int b = 20; cout <<a <<" "<< b << endl; myswap(a,b,"Hello"); cout <<a <<" "<< b << endl; char c = ‘A‘; char d = ‘B‘; cout <<c <<" "<< d << endl; myswap(c,d,"Linux"); cout <<c <<" "<< d << endl; return 0; } [email protected]://990487026.blog.51cto.com~/c++$ g++ -g -o run main.cpp && ./run 10 20 Hello 20 10 A B Linux B A [email protected]://990487026.blog.51cto.com~/c++$
函数模板,实现int类型数组,char字符串排序:
[email protected]://990487026.blog.51cto.com~/c++$ cat main.cpp #include<iostream> #include<stdio.h> #include<string.h> using namespace std; template <typename T1,typename T2> void select_sort(T1 array[],T2 len) { if(array == NULL) { return ; } for(int i=0;i<len-1;i++) { int min = i; for(int j=i+1;j<len;j++) { if(array[min] > array[j]) { min = j ; } } int tmp = array[i]; array[i] = array[min]; array[min]= tmp; } } template <typename T1,typename T2> void print_arr(T1 array[],T2 len) { if(array == NULL) { return ; } for(int i=0;i<len;i++) { cout << array[i] << " "; } cout << "\n"; } int main() { int arr[]= {2,1,4,3,6,5,8,7,0,9}; int n =sizeof(arr)/sizeof(int); select_sort<int ,int>(arr,n); print_arr<int,int>(arr,n); char str[]= "Hello,Linux!"; int m = strlen(str); select_sort<char ,int>(str,m); print_arr<char ,int>(str,m); return 0; } [email protected]://990487026.blog.51cto.com~/c++$ g++ -g -o run main.cpp && ./run 0 1 2 3 4 5 6 7 8 9 ! , H L e i l l n o u x [email protected]://990487026.blog.51cto.com~/c++$
函数模板 与 普通函数的本质区别
[email protected]://990487026.blog.51cto.com~/c++$ cat main.cpp #include<iostream> #include<stdio.h> #include<string.h> using namespace std; //swap 在 C++是个库函数 template <typename T> void my_swap(T &a,T &b) { cout <<a << " " <<b<< " 我是函数模板\n"; } void my_swap(int a,char b) { cout << a << " " << b << " 我是普通函数\n"; } int main() { int a = 69; char c = ‘A‘; my_swap(a,c); my_swap(c,a); //会隐式的自动转换 my_swap(a,a); //[本质的区别]函数模板,将严格按照类型匹配,不会自动转换 return 0; } [email protected]://990487026.blog.51cto.com~/c++$ g++ -g -o run main.cpp && ./run 69 A 我是普通函数 65 E 我是普通函数 69 69 我是函数模板 [email protected]://990487026.blog.51cto.com~/c++$
函数模板 和 普通函数在一起 的调用型研究:
【补充】在同一作用域,可以发生函数重载
[email protected]://990487026.blog.51cto.com~/c++$ cat main.cpp /* 1 函数模板可以像普通函数一样被重载 2 C++编译器优先考虑普通函数 3 如果函数模板可以产生一个更好的匹配,那么选择模板 4 可以通过空模板实参列表的语言限定编译器只通过模板匹配 */ #include<iostream> #include<stdio.h> #include<string.h> using namespace std; template <typename T> T Max(T a,T b) //不要引用类型 { cout <<a << " " <<b<< " 我是2号函数模板\n"; return a>b?a:b; } template <typename T> T Max(T a,T b,T c) { cout <<a << " " <<b<< " 我是3号函数模板,会调用两次max函数\n"; return Max(Max(a,b),c); } int Max(int a,int b) { cout << a << " " << b << " 我是普通函数\n"; return a>b?a:b; } int main() { int a = 1; int b = 2; Max(a,b); //C++编译器优先考虑普通函数 Max<>(a,b); //可以通过空模板实参列表的语言限定编译器只通过模板匹配 Max(3.5,4.4); //优先选择模板函数,因为模板函数能产生更好的匹配 Max(3.5,4.4,6.8);//只有函数模板能匹配 Max(‘A‘,66); //普通函数可以进行隐式转换 return 0; } [email protected]://990487026.blog.51cto.com~/c++$ g++ -g -o run main.cpp && ./run 1 2 我是普通函数 1 2 我是2号函数模板 3.5 4.4 我是2号函数模板 3.5 4.4 我是3号函数模板,会调用两次max函数 3.5 4.4 我是2号函数模板 4.4 6.8 我是2号函数模板 65 66 我是普通函数 [email protected]://990487026.blog.51cto.com~/c++$
C++是如何支持函数模板机制的?
C++: #include<iostream> #include<stdio.h> #include<string.h> using namespace std; //swap 在 C++是个库函数 template <typename T> void my_swap(T &a,T &b) { T c = 0; c = a; a = b; b = c; cout <<a << " " <<b<< " 我是函数模板\n"; } int main() { int a = 69; int b = 20; my_swap<int>(a,b); char c = ‘C‘; char d = ‘D‘; my_swap<char>(c,d); return 0; }
编译成汇编:
.file "main.c" .lcomm __ZStL8__ioinit,1,1 .def ___main; .scl 2; .type 32; .endef .text .globl _main .def _main; .scl 2; .type 32; .endef _main: LFB1025: .cfi_startproc .cfi_personality 0,___gxx_personality_v0 .cfi_lsda 0,LLSDA1025 pushl %ebp .cfi_def_cfa_offset 8 .cfi_offset 5, -8 movl %esp, %ebp .cfi_def_cfa_register 5 andl $-16, %esp subl $32, %esp call ___main movl $69, 28(%esp) movl $20, 24(%esp) leal 24(%esp), %eax movl %eax, 4(%esp) leal 28(%esp), %eax movl %eax, (%esp) LEHB0: call __Z7my_swapIiEvRT_S1_ movb $67, 23(%esp) movb $68, 22(%esp) leal 22(%esp), %eax movl %eax, 4(%esp) leal 23(%esp), %eax movl %eax, (%esp) call __Z7my_swapIcEvRT_S1_ LEHE0: movl $0, %eax jmp L5 L4: movl %eax, (%esp) LEHB1: call __Unwind_Resume LEHE1: L5: leave .cfi_restore 5 .cfi_def_cfa 4, 4 ret .cfi_endproc LFE1025: .def ___gxx_personality_v0; .scl 2; .type 32; .endef .section .gcc_except_table,"w" LLSDA1025: .byte 0xff .byte 0xff .byte 0x1 .uleb128 LLSDACSE1025-LLSDACSB1025 LLSDACSB1025: .uleb128 LEHB0-LFB1025 .uleb128 LEHE0-LEHB0 .uleb128 L4-LFB1025 .uleb128 0 .uleb128 LEHB1-LFB1025 .uleb128 LEHE1-LEHB1 .uleb128 0 .uleb128 0 LLSDACSE1025: .text .section .rdata,"dr" LC0: .ascii " \0" LC1: .ascii " \316\322\312\307\272\257\312\375\304\243\260\345\12\0" .section .text$_Z7my_swapIiEvRT_S1_,"x" .linkonce discard .globl __Z7my_swapIiEvRT_S1_ .def __Z7my_swapIiEvRT_S1_; .scl 2; .type 32; .endef __Z7my_swapIiEvRT_S1_: LFB1026: .cfi_startproc pushl %ebp .cfi_def_cfa_offset 8 .cfi_offset 5, -8 movl %esp, %ebp .cfi_def_cfa_register 5 pushl %ebx subl $36, %esp .cfi_offset 3, -12 movl $0, -12(%ebp) movl 8(%ebp), %eax movl (%eax), %eax movl %eax, -12(%ebp) movl 12(%ebp), %eax movl (%eax), %edx movl 8(%ebp), %eax movl %edx, (%eax) movl 12(%ebp), %eax movl -12(%ebp), %edx movl %edx, (%eax) movl 12(%ebp), %eax movl (%eax), %ebx movl 8(%ebp), %eax movl (%eax), %eax movl %eax, (%esp) movl $__ZSt4cout, %ecx call __ZNSolsEi subl $4, %esp movl $LC0, 4(%esp) movl %eax, (%esp) call __ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc movl %ebx, (%esp) movl %eax, %ecx call __ZNSolsEi subl $4, %esp movl $LC1, 4(%esp) movl %eax, (%esp) call __ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc movl -4(%ebp), %ebx leave .cfi_restore 5 .cfi_restore 3 .cfi_def_cfa 4, 4 ret .cfi_endproc LFE1026: .section .text$_Z7my_swapIcEvRT_S1_,"x" .linkonce discard .globl __Z7my_swapIcEvRT_S1_ .def __Z7my_swapIcEvRT_S1_; .scl 2; .type 32; .endef __Z7my_swapIcEvRT_S1_: LFB1027: .cfi_startproc pushl %ebp .cfi_def_cfa_offset 8 .cfi_offset 5, -8 movl %esp, %ebp .cfi_def_cfa_register 5 pushl %ebx subl $36, %esp .cfi_offset 3, -12 movb $0, -9(%ebp) movl 8(%ebp), %eax movzbl (%eax), %eax movb %al, -9(%ebp) movl 12(%ebp), %eax movzbl (%eax), %edx movl 8(%ebp), %eax movb %dl, (%eax) movl 12(%ebp), %eax movzbl -9(%ebp), %edx movb %dl, (%eax) movl 12(%ebp), %eax movzbl (%eax), %eax movsbl %al, %ebx movl 8(%ebp), %eax movzbl (%eax), %eax movsbl %al, %eax movl %eax, 4(%esp) movl $__ZSt4cout, (%esp) call __ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_c movl $LC0, 4(%esp) movl %eax, (%esp) call __ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc movl %ebx, 4(%esp) movl %eax, (%esp) call __ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_c movl $LC1, 4(%esp) movl %eax, (%esp) call __ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc addl $36, %esp popl %ebx .cfi_restore 3 popl %ebp .cfi_restore 5 .cfi_def_cfa 4, 4 ret .cfi_endproc LFE1027: .text .def ___tcf_0; .scl 3; .type 32; .endef ___tcf_0: LFB1033: .cfi_startproc pushl %ebp .cfi_def_cfa_offset 8 .cfi_offset 5, -8 movl %esp, %ebp .cfi_def_cfa_register 5 subl $8, %esp movl $__ZStL8__ioinit, %ecx call __ZNSt8ios_base4InitD1Ev leave .cfi_restore 5 .cfi_def_cfa 4, 4 ret .cfi_endproc LFE1033: .def __Z41__static_initialization_and_destruction_0ii; .scl 3; .type 32; .endef __Z41__static_initialization_and_destruction_0ii: LFB1032: .cfi_startproc pushl %ebp .cfi_def_cfa_offset 8 .cfi_offset 5, -8 movl %esp, %ebp .cfi_def_cfa_register 5 subl $24, %esp cmpl $1, 8(%ebp) jne L9 cmpl $65535, 12(%ebp) jne L9 movl $__ZStL8__ioinit, %ecx call __ZNSt8ios_base4InitC1Ev movl $___tcf_0, (%esp) call _atexit L9: leave .cfi_restore 5 .cfi_def_cfa 4, 4 ret .cfi_endproc LFE1032: .def __GLOBAL__sub_I_main; .scl 3; .type 32; .endef __GLOBAL__sub_I_main: LFB1034: .cfi_startproc pushl %ebp .cfi_def_cfa_offset 8 .cfi_offset 5, -8 movl %esp, %ebp .cfi_def_cfa_register 5 subl $24, %esp movl $65535, 4(%esp) movl $1, (%esp) call __Z41__static_initialization_and_destruction_0ii leave .cfi_restore 5 .cfi_def_cfa 4, 4 ret .cfi_endproc LFE1034: .section .ctors,"w" .align 4 .long __GLOBAL__sub_I_main .ident "GCC: (rev2, Built by MinGW-builds project) 4.8.0" .def __Unwind_Resume; .scl 2; .type 32; .endef .def __ZNSolsEi; .scl 2; .type 32; .endef .def __ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc; .scl 2; .type 32; .endef .def __ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_c; .scl 2; .type 32; .endef .def __ZNSt8ios_base4InitD1Ev; .scl 2; .type 32; .endef .def __ZNSt8ios_base4InitC1Ev; .scl 2; .type 32; .endef .def _atexit; .scl 2; .type 32; .endef
类模板:
类模板用于实现类所需数据的类型参数化
类模板在表示如数组、表、图等数据结构显得特别重要,
类模板的定义
类末班的使用
[email protected]://990487026.blog.51cto.com~/c++$ cat main.cpp #include<iostream> #include<stdio.h> #include<string.h> using namespace std; template <typename T> class A { public: A(T a) { this->a = a; } void printf() { cout << "a= "<<a <<endl; } private: T a; }; int main() { //类本身就是一个抽象的 A <int>a(11); //模板类是抽象类 必须指定具体的类型,告诉编译器给我分配多少内存 a.printf(); return 0; } [email protected]://990487026.blog.51cto.com~/c++$ g++ -g -o run main.cpp && ./run a= 11 [email protected]://990487026.blog.51cto.com~/c++$
类模板做函数的参数
[email protected]://990487026.blog.51cto.com~/c++$ cat main.cpp #include<iostream> #include<stdio.h> #include<string.h> using namespace std; template <typename T> class A { public: A(T a) { this->a = a; } void printf() { cout << "a= "<<a <<endl; } private: T a; }; //类模板做函数的参数 void useA(A<int> &a)//这里如果来个变量,会进行拷贝构造,引用就不会 { a.printf(); } int main() { A <int> a(11); //定义一个变量 A <int> b(22),c(33); useA(a); //来模板做函数的参数 useA(b); useA(c); return 0; } [email protected]://990487026.blog.51cto.com~/c++$ g++ -g -o run main.cpp && ./run a= 11 a= 22 a= 33 [email protected]://990487026.blog.51cto.com~/c++$
类模板的派生成普通类
[email protected]://990487026.blog.51cto.com~/c++$ cat main.cpp #include<iostream> #include<stdio.h> #include<string.h> using namespace std; template <typename T> class A { public: A(T a) { this->a = a; } void printf() { cout << "a= "<<a <<endl; } protected: T a; }; class B :public A<int> { public: B(int a,int b):A<int>(a) { this->b = b; } void printf() { cout << "a="<<a << " b=" << b << " \n"; } private: int b; }; int main() { B b(1,2); b.printf(); return 0; } [email protected]://990487026.blog.51cto.com~/c++$ g++ -g -o run main.cpp && ./run a=1 b=2
模板类的派生成模板类
[email protected]://990487026.blog.51cto.com~/c++$ cat main.cpp #include<iostream> #include<stdio.h> #include<string.h> using namespace std; template <typename T> class A { public: A(T a) { this->a = a; } void printf() { cout << "a= "<<a <<endl; } protected: T a; }; template <typename T> class C:public A<T> { public: C(T c,T a):A<T>(a) { this->c = a; } void printf() { cout << "c=" << c<< endl; } private: T c; }; int main() { C <int> c1(1,2); c1.printf(); C <char> c2(66,65); c2.printf(); return 0; } [email protected]://990487026.blog.51cto.com~/c++$ g++ -g -o run main.cpp && ./run c=2 c=A [email protected]://990487026.blog.51cto.com~/c++$
复数类,所有函数都写在类的内部,运算符重载热身
[email protected]://990487026.blog.51cto.com~/c++$ cat main.cpp #include<iostream> #include<stdio.h> #include<string.h> using namespace std; class Complex { friend ostream& operator<<(ostream &out,Complex &c); public: Complex(int a = 0,int b = 0) { this->a = a; this->b = b; } void printf() { cout << "a= "<<a <<" b="<<b <<endl; } Complex operator+(Complex &c2) { //cout << "this->a=" << this->a << " c2.a="<<c2.a<<endl; //cout << "this->b=" << this->b << " c2.b="<<c2.b<<endl; Complex tmp(this->a + c2.a,this->b+c2.b); return tmp; } protected: int a; int b; }; //为输出类的信息,实现操作符重载 ostream& operator<<(ostream &out,Complex &c) { cout <<"a="<<c.a<<" b="<<c.b<< " "; return out; } int main() { Complex c1(2,4); Complex c2(3,5); Complex c3 = c1 +c2; c3.printf(); cout << c3 << endl; //在得不到cout的源代码下,只能使用友元函数 return 0; } [email protected]://990487026.blog.51cto.com~/c++$ g++ -g -o run main.cpp && ./run a= 5 b=9 a=5 b=9 [email protected]://990487026.blog.51cto.com~/c++$
复数类,所有函数都写在类的内部, 类模板
[email protected]://990487026.blog.51cto.com~/c++$ cat main.cpp #include<iostream> #include<stdio.h> #include<string.h> using namespace std; /* 运算符重载的正规写法 重载<< >> 只能用友元函数,其他操作符都要写成成员函数,不要滥用友元函数 */ template <typename T> class Complex { //为输出类的信息,实现操作符重载 friend ostream& operator<<(ostream &out,Complex &c) { cout << "进入<<操作符重载 -> "; out <<"a="<<c.a<<" b="<<c.b<< " "; return out; } public: Complex (T a ,T b) { this->a = a; this->b = b; } void printf() { cout << "a= "<<a <<" b="<<b <<endl; } Complex operator+(Complex &c2) { //cout << "this->a=" << this->a << " c2.a="<<c2.a<<endl; //cout << "this->b=" << this->b << " c2.b="<<c2.b<<endl; Complex tmp(this->a + c2.a,this->b+c2.b); return tmp; } protected: T a; T b; }; /* ostream& operator<<(ostream &out,Complex &c) { cout <<"a="<<c.a<<" b="<<c.b<< " "; return out; } */ int main() { //要把模板类具体化才能分配内存 Complex<int> c1(2,4); Complex<int> c2(3,5); Complex<int> c3 = c1 +c2; c3.printf(); cout << c3 << endl; //在得不到cout的源代码下,只能使用友元函数 return 0; } [email protected]://990487026.blog.51cto.com~/c++$ g++ -g -o run main.cpp && ./run a= 5 b=9 进入<<操作符重载 -> a=5 b=9 [email protected]://990487026.blog.51cto.com~/c++$
【演示】滥用友元函数的后果--正常的代码准备
[email protected]://990487026.blog.51cto.com~/c++$ cat main.cpp #include<iostream> #include<stdio.h> #include<string.h> using namespace std; /* 运算符重载的正规写法 重载<< >> 只能用友元函数,其他操作符都要写成成员函数,不要滥用友元函数 */ template <typename T> class Complex { //为输出类的信息,实现操作符重载 friend ostream& operator<<(ostream &out,Complex &c) { cout << "进入<<操作符重载 -> "; out <<"a="<<c.a<<" b="<<c.b<< " "; return out; } friend Complex MySub(Complex& c1,Complex&c2) { cout << "进入MySub 函数 \n"; Complex tmp(c1.a - c2.a,c1.b - c2.b); return tmp; } public: Complex (T a ,T b) { this->a = a; this->b = b; } void printf() { cout << "a= "<<a <<" b="<<b <<endl; } Complex operator+(Complex &c2) { //cout << "this->a=" << this->a << " c2.a="<<c2.a<<endl; //cout << "this->b=" << this->b << " c2.b="<<c2.b<<endl; Complex tmp(this->a + c2.a,this->b+c2.b); return tmp; } protected: T a; T b; }; /* ostream& operator<<(ostream &out,Complex &c) { cout <<"a="<<c.a<<" b="<<c.b<< " "; return out; } */ int main() { //要把模板类具体化才能分配内存 Complex<int> c1(2,4); Complex<int> c2(3,5); Complex<int> c4 = MySub(c1,c2); cout << c4 << endl; return 0; } [email protected]://990487026.blog.51cto.com~/c++$ g++ -g -o run main.cpp && ./run 进入MySub 函数 进入<<操作符重载 -> a=-1 b=-1 [email protected]://990487026.blog.51cto.com~/c++$
复数类,所有函数都写在类的内部(一个CPP中):问题抛出
[email protected]://990487026.blog.51cto.com~/c++$ cat main.cpp #include<iostream> #include<stdio.h> #include<string.h> using namespace std; template <typename T> class Complex { //为输出类的信息,实现操作符重载 friend ostream& operator<<(ostream &out,Complex &c); friend Complex MySub(Complex& c1,Complex&c2) { cout << "进入MySub 函数 \n"; Complex tmp(c1.a - c2.a,c1.b - c2.b); return tmp; } public: Complex (T a ,T b); void printf(); Complex operator+(Complex &c2); protected: T a; T b; }; /* 问题的本质是:模板是两次编译, 第一次生成的函数头 和第二次生成的不一样 */ template <typename T> ostream& operator<<(ostream &out,Complex &c) { out <<"a="<<c.a<<" b="<<c.b<< " "; return out; } template <typename T> //函数的返回的类型要具体化 //函数的实参也要具体化 Complex<T> Complex<T>:: operator+(Complex<T> &c2) { cout << "进入外部 operator+ \n"; //Complex<T> tmp(this->a + c2.a,this->b+c2.b); Complex tmp(this->a + c2.a,this->b+c2.b); return tmp; } template <typename T> Complex<T>::Complex (T a ,T b) { cout << "进入外部Complex函数 a=" <<a<<" b="<< b<< endl; ; this->a = a; this->b = b; } template <typename T> void Complex<T>::printf() { cout << "进入外部printf函数 "; cout << "a="<<a <<" b="<<b <<endl; } int main() { Complex<int> c1(2,4); c1.printf(); Complex<int> c2(3,5); Complex<int> c3 = c1 + c2; Complex<int> c4 = MySub(c1,c2); cout << c4 << endl; return 0; } [email protected]://990487026.blog.51cto.com~/c++$ g++ -g -o run main.cpp && ./run main.cpp:10:52: warning: friend declaration ‘std::ostream& operator<<(std::ostream&, Complex<T>&)’ declares a non-template function [-Wnon-template-friend] friend ostream& operator<<(ostream &out,Complex &c); [email protected]://990487026.blog.51cto.com~/c++$
当模板函数遇到友元函数,问题解决:
[email protected]://990487026.blog.51cto.com~/c++$ cat haha.cpp #include<iostream> using namespace std; template <typename T> class Complex { friend std::ostream& operator << <T>(std::ostream& os, const Complex<T>& c); public: Complex(T a, T b); protected: T a; T b; }; template <typename T> std::ostream& operator <<(std::ostream& os, const Complex<T>& c) { os << "a="<<c.a <<" b= "<< c.b<<std::endl; return os; } template <typename T> Complex<T>::Complex(T a, T b) { cout << "进入外部Complex函数 a=" << a << " b=" << b << endl;; this->a = a; this->b = b; } int main() { Complex<int> c1(2, 4); cout << c1 << endl; return 0; } VS下编译,通过: C:\Users\chunli\Documents\c_c++\ConsoleApplication2\Debug>ConsoleApplication2.exe 进入外部Complex函数 a=2 b=4 a=2 b= 4 Linux GCC 编译 不通过: [email protected]://990487026.blog.51cto.com~/c++$ g++ -g -o run haha.cpp && ./run haha.cpp: In instantiation of ‘class Complex<int>’: haha.cpp:33:17: required from here haha.cpp:7:23: error: template-id ‘operator<< <int>’ for ‘std::ostream& operator<<(std::ostream&, const Complex<int>&)’ does not match any template declaration friend std::ostream& operator << <T>(std::ostream& os, const Complex<T>& c); ^ [email protected]://990487026.blog.51cto.com~/c++$
滥用友元函数的后果
#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 { friend std::ostream& operator << <T>(std::ostream& os, const Complex<T>& c); friend Complex<T> MySub<T>(Complex<T> &c1, Complex<T> &c2); public: Complex(T a, T b); protected: T a; T b; }; 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; } template <typename T> std::ostream& operator <<(std::ostream& os, const Complex<T>& c) { os << "a="<<c.a <<" b= "<< c.b<<std::endl; return os; } template <typename T> Complex<T>::Complex(T a, T b) { cout << "进入外部Complex函数 a=" << a << " b=" << b << endl;; this->a = a; this->b = b; } int main() { Complex<int> c1(2, 4); Complex<int> c2(1, 2); Complex<int> c3 = MySub<int>(c1,c2); cout << c3 << endl; return 0; } Linux GCC编译不通过 VS 编译OK C:\Users\chunli\Documents\c_c++\ConsoleApplication2\Debug>ConsoleApplication2.exe 进入外部Complex函数 a=2 b=4 进入外部Complex函数 a=1 b=2 进入外部Complex函数 a=1 b=2 a=1 b= 2
【结论】:不需要友元函数,不要用友元函数
【结论】模板类的cpp文件 与 .h头文件分开写的时候,要把cpp文件也包含进来
当类模板中有static成员变量时;
从类模板的实质分析,编译器你会自动为我们写成两个类
[email protected]://990487026.blog.51cto.com~/c++$ cat main.cpp #include<iostream> using namespace std; template <typename T> class A { public: static T a; }; template <typename T> T A<T>::a = 0; int main() { A<int> a1; a1.a++; A<int> a2; a2.a++; A<int> a3; a3.a++; cout << "a3="<<a3.a << "\n"; A<double> b1; b1.a += 3.5; A<double> b2; b2.a += 3.5; A<double> b3; b3.a += 3.5; cout << "b3="<<b3.a << "\n"; return 0; } [email protected]://990487026.blog.51cto.com~/c++$ g++ -g -o run main.cpp && ./run a3=3 b3=10.5 [email protected]://990487026.blog.51cto.com~/c++$
类模板 数组案例
1主函数:
#define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std; #include "MyVector.cpp" void main() { MyVector<int> myv1(10); for (int i = 0; i<myv1.getLen(); i++) { myv1[i] = i + 1; cout << myv1[i] << " "; } cout << endl; MyVector<int> myv2 = myv1; for (int i = 0; i<myv2.getLen(); i++) { cout << myv2[i] << " "; } cout << myv2 << endl; cout << "hello..." << endl; system("pause"); return; }
2头文件:MyVector
#include <iostream> using namespace std; template <typename T> class MyVector { friend ostream & operator<< <T>(ostream &out, const MyVector &obj); public: MyVector(int size = 0); //构造函数 MyVector(const MyVector &obj); // 拷贝构造函数 ~MyVector(); //析构函数 public: T& operator[] (int index); // a3 = a2 = a1; MyVector &operator=(const MyVector &obj); public: int getLen() { return m_len; } protected: T *m_space; int m_len; };
类函数实现文件 MyVector.cpp
#include <iostream> using namespace std; #include "MyVector.h" template <typename T> ostream & operator<<(ostream &out, const MyVector<T> &obj) { for (int i = 0; i<obj.m_len; i++) { out << obj.m_space[i] << " "; //out << t1; } out << endl; return out; } //MyVector<int> myv1(10); template <typename T> MyVector<T>::MyVector(int size) //构造函数 { m_space = new T[size]; m_len = size; } //MyVector<int> myv2 = myv1; template <typename T> MyVector<T>::MyVector(const MyVector &obj) // 拷贝构造函数 { //根据myv1的大小分配内存 m_len = obj.m_len; m_space = new T[m_len]; //copy数据 for (int i = 0; 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 = 0; } } template <typename T> T& MyVector<T>::operator[] (int index) { return m_space[index]; } // a3 = a2 = a1; 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 = 0; } //根据a1分配内存 m_len = obj.m_len; m_space = new T[m_len]; //copy数据 for (int i = 0; i<m_len; i++) { m_space[i] = obj[i]; } return *this; // a2 = a1; 返回给a2 的自身 }
VS环境编译运行: C:\Users\chunli\Documents\c_c++\ConsoleApplication2\Debug>ConsoleApplication2.exe 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 hello... 请按任意键继续. . .
类模板 结构体案例
类函数实现文件 MyVector.h 内容不变
类函数实现文件 MyVector.cpp内容不变
主函数修改为:
main.cpp
#define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std; #include "MyVector.cpp" class Teacher { public: Teacher() { age = 33; strcpy(name, ""); } Teacher(char *name, int age) { this->age = age; strcpy(this->name, name); } void printT() { cout << name << ", " << age << endl; } private: int age; char name[32]; }; void main() { Teacher t1("t1", 31), t2("t2", 32), t3("t3", 33), t4("t4", 34); MyVector<Teacher> tArray(4); tArray[0] = t1; tArray[1] = t2; tArray[2] = t3; tArray[3] = t4; for (int i = 0; i<4; i++) { Teacher tmp = tArray[i]; tmp.printT(); } } VS编译运行: C:\Users\chunli\Documents\c_c++\ConsoleApplication2\Debug>ConsoleApplication2.exe t1, 31 t2, 32 t3, 33 t4, 34 C:\Users\chunli\Documents\c_c++\ConsoleApplication2\Debug>
作业:
在上面这个程序的基础之上:
1 优化Teacher类, 属性变成 char *panme, 购置函数里面 分配内存
2 优化Teacher类,析构函数 释放panme指向的内存空间
3 优化Teacher类,避免浅拷贝 重载= 重写拷贝构造函数
4 优化Teacher类,在Teacher增加 <<
5 在模板数组类中,存int char Teacher Teacher*(指针类型)