类和对象
-
面向对象的三大特性:分装、继承、多态
-
对象:有属性和行为
-
类:具有相同性质的对象抽象而来
1. 封装
1.1封装
-
意义:将属性和行为作为一个整体,并加以权限控制 ,来表现事物
-
格式:
class ClassName{ AccessPermission: property/behavior//属性/行为 }
- 访问权限:
- public(公共权限:成员在类内类外都可以访问,派生类的可以访问)
- protected(保护权限:类内可以访问,类外不可以访问,派生类的可以访问)
- private(私有权限:类内可以访问,类外不可以访问,派生类的不可以访问)
1.2 struct和class区别
- class:默认访问权限是私有
- struct:默认访问权限是公有
- 成员属性设为私有:可自己控制读写权限,并可检测数据的有效性
2 对象的初始化和清理
- 默认情况下,编译器会自动生成三个函数:默认构造函数、默认析构函数、默认拷贝构造函数
2.1 构造函数:初始化
-
格式
ClassName(){}
-
注意:
- 无返回值,不用写void
- 函数名与类名相同
- 可以有参数,可重载
- 在调用对象时自动构造,只会调用一次,无需手动调用
-
分类:
-
参数:有参构造和无参构造(就是小括号内有无参数)
-
类型:普通构造和拷贝构造
-
拷贝构造函数的格式
ClassName(const ClassName &t){赋值操作}
-
-
-
调用
- 类型:括号法、显示法、隐式转换法
-
括号法
//t,t1是类 ClassName t;//默认构造 ClassName t(5);//有参构造函数 ClassName t(t1);
注意:默认构造函数不要写小括号!!!如果加括号,编译器会认为是函数声明。
-
显示法
//t,t1是类 ClassName t;//默认构造 ClassName t=ClassName(5);//有参构造函数 ClassName t=ClassName(t1);//拷贝构造
注意:
1. ClassName(t1)是匿名对象,当前行执行结束后,会立马回收
2. 不可用拷贝构造函数初始化匿名对象,编译器默认ClassName(t1) ⟺ ClassName t1 ClassName(t1) \iff ClassName\ t1 ClassName(t1)⟺ClassName t1 -
隐式转换法
ClassName t=10;//有参构造 ClassName t=t1;//拷贝构造
- 使用环境:
- 用已创建的对象初始化新的对象
- 值传递的方式给函数参数传值
- 以值的方式返回局部对象(函数返回值)
-
规则:
- 若自定义了有参构造函数,则不默认生成无参构造函数
- 若自定义了拷贝构造函数,则不默认生成无参构造函数
2.2 析构函数:清理
-
格式
~ClassName(){}
-
注意:
- 无返回值,不用写void
- 函数名与类名相同,并在前面加上~
- 不可以有参数,因此不可重载
- 在对象销毁前自动调用,只会调用一次,无需手动调用
2.3 深拷贝和浅拷贝
-
浅拷贝:简单的赋值拷贝操作
-
深拷贝:在堆区重新申请空间,进行拷贝
-
浅拷贝会出问题,解决方案:需自己实现一个拷贝构造函数来解决浅拷贝问题,否则会造成通用一块地址,一个释放,另一个无法获取原数值
-
例
//拷贝构造函数 m是指针 ClassName(const ClassName &t){ ...; //m=t.m;//编译器默认的操作 m=new int(*t.m);//重新开辟一块内存,用来存放该数据, }
2.4 初始化列表:初始化属性,简化构造函数
-
格式
ClassName():property1(value1),property2(value2),property3(value3)...{}
2.5 类对象作为类成员
- 对象成员:该成员变量是另外一个类的对象
- 构造与析构的顺序:两者正好相反,构造是先构造类对象再构造自身
2.6 静态成员:在成员变量和成员函数前加上关键字 statics
- 分类:静态成员变量、静态成员函数
- 静态成员变量
-
特点
- 所有对象的数值相同,修改时同时变
- 在编译阶段分配内存
- 类内声明,类外初始化(必须有初始值)
- 也有访问权限
-
格式
ClassName{ statics ElemType t; }; ElemType ClassName::t=100;
-
访问方式
-
通过对象进行访问
ClassName t1; cout<<ti.t<<endl;
-
通过类名进行访问
cout<<ClassName::t<<endl;
-
- 静态成员函数
-
特点
- 所有对象共享一个函数
- 只能访问静态成员变量(因无法区分到底是哪个对象的某个属性)
- 也有访问权限
-
格式
ClassName{ statics void func(){cout<<"void调用"<<endl;} };
-
访问方式
-
通过对象进行访问
ClassName t; t.func();
-
通过类名进行访问
ClassName::func();
-
3. c++对象模型和this指针
3.1 成员变量和成员函数分开存储
在c++中类内的成员变量和成员函数分开存储,只有非静态成员变量才属于类的对象上
- 占用大小:看变量类型所占用空间,若是个空类,则生成的实例占用1字节(byte)
3.2 this指针
由上一节可知:每一个费静态成员函数只会诞生一份函数实例,即多个同类型对象会共用一块代码,那他如何区分不同的对象呢?
c++通过特定的对象指针this,其指向被调用的成员函数所属的对象
this指针不需要定义,是个指针常量,不可修改,可直接使用
-
用途:当形参与成员变量同名时,以this指针区分
-
在类的非静态成员函数中返回对象本身
ClassName{ public: ClassName& func(ElemType t){ this.t=t; return *this; } ElemType t; }
3.3 空指针访问成员函数
c++中空指针也可调用成员函数,但要注意函数体内部是否使用this指针,若使用了,则需判断代码的健壮性
3.4 const修饰成员函数
- 常函数:成员函数后加const,修饰的是this指针
- 不可修改成员属性
- 声明时加关键字mutable
- ElemType前加mutable,使得该值可在常函数中修改
- 常对象:成员对象前加const
- 只可调用常函数,普通成员函数可修改属性
- ElemType前加mutable,使得该值可在常对象中修改
4. 友元
- 目的:让一个函数或者类,访问另一个类中的私有成员
- 关键字:friend
- 实现:
- 全局函数做友元
- 函数在外直接声明,再在类内以friend开头声明函数,类外实现
- 类做友元
- 在类内以friend开头声明另一个类
- 成员函数做友元
- 函数在原类中直接声明,再在所需访问的类内以friend开头声明函数,类外实现
- 全局函数做友元
5. 运算符重载
- 概念:对已有运算符重新进行定义,赋予其另外一种功能,以适应不同数据类型
5.1 加号运算符重载
-
作用:实现两个自定义数据类型的相加
-
类型:
-
成员函数重载
ClassName operator+(ClassName &t1){ ClassName t; t.m_A=t1.m_A+this->m_A; t.m_B=t1.m_B+this->m_B; return t; } ClassName t=t1.oprator+(t2);//本质调用 ClassName t=t1+t2;//简化
-
全局函数重载
ClassName operator+(ClassName &t1,ClassName &t2){ ClassName t; t.m_A=t1.m_A+t2.m_A; t.m_B=t1.m_B+t2.m_B; return t; } ClassName t=oprator+(t1,t2);//本质调用 ClassName t=t1+t2;//简化
-
5.2 左移运算符
-
只能用全局函数进行重载,否则输出顺序就会不同
ostream operator <<(ostream &cout,ClassName &t){ //本质 operator<<(cout,t) 可简化为 cout<<t //这里用 & 是因为cout只有一个,且cout可以改成其他别名 cout<<t.m_A<<t.m_B; return cout;//使用了链式思想使得输出可无限增加 } cout<<t1<<t2<<endl;//调用
5.3 递增运算符重载
-
前置递增
ClassName& operator++(){ //& 是为了一直对一个数据进行++ m_A++; return *this; }
-
后置递增
ClassName operator++(int){ //这里返回的必须是值,t是个局部变量,函数结束就会释放掉,因此若返回引用,则后续均为非法操作 //必须给个参数,以区分于前置递增 ClassName t=*this; m_A++; return t; }
5.4 赋值运算符重载
-
编译器会给一个类默认添加operator=
ClassName operator=(ClassName &t){ if(m_A != NULL){ delete m_A; m_A=NULL; } m_A=new ElemType(*t.m_A); return *this; }
5.5 关系运算符重载
bool operator==(ClassName &t){
if(this->m_A == t.m_A && this->m_B == t.m_B){
return true;
}
return false;
}
5.6 函数调用运算符重载
-
重载后使用方式与函数的调用类似,被称为仿函数
-
较为灵活,无固定写法
-
匿名函数对象:重载小括号,当前被执行完就立即释放
-
形式
ClassName()(100,200);
-
6. 继承
6.1 继承
-
专有名词
- 基类(父类,base class):其中被继承的已有类
- 派生类(子类,扩展类,derived class):继承得到的新类
-
格式
class DerivedClass:继承放肆 BaseClass
-
方式
-
作用:减少代码量
6.3 继承方式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8vPWxZtx-1629084952541)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20210815164144510.png)]
6.3 继承中的对象模型
- 子类会继承父类中的所有属性和方法
- 工具:利用开发人员命令提示工具查看对象模型
- 跳转盘符
- 进入所属文件目录
- 输入命令
cl /d1 reportSingleClassLayout类名 "文件名.cpp"
6.4 继承中构造和析构顺序
- 顺序:先构造父类,再构造子类,析构顺序则相反
6.5 继承同名(静态)成员处理方式
-
子类对象可直接访问子类同名成员
-
子类对象访加作用域(即在成员名前加上 父类:: )可访问父类同名成员
-
特殊的静态成员处理:
//静态成员属性通过类名访问父类的 Son::Base::m_A; Son::Base::func();
-
-
若子类出现和父类同名的成员函数,子类会隐藏掉父类中所有同名成员函数,加作用域可访问父类中被隐藏的同名成员函数
6.6 多继承:允许一个类继承多个类
-
格式
class Son: 继承方式 Base1, 继承方式 Base2...
-
可能会引发父类中有同名成员的出现,需加作用域加以区分
6.7 菱形继承(钻石继承)
- 特点:
- 两个派生类继承同一个基类
- 又有某个类同时继承两个派生类
- 产生的问题:
- 二义性,即某类同时继承两个派生类,而两个派生类中有成员相同
- 某类会继承两份来及最上面基类的数据,造成数据冗余
- 解决:利用虚继承,在继承前加上关键词virtual 变为虚继承,而最上面的基类被称为虚基类