C++类的相关问题、构造函数与析构函数、复制构造函数实例讲解
面向对象的基本任务是描述对象并对对象进行归类总结。类类型与int类型一样,也没有任何内存分配。类的属性和对外接口是类定义的重点和难点,原则是尽量让内部操作私有化,提供简单易用的接口函数。
1、类的相关问题
在定义类的数据成员时,不能像定义变量一样进行初始化,若在定义时未指明访问限定符,默认为private;
在定义类的方法时,若方法中不用修改类的数据成员,则最好在方法声明的最后使用const关键字,表示用户不能在此方法中修改类的数据成员。若类中包含指针成员,在const方法中不可以重新为指针赋值,但可以修改指针所指向的地址中的数据。
类中,除静态成员可以直接访问外,其它成员是通过对象来实现访问的。在定义类时并没有分配存储空间,只有当实例化对象时,才分配存储空间。也可以将类对象声明为一个指针,并使用 ?new运算符为其分配内存,如下所示:
若将类对象声明为常量指针,则只能调用类中的常量方法。
2、构造函数与析构函数
每个类都有构造函数与析构函数,构造函数在定义对象时被调用,析构函数在对象释放时被调用。构造函数负责类对象生成之前的初始化,析构函数负责对象销毁后的处理。
构造函数没有返回值,若用户没有提供构造函数和析构函数,则使用默认的构造析构函数。一个类可以包含多个构造函数,各函数通过重载来进行区分,下面是指针调用参数构造函数进行初始化:
类体中定义的数据成员不能初始化,故类中初始化只能借助构造函数的初始化列表实现。类成员函数,若在类体中定义,函数前即使没有使用inline,该成员函数也被认为是内联函数。
析构函数没有返回值,也没有参数,故不能重载。
3、复制构造函数
复制构造函数与类的其它函数构造类似,以类名作为函数的名称,第一个参数为该类的常量引用类型。
下面的三种情况要用到复制构造函数:
对象以值传递的方式传入函数参数;
对象以值传递的方式从函数返回;
对象需要通过另外一个对象进行初始化;
编译器有默认的拷贝构造函数,这个函数仅仅是使用老对象的数据成员的值对“新对象”数据成员一一进行赋值,这称为浅拷贝,即只是对象中数据成员进行简单的赋值,这种方式在对象中存在动态成员时则会出现问题,比如对象中的指针。浅拷贝只是使两个指针有相同的值,而不是我们需要的两块不同的地址。
深拷贝,对于对象中动态成员,不仅仅赋值,还重新动态分配空间,从而使得指针指向两块不同的内存,但内存中的值相同。
在编写函数时,尽量按引用方式传递参数,这样可以避免调用复制构造函数,可以极大地提高程序效率。也可以将拷贝函数放在private中,从而防止默认拷贝的发生。
4、静态类成员
普通类成员只能通过实例化对象访问,静态类成员还可以通过类名直接访问,访问时用::域访问符。在定义静态数据成员时,要在类体外部对静态数据成员初始化。静态数据成员是被所有类对象共享的。
静态数据成员可以是当前的类型,而其他数据成员只能是当前类的指针或引用类型,如:
针对静态数据成员有如下几点:
静态数据成员可以作为成员函数的默认参数,但是普通成员不可以;
类的静态成员函数只能访问类的静态成员,不能访问普通数据成员;
静态成员函数末尾不能用const关键字修饰;
静态数据成员和静态成员函数在类体之外初始化或定义时,去掉static关键字;
5、运算符重载
运算符重载要用到operator关键字,它其实是函数重载的一种,因为运算符本来就是一个函数。对于重载的运算符,两个函数不能交换顺序,重载是什么顺序,只能用这各顺序调用。
对于++和--运算符,由于涉及前置和后置,故默认情况下,重载运算符没有参数,表示是前置运算;若用整型int作为参数,则表示后置运算。
并不是所有的运算符都可以重载,大多数是可以重载的,但是::,?,:,. 是不能重载的。
6、有关类的sizeof问题
空类也会被实例化,编译器会给类隐含添加一个字节,故空类的sizeof()结果为1;sizeof()用来计算字符串的长度时包含”\0”,strlen()统计的长度不包含”\0”。
构造函数、析构函数都不归入sizeof()统计范围之内。
虚函数由于要维护在虚函数表中的位置,故要占据一个指针的大小。
静态成员也不归入sizeof()统计范围。
总起来说,类的大小与非静态成员大小和虚函数有关,与其他普通成员函数无关。类的大小也遵守内存分配时的字节对齐规则:
7、友元类与友元方法
当用户希望另一个类可以访问当前类的私有成员时,可以在当前类中将另一个类作为自己的友元类。友元即朋友,私有成员只有朋友可以访问。
若只想让某个成员函数访问类的私有成员,则可以将此函数声明为类的友元函数,即在函数返回值前加上friend关键字。友元函数不仅可以是类成员函数,也可以是全局变量函数。
8、引用和指针的区别
引用是一个变量的别名,引用被创建的同时必须被初始化,指针可以在任何时候被初始化;
不能有null引用,引用必须与合法的存储单元关联,指针则可以是null;
引用一旦被初始化,就不能改变引用关系,指针则可以随时改变所指的对象。
9、类的继承
继承是面向对象的主要特征之一,它使一个类可以从现有类中派生,而不必重新定义一个新类,类继承时使用“:”运算符。
类继承与访问关系如下表:
用public继承时,原类中public和protected在派生类中类型不变;
用protected继承时,原类中public和protected在派生类中都变成protected类型;
用private继承时,原类中public和protected在派生类中都变为private类型;
用户在父类派生子类时,可能存在一种情况,即在子类中定义了一个与父类同名的方法,此时称子类隐藏父类方法,这样父类中所有的同名方法包括重载方法均被隐藏。若要访问父类中的方法,可用域访问的方式。
在派生完一个子类后,可以定义一个父类指针,通过子类构造函数为其创建对象。因为编译器对同名方法是静态绑定的,即根据对象定义的类型来确定调用哪个类方法。
在定义方法时,在方法的前面使用virtual关键字,这种是虚方法,使用虚方法可以实现动态绑定,即根据对象运行的类型来确定调用哪个类的方法。在父类中定义的虚方法,在子类中同名的方法,即使前面没有virtual关键字,也为虚方法。
纯虚方法:
c++中除了能定义虚方法外,还可定义纯虚方法,也即抽象方法。一个包含纯虚方法的类称为抽象类。抽象类不能实例化,不能当返回值,不能做函数参数,但可以设置指针,它常用于接口的实现。
抽象类常作为其它类的父类,从抽象类派生的子类要实现父类中所有的纯虚方法。定义纯虚函数的意义在于告诉子类,这个接口是一定要继承的,并且要重写函数体,增强它的功能。
10、子类对象的创建和释放
当从父类派生一个子类,定义一个子类对象时,它将依次调用父类构造函数、子类构造函数,在释放对象时,先调用子类析构函数,再调用父类析构函数;
当定义一个父类对象,调用子类创建一个对象时,它依次调用父类构造函数、子类构造函数,在释放时分两种情况:
1.析构函数不是虚函数,则只调用父类析构函数;
2.析构函数是虚函数(父类),则先调用子类的析构函数,再调用父类析构函数。
由此可见,若父类不是虚析构函数,在子类分配了空间,没有调用了类的析构函数会产生内存泄漏,因此在编写函数时,析构函数常用虚函数。
11、多继承
多继承是子类可以继承多个父类,各父类间用逗号隔开,都要带关键字。若不同父类中有同名方法,子类实例在访问时要指明父类的名称。多继承的继承链不能有环,可以用uml语言画出继承图。
在多继承时,通常第三代子类中存在两个父类的备份,c++提供了一种虚继承机制使之只有一个备份,方法是在两个子类继承父类时,加上virtual关键字。虚继承不再从第一代父类开始创建构造函数,直接从两个子类开始创建。
12、类模板
类模板能够为类的数据成员、成员函数的参数、返回值提供动态参数化的机制,使用户可以方便地设计出功能更为灵活的类。
类模板中也可以设置静态数据成员,不同类型的模板实例都有各自的静态数据成员,同一类型各实例共享静态数据成员。
推荐阅读
-
C++类的相关问题、构造函数与析构函数、复制构造函数实例讲解
-
C++编写字符串类CNString,该类有默认构造函数、类的拷贝函数、类的析构函数及运算符重载
-
C++的构造函数、析构函数、拷贝构造函数等代码实例讲解
-
【php】利用php的构造函数与析构函数编写Mysql数据库查询类
-
C++类的相关问题、构造函数与析构函数、复制构造函数实例讲解
-
关于PHP面向对象中—类的定义与对象的实例化操作以及构造、析构函数的特殊用法 - WORSHIP亚萨
-
python析构函数与构造函数的用法讲解
-
python析构函数与构造函数的用法讲解
-
C++在单继承、多继承、虚继承时,构造函数、复制构造函数、赋值操作符、析构函数的执行顺序和执行内容...
-
C++类(构造函数和析构函数、子类继承和调用父类的构造方法)