2014年2月3日 内容整理自《程序设计教程: 用C++语言编程 第三版》 陈家骏 郑滔
---------------------------------------------------------------------------------------------------------------------------------
(一)函数模板
1)类属函数和类属类
- 类属函数是指一个函数能够对不同类型的数据(参数)完成相同的操作。
- 类属类是指一个类的成员类型可变,从而可以用它描述不同种类的对象。
C++是一个静态类型语言,它提供了多种实现类属函数的途径,其中包括:
- 宏定义
- 指针类型
- 函数模板
其中宏定义虽然能实现类属函数的效果,但它毕竟不是函数,而只是在编译之前的文字替换,因此下面介绍指针参数和函数模板实现的类属函数。
2)用指针参数实现类属函数
#include<iostream> using namespace std; //用指针参数实现类属的排序函数(框架) void sort( //1.先定义一个void* 类型的形参base,使得它能够接受任意待排序数组的首地址 void *base, //2.count参数用于获得数组元素的个数 unsigned int count, //3.element_size用于每个数据元素的尺寸 unsigned int element_size, //4.最后定义一个函数指针类型的形参cmp,它从调用者处获得一个函数,这个函数功能是比较两个数组元素的大小 bool (*less_than)(const void*,const void*) ){ //(1)取第i个元素,i从0到count-1,下面是第i个元素的首地址 (char*)base+i*element_size; //(2)比较第i个和第j个元素的大小:首先分别计算i和J的首地址,然后调用cmp指向的函数来比较这两个地址上元素的大小 (*less_than)( (char*)base+i*element_size,(char*)base+j*element_size )//传入i和j的地址 //(3)交换i和j,逐个字节交换这两个地址上的元素 char* p1=(char*)base+i*element_size;//p1是i的地址 char* p2=(char*)base+j*element_size;//p2是j的地址 for(int k=0;k<element_size;k++){ char temp=p1[k]; p1[k]=p2[k]; p2[k]=temp; }//逐个字节交换地址上的元素 } int main(){ return 0; }
-------------------------------------------------------------------------------------------------------------------------------
下面的程序片段是利用上面定义的排序函数分别对int、double、以及A类型的数组进行排序
//比较int类型的元素大小 bool int_less_than(const void* p1,const void* p2){ if( *(int *)p1<*(int *)p2 ) return true; else return false; } //比较double类型的元素大小 bool double_less_than(const void* p1,const void* p2){ if( *(double *)p1<*(double *)p2 ) return true; else return false; } //比较A类型的元素大小 bool A_less_than(const void* p1,const void* p2){ if( *(A *)p1<*(A *)p2 ) return true; else return false; }
---------------------------------------------------------------------------------------------------------------------------------
用指针实现类属函数的不足之处在于:除了数组首地址和数组元素个数外,需要定义额外的参数(元素尺寸和比较函数),并且要进行大量的指针运算,这不仅使得实现比较麻烦,而且使得程序易读性差和容易出错。另外,用指针实现类属函数也不便于编译程序进行类型检查。。
3)用函数模板实现类属函数
C++中,实现类属函数的另外一种方法是用函数模板,function template是指带有类型参数的函数定义,格式如下:
template <class T1,class T2...,class Tn>
<返回值类型> <函数名> (<形参列表>){
...
}
例子:
//用模板实现类属的排序函数框架 template <class T>//T是数组的元素类型,可以是int、double、A。。。 void sort(T elements[],unsigned int count){ //取第i个元素 elements[i]; //比较第i个和第j个元素的大小 elements[i]<elements[j] //交换第i和第j个元素 T temp=elements[i]; elements[i]=elements[j]; elements[j]=temp; }//sort ... int a[100]; sort(a,100);//对int类型数组进行排序 double b[200]; sort(b,200);//对double类型数组进行排序 A c[300]; sort(c,300);//对A类型数组进行排序
4)函数模板的实例化
- 函数模板可以隐式实例化,也可以显式实例化
- 类模板只能显式实例化
显式实例化格式:
//模板函数max的声明
template <class T>
T max(T a,T b){
return a>b?a:b;
}
//模板函数max的调用(显式)
max<double>(1.4,3.5);
5)除了类型参数外,模板也可以带有非类型参数
#include<iostream>
using namespace std;
//第一个参数T是类型参数,取值为(int,double,float等),第二个参数是普通参数
//模板函数定义
template <class T,int size>
void function(T t){
T temp[size];
}
//模板函数调用示例
int main(){
function<float,10>(8.9);
//T类型为float,size为10的,参数为(float t)且取t=8.9的函数
return 0;
}
(二)类模板
如果一个类的成员类型可变,则该类成为类属类。在C++中,类属类一般用类模板实现。
类模板(class template)是指带有类型参数的类定义
1)格式
template <class T1,class T2,...,class Tn> class <类名>{ ... };
//对于在类内声明,类外定义的成员函数,应采用如下格式 template <class T1,class T2,...,class Tn> <返回值类型> <类名><T1,T2,...Tn>::<成员函数名>(<参数列表>){ ... }
2)类模板示例练习01(自己动手打一遍才能记住)
1.模板类的定义
//定义一个可以表示各种元素的Stack类
template <class T>
class Stack{
//private数据成员
T array[100];//定义一个元素类型为T的,长度为100的数组array
int top;
//public功能函数
public:
Stack(){ top=-1;}//构造函数
void push(const T &x);//在类内声明,等一会需要再类外定义的成员函数,传入一个T类型的数据压入栈中
void pop(T &x);//类型为T的元素x退栈
};
2.类外函数的定义
template <class T>
void Stack<T>::push(const T &x) {
...
}
template <class T>
void Stack<T>::pop(const T &x){
...
}
3.在main中使用该模板类的格式示例
//模板类调用示例
int main(){
Stack<int> S1;//T被实例化为int,创建了一个类的对象S1
int x;
S1.push(10);
S1.pop(x);
Stack<double> S2;
double y;
S2.push(4.4);
S2.pop(y);
Stack<A> S3;
A m,n;
S3.push(m);
S3.pop(n);
return 0;
}
3)类模板示例练习02
1.模板类的定义
template <class T,int size>//同时带有类型参数和非类型参数的模板类
class Stack{
T array[size];//每个元素为T类型,大小为size的数据成员
int top;
public:
Stack(){ top=-1;}
void push(const T &x);
void pop(T &x);
};
2.类外函数的定义
template <class T,int size>
void Stack<T,size>::push(const T &x){
}
template <class T,int size>
void Stack<T,size>::pop(T &x){
}
3.在main中使用该模板类的格式示例
//模板类调用示例
int main(){
Stack<int,100> S1;//创建一个最多有100个int类型的元素所构成的Stack对象
Stack<double,44> S2;//创建一个最多有44个double类型的元素所构成的Stack对象
return 0;
}
(三)类模板分文件编写(.h和.cpp)
1)第一种方式:直接包含源文件.cpp
原因:在.h中只有声明,对于模板类来说是没有创建的,所以编译器会找不到实例;假如直接“#include xx.cpp”由于该.cpp文件中已经包含了.h,所以代码是完整的。
2)第二种方式:(更常用)将源文件和头文件的内容写到一起,将后缀名改为.hpp
-------------------------------------------------------
写在最后,这几天零零散散看了C++里面的模板和容器,因为这章比较难,看The Cherno就看不太懂了,视频换成了b站的黑马程序员,加上读教材,磨了好几天终于有一点点明白了,OK。因为宅家太穷又去打了5天零工,每天11点睡5点半起床,洗把脸就得骑车去卡点上班。然后,如果说Cherno的C++系列是适合有编程经验的精华教程,精灵密道,那么黑马就是适合小白提升能力的高速大马路。黑马的那个老师用的软件是什么不知道,就是看起来非常舒服,小巧便捷,启动快,截图如下,感觉学编程这样搞,整洁、有秩序、有逻辑,会让自己的认知加速。