【C++】什么是类与对象?

🦄个人主页 :修修修也

🎏所属专栏 :C++

⚙️操作环境 :Visual Studio 2022


面向对象概述

面向对象是一种符合人类思维习惯编程思想 。现实生活中存在各种形态不同的事物,这些事物之间存在着各种各样的联系。在程序中使用对象来映射现实中的事物,使用对象的关系来描述事物之间的联系,这种思想就是面向对象。

对于学习过其他编程语言的人员来说,可能会想到面向过程。面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一一实现,使用的时候依次调用就可以了。面向对象则是把构成问题的事物按照一定规则划分为多个独立的对象,然后通过调用对象的方法来解决问题。当然,一个应用程序会包含多个对象,通过多个对象的相互配合即可实现应用程序所需的功能,这样当应用程序功能发生变动时,只需要修改个别的对象就可以了,从而使代码更容易维护。

面向对象的特点 可以概括为封装继承多态 ,接下来针对这三种特性进行简单介绍。

封装

封装是面向对象的核心思想,将对象的属性和行为封装起来,不需要让外界知道具体实现细节,这就是封装思想。例如,用户使用计算机,只需要使用手指敲键盘就可以了,无须知道计算机内部是如何工作的,即使用户可能碰巧知道计算机的工作原理,但在使用时,并不完全依赖计算机工作原理这些细节。

继承

继承主要描述的就是类与类之间的关系,通过继承,可以在无须重新编写原有类的情况下,对原有类的功能进行扩展。例如,有一个汽车的类,该类中描述了汽车的普通属性和功能。而轿车的类中不仅应该包含汽车的属性和功能,还应该增加轿车特有的属性和功能,这时,可以让轿车类继承汽车类,在轿车类中单独添加轿车特有的属性和功能就可以了。继承不仅增强了代码的复用性,提高了开发效率,还为程序的维护补充提供了便利。

多态

多态指的是在一个类中定义的属性和功能被其他类继承后,当把子类对象直接赋值给父类引用变量时,相同引用类型的变量调用同一个方法所呈现出的多种不同行为特性。例如,当听到cu这个单词时,理发师的行为表现是剪发,演员的行为表现是停止表演等。不同的对象,所表现的行为是不一样的。


类是什么?

  • 类是面向对象语言的程序设计中的概念,是面向对象编程的基础。
  • 类的实质是一种引用数据类型,类似于 byte、short、int(char)、long、float、double 等基本数据类型,不同的是它是一种复杂的数据类型 。因为它的本质是数据类型 ,而不是数据 ,所以不存在于内存中不能被直接操作只有被实例化为对象时才会变得可操作
  • 类是对现实生活中一类具有共同特征的事物的抽象。如果一个程序里提供的数据类型与应用中的概念有直接的对应,这个程序就会更容易理解,也更容易修改。一组经过很好选择的用户定义的类会使程序更简洁。此外,它还能使各种形式的代码分析更容易进行。特别地,它还会使编译器有可能检查对象的非法使用。
  • 类的内部封装了属性和方法,用于操作自身的成员 。类是对某种对象的定义,具有行为(behavior),它描述一个对象能够做什么以及做的方法 (method),它们是可以对这个对象进行操作的程序和过程。它包含有关对象行为方式的信息 ,包括它的名称属性、方法事件
  • 类的构成包括成员属性成员方法数据成员成员函数)。数据成员对应类的属性,类的数据成员也是一种数据类型,并不需要分配内存。成员函数则用于操作类的各项属性,是一个类具有的特有的操作,比如"学生"可以"上课",而"水果"则不能。类和外界发生交互的操作称为接口。

C++中类的引入

C语言结构体中只能定义变量,比如:之前在数据结构中,我们用C语言方式实现的栈,结构体中只能定义成员变量:

【数据结构】C语言实现顺序栈(附完整运行代码)

cpp 复制代码
//C语言中定义顺序栈的结构体

struct Stack
{
    int *arr;
    int top;
    int capacity;
};

并且,在C语言中,我们创建的结构体变量,只能访问自己的成员变量:


而在C++中,结构体内不仅可以定义成员变量,也可以定义函数。现在我们以C++方式实现栈,可以发现struct中也可以定义函数:

cpp 复制代码
struct Stack
{
    void Init(size_t capacity)
    {
        _arr = (int*)malloc(sizeof(int) * capacity);
        if (nullptr == _arr)
        {
            perror("malloc申请空间失败");
            return;
        }
        _capacity = capacity;
        top = 0;
    }

    //成员变量的位置可以是类内的任意地方
    int *arr;
    int top;
    int capacity;
};

并且,在C++中,我们创建的结构体变量,是可以访问结构体成员函数的:


C++中类的定义

虽然C++因为兼容C语言的缘故,将C语言中的struct升级为了类,但实际应用中,C++更喜欢使用class关键字来声明类。class 声明类时格式如下:

cpp 复制代码
class class_name
{
    //类体:由成员函数和成员变量组成
};  

class 为定义类的关键字 ,class_name类的名字 ,{} 中为类的主体 ,注意类定义结束后最后的分号不能省略

类中的内容称为类的成员:类中的变量 称为类的属性成员变量 ,类中的函数 称为类的方法 或者成员函数.

类的两种定义方式:

1.声明和定义全部放在类体中

需要注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理。

cpp 复制代码
class Stack
{
public:

    void Init(size_t capacity)
    {
        _arr = (int*)malloc(sizeof(int) * capacity);
        if (nullptr == _arr)
        {
            perror("malloc申请空间失败");
            return;
        }
        _capacity = capacity;
        _top = 0;
    }

public:

    //成员变量的位置可以是类内的任意地方
    int* _arr;
    int _top;
    int _capacity;
};

注意:为了防止在类中定义函数时像下面这样容易使成员变量函数参数混淆:

一般当声明和定义全部放在类体中时我们会将**类中的成员变量名前面加_**以示区分:

2.类声明与成员函数定义分别放在不同的工程文件中:

类声明放在.h文件中:

成员函数定义放在.cpp文件中,注意:成员函数名 前需要加类名::


类的访问限定符

C++实现封装的方式:用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用。

访问限定符的特性:

  • public 修饰的成员在类外可以直接被访问
  • protectedprivate修饰的成员在类外不能直接被访问 (此处protectedprivate是类似的)。
  • 访问权限作用域****从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
  • 如果后面没有访问限定符,作用域就到 } 即类结束。
  • class默认访问权限为privatestruct为public(因为struct要兼容C)。

注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别
C++中struct和class的区别是什么?

C++需要兼容C语言,所以C++中struct可以当成结构体使用。另外C++中struct还可以用来定义类。和class定义类是一样的,区别是struct定义的类默认访问权限是public,class定义的类默认访问权限是private。在继承和模板参数列表位置,struct和class也有部分区别。


类的封装

我们在文章一开始就介绍了面对对象的三大特性 :封装,继承,多态
封装是面向对象的核心思想 ,将对象的属性和行为封装起来,不需要让外界知道具体实现细节,这就是封装思想。

封装本质上是一种管理让用户更方便使用类 。比如:对于电脑这样一个复杂的设备,提供给用户的就只有开关机键、通过键盘输入,显示器,USB插孔等,让用户和计算机进行交互,完成日常事务。但实际上电脑真正工作的却是CPU、显卡、内存等一些硬件元件。对于计算机使用者而言,不用关心内部核心部件,比如主板上线路是如何布局的,CPU内部是如何设计的等,用户只需要知道,怎么开机、怎么通过键盘和鼠标与计算机进行交互即可。因此计算机厂商在出厂时,在外部套上壳子,将内部实现细节隐藏起来,仅仅对外提供开关机、鼠标以及键盘插孔等,让用户可以与计算机进行交互即可。

在C++语言中实现封装,可以通过类将数据以及操作数据的方法进行有机结合,通过访问权限来隐藏对象内部实现细节控制哪些方法可以在类外部直接被使用
在C语言中,由于对结构体没有封装的概念,从而会导致使用者通过变量对结构体中的成员变量进行任意不合规的操作。

如下,在栈程序中,使用者就没有通过取栈顶元素函数获取栈顶元素,而通过直接访问成员变量的方式访问了栈顶元素:

而在C++中,我们可以通过访问权限来隐藏对象内部实现细节控制哪些方法可以在类外部直接被使用 ,而哪些方法在类外部不可以被使用。


类的作用域

类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员(包括变量和函数)时,需要使用 :: 作用域操作符指明成员属于哪个类域。

如,上文中在C++中类的定义方式中我们提到的:
有关类的作用域,有以下几点需要注意:

  • 局部域,全局域,命名空间域,类域,不同的域中可以定义同名变量。
  • 类中的变量查找优先级:局部域>类域>全局域/命名空间域(非指定展开则不查找)。
  • 局部域和全局域会影响生命周期,类域和命名空间域不会影响生命周期。

类的实例化

用类类型创建对象的过程,称为类的实例化

  1. 类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类但并没有分配实际的内存空间来存储它;比如:入学时发放的学生信息表,表格就可以看成是一个类,来描述具体学生信息。但在还没有学生填表时,这个表格就并没有实际存储学生的任何信息。
  2. 一个类可以实例化出多个对象,实例化出的对象 占用实际的物理空间,存储类成员变量。 打个比方来说,上面这个例子中的Date就类似于一个房屋建造的图纸,它规定了这个房屋建造多高,多宽,多长,以及各个地方的设计细节。但你如果不真正的将房子按照图纸建造出来,你就永远无法将家具之类的东西搬进房子里住,上图中使用Date类直接存储_year的数据就类似于要将现实世界的床搬进房屋设计图纸里住人。

类对象模型

如何计算类对象的大小

在C语言中,我们学习过如何使用sizeof操作符计算结构体的大小 ,以及如何通过结构体对齐规则去计算结构体的大小(还不太了解的朋友可以移步这篇博客):

【C语言】结构体的大小是如何计算的?(结构体对齐)https://blog.csdn.net/weixin_72357342/article/details/131135555?spm=1001.2014.3001.5502

在C++中,对类的大小计算 我们同样是使用sizeof操作符,如下例子,我们使用sizeof操作符分别计算Date类和Date类对象Birthday的大小:

cpp 复制代码
class Date
{
public:
    void InitDate(int year, int mouth, int day)
    {
        _year = year;
        _mouth = mouth;
        _day = day;
    }
public:
    int _year;
    int _mouth;
    int _day;
};

int main() {
    Date Birthday;
    Birthday._year = 2000;

    int sz1 = sizeof(Date);
    int sz2 = sizeof(Birthday);

    printf("%d %d\n", sz1, sz2);
    return 0;
}

运行查看结果,可以看到,类的大小和类对象的大小是一致的 ,这和结构体与结构体变量的大小计算结果一致:


类对象存储方式

在知道了如何使用sizeof操作符计算类的大小后,我们还需要探究一下类对象究竟在内存中是怎样存储的。

在C语言中,结构体的大小是由成员变量构成的,这我们很好理解,但在C++中,类的大小计算起来却看起来复杂的多,因为类中不仅包含了成员变量,还包含了成员函数,它们是如何存储在内存中的?它们和成员变量的关系又是怎样的?下面我们一起来探究一下:
` 如下,我们分别构建了一个类对象Date1,以及一个结构体对象Date2,分别计算他们的大小以及由它们创建的变量Birthday1,Birthday2的大小:

cpp 复制代码
class Date1{
public:
    void InitDate(int year, int mouth, int day){
        _year = year;
        _mouth = mouth;
        _day = day;
    }
public:
    int _year;
    int _mouth;
    int _day;
};

struct Date2{
    int year;
    int mouth;
    int day;
};

int main() {
    Date1 Birthday1;
    Birthday1._year = 2000;

    int sz1 = sizeof(Date1);
    int sz2 = sizeof(Birthday1);
    printf("%d %d\n", sz1, sz2);

    Date2 Birthday2;
    Birthday2.year = 2000;

    int sz3 = sizeof(Date2);
    int sz4 = sizeof(Birthday2);
    printf("%d %d\n", sz3, sz4);
    return 0;
}

运行程序,我们可以发现,它们的大小是一样大的:
也就是说,类的大小是不包含成员函数的大小 的,它的大小只取决于类中的成员变量 .同样的,类对象的大小也仅取决于成员变量的大小.

这样的存储策略其实并不难理解,打个比方说,如果我们将类比作一个小区,那么类对象中的成员变量就类似于一间一间的居民楼,供用户存放个人的物品使用,而类中的成员函数则类似于小区中的凉亭或健身器材区域,它是可以提供给所有小区中的人使用的,并且所有人使用它都是一样的,因此完全不需要给每家都修一个,而是可以整个小区只修建一个,大家一起使用就可以了。
因此,我们可以推断出,类成员的存储方式应该类似于下图这种结构:
下面我们设计几个类来验证一下我们的推断:

cpp 复制代码
//类中既有成员变量也有成员函数
class Date1 {
public:
    void InitDate(int year, int mouth, int day) {
        _year = year;
    }
public:
    int _year;
    int _mouth;
    int _day;
};

//类中只有成员函数
class Date2 {
public:
    void InitDate(int year, int mouth, int day) {
        year=2000;
    }
};

//类中只有成员变量
class Date3 {
public:
    int _year;
    int _mouth;
    int _day;
};

//类中什么都没有
class Date4 {

};

int main() {
    int sz1 = sizeof(Date1);
    int sz2 = sizeof(Date2);
    int sz3 = sizeof(Date3);
    int sz4 = sizeof(Date4);
    printf("%d %d %d %d\n", sz1, sz2,sz3,sz4);
    return 0;
}

运行程序,得到结果:

可以看到,类中既有成员函数也有成员变量和类中只有成员变量的大小是一样的 ,而类中只有成员函数和类中什么都没有的大小是一样的.

需要注意的是,在没有成员变量的情况下,类的大小是1,它需要1字节的空间占位,表示对象存在,但不存储任何有效数据。
需要注意的是,类中的成员变量的计算规则和C语言中的结构体一样 ,都遵循结构体对齐规则,所以下面这种情况中,Date1类的大小是8而不是5:

对于结构体对齐规则的详解,感兴趣的朋友可以移步下面这篇博客:

【C语言】结构体的大小是如何计算的?(结构体对齐)https://blog.csdn.net/weixin_72357342/article/details/131135555?spm=1001.2014.3001.5502


结语

希望这篇关于类与对象的基础博客能对大家有所帮助,欢迎大佬们留言或私信与我交流.

学海漫浩浩,我亦苦作舟!关注我,大家一起学习,一起进步!

相关文章推荐

【C++】缺省参数(默认参数)

【C++】"Hello World!"
【C++】命名空间



慎在此祝愿全世界伟大的劳动妇女们,节日快乐!

广阔天地,大有作为!

相关推荐
I_Am_Me_1 小时前
【JavaEE初阶】线程安全问题
开发语言·python
运维&陈同学1 小时前
【Elasticsearch05】企业级日志分析系统ELK之集群工作原理
运维·开发语言·后端·python·elasticsearch·自动化·jenkins·哈希算法
金士顿3 小时前
MFC 文档模板 每个文档模板需要实例化吧
c++·mfc
ZVAyIVqt0UFji4 小时前
go-zero负载均衡实现原理
运维·开发语言·后端·golang·负载均衡
loop lee4 小时前
Nginx - 负载均衡及其配置(Balance)
java·开发语言·github
SomeB1oody4 小时前
【Rust自学】4.1. 所有权:栈内存 vs. 堆内存
开发语言·后端·rust
toto4124 小时前
线程安全与线程不安全
java·开发语言·安全
PP东4 小时前
ES6学习Generator 函数(生成器)(八)
javascript·学习·es6
水木流年追梦5 小时前
【python因果库实战10】为何需要因果分析
开发语言·python
人才程序员6 小时前
QML z轴(z-order)前后层级
c语言·前端·c++·qt·软件工程·用户界面·界面