C/C++学习之路: 模板和异常
目录
模板 类型转换 异常
1. 模板
1. 模板概述
c++提供了函数模板(function template),函数模板实际上是建立一个通用函数,其函数类型和形参类型不具体制定,用一个虚拟的类型来代表,这个通用函数就成为函数模板。 凡是函数体相同的函数都可以用这个模板代替,不必定义多个函数,只需在模板中定义一次即可。 在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现不同函数的功能。
c++提供两种模板机制:函数模板和类模板 类属 - 类型参数化,又称参数模板 总结:
模板把函数或类要处理的数据类型参数化,表现为参数的多态性,成为类属。 模板用于表达逻辑结构相同,但具体数据元素类型不同的数据对象的通用行为。
2. 函数模板
用模板是为了实现泛型,可以减轻编程的工作量,增强函数的重用性。
void SwapInt ( int & a, int & b) {
int temp = a;
a = b;
b = temp;
}
void SwapChar ( char & a, char & b) {
char temp = a;
a = b;
b = temp;
}
template < class T >
void MySwap ( T& a, T& b) {
T temp = a;
a = b;
b = temp;
}
void test01 ( ) {
int a = 10 ;
int b = 20 ;
cout << "a:" << a << " b:" << b << endl;
MySwap ( a, b) ;
cout << "a:" << a << " b:" << b << endl;
char c1 = 'a' ;
char c2 = 'b' ;
cout << "c1:" << c1 << " c2:" << c2 << endl;
MySwap< char > ( c1, c2) ;
cout << "c1:" << c1 << " c2:" << c2 << endl;
}
3. 函数模板和普通函数区别
函数模板不允许自动类型转化 普通函数能够自动进行类型转化
template < class T >
T MyPlus ( T a, T b) {
T ret = a + b;
return ret;
}
int MyPlus ( int a, char b) {
int ret = a + b;
return ret;
}
void test02 ( ) {
int a = 10 ;
char b = 'a' ;
MyPlus ( a, a) ;
MyPlus ( b, b) ;
MyPlus ( a, b) ;
MyPlus ( b, a) ;
}
4. 函数模板和普通函数在一起调用规则
c++编译器优先考虑普通函数 可以通过空模板实参列表的语法限定编译器只能通过模板匹配 函数模板可以像普通函数那样可以被重载 如果函数模板可以产生一个更好的匹配,那么选择模板
template < class T >
T MyPlus ( T a, T b) {
T ret = a + b;
return ret;
}
int MyPlus ( int a, int b) {
int ret = a + b;
return ret;
}
void test03 ( ) {
int a = 10 ;
int b = 20 ;
char c = 'a' ;
char d = 'b' ;
cout << MyPlus ( a, b) << endl;
cout << MyPlus< > ( a, b) << endl;
cout << MyPlus ( c, d) ;
}
5. 模板机制解析
编译器并不是把函数模板处理成能够处理任何类型的函数 函数模板通过具体类型产生不同的函数 编译器会对函数模板进行两次编译,在声明的地方对模板代码本身进行编译,在调用的地方对参数替换后的代码进行编译。
6. 模板的局限性
假设有如下模板函数:
template < class T >
void f ( T a, T b)
{ … }
如果代码实现时定义了赋值操作 a = b,但是T为数组,这种假设就不成立了 同样,如果里面的语句为判断语句 if(a>b),但T如果是结构体,该假设也不成立,另外如果是传入的数组,数组名为地址,因此它比较的是地址,而这也不是我们所希望的操作。 总之,编写的模板函数很可能无法处理某些类型,另一方面,有时候通用化是有意义的,但C++语法不允许这样做。为了解决这种问题,可以提供模板的重载,为这些特定的类型提供具体化的模板。
7. 类模板
类模板和函数模板的定义和使用类似,有时,有两个或多个类,其功能是相同的,仅仅是数据类型不同。 类模板用于实现类所需数据的类型参数化
1. 类模板做函数参数
template < class NameType , class AgeType >
class Person {
public :
Person ( NameType name, AgeType age) {
this -> mName = name;
this -> mAge = age;
}
void showPerson ( ) {
cout << "name: " << this -> mName << " age: " << this -> mAge << endl;
}
public :
NameType mName;
AgeType mAge;
} ;
void test01 ( ) {
Person< string, int > P1 ( "德玛西亚" , 18 ) ;
P1. showPerson ( ) ;
}
2. 类模板派生普通类
template < class T >
class MyClass {
public :
MyClass ( T property) {
this -> mProperty = property;
}
public :
T mProperty;
} ;
class SubClass : public MyClass < int > {
public :
SubClass ( int b) : MyClass< int > ( 20 ) {
this -> mB = b;
}
public :
int mB;
} ;
3. 类模板派生类模板
template < class T >
class Base {
T m;
} ;
template < class T >
class Child2 : public Base < double > {
public :
T mParam;
} ;
void test2 ( ) {
Child2< int > d2;
}
4. 类模板类内实现
template < class NameType , class AgeType >
class Person {
public :
Person ( NameType name, AgeType age) {
this -> mName = name;
this -> mAge = age;
}
void showPerson ( ) {
cout << "name: " << this -> mName << " age: " << this -> mAge << endl;
}
public :
NameType mName;
AgeType mAge;
} ;
void test01 ( ) {
Person< string, int > P1 ( "德玛西亚" , 18 ) ;
P1. showPerson ( ) ;
}
5. 类模板类外实现
template < class T1 , class T2 >
class Person {
public :
Person ( T1 name, T2 age) ;
void showPerson ( ) ;
public :
T1 mName;
T2 mAge;
} ;
template < class T1 , class T2 >
Person < T1, T2> :: Person ( T1 name, T2 age) {
this -> mName = name;
this -> mAge = age;
}
template < class T1 , class T2 >
void Person < T1, T2> :: showPerson ( ) {
cout << "Name:" << this -> mName << " Age:" << this -> mAge << endl;
}
void test4 ( ) {
Person< string, int > p ( "Obama" , 20 ) ;
p. showPerson ( ) ;
}
6. 模板类遇到友元函数
template < class T1 , class T2 >
class Person ;
template < class T1 , class T2 >
void PrintPerson2 ( Person< T1, T2> & p) ;
template < class T1 , class T2 >
class Person {
friend void PrintPerson ( Person< T1, T2> & p) {
cout << "Name:" << p. mName << " Age:" << p. mAge << endl;
}
friend void PrintPerson2< > ( Person< T1, T2> & p) ;
template < class U1 , class U2 >
friend void PrintPerson ( Person< U1, U2> & p) ;
public :
Person ( T1 name, T2 age) {
this -> mName = name;
this -> mAge = age;
}
void showPerson ( ) {
cout << "Name:" << this -> mName << " Age:" << this -> mAge << endl;
}
private :
T1 mName;
T2 mAge;
} ;
void test01 ( ) {
Person< string, int > p ( "Jerry" , 20 ) ;
PrintPerson ( p) ;
}
template < class T1 , class T2 >
void PrintPerson2 ( Person< T1, T2> & p) {
cout << "Name2:" << p. mName << " Age2:" << p. mAge << endl;
}
void test02 ( ) {
Person< string, int > p ( "Jerry" , 20 ) ;
PrintPerson2 ( p) ;
}
int main ( ) {
test02 ( ) ;
system ( "pause" ) ;
return EXIT_SUCCESS;
}
2. 类型转换
类型转换(cast)是将一种数据类型转换成另一种数据类型。 例如,如果将一个整型值赋给一个浮点类型的变量,编译器会暗地里将其转换成浮点类型。 转换是非常有用的,但是它也会带来一些问题,比如在转换指针时,我们很可能将其转换成一个比它更大的类型,但这可能会破坏其他的数据。 所以应该小心类型转换,因为转换也就相当于对编译器说:忘记类型检查,把它看做其他的类型。 一般情况下,尽量少的去使用类型转换,除非用来解决非常特殊的问题。 标准c++提供了一个显示的转换的语法,来替代旧的C风格的类型转换。 使用C风格的强制转换可以把想要的任何东西转换成我们需要的类型。那为什么还需要一个新的C++类型的强制转换呢? 新类型的强制转换可以提供更好的控制强制转换过程,允许控制各种不同种类的强制转换。C++风格的强制转换其他的好处是,它们能更清晰的表明它们要干什么。程序员只要扫一眼这样的代码,就能立即知道一个强制转换的目的。
1. 静态转换(static_cast)
用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换。 进行上行转换(把派生类的指针或引用转换成基类表示)是安全的; 进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的。 用于基本数据类型之间的转换,如把int转换成char,把char转换成int。这种转换的安全性也要开发人员来保证。
class Animal {
} ;
class Dog : public Animal {
} ;
class Other {
} ;
void test01 ( ) {
char a = 'a' ;
double b = static_cast < double > ( a) ;
}
void test02 ( ) {
Animal * animal01 = NULL ;
Dog * dog01 = NULL ;
Animal * animal02 = static_cast < Animal * > ( dog01) ;
Dog * dog02 = static_cast < Dog * > ( animal01) ;
}
void test03 ( ) {
Animal ani_ref;
Dog dog_ref;
Animal & animal01 = ani_ref;
Dog & dog01 = dog_ref;
Animal & animal02 = static_cast < Animal & > ( dog01) ;
Dog & dog02 = static_cast < Dog & > ( animal01) ;
}
void test04 ( ) {
Animal * animal01 = NULL ;
Other * other01 = NULL ;
}
2. 动态转换
dynamic_cast主要用于类层次间的上行转换和下行转换; 在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的; 在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全;
class Animal {
public :
virtual void ShowName ( ) = 0 ;
} ;
class Dog : public Animal {
virtual void ShowName ( ) {
cout << "I am a dog!" << endl;
}
} ;
class Other {
public :
void PrintSomething ( ) {
cout << "我是其他类!" << endl;
}
} ;
void test01 ( ) {
int a = 10 ;
}
void test02 ( ) {
Animal * animal01 = NULL ;
Dog * dog01 = new Dog;
Animal * animal02 = dynamic_cast < Animal * > ( dog01) ;
animal02-> ShowName ( ) ;
}
void test03 ( ) {
Dog dog_ref;
Dog & dog01 = dog_ref;
Animal & animal02 = dynamic_cast < Animal & > ( dog01) ;
animal02. ShowName ( ) ;
}
void test04 ( ) {
Animal * animal01 = NULL ;
Other * other = NULL ;
}
3. 常量转换(const_cast)
const_cast运算符用来修改类型的const属性。 常量指针被转化成非常量指针,并且仍然指向原来的对象; 常量引用被转换成非常量引用,并且仍然指向原来的对象; 注意:不能直接对非指针和非引用的变量使用const_cast操作符去直接移除它的const.
void test01 ( ) {
const int * p = NULL ;
int * np = const_cast < int * > ( p) ;
int * pp = NULL ;
const int * npp = const_cast < const int * > ( pp) ;
const int a = 10 ;
}
void test02 ( ) {
int num = 10 ;
int & refNum = num;
const int & refNum2 = const_cast < const int & > ( refNum) ;
}
4. 重新解释转换(reinterpret_cast)
这是最不安全的一种转换机制,最有可能出问题。 主要用于将一种数据类型从一种类型转换为另一种类型。 它可以将一个指针转换成一个整数,也可以将一个整数转换成一个指针。
3. 异常
1. 异常基本概念
异常处理就是处理程序中的错误。所谓错误是指在程序运行的过程中发生的一些异常事件(如:除0溢出,数组下标越界,所要读取的文件不存在,空指针,内存不足等等) c++异常机制相比C语言异常处理的优势?
函数的返回值可以忽略,但异常不可忽略。如果程序出现异常,但是没有被捕获,程序就会终止,这多少会促使程序员开发出来的程序更健壮一点。而如果使用C语言的error宏或者函数返回值,调用者都有可能忘记检查,从而没有对错误进行处理,结果造成程序莫名其面的终止或出现错误的结果。 整型返回值没有任何语义信息。而异常却包含语义信息,有时你从类名就能够体现出来。 整型返回值缺乏相关的上下文信息。异常作为一个类,可以拥有自己的成员,这些成员就可以传递足够的信息。 异常处理可以在调用跳级。这是一个代码编写时的问题:假设在有多个函数的调用栈中出现了某个错误,使用整型返回码要求你在每一级函数中都要进行处理。而使用异常处理的栈展开机制,只需要在一处进行处理就可以了,不需要每级函数都处理。
int A_MyDivide ( int a, int b) {
if ( b == 0 ) {
return - 1 ;
}
return a / b;
}
int B_MyDivide ( int a, int b) {
int ba = a + 100 ;
int bb = b;
int ret = A_MyDivide ( ba, bb) ;
return ret;
}
int C_MyDivide ( ) {
int a = 10 ;
int b = 0 ;
int ret = B_MyDivide ( a, b) ;
if ( ret == - 1 ) {
return - 1 ;
} else {
return ret;
}
}
2. 异常语法
int A_MyDivide ( int a, int b) {
if ( b == 0 ) {
throw 0 ;
}
return a / b;
}
int B_MyDivide ( int a, int b) {
int ba = a;
int bb = b;
int ret = A_MyDivide ( ba, bb) + 100 ;
return ret;
}
int C_MyDivide ( ) {
int a = 10 ;
int b = 0 ;
int ret = 0 ;
# if 1
ret = B_MyDivide ( a, b) ;
# else
try {
ret = B_MyDivide ( a, b) ;
}
catch ( int e) {
cout << "C_MyDivide Call B_MyDivide 除数为:" << e << endl;
}
# endif
return ret;
}
int main ( ) {
C_MyDivide ( ) ;
system ( "pause" ) ;
return EXIT_SUCCESS;
}
若有异常则通过throw操作创建一个异常对象并抛出。 将可能抛出异常的程序段放到try块之中。 如果在try段执行期间没有引起异常,那么跟在try后面的catch字句就不会执行。 catch子句会根据出现的先后顺序被检查,匹配的catch语句捕获并处理异常(或继续抛出异常) 如果匹配的处理未找到,则运行函数terminate将自动被调用,其缺省功能调用abort终止程序。 处理不了的异常,可以在catch的最后一个分支,使用throw,向上抛。 c++异常处理使得异常的引发和异常的处理不必在一个函数中,这样底层的函数可以着重解决具体问题,而不必过多的考虑异常的处理。上层调用者可以在适当的位置设计对不同类型异常的处理。
1. 异常严格类型匹配
异常机制和函数机制互不干涉,但是捕捉方式是通过严格类型匹配。
void TestFunction ( ) {
cout << "开始抛出异常..." << endl;
string ex = "string exception!" ;
throw ex;
}
int main ( ) {
try {
TestFunction ( ) ;
}
catch ( int ) {
cout << "抛出Int类型异常!" << endl;
}
catch ( char ) {
cout << "抛出Char类型异常!" << endl;
}
catch ( char * ) {
cout << "抛出Char*类型异常!" << endl;
}
catch ( string) {
cout << "抛出string类型异常!" << endl;
}
catch ( . . . ) {
cout << "抛出其他类型异常!" << endl;
}
system ( "pause" ) ;
return EXIT_SUCCESS;
}
2. 栈解旋(unwinding)
异常被抛出后,从进入try块起,到异常被抛掷前,这期间在栈上构造的所有对象,都会被自动析构。析构的顺序与构造的顺序相反,这一过程称为栈的解旋(unwinding).
class Person {
public :
Person ( string name) {
mName = name;
cout << mName << "对象被创建!" << endl;
}
~ Person ( ) {
cout << mName << "对象被析构!" << endl;
}
public :
string mName;
} ;
void TestFunction ( ) {
Person p1 ( "aaa" ) ;
Person p2 ( "bbb" ) ;
Person p3 ( "ccc" ) ;
throw 10 ;
}
int main ( ) {
try {
TestFunction ( ) ;
}
catch ( . . . ) {
cout << "异常被捕获!" << endl;
}
system ( "pause" ) ;
return EXIT_SUCCESS;
}
3. 异常接口声明
为了加强程序的可读性,可以在函数声明中列出可能抛出异常的所有类型,例如:void func() throw(A,B,C);这个函数func能够且只能抛出类型A,B,C及其子类型的异常。 如果在函数声明中没有包含异常接口声明,则此函数可以抛任何类型的异常,例如:void func() 一个不抛任何类型异常的函数可声明为:void func() throw() 如果一个函数抛出了它的异常接口声明所不允许抛出的异常,unexcepted函数会被调用,该函数默认行为调用terminate函数中断程序。
void TestFunction01 ( ) {
throw 10 ;
}
void TestFunction02 ( ) throw ( int , char , char * ) {
string exception = "error!" ;
throw exception;
}
void TestFunction03 ( ) throw ( ) {
throw 10 ;
}
int main ( ) {
try {
}
catch ( . . . ) {
cout << "捕获异常!" << endl;
}
system ( "pause" ) ;
return EXIT_SUCCESS;
}
4. 异常变量生命周期
throw的异常是有类型的,可以是数字、字符串、类对象。 throw的异常是有类型的,catch需严格匹配异常类型。
class MyException {
public :
MyException ( ) {
cout << "异常变量构造" << endl;
} ;
MyException ( const MyException & e) {
cout << "拷贝构造" << endl;
}
~ MyException ( ) {
cout << "异常变量析构" << endl;
}
} ;
void DoWork ( ) {
throw new MyException ( ) ;
}
void test01 ( ) {
try {
DoWork ( ) ;
}
catch ( MyException e) {
cout << "捕获 异常" << endl;
}
}
void test02 ( ) {
try {
DoWork ( ) ;
}
catch ( MyException & e) {
cout << "捕获 异常" << endl;
}
}
void test03 ( ) {
try {
DoWork ( ) ;
}
catch ( MyException * e) {
cout << "捕获 异常" << endl;
delete e;
}
}
5. 异常的多态使用
class BaseException {
public :
virtual void printError ( ) { } ;
} ;
class NullPointerException : public BaseException {
public :
virtual void printError ( ) {
cout << "空指针异常!" << endl;
}
} ;
class OutOfRangeException : public BaseException {
public :
virtual void printError ( ) {
cout << "越界异常!" << endl;
}
} ;
void doWork ( ) {
throw NullPointerException ( ) ;
}
void test ( ) {
try {
doWork ( ) ;
}
catch ( BaseException & ex) {
ex. printError ( ) ;
}
}