目录
1.初识类
(1)类的引入
struct是C语言中用来定义结构体的关键字,但结构体中不可以定义函数,只能在结构体外定义,下面用结构体来模拟实现类的功能。
代码一:使用C语言中的结构体模拟实现一个简单的日期类来举例。(本文代码均在win10系统的vs2019验证)
结构体的初始化通过传递结构体类型变量的指针和实参来进行。操作结构体的函数都需要传递结构体类型变量的指针来进行操作。
//代码一
#include <stdio.h>
struct Date {
int _year;
int _month;
int _day;
};
void Init(struct Date* pd, int year, int month, int day) {
pd->_year = year;
pd->_month = month;
pd->_day = day;
}
void Print(struct Date* pd) {
printf("%d-%d-%d", pd->_year, pd->_month, pd->_day);
}
int main() {
struct Date d1;
Init(&d1, 2012, 12, 12);
Print(&d1);//输出 2012-12-12
}
C++中则可以将函数定义在类的内部,称为成员函数。
代码二:将代码一中的结构体使用C++进一步改造成类。
//代码二
#include "iostream"
using namespace std;
class Date {
public:
int _year;
int _month;
int _day;
void Init(int year, int month, int day) {
_year = year;
_month = month;
_day = day;
}
void Print() {
cout << _year << "-" << _month << "-" << _day;
}
};
int main() {
Date d1;
d1.Init(2012, 12, 12);
d1.Print();//输出 2012-12-12
}
可以观察到,将函数定义在类中后,比代码一的函数少了一个指针参数。可以说,类继承了结构体的优点,同时将它的缺点也进行了改进,并且降低了函数参数的复杂度。
(2)类的定义
上文中简单展示了类,接下来详细学习如何定义类。C++中的结构体内可以定义函数,也就是说,C++中的struct和class都可以用来定义类。定义类共有两种方法,如下。
<1>类的声明与定义均在类体中。如同上文中的代码二。
<2>类的声明在头文件,类的定义在源文件实现。如代码三与代码四
代码三:在头文件Hclass.h中对类进行声明
//代码三
#pragma once
class Date {
public:
int _year;
int _month;
int _day;
void Init(int year, int month, int day);
void Print();
};
代码四:在Mclass.cpp中对成员函数进行定义并使用。
//代码四
#include "iostream"
#include "Hclass.h"
using namespace std;
void Date::Init(int year, int month, int day) {
_year = year;
_month = month;
_day = day;
}
void Date::Print() {
cout << _year << "-" << _month << "-" << _day;
}
int main() {
Date d1;
d1.Init(2012, 12, 12);
d1.Print();//输出 2012-12-12
}
注意:在类外定义成员函数时,必须在函数名前加上 (类名::)
(3)类的访问限定符
访问限定符是一种设置类中成员的可见范围的关键字。在类和对象的初级阶段,我们只需要知道如下作用即可。
上图中所谓的直接被访问就是在类外通过 对象.成员 这样的方式来访问。
访问限定符的要点:
1.访问权限作用域从该访问限定符出现的位置开始一直到下一个访问限定符出现时为止,最后一个访问限定符的作用域一直到类体结束。
2.使用class定义的类,类中成员的默认访问权限是private。使用struct定义的类,类中成员的默认访问权限是public。
2.类的特性之封装
(1)封装的概念
封装:将数据和操作数据的方法有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
举例:人为什么要穿衣服?御寒,遮体。这是对人的一种保护。
也就是说,封装就是对程序的一种保护。
(2)封装的实现
如何实现封装,利用上文讲过的访问限定符来修饰类中的成员。
举例,代码五:本代码中将成员变量设置为private,成员函数设置为public。这样就只能通过成员函数来访问成员变量,而不能直接通过 (对象.成员变量) 这样的方式来访问。
//代码五
#include "iostream"
using namespace std;
class Date {
private:
int _year;
int _month;
int _day;
public:
void Init(int year, int month, int day) {
_year = year;
_month = month;
_day = day;
}
void Print() {
cout << _year << "-" << _month << "-" << _day;
}
};
int main() {
Date d1;
d1.Init(2012, 12, 12);
d1.Print();//输出 2012-12-12
}
3.初识对象
(1)对象的概念
上文中其实已经用到了对象。打个比方,类就是建筑设计图,对象则是根据设计图建好的房子。
(2)对象中成员的存储方式
类中的函数存放在代码段,函数名也即是函数的入口地址。
1.猜测一:对象中将所有成员存储到一起。
代码六:验证猜测一,按照猜测一,计算对象的大小。(其实和计算结构体大小相同,需要考虑内存对齐) 注意:函数名是函数的入口地址,相当于指针,在32位系统下,指针大小是4字节。
计算得出,猜测一的答案应该是 20字节。与实际不符合,猜测一错误。
//代码六
#include "iostream"
using namespace std;
class Date {
private:
int _year;
int _month;
int _day;
public:
void Init(int year, int month, int day) {
_year = year;
_month = month;
_day = day;
}
void Print() {
cout << _year << "-" << _month << "-" << _day;
}
};
int main() {
Date d1;
cout << "d1的大小:" << sizeof(d1);//输出 d1的大小:12
}
2.猜测二:在猜测一中发现对象实际大小比猜测的小。那么会不会是将成员函数全部存储到一个空间中,然后对象中只保存这个空间的地址。也就是说,对象中除了存储成员变量以外,只需要存储一个指针。通过代码七来验证。
下图是猜测的存储模型。
代码七:按照猜测二,对象大小应该是20,但实际只有16。猜测二依然不对。那么来看一下这段代码在函数调用时的汇编。
//代码七
#include "iostream"
using namespace std;
class MC {
private:
int _a;
char _c;
double _b;
public:
void Init(int a, double b, char c) {
_a = a;
_b = b;
_c = c;
}
void Print() {}
};
int main() {
MC m1;
MC m2;
m1.Init(1, 3.14, 'p');
m2.Init(0, 1.2, 't');
m1.Print();
m2.Print();
cout << "m1的大小:" << sizeof(m1);//输出 m1的大小:16
}
下图是函数调用时的汇编,我将其处理了一下,只保留关键信息。call指令代表着函数调用,画红线的是函数地址,可以看出来,两个对象调用的同名函数其实是同一个函数,因为相同函数名的函数的地址也相同,这不就是同一个函数吗。
那么来根据上述这些猜测我们可以发现,如果对象中只存储了成员变量,那么计算出来的大小和程序运行的完全一致,那么说明,对象中只存储了成员变量。
注意:空类和只有成员函数而没有成员变量的类大小均为1。
4.认识this
(1)引入this
在上述的代码七和它函数调用汇编中可以看到,不同的对象调用的其实是同一个函数,那么,编译器是怎么区分它们的呢?当m1调用初始化函数时,编译器怎么就知道它应该给m1初始化还是m2初始化?
你可以再看一下本文的代码一,有没有发现struct变量调用的函数都需要传递一个指针?C语言中的结构体就是通过这个指针来识别究竟是哪个结构体变量调用此函数的。
C++中其实也是有一个指针的,只不过并不需要我们自己在函数中写,而是编译器在编译时添加的,这个指针就是this指针。
可以用如下代码验证this指针的存在:
代码八:可以看到,通过this指针访问成员变量依然可以成功。但是,不要自己为函数的参数添加this指针,否则会报错。
//代码八
#include "iostream"
using namespace std;
class Date {
private:
int _year;
int _month;
int _day;
public:
void Init(int year, int month, int day) {
this->_year = year;
this->_month = month;
this->_day = day;
}
void Print() {
cout << this->_year << "-" << this->_month << "-" << this->_day;
}
};
int main() {
Date d1;
cout << "d1的大小:" << sizeof(d1);//输出 d1的大小:12
}
(2)this指针的特性
1.this指针的的类型是:类类型 * const。代码八中的this类型就是 Date * const。
2.this指针只能在“成员函数”内部使用。
3.this指针本质上其实是一个成员函数的形参,是对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针。
4.this指针是成员函数第一个隐含的形参,不需要用户手动传递。