欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

C++_进阶之函数模板_类模板

程序员文章站 2022-05-08 17:21:01
c++提供了函数模板(function template.)所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具*定,用一个虚拟的类型来代表。这个通用函数就成为函数模板。凡是函数体相同的函数都可以用这个模板代替,不必定义多个函数,只需在模板中定义一次即可。在调用函数时系统会根据实参的类... ......

 C++_进阶之函数模板_类模板

第一部分

前言

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

数的功能。

  1)c++提供两种模板机制:函数模板和类模板
  2)类属 - 类型参数化,又称参数模板
    使得程序(算法)可以从逻辑上抽象,把被处理的对象(数据)类型作为参数传递。
总结:
  1)模板把函数或类要处理的数据类型参数化,表现为参数的多态性,成为类属。
  2)模板用于表达逻辑结构相同,但具体数据元素类型不同的数据对象的通用行为。

C++_进阶之函数模板_类模板

第二部分

1.函数模板

1.1为什么要有函数模板

需求:写n个函数,交换char类型、int类型、double类型变量的值。

案例:

C++_进阶之函数模板_类模板
 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 }
View Code

1.2函数模板语法

函数模板定义形式

  template    < 类型形式参数表 >    

    类型形式参数的形式为:

      typename T1 ,  typename T2 , …… , typename Tn 

      或 class T1 ,  class T2 , …… , class Tn

 C++_进阶之函数模板_类模板

函数模板调用

    myswap<float>(a, b);  //显示类型调用

    myswap(a, b); //自动数据类型推导 

1.3函数模板和模板函数

转自:函数模板和模板函数

C++_进阶之函数模板_类模板

1.4函数模板做函数参数

C++_进阶之函数模板_类模板
 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 }
View Code

1.5函数模板遇上函数重载

函数模板和普通函数区别结论:

  (1)函数模板不允许自动类型转化

  (2)普通函数能够进行自动类型转换

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

  1 函数模板可以像普通函数一样被重载

  2 C++编译器优先考虑普通函数

  3 如果函数模板可以产生一个更好的匹配,那么选择模板

  4 可以通过空模板实参列表的语法限定编译器只通过模板匹配

以下代码对上面文字进行说明:

案例1:

C++_进阶之函数模板_类模板
 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 }
View Code

案例2:

C++_进阶之函数模板_类模板
 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 }
View Code

案例3:

C++_进阶之函数模板_类模板
 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 }
View Code

案例4:

C++_进阶之函数模板_类模板
 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 }
View Code

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编译工具是一个工具链。。。。

C++_进阶之函数模板_类模板
 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.cgcc -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

    汇编语言:略过

C++_进阶之函数模板_类模板
  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
View Code

1.7函数模板机制结论

编译器并不是把函数模板处理成能够处理任意类的函数

编译器从函数模板通过具体类型产生不同的函数

编译器会对函数模板进行两次编译

在声明的地方对模板代码本身进行编译;在调用的地方对参数替换后的代码进行编译。

2.类模板

2.1为什么需要类模板

类模板与函数模板的定义和使用类似,我们已经进行了介绍。 有时,有两个或多个类,其功能是相同的,仅仅是数据类型不同,如下面语句声明了一个类:

C++_进阶之函数模板_类模板

 

 

  • 类模板用于实现类所需数据的类型参数化
  • 类模板在表示如数组、表、图等数据结构显得特别重要,

    这些数据结构的表示和算法不受所包含的元素类型的影响

2.2单个类模板语法

C++_进阶之函数模板_类模板
 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 }
View Code

2.3继承中的类模板语法

 C++_进阶之函数模板_类模板

 

 

 

 

 

 

 

 

 

案例1:

C++_进阶之函数模板_类模板
 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 }
View Code

案例2:

C++_进阶之函数模板_类模板
 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 }
View Code

2.4类模板的基础语法

C++_进阶之函数模板_类模板
 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 }
View Code

2.5类模板语法知识体系梳理

1.所有的类模板函数写在类的内部

代码:

复数类:

C++_进阶之函数模板_类模板
 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 }
View Code

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;

结论:友元函数只用来进行 左移 友移操作符重载。

复数类:

代码:

C++_进阶之函数模板_类模板
 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 }
View Code

所有的类模板函数写在类的外部,在不同的.h和.cpp中

也就是类模板函数说明和类模板实现分开

//类模板函数

  构造函数

  普通成员函数

友元函数

  用友元函数重载<<>>;

  用友元函数重载非<< >>

  //要包含.cpp

demo_09complex.cpp

C++_进阶之函数模板_类模板
 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 //}
View Code

demo_09complex.h

C++_进阶之函数模板_类模板
 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 };
View Code

demo_09complex_text.cpp

C++_进阶之函数模板_类模板
 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 }
View Code

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数据成员副本
C++_进阶之函数模板_类模板
 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 }
View Code

案例2:以下来自:C++类模板遇上static关键字

C++_进阶之函数模板_类模板
 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 }
View Code

当类模板中出现static修饰的静态类成员的时候,我们只要按照正常理解就可以了。static的作用是将类的成员修饰成静态的,所谓的静态类成员就是指类的成员为类级别的,不需要实例化对象就可以使用,而且类的所有对象都共享同一个静态类成员,因为类静态成员是属于类而不是对象。那么,类模板的实现机制是通过二次编译原理实现的。c++编译器并不是在第一个编译类模板的时候就把所有可能出现的类型都分别编译出对应的类(太多组合了),而是在第一个编译的时候编译一部分,遇到泛型不会替换成具体的类型(这个时候编译器还不知道具体的类型),而是在第二次编译的时候再将泛型替换成具体的类型(这个时候编译器知道了具体的类型了)。由于类模板的二次编译原理再加上static关键字修饰的成员,当它们在一起的时候实际上一个类模板会被编译成多个具体类型的类,所以,不同类型的类模板对应的static成员也是不同的(不同的类),但相同类型的类模板的static成员是共享的(同一个类)。

相关连接:

C++--类模板中的static关键字 - CSDN博客

2.7类模板在项目开发中的应用

小结

  • 模板是C++类型参数化的多态工具。C++提供函数模板和类模板。
  • 模板定义以模板说明开始。类属参数必须在模板定义中至少出现一次。
  • 同一个类属参数可以用于多个模板。
  • 类属参数可用于函数的参数类型、返回类型和声明函数中的变量。
  • 模板由编译器根据实际数据类型实例化,生成可执行代码。实例化的函数。