首先要说的是默认构造函数,编译器可以帮用户定义一个默认构造函数,前提是用户没有定义任何构造函数,一旦用户定义了某个构造函数,不管它是不是默认的,那么编译器都不会再帮用户定义默认构造函数了,在用户定义自己的默认构造函数时,要么没有参数,要么所有的参数都有一个默认值【比如有一个类A,那么带参数的默认构造函数应该是这样 A(int a=0,string s = "null")】。接着考虑在继承链中的类,如果有一个类B,它继承自类A(或者说B里面有一个类A的成员组件),那么对于B的构造函数而言,如果里面没有显式的调用类A的构造函数(不管是不是默认构造函数),那么编译器会自动调用类A的默认构造函数(如果类A没有默认构造函数,那么会发生编译错误!),以便初始化B中属于类A的数据成员。总而言之,为一个类定义默认构造函数是一种良好的编程习惯,它可以确保类的数据成员会被初始化为正确的值。
其次要注意的是复制构造函数和赋值操作符重载函数,先说复制构造函数,所谓复制构造函数,就是说这个构造函数的参数表里面的参数是本类类型的类对象。复制构造函数主要应用在以下场景:
1、用这个类对象初始化一个新的同类类型对象
2、将这个类对象作为一个参数传递给某个函数(以按值传递的方式)
3、某个函数需要返回这个类的对象
4、在某些场合下需要创建这个类的临时对象
同默认构造函数一样,如果用户没有定义自己的复制构造函数,那么编译器将会生成一个复制构造函数,但这种编译器做出来的复制构造函数是有着致命缺陷的:编译器的复制构造函数只是简单的对数据成员进行浅拷贝,可如果类中的数据成员有动态内存(比如指针)的话,那么这种浅拷贝就会造成错误。所以遇到这种情况时,用户应该自己定义一个深度拷贝的复制构造函数。
同复制构造函数一样,赋值操作符重载函数也应该由用户自己定义,需要注意的是,赋值操作符是不能被继承的,这是因为子类继承的函数方法,其参数表与父类对应的那个函数方法完全一致,但对于赋值操作符函数来说就不一定了,因为很常见的一个情况是:同类对象之间的赋值操作,它们的函数参数都是本类类型,如果子类能继承父类的赋值操作符函数,那么子类的一些新增数据成员,有可能就无法得到正确的处理了。但是如果我们将一个子类的对象赋值给一个父类对象时(没有为子类定义赋值操作符),那么编译器生成的阴式赋值操作符,将会把子类对象中的父类相关的东西剥离开来,然后用父类的赋值操作符函数来赋值,而对子类中的其他数据成员,都会采用简单的拷贝方式赋值,那么反过来将父类对象赋值给子类对象呢?比如B是A的子类,那么【B对象 = A对象】呢?由于赋值操作会被编译器转换成=号左边的那个类型的赋值操作符,所以实际调用的是B::operator(...),再由于编译器不支持类型的向下转换,所以这句代码是错的,除非我们为A->B定义了一个转换函数。同样的道理,如果被赋值对象类型不同,那么编译器将使用被赋值对象的那个赋值操作符函数,并且最好有相应的类型转换函数。
私有成员(private)与保护成员(protected)的区分
对于子类而言,父类中的保护成员其实就是公有成员,但对于外部而言,保护成员则相当于私有成员。在子类中可以直接访问父类的保护成员,但是父类的私有成员如果子类想访问,那就只能通过父类的成员函数类访问了。至于具体怎么划分这个界限,用户需要直接酌情考虑。
公有方式派生的子类中的父类方式的说明:
1、子类对象可以自动使用父类的方法
2、子类的构造函数会自动调用父类的构造函数,如果没有在成员初始化列表中指出其它构造函数的话,那么调用的会是父类的默认构造函数(不带参数的那个)
3、在子类的函数中,可以使用父类的作用于解析操作符来显式调用父类的受保护成员方法,但无法访问私有成员方法
4、子类的友元函数可以通过强制类型转换,将子类的引用或指针转换为父类的引用或指针,然后通过这个引用或指针来调用父类的友元函数。