------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
1.赋值运算符的重载
步骤:
1)判断是不是自赋值,如果是的话,则什么操作都不做,直接跳到第四步。
2)如果不是的话,释放掉原来的空间,在堆上开辟一片新的空间。
3)实现参数内容到新分配空间的拷贝。
4)返回*this,实现链式调用。
2.格式(以Account类为例):const Accoutn& operator=(const Account& account)
其中const是为了防止(c1=c2)=c3,因为这样赋值是没有意义的。
为引用是因为:对于简单类型来说int a;int b ;a = b以后,返回值也是a,是它本身
3.三大件:
拷贝构造函数
赋值运算符的重载
析构函数
当类的数据成员包含指针时,并且指针指向堆上的空间。需要写这三大件
4.重载++,—
分为前置和后置,为了区分这两种版本,需要再后置的情况下带一个参数,这个参数没有什么实质的意义,只是为了区分
前置++(以Fraction为例)
Fraction & operator++();
分析:因为前置++允许这种操作:++++c,所以返回值不能是const,又因为++操作改变的是c本身,所以返回值要是引用。
后置++
const Fraction operator++(int)
分析:因为后置++不允许这个操作:c++++,所以返回值要是const,因为后置++返回值是一个局部变量,所以返回值不能为引用。
1.拷贝构造函数
1)作用:利用已存在的对象来创建一个新的对象。
2)调用拷贝构造函数的情况
(1)一个对象需要通过另一个对象来初始化。
a)CExample B=A;
b)CExample B(A);
c)CExample A;
CExample *p = new CExample(A);
(2)对象以值传递的方式传入函数的参数
(3)对象以值返回的方式从函数返回
3)构造函数的形式
Vector(const Vector& vector);
其中参数必须是一个类对象的引用:如果参数以值传递的方式传入拷贝构造函数中,则会再次调用拷贝构造函数,这样就会形成一个死循环。
2.析构函数
1)如果一个类种没有包含一个析构函数,编译器将提供一个默认的析构函数。如果类中的数据成员有指针类型,则编译器将不会释放掉指针指向的堆空间,从而造成内存泄露。这种情况下,我们必须要定义一个析构函数来释放指针指向的堆空间。
2)当我们想深拷贝的时候,赋值运算符重载,拷贝构造函数,析构函数都是必须要实现的。
1.类型转化与构造函数 1)当我们需要一个A类对象作为参数的时候,可是给提供的却是一个T类型的变量,这是编译器会在A类中寻找有没有一个参数类型是T类型的,且参数个数是一个,或者是有多个参数但是参数是带有默认值的构造函数,如果有的话则调用这个构造函数将T类型的变量转换成为A类的一个对象。如果在这个构造函数的前面加一个关键字explicit,则不会实现这个过程。 2)一个例子: char* a=“first string”; print(a);print()函数要接受一个String类型的对象,在String类中有一个构造函数的参数是一个char*类型的。 编译器发现a不是String类的对象,会调用这个类型转化构造函数,创建一个temp临时对象,然后调用print函数,最后再将这个temp临时变量释放掉。 3)又是一个例子 String b =“bad initialization”; 编译器会将上面的一句话转换成:String b(”bad initialization”); 2.对象数组 String ss[5];调用默认构造函数 String* sss= new String();调用默认构造函数 String*ssss = new String[10];调用默认构造函数在堆上分配十个对象大小的空间 String ss[5] = {String(),String(),String(),String(),String()};显示的进行初始化 对象数组没有初始化会调用默认的构造函数惊醒初始化。 3.继承 派生类继承了基类的所有的成员函数,除了构造函数,析构函数,赋值运算符重载的函数 父类的私有成员,在派生类中也是私有成员,但是在派生类中是不可以访问的。 派生类可以定义和基类一模一样的成员,这样派生类中定义的成员函数将覆盖从基类中继承来的成员函数(override) 如果不写继承方式的话,默认的是private的继承方式 private:只能在本类中访问 protected:在本类中和子类中都可以访问 public:在本类中,子类中,类的外面中都可以访问 1.继承的使用 1)通过继承可以构造一个类层次结构反映出这些类的关系,类所代表的身份在自然界中存在着天然的继承关系。如学生,大学生,研究生 2)实现代码的重用。 2.将派生类对象作为基类对象使用 当继承方式为公有继承的时候,派生类对象可以被当成基类对象使用。 1)可以将派生类对象赋值给基类的对象,但是不能将基类的对象赋值给派生类的对象。 2)基类的引用可以指向派生类对象。 3)基类的指针可以指向派生类的对象。 在以上2)3)两种情况下,当基类和派生类中定义了相同的成员函数,使用指向派生类对象的基类引用或者指针调用该成员函数时,表现的是基类中得版本。 当基类的指针或者是引用指向派生类的对象时,可以将基类的指针或者是引用强制转换成派生类指针或者是引用。 可以将派生类的对象强制转换成基类的对象,但是不能将基类对象强制转换成派生类的对象。 3.派生类的构造函数,析构函数,赋值运算符重载函数。 派生类通过继承是不能继承基类的构造函数,析构函数,赋值运算符重载函数的。所以在派生类中要重写这三种函数。 1)创建一个派生类的对象时,要先调用基类的构造函数,然后再调用派生类的构造函数,所以在派生类的构造函数中要指明要调用的基类的构造函数,如果没有指明的话,则要调用基类的默认的构造函数。 2)在释放派生类的对象的时候,要先调用派生类的析构函数,然后再调用基类的构造函数。
异常处理 将可能产生异常的代码放在try块中 异常可能在try块中的语句中产生,或者在try块中调用的函数中产生,或者是在try块中调用的函数中调用的其他函数中产生异常。 想一下,异常可能会在代码中产生。在异常发生之前,通过用测试(经常是条件表达式)来判断异常是否会发生。如果符合异常发生的条件,则用throw语句将异常抛出。例如,下面的例子中,有一个条件判断除数是否为0,如果为0,则抛出一个异常。 在异常抛出后,程序流返回给调用者。如果异常在try块中产生了,则在try块中产生异常代码之后的代码将不会被执行。通常,在try块后面经常跟着catch块。catch块是一个异常处理者。 参数的名字可以被省略。因此,catch需要根据给定的类型来捕获异常。但是它不能使用从throw语句中抛出的信息(有问题)。如果在catch块中的类型匹配抛出的表达式,则catch块开始执行。在一个try块后可能存在许多个catch块。程序寻找到匹配的一个catch块,执行匹配的catch块里面的内容。catch块执行完之后,如果程序还没有被终止的话,则继续执行后面的代码。如果没有找到匹配的catch块,程序跳到上层的try-catch块中(如果有的话)。如果没有匹配的的,那么程序将被终止。 注意,catch块不能访问定义在try块中的局部变量,因为执行catch块的时候,try块已经执行完了。 异常处理机制将程序处理代码从源代码的执行序列中分离出来。异常处理代码,也就是catch块,通常放在一个函数中。这个函数不同于异常产生的函数。如果一个异常能直接在其产生的作用域中直接进行处理,这种机制是不推荐的,因为它不被设计来优化性能。 一个异常可以在一个catch块中被捕获,然后可以再次抛给顶层的catch块。或者在catch块中可能产生一个新的异常对象被顶层的try块捕获… An exception can be caught in a catch block, and throws again to an upper level catch block. Or, the catch block may throw a new exception object to the next nesting try level. 这里有一种catch块可以捕获所有类型的异常 catch( ... ) // Use three dots in the parameter list使用三个点 { // ... } 因为此种catch块没有参数,捕获的异常对象将不能被使用。 如果这里还有比这个更“常用”的catch块,它们必须放在这个catch块之前才能产生作用 抛出的异常可能是一个类的对象。如果这个类派生自一个基类,那么就可以把其基类对象作为catch块的参数就可以捕获这个异常。 如果这里有一个以派生类对象作为参数的catch块,则这个catch块必须放置在以基类对象作为参数的catch块之前才会产生作用。 想一下,在一个函数正调用另外一个函数的时候,在那个函数中抛出一个异常。调用者终止,局部变量被销毁。尤其是,如果这里有一个类的对象,将会调用此类的析构函数。如果局部对象是一个指针变量,则会产生内存泄露。 为了避免这种情况的内存泄露,这个指针需要抛给调用者,调用者负责对内存进行释放。这可以通过在类中封装指向错误信息的指针来实现,最后抛出这个类的对象即可。为了避免发生拷贝,catch块中需要是这个对象的引用类型。 这也可以通过在调用者中通过捕获异常来做到,并且把异常抛给上一级。 如果异常在整个程序中都没有找到任何能与之匹配的catch块,程序将会调用terminate()函数,然后terminate()函数将会调用abort()函数来终止程序。这里有两个函数被调用的原因是,在调用abort()函数之前,你可能会让terminate()函数调用用户自定义的一些函数。这通过调用定义在<eh.h>头文件中的set_terminate()函数来实现。函数set_terminate()接收 void(*)(),也就是指向返回值类型为void,没有参数的函数的函数指针作为参数。实际参数是用户自定义函数的名字,此函数指定了,如发生未处理异常程序该如何做。 在这个try块中,函数divide()抛出一个异常。这个异常没有被捕获。随后terminate()函数被调用。terminate()函数首先调用my_terminate(),然后调用abort()函数结束程序 一个函数可以通过异常表达式指定函数抛出异常的特定类型。 这个函数可能抛出一个int类型或者一个Error_message对象类型的异常。如果一个函数期望不抛出任何类型异常,可以用throw()来指定。 有指定抛出的特定类型,则函数可能会抛出任何类型的异常。 指定特定类型的异常通常被用来添加一层错误类型检查。如果一个被抛出的异常不是指定特定类型的异常,则unexpected()函数会被自动调用。这个函数默认调用terminate()函数。或者调用用户自定义的set_unexpected()函数。
|