目录
一、前言
二、struct在C++中的变化
三、类的定义
四、类的访问限定符
五、封装
六、类的实例化
七、类对象模型
7.1 如何计算类对象的大小
7.2 类对象的存储方式
八、this指针
8.1 this指针的用途
8.2 this指针的特性
一、前言
C语言是一种面向过程的语言,关注于求解问题的过程,通过函数调用逐步解决问题。
C++在C语言的基础上增加了面向对象编程,将一件事情拆分成不同的对象,关注对象之间的交互
C++认为万事万物皆为对象,都有其自己的属性和行为,例如:
人可以作为对象,属性有姓名、年龄、身高......行为有走、跑、跳、说话......
车也可以作为对象,属性有轮胎、方向盘、车灯......行为有载人、放音乐......
具有相同性质的对象,可以抽象为类,不同的人可以属于人类,车属于车类
类是C++的核心特性,通常被称为用户定义的类型,是一种封装了数据和函数的组合。
类中的数据称为成员变量, 函数称为成员函数。
二、struct在C++中的变化
在C语言中,在struct定义的结构体中只能定义变量。而在C++中结构体中不仅可以定义变量,还可以定义函数。
因为C++中struct也可以用来定义类。
例如我们要实现一个栈,用C语言实现的话各种接口函数只能在结构体外定义,而用C++实现我们会发现struct中也可以定义函数,例如:
struct Stack
{
void Init(int capacity = 4)
{
_array = (int*)malloc(sizeof(int) * capacity);
if (nullptr == _array)
{
perror("malloc fail");
return;
}
_capacity = capacity;
_size = 0;
}
void Push(int x)
{
//...
_array[_size] = x;
++_size;
}
//...
int* _array;
int _capacity;
int _size;
};
int main()
{
Stack s;
s.Init();
s.Push(1);
s.Push(2);
s.Push(3);
return 0;
}
另外,在C语言中,结构体的类型名是包含struct的,但是在C++中类型名不包含struct,例如:
三、类的定义
虽然C++中可以用struct定义类,但是我们通常喜欢用class来代替它
class ClassName
{
// 类体:由成员函数和成员变量组成
};
class是定义类的关键字,ClassName是类名,大括号中为类的主体,注意类后的分号不能省略。
类体中的内容为类的成员,其中的变量称为类的属性或成员变量,函数称为类的方法或成员函数。
类有两种定义方式:
(1)成员函数的声明和定义全部放在类体中,如上面栈的定义
(2)成员函数的声明和定义分离,在头文件中定义类,函数的声明放在类体中,在源文件中定义接口函数,如:
需要注意的是,当我们定义了一个类的时候,就是创建了一个类作用域。使用第二种方法定义类时要在成员函数名的前面加上类名和作用域限定符。
另外,成员变量在命名时,建议在前面加上一个下横线_,这样可以方便与形参进行区分
例如:
class Date
{
void Init(int year)
{
// 这里的year到底是成员变量,还是函数形参?
year = year;
}
int year;
};
如果前面加上下横线区分,就能避免这种尴尬的情况。当然你也可以选择其他方式进行区分
不过,在上面第二种类的定义方式中,我们去使用函数的时候会出现以下情况:
这里与C++的访问限定符有关
四、类的访问限定符
C++中有三种访问限定符:public(公有)、protected(保护)和private(私有)
其中,因为C++要兼容C语言,所以struct中的成员默认公有,而class中的成员默认私有,所以才会导致上面那种情况
关于访问限定符的说明:
- public的成员在类外可以直接被访问,protected和private修饰的成员则不能
- 访问限定符的作用域从该限定符的位置开始直到下一个限定符出现结束,如果后面没有访问限定符,就遇到大括号结束
当我们将成员函数用public修饰后,就能在类外访问它们了
五、封装
C++面向对象的三大特性:封装、继承、多态
在学习类和对象的阶段,我们主要了解类的封装特性
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互
例如一台电脑,提供给用户进行交互的只有各种按钮和插口,而实际工作的是内部的各种硬件。对于用户而言,不需要知道内部的结构如何,只需要知道怎么去使用即可。
在C++中,使用类将数据和方法结合,通过调整访问权限来隐藏对象的内部细节,开放成员函数的权限以供使用,就是一种封装。
六、类的实例化
用类创建对象的过程,称为类的实例化。
类就像一张图纸,限定了类有哪些成员,但是并没有分配实际的内存空间来存储它。我们按照图纸建房子,也就是实例化出具体的对象。
之前我们定义的Stack类是没有空间的,只有Stack类实例化出的对象才占有具体的内存空间
七、类对象模型
7.1 如何计算类对象的大小
请问,Stack类实例化出的对象应该是多大呢?
答案是12,据此我们可以推算出,类的大小也是遵守内存对齐规则的,并且只计算成员变量,不计算成员函数
7.2 类对象的存储方式
为什么计算类对象的大小时不包含成员函数呢?
同一类的每个对象中成员变量的值是不一样的,所以需要独立存储。
但是每个对象调用的成员函数是一样的,如果把成员函数存储在对象中,占用对象的内存空间,就导致用一个类实例化多个对象时,每个对象都会保存一份代码,浪费内存空间。
所以我们将成员函数存放在公共的代码段中
问题又来了,下面这两个类的大小是多少呢?
class c1
{
public:
void func()
{
;
}
};
class c2
{
};
答案是 1
虽然一个类中只有成员函数,一个类中什么也没有,但是还是会分配1byte的内存空间用来占位,标识对象已被实例化出来了。
这1byte空间不会存储有效数据。
八、this指针
现在我们知道,在C++中成员变量和成员函数是分开存储的,那么当我们调用成员函数的时候,函数是怎么区分是哪个对象调用自己的呢?
C++中提供this指针来解决该问题,this指针指向调用函数的对象,并隐式传递给函数。
我们不能手动显式地在参数列表中添加this指针,编译器自己会完成
8.1 this指针的用途
(1)当形参与成员变量同名时,可用this指针区分
(2)在类的非静态成员函数中返回对象本身
8.2 this指针的特性
(1)this指针本质是一个指针常量:const Type* const pointer,所以在成员函数中不能给this指针赋值
(2)this指针只能在非静态成员函数中使用,在全局函数、静态成员函数中都不能用
(3)this指针本质上是成员函数的形参,当对象调用成员函数时,编译器将对象地址作为实参传递给this形参,所以this指针不存储在对象中
(4)this指针会因编译器不同而存储在不同的位置,通常存在栈中,vs上存储在ecx寄存器中
this指针可以为空吗?
提问:下面这种情况,程序会报错、崩溃还是正常运行呢?
答案是:正常运行
下面这种情况呢?
答案是:崩溃
为什么第一个情况能正常运行,而第二个情况就会崩溃呢?
因为出现箭头并不一定会解引用,Func不在对象内部,也没有对空指针d1进行解引用,所以可以正常运行;而Init中进行了解引用,访问了空指针,所以崩溃。
是否解引用,取决于右边要访问的东西在不在对象内部,而不是根据符号判断。
完.