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

对象的初始化,运算符重载,类的继承和多态知识总结

程序员文章站 2022-04-30 15:29:28
...

对象的初始化,运算符重载,类的继承和多态知识总结

第7章 对象的初始化、复制和销毁

1、默认初始化:如果定义对象时没有指定初值,对象被默认初始化,调用类中的默认构造函数。

2、直接初始化:初始值在圆括号“()”中,可以提供多个初始值,根据初始值类型和个数直接调用最匹配的构造函数。

3、拷贝初始化:用等号“=”初始化一个对象时,执行拷贝初始化,编译器用等号右边的初始值创建一个对象,复制给新创建的对象。等号右边的初始值只能有一个,调用与初始值类型匹配的构造函数。

4、列表初始化:用花括号“{}”中的初始值构造对象,调用相应的构造函数,与直接初始化类似。花括号可以是初始值列表,用来初始化数组的每个元素。

5、默认构造函数:可不提供实参就能调用的构造函数称为默认构造函数。在定义对象时如果没有提供初始值,或不便提供初始值时,调用默认构造函数进行初始化。默认构造函数可以是没有形参的构造函数。通常需要为类定义一个默认构造函数,如果一个类没有定义任何构造函数,编译器会在需要时自动合成一个默认构造函数,类中一旦定义了构造函数,即使不是默认构造函数,编译器也不再合成。当对象被默认初始化或值初始化时自动执行默认构造函数。

6、析构函数:对象离开作用域时析构函数会被自动调用,同一作用域的对象析构函数的调用次序和构造函数的调用次序相反,析构函数执行与构造函数相反的操作。析构函数包括函数体和隐式的析构部分,首先执行函数体,然后执行析构部分销毁成员,成员按初始化的逆序销毁,成员的销毁方式完全依赖于成员的类型,销毁类类型的成员需要执行成员自己的析构函数。

7、拷贝构造函数:有时候会用已有对象去初始化另一个同类型的对象,需要构造函数,称为拷贝构造函数。

拷贝构造函数的形式:拷贝构造函数的一般形式是X(X&)或者X(const X&),X(X&),不能用const 对象来初始化另一个X 类型的对象。

如果用X 类的对象a 初始化X 类的对象b,会引起拷贝构造函数的调用,在调用X(X obj)时,按值传参数是用实参a 初始化形参obj,这同样是用一个对象初始化另一个同类对象,又需要调用拷贝构造函数,重复这一过程,显然会陷入对X(X)的无限循环调用,因此,拷贝构造函数的形参不会是X(X)。用对象初始化另一个对象,不是用地址,不会是X(X*)。

如果一个构造函数的第一个形参是自身类型的引用,且任何额外的参数都有默认实参,则此构造函数是拷贝构造函数。只需要提供一个本类型对象作实参就可以调用的构造函数是拷贝构造函数。

8、何时调用拷贝构造函数?

以下三种情况都是用一个对象初始化另一个同类对象的语义,会调用拷贝构造函数:用一个对象显式或隐式初始化另一个同类对象,函数调用时,按传值方式传递对象参数,函数返回时,按传值方式返回对象。

9、重载赋值运算符:定义类时要控制对象如何赋值,可以重载赋值运算符。

如果类没有定义拷贝赋值运算符,编译器会自动合成,行为是将右操作数对象的每个非static
成员赋值给左操作数对象的对应成员,最后返回左操作数对象的引用。

类类型的成员通过其拷贝赋值运算符进行赋值,对数组类型的成员,逐个数组元素赋值。

重载赋值运算符:类X 的赋值运算符要定义为类X 的成员函数:X& operator=(const X&){…}。

第8章 运算符重载

1、运算符重载:将运算符看作是一种特殊的函数,操作数是函数的参数,运算结果是函数的返回值。如果运算符被看作是函数,自然也可以像函数一样重载。已见过的运算符重载现象:移位运算符>>和<<,用作流输入和输出运算符。

2、重载的运算符

定义重载运算符和定义普通函数类似,函数名由关键字operator 和其后要定义的运算符组成,operator=, operator<<, operator+。返回类型:运算结果的类型。参数表:提供参与运算的操作数,参数个数取决于运算符的操作数个数和运算符函数是成员函数还是非成员函数。函数体:进行运算,返回运算结果,即表达式的值。

3、类的成员运算符函数:this 指向的对象被作为运算符的第一个操作数(左);一元运算符函数不需要提供参数;二元运算符提供一个参数作为右操作数。

4、非成员运算符函数:一元运算符要提供一个类类型的参数;二元运算符需要提供两个参数分别作为左、右操作数,其中至少一个参数必须是类类型的;通常声明为类的友元,以便访问私有数据成员。

5、运算符函数只有在类类型的对象参与运算时才起作用,当运算符作用于内置类型的运算对象时,不会改变该运算符原来的含义,将运算符作用于类型正确的实参时,会引起重载运算符的调用,也可以像普通函数一样直接调用运算符函数,较少用。

6、运算符重载的实质:运算符重载仅仅提供了一种语法上的方便,是以另外一种方式调用函数,只有至少一个操作数是用户自定义类型时,才可能调用重载的运算符。

7、不建议重载的特殊运算符:逻辑与(&&)、逻辑或(||)、逗号运算符(,)、取地址运算符(&)。

8、慎用运算符重载:设计类时,只有操作是类需要的,且在逻辑上与运算符相关,才适合定义成重载的运算符:如果类执行I/O 操作,可以定义移位运算符>>和<<;如果类的某个操作是检查相等性,则定义operator==;这时候通常也应该有operator!=;如果类包含内在的单序比较操作,则定义operator<;此时也应该有其他关系操作;重载运算符的返回类型通常情况下应该与内置版本的返回类型兼容;如果类定义了赋值运算符,同时还定义了算术运算符或位运算符,那么最好也提供对应的复合赋值运算符。

9、定义运算符函数时选择成员还是非成员?

赋值(=)、下标([])、函数调用(())和成员函数访问箭头(->)运算符必须是成员函数;复合赋值运算符一般应该是成员,但并非必须;改变对象状态的运算符或者与给定类型密切相关的运算符,如自增、自减和解引用运算符,通常应该是成员;具有对称性的运算符可能转换两个操作数中的任何一个,如算术、关系和位运算符等,通常应该是非成员函数;重载移位运算符<<和>>用于对象的I/O 操作时,左操作数是标准库流对象,右操作数才是类类型的对象,只能用非成员函数。

10、一元运算符

自增和自减运算符:前缀和后缀形式都会改变对象,不能对常量对象操作;

前缀形式返回改变后的对象,后缀形式返回改变之前的值;

后缀形式的自增和自减比前缀形式多一个int参数,这个参数在函数中并不使用,只是作为重载函数的标记来区分前缀和后缀运算;重载自增和自减运算符时应同时定义前缀和后缀形式。

11、二元运算符

赋值运算符:赋值运算符只能用成员函数重载,复合赋值运算符可以用成员或非成员重载。

运算符函数的参数和返回类型

对于类类型的参数,如果仅仅只是读参数的值,而不改变参数,应该作为const引用来传递,普通算术运算符、关系运算符、逻辑运算符都不会改变参数,所以以const引用作为参数传递方式,当运算符函数是类的成员函数时,就将其定义为const成员函数。

返回值的类型取决于运算符的具体含义,所有赋值运算符均改变左值,逻辑运算符和关系运算符最好返回bool值。

12、非成员运算符和成员运算符

成员运算符左操作数必须是当前类的对象,左操作数不能进行自动类型转换;非成员运算符为两个操作数都提供了转换的可能性。

重载的赋值运算符必须定义为成员函数,返回左操作数的引用,与内置类型的赋值运算符一致。复合赋值运算符不是必须用成员函数定义,但一般倾向于定义为成员函数,也返回左操作数的引用。

13、下标运算符operator[]必须是成员函数,接收一个参数,通常是整值类型,返回一个元素的引用,以便用作左值。

14、如果类重载了函数调用运算符operator(),就可以像使用函数一样使用该类的对象,函数调用运算符必须是成员函数。使用函数调用运算符的方式是令对象作用于一个实参列表,形式类似于函数调用。

第9章 组合与继承

1、将一个类的对象作为另一个类的成员,被称作组合或包含。

2、创建包含对象成员的组合对象时,会执行成员类的构造函数初始化对象成员,当组合对象被撤销时,会执行其析构函数,成员对象的析构函数也会被执行。

3、成员初始化的次序和成员声明的次序相同,并不考虑它们在初始化列表中的排列顺序。

4、继承是面向对象的核心特征之一,继承也是一种复用已有类的机制。在已有类的基础上继承得到新类型,这个新类型自动拥有已有类的特性,并可以修改继承到的特性或者增加自己的新特性。

5、在C++中,被继承的已有类称为基类;继承得到的新类称为派生类;派生类可以再被继承,这样构成的层次结构称为继承层次。

6、类继承关系的语法形式

   class 派生类名 : 基类名表

  {

         数据成员和成员函数声明

  };

基类名表构成:访问控制 基类名1, 访问控制 基类名2 ,… , 访问控制 基类名n。

访问控制表示派生类对基类的继承方式,使用关键字:public公有继承,private私有继承,protected保护继承。

7、派生类的生成过程经历了三个步骤:吸收基类成员(全部吸收(构造、析构除外),但不一定可见);改造基类成员;添加派生类新成员。

8、派生类定义了与基类同名的成员,在派生类中访问同名成员时屏蔽(hide)了基类的同名成员,在派生类中使用基类的同名成员,显式地使用类名限定符:类名:: 成员。

9、基类定义的静态成员,将被所有派生类共享(基类和派生类共享基类中的静态成员),根据静态成员自身的访问特性和派生类的继承方式,在类层次体系中具有不同的访问性质,派生类中访问静态成员,用以下形式显式说明:类名 :: 成员或通过对象访问,对象名 . 成员。

10、在创建派生类对象时用指定参数调用基类的构造函数来初始化派生类继承基类的数据,派生类构造函数声明为:派生类构造函数 ( 变元表 ) : 基类 ( 变元表 ) , 对象成员1( 变元表 )… 对象成员n ( 变元表 ) ;构造函数执行顺序:基类,对象成员,派生类。

11、派生类构造函数和析构函数的使用原则:基类的构造函数和析构函数不能被继承;如果基类没有定义构造函数或有无参的构造函数,
派生类也可以不用定义构造函数;如果基类无无参的构造函数,派生类必须定义构造函数;如果派生类的基类也是派生类,则每个派生类只负责直接基类的构造;派生类是否定义析构函数与所属的基类无关。

12、在C++中,派生类构造函数的一般格式为:

派生类::派生类名(参数总表):基类名(参数表)

{

// 派生类新增成员的初始化语句

}

 注意:这是基类有构造函数且含有参数时使用。

13、一个类有多个直接基类的继承关系称为多继承。

多继承声明语法:

class 派生类名 : 访问控制 基类名1 , 访问控制 基类名2 , … , 访问控制 基类名n

{

 数据成员和成员函数声明

};

14、多继承方式下构造函数的执行顺序:先执行所有基类的构造函数;再执行对象成员的构造函数;最后执行派生类的构造函数。

15、赋值兼容规则指在程序中需要使用基类对象的任何地方,都可以用公有派生类的对象来替代。赋值兼容规则中所指的替代包括以下的情况:a派生类的对象可以赋给基类对象;b 派生类的对象可以初始化基类的引用;c 派生类的对象的地址可以赋给基类类型的指针。

第10章 虚函数与多态

1、多态性是指一个名字,多种语义;或界面相同,多种实现。重载函数是多态性的一种简单形式。虚函数允许函数调用与函数体的联系在运行时才进行,称为动态联编。

2、冠以关键字 virtual 的成员函数称为虚函数。实现运行时多态的关键首先是要说明虚函数,另外,必须用基类指针调用派生类的不同实现版本。

3、注意:一个虚函数,在派生类层界面相同的重载函数都保持虚特性;虚函数必须是类的成员函数;虚函数可以是另一个类的友元;析构函数可以是虚函数,但构造函数不能是虚函数。

4、在派生类中重载基类的虚函数要求函数名、返回类型、参数个数、参数类型和顺序完全相同。如果仅仅返回类型不同,C++认为是错误重载。如果函数原型不同,仅函数名相同,丢失虚特性。

5、说明:

a.派生类应该从它的基类公有派生。b.必须首先在基类中定义虚函数。c.派生类对基类中声明虚函数重新定义时,关键字virtual可以不写。d.一般通过基类指针访问虚函数时才能体现多态性。e.一个虚函数无论被继承多少次,保持其虚函数特性。f.虚函数必须是其所在类的成员函数,而不能是友元函数,也不能是静态函数。g.构造函数、内联成员函数、静态成员函数不能是虚函数。h.析构函数可以是虚函数,通常声明为虚函数。

6、纯虚函数是一个在基类中说明的虚函数,在基类中没有定义, 要求任何派生类都定义自己的版本,纯虚函数为各派生类提供一个公共界面,纯虚函数说明形式:virtual 类型 函数名(参数表)= 0 ; 一个具有纯虚函数的基类称为抽象类。

感悟:通过最后这几章的学习,收获还是比较多的,运算符重载丰富了运算符的含义,使得代码更易读,某些操作更加符合一般的习惯。我对类的封装,继承和多态有了一些了解,类的继承使得代码层次更加分明,而且可以减少代码行数,既节省了时间又使代码更加易读。学习了多态之后,我更加感受到了这门学科的神奇,多态性使得,界面相同却可以有多种实现。我越来越觉得,C++这门语言还有很多知识我不了解,要想知道的更多,还是要自己多看书钻研,以后会更努力的。

相关标签: 知识总结