C++之模版
来源:互联网 发布:怎么利用大数据抄股票 编辑:程序博客网 时间:2024/06/06 03:06
学完C这个面向过程的语言之后,大家应该都养成写C程序的习惯,介绍模版之前,我们先考虑这样一个问题:交换char,int,double等类型的数据,按照以往习惯是不是如此:
void swap(int &a,int &b) {int tmp = a; a = b; b = tmp; } void swap (char ch1,char ch2) { }
等等。。。需要多少个类型就要写多少个交换函数,这样简直大大增加代码的阅读量和对代码的理解,那么就有一个想法,可不可以传类型呢?那么C++提供了函数模版。
1、模版
所谓函数模版。实际上是建立了一个通用函数,其函数类型和形参类型不具体确定,用一个虚拟的类型代表。这个通用函数就称为函数模版,凡是函数体相同的函数都可以用这个模版来代替,不必定义多个函数,只需在模版中定义一次,在调用函数时系统会根据实参的类型来取代模版中的虚拟类型,从而实现函数不同的功能。
C++中提供两个模版机制:函数模版和类模版。
2、函数模版
语法格式:template <typename T1,typename T2....>
那这样交换函数是
int a = 10; int b = 20; MySwap(a,b);
(2)显示调用,用<>来显示说明类型
char ch1 = 'A'; char ch2 = 'B'; swap<char> (ch1,ch2);
注意:template <typename T>每声明一个模版就要写一次
有了模版之后,就方便很多,比如你要写很长一段的冒泡排序,只能对整数交换,如果相对其他类型的数据进行排序,又要写很长一段,就可以用那个函数模版。
模版的作用将算法与数据类型分离,将数据类型独立出去,只要关注算法。
3、函数模版与普通函数的区别
(1)普通函数,可以做类型的隐式转换,比如:以%d输出字符'A',是输出它的ASCII值65。
(2)函数模版在未指明类型,不支持隐式转换。
当函数模版遇上函数重载,首先要知道函数模板可以像普通函数一样被重载。
(1)那么当函数模版和普通函数都可以被调用时,有限调用普通函数(开销问题,用函数模版,它得加工出一个模版函数),如果有的人非要用模版函数呢,可以的,
可以通过空类型列表<>,强制调用模版函数。
c = Max<>(a,b);
(2)在模版函数可以提供更优的选择情况下,调用函数模版。什么是更优呢,意思就是需要类型转换,这种情况有函数模版就用模版,没有就只能委屈它强转了。
ch = Max(ch1,ch2);
函数模版的机制;编译器并不是把函数模板处理成能够处理任意类的函数,编译器从函数模板通过具体类型产生不同的函数,编译器会对函数模板进行两次编译,在声明的地方对模板代码本身进行编译;在调用的地方对参数替换后的代码进行编译。
函数模版与模版函数之间的关系就像工厂与产品的关系,函数模版加工成模版函数。
4、类模版
为什么要有类模版?或者说类模版解决了什么?
类模板用于实现类所需数据的类型参数化。类模板的定义方式和用法都类似函数模板。但在类模板被用来定义对象时,必须指明类型。不支持类型的自动推导。
Test<int> a; Test<int> b(3); 语法格式:template <typename T>class A{public:A(T a){this->a = a;}virtual void print (){cout << "a = " << a << endl;}protected:T a;};
模版类做函数参数时的使用方式:
(1)写出具体的模版类的类型
void func (A<int> &p){p.print();}
(2)写成函数模板
template <typename T>void func(A<T> &p){p.print();}
模版类的派生类怎么写?有两种写法:派生普通类和派生类模版
(1)生成一个具体类,要求A写出具体的类型:
class B:public A<int>
注意:类B不是一个模版类,而且前面也没有template <typename T>
(2)生成一个类模版
template <typename T> class C : public A<T> { public: C(T a, T c): A(a) { this->c = c; } void print() { cout << "a = " << a << ", c = " << c << endl; } private:T c;};
综合上面的介绍,可以这样声明和使用类模版:
1) 先写出一个实际的类。由于其语义明确,含义清楚,一般不会出错。
2) 将此类中准备改变的类型名(如int要改变为float或char)改用一个自己指定的虚拟类型名(如下面的numtype)。
3) 在类声明前面加入一行,格式为:
template <typename 虚拟类型参数>如: template <typename numtype> //注意本行末尾无分号 class Compare {…}; //类体
4) 用类模板定义对象时用以下形式:
类模板名<实际类型名>对象名;
类模板名<实际类型名>对象名(实参表列);
如:
Compare<int> cmp; Compare<int> cmp(3,7);
5) 如果在类模板外定义成员函数,应写成类模板形式:
template <typename 虚拟类型参数>
函数类型 类模板名<虚拟类型参数>::成员函数名(函数形参表列) {…}
关于类模板的几点说明:
1) 类模板的类型参数可以有一个或多个,每个类型前面都必须加typename,如:
template <typename T1,typename T2> class someclass {…};
在定义对象时分别代入实际的类型名,如:
someclass<int,double> obj;
2) 和使用类一样,使用类模板时要注意其作用域,只能在其有效作用域内用它定义对象。
3) 模板可以有层次,一个类模板可以作为基类,派生出派生模板类。
5、类模版的实现在内部
模板类的友元函数要在类的内部实现,意思要写函数体(但它还是友元,不能去掉friend);当然,不用再定义为模板函数了。比如这样一个<< 操作符的重载函数:
template <typename T> class Complex { friend ostream &operator<< (ostream &out, Complex<T> &obj) { out << obj.a << " + " << obj.b << "i"; return out; } private: T a; T b; };
6、类模版的实现在外部
当类的实现是在类的外部时,所有函数都要定义成模板函数。在函数实现时还要加上域解析符,如:
template <typename T> Test<T>::Test (){}
用友元函数在类外部实现,是一个惨痛的经历,过程十分复杂,所以没事别用友元函,下面是步骤:
1、进行模板类声明
2、声明友元函数(模板类)
3、在类的内部进行友元函数的声明(友元,非模板),此时需要在函数名后面加上参数类型 <T>,如
friend Complex<T> mySub <T>(Complex<T> &c1, Complex<T> &c2);
4、在类的外部实现(在友元函数内部定义对象时要指定<T>,返回值也需要加上!!!)
5)当模板类的声明和实现在不同的文件中时:
在main 函数中需要包含模板类的实现文件;通常这种需要被包含的实现文件,后缀一般要写成.hpp
7、类模版中的static关键字
1)类模板的静态变量 也要在类的定义并初始化
template <typename T> class A{public:static T a;};template <typename T>T A<T>::a = 0;
2)类模板中不同的模板类中的静态变量是不共享的,每一个模板类都有自己的静态变量,该模板类的所有对象共享一个static数据成员。
A<int> a, b;a.a = 100;printf ("a = %d\n", b.a); // a = 100A<char> ch1, ch2;ch1.a = 'A';printf ("a = %c\n", ch2.a); // a = Aprintf ("a = %c\n", b.a); // a = d
3)每个模板类有自己的类模板的static数据成员副本
8、数组的类模版
以前我们也用类来模仿数组,能实现基本功能,但当时只能存放int类型数据,现在我们想存放任意类型的数据,这时候就可以用模版来实现。
需要注意的几个地方:
1)当自定义类型存放在数组中时,自定义类中需要加上无参构造,如果有指针,一定初始化为 NULL;
2)由于数组打印是通过重载左移操作符来实现的,所以需要在自定义类中也要实现左移操作符的重载,不重载<<也可以,那就不要用cout<<
3)数组内部赋值运算符,默认的赋值方式是浅拷贝,一旦涉及指针的操作就会导致问题,所以需要做赋值操作符=的运算符重载
4)另外,在做delete操作时,在delete数组时,要加上[ ],经常忘记。
5)在我们写程序的时候,拷贝构造和赋值运算符不用的时候就设成私有!!
下面附上数组的类模版的头文件:
#ifndef __MYARRAY_H__#define __MYARRAY_H__#include <iostream>using namespace std;template <typename T>class MyArray{friend ostream & operator<< <T>(ostream &out, MyArray<T> &a);public:MyArray(int len);MyArray(const MyArray &obj);~MyArray();int getLen();public:T& operator[] (int index);MyArray& operator= (const MyArray &obj);private:int len;T *m_p;};template <typename T>MyArray<T>::MyArray(int len){this->len = len;m_p = new T[len];}template <typename T>MyArray<T>::MyArray(const MyArray &obj){len = obj.len;m_p = new T[len];for (int i = 0; i < len; i++){m_p[i] = obj.m_p[i];}}template <typename T>MyArray<T>::~MyArray(){if (m_p != NULL){delete [] m_p;m_p = NULL;}len = 0;}template <typename T>int MyArray<T>::getLen(){return len;}template <typename T>T& MyArray<T>::operator[] (int index){return m_p[index];}template <typename T>MyArray<T>& MyArray<T>::operator= (const MyArray &obj){// 赋值运算符重载的3个步骤,准确来说是4个// 1、判断是不是自身if (this == &obj)return *this;// 2、释放旧空间if (m_p != NULL)delete [] m_p;// 3、开辟新空间m_p = new T[obj.len];// 4、赋值len = obj.len;for (int i = 0; i < len; i++){m_p[i] = obj.m_p[i];}return *this;}template <typename T>ostream & operator<< (ostream &out, MyArray<T> &a){for (int i = 0; i < a.len; i++){out << a[i] << " ";}out << endl;return out;}
- C++ 之函数模版
- c语言图形模版
- 用C也能写模版
- 模版文件 target.c
- 邻接表模版c+
- Template模版实例(C++)
- 【C++】模版矩阵类
- C++template--函数模版
- 【c++】函数模版
- c++模版
- 【C++】认识模版函数
- C/C++模版
- C++——模版:函数模版
- C++——模版:类模版
- C++ 基础之 "模版函数","类模版"
- C++模版初探之模版函数
- C++之函数模版和类模版
- 函数模版之多边形
- 用SQL进行多值列拆分成二值列的一个实现
- 10分钟适配 iOS 11 & iPhone X
- 第二课JavaScript函数传参
- Springframework下所有jar包作用简介
- 快速排序
- C++之模版
- MYSQL 学习笔记二 数据库引擎与数据表的基本操作
- 制作根文件系统debian9
- gerrit克隆,权限不够的解决方法
- 有关编译的笔记
- Linux服务器域证书安装方法
- JAVA中循环删除list中元素的方法总结
- dos命令配置java环境变量
- Deep Learning模型之:CNN的反向求导及练习