C++_进阶之函数模板_类模板
https://www.cnblogs.com/wanghui1234/p/8846881.html
C++容器(STL容器)
http://c.biancheng.net/view/331.html
目录
正文
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:
View Code
案例2:
View Code
案例3:
View Code
案例4:
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编译工具是一个工具链。。。。 |
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
汇编语言:略过
View Code
1.7函数模板机制结论
编译器并不是把函数模板处理成能够处理任意类的函数
编译器从函数模板通过具体类型产生不同的函数
编译器会对函数模板进行两次编译
在声明的地方对模板代码本身进行编译;在调用的地方对参数替换后的代码进行编译。
2.类模板
2.1为什么需要类模板
类模板与函数模板的定义和使用类似,我们已经进行了介绍。 有时,有两个或多个类,其功能是相同的,仅仅是数据类型不同,如下面语句声明了一个类:
- 类模板用于实现类所需数据的类型参数化
- 类模板在表示如数组、表、图等数据结构显得特别重要,
这些数据结构的表示和算法不受所包含的元素类型的影响
2.2单个类模板语法
View Code
2.3继承中的类模板语法
案例1:
View Code
案例2:
View Code
2.4类模板的基础语法
View Code
2.5类模板语法知识体系梳理
1.所有的类模板函数写在类的内部
代码:
复数类:
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;
结论:友元函数只用来进行 左移 友移操作符重载。
复数类:
代码:
View Code
所有的类模板函数写在类的外部,在不同的.h和.cpp中
也就是类模板函数说明和类模板实现分开
//类模板函数
构造函数
普通成员函数
友元函数
用友元函数重载<<>>;
用友元函数重载非<< >>
//要包含.cpp
demo_09complex.cpp
View Code
demo_09complex.h
View Code
demo_09complex_text.cpp
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数据成员副本
View Code
案例2:以下来自:C++类模板遇上static关键字
View Code
当类模板中出现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类含有指针属性哪?
View Code
结论1: 如果把Teacher放入到MyVector数组中,并且Teacher类的属性含有指针,就是出现深拷贝和浅拷贝的问题。
结论2:需要Teacher封装的函数有:
1) 重写拷贝构造函数
2) 重载等号操作符
3) 重载左移操作符。
理论提高:
所有容器提供的都是值(value)语意,而非引用(reference)语意。容器执行插入元素的操作时,内部实施拷贝动作。所以STL容器内存储的元素必须能够被拷贝(必须提供拷贝构造函数)。
3) 请从数组模板中进行派生
View Code
作业:
封装你自己的数组类;设计被存储的元素为类对象;
思考:类对象的类,应该实现的功能。
//1 优化Teacher类, 属性变成 char *panme, 构造函数里面 分配内存
//2 优化Teacher类,析构函数 释放panme指向的内存空间
//3 优化Teacher类,避免浅拷贝 重载= 重写拷贝构造函数
//4 优化Teacher类,在Teacher增加 <<
//5 在模板数组类中,存int char Teacher Teacher*(指针类型)
zuoye.h
View Code
zuoye_test12.cpp
View Code
zuoye12.cpp
View Code
参考一些资料,加上一些见解,如果有雷同,纯属巧合。