知识点4:类和对象
4.1封装
4.1.1封装的意义
封装的案例:
类内可以访问,表示在类三种权限里面的属性都可以访问。无论是属性也好还是方法也好,都可以设置权限,比如说,类里面的函数如果设置为public,在主函数中实例化出来的对象可以方位这个方法,如果是这个方法被设置为私有权限(private)或保护权限(protected)在类外是不可以被访问的。但是在类内无论什么权限都可以访问
如果方法是私有属性,就不可以访问了
4.1.2struct和class区别
4.1.3成员属性设置为私有
在类中可以让另一个类,作为本类中的成员
你可以在源文件中调用头文件中声明的函数,但函数的实现仍然需要在链接阶段解析
在写代码的时候不能可能将所有的类写在一个文件,大型开发更不可能写在一个文件里面,通常将会将一个类拆到另一个文件里面。在头文件中只写它成员函数和成员变量的声明。
当你在写功能的时候包含头文件,编译器会将头文件的内容插入到你的源文件中,使得你可以在源文件中调用头文件中声明的函数或使用头文件中声明的变量。但是,这并不意味着函数的实现也被插入到你的源文件中。函数的实现通常在对应的源文件中,然后通过编译器将源文件编译成目标文件,最后链接成可执行文件。因此,虽然你可以在源文件中调用头文件中声明的函数,但函数的实现仍然需要在链接阶段解析。
头文件提供了函数的声明和接口,而源文件包含了函数的实现。在编写功能时,头文件的目的是让你能够使用接口而不需要了解具体的实现细节。
4.2对象的初始化和清理
4.2.1构造函数和析构函数
构造函数:
没有返回值 不用写void
函数名 与类名相同
构造函数可以有参数,可以发生重载
创建对象的时候,构造函数会自动调用,而且只调用一次
即使你不写构造函数,编译器会给你写一个构造函数,只是这个构造函数是空实现。
析构函数:
进行清理操作
没有返回值,不写void
函数名和类名相同 在名称前加~
析构函数不可以有参数,不可以发生重载
对象在销毁前 会自动调用析构函数,而且只会调用一次
4.2.2构造函数的分类及调用
注意事项:不要利用拷贝构造函数,初始化匿名对象 编译器会认为person(p3)==person p3;对象声明。也就是说编译器会把person(p3)当作person p3;而后面的person p3就是对象的声明。
4.2.3拷贝构造函数调用时机
值传递的方式给函数参数传值,方框中的两个p,不是同一个p,在dowork中修改p中变量的值不会影响test02中的p 。
在这段代码中dowork不会返回p1这个对象,而是把p1这个对象拷贝一个新的对象返回给外边。
函数是在栈上的,当函数执行结束后就会释放掉,所以不会将p1这个对象返回,而是返回一个新的对象给外边。
4.2.4构造函数调用规则
将上面默认构造函数注释掉,只留下有参构造函数和拷贝构造函数,会出现下面的情况
将拷贝构造函数也注释掉,只保留有参构造函数,那么会生成一个默认的拷贝构造函数。
如果只提供有参构造函数,那么默认构造函数不会提供,但是拷贝构造函数会提供。
如果只提供了拷贝构造函数,那么默认构造函数和有参构造函数都不在提供。
4.2.5深拷贝和浅拷贝
浅拷贝:
浅拷贝中,因为两个指针指向同一块内存,所以析构函数析构了两次,会发生错误!!!
深拷贝:
代码实现如下:
上面的代码就是使用了默认的拷贝构造函数,而默认的拷贝构造函数是浅拷贝,在析构的时候就会析构两次。代码就会出现问题。
代码改进
给上面的写一个自己的拷贝构造函数。不使用编译器的拷贝构造函数。
4.2.6初始化列表
构造函数主要的用途就是给属性做初始化操作,C++中也提供了另一种方法也可以给属性做初始化操作,就是初始化列表。
初始化列表需要注意:冒号的位置!!!!
4.2.7类对象作为类成员
当一个类中有其他成员类时,我们在实例化对象的时候,先有胳膊腿,才能有这个人,所以是先构造的胳膊腿,在构造的这个人。
总结:
当类中成员是其他类对象时,我们称该成员 对象成员
构造的顺序是:先调用对象成员的构造,在调用本类构造
析构的顺序与构造顺序相反
4.2.8静态成员
4.2.8.1静态成员变量
在编译阶段分配内存,它的创建时机并不是创建对象之后,分配在栈上或者是堆上,而是说在编译阶段分配内存相当于,程序还没有运行之前就已经给它分配内存了,程序运行前有代码区和全局区,那肯定是在全局区中。
这个变量必须要有一个初始值,否者的话没法去用它,所以类内声明类外初始化是一个必要的操作。
静态成员变量类内声明类外初始化 。
静态成员变量的访问方式,通过对象进行访问,通过类名进行访问。
4.2.8.2静态成员函数
所有对象共享同一个函数
静态成员函数只能访问静态成员变量
总结:
静态成员函数特点:
1 程序共享一个函数
2 静态成员函数只能访问静态成员变量
静态成员函数的两种访问方式:
1 通过对象访问
2 通过类名访问,如果该静态成员函数是私有权限,那么类外访问不到。
4.3C++对象模型和this指针
4.3.1 成员变量和成员函数分开存储
非静态成员变量属于类的对象。
静态成员变量、非静态成员函数、静态成员函数都不属于类的对象上。
4.3.2 this指针的概念
C++中成员变量和成员函数是分开存储的,每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码,这一块代码是如何区分哪个对象调用自己的呢?
C++通过提供特殊的对象指针,this指针,解决上述问题。
this指针指向被调用的成员函数所属的对象。
例如:一个类示例化了3个对象,p1,p2,p3,假如这些对象都去在使用这个函数,好多对象都在调用这个函数,这个函数只有一份,那么这个函数体怎么区分,这是p1调的,这是p2调的,这是p3调的呢?我这函数该怎么区分是修改p1的属性,还是修改p2的属性,还是修改p3的属性呢?就是要通过this指针,当p1调用这个函数了,那么this就会指向p1这个对象。
this->p1.通过点就可以修改里面的属性了。!!!!!!
this指针的用途:
- 当形参和成员变量同名时,可以用this指针来区分
- 当类的非静态成员函数中返回对象本身,可以使用return*this;
- this是一个指针,假设指向对象p1,那么*this就是p1这个对象了
1、解决命名冲突
2、
注意:如果函数返回的不是指针,而是值
如果返回的不是引用,而是返回的值的话,那在函数是在栈上保存,运行结束就会自动释放,而返回的对象是拷贝的对象,而不是原来的对象,这个返回的新对象里面的属性是原始值
4.3.3 空指针访问成员函数
类中的第一个函数中即使是空指针调用也会运行,但是第二个函数不会,因为第二个函数使用了成员变量。
4.3.4 const修饰成员函数
4.4友元
4.4.1全局函数做友元
友元函数是告诉编译器goodGay这个全局函数 是building类的好朋友,可以访问类中的私有内容
4.4.2类做友元
//类做友元
在本类的构造函数中怎样实例化其他类。使用new创建其他类的对象。
在类外写类的构造函数要加作用域限定符 ,但是还是需要在类中声明构造函数。
如果本类中要使用到其他类,那么需要把这个使用到的类在本类上面做一下声明。
4.4.3成员函数做友元
4.5运算符重载
运算符重载概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型
4.5.1加号运算符重载
运算符重载知识引入:
运算符重载:它可以实现自定义数据类型加减乘除,只不过这个函数的名称是由编译器起的
运算符重载代码实现:
- 函数重载就是在同一个作用域下:函数的名字相同,但是函数传入参数不同。
- 运算符重载也可以发生函数重载。
- 无论成员函数重载还是全局函数重载,都可以简写。如:p3=p2+p1;
总结1:对于内置的数据类型的表达式的运算符是不可以改变的,我们可以修改的是自定义的数据类型
总结2:不要滥用运算符重载。滥用就是把加法写为减法。
4.5.2左移运算符重载
左移运算符知识点引入:
重载左移运算代码:
全局函数本质 operator << (cout , p) 简化 cout << p 。
总结:重载左移运算符配合友元可以实现输出自定义数据类型
4.5.3递增运算符重载
递增运算符知识点引用:
重载右移运算符代码:
总结:前置递增返回的是引用,后置递增返回的是值。
4.5.4赋值运算符重载
4.5.5关系运算符重载
关系运算符问题引入:
关系运算符重载代码:
4.5.6函数调用运算符重载
函数调用运算()重载之后的使用非常像函数的调用,因此又称为仿函数。
4.6继承
4.6.1继承的基本语法
不使用继承代码:
使用继承后的代码:
4.6.2继承的方式
父类中私有的属性,子类无论是那种继承方式都不能访问。
除了父类中的私有属性不可访问外,其他两种属性如下:
- 公有继承:父类中是什么属性子类是什么。
- 保护继承:父类中的公有属性变为保护属性。
- 私有继承:父类中的共有属性和保护属性在子类中都为私有属性。
子类私有继承父类中的共有和保护,那么父类中的共有和保护都变为了子类的私有成员,所以子类可以访问。
保护权限在类内可以访问,在类外不可以访问。子类中也是一样,如果子类共有继承了父类,那么父类中保护属性在子类中也是保护属性,也就是在子类类内可以访问,类外不可以访问。
父类中的私有属性,在子类中也都是私有属性,在父类中可以访问,在子类中不可以访问,在类外也不可以访问。父亲的隐私内容,子类和类外都访问不到。
子类的子类:
4.6.3继承中的对象模型
如何查看对继承模型步骤如下:
结论:父类中私有成员也是被子类继承下去了,只有由编译器给隐藏后访问不到了。
4.6.4继承中构造和析构的顺序
总结:继承中先调用类构造函数,再在调用子类构造函数,析构顺序与构造相反。
4.6.5继承同名成员处理方式
4.6.6继承同名静态成员处理
静态变量需要类内声明类外初始化。
总结:同名静态成员函数处理方式和非静态处理方式一样,不同的是静态成员函数有两种访问方式(通过对象访问和通过类名访问)
4.6.7多继承语法
4.6.8菱形继承
通过工具查看可知确实有两份数据,这份数据只需要有一份即可。
解决问题加关键字virtual。
虚继承:有两个子类继承了同一个父类,并且还有一个孙子类同时继承了这两个之类,父类中有一个变量,两个子类都继承了,那么,孙子类同时继承了这两个子类,那么孙子类访问子类继承的父类中的变量时,父类中的这两个变量
4.7多态
4.7.1多态的基本概念
4.7.1.1多态的基本语法
静态多态:
多态就是多种形态:函数重载就是可以让函数名有多种形态表现出来,由于我们的参数传入的个数不同,类型不同,或者顺序不同,都可以让函数名有多种形态来表现。运算符重载就是让我们的这些符号,加减乘除这些符号有多种形态表现出来,对于自定义的类型也可以加减乘除,这些都属于静态多态。
动态多态:
通常我们说的这种多态是动态多态,派生类和虚函数运行时多态。
在C++允许父子之间类型转换 ,它不需要强制类型转换,父类的引用可以直接指向子类的对象
重载:是函数名相同,参数是不一样的,我们叫重载。
重写:函数的返回值相同,函数名要相同,形参的内容(参数列表:就是函数中括号里面的形参)也要相同,我们才叫重写。
函数前面加上virtual 关键字,变成虚函数,那么编译器编译的时候不能确定函数调用了。
4.7.1.2多态的原理剖析:
上面示例代码中动物类如果没有virtual关键字修饰的话,动物类就是一个空类,大小一个字节,只是为了区分
当在猫类里面没有发生重写时,猫类继承了动物类,因为没有发生重写,所以是继承的是动物类中 的函数,如下图
当猫类中重写动物类中的同名函数时,如下图:
总结如下:
4.7.2多态案例--计算器类
不使用多态实现计算器
使用多态的计算器:
总结:C+开发提倡利用多态设计程序架构,因为多态优点很多。
4.7.3纯虚函数和抽象类
纯虚函数:在第一个示例中,我们在动物类(也就是父类中)写的虚函数,我们的本意是传入什么动物让什么动物说话,并没有想要调用动物类(父类)中虚函数。所以我们就会发现父类中的虚函数用不到,因此我们可以把这个虚函数写为纯虚函数了。当类中出现了纯虚函数后,我们就把这这个类叫做抽象类。
1、它无法实例化对象,无论是在栈上还是在堆上。
2、子类必须重写抽象类中的纯虚函数,否者子类也属于抽象类,子类也无法实例化对象。
4.7.4多态案例二--制作饮品
4.7.5虚析构和纯虚析构
纯虚析构需要在类内声明,还需要再类外实现。
- 什么情况下需要虚析构和纯虚析构,再子类中如果有一些属性(变量数据)开辟到了堆区,所以需要走子类中的析构代码,如果使用多态是走不到(不经过)子类的析构,所以我们再父类中才加上虚析构和纯虚析构。
- 不管是虚析构还是纯虚析构都是解决,多态中析构时不经过子类中析构代码的问题。
4.7.6多态案例三--电脑组装
电脑组装需求:
电脑组装的实现: