C++类和对象——类的基础

目录

  • 类的引入
  • 类的定义
  • 类的访问限定符和封装
  • 对象的实例化
  • 类对象的大小
  • this指针

类的引入

在C语言中,结构体中只能定义变量
但是在C++中,结构体不仅可以定义变量,还可以定义函数

下面就是C++中的一个结构体:

struct Stack
{void init(int capacity = 4){_a = (int*)malloc(sizeof(int) * capacity);if (_a == nullptr){perror("malloc fail");return;}_capacity = capacity;_top = 0;}void destroy(){free(_a);_top = _capacity = 0;_a = nullptr;}int* _a;int _top;int _capacity;
};

可以看到在C++中的结构体中,可以有变量,也可以有函数
C++兼容C语言,结构体以前的用法在C++里仍让可以使用,只是C++中struct实际上是升级成了类。

在C++中,更喜欢使用class替代struct


类的定义

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

类的定义有2中方式:

值得注意的是:成员变量本身只能在类中声明且无定义,成员函数的声明必须在类中,其定义可以在类外也可以在类内

  1. 成员的声明和定义都在类体中,这时类中定义成员函数,编译可能会把成员函数当作内联函数处理(如果成员函数过长,编译器还是会把成员函数作为普通函数处理,是否内联取决于编译)
class Stack
{
public://成员函数在类中定义void init(int capacity = 4){_a = (int*)malloc(sizeof(int) * capacity);if (_a == nullptr){perror("malloc fail");return;}_capacity = capacity;_top = 0;}private:int* _a;int _top;int _capacity;
};
  1. 也可以将成员函数定义在类体外,就需要使用::作用域操作符指明函数属于哪个类
class Stack
{
public://成员函数在类外定义void init(int capacity);private:int* _a;int _top;int _capacity;
};void  Stack::init(int capacity)
{_a = (int*)malloc(sizeof(int) * capacity);if (_a == nullptr){perror("malloc fail");return;}_capacity = capacity;_top = 0;
}

需要用作用域操作符是因为:在定义一个类时,类定义了一个新的作用域
不同的作用域中可能会有重名的函数,所以函数在类外定义时,就必须用::指明这个函数属于哪个类


类的访问限定符和封装

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

访问限定符有三个:公有public,私有private,保护protected

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

一般情况下,我们定义类时,把成员函数设为公有,把成员变量设为私有,这时,在类外就无法直接访问成员变量,只能通过访问成员函数,在成员函数中对成员变量进行操作。这样既保护了成员变量,同时使用成员函数保证了成员变量在我们设定好的思路下进行一系列操作。

这也是封装的基本思想

面向对象的三大特性:封装、继承、多态。

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

封装本质上是一种管理,让用户更方便使用类
就像对于一个计算机,我们用户不用去管它的内部各个部件是如何是如果排布的,也不用管CPU,GPU,内存等是如何工作的,厂商都把它们封装在一个壳子里了,对于我们,只能通过厂商给好的键盘,鼠标,和接口对计算机进行操作

同理,对于C++中的封装也是同理,用户不必去管成员函数内部具体逻辑,也更不会操作到成员变量,用户只需要会调用成员函数即可

通过类将数据以及操作数据的方法进行有机结合,通过访问权限来隐藏对象内部实现细节,控制哪些方法可以在类外部直接被使用。


对象的实例化

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

对于变量,声明和定义的区别在于是否开辟内存空间,所以在类定义中的成员变量是声明而不是定义

类的实例化:

class Stack
{
public:void init(int capacity);
private:int* _a;int _top;int _capacity;
};void  Stack::init(int capacity)
{_a = (int*)malloc(sizeof(int) * capacity);if (_a == nullptr){perror("malloc fail");return;}_capacity = capacity;_top = 0;
}int main()
{//Stack类的实例化Stack s1;Stack s2;
}

一个类是没有空间的,只有它实例化出的对象才有物理空间

类的对象的关系就像图纸和实物的关系,我们可以依照同一个图纸创造出许多一样的实物,这些实物有自己的体积,而图纸没有


类对象的大小

前面说了,一个类没有大小,只有它实例化出的对象有大小,那么它的对象大小怎么计算呢?

事实上,类成员的储存空间中,只保存成员变量,成员函数都放在公共的代码段

这是因为,类实例化出不同的对象,这些对象的成员变量不同,所以必须将每个对象的成员变量单独存储,而不同对象的成员函数是一样的,只不过是传参数的值不同罢了,没有必要再将成员函数单独存储。所以就将成员函数都放在公共的代码段
在这里插入图片描述

假设A类中所有成员都是公有的,A实例化一个对象aa.print()实际上是去公共空间里去找,a.num是到对象里面找

所以,一个对象的大小,就是计算其中成员变量大小“之和”,这里要注意内存对齐原则

这里的内存对齐原则和结构体内存对齐是一样的,内存对齐具体的内容参考:结构体内存对齐

注意空类的大小,空类是指没有成员变量只有成员函数的类
对于空类,编译器给了空类一个字节来唯一标识这个类的对象,是为了占位,表示对象存在
在这里插入图片描述


this指针

class Date
{
public:void Init(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}private:int _year;int _month;int _day;
};int main()
{Date d1, d2;d1.Init(2022, 1, 11);d2.Init(2022, 1, 12);d1.Print();d1.print(&d1)d2.Print();return 0;
}

先来思考一个问题,一个类实例化了2个对象,通过2个对象访问同一个成员函数时,前面知道成员函数存储在公共代码段里,函数体中没有关于不同对象的区分
那么d1调用 Init 函数时,该函数是如何知道应该设置d1对象,而不是设置d2对象呢

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

所以不同对象虽然调用的函数相同,但是函数中的形参不同.

Print函数为例,编译器会处理成员函数中隐藏的this指针
在这里插入图片描述

对于调用成员函数,也会被编译器自动处理:
在这里插入图片描述

通过d1调用Init函数时,d1.Init(2022, 1, 11),实际上是传递4个参数:d1.Init(&d1,2022, 1, 11)

要注意的是:C++里,不允许在实参或形参中显示使用this指针,但是允许在函数中使用
在这里插入图片描述

  • this指针的类似:类类型 * const ,所以在成员函数中,不能给this赋值
  • this指针只能在成员函数中使用
  • this指针是一个隐藏的形参,当调用成员函数时,将对象地址作为实参传递给this实参
  • this作为形参,存储在栈区,不存储在对象中,函数结束后,this指针自动销毁
  • this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递

分析一下下面的代码是否能正确运行:

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

p->PrintA()p调用PrintA()也不会发生解引用错误,因为PrintA()是成员函数,它储存在公共代码段,不在对象中

这里将A类型的指针p设为空指针,通过p去访问PrintA(),这里实际上把p作为实参传递给了PrintA()里的this形参,所以在成员函数PrintA()中的this指针是空指针,但是在这个函数里并没有对this指针进行解引用,所以这个函数可以正常运行出来

在分析下面这个代码:

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

和上面代码类似,p作为实参传递给了成员函数中的形参,但是在这个函数中,cout << _a << endl实际上是cout << this->_a << endl这里对this指针进行解引用了,所以这段代码会出错。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/28304.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《Linux0.11源码解读》理解(五) head之开启分页

先回顾一下地址长度以及组合的演变&#xff1a;16位cpu意味着其数据总线/寄存器也是16位&#xff0c;但是地址总线&#xff08;寻址能力&#xff09;与此无关&#xff0c;可能是20位。可以参考&#xff1a;cpu的位宽、操作系统的位宽和寻址能力的关系_cpu位宽_brahmsjiang的博客…

Android TextView 在最后一行末尾加图标

当前有个需求.显示一段文本&#xff0c;文本最多显示两行&#xff0c;点击展开后才显示完全。当没有显示完全的时候&#xff0c;需要在文本的第二行末尾显示图标&#xff0c;点击图标和文本&#xff0c;文本展开。难点在于图标需要和第二行文本显示在同一行&#xff0c;高度和文…

oracle 如何连同空表一起导出成dmp的方法

1、oracle导出dmp文件的时候&#xff0c;经常会出现一些空表&#xff0c;没有一并被导出的情况。 执行sql select alter table ||table_name|| allocate extent; from user_tables where num_rows0 or num_rows is null; 新建一个sql窗口&#xff0c;把查询结果的sql&#…

Pytorch自动求导机制详解

目录 1. 自动求导 1.1 梯度计算 1.1.1 一阶导数 1.1.2 二阶导数 1.1.3 向量 1.2 线性回归实战 1. 自动求导 在深度学习中&#xff0c;我们通常需要训练一个模型来最小化损失函数。这个过程可以通过梯度下降等优化算法来实现。梯度是函数在某一点上的变化率&#xff0c;可以告…

[黑苹果EFI]Lenovo ThinkPad T490电脑 Hackintosh 黑苹果引导文件

原文来源于黑果魏叔官网&#xff0c;转载需注明出处。&#xff08;下载请直接百度黑果魏叔&#xff09; 硬件型号驱动情况 主板Lenovo ThinkPad T490 处理器Intel Intel Core i5 8265U (Quad Core)已驱动 内存16 GB:8 GB Samsung DDR 4 2666 Mhz *2已驱动 硬盘PC SN520 NVM…

CentOS7中安装docker并配置阿里云加速器

文章目录 一、docker的安装二、docker的卸载三、配置加速器四、docker-compose安装五、docker-compose卸载六、docker-compose相关命令七、常用shell组合 一、docker的安装 参考&#xff1a;https://docs.docker.com/engine/install/centos 本文内容是基于&#xff1a;CentOS L…

文件IO 文件属性获取,目录操作

文件属性获取 int stat(const char *path, struct stat *buf); 功能&#xff1a;获取文件属性参数&#xff1a; path&#xff1a;文件路径名 buf&#xff1a;保存文件属性信息的结构体返回值&#xff1a; 成功&#xff1a;0 失败&#xff1a;-1struct stat {ino_t st_ino;…

批发小程序怎么做

批发订货小程序功能介绍 我们的批发订货小程序是一个集订货浏览权限、一客一价、业务员端口、代客下单、订单汇总和订单打印等功能于一体的专业平台。以下是对这些功能的详细描述&#xff1a; 1. 订货浏览权限&#xff1a;我们的小程序可以为不同用户分配不同的订货浏览权限。…

在LLM的支持下使游戏NPC具有记忆化的方法

问题 使用GPT这样的LLM去处理游戏中的NPC和玩家的对话是个很好的点子&#xff0c;那么如何处理记忆化的问题呢。 因为LLM的输入tokens是有限制的&#xff0c;所以伴随着问题的记忆context是有窗口大小限制的&#xff0c;将所有的记忆输入LLM并不现实。 所以这里看到了stanfo…

TOWE智能PDU是如何帮助机房安然度夏的?

最近&#xff0c;全国各地纷纷进入高温“火炉”模式&#xff0c;炎炎夏日&#xff0c;数据中心的工作温度不应该超过一定的限度。数据中心机房不仅要确保在高温多雨天气下安全、稳定的运维&#xff0c;还要承受降低企业总体运营成本的压力。这种需求下&#xff0c;相较于传统基…

【Matlab】智能优化算法_遗传算法GA

【Matlab】智能优化算法_遗传算法GA 1.背景介绍2.数学模型3.文件结构4.详细代码及注释4.1 crossover.m4.2 elitism.m4.3 GeneticAlgorithm.m4.4 initialization.m4.5 Main.m4.6 mutation.m4.7 selection.m4.8 Sphere.m 5.运行结果6.参考文献 1.背景介绍 遗传算法&#xff08;Ge…

Python基础合集 练习28 (数值运算函数)

from this import d x -120 x的绝对值 x1 abs(x) 同时输出商和余数 y 7 y1 divmod(x1, y) print(y1) /进行幂余运算 z可以省略 (x**y)%z pow(x,y[,z]) pow(3, pow(3, 99), 10000) 四舍五入函数 d是保留小数位数&#xff0c;默认为0 round(x,[,d]) print(round…