欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

C++类的相关问题、构造函数与析构函数、复制构造函数实例讲解

程序员文章站 2023-10-28 21:32:58
面向对象的基本任务是描述对象并对对象进行归类总结。类类型与int类型一样,也没有任何内存分配。类的属性和对外接口是类定义的重点和难点,原则是尽量让内部操作私有化,提供简单易用的接口函数。 1、类的相...

面向对象的基本任务是描述对象并对对象进行归类总结。类类型与int类型一样,也没有任何内存分配。类的属性和对外接口是类定义的重点和难点,原则是尽量让内部操作私有化,提供简单易用的接口函数。

1、类的相关问题

在定义类的数据成员时,不能像定义变量一样进行初始化,若在定义时未指明访问限定符,默认为private;

在定义类的方法时,若方法中不用修改类的数据成员,则最好在方法声明的最后使用const关键字,表示用户不能在此方法中修改类的数据成员。若类中包含指针成员,在const方法中不可以重新为指针赋值,但可以修改指针所指向的地址中的数据。

类中,除静态成员可以直接访问外,其它成员是通过对象来实现访问的。在定义类时并没有分配存储空间,只有当实例化对象时,才分配存储空间。也可以将类对象声明为一个指针,并使用 ?new运算符为其分配内存,如下所示:

C++类的相关问题、构造函数与析构函数、复制构造函数实例讲解

若将类对象声明为常量指针,则只能调用类中的常量方法。

2、构造函数与析构函数

每个类都有构造函数与析构函数,构造函数在定义对象时被调用,析构函数在对象释放时被调用。构造函数负责类对象生成之前的初始化,析构函数负责对象销毁后的处理。

构造函数没有返回值,若用户没有提供构造函数和析构函数,则使用默认的构造析构函数。一个类可以包含多个构造函数,各函数通过重载来进行区分,下面是指针调用参数构造函数进行初始化:

C++类的相关问题、构造函数与析构函数、复制构造函数实例讲解

类体中定义的数据成员不能初始化,故类中初始化只能借助构造函数的初始化列表实现。类成员函数,若在类体中定义,函数前即使没有使用inline,该成员函数也被认为是内联函数。

析构函数没有返回值,也没有参数,故不能重载。

3、复制构造函数

复制构造函数与类的其它函数构造类似,以类名作为函数的名称,第一个参数为该类的常量引用类型。

下面的三种情况要用到复制构造函数:

对象以值传递的方式传入函数参数;

对象以值传递的方式从函数返回;

对象需要通过另外一个对象进行初始化;

编译器有默认的拷贝构造函数,这个函数仅仅是使用老对象的数据成员的值对“新对象”数据成员一一进行赋值,这称为浅拷贝,即只是对象中数据成员进行简单的赋值,这种方式在对象中存在动态成员时则会出现问题,比如对象中的指针。浅拷贝只是使两个指针有相同的值,而不是我们需要的两块不同的地址。

深拷贝,对于对象中动态成员,不仅仅赋值,还重新动态分配空间,从而使得指针指向两块不同的内存,但内存中的值相同。

在编写函数时,尽量按引用方式传递参数,这样可以避免调用复制构造函数,可以极大地提高程序效率。也可以将拷贝函数放在private中,从而防止默认拷贝的发生。

4、静态类成员

普通类成员只能通过实例化对象访问,静态类成员还可以通过类名直接访问,访问时用::域访问符。在定义静态数据成员时,要在类体外部对静态数据成员初始化。静态数据成员是被所有类对象共享的。

静态数据成员可以是当前的类型,而其他数据成员只能是当前类的指针或引用类型,如:

C++类的相关问题、构造函数与析构函数、复制构造函数实例讲解

针对静态数据成员有如下几点:

静态数据成员可以作为成员函数的默认参数,但是普通成员不可以;

类的静态成员函数只能访问类的静态成员,不能访问普通数据成员;

静态成员函数末尾不能用const关键字修饰;

静态数据成员和静态成员函数在类体之外初始化或定义时,去掉static关键字;

5、运算符重载

运算符重载要用到operator关键字,它其实是函数重载的一种,因为运算符本来就是一个函数。对于重载的运算符,两个函数不能交换顺序,重载是什么顺序,只能用这各顺序调用。

对于++和--运算符,由于涉及前置和后置,故默认情况下,重载运算符没有参数,表示是前置运算;若用整型int作为参数,则表示后置运算。

并不是所有的运算符都可以重载,大多数是可以重载的,但是::,?,:,. 是不能重载的。

6、有关类的sizeof问题

空类也会被实例化,编译器会给类隐含添加一个字节,故空类的sizeof()结果为1;sizeof()用来计算字符串的长度时包含”\0”,strlen()统计的长度不包含”\0”。

构造函数、析构函数都不归入sizeof()统计范围之内。

虚函数由于要维护在虚函数表中的位置,故要占据一个指针的大小。

静态成员也不归入sizeof()统计范围。

总起来说,类的大小与非静态成员大小和虚函数有关,与其他普通成员函数无关。类的大小也遵守内存分配时的字节对齐规则:

C++类的相关问题、构造函数与析构函数、复制构造函数实例讲解

7、友元类与友元方法

当用户希望另一个类可以访问当前类的私有成员时,可以在当前类中将另一个类作为自己的友元类。友元即朋友,私有成员只有朋友可以访问。

若只想让某个成员函数访问类的私有成员,则可以将此函数声明为类的友元函数,即在函数返回值前加上friend关键字。友元函数不仅可以是类成员函数,也可以是全局变量函数。

8、引用和指针的区别

引用是一个变量的别名,引用被创建的同时必须被初始化,指针可以在任何时候被初始化;

不能有null引用,引用必须与合法的存储单元关联,指针则可以是null;

引用一旦被初始化,就不能改变引用关系,指针则可以随时改变所指的对象。

9、类的继承

继承是面向对象的主要特征之一,它使一个类可以从现有类中派生,而不必重新定义一个新类,类继承时使用“:”运算符。

类继承与访问关系如下表:

C++类的相关问题、构造函数与析构函数、复制构造函数实例讲解

用public继承时,原类中public和protected在派生类中类型不变;

用protected继承时,原类中public和protected在派生类中都变成protected类型;

用private继承时,原类中public和protected在派生类中都变为private类型;

用户在父类派生子类时,可能存在一种情况,即在子类中定义了一个与父类同名的方法,此时称子类隐藏父类方法,这样父类中所有的同名方法包括重载方法均被隐藏。若要访问父类中的方法,可用域访问的方式。

在派生完一个子类后,可以定义一个父类指针,通过子类构造函数为其创建对象。因为编译器对同名方法是静态绑定的,即根据对象定义的类型来确定调用哪个类方法。

在定义方法时,在方法的前面使用virtual关键字,这种是虚方法,使用虚方法可以实现动态绑定,即根据对象运行的类型来确定调用哪个类的方法。在父类中定义的虚方法,在子类中同名的方法,即使前面没有virtual关键字,也为虚方法。

纯虚方法:

c++中除了能定义虚方法外,还可定义纯虚方法,也即抽象方法。一个包含纯虚方法的类称为抽象类。抽象类不能实例化,不能当返回值,不能做函数参数,但可以设置指针,它常用于接口的实现。

抽象类常作为其它类的父类,从抽象类派生的子类要实现父类中所有的纯虚方法。定义纯虚函数的意义在于告诉子类,这个接口是一定要继承的,并且要重写函数体,增强它的功能。

10、子类对象的创建和释放

当从父类派生一个子类,定义一个子类对象时,它将依次调用父类构造函数、子类构造函数,在释放对象时,先调用子类析构函数,再调用父类析构函数;

当定义一个父类对象,调用子类创建一个对象时,它依次调用父类构造函数、子类构造函数,在释放时分两种情况:

1.析构函数不是虚函数,则只调用父类析构函数;

2.析构函数是虚函数(父类),则先调用子类的析构函数,再调用父类析构函数。

由此可见,若父类不是虚析构函数,在子类分配了空间,没有调用了类的析构函数会产生内存泄漏,因此在编写函数时,析构函数常用虚函数。

11、多继承

多继承是子类可以继承多个父类,各父类间用逗号隔开,都要带关键字。若不同父类中有同名方法,子类实例在访问时要指明父类的名称。多继承的继承链不能有环,可以用uml语言画出继承图。

在多继承时,通常第三代子类中存在两个父类的备份,c++提供了一种虚继承机制使之只有一个备份,方法是在两个子类继承父类时,加上virtual关键字。虚继承不再从第一代父类开始创建构造函数,直接从两个子类开始创建。

12、类模板

类模板能够为类的数据成员、成员函数的参数、返回值提供动态参数化的机制,使用户可以方便地设计出功能更为灵活的类。

类模板中也可以设置静态数据成员,不同类型的模板实例都有各自的静态数据成员,同一类型各实例共享静态数据成员。