例子1 复数类----所有函数写的类的内部
#include<iostream>using namespace std;template<typename T>class Complex{ firend ostream & operator<<(ostream &out, Complex &c3) { cout << "a:" << " + " << "b:" << c3.b << endl; return out; }public: Complex(T a=0,T b=0); { this->a = a; this->b = b; } Complex operator+(Complex &c2) { Complex tmp(a + c2.a, b + c2.b); return tmp; } void printComplex() { cout << "a:" << a << "b:" << b << endl; } private: T a; T b;};//运算符重载的正规写法,重载<< >>只能用友员函数,其他运算符重载,都要写成成员函数,不要滥用友员函数void main(){//需要把模板类进行具体化以后,才能定义对象,c++编译器要分配内存 Complex<int> c1(1,2); Complex <int> c2(3, 4); Complex<int> c3=c1+c2; cout << c3 << endl; system("pause"); return;}
例子2 复数类----所有函数写的类的外部,但在一个cpp里
#include<iostream>using namespace std;template<typename T>class Complex{ firend Complex MySub<<(Complex &c1, Complex &c2) { Complex tmp(a + c2.a, b + c2.b); return tmp; } firend ostream & operator<< <T> (ostream &out, Complex &c3); public: Complex(T a, T b) Complex operator+(Complex &c2) void printComplex();private: T a; T b;};template<typename T>//构造函数的实现,写在了类的外部Complex<T>::Complex(T a,T b){ this->a = a; this->b = b;}template<typename T>void Complex::printComplex(){ cout << "a:" << a << "b:" << b << endl;}template<typename T>Complex<T> Complex<T>::operator+(Complex<T> &c2)//成员函数实现+运算符重载{ Complex tmp(a + c2.a, b + c2.b); return tmp;}template<typename T>ostream & operator<<(ostream &out, Complex<T>&c3)//友员函数实现运算符重载{ cout << "a:" << " + " << "b:" << c3.b << endl; return out;}void main(){ Complex<int> c1(1, 2); Complex <int> c2(3, 4); Complex<int> c3 = c1 + c2; cout << c3 << endl; system("pause"); return;}
归纳以上的介绍,可以这样使用.声明类模板
1)先写出一个实际的类.由于其语义明确,含义清楚,一般不会出错.
2)将此类中准备改变的类型名(如int 要改为char)改用一个自己指定的虚拟类型名字
3)在类声明前加入一行,格式为:
template<class 虚拟类型函数>//注意本行末尾无分号
4)用类模板定义对象使用以下形式:
类模板名<实际类型名>对象名
类模板名<实际类型名>对象名(实参列表)
如:
Compare<int> cmp;
Compare<int> cmp(3,4);
5)如果在类模板外定义成员函数,应该写成类模板形式:
template<class 虚拟类型函数>
函数类型 类模板名<虚拟函数参数>::成员函数名(参数形参列表)(......)
关于类模板的几点说明
1)类模板的类型参数可以有一个或多个,每个类型前面都必须加class,
如:
template<class T1,class T2>
class someclass
{......}
定义对象时分别带入实际的类型名,如;
someclass<int,double>obj;
2)和使用类一样,使用类模板时要注意其作用域,只能在其有效作用域内用它定义对象.
3)模板可以有层次,一个类模板可以作为基类,派生出派生类的模板,有关这方面的知识实际应用比较少,感兴趣的可以自行查阅.
类模板中的ststic关键字
从类模板比例实例化的每个模板类都有自己的类模板数据成员,该模板类的所有对象共享一个
ststic数据成员.
和非模板类的ststic数据成员一样,模板类的ststic数据成员,也应该在文件范围定义和初始化.
每个模板类都有自己的类模板和ststic数据成员副本.
例子
#include<iostream>using namespace std;template<typename T>class AA{public: static T m_a;private:};class AA1{public: static int m_a;private:};template<typename T>int AA1::m_a = 0;class AA2{public: static char m_a;private:};char AA2::m_a = 0;void main(){ AA<int> a1, a2, a3; a1.m_a = 10; a2.m_a++; a3.m_a++; cout << AA<int>::m_a << endl; AA<char> b1, b2, b3; b1.m_a = 'a'; b2.m_a++; b2.m_a++; cout << AA<char>::m_a << endl; //m_a应该是每一种类型的类,使用自己的m_a system("pause"); return;}
异常问题
一、为什么要有异常——WHY?
1.通过返回值表达错误
像malloc会返回0或1.
局部对象都能正确的析构
层层判断返回值,流程繁琐
例子:
#include <iostream>#include <cstdio>using namespace std;int func3 (void) {FILE* fp = fopen ("none", "r");//fopen失败会返回控指针NULL。if (! fp)return -1;// ...fclose (fp);return 0;}int func2 (void) {if (func3 () == -1)return -1;// ...return 0;}int func1 (void) {if (func2 () == -1)return -1;// ...return 0;}int main (void) {//层层判断返回值if (func1 () == -1) {cout << "执行失败!改天再见!" << endl;return -1;}// ...cout << "执行成功!恭喜恭喜!" << endl;return 0;}
2.通过setjmp/longjmp远程跳转
一步到位进入错误处理,流程简单
局部对象会失去被析构的机会
例子:
#include <iostream>#include <cstdio>#include <csetjmp> //标c的函数,跳转using namespace std;jmp_buf g_env; //jmp是专门为c量身定造的,有类的情况不适用,会跳转,因为不执行右括号,局部对象失去执行析构的机会,不会调用析构函数,会造成内存泄露class A {public:A (void) {cout << "A构造" << endl;}~A (void) {cout << "A析构" << endl;}};void func3 (void) {A a;FILE* fp = fopen ("none", "r");if (! fp)longjmp (g_env, -1); //(没有定义类的时候)这个时候是的g_env变为-1,但是不在这返回,在main函数的setjmp处返回// ...fclose (fp);}void func2 (void) {A a;func3 ();// ...}void func1 (void) {A a;func2 ();// ...}int main (void) {if (setjmp (g_env) == -1) { //(没有定义类的时候)第一次到这,genv是0,所以执行下面的func1(),执行了后在fun3中的longjmp处在缓冲区使得g_env变为1,并在这使g_env返回cout << "执行失败!改天再见!" << endl;return -1;}func1 ();// ...cout << "执行成功!恭喜恭喜!" << endl;return 0;}
---------------------------------------------------------------------
3.异常处理
局部对象都能正确的析构
一步到位进入错误处理,流程简单
---------------------------------------------------------------------
二、异常的语法——WHAT?
1.异常的抛出
throw 异常对象;
异常对象可以是基本类型的变量,也可以是类类型的对象。
当程序执行错误分支时抛出异常。
2.异常的捕获
try {
可能抛出异常的语句块;
}
catch (异常类型1 异常对象1) {
处理异常类型1的语句块;
}
catch (异常类型2 异常对象2) {
处理异常类型2的语句块;
}
...
catch (...) {
处理其它类型异常的语句块;
}
异常处理的流程,始终沿着函数调用的逆序,依次执行右花括号,直到try的右花括号,保证所有的局部对象都能被正确地析构,然会根据异常对象的类型,匹配相应的catch分支,进行有针对性的错误处理。
例子:#include <iostream>#include <cstdio>using namespace std;class A {public:A (void) {cout << "A构造" << endl;}~A (void) {cout << "A析构" << endl;}};void func3 (void) {A a;FILE* fp = fopen ("none", "r");if (! fp) { //如果不发生异常,不执行throw,直接执行throw后面的语句cout << "throw前" << endl;throw -1; //如果有异常,throw之后的语句不执行,直接右括号cout << "throw后" << endl;}cout << "文件打开成功!" << endl;// ...fclose (fp);}void func2 (void) {A a;cout << "func3()前" << endl;func3 (); //如果有异常,则直接右括号cout << "func3()后" << endl;// ...}void func1 (void) {A a;cout << "func2()前" << endl;func2 (); //有异常,直接右括号cout << "func2()后" << endl;// ...}int main (void) {try {cout << "func1()前" << endl;func1 (); //之后进入func1,先创建a,执行构造,再进入func2,又创建a,执行构造,再进入func3,又创建a,执行构造,然后执行throw,抛出-1;结束func3,释放func3中的a,调用析构,然后func2结束,释放func2的a,调用析构,然后func1结束,释放func1的a,调用析构。然后直接到try的右花括号,然后执行异常处理,根据异常对象的类型匹配相应的catch,这里是“执行失败”。cout << "func1()后" << endl;}catch (int ex) {if (ex == -1) {cout << "执行失败!改天再见!" << endl;return -1;}}// ...cout << "执行成功!恭喜恭喜!" << endl;return 0;}