C++:类和对象(上)
类的概念及定义
类的概念:
类(Class)是C++中面向对象编程的基础概念之一,它定义了一种数据类型,是一种用户自定义的数据结构。类可以看作是对象的蓝图或模板,描述了对象具有的属性(数据成员)和行成员函数)
如上图 外卖平台就是一个模板,而C语言更注重过程,考虑的是一个订单从出餐到送餐的过程,而C++除了注重过程还更加注重还关注整个外卖服务的各个方面。在C++中,你会把订单看作一个对象,这个对象不仅包含了订单的数据还包含了一系列与订单相关的行为。这些行为(或者说函数)与订单对象紧密相关,被封装在订单对象内部。
C++则更偏向于以对象为中心,通过类和对象来组织代码,并且强调数据和行为的封装与抽象化
类定义的格式:
#pragma once
#include<iostream>
#include<stdlib.h>
class Stack
{void STInit(int capaticy = 4){a = (int*)malloc(sizeof(int) * capaticy);_top = 0;_capaticy = capaticy;}void STPush(int x){a[_top++] = x;}void Destroy(){free(a);a = nullptr;}int* a;int _top;int _capaticy;
};
如上图这段代码是一个用C++编写的简单的栈(Stack)类的实现。可以看出和C语言中的栈的结构体类似但又有很大的区别。
在类里不仅可以定义成员变量,更可以定义成员函数(这点在C语言上是不允许的)。
可以看到通过使用类对栈进行初始化以及进行插入时的函数调用就会简洁很多,如果是C语言那么则还需要传地址会相对麻烦。
类的访问限定符:
在C++中,访问限定符用于控制类(或结构体)的成员变量和成员函数对外部代码的可见性和访问权限。主要的访问限定符包括public(公共)、protected(保护)和private(私人)。
class Stack
{
public:void STInit(int capaticy = 4);void STPush(int x);void Destroy(); private:int* a;int _top;int _capaticy;
};
上图小编将Stack类里的成员函数权限设定为public,将成员变量权限设置为私人。可以看出主函数中调用类里的成员函数是没有任何问题的,而一旦想要直接调用类里的成员变量则会发生编译错误,因为私人区域是不允许直接调用。
上图的将成员变量和成员函数捆绑在一起,并加以保护,是为了防止外部直接访问和修改对象的内部状态。封装有助于隐藏对象的内部细节,同时提供了一个清晰定义的接口,使得程序员无需关心对象内部的实现细节,只需要通过公共接口与对象进行交互即可,这种就是类的第一特性:封装。
封装的本质是⼀种更严格规范的管理,避免出现乱访问修改的导致类的属性被破坏的问题。
类域:
实体化
实体化概念:
class Data
{
public:void Init(int year,int month,int data){_year = year;_month = month;_data = data;}void Print(){cout << _year << "-" << _month << "-" << _data << endl;}private://变量只是声明并没有实际的开辟空间int _year;int _month;int _data;
};int main()
{//实体化出对象d1与d2Data d1, d2;d1.Init(2022, 11, 11);d2.Init(2022, 12, 12);d1.Print();d2.Print();}
从上图可以看到,如果直接给Data类里的_data直接进行赋值会报错,而实体化出的对象d1则不会。这就类似于建房子,你不可能在图纸样板里面建房子,只能在实际的地面上才能。
关于实体化里的对象大小:
在了解实体化里对象大小的概念前需要了解结构体的内存对齐以及内存对齐的规则,如果有不清楚的可以看看小编之前写的一篇文章C语言结构体知识-CSDN博客。
从上图可以看出使用sizeof计算出Data类与对象d1的大小结果都是12,那上面又说成员变量只是声明没有分配空间。为什么sizeof还能计算出类的大小呢?其实要想明白也很简单,就好房子的图纸上都有标注空间长度大小,而d1又类似于实际去房子里丈量,两种方法都可以得知房子的尺寸。
而通过sizeof发现,类的大小为12,刚好三个成员变量int的值。那么成员函数难道没有参与计算吗?
此时将代码进行调试并转到反汇编指令时发现,d1.Init()与 d2.Init()都call向同一块空间,那么也就说明d1的成员函数与d2的成员函数是共用的,所以类成员函数的是存放在一个公共的代码区域段的地方,那么这样做也是为了节约空间。
就好比对象1与对象2相约一起打篮球,而打篮球需要篮球场。篮球场可以建在对象1的家里同样也可以建在对象2的家里但这样做都会很浪费空间。那么将篮球场建立在一个公共的区域,既方便调用又节约了对象1与对象2自身的空间。
通过上图运行代码,我们可以看到A1类既没成员函数也没成员变量,A2类只有一个成员函数。A1类的对象a与A2类的对象b通过sizeof计算出它们的大小都为1,这是为了确保每个对象在内存中占有独立的空间,即使是空类也需要有独特的地址。因为如果⼀个字节都不给,怎么表⽰对象存在过呢!
this指针:
通过上图可以看到将Data类的d1与d2对象进行了初始化,并且输出他们的成员变量。但根据上面的知识我们可以得知,类成员函数是存在于公共代码段里的,而Print函数又没有参数但又却能准确的显示d1与d2对象中的参数,那编译器是怎么知道是调用d1还是d2呢?
通过观察反汇编指令可以发现,编译器在调用Print函数之前会先将d1的地址与d2的地址压入ecx寄存器中然后再调用函数,但我们自己并没有去手动的将d1与d2的地址进行传参,那么这里就涉及到C++中的this指针。
编译器编译后,类的成员函数默认都会在形参第⼀个位置,增加⼀个当前类类型的指针,叫做this指针。⽐如Date类的Print的真实原型为void Print( Data* this),而类的成员函数中访问成员变量,本质都是通过this指针访问的也就是 cout << this-> _year << "-" << this->_month << "-" << this->_data << endl;
C++规定不能在实参和形参的位置显示的写this指针(编译时编译器会处理),但是可以在函数体内显示使用this指针。