面向对象的三大特性:封装,继承,多态
4.1 封装
- public 内外都可以访问
- protected 类内可以访问,类外不行
- private 类内可以访问,类外不行
1 |
|
4.1.2 struct和class曲别
默认的访问权限不同
- struct默认权限为公有
- class默认权限为私有
4.1.3 成员属性设置为私有
优点1:将所有成员属性设置为私有,可以自己控制读写权限
优点2:对于写权限,我们可以检测数据的有效性1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include<iostream>
using namespace std;
//成员属性设置为私有
class Person
{
public:
void setName(string name)//赋予写权限
{
m_Name = name;
}
void setLover(string name)
{
m_Lover = name;
}
string getName()
{
return m_Name;
}
int getAge()
{
return m_Age;
}
void setAge(int age)
{
if(age < 0 ||age > 150)//检测数据的有效性
{
m_Age = 0;
cout << "赋值有误" << endl;
return;
}
m_Age = age;
}
//string getLover()
//{
// return m_Lover;
//}//没有这个函数,Lover就变成只写变量了
private:
//对读写权限的控制
string m_Name;
int m_Age;
string m_Lover;
};
int main() {
Person p;
p.setName("Eric");
cout << "姓名:" << p.getName() << endl;
p.setAge(1000);
cout << "年龄:" << p.getAge() << endl;
p.setLover("Ding");
//cout << "Lover:" << p.getLover() << endl;
system("pause");
return 0;
}
4.2 对象的初始化和清理
4.2.1 构造函数和析构函数
构造函数: 创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无需手动调用。类名(){}
没有返回值,与类名相同,可以有参数,无需手动调用,系统会调用一次
析构函数: 对象销毁前系统自动调用,执行一些清理工作。~类名(){}
与类名相同,在名称前加~。不能有参数,无返回值,无需手动调用,系统会调用一次
4.2.2 构造函数的分类及调用
两种分类方式,三种调用方式。
按参数分:有参构造,无参构造
按类型分:普通构造,拷贝构造
括号法,显示法,隐式转换法
4.2.3 拷贝构造函数的调用时机
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数
- 如果用户定义有参构造函数,C++不再提供默认无参构造
- 如果用户定义拷贝构造函数,C++不再提供其他构造函数
4.2.5 深拷贝与浅拷贝
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间进行拷贝操作有指针的时候要注意1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include<iostream>
using namespace std;
//构造函数的调用规则
class Person
{
public:
Person()
{
cout << "Person的默认构造函数调用" << endl;
}
Person(int a,int height)
{
m_Age = a;
m_Height = new int(height);
cout << "Person的有参构造函数调用" << endl;
}
//自己实现一个拷贝构造函数来解决浅拷贝带来的问题
Person(const Person &p)
{
cout << "Person的拷贝构造函数调用" << endl;
m_Age= p.m_Age;
//m_Height = p.m_Height;编译器默认实现就是这行代码
//深拷贝操作
m_Height = new int(*p.m_Height);
}
~Person()
{
//通常将堆区开辟的数据做释放操作
//浅拷贝带来的问题就是,堆区内存的重复释放
if(m_Height != NULL)
{
delete m_Height;
m_Height = NULL;
}
cout << "Person的析构函数调用" << endl;
}
int m_Age;
int *m_Height;
};
void test01()
{
Person p1(18,160);
cout << "p1的年龄为 " << p1.m_Age << " 身高为: " << *p1.m_Height << endl;
Person p2(p1);
cout << "p2的年龄为" << p2.m_Age << " 身高为: " << *p2.m_Height << endl;
}
int main(){
test01();
system("pause");
return 0;
}4.2.6 初始化列表
语法: 构造函数():属性1(值1),属性2(值2)…1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include<iostream>
using namespace std;
//初始化列表
class Person
{
public:
//传统初始化操作
// Person(int a,int b,int c)
// {
// m_A = a;
// m_B = b;
// m_C = c;
// }
//初始化列表初始化属性,注意冒号位置
Person(int a,int b,int c):m_A(a),m_B(b),m_C(c)
{
}
int m_A;
int m_B;
int m_C;
};
void test01()
{
Person p(1,2,3);
cout << "m_A = " << p.m_A << endl;
cout << "m_B = " << p.m_B << endl;
cout << "m_C = " << p.m_C << endl;
}
int main(){
test01();
system("pause");
return 0;
}4.2.7 类对象作为类成员
C++类中的成员可以实另一个类的对象,我们称该成员为对象成员。
当其他类对象作为本类成员,构造时候先构造类对象,再构造自身。
析构函数调用正好相反,后构造的先释放,类似堆,后进先出。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include<iostream>
using namespace std;
#include<string>
//类对象作为类成员,
class Phone
{
public:
Phone (string pName)
{
cout << "Phone构造函数调用" << endl;
m_pName = pName;
}
//手机品牌名称
~Phone()
{
cout << "Phone析构函数调用" << endl;
}
string m_pName;
};
class Person
{
public:
Person(string name,string pName):m_Name(name) ,m_Phone(pName)//隐式转换法
{
cout << "Person构造函数调用" << endl;
}
~Person()
{
cout << "Person析构函数调用" << endl;
}
string m_Name;
Phone m_Phone;
};
void test01()
{
Person p("张三","iphone max");
cout << p.m_Name << " 拿着: " << p.m_Phone.m_pName << endl;
}
int main(){
test01();
system("pause");
return 0;
}1
2
3
4
5
6Phone构造函数调用
Person构造函数调用
张三 拿着: iphone max
Person析构函数调用
Phone析构函数调用
请按任意键继续. . .4.2.8 静态成员
就是在成员变量和成员函数前加上关键字static ,称为静态成员
- 静态成员变量
- 所有对象共享同一份数据
- 在编译阶段分配内存
- 类内声明,类外初始化
- 静态成员函数
- 所有对象共享同一个函数
- 静态成员函数只能访问静态成员变量
4.3 C++对象模型和this指针
4.3.1 成员变量和成员函数分开存储
4.2.3 this指针概念
this指针指向被调用成员函数所属的对象
是隐含在每一个 非静态成员函数 体内的一种指针,不需要定义,直接使用就行,返回对象本身可以用return *this;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include<iostream>
using namespace std;
class Person
{
public:
Person(int age)
{
//this指针指向的是被调用的成员函数 所属的对象
this->age = age;
}
//如果这里返回的是Person不是Person&,就会创建一个新的对象
Person& PersonAddAge(Person &p)
{
this->age += p.age;
//this是指向p2的指针,而*this是p2本体
return *this;
}
int age;
};
//解决名称冲突
void test01()
{
Person p1(18);
cout << "p1的年龄为:" << p1.age << endl;
}
void test02()
{
Person p1(10);
Person p2(10);
//链式编程思想
p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);
cout << "p2的年龄为: " << p2.age << endl;
}
int main(){
test02();
system("pause");
return 0;
}4.3.3 空指针访问成员函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include<iostream>
using namespace std;
//空指针调用成员函数
class Person
{
public:
void showClassName()
{
cout << "This is Person class!" << endl;
}
void showPersonAge()
{
if (this == NULL)//提高代码的健壮性
{
return;
}
cout << "age = " << m_Age << endl;//其实这里的m_Age是this->m_Age的一种表达形式
}
int m_Age;
};
void test01()
{
Person * p = NULL;
p->showClassName();
p->showPersonAge();
}
int main(){
test01();
system("pause");
return 0;
}4.3.4 const修饰成员函数
常函数:
- 成员函数加const后可以称为常函数
- 长函数类不可修改成员属性
- 成员属性声明时加关键字mutable后,在常函数中依然可以修改
常对象:
- 声明对象前加consy称为常对象
常对象只能调用常函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include<iostream>
using namespace std;
class Person
{
public:
//this指针的本质 是指针常量,指针的指向是不可以修改的
//下面const类似于 const Person * const this;
//在成员函数后加const,修饰的是this指向,让指针指向的对象包含的成员值也不可修改
void showPerson() const
{
this->m_B = 10;
//this->m_A = 10;//左值不可修改
//this = NULL;
}
void func()
{
m_A = 10;
}
int m_A;
mutable int m_B;//特殊变量,在常函数中也可以修改这个值,加关键字mutable
};
void test01()
{
Person p;
p.showPerson();
}
//常对象
void test02()
{
const Person p;//在对象前加上const变为常对象
//p.m_A = 100;//不可修改
p.m_B = 100;//m_B是特殊值,在常对象下也可以修改
//常对象只能调用常函数(const只能调用const)
p.showPerson();
//p.func();//常对象不能调用普通成员函数,因为这样就可能修改成员属性了
}
int main(){
test01();
system("pause");
return 0;
}4.4 友元
有些私有属性想让类外一些特殊的函数或者类访问
三种实现:全局函数做友元
- 类做友元
- 成员函数做友元
4.4.1 全局函数做友元
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include<iostream>
#include<string>
using namespace std;
//建筑物类
class Building
{
//goodGay全局函数是Building好朋友,可以访问私有成员
friend void goodGay(Building *building);
public:
Building()
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
string m_SittingRoom;
private:
string m_BedRoom;
};
void goodGay(Building *building)
{
cout << "friend is visiting : " << building->m_SittingRoom << endl;
cout << "friend is visiting : " << building->m_BedRoom << endl;
}
void test01()
{
Building building;
goodGay(&building);
}
int main(){
test01();
system("pause");
return 0;
}4.4.2 类做友元
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include<iostream>
using namespace std;
#include<string>
class Building;
class GoodGay
{
public:
GoodGay();
void visit();//参观函数,访问Building中的属性
Building * building;
int m_Age;
};
class Building
{
//告诉编译器GoodGay可以访问Building中的私有内容
friend class GoodGay;
public:
Building();
string m_SittingRoom;
private:
string m_BedRoom;
};
Building::Building()
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
GoodGay::GoodGay()
{
//创建一个建筑物对象
building = new Building;
}
void GoodGay::visit()
{
cout << "friend is visiting : " << building->m_SittingRoom << endl;
cout << "friend is visiting : " << building->m_BedRoom << endl;
}
void test01()
{
GoodGay gg;
gg.visit();
}
int main(){
test01();
system("pause");
return 0;
}4.4.3成员函数做友元
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include<iostream>
using namespace std;
#include<string>
class Building;
class GoodGay
{
public:
GoodGay();
void visit();//参观函数,访问Building中的属性
void visit2();//不可访问Building中的属性
Building * building;
int m_Age;
};
class Building
{
//告诉编译器GoodGay类中的visit()可以访问Building中的私有内容
friend void GoodGay::visit();
public:
Building();
string m_SittingRoom;
private:
string m_BedRoom;
};
Building::Building()
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
GoodGay::GoodGay()
{
//创建一个建筑物对象
building = new Building;
}
void GoodGay::visit()
{
cout << "visit is visiting : " << building->m_SittingRoom << endl;
cout << "visit is visiting : " << building->m_BedRoom << endl;
}
void GoodGay::visit2()
{
cout << "visit2 is visiting : " << building->m_SittingRoom << endl;
//cout << "friend is visiting : " << building->m_BedRoom << endl;
}
void test01()
{
GoodGay gg;
gg.visit();
gg.visit2();
}
int main(){
test01();
system("pause");
return 0;
}4.5 运算符重载
对已有的运算符重新进行定义,赋予另一种功能,以适应不同的数据类型4.5.1 加号运算符重载+
实现两个自定义数据类型的相加运算
内置的数据类型的表达式是不能改变的
不要滥用1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include<iostream>
using namespace std;
//加号运算符重载
class Person
{
public:
//1、成员函数实现重载
// Person operator+(Person &p)
// {
// Person temp;
// temp.m_A = this->m_A + p.m_A;
// temp.m_A = this->m_B + p.m_B;
// return temp;
// }
int m_A;
int m_B;
};
//2、全局函数重载+
Person operator+(Person &p1,Person &p2)
{
Person temp;
temp.m_A = p1.m_A + p2.m_A;
temp.m_A = p1.m_B + p2.m_B;
return temp;
}
//函数重载版本
Person operator+(Person &p1,int num)
{
Person temp;
temp.m_A = p1.m_A + num;
temp.m_A = p1.m_B + num;
return temp;
}
void test01()
{
Person p1;
p1.m_A = 10;
p1.m_B = 10;
Person p2;
p2.m_A = 10;
p2.m_B = 10;
//成员函数本质调用
//Person p3 = p1.operator+(p2);
//全局函数本质调用
//Person p3 = operator+(p1,p2);
Person p3 = p1 + p2;
//运算符重载也可以发生函数重载
Person p4 = p1 + 100;
cout << "p3.m_A = " << p3.m_A << endl;
cout << "p3.m_A = " << p3.m_A << endl;
cout << "p4.m_A = " << p4.m_A << endl;
cout << "p4.m_A = " << p4.m_A << endl;
}
int main(){
test01();
system("pause");
return 0;
}4.5.2 左移运算符重载<<
作用 输出自定义数据类型
Person p;
cout << p << endl; 怎么定义?1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <iostream>
using namespace std;
//左移运算符重载
class Person
{
friend ostream & operator<<(ostream &cout, Person &p);
public:
Person (int a,int b)
{
m_A = a;
m_B = b;
}
// //利用成员函数重载 左移运算符 p.operator<<(cout) 简化版本p << cout
// //不会利用成员函数重载<<运算符,因为无法实现cout在左侧
// void operator<<(cout)
// {
// }
private:
int m_A;
int m_B;
};
//只能用全局函数重载左移运算符
ostream & operator<<(ostream &cout, Person &p) //本质 opertor<<(cout,p) 简化cout << p
{
cout << "m_A = " << p.m_A << " m_B = " << p.m_B << endl;
return cout;
}
void test01()
{
Person p(10,10);
// p.m_A = 10;
// p.m_B = 10;
cout << p << "hello world!" << endl;
}
int main()
{
test01();
system("pause");
return 0;
}4.5.3 递增运算符重载++
作用: 通过重载递增运算符,实现自己的整型数据
前置递增返回引用,后置递增返回值1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include<iostream>
using namespace std;
//递增运算符重载
class MyInteger
{
friend ostream & operator<<(ostream &cout,MyInteger myint);
public:
MyInteger()
{
m_Num = 0;
}
//重载前置++运算符
//为了一直对一个数据进行递增操作
MyInteger & operator++()
{
m_Num++;
//将自身返回
return *this;
}
//重载后置++运算符
//int代表占位参数,可以用于区分前置和后置
MyInteger operator++(int)
{
//先 记录当时结果
MyInteger temp = *this;
//后 递增
m_Num++;
return temp;
}
private:
int m_Num;
};
//重载<<运算符
ostream & operator<<(ostream &cout,MyInteger myint)
{
cout << myint.m_Num;
return cout;
}
void test01()
{
MyInteger myint;
cout << ++(++myint) << endl;
cout << myint << endl;
}
void test02()
{
MyInteger myint;
cout << myint++ << endl;
cout << myint << endl;
}
int main(){
test02();
system("pause");
return 0;
}4.5.4 赋值运算符重载=
如果类中有属性指向堆区,做赋值操作是也会出现深浅拷贝问题
4.5.5关系运算符重载 == ,!=
4.5.6函数调用运算符重载
函数调用运算符()也可以重载
4.6 继承
继承实现页面
减少重复代码
语法: class 子类 : 继承方式 父类
子类 也称为 派生类
父类,也称为 基类
公共页面类
1 |
|
4.6.2继承方式
即从父类继承的东西是子类的什么部分
- 公共继承
private里面的子类都继承不到,public和protected都直接继承 - 保护继承
private里面的子类都继承不到,public和protected都继承为子类的protected - 私有继承
private里面的子类都继承不到,public和protected都继承为子类的private1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class Base1
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
//公共继承
class Son1 : public Base1
{
public:
void func()
{
m_A = 10;//父类中的public,在子类中为public
m_B = 10;//父类中的protected,在子类中为protected
//m_C = 10;//父类中的private 子类访问不到
}
};
//保护继承
class Son2 : protected Base1
{
public:
void func()
{
m_A = 100;//父类中的public,在子类中为protected
m_B = 100;//父类中的protected,在子类中为protected
//m_C = 10;//父类中的private 子类访问不到
}
};
//私有继承
class Son3 : private Base1
{
public:
void func()
{
m_A = 100;//父类中的public,在子类中为private
m_B = 100;//父类中的protected,在子类中为private
//m_C = 10;//父类中的private 子类访问不到
}
};4.6.3 继承中的对象模型
利用开发人员命令提示工具查看对象模型
其实父类中所有的非静态成员属性都会被子类继承,私有成员属性被编译器隐藏了,因此访问不到,但是确实被继承了。
4.6.4 继承中构造和析构顺序
子类继承父类后,当子类创建对象,也会调用父类的构造函数,到底先调用哪一个?
先父类的构造,再子类构造,然后析构子类,再析构父类,一个对应一个,一层一层来。
4.6.5 继承同名成员的处理方式
- 访问子类同名成员,直接访问就行
- 访问父类同名成员,需要加作用域
子类中出现和父类同名的成员函数,子类成员会屏蔽所有父类的同名函数,如果想访问到被隐藏的,加作用域
4.6.6 继承 同名静态成员的处理方式
同上
4.6.7 继承 多继承语法
相当于允许一个儿子有多个爹
实际开发中不建议用多继承,容易有二义性
子类对象的size
4.6.7 继承 菱形继承语法
两个派生类继承自同一个基类;又有一个子类继承两个派生类
解决方案:虚继承
这份数据就只有一份了
原理:虚继承把原本继承的各个基类的内部的变量值变成vbptr虚基类指针,指向唯一真实值
4.7 多态
4.7.1 多态的基本概念
C++面向对象的三大特性之一
多态分为两类
- 静态多态:函数重载 运算符重载属于静态多态,服用函数名
- 动态多态:派生类和虚函数实现运行时多态
曲别:
- 静态多态的函数地址早绑定-编译阶段确定函数地址
- 动态多态的函数地址晚绑定(虚函数),运行阶段确定函数地址;
1 |
|
内部这样写,存储的会是虚函数(表)指针
vfptr:virtual function pointer
指向一个
vftable虚函数表
表内部记录虚函数的地址
子类重写的父类虚函数
子类中的虚函数表 内部 会替换成 子类重写的虚函数的地址
当父类的指针或引用指向子类对象时,发生多态。
用猫类的虚函数表替换继承自父类的虚函数表
4.7.2 多态案例-计算机类
案例描述:
分别利用普通写法和多态技术,设计及实现两个操作数进行运算的计算机类
如果想扩展新的功能,需求修改源码
在真实的开发中,提倡开闭原则
对扩展进行开发,对修改进行关闭
多态的好处:
- 组织结构清晰,哪个功能出错了马上就可以定位到
- 可读性强
- 前期和后期的扩展和维护性高:如果想扩展新的功能,需求修改源码,在真实的开发中,提倡开闭原则,对扩展进行开发,对修改进行关闭.
1 |
|
4.7.3 纯虚函数和抽象类
一般在使用多态时,父类虚函数无意义,都是用子类调用,重写内容
因此可以将父类改成 纯虚函数
virtual 返回值类型 函数名 (参数列表)= 0;
此时这个类称为抽象类
- 无法实例化对象
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
1 |
|
4.7.4 多态案例:制作饮品
1 |
|
4.7.5 虚析构和纯虚析构
多态使用时,如果子类中有一些属性放在堆区,父类指针在释放时无法调用子类的析构代码
如图,没有调用Cat的析构函数,原因在于用父类指针指向的子类对象,父类指针在析构的时候不会调用子类中的析构函数
解决:将父类中的析构函数改为虚析构或纯虚析构
共性:
- 都可以解决父类指针释放子类对象
- 都需要有具体的函数实现
区别: - 如果是纯虚构,该类属于抽象类,无法实例化对象
虚析构语法:virtual ~类名(){}
纯虚构语法:virtual ~类名 () = 0
1 |
|