C++_进阶之函数模板_类模板
C++_进阶之函数模板_类模板
第一部分
前言
c++提供了函数模板(function template.)所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具*定,用一个虚拟的类型来代表。这个通用函数就成为函数模板。凡是函数体相同的函数都可以用这个模板代替,不必定义多个函数,只需在模板中定义一次即可。在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现不同函
数的功能。
1)c++提供两种模板机制:函数模板和类模板
2)类属 - 类型参数化,又称参数模板
使得程序(算法)可以从逻辑上抽象,把被处理的对象(数据)类型作为参数传递。
总结:
1)模板把函数或类要处理的数据类型参数化,表现为参数的多态性,成为类属。
2)模板用于表达逻辑结构相同,但具体数据元素类型不同的数据对象的通用行为。
第二部分
1.函数模板
1.1为什么要有函数模板
需求:写n个函数,交换char类型、int类型、double类型变量的值。
案例:
1 #include <iostream> 2 using namespace std; 3 /* 4 void myswap(int &a, int &b) 5 { 6 int t = a; 7 a = b; 8 b = t; 9 } 10 void myswap(char &a, char &b) 11 { 12 char t = a; 13 a = b; 14 b = t; 15 } 16 */ 17 //template 关键字告诉C++编译器 我要开始泛型了.你不要随便报错 18 //数据类型T 参数化数据类型 19 template <typename T> 20 void myswap(T &a, T &b) 21 { 22 T t; 23 t = a; 24 a = b; 25 b = t; 26 } 27 void main() 28 { 29 //char a = 'c'; 30 31 int x = 1; 32 int y = 2; 33 myswap(x, y); //自动数据类型 推导的方式 34 35 float a = 2.0; 36 float b = 3.0; 37 38 myswap(a, b); //自动数据类型 推导的方式 39 myswap<float>(a, b); //显示类型调用 40 41 cout<<"hello..."<<endl; 42 system("pause"); 43 return ; 44 }
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函数模板做函数参数
1 #include<iostream> 2 using namespace std; 3 4 /* 5 让你对int行数组 和字符数组排序 6 函数模板本质:类型参数化 7 */ 8 template <typename T, typename T2> 9 int mySort(T *array, int size) 10 { 11 if (array == NULL) 12 { 13 return -1; 14 } 15 for (T2 i =0 ;i<size; i++) 16 { 17 for (T2 j =i+1; j<size; j++) 18 { 19 if (array[i] > array[j]) 20 { 21 T temp; 22 temp = array[i]; 23 array[i] = array[j]; 24 array[j] = temp; 25 } 26 } 27 } 28 return 0; 29 } 30 template <typename T, typename T2> 31 int myPrintf(T *array, T2 size) 32 { 33 for (T2 i = 0; i<size; i++) 34 { 35 cout << array[i] << endl; 36 } 37 return 0; 38 } 39 void main21() 40 { 41 {//int类型 42 int myarray[] = { 22, 33,44, 43, 56, 2, 44, 76 }; 43 int size = sizeof(myarray) / sizeof(*myarray); 44 mySort<int, int>(myarray, size); 45 46 printf("排序之后:\n"); 47 myPrintf<int, int>(myarray, size); 48 } 49 50 { 51 //char类型 52 char buf[] = "ggggggghhhhhjjjdfffzzzzvvv"; 53 int len = strlen(buf); 54 mySort<char, int>(buf, len); 55 myPrintf<char, int>(buf, len); 56 } 57 58 system("pause"); 59 }
1.5函数模板遇上函数重载
函数模板和普通函数区别结论:
(1)函数模板不允许自动类型转化
(2)普通函数能够进行自动类型转换
函数模板和普通函数在一起,调用规则:
1 函数模板可以像普通函数一样被重载
2 C++编译器优先考虑普通函数
3 如果函数模板可以产生一个更好的匹配,那么选择模板
4 可以通过空模板实参列表的语法限定编译器只通过模板匹配
以下代码对上面文字进行说明:
案例1:
1 #include <iostream> 2 using namespace std; 3 4 template <typename T> 5 void myswap(T &a, T &b) 6 { 7 T t; 8 t = a; 9 a = b; 10 b = t; 11 cout<<"myswap 模板函数do"<<endl; 12 } 13 void myswap(char &a, int &b) 14 { 15 int t; 16 t = a; 17 a = b; 18 b = t; 19 cout<<"myswap 普通函数do"<<endl; 20 } 21 22 void main() 23 { 24 char cData = 'a'; 25 int iData = 2; 26 27 //myswap<int>(cData, iData); //结论 函数模板不提供隐式的数据类型转换 必须是严格的匹配 28 29 myswap(cData, iData); 30 //myswap(iData, cData); 31 32 cout<<"hello..."<<endl; 33 system("pause"); 34 return ; 35 }
案例2:
1 #include<iostream> 2 using namespace std; 3 4 //让类型参数化---》方便程序员进行编码 5 //泛型编程 6 //template告诉C++编译器,开始泛型编程,不要随便报错 7 template <typename T> 8 void myswap(T &a, T &b) 9 { 10 T c; 11 c = a; 12 a = b; 13 b = c; 14 cout << "我是模板函数-----》" << endl; 15 } 16 void myswap(int a, char c) 17 { 18 cout << "a:" << "c:" << c << endl; 19 cout << "我是普通函数-----》" << endl; 20 } 21 void main31() 22 { 23 int a = 10; 24 char c = 'z'; 25 myswap(a,c);//当普通函数调用,可以进行隐式的类型转化 26 27 myswap(c, a); 28 29 myswap(a, a);//调用函数模板,(本质:类型参数化) 将严格进行类型匹配,不会进行类型转化 30 31 }
案例3:
1 #include "iostream" 2 using namespace std; 3 4 int Max(int a, int b) 5 { 6 cout<<"int Max(int a, int b)"<<endl; 7 return a > b ? a : b; 8 } 9 10 template<typename T> 11 T Max(T a, T b) 12 { 13 cout<<"T Max(T a, T b)"<<endl; 14 return a > b ? a : b; 15 } 16 17 template<typename T> 18 T Max(T a, T b, T c) 19 { 20 cout<<"T Max(T a, T b, T c)"<<endl; 21 return Max(Max(a, b), c); 22 } 23 24 void main() 25 { 26 int a = 1; 27 int b = 2; 28 29 cout<<Max(a, b)<<endl; //当函数模板和普通函数都符合调用时,优先选择普通函数 30 cout<<Max<>(a, b)<<endl; //若显示使用函数模板,则使用<> 类型列表 31 32 cout<<Max(3.0, 4.0)<<endl; //如果 函数模板产生更好的匹配 使用函数模板 33 34 cout<<Max(5.0, 6.0, 7.0)<<endl; //重载 35 36 cout<<Max('a', 100)<<endl; //调用普通函数 可以隐式类型转换 37 system("pause"); 38 return ; 39 }
案例4:
1 /* 2 函数模板和普通函数区别结论: 3 函数模板不允许自动类型转化 4 普通函数能够进行自动类型转换 5 */ 6 7 /*函数模板和普通函数在一起,调用规则: 8 1 函数模板可以像普通函数一样被重载 9 2 C++编译器优先考虑普通函数 10 3 如果函数模板可以产生一个更好的匹配,那么选择模板 11 4 可以通过空模板实参列表的语法限定编译器只通过模板匹配 12 */ 13 #include "iostream" 14 using namespace std; 15 16 17 int Max(int a, int b) 18 { 19 cout << "int Max(int a, int b)" << endl; 20 return a > b ? a : b; 21 } 22 23 template<typename T> 24 T Max(T a, T b) 25 { 26 cout << "T Max(T a, T b)" << endl; 27 return a > b ? a : b; 28 } 29 30 template<typename T> 31 T Max(T a, T b, T c) 32 { 33 cout << "T Max(T a, T b, T c)" << endl; 34 return Max(Max(a, b), c); 35 } 36 37 38 void main41() 39 { 40 int a = 1; 41 int b = 2; 42 43 cout << Max(a, b) << endl; //当函数模板和普通函数都符合调用时,优先选择普通函数 44 cout << Max<>(a, b) << endl; //若显示使用函数模板,则使用<> 类型列表 45 46 cout << Max(3.0, 4.0) << endl; //如果 函数模板产生更好的匹配 使用函数模板 47 48 cout << Max(5.0, 6.0, 7.0) << endl; //重载 49 50 cout << Max('a', 100) << endl; //调用普通函数 可以隐式类型转换 51 system("pause"); 52 return; 53 }
1.6C++编译器模板机制剖析
思考:为什么函数模板可以和函数重载放在一块。C++编译器是如何提供函数模板机制的?
1 #include<iostream> 2 using namespace std; 3 4 //1.cpp 5 6 //g++ -S 1.cpp -o 1.s 变成汇编语言 7 template <typename T> 8 void myswap(T &a, T &b) 9 { 10 T c; 11 c = a; 12 a = b; 13 b = c; 14 cout << "hello------" << endl; 15 } 16 //函数模板的调用,显示类型调用,自动类型推倒 17 void main51() 18 { 19 { 20 int x = 10; 21 int y = 20; 22 myswap<int>(x, y);//函数模板的显示类型调用 23 24 25 printf("x:%d y:%d \n", x, y); 26 } 27 { 28 char a = 'a'; 29 char b = 'b'; 30 myswap<char>(a, b);//函数模板的显示类型调用 31 32 printf("x:%d y:%d \n", a, b); 33 } 34 35 } 36 /* 37 原理: 38 C++编译器会根据你的调用来产生函数,如果是int型的会产生int型的函数 39 ,如果是char会产生,char型的函数,如果有的话,就不会产生了。 40 41 C++编译器帮我们写了一个函数,经过两次编译,形成的 42 */ 43 /* 44 函数模板机制结论 45 编译器并不是把函数模板处理成能够处理任意类的函数 46 编译器从函数模板通过具体类型产生不同的函数 47 编译器会对函数模板进行两次编译 48 在声明的地方对模板代码本身进行编译;在调用的地方对参数替换后的代码进行编译。 49 */
首先补充一些知识:
编译器编译原理:
什么是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
汇编语言:略过
1 .file "7.cpp" 2 .text 3 .def __ZL6printfPKcz; .scl 3; .type 32; .endef 4 __ZL6printfPKcz: 5 LFB264: 6 .cfi_startproc 7 pushl %ebp 8 .cfi_def_cfa_offset 8 9 .cfi_offset 5, -8 10 movl %esp, %ebp 11 .cfi_def_cfa_register 5 12 pushl %ebx 13 subl $36, %esp 14 .cfi_offset 3, -12 15 leal 12(%ebp), %eax 16 movl %eax, -12(%ebp) 17 movl -12(%ebp), %eax 18 movl %eax, 4(%esp) 19 movl 8(%ebp), %eax 20 movl %eax, (%esp) 21 call ___mingw_vprintf 22 movl %eax, %ebx 23 movl %ebx, %eax 24 addl $36, %esp 25 popl %ebx 26 .cfi_restore 3 27 popl %ebp 28 .cfi_restore 5 29 .cfi_def_cfa 4, 4 30 ret 31 .cfi_endproc 32 LFE264: 33 .lcomm __ZStL8__ioinit,1,1 34 .def ___main; .scl 2; .type 32; .endef 35 .section .rdata,"dr" 36 LC0: 37 .ascii "a:%d b:%d \12\0" 38 LC1: 39 .ascii "c1:%c c2:%c \12\0" 40 LC2: 41 .ascii "pause\0" 42 .text 43 .globl _main 44 .def _main; .scl 2; .type 32; .endef 45 _main: 46 LFB1023: 47 .cfi_startproc 48 .cfi_personality 0,___gxx_personality_v0 49 .cfi_lsda 0,LLSDA1023 50 pushl %ebp 51 .cfi_def_cfa_offset 8 52 .cfi_offset 5, -8 53 movl %esp, %ebp 54 .cfi_def_cfa_register 5 55 andl $-16, %esp 56 subl $32, %esp 57 call ___main 58 movl $0, 28(%esp) 59 movl $10, 24(%esp) 60 movb $97, 23(%esp) 61 movb $98, 22(%esp) 62 leal 24(%esp), %eax 63 movl %eax, 4(%esp) 64 leal 28(%esp), %eax 65 movl %eax, (%esp) 66 call __Z6myswapIiEvRT_S1_ //66 ===>126 67 movl 24(%esp), %edx 68 movl 28(%esp), %eax 69 movl %edx, 8(%esp) 70 movl %eax, 4(%esp) 71 movl $LC0, (%esp) 72 call __ZL6printfPKcz 73 leal 22(%esp), %eax 74 movl %eax, 4(%esp) 75 leal 23(%esp), %eax 76 movl %eax, (%esp) 77 call __Z6myswapIcEvRT_S1_ //77 ===>155 78 movzbl 22(%esp), %eax 79 movsbl %al, %edx 80 movzbl 23(%esp), %eax 81 movsbl %al, %eax 82 movl %edx, 8(%esp) 83 movl %eax, 4(%esp) 84 movl $LC1, (%esp) 85 call __ZL6printfPKcz 86 movl $LC2, (%esp) 87 LEHB0: 88 call _system 89 LEHE0: 90 movl $0, %eax 91 jmp L7 92 L6: 93 movl %eax, (%esp) 94 LEHB1: 95 call __Unwind_Resume 96 LEHE1: 97 L7: 98 leave 99 .cfi_restore 5 100 .cfi_def_cfa 4, 4 101 ret 102 .cfi_endproc 103 LFE1023: 104 .def ___gxx_personality_v0; .scl 2; .type 32; .endef 105 .section .gcc_except_table,"w" 106 LLSDA1023: 107 .byte 0xff 108 .byte 0xff 109 .byte 0x1 110 .uleb128 LLSDACSE1023-LLSDACSB1023 111 LLSDACSB1023: 112 .uleb128 LEHB0-LFB1023 113 .uleb128 LEHE0-LEHB0 114 .uleb128 L6-LFB1023 115 .uleb128 0 116 .uleb128 LEHB1-LFB1023 117 .uleb128 LEHE1-LEHB1 118 .uleb128 0 119 .uleb128 0 120 LLSDACSE1023: 121 .text 122 .section .text$_Z6myswapIiEvRT_S1_,"x" 123 .linkonce discard 124 .globl __Z6myswapIiEvRT_S1_ 125 .def __Z6myswapIiEvRT_S1_; .scl 2; .type 32; .endef 126 __Z6myswapIiEvRT_S1_: //126 127 LFB1024: 128 .cfi_startproc 129 pushl %ebp 130 .cfi_def_cfa_offset 8 131 .cfi_offset 5, -8 132 movl %esp, %ebp 133 .cfi_def_cfa_register 5 134 subl $16, %esp 135 movl 8(%ebp), %eax 136 movl (%eax), %eax 137 movl %eax, -4(%ebp) 138 movl 12(%ebp), %eax 139 movl (%eax), %edx 140 movl 8(%ebp), %eax 141 movl %edx, (%eax) 142 movl 12(%ebp), %eax 143 movl -4(%ebp), %edx 144 movl %edx, (%eax) 145 leave 146 .cfi_restore 5 147 .cfi_def_cfa 4, 4 148 ret 149 .cfi_endproc 150 LFE1024: 151 .section .text$_Z6myswapIcEvRT_S1_,"x" 152 .linkonce discard 153 .globl __Z6myswapIcEvRT_S1_ 154 .def __Z6myswapIcEvRT_S1_; .scl 2; .type 32; .endef 155 __Z6myswapIcEvRT_S1_: //155 156 LFB1025: 157 .cfi_startproc 158 pushl %ebp 159 .cfi_def_cfa_offset 8 160 .cfi_offset 5, -8 161 movl %esp, %ebp 162 .cfi_def_cfa_register 5 163 subl $16, %esp 164 movl 8(%ebp), %eax 165 movzbl (%eax), %eax 166 movb %al, -1(%ebp) 167 movl 12(%ebp), %eax 168 movzbl (%eax), %edx 169 movl 8(%ebp), %eax 170 movb %dl, (%eax) 171 movl 12(%ebp), %eax 172 movzbl -1(%ebp), %edx 173 movb %dl, (%eax) 174 leave 175 .cfi_restore 5 176 .cfi_def_cfa 4, 4 177 ret 178 .cfi_endproc 179 LFE1025: 180 .text 181 .def ___tcf_0; .scl 3; .type 32; .endef 182 ___tcf_0: 183 LFB1027: 184 .cfi_startproc 185 pushl %ebp 186 .cfi_def_cfa_offset 8 187 .cfi_offset 5, -8 188 movl %esp, %ebp 189 .cfi_def_cfa_register 5 190 subl $8, %esp 191 movl $__ZStL8__ioinit, %ecx 192 call __ZNSt8ios_base4InitD1Ev 193 leave 194 .cfi_restore 5 195 .cfi_def_cfa 4, 4 196 ret 197 .cfi_endproc 198 LFE1027: 199 .def __Z41__static_initialization_and_destruction_0ii; .scl 3; .type 32; .endef 200 __Z41__static_initialization_and_destruction_0ii: 201 LFB1026: 202 .cfi_startproc 203 pushl %ebp 204 .cfi_def_cfa_offset 8 205 .cfi_offset 5, -8 206 movl %esp, %ebp 207 .cfi_def_cfa_register 5 208 subl $24, %esp 209 cmpl $1, 8(%ebp) 210 jne L11 211 cmpl $65535, 12(%ebp) 212 jne L11 213 movl $__ZStL8__ioinit, %ecx 214 call __ZNSt8ios_base4InitC1Ev 215 movl $___tcf_0, (%esp) 216 call _atexit 217 L11: 218 leave 219 .cfi_restore 5 220 .cfi_def_cfa 4, 4 221 ret 222 .cfi_endproc 223 LFE1026: 224 .def __GLOBAL__sub_I_main; .scl 3; .type 32; .endef 225 __GLOBAL__sub_I_main: 226 LFB1028: 227 .cfi_startproc 228 pushl %ebp 229 .cfi_def_cfa_offset 8 230 .cfi_offset 5, -8 231 movl %esp, %ebp 232 .cfi_def_cfa_register 5 233 subl $24, %esp 234 movl $65535, 4(%esp) 235 movl $1, (%esp) 236 call __Z41__static_initialization_and_destruction_0ii 237 leave 238 .cfi_restore 5 239 .cfi_def_cfa 4, 4 240 ret 241 .cfi_endproc 242 LFE1028: 243 .section .ctors,"w" 244 .align 4 245 .long __GLOBAL__sub_I_main 246 .ident "GCC: (rev2, Built by MinGW-builds project) 4.8.0" 247 .def ___mingw_vprintf; .scl 2; .type 32; .endef 248 .def _system; .scl 2; .type 32; .endef 249 .def __Unwind_Resume; .scl 2; .type 32; .endef 250 .def __ZNSt8ios_base4InitD1Ev; .scl 2; .type 32; .endef 251 .def __ZNSt8ios_base4InitC1Ev; .scl 2; .type 32; .endef 252 .def _atexit; .scl 2; .type 32; .endef
1.7函数模板机制结论
编译器并不是把函数模板处理成能够处理任意类的函数
编译器从函数模板通过具体类型产生不同的函数
编译器会对函数模板进行两次编译
在声明的地方对模板代码本身进行编译;在调用的地方对参数替换后的代码进行编译。
2.类模板
2.1为什么需要类模板
类模板与函数模板的定义和使用类似,我们已经进行了介绍。 有时,有两个或多个类,其功能是相同的,仅仅是数据类型不同,如下面语句声明了一个类:
- 类模板用于实现类所需数据的类型参数化
- 类模板在表示如数组、表、图等数据结构显得特别重要,
这些数据结构的表示和算法不受所包含的元素类型的影响
2.2单个类模板语法
1 //类的类型参数化 抽象的类 2 //单个类模板 3 template<typename T> 4 class A 5 { 6 public: 7 A(T t) 8 { 9 this->t = t; 10 } 11 12 T &getT() 13 { 14 return t; 15 } 16 protected: 17 public: 18 T t; 19 }; 20 void main() 21 { 22 //模板了中如果使用了构造函数,则遵守以前的类的构造函数的调用规则 23 A<int> a(100); 24 a.getT(); 25 printAA(a); 26 return ; 27 }
2.3继承中的类模板语法
案例1:
1 //结论: 子类从模板类继承的时候,需要让编译器知道 父类的数据类型具体是什么(数据类型的本质:固定大小内存块的别名)A<int> 2 // 3 class B : public A<int> 4 { 5 public: 6 B(int i) : A<int>(i) 7 { 8 9 } 10 void printB() 11 { 12 cout<<"A:"<<t<<endl; 13 } 14 protected: 15 private: 16 }; 17 18 //模板与上继承 19 //怎么样从基类继承 20 //若基类只有一个带参数的构造函数,子类是如何启动父类的构造函数 21 void pintBB(B &b) 22 { 23 b.printB(); 24 } 25 void printAA(A<int> &a) //类模板做函数参数 26 { 27 // 28 a.getT(); 29 } 30 31 void main() 32 { 33 A<int> a(100); //模板了中如果使用了构造函数,则遵守以前的类的构造函数的调用规则 34 a.getT(); 35 printAA(a); 36 37 B b(10); 38 b.printB(); 39 40 41 cout<<"hello..."<<endl; 42 system("pause"); 43 return ; 44 }
案例2:
1 #include<iostream> 2 using namespace std; 3 //A编程模板类--类型参数化 4 /* 5 类模板的定义 类模板的使用 类模板做函数参数 6 */ 7 template <typename T> 8 class A 9 { 10 public: 11 A(T a = 0) 12 { 13 this->a = a; 14 } 15 public: 16 void printA() 17 { 18 cout << "a:" << a << endl; 19 } 20 protected: 21 T a; 22 private: 23 24 }; 25 //从模板类派生时,需要具体化模板类,C++编译器需要知道父类的数据类型是什么样子的 26 //要知道父类所占的内存多少 27 class B :public A<int> 28 { 29 public: 30 B(int a =10, int b =20):A<int>(a) 31 { 32 this->b = b; 33 } 34 void printB() 35 { 36 cout << "a:" << a << "b:" << b << endl; 37 } 38 protected: 39 private: 40 int b; 41 42 }; 43 //从模板类派生模板类 44 template <typename T> 45 class C :public A<T> 46 { 47 48 public: 49 C(T c,T a) : A<T>(a) 50 { 51 this->c = c; 52 } 53 void printC() 54 { 55 cout << "c:" << c << endl; 56 } 57 protected: 58 T c; 59 private: 60 61 }; 62 63 void main() 64 { 65 //B b1(1, 2); 66 //b1.printB(); 67 C<int> c1(1,2); 68 c1.printC(); 69 }
2.4类模板的基础语法
1 #include<iostream> 2 using namespace std; 3 //A编程模板类--类型参数化 4 /* 5 类模板的定义 类模板的使用 类模板做函数参数 6 */ 7 template <typename T> 8 class A 9 { 10 public: 11 A(T a = 0) 12 { 13 this->a = a; 14 } 15 public: 16 void printA() 17 { 18 cout << "a:" << a << endl; 19 } 20 protected: 21 private: 22 T a; 23 }; 24 //参数 C++编译器具体的类 25 void UseA(A<int> &a) 26 { 27 a.printA(); 28 } 29 void main() 30 { 31 //模板类本身就是抽象的,具体的类,具体的变量 32 A<int> a1(11),a2(22),a3(33);//模板类是抽象的, 需要类型具体化 33 //a1.printA(); 34 35 UseA(a1); 36 UseA(a2); 37 UseA(a3); 38 }
2.5类模板语法知识体系梳理
1.所有的类模板函数写在类的内部
代码:
复数类:
1 #include<iostream> 2 using namespace std; 3 template <typename T> 4 class Complex 5 { 6 public: 7 friend Complex MySub(Complex &c1, Complex &c2) 8 { 9 Complex tmp(c1.a-c2.a, c1.b-c2.b); 10 return tmp; 11 } 12 13 friend ostream & operator<< (ostream &out, Complex &c3) 14 { 15 out << c3.a << "+" << c3.b <<"i"<< endl; 16 return out; 17 } 18 Complex(T a, T b) 19 { 20 this->a = a; 21 this->b = b; 22 } 23 Complex operator+(Complex &c2) 24 { 25 Complex tmp(a + c2.a, b + c2.b); 26 return tmp; 27 } 28 void printCom() 29 { 30 cout << "a:" << a << " b:" << b << endl; 31 } 32 protected: 33 private: 34 T a; 35 T b; 36 }; 37 38 /* 39 重载运算符的正规写法: 40 重载左移<< 右移>> 只能用友元函数,其他的运算符重载都要用成员函数,不要滥用友元函数 41 */ 42 //ostream & operator<< (ostream &out, Complex &c3) 43 //{ 44 // out<< "a:" << c3.a << " b:" << c3.b << endl; 45 // return out; 46 //} 47 void main() 48 { 49 Complex<int> c1(1,2); 50 Complex<int> c2(3, 4); 51 52 Complex<int> c3 = c1 + c2;//重载加号运算符 53 54 c3.printCom(); 55 56 //重载左移运算符 57 cout << c3 << endl; 58 59 { 60 Complex<int> c4 = MySub(c1 , c2); 61 62 cout << c4 << endl; 63 } 64 system("pause"); 65 }
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;
结论:友元函数只用来进行 左移 友移操作符重载。
复数类:
代码:
1 #include<iostream> 2 using namespace std; 3 4 template<typename T> 5 class Complex; 6 template<typename T> 7 Complex<T> mySub(Complex<T> &c1, Complex<T> &c2); 8 9 template <typename T> 10 class Complex 11 { 12 public: 13 friend Complex<T> mySub <T>(Complex<T> &c1, Complex<T> &c2); 14 15 friend ostream & operator<< <T>(ostream &out, Complex &c3); 16 Complex(T a, T b); 17 void printCom(); 18 Complex operator+(Complex &c2); 19 Complex operator-(Complex &c2); 20 21 protected: 22 private: 23 T a; 24 T b; 25 }; 26 27 //构造函数的实现,写在了外部 28 template <typename T> 29 Complex<T>::Complex(T a, T b) 30 { 31 this->a = a; 32 this->b = b; 33 } 34 35 template <typename T> 36 void Complex<T>::printCom() 37 { 38 cout << "a:" << a << " b:" << b << endl; 39 } 40 //成员函数实现加号运算符重载 41 template <typename T> 42 Complex<T> Complex<T>::operator+(Complex<T> &c2) 43 { 44 Complex tmp(a + c2.a, b + c2.b); 45 return tmp; 46 } 47 template <typename T> 48 Complex<T> Complex<T>::operator-(Complex<T> &c2) 49 { 50 Complex(a-c2.a,a-c2.b); 51 return tmp; 52 } 53 //友元函数实现<<左移运算符重载 54 55 /* 56 严重性 代码 说明 项目 文件 行 禁止显示状态 57 错误 C2768 “operator <<”: 非法使用显式模板参数 泛型编程课堂操练 58 59 错误的本质:两次编译的函数头,第一次编译的函数头,和第二次编译的函数有不一样 60 */ 61 template <typename T> 62 ostream & operator<< (ostream &out, Complex<T> &c3)//不加T 63 { 64 out << c3.a << "+" << c3.b << "i" << endl; 65 return out; 66 } 67 68 69 ////////////////////////////////////////////////// 70 template <typename T> 71 Complex<T> mySub(Complex<T> &c1, Complex<T> &c2) 72 { 73 Complex<T> tmp(c1.a - c2.a, c1.b - c2.b); 74 return tmp; 75 } 76 77 void main() 78 { 79 Complex<int> c1(1, 2); 80 Complex<int> c2(3, 4); 81 82 Complex<int> c3 = c1 + c2;//重载加号运算符 83 84 c3.printCom(); 85 86 //重载左移运算符 87 cout << c3 << endl; 88 89 { 90 Complex<int> c4 = mySub<int>(c1, c2); 91 92 cout << c4 << endl; 93 } 94 system("pause"); 95 }
所有的类模板函数写在类的外部,在不同的.h和.cpp中
也就是类模板函数说明和类模板实现分开
//类模板函数
构造函数
普通成员函数
友元函数
用友元函数重载<<>>;
用友元函数重载非<< >>
//要包含.cpp
demo_09complex.cpp
1 #include"demo_09complex.h" 2 #include<iostream> 3 using namespace std; 4 5 template <typename T> 6 Complex<T>::Complex(T a, T b) 7 { 8 this->a = a; 9 this->b = b; 10 } 11 12 template <typename T> 13 void Complex<T>::printCom() 14 { 15 cout << "a:" << a << " b:" << b << endl; 16 } 17 //成员函数实现加号运算符重载 18 template <typename T> 19 Complex<T> Complex<T>::operator+(Complex<T> &c2) 20 { 21 Complex tmp(a + c2.a, b + c2.b); 22 return tmp; 23 } 24 //template <typename T> 25 //Complex<T> Complex<T>::operator-(Complex<T> &c2) 26 //{ 27 // Complex(a - c2.a, a - c2.b); 28 // return tmp; 29 //} 30 template <typename T> 31 ostream & operator<< (ostream &out, Complex<T> &c3)//不加T 32 { 33 out << c3.a << "+" << c3.b << "i" << endl; 34 return out; 35 } 36 37 38 ////////////////////////////////////////////////// 39 //template <typename T> 40 //Complex<T> mySub(Complex<T> &c1, Complex<T> &c2) 41 //{ 42 // Complex<T> tmp(c1.a - c2.a, c1.b - c2.b); 43 // return tmp; 44 //}
demo_09complex.h
1 #pragma once 2 #include<iostream> 3 using namespace std; 4 template <typename T> 5 class Complex 6 { 7 public: 8 //friend Complex<T> mySub <T>(Complex<T> &c1, Complex<T> &c2); 9 10 friend ostream & operator<< <T>(ostream &out, Complex &c3); 11 Complex(T a, T b); 12 void printCom(); 13 Complex operator+(Complex &c2); 14 //Complex operator-(Complex &c2); 15 16 protected: 17 private: 18 T a; 19 T b; 20 };
demo_09complex_text.cpp
1 #include"demo_09complex.h" 2 #include"demo_09complex.cpp" 3 4 #include<iostream> 5 using namespace std; 6 7 void main() 8 { 9 Complex<int> c1(1, 2); 10 Complex<int> c2(3, 4); 11 12 Complex<int> c3 = c1 + c2;//重载加号运算符 13 14 c3.printCom(); 15 16 //重载左移运算符 17 cout << c3 << endl; 18 19 /*{ 20 Complex<int> c4 = mySub<int>(c1, c2); 21 22 cout << c4 << endl; 23 }*/ 24 system("pause"); 25 }
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数据成员副本
1 #include<iostream> 2 using namespace std; 3 template <typename T> 4 class AA 5 { 6 public: 7 static T m_a; 8 protected: 9 private: 10 }; 11 template <typename T> 12 T AA<T>::m_a =0; 13 void main() 14 { 15 AA<int> a1, a2, a3; 16 a1.m_a = 10; 17 a2.m_a++; 18 a3.m_a++; 19 cout << AA<int>::m_a << endl; 20 21 AA<char> b1, b2, b3; 22 b1.m_a = 'a'; 23 b2.m_a++; 24 b3.m_a++; 25 cout << AA<char>::m_a << endl; 26 27 //m_a是每个类型的类,去使用,手工写两个类 int char 28 system("pause"); 29 }
案例2:以下来自:C++类模板遇上static关键字
1 #include <iostream> 2 using namespace std; 3 4 template<typename T> 5 class Obj{ 6 public: 7 static T m_t; 8 }; 9 10 template<typename T> 11 T Obj<T>::m_t = 0; 12 13 int main04(){ 14 Obj<int> i1,i2,i3; 15 i1.m_t = 10; 16 i2.m_t++; 17 i3.m_t++; 18 cout << Obj<int>::m_t<<endl; 19 20 Obj<float> f1,f2,f3; 21 f1.m_t = 10; 22 f2.m_t++; 23 f3.m_t++; 24 cout << Obj<float>::m_t<<endl; 25 26 Obj<char> c1,c2,c3; 27 c1.m_t = 'a'; 28 c2.m_t++; 29 c3.m_t++; 30 cout << Obj<char>::m_t<<endl; 31 }
当类模板中出现static修饰的静态类成员的时候,我们只要按照正常理解就可以了。static的作用是将类的成员修饰成静态的,所谓的静态类成员就是指类的成员为类级别的,不需要实例化对象就可以使用,而且类的所有对象都共享同一个静态类成员,因为类静态成员是属于类而不是对象。那么,类模板的实现机制是通过二次编译原理实现的。c++编译器并不是在第一个编译类模板的时候就把所有可能出现的类型都分别编译出对应的类(太多组合了),而是在第一个编译的时候编译一部分,遇到泛型不会替换成具体的类型(这个时候编译器还不知道具体的类型),而是在第二次编译的时候再将泛型替换成具体的类型(这个时候编译器知道了具体的类型了)。由于类模板的二次编译原理再加上static关键字修饰的成员,当它们在一起的时候实际上一个类模板会被编译成多个具体类型的类,所以,不同类型的类模板对应的static成员也是不同的(不同的类),但相同类型的类模板的static成员是共享的(同一个类)。
相关连接:
C++--类模板中的static关键字 - CSDN博客
2.7类模板在项目开发中的应用
小结
- 模板是C++类型参数化的多态工具。C++提供函数模板和类模板。
- 模板定义以模板说明开始。类属参数必须在模板定义中至少出现一次。
- 同一个类属参数可以用于多个模板。
- 类属参数可用于函数的参数类型、返回类型和声明函数中的变量。
- 模板由编译器根据实际数据类型实例化,生成可执行代码。实例化的函数。