函数模板
C++最重要的特性之一就是代码重用,为了实现代码重用,代码必须具有通用性。通用代码应不受数据类型的限制,并且可以自适应数据类型的变化。这种程序设计方法称为参数化程序设计。模板是C++支持参数化设计的工具,通过它可以实现参数化多态性。
参数化多态,就是将程序所处理的对象的类型参数化,使得一段程序可以用于处理多种不同类型的对象
int abs(int x){
return x < 0 ? -x : x;
}
double abs(double x){
return x < 0 ? -x : x;
}
这两个函数只有参数类型不同,功能完全相同(函数体一样)。如果能写一段通用代码,能适合于多种不同数据类型,便会使代码的可重用性大大提高,从而提高软件的开发效率。使用函数模板可以实现这一目的。
程序员只需要对函数模板编写一次,然后基于调用函数时提供的参数类型,C++编译器将自动产生相应的函数来正确处理该类型的数据。
template<typename T>
T abs(T x){
return x < 0 ? -x : x;
}
int main() {
int n = -1;
double d = -2.5;
cout << abs(n) << endl;
cout << abs(d) << endl;
return 0;
}
当类型参数含义确定后,编译器将以函数模板为样板,生成一个函数,这一过程称为函数模板的实例化,该函数称为函数模板abs的一个实例。因此当主函数第一次调用abs时,执行的实际上函数模板生成的函数:
int abs(int x);
第二次调用abs时,执行的实际上是由函数模板生成的函数:
double abs(double x);
template<typename T>
void show(const T* array, int count){
for(int i=0;i<count;i++){
cout << *(array+i) << " ";
}
printf("\n");
}
int main() {
int a[] = {1, 2, 3, 4, 5};
double d[] = {1.1, 2.1, 3.2, 4.5, 6.6};
char s[] = "i love china!";
show(a, sizeof(a)/sizeof(a[0]));
show(d, sizeof(d)/sizeof(d[0]));
show(s, strlen(s));
return 0;
}
修改Complex类,使其能够适应以上函数,完成Complex数组的排序和输出
函数模板本身在编译时不会生成任何目标代码,只有由模板生成的实例会生成目标代码 被多个源文件引用的函数模板,应当连同函数体一同放在头文件中,而不能像普通函数那样只将声明放在头文件中。 函数指针也能指向模板的实例,而不能指向模板本身。
类模板
类模板是后期C++加入的一种可以大大提高编程效率的方法。 使用类模板使用户可以为类定义一种模式,使得类中的某些数据成员,某些成员函数的参数,返回值或局部变量能取任意类型(包括基本数据类型和用户自定义的)。由于类模板需要一种或多种类型参数,所以类模板也常称为参数化类。
一个类模板声明并不是一个类,它说明了类的一个家族。只有当被其它代码引用时,模板才根据需要生成具体的类 增加一个Point类,并修改以上代码,使之能也能够适合Point类对象的存储 用类模板设计一个队列(可选用数组或链表),解决n个人围成一圈的问题
template<typename T>
class Queue {
public:
Queue(int size){
MAX_SIZE = size;
tp = new T[size];
this->size = 0;
this->start = 0;
this->end = -1;
}
~Queue(){
delete tp;
}
void push(T dat){
if(size == MAX_SIZE){
printf("Stack is overflow!\n");
return;
}
end++;
end%=MAX_SIZE;
tp[end] = dat;
size++;
}
T pop(){
if(size == 0){
printf("Stack is empty!\n");
exit(1);
}
T temp = tp[start++];
start%=MAX_SIZE;
size--;
return temp;
}
int length(){
return size;
}
void travel(){
int len = size;
int start = this->start;
printf(":===============================\n");
printf("size=%d\n", size);
while(len--){
printf("%d", tp[start++]);
start%=MAX_SIZE;
}
printf("\n===============================;\n");
}
private:
int MAX_SIZE;
int size;
T* tp;
int start ; //position of first element , infected by pop
int end ; //position of last element , infected by push
};
int main() {
Queue<int> queue(30);
for(int i=1;i<=13;i++){
queue.push(i);
}
queue.travel();
int count = 0;
while(queue.length()){
count++;
int temp = queue.pop();
if(count % 3 == 0){
printf("%d\n", temp);
}else{
queue.push(temp);
}
}
queue.travel();
return 0;
}
异常处理的思想
异常就是运行时的错误
C++的异常处理机制使得异常的引发和处理不必在同一函数中,这样底层的函数可以着重解决具体问题,而不必过多地考虑对异常的处理。上层调用者可以在适当的位置设计对不同类型异常的统一处理。
如果某段程序发现了自己不能或不想处理的异常,就可以使用throw表达式抛出这个异常,将它抛给调用者。throw的操作数表示异常类型,语法上与return语句的操作数类似。如果程序中有多种要抛出的异常,应该用不同的操作数类型来互相区别。 try子句后的复合语句是受保护的代码段。如果预料某段程序代码或对某函数的调用可能发生异常,就将它放在try块中。如果这段代码运行时真的发生异常,其中的throw表达式就会抛出这个异常。 catch子句是异常处理程序,捕获由throw表达式抛出的异常。异常声明部分指明了子句处理异常的类型和异常参数名称。类型可以是任何有效的数据类型,包括C++的类。当异常被抛出以后,catch子句便依次检查,若某个catch子句声明的异常类型与被抛出的异常类型一致,则执行该段代码。如果异常类型是一个省略号,catch子句便处理所有类型的异常,这段处理程序必须是catch子句的最后一个分支。
当以下条件之一成立时,抛出的异常与一个catch子句中声明的异常类型匹配
catch子句中声明的异常类型就是抛出的异常对象的类型或其引用
catch子句中声明的异常类型是抛出异常对象的类型的公共基类或其引用
抛出异常类型和catch子句中声明的异常类型皆为指针,且前者到后者可以隐含转换
除零异常
int divide(int x, int y){
if(y == 0){
throw x;
}
return x/y;
}
int main() {
try{
cout << "5/2=" << divide(5,2) << endl;
cout << "8/0=" << divide(8,0) << endl;
cout << "7/1=" << divide(7,1) << endl;
}catch(int e){
cout << e << " is divided by zero!" << endl;
}
cout << "That is ok." << endl;
return 0;
}
总结:
异常在divide函数中被抛出,由于在divide函数中没有对异常进行处理,
catch处理程序的出现顺序很重要,因为在一个try块中,异常处理程序是按照它出现的次序被检查和匹配的。只要找到一个匹配的异常类型,后面的异常处理都将被忽略。所以catch(...)应该放在最后
异常接口声明
为了增强程序的可读性和安全性,使函数的用户能够方便地知道所使用的函数会抛出哪些异常,可以在函数的声明中列出这个函数可能抛出的所有异常类型,例如: void fun() throw (A, B, C, D); 如果在函数的声明中没有包括异常接口声明,则此函数可以抛出任何类型的异常,一个不抛出任何类型异常的函数可以这样声明: void fun() throw (); 如果一个函数抛出了它的异常声明所不允许抛出的异常时, unexpected函数会被调用,该函数的默认行为是调用terminate函数中止程序。用户也可以定义自己的unexpected函数,替换默认函数。
int divide(int x, int y) throw( int){
float b = 10;
if(y == 0){
throw b;
}
return x/y;
}
void myUnexpected(){
printf("Bad Exception!\n");
throw 0;
}
int main() {
set_unexpected(myUnexpected);
}
异常处理中的构造与析构
C++异常处理的真正功能,不仅在于它能够处理各种不同类型的异常,还在于它具有为异常抛出前构造的所有局部对象自动调用析构函数的能力。这一过程称为栈的解旋。
用一个不带操作数的throw表达式,可以将当前正在被处理的异常再次抛出,这样一个表达式只能出现catch子句或catch子句内部调用的函数中,再次抛出的异常是源异常对象,不是副本