类和对象
从本章开始我们就要开始学习C++ 中的最为重要的部分,也是让C++ 得以实现面向对象,得以更加方便的进行大型项目编程的最重要的部分——类和对象,类和对象的存在使C++得以完成封装。
类和对象初步认识
简介
什么是类什么是对象呢? 类可以看作是一个类别,是一类事物的抽象和归纳。比如在现实世界中类可以是兔子,可以是人,可以是某一个职业,这是一类事物,我们将其抽象出来,而并非具体的。与之相对的是对象,对象就是类的实例化,比如说快递员这个类我们可以说快递员是一个类,而假如今天我们点了个外卖,呢么具体的今天来给我们送餐的这个人就是一个对象,对象是属于类的,,是类的具体实例。
类的定义
类在C++ 中的语法更加类似于C语言中结构体的语法,不过在C语言中结构体内部只能定义成员变量,不能定义函数,在C++ 中结构体中可以定义成员函数,使得我们可以讲很多东西封装到类中支持面向对象的编程思想。并且在C++中我们更崇尚于用class
来代替struct
。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18struct AddStruct
{
int num1;
int num2;
int AddNum()
{
return num1 + num2;
}
};
class AddClass
{
int num1;
int num2;
int AddNum()
{
return num1 + num2;
}
};
以上我们用两种方法定义了两个类,可以看出在C++中struct
与class
的区别不是很大,但是即使如此它们之间依旧有所区别。其中最主要的区别就是在struct
中的成员默认是公有的(public
),而class
中成员默认是私有的(private
)。
类的访问限定符及封装
封装
在C++中是如何实现封装的呢?其中最为重要的途径就是利用类和它*访问限定符,我们将对象的属性和方法封装在一个类中并为她们加上访问限定符让外界不能随意无序的进行访问我们就完成了封装。
访问限定符
访问限定符一共有三个:private
(私有);public
(公有);protected
(保护)。访问限定符的作用域是从当前访问限定符开始到下一个访问限定符结束或者到整个类的结束。
这里重点介绍前两个,protected
的主要作用起在类的继承方面,后续再做讨论。
public
公有的访问限定符范围内的成员可以被类外部进行访问,我们可以在类外和类内随意使用类中public
的成员,因此我们往往将类向外部提供的接口放在public
中。
private
私有的访问限定符范围内的成员不能被类外部进行访问,我们只能在类内调用或使用private
的成员,因此private
往往有保护类内数据的作用,为了方便起见我们往往将类内的成员变量全部定义为private
,如果想要对类内数据机型修改则需要另写接口,这样的安全性更高。
之后我们就用类访问限定符改造我们之前定义的Add
类。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19class AddClass
{
private:
int _num1;
int _num2;
public:
void GetNum1(int num1)
{
_num1 = num1;
}
void GetNum2(int num2)
{
_num2 = num2;
}
int AddNum()
{
return _num1 + _num2;
}
};
经过改造的类中的成员变量由于是private
类型受到保护因此我们要再向外多提供几个接口以便让外部可以修改内部的值。同时为了将成员变量与函数中的变量加以区分我们往往会用_
来修饰成员变量,这不是必须但是会提升代码可读性。
类的作用域
其实类一旦声明再类内就形成了一个域,域外无法访问域内的成员。我们在域内可以直接声明并且定义成员函数,但是在域内定义的成员函数会默认作为内联函数对待,而且为了代码的可读性我们也并不能将所有的成员函数都定义在类内,那么如何在类外定义成员函数呢?为此我们再将之前写的类进行改造。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23class AddClass
{
private:
int _num1;
int _num2;
public:
//默认内联但为了可读性我们还是加上内联的标志
inline void GetNum1(int num1)
{
_num1 = num1;
}
inline void GetNum2(int num2)
{
_num2 = num2;
}
//在类内声明
int AddNum();
};
//在类外定义
int AddClass::AddNum()
{
return _num1 + _num2;
}
我们可以通过域限定符来在类外定义函数,这样我们就可以将声明和定义分开,使代码可读性更高。
类的实例化
在定义完类之后我们就需要用类来建立我们真正要使用的对象,我们称这一过程也叫类的实例化。类进行实例化十分方便,和用结构体定义变量无异。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#include <iostream>
using namespace std;
class AddClass
{
private:
int _num1;
int _num2;
public:
//默认内联但为了可读性我们还是加上内联的标志
inline void GetNum1(int num1)
{
_num1 = num1;
}
inline void GetNum2(int num2)
{
_num2 = num2;
}
//在类内声明
int AddNum();
};
//在类外定义
int AddClass::AddNum()
{
return _num1 + _num2;
}
int main()
{
AddClass add;
add.GetNum1(1);
add.GetNum2(2);
cout << add.AddNum() << endl;
}
[misaki@localhost 第二章-类和对象]$ ./Class
3
由此即完成了类的实例化即成员调用。
类的大小计算
类的大小计算与结构体无异遵循内存对齐的规则,但有几点需要注意:
1、类的大小只计算成员变量的大小,遵循内存对齐规则。
2、类中的方法不算做类的大小,为了节省空间将方法存储在公共的区域且只存一个。
3、空类的大小为1,这里占一个字节不进行数据存储只是为了在内存上占位。这里包括所有没有成员变量的类,哪怕有再多方法也是只有1字节大小。
this指针
什么是this指针
既然我们类中的函数都存在同一块区域中,那么编译器使怎么区分是哪个对象调用了成员函数呢?这就牵扯到了每个类中隐藏的成员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#include <iostream>
using namespace std;
class AddClass
{
private:
int _num1;
int _num2;
public:
//默认内联但为了可读性我们还是加上内联的标志
inline void GetNum1(int num1)
//inline void GetNum1(AddClass* this, int num1)
{
_num1 = num1;
}
inline void GetNum2(int num2)
{
_num2 = num2;
}
//在类内声明
int AddNum();
//int AddNum(AddClass* this);
};
//在类外定义
int AddClass::AddNum()
{
return _num1 + _num2;
}
int main()
{
AddClass add;
add.GetNum1(1);
//add.GetNum1(&add, 1);
add.GetNum2(2);
cout << add.AddNum() << endl;
//AddNum(&add);
}
this指针特性
1、this指针不存储在类中,每个编译器对this指针存储的地方都有所不同,在vs中this指针存储在寄存器中。
2、this指针可以为空,但是一旦调用需要访问对象中成员的函数就会由于this为空发生内存越界而导致崩溃。