C++ (4)
1. C++模板
1.1 模板的概论
C++提供了函数模板(function template),函数模板是将函数的参数类型不具体化,在函数使用时,再给定具体的参数的数据类型。
C++给定两个模板机制:函数模板和类模板
【注】都属于参数类型的模板,又称之为参数模板
1.2 函数模板
函数模板可以自动推到参数的类型,当然也可以显示指定类型。
语法:
template<class或typename T>
函数的返回值 函数名(T &n1,T &n2)
{
//函数体
}
【推荐】在定义函数模板时,使用typename,表示T是某一种类型的名称
1.3 函数模板和普通函数的区别
-
函数模板不允许自动类型转化
-
普通函数能够自动进行类型转化
1.4 函数模板和普通函数在一起调用规则
- c++编译器有限考虑普通函数
- 可以通过空模板实参列表的语法限定编译器只能通过模板匹配
- 函数模板可以像普通函数一样被重载
- 如果函数模板可以产生一个更好的匹配,选择模板
1.5 模板实现机制
函数模板机制结论:编译器并不是把函数模板处理成能够处理的任何类型的函数。
函数模板通过具体类型产生不同的函数。编译器会对函数模板进行两次编译,在声明的地方对模板代码本身进行编译,在调用的地方对参数替换后的代码进行编译。
模板的局限性:函数模板是存在一些局限性的,如果函数模板声明的泛型,在实际使用中,具体化为基础数据类型则完全可以使用,但是具体化是一个类对象时或地址时,则需要特殊的处理:
- 具体化优先于常规模板
- 具体化函数模板为友元函数,可以访问类对象的私有成员
- 类中定义运算符重载
1.6 类模板
类模板是类成员变量的数据类型泛化。
类模板的声明作用于整个类,即类的内部的任何位置都可以使用
如:定义一个长方形
template <typename T>
class Rect
{
private:
T width, height;
public:
Rect(T w, T h) : width(w), height(h) {}
void draw()
{
cout << "绘制方形 width=" << width << ",height=" << height << endl;
}
T length()
{
return (width + height) * 2;
}
};
1.6.1 类模板作为函数参数
类模板作为函数参数时,必须指定泛型的具体类型。
当然,类模板的泛型也可以是函数模板的泛型。
// 类模板作为函数的参数, 具体化类模板
void drawShape(Rect<int> &r)
{
r.draw();
cout << "r周长: " << r.length() << endl;
}
// 类模板作为函数的参数, 具体化类模板
// 将类模板的泛型再次泛型,通过函数模板
template <typename T>
void drawShape(Rect<T> &r)
{
r.draw();
cout << "r周长: " << r.length() << endl;
}
1.6.2 类模板派生普通类
类模板派生普通类的时候,必须指定具体的泛型的数据类型,以确保子类对象创建时的具体的父类
template<typename T>
class Shape{
protected:
T mLen;
public:
virtual T length(){
return mLen;
}
};
class Rect: public Shape<int>{
private:
int w,h;
public:
Rect(int w, int h): w(w),h(h){
mLen = 2*(w+h);
}
};
class Triangle: public Shape<float>{
private:
float a, b,c;
public:
Triangle(float a, float b, float c){
this->a = a;
this->b = b;
this->c = c;
mLen = a+b+c;
}
};
1.6.3 类模板派生类模板
类模板派生子类时,子类也可以是类模板,将父类中的泛型指定为子类的类模板泛型
【注】派生的子类模板的类内部不能访问父类的泛型成员,并且子类模板创建对象时,必须指定泛型的具体的类型
template <typename T>
class Shape
{
public:
virtual T length() = 0;
};
template <typename T>
class Rect : public Shape<T>
{
private:
T w, h;
public:
Rect(T w, T h) : w(w), h(h)
{
}
virtual T length()
{
return 2 * (w + h);
}
};
void test()
{
Rect<int> r1(10, 20);
cout << "r1的周长: " << r1.length() << endl;
return 0;
}
1.6.4 类模板类内实现
在类模板内部的成员函数中,可以使用泛型成员变量
template <typename T1, typename T2>
class Point
{
private:
T1 x;
T2 y;
public:
Point(T1 x, T2 y) : x(x), y(y) {}
// T1, T2是哪一个对象的泛型的具体化: 是当前类对象的泛型
// 此函数要求:other对象的泛型同当前类对象的泛型保持一致,否则编译器认为一个其它的类
int distancePow(Point<T1, T2> &other)
{
return (x - other.x) * (x - other.x) + (y - other.y) * (y - other.y);
}
};
1.6.5 类模板类外实现
类外部实现成员函数时,指定类模板的泛型转化为函数模板
template<typename T>
class Point{
private:
T x, y;
public:
Point(T x, T y);
void show();
};
template<typename T>
Point<T>::Point(T x, T y){
this->x = x;
this->y = y;
}
template<typename T>
void Point<T>::show(){
cout << "x=" << x << ",y=" << y << endl;
}
1.6.6 类模板头文件与源文件分离的问题
在使用类模板的情况下,声明与实现必须放在一起
在 Linux 和 vs 编辑器下如果只包含头文件,那么会报错链接错误,需要包含 cpp 文件,但是如果类模板中有友元类,那么编译失败!
类模板需要二次编译,在出现模板的地方编译一次,在调用模板的地方再次编译。C++编译规则为独立编译。
1.6.7 模板类遇到友元函数
模板类中的友元函数的写法:1) 内部实现 2) 外部实现 3)友元函数模板
内部实现:
template <typename T>
class A
{
// 内部实现友元函数, 接收当前类模板的引用时,可以指定当前的类模板的泛型
// 友元函数(全局性的),类的外部直接访问,不需要类对象调用
friend void showIn(A<T> &a)
{
cout << a.item << endl;
}
private:
T item;
public:
A(T item)
{
this->item = item;
}
};
外部实现:
外部实现的友元函数是函数模板时,必须在类定义之前声明。在类中声明友元全局函数时,必须使用<>空泛型表示此函数是函数模板。
template <typename T>
class A;
template <typename T>
void showOut(A<T> &a);
template <typename T>
class A
{
// 外部实现的友元函数,它有自己的模板
// <> 空泛型表示外部是 函数模板,模板的泛型同当前类的泛型
// 要求:必须之前先声明此函数为函数模板
friend void showOut<>(A<T> &a);
private:
T item;
public:
A(T item)
{
this->item = item;
}
};
template <typename T>
void showOut(A<T> &a) // 全局友元函数
{
cout << "out item is " << a.item << endl;
}
友元函数模板:
格式:
template<typename T>
friend 返回值类型 函数名(T &参数名);
template <typename T>
class A
{
// 友元函数模板, 声明的友元函数名不需要加 <> 空泛型
template <typename U>
friend void show(A<U> &a);
private:
T item;
public:
A(T item)
{
this->item = item;
}
};
template <typename U>
void show(A<U> &a)
{
cout << "template item is " << a.item << endl;
}
2. C++类型转换
类型转换(cast)是将一种数据类型转换成另一种数据类型,一般数据类型转换由编译器自动完成,除非强制类型转换需要手动完成
【注意】一般情况下,尽量少的去使用类型转换,除非用来解决非常特殊的问题
语法:
目标类型 变量名 = 新式转换函数名<目标类型>(转换变量);
2.1 静态转换
static_cast静态转换函数模板
上行转换和下行转换:
- 上行转换:把派生类的指针或引用转换成基类表示,安全的。【多态的体现】
- 下行转换:把基类指针或引用转换成派生类表示,不安全的。【调用子类的扩展功能】
1.不同的基本数据类型的指针不能相互静态转换
2.支持父类与子类的指针的静态上行或下行的转换。
3.不支持不相关的两类的引用或指针转换
2.2 动态转换
dynamic_cast动态转换函数模板
主要用于类层次间的上行转换和下行转换;
在类层次间进行上行转换时,动态和静态转换的效果是一样的
在进行下行转换时,动态转换具有类型检查的功能,比静态更安全
不支持基本的数据类型之间的转换
2.3 常量转换
const_cast常量转换函数模板
可以将指针或引用转化为const指针或引用变量,也可以将const指针或引用变量转化为指针或引用变量
【注】不能直接对非指针和非引用的变量使用const_cast去直接移除它的const
2.4 重新解释转换
reinterpret_cast重新解释转换函数模板
主要用于将一种数据类型转为另一种;它可以将一个指针转换成一个整数,也可以将一个整数转换成一个指针。
这是最不安全的一种转换机制。
3. C++异常
3.1 异常基本概念
C语言中处理异常的方式:不会中断程序的执行
1)返回常量值,如0成功,1失败
2)宏 errno (类似于全局整数类型的变量),记录程序出现异常的标识
perror()进行打印错误信息
3)NULL 指针的NULL表示,表示异常的情况
FILE *f = fopen("不存在的文件","r");
C++处理异常的方式:抛出异常、捕获异常
3.2 C++异常语法
3.2.1 throw抛出异常
语法:throw 异常数据
异常数据包含基本数据类型、类、struct等
【注】如果抛出的异常没有处理时,则会中断程序的执行
3.2.2 处理异常的语法
语法格式:
try{
执行可能存在异常的语句块;
}catch(异常数据的类型1 变量){ //捕获相应数据类型的异常信息
//处理异常的语句;
}catch(异常数据的类型2 变量){
//处理异常的语句;
}
...
catch(...){ //必须放在最后
//以上异常类型都没有匹配成功时,执行的语句
}
【小结】如果异常被捕获之后,程序则不会中断。捕获异常信息的时候,一定考虑异常信息的数据类型。
3.2.3 throw的限制与严格类型异常匹配
在使用throw抛出异常信息时,受到函数声明处的throw()声明的可抛出异常类型的限制
1.函数内可以抛出任何异常
#include <iostream>
using namespace std;
class A
{
public:
int n;
A(int n) : n(n) {}
};
void show(int x)
{
if (x == 1)
throw 0;
else if (x == 2)
throw 'a';
else if (x == 3)
throw "abc";
else if (x == 4)
throw 1.25;
else if (x == 5)
throw A(100);
cout << "x=" << x << endl;
}
int main()
{
try
{
show(5);
}
catch (int error)
{ // 与throw int 匹配
cout << "error is " << error << endl;
}
catch (const char &error)
{
cout << "error is " << error << endl;
}
catch (A &error)
{
cout << "error is " << error.n << endl;
}
}
2.限制函数抛出的异常类型
#include <iostream>
using namespace std;
class A
{
public:
int n;
A(int n) : n(n) {}
};
void show(int x) throw(int,char)
{
if (x == 1)
throw 0;
else if (x == 2)
throw 'a';
else if (x == 3)
throw "abc";
else if (x == 4)
throw 1.25;
else if (x == 5)
throw A(100);
cout << "x=" << x << endl;
}
int main()
{
try
{
show(5);
}
catch (int error)
{ // 与throw int 匹配
cout << "error is " << error << endl;
}
catch (const char &error)
{
cout << "error is " << error << endl;
}
catch (A &error)
{
cout << "error is " << error.n << endl;
}
}
3.限制函数抛出异常
在函数的声明位置使用throw()
#include <iostream>
using namespace std;
void show(int x) throw()
{
if (x == 1)
throw 0;
cout << "x=" << x << endl;
}
int main()
{
try
{
show(1);
}
catch (int error)
{ // 与throw int 匹配
cout << "error is " << error << endl;
}
}
3.2.4 栈解旋(unwinding)
异常被抛出后,从进入try块起,到异常被抛掷前,这期间在栈上构造的所有对象,都会被自动析构,析构的顺序与构造的顺序相反,这一过程称为栈的解旋
#include <iostream>
using namespace std;
class A
{
public:
A()
{
cout << "A()" << endl;
}
~A()
{
cout << "~A()" << endl;
}
};
int main(int argc, char const *argv[])
{
try
{
A a1;
throw 0; // 抛出异常时,则会回收 a1栈中的空间(解旋)
}
catch (...)
{
cout << "异常被处理" << endl;
}
return 0;
}
3.2.5 异常接口声明
在声明函数时,可以声明throw()可抛出的异常接口(基本数据类型、类、结构体)。即为函数内限制throw抛出异常信息的类型
void func();则此函数可以抛任何类型的异常
void func() throw(A,B,C);这个函数func能够且只能抛出类型A,B,C及其子类型的异常
void func() throw();不抛任何类型异常的函数
3.2.6 异常变量生命周期
catch()捕获异常信息的对象的生成周期,如果声明时变量或对象时,会在捕获到时,则会在栈中临时会创建变量或对象空间
3.2.7 异常的多态
throw抛出的异常信息时属于子类对象,catch捕获异常的类型是父类的引用或指针
3.3 C++标准异常库
C++提供了异常类的基类:exception
3.3.1 标准异常库的说明
c++提供的异常类的常用类:使用时引入<exception>
头文件
标准异常类的成员:
1)每个类都有提供了构造函数,拷贝构造函数和赋值操作符重载
2)logic_error 类及其子类、runtime_error类及其子类,它们的构造函数是接受一个string类型的形式参数,用于异常信息的描述
3)所有的异常类都有一个what()方法,返回const char* 类型(C风格字符串)的值,描述异常信息
标准异常类的具体描述:
exception 所有标准异常类的父类
bad_alloc 当operator new and operator new[]请求分配内存失败时
bad_exception 这是个特殊的异常,如果函数的异常抛出列表里声明了bad_exception异常,当函数内部抛出了异常抛出列表中没有的异常,这时调用的 unexpected 函数中若抛出异常,不论什么类型,都会
被替换为 bad_exception 类型。
bad_typeid 使用typeid操作符,操作一个NULL指针,而该指针是带有虚函数的类,这时抛出bad_typeid异常
bad_cast 使用dynamic_cast转换引用失败的时候
ios_base::failure io操作过程出现错误
logic_error 逻辑错误,可以在运行前检测的错误
runtime_error 运行时错误,仅在运行时才可以检测的错误
logic_error的子类:
length_error 试图生成一个超出该类型最大长度的对象时,例如vector的resize操作
domain_error 参数的值域错误,主要用在数学函数中,例如使用一个负值调用只能操作非负数的函数
out_of_range 超出有效范围
invalid_argument 参数不适合,在标准库中,当利用string对象构造bitset时,而string中的字符不是'0'或'1'的时候,抛出该异常
runtime_error的子类:
range_error 计算结果超出了有意义的值域范围
overflow_error 算术计算上溢
underflow_error 算术计算下溢
3.3.2 自定义标准异常类
1.自己的异常类要继承标准异常类
因为C++中可以抛出任何类型的异常,所以我们的异常类可以不继承自标准异常,但是这样可能会导致程序混乱,尤其是当我们多人协同开发时。
2.当继承标准异常类时,应该重载父类的what函数和虚析构函数。
3.因为栈展开的过程中,要赋值异常类型,那么要根据你在类中添加的成员考虑是否提供自己的复制构造函数
如:自定义OutOfRangeError
class OutOfRangeError : public exception
{
public:
virtual const char *what() const throw()
{
return "访问位置越界";
}
};
标签:函数,c++,template,泛型,异常,throw,模板
From: https://www.cnblogs.com/dijun666/p/17851149.html