c++模板与类型转换与异常

模板

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

  • c++面向对象编程思想:封装、继承、多态
  • 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<

函数模板 会编译两次:

  • 第一次:是对函数模板 本身编译
  • 第二次:函数调用处 将T的类型具体化

函数模板目标:模板是为了实现泛型,可以减轻编程的工作量,增强函数的重用性。

函数模板的注意点

函数模板 和 普通函数 都识别。(优先选择 普通函数)

函数模板 和 普通函数 都识别。可以强制使用函数模板

//强制使用函数模板
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等,同样是内存的扩大访问,不能保证安全。

static_cast静态类型转换

普通类型转换

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

dynamic_cast动态类型转换

用于类层次间上行和下行的转换,在进行下行转换时会进行动态类型转换是安全的。

基本类型:不支持

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_cast常量转换

将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);

重新解释转换(reinterpret_cast) (最不安全)

基本类型,不支持

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();
    }
}

c++标准异常

c++模板与类型转换与异常_第1张图片

说明:

异常名称 描述
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;
    }
}

你可能感兴趣的:(c++,开发语言)