写点什么

C++ 来时路 _ 重温经典之 C++ 类和对象 | 三大特性之一 - 封装 | 腾讯面试题

作者:Dream-Y.ocean
  • 2022 年 9 月 27 日
    广东
  • 本文字数:4111 字

    阅读完需:约 13 分钟

C++来时路 _ 重温经典之C++类和对象 | 三大特性之一 - 封装 | 腾讯面试题

💛 前情提要💛


本章节是C++类和对象- 封装的相关知识~


接下来我们即将进入一个全新的空间,对代码有一个全新的视角~


以下的内容一定会让你对C++有一个颠覆性的认识哦!!!


以下内容干货满满,跟上步伐吧~


💡本章重点

  • 了解C++类的定义

  • 认识C++类和对象

  • 了解C++面向对象三大特性之一:封装



🍞一.类和对象

💡类和对象:


  • C语言中,是面向过程的,关注的是过程,即为了解决一个问题,一步步分析出解题的步骤,通过函数的调用逐步解决

  • 而在C++中,是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,通过实现对象之间的交互操作、交互关系从而解决问题


补充:


  • 在 C++中我们一般称变量为对象


👆有了以上的基础了解,我们深入分析究竟是什么~



🍞二.类

💡类的引入:


  • 在 C 语言中其实我们已经了解过的概念,本质就是 C 语言中的结构体

  • 而在 C++中,因为面向对象的概念引入,所以创建了一种新的类型:

  • 本质: 类可以看作不同对象的集合【Eg:不同的人,有着不同的身高、体重……但可以定义一个person类去表述出不同的人】


➡️类的构成:


  • 1️⃣类的属性:即类的成员变量

  • 2️⃣类的方法(行为):即类的成员函数


👆简单来说:


  • C 语言中,结构体中只能定义变量

  • 在 C++中,结构体内不仅可以定义变量,也可以定义函数



🥐Ⅰ.类的定义

💡类的定义:


class className{    //类的主体};
复制代码


👆由上我们可得知三点:


  • class:为定义类的关键字

  • className:为类的名字

  • 类的主体:可由成员变量成员函数组成


👉代码示例:


typedef int STDataType;
class Stack{ //成员函数: void Init(int inintSize = 4); { a = (STDataType*)malloc(sizeof(STDataType) * inintSize); size = 0; capacity = 4; }
void Push(STDataType x) { //增容 //... a[size] = x; size++; }
//成员变量:【即 这个 类 的一些属性、特征、数据】 STDataType* a; int size; int capacity;};
复制代码


特别注意:


  • 成员变量在类里面是声明,而非定义

  • 成员变量只有在类实例化(创建)一个对象的时候才被定义

  • 如果是声明+定义的话,相当于多个这个类的对象共用类里的成员变量,所以这是不对的

  • C++虽然兼容 C 语言的结构体struct,但更喜欢用class定义类【还有一些属性上的不同,后面会继续介绍】



🥐 Ⅱ.封装

💡面向对象的三大特性:


  • 封装

  • 继承

  • 多态


【实际中不止三种特性,还有:抽象、反射,但与上述三种相比较之下,上述的三种特性更为重要】


➡️什么是封装:


  • 将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互


👆简单来说:


  • 封装本质是一种管理方法:即把类中的数据(成员变量)和方法(成员函数)管理起来,将给用户访问的定义成公有,不想让人访问的定义成私有or保护

  • 这样可以在面向对象的过程中更加规范、严格且安全

  • 即不是不给访问,而是在访问的基础上加上保护措施,进行合理的访问以确保这次的访问不会一次就直接破坏了这个类,而是提供持续性地保护


👉接下来我们就深入探讨是是如何进行封装的~



🥐 Ⅲ.类的访问限定符

➡️C++实现封装的方式:


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


💡类的访问限定符:


  • public(公有):所修饰的成员(变量 or 函数)在类外面可以直接被访问

  • protect(保护)、 private(私有):所修饰的成员(变量 or 函数)在类外面不能直接被访问


特别注意:


  • 访问权限作用域:从该访问限定符出现的位置开始,直到下一个访问限定符出现时为止

  • C++为了兼容 C 语言的 struct

  • 于是struct的默认访问权限为public【这也就是为什么结构体的成员可以直接被访问】

  • class的默认访问权限为private【这也就是为什么在 C++中定义类用class关键字,因为这样可以默认保护成员变量】

  • 类的访问限定符,限定的时类外面的程序对类成员的直接修改、访问,但不限制类里面的访问和修改【即家里上锁的目的是:防外人,但家里人可以随意进出】


👆上述所提到的就很好的回答了一道经典的面试题:“C++中 struct 和 class 的区别是什么?”


综上: 一般情况下,在类的设计中将成员函数设计成public【可以在类外直接访问得到】,将成员变量设计成protect private【不想让类外面的访问得到】



🥐 Ⅳ.类的作用域

💡作用域:


  • 类域跟命名空间类似,即类定义了一个新的作用域,所有类的成员都在类的作用域中

  • 若类中的成员函数的声明和定义是分离的话,此时成员函数的定义在类外就需要用::(域作用限定符)去指定表明这个成员函数的定义是哪个类域中的


👉代码示例:


typedef int STDataType;
class Stack{public: //成员函数: void Init(int inintSize = 4); { a = (STDataType*)malloc(sizeof(STDataType) * inintSize); size = 0; capacity = 4; } //成员函数的声明: void Push(STDataType x); private: //成员变量:【即 这个 类 的一些属性、特征、数据】 STDataType* a; int size; int capacity;};
//成员函数的定义:void Stack::Push(STDataType x);{ //增容 //... a[size] = x; size++;}
复制代码



🥯Ⅴ.总结

综上: 就是类的相关知识点的了解啦~



🍞三.类对象

💡类的实例化:


  • 简单来说就是用类创建对象的过程,成为类的实例化

  • 因为类只是相当于一个设计图,只是限定了有哪些成员,但并没有实际创建出来,所以要通过实例化来建造一个对象从而分配实际的内存空间来存储成员


➡️一个类可以实例化多个对象~



🥐Ⅰ.类对象的大小

💡类对象的大小:


  • 类对象的大小的计算是遵循内存对齐的规则【相关知识可>点击<跳转食用】

  • 但现在有一个问题:类里包含了成员函数,成员函数的大小是否包含在类对象的大小中呢?


👉我们便可以通过如下代码进行测试:



  • 由此可见,计算出来的大小为4,而这恰好仅仅是只有一个int类型在类里经过内存对齐计算出来的大小



  • 从这里可以看得出:空类和仅有成员函数的类的空间大小是一样的,只有1字节


综上我们可知:


  • 一个类的大小只计算成员变量的大小(遵循内存对齐的规则),不计算成员函数的大小在内

  • 对于"空类"的空间大小,只有1字节


特别注意:


  • 其中,成员函数真正存储的地方,并不是在类里面,而是在公共代码区

  • 这样的好处是当多个对象调用成员函数时,并不会产生多份重复的成员函数从而占用空间、浪费空间


❓这里不免会产生疑惑:"空类"为什么不给0字节呢


  • 这是因为如果对象的空间大小为0字节的话,是无法区分都为空类时对象的地址【因为一个字节对应一个地址】,它们此时地址就会重合

  • 所以,为了可以区分是不同对象,于是给1字节表示占位【即表示对象存在】



🥯Ⅱ.总结

综上: 分析下来,相信大家对类对象有了更深一步的了解啦~



🍞四.this 指针

💡this 指针:


  • C++中为何会有this指针的概念呢?

  • this指针在 C++中是如何使用的?


如上问题,我将通过下述代码示例,带领大家深入了解this指针


👉代码示例:


class Date{public:        void Print()    {        cout << _year << "-" << _month << "-" << _day << endl;    }
void SetDate(int year, int month, int day) { _year = year; _month = month; _day = day; }
private: int _year; // 年 int _month; // 月 int _day; // 日};
复制代码



👆由上通过Date类(日期类)创建了了两个对象:d1d2,但当对象都调用成员函数的时候,函数体内并没关于不同对象的区分,那成员函数是如何知道该设置哪个对象呢?


  • 为了解决这个问题,C++便引入了this指针

  • C++编译器给每个 “非静态的成员函数” 增加了一个 隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有成员变量的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成


➡️简单来说:


  • 就是在对象调用成员函数时,为了指明成员函数修改的是哪个对象的成员变量,于是 C++在对象调用成员函数的时候,增加了一个隐藏的参数(this指针),从而让成员函数可以区分是哪个对象调用的

  • 其中,this指针是在调用成员函数的时候编译器隐藏自动帮我们传递的,这也就是为什么我们会产生上述问题的原因,因为我们看不见这个this指针


👉示例:



  • 本质:this指针存储的就是对象的地址【传递this指针,也就是传递对象的地址,这也就是为什么成员函数可以区分开来是哪个对象调用的,从而访问哪个对象中的成员变量】


❗特别注意:


  • this指针是隐含的,是编译器编译时加的,所以我们在调用成员函数传参的时候时=不能显示的加上this指针

  • 但我们可以显示地在成员函数 中使用this指针(如上示例)

  • this指针一般时储存在栈上的【不过不同编译器不同,在VS中时使用eax寄存器存储、传参的】



🥐Ⅰ.腾讯面试题

💡以上的知识便被腾讯运用在一道面试题目中:


  • this指针可以为空吗?

  • 下面程序能编译通过吗?

  • 程序会崩溃吗?在哪里崩溃?


class A{public:    void PrintA()    {        cout << _a << endl;    }
void Show() { cout << "Show()" << endl; }private: int _a;};
int main(){ A* p = nullptr; p->PrintA(); p->Show(); return 0;}
复制代码


🙌我们来看一下答案吧:



➡️我们来解析一下吧:



  • 1️⃣一个类型为A*的指针 p 被赋值为空指针,此时 p 会被看作为一个对象的地址【本身是没有这个对象的,但因为类型为A*,所以被看作有访问类里的成员的权力】

  • 2️⃣在调用PrintA成员函数时,隐藏传递的对象的地址在这里传递的其实就是p(空指针)本身,所以this指针接收的也就是空指针,但因为PrintA内有涉及访问访问成员变量(即对this指针进行解引用:nullptr->_a),对空指针非法解引用了,所以会引发空指针访问的崩溃

  • 3️⃣而在调用Show成员函数时,依旧传递的是空指针,但Show函数内并没有对 p 这个指针进行解引用,所以这里的程序是正常执行,不会引发崩溃的



🥯Ⅱ.总结

综上: 关于this指针的细节还是很多的哟,但全部理解下来后看回去还是很简单的呢!



🫓总结

综上,我们基本了解了 C++中的“类和对象 - 封装”的知识啦~


恭喜你的内功又双叒叕得到了提高!!!


感谢你们的阅读


后续还会继续更新,欢迎持续关注哟~


发布于: 刚刚阅读数: 3
用户头像

Dream-Y.ocean

关注

还未添加个人签名 2022.06.17 加入

还未添加个人简介

评论

发布
暂无评论
C++来时路 _ 重温经典之C++类和对象 | 三大特性之一 - 封装 | 腾讯面试题_c++_Dream-Y.ocean_InfoQ写作社区