c++提供了函数模板(function template.)所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体制定,用一个虚拟的类型来代表。这个通用函数就成为函数模板。凡是函数体相同的函数都可以用这个模板代替,不必定义多个函数,只需在模板中定义一次即可。在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现不同函数的功能。 c++提供两种模板机制:函数模板和类模板。
模板的分类:函数模板、类模板
将功能相同,类型不同的函数(类)的类型抽象成虚拟的类型。当调用函数(类实例化对象)的时候,编译器自动将虚拟的类型 具体化。这个就是函数模板(类模板)。
建立一个通用函数,其函数返回值类型和形参类型可以不具体制定,用一个虚拟的类型来代表。
template
函数声明或定义
template — 声明创建模板
typename — 表面其后面的符号是一种数据类型,可以用class代替
T — 通用的数据类型,名称可以替换,通常为大写字母
#include
using namespace std;
templatevoid fun(T &a,T &b){
T tem=a;
a=b;
b=tem;
}
int main()
{
int a=10,b=20;
cout<
函数模板 会编译两次:
函数模板目标:模板是为了实现泛型,可以减轻编程的工作量,增强函数的重用性。
函数模板 和 普通函数 都识别。(优先选择 普通函数)
函数模板 和 普通函数 都识别。可以强制使用函数模板
//强制使用函数模板
fun<>(a, b);//调用函数模板
函数模板 自动类型推导时 不能对函数的参数 进行 自动类型转换。
#include
using namespace std;
templatevoid fun(T a,T b){
T tem=a;
a=b;
b=tem;
cout<<"函数模板"<(10,'b');//函数模板,强制说明T为int类型 就支持自动类型转换
}
#include
using namespace std;
templatevoid fun(T a,T b){
T tem=a;
a=b;
b=tem;
cout<<"函数模板a,b"<void fun(T a){
cout<
当函数模板 推导出 T为数组或其他自定义类型数据 可能导致运算符 不识别。
#include
using namespace std;
class Da
{
public:
int data; //data的属性如果是private,需要在类中声明友元
Da() {cout<<"无参构造"<void fun(T a){cout<
具体化函数模板
#include
using namespace std;
class Da
{
public:
int data;
Da() {cout<<"无参构造"<void fun(T a){cout<void fun (Da a){cout<
类模板和函数模板的定义和使用类似,有两个或多个类,其功能是相同的,仅仅是数据类型不同。 类模板用于实现类所需数据的类型参数化。
#include
using namespace std;
template
class Data{
private:
T1 a;
T2 b;
public:
Data(){cout<<"无参构造"<a=a;
this->b=b;
cout< ob(100,"lulu");//实例化对象 不能自动类型推导必须指明T的类型
}
#include
using namespace std;
//template只修饰class Data他们是一起的
template class Data{
private:
T1 a;
T2 b;
public:
Data(){cout<<"无参构造"< 类模板中成员函数类外实现时,需要加上模板参数列表
* Data 才是类的类型
*/
template Data::Data(T1 a,T2 b){ //类外实现
this->a=a;
this->b=b;
cout< void Data::fun(){ //类外实现
cout< ob(100,"lulu");//实例化对象 不能自动类型推导必须指明T的类型
//Data 才是类的类型
ob.fun();
}
#include
using namespace std;
//template只修饰class Data他们是一起的
template<class T1,class T2> class Data{
//注意friend 的位置,typename的参数名
template<typename T3,typename T4> friend void fun2(Data<T3,T4> &ob);
private:
T1 a;
T2 b;
public:
Data(){cout<<"无参构造"<<endl;}
Data(T1 a,T2 b); //类内声明
void fun(void); //类内声明
};
/*
* template 类模板中成员函数类外实现时,需要加上模板参数列表
* Data 才是类的类型
*/
template<class T1,class T2> Data<T1,T2>::Data(T1 a,T2 b){ //类外实现
this->a=a;
this->b=b;
cout<<a<<" "<<b<<endl;
}
template<class T1,class T2> void Data<T1,T2>::fun(){ //类外实现
cout<<a<<" "<<b<<endl;
}
template<typename T3,typename T4> void fun2(Data<T3,T4> &ob){ //函数模板,形参类型为Data
cout<<ob.a<<" "<<ob.b<<endl;
}
int main()
{
Data<int,string> ob(100,"lulu");//实例化对象 不能自动类型推导必须指明T的类型
//Data 才是类的类型
//ob.fun();
fun2(ob);
}
#include
using namespace std;
//template只修饰class Data他们是一起的
template<class T1,class T2> class Data{
friend void fun2(Data<int,string> &ob);//声明友元
private:
T1 a;
T2 b;
public:
Data(){cout<<"无参构造"<<endl;}
Data(T1 a,T2 b); //类内声明
void fun(void); //类内声明
};
/*
* template 类模板中成员函数类外实现时,需要加上模板参数列表
* Data 才是类的类型
*/
template<class T1,class T2> Data<T1,T2>::Data(T1 a,T2 b){ //类外实现
this->a=a;
this->b=b;
cout<<a<<" "<<b<<endl;
}
template<class T1,class T2> void Data<T1,T2>::fun(){ //类外实现
cout<<a<<" "<<b<<endl;
}
void fun2(Data<int,string> &ob){ //函数模板,形参类型为Data
cout<<ob.a<<" "<<ob.b<<endl;
}
int main()
{
Data<int,string> ob(100,"lulu");//实例化对象 不能自动类型推导必须指明T的类型
//Data 才是类的类型
//ob.fun();
fun2(ob);
}
data.hpp头文件
#ifndef DATA_H
#define DATA_H
#include
using namespace std;
templateclass Data{
private:
T1 a;
T2 b;
public:
Data(); //无参构造声明
Data(T1 a, T2 b); //有参构造声明
void showData(void);//成员函数申明
};
template Data::Data(){ //类外实现无参构造
cout<<"无参构造"< Data::Data(T1 a, T2 b){ //类外实现有参构造
this->a = a;
this->b = b;
}
template void Data::showData(void) //类外实现成员函数
{
cout<
main.cpp
#include
#include"data.hpp" //包含头文件
using namespace std;
int main(int argc, char *argv[])
{
Data<int,char> ob1(100,'A');
ob1.showData(); //100 A
return 0;
}
.hpp头文件
#ifndef LEISHUZU_HPP
#define LEISHUZU_HPP
#include
#include
using namespace std;
template class MyArry{
template friend ostream& operator<<(ostream &out,MyArry ob);
private:
T *arr;
int size; //大小
int capacity; //容量
public:
MyArry(); //无参构造
MyArry(int capacity); //有参构造
MyArry(const MyArry &ob); //拷贝构造,类中有指针成员且指向堆区空间必须实现拷贝构造完成深拷贝动作。
~MyArry(); //析构,一个类有指针成员,这个类必须写析构函数,释放指针成员所指向空间
MyArry& operator =(MyArry &ob); //完成深拷贝,类中有指针成员,且指向堆区空间
void pushBack(T elem); //插入数据
void sortArray(); //排序
};
#endif // LEISHUZU_HPP
template
MyArry::MyArry() //MyArry才是真正的类型
{
capacity=5;
size=0;
arr=new T[capacity];
memset(arr,0,sizeof(T)*capacity); //清空数组内容,需要包含头文件string.h
}
template
MyArry::MyArry(int capacity)
{
this->capacity=capacity;
size=0;
arr=new T[capacity];
memset(arr,0,sizeof(T)*capacity);
}
template
MyArry::MyArry(const MyArry &ob) //拷贝构造旧对象给新对象赋值完成深拷贝
{
capacity=ob.capacity;
size=ob.size;
arr=new T[capacity]; //开辟新空间
memset(arr,0,sizeof(T)*capacity);
memcpy(arr,ob.arr,sizeof(T)*capacity); //memcpy,完成值的拷贝
}
template
MyArry::~MyArry()
{
delete [] arr; //释放堆区空间
}
template
MyArry &MyArry::operator =(MyArry &ob)
{
if(arr!=NULL){delete [] arr;arr=NULL;}//判断this->arr是否存在旧空间,有就删除旧空间
capacity=ob.capacity;
size=ob.size;
arr=new T[capacity]; //开辟新空间
memset(arr,0,sizeof(T)*capacity);
memcpy(arr,ob.arr,sizeof(T)*capacity); //memcpy,完成值的拷贝
return *this; //完成链式操作
}
template
void MyArry::pushBack(T elem)
{
if(size==capacity)
{
capacity=2*capacity; //容器满的话就扩展容量
T *tem=new T[capacity]; //容器满的话就扩展容量,capacity变成了原来的两倍
if (arr!=NULL)
{
memcpy(tem,arr,sizeof(T)*size); //拷贝旧空间内容
delete [] arr; //释放旧空间
}
arr=tem;//arr指向新申请的空间
}
arr[size]=elem;
size++;
return;
}
template
void MyArry::sortArray() //冒泡排序
{
if(size==0)
{
cout<<"没有数据"< arr[j+1])
{
T tmp = arr[j];
arr[j] = arr[j+1];
arr[j+1]=tmp;
}
}
}
}
return;
}
template ostream& operator<<(ostream &out,MyArry ob)
{
int i=0;
for(i=0;i
.cpp源文件
#include
#include"leishuzu.hpp"
#include
using namespace std;
class stu
{
friend ostream& operator <<(ostream &out,stu ob);
private:
int num;
string name;
float score;
public:
stu(){}
stu(int num,string name,float score)
{
this->num=num;
this->name=name;
this->score=score;
}
bool operator>(stu ob) //>符号的重载
{
return num>ob.num;
}
};
ostream& operator <<(ostream &out,stu ob)
{
out<arr1;
arr1.pushBack(10);
arr1.pushBack(57);
arr1.pushBack(34);
arr1.pushBack(14);
arr1.pushBack(23);
arr1.sortArray();
cout<arr2;
arr2.pushBack('a');
arr2.pushBack('d');
arr2.pushBack('c');
arr2.pushBack('e');
arr2.pushBack('b');
arr2.sortArray();
cout<arr3;
arr3.pushBack(stu(101,"lulu",98));
arr3.pushBack(stu(1004,"caicai",9));
arr3.pushBack(stu(109,"kunkun",97));
arr3.sortArray(); //需要重载运算符,stu类型两个对象比较,在对应类里实现
cout<
#include
using namespace std;
template
class stu{
private:
T1 a;
T2 b;
public:
stu(){}
stu(T1 a,T2 b);
void fun();
};
template
stu::stu(T1 a, T2 b) //有参构造
{
this->a=a;
this->b=b;
}
template
void stu::fun() //成员函数
{
cout< //类模板 派生出 普通类
{
public:
int c;
public:
son() {}
son(int a,char b,int c):stu(a,b) //初始化列表,子类实例对象时 必须使用初始化列表 调用成员对象、父类的有参构造。
{
this->c=c;
}
};
int main()
{
son ob(1,'a',2);
ob.fun();
cout<
#include
using namespace std;
template<typename T1,typename T2>
class stu{
private:
T1 a;
T2 b;
public:
stu(){}
stu(T1 a,T2 b);
void fun();
};
template<typename T1, typename T2>
stu<T1,T2>::stu(T1 a, T2 b) //有参构造
{
this->a=a;
this->b=b;
}
template<typename T1, typename T2>
void stu<T1,T2>::fun() //成员函数
{
cout<<a<<" "<<b<<endl;
}
template<typename T1,typename T2,typename T3>
class son:public stu<T1,T2> //类模板继承类模板
{
public:
T3 c;
son(){}
son(T1 a,T2 b,T3 c):stu<T1,T2>(a,b)
{
this->c=c;
}
};
int main()
{
son<int,char,int> ob(1,'a',2); //必须指定类型
ob.fun();
cout<<ob.c<<endl;
}
子类空间肯定是大于等于父类空间的。
子类空间给父类指针保存,子类转换成父类,上行转换(安全)
子类指针转换为基类指针,属于缩小内存访问,所以是安全的。
父类空间给子类指针保存,父类转换成子类,下行转换(不安全,发生内存越界)
父类指针转换为子类指针,由于没有做运行时检查,是不安全的,主要还是子类的访问空间是大于父类,所以多出来的访问空间不保证安全。类似于int转char,int转double等,同样是内存的扩大访问,不能保证安全。
普通类型转换
class Base{};
class Son:public Base{};
class Other{};
基本类型
int num = static_cast(3.14);//ok
上行转换:支持 安全
Base *p = static_cast (new Son); //子类空间给父类指针保存
下行转换:支持 (不安全)
Son *p2 = static_cast(new Base); //父类空间给子类指针保存
不相关类型转换:不支持
Base *p3 = static_cast (new Other);//err
用于类层次间上行和下行的转换,在进行下行转换时会进行动态类型转换是安全的。
基本类型:不支持
int num = dynamic_cast(3.14);//err
上行转换:支持
对于上行转换,static_cast和dynamic_cast效果一样,都安全;
Base *p1 = dynamic_cast (new Son);//ok
不相关类型转换:不支持
Base *p3 = dynamic_cast (new Other);//err
对于下行转换:你必须确定要转换的数据确实是目标类型的数据,即需要注意要转换的父类类型指针是否真的指向子类对象,如果是,static_cast和dynamic_cast都能成功;如果不是static_cast能返回,但是不安全,可能会出现访问越界错误,而dynamic_cast在运行时类型检查过程中,判定该过程不能转换,返回NULL。
Son *p2 = dynamic_cast(new Base);
将const修饰的指针或引用 转换成 非const (支持)
const int *p1;
int *p2 = const_cast(p1);
const int &ob = 10;
int &ob1 = const_cast(ob);
将非const修饰的指针或引用 转换成 const (支持)
int *p3;
const int *p4 = const_cast(p3);
int data = 10; //常量是不能取引用
const int &ob2 = const_cast(data);
基本类型,不支持
int num=reinterpret_cast(3.14f);//err
基本类型指针,支持
float *q;
int *p=reinterpret_cast(q);
上行转换:支持
Base *p1 = dynamic_cast (new Son);//ok
不相关类型转换:支持
Base *p3 = dynamic_cast (new Other);
下行转换:支持
Son *p2 = static_cast(new Base); //父类空间给子类指针保存
程序遇到错误,然后抛出异常,使用者捕获异常。
异常:是指在程序运行的过程中发生的一些异常事件(如:除0溢出,数组下标越界,所要读取的文件不存在,空指针,内存不足,访问非法内存等等)。(异常是一个类)
c++异常机制相比C语言异常处理的优势?
c语言通过返回值处理异常比如返回0表示成功,-1表示失败。但是函数的返回值可以忽略,异常不可忽略。(忽略异常 程序结束)
整型返回值没有任何语义信息。而异常却包含语义信息,有时你从类名就能够体现出来。
try
{
throw 异常值;
}
catch(异常类型1 异常值1)
{
处理异常的代码1;
}
catch(异常类型2 异常值2)
{
处理异常的代码2;
}
catch(...)//任何异常都捕获
{
处理异常的代码3;
}
try
{
//throw 1;
throw 'A';
//throw 2.14f;
}
catch(int e)//捕获,用int类型的普通变量接异常值
{
cout<<"int异常值为:"<<e<<endl;
}
catch(char e)//捕获
{
cout<<"char异常值为:"<<e<<endl;
}
catch(...)//捕获所有异常
{
cout<<"其他异常值为:err"<<endl;
}
return 0;
异常被抛出后,从进入try块起,到异常被抛掷前,这期间在栈上构造的所有对象,都会被自动析构。析构的顺序与构造的顺序相反,这一过程称为栈的解旋.
#include
using namespace std;
class Data{
public:
int a;
public:
Data(){}
Data(int a)
{
this->a = a;
cout<<"构造函数"<
描述的是 可以抛出哪些类型的异常
void fun01()
{
//throw 1;
//throw '1';
throw "hello";//抛出字符串
}
#include
using namespace std;
void fun02() throw(int,char)
{
//throw 1;
//throw '1';
throw 3.14f;//抛出 不能捕获,terminate called after throwing an instance of 'float'
}
int main()
{
try
{
fun02();
}
catch(int)//捕获int类型的异常抛出
{
cout<<"int异常值为:"<<endl;
}
catch(char)//捕获
{
cout<<"char异常值为:"<<endl;
}
catch(...)//捕获
{
cout<<"其他异常值为:"<<endl;
}
}
void fun03() throw()
{
throw 1;
//throw '1';
//throw "hello";//抛出 不能捕获
}
以普通对象接异常值,会有拷贝构造,
以对象指针接受异常值,会有空间的开辟
推荐对象引用 接异常值
#include
using namespace std;
class stu{
public:
stu(){
cout<<"异常变量构造"<
结果
异常变量构造
拷贝构造
普通对象接异常
异常变量析构
异常变量析构
try{
throw new stu;
}
catch(stu *e)
{
cout<<"普通对象接异常"<
结果,会涉及内存空间的开辟和delete
异常变量构造
普通对象接异常
异常变量析构
try{
throw stu();
}
catch(stu &e)
{
cout<<"普通对象接异常"<
既不涉及拷贝构造,又没有空间的开辟
父类的引用捕获子类的异常
#include
using namespace std;
//异常基类,用来操作所有的子类,父类使用虚函数。
class BaseException{
public:
virtual void printError(){}; //虚函数
};
//空指针异常
class NullPointerException : public BaseException{ //父类是BaseException
public:
virtual void printError() //子类必须重写虚函数
{
cout << "空指针异常!" << endl;
}
};
//越界异常
class OutOfRangeException : public BaseException{
public:
virtual void printError() //子类必须重写虚函数
{
cout << "越界异常!" << endl;
}
};
void doWork()
{
//throw NullPointerException();
throw OutOfRangeException();
}
int main()
{
try{
doWork();
}
catch (BaseException& ex)//父类引用 可以捕获搭配该父类派生出的所有子类的子类
{
ex.printError();
}
}
说明:
异常名称 | 描述 |
---|---|
exception | 所有标准异常类的父类 |
bad_alloc | 当operator new and operator new[],请求分配内存失败时 |
bad_exception | 这是个特殊的异常,如果函数的异常抛出列表里声明了badexception异常, 当函数内部抛出了异常抛出列表中没有的异常,这是调用的unexpected函数中若抛出异常,不论什么类型,都会被替换为badexception类型 |
bad_typeid | 使用typeid操作符,操作一个NULL指针,而该指针是带有虚函数的类,这时抛出bad_typeid异常 |
bad_cast | 使用dynamic_cast转换引用失败的时候 |
ios_base::failur | io操作过程出现错误 |
logic_error | 逻辑错误,可以在运行前检测的错误 |
runtime_error | 运行时错误,仅在运行时才可以检测的错误 |
logic_error的子类
异常名称 | 描述 |
---|---|
length_error | 试图生成一个超出该类型最大长度的对象时,例如vector的resize操作 |
domain_error | 参数的值域错误,主要用在数学函数中。例如使用一个负值调用只能操作非负数的函数 |
outofrange | 超出有效范围 |
invalid_argumen | 参数不合适。在标准库中,当利用string对象构造bitset时,而string中的字 符不是’0’或’1’的时候,抛出该异常 |
runtime_error的子类
异常名称 | 描述 |
---|---|
range_error | 计算结果超出了有意义的值域范围 |
overflow_error | 算术计算上溢 |
underflow_error | 算术计算下溢 |
invalid_argument | 参数不合适。在标准库中,当利用string对象构造bitset时,而string中的字符不是’0’或’1’的时候,抛出该异常 |
try{
throw out_of_range("越界了"); //out_of_range 的父类是exception,what是虚函数
}
catch (exception &ex)
{
cout<
#include
#include //包含标准异常的头文件
using namespace std;
class NewException:public exception //必须继承exception
{
private:
string msg;
public:
NewException(){}
NewException(string msg)
{
this->msg = msg;
}
//必须重写父类的what虚函数
virtual const char* what()const throw()//防止父类在子类前抛出标准异常,
{
//将string类转换成char *
return this->msg.c_str();
}
~NewException(){}
};
int main()
{
try
{
throw NewException("自己的异常");
}
catch(exception &e)//父类引用接异常值
{
cout<<e.what()<<endl;
}
}