谈对象系列:C++类和对象
文章目录
- 一、类的定义
- 1.1类定义的格式
- 类的两种定义方法
- 结构体:
- 1.2访问限定符
- 1.3类域
- 二、实例化
- 2.1变量的声明和定义
- 2.2类的大小
- 计算空类的大小(面试):
- 三、this指针
- 小考题
一、类的定义
1.1类定义的格式
使用class关键字,定义类,calssName是类名,{}中为类的主体,最后的分号 ;
可别忘了加上。
类的名称,就是类的类型。
- 类定义的函数,属于inline内联函数,但具体展开还是得看编译器的选择。
class calssName
{//成员函数 //成员变量
};
int main()
{tag st1;//类名,就是类型
}
成员变量(member)的特殊标识:
- 在变量名前加上下划线
- 在变慢名前加上字母m
类的两种定义方法
将函数的定义和声明放在类里面实现:
这里使用class定义了一个日期类,在main函数里声明了一个day1对象,使用点操作符(.)
、箭头操作符 (->)
来访问类成员函数。
#include <iostream>
using std::cout;
using std::endl;class Data
{
public:void Init(int year = 2024, int month = 6, int day = 6){_year = year;_month = month;_day = day;}void print(){cout << _year << "/" << _month << "/" << _day << endl;}
private:int _year;int _month;int _day;
};
int main()
{Data day1;day1.Init(2024, 5, 20); day1.print();return 0;
}
将函数声明放在类里面,函数定义在类外面实现。
stack.h
#include <iostream>
class Stack
{
public:void Init(int capacity = 4);void Push(int x);void Pop();int Top();int Empty();void Destry(){assert(_next);if (_next)free(_next);_next = NULL;_top = 0;_capacity = 0;}
private:int _top;int _capacity;int* _next;
};
stack.cpp
void Stack::Init(int capacity)
{_next = (int*)malloc(sizeof(int) * capacity);_top = 0;_capacity = capacity;
}
void Stack::Push(int x)
{if (_top == _capacity){int newcapacity = _capacity == 0 ? 4 : _capacity * 2;int* newNext = (int*)realloc(_next, sizeof(int) * newcapacity);if (newNext == NULL){perror("realloc fail");exit(1);}_next = newNext;_capacity = newcapacity;}_next[_top++] = x;
}void Stack::Pop()
{assert(_next && _top > 0);_top--;
}
int Stack::Top()
{assert(_next && _top > 0);return _next[_top - 1];
}
int Stack::Empty()
{return _top == 0;
}
结构体:
在C++中兼容C语言,C++编译器也支持struct关键字,满足结构体的功能C,不仅如此C++还对它进行了升级,也支持类的定义格式。
这里在C语言中定义一个链表的结构的。
struct ListNode
{int val;struct ListNode* next;
};
而C++兼容C语言,又对结构进行升级,在定义链表时可以直接省略struct关键字。
struct ListNode
{int val;ListNode* next;
};
在结构体里和类一样还可以定义函数。下面使用C++实现栈。
struct Stack
{
public:void Init(int capacity = 4){_next = (int*)malloc(sizeof(int) * capacity);_top = 0;_capacity = capacity;}void Push(int x){if (_top == _capacity){int newcapacity = _capacity == 0 ? 4 : _capacity * 2;int* newNext = (int*)realloc(_next, sizeof(int) * newcapacity);if (newNext == NULL){perror("realloc fail");exit(1);}_next = newNext;_capacity = newcapacity;}_next[_top++] = x;}void Pop(){assert(_next && _top > 0);_top--;}int Top(){assert(_next && _top > 0);return _next[_top - 1];}int Empty(){return _top == 0;}void Destry(){assert(_next);if (_next)free(_next);_next = NULL;_top = 0;_capacity = 0;}
private:int _top;int _capacity;int* _next;
};
int main()
{Stack s1;s1.Init();s1.Push(1);s1.Push(2);s1.Push(3);s1.Push(4);while (!s1.Empty()){cout << s1.Top() << endl;s1.Pop();}s1.Destry();return 0;
}
-
可以发现使用C++实现的栈,与C语言没有过多的区别,只是将栈的接口放在了结构体内部。
-
而在使用栈的结构体时,并不像C语言那样需要频繁的传地址,只需调用类成员函数即可,以及在每一个函数名前省略了Stack,栈的函数是在结构体内实现的,属于Stack类域,即使是其它地方的函数实现了,Init、Push、Pop等功能,那也是支持的。
-
调用栈的接口函数需使用 点操作符
(.)
。
1.2访问限定符
可以发现,在上述实现类中,使用了 pulic
、private
这两个是访问限定符,用于限定对类成员访问的权限
- C++将对象的属性与方法封装在一起,通过访问权限选择性的将接口提供给外部使用。
public
公有的,被public修饰的成员在类外可以被访问,private和protected
修饰的成员不能在类外部直接访问,它两的效果差不多,常用private
,两者之间的区别要在继承中才能体现出。- 访问限定符的作用域,从限定符开始知道遇见下一个限定符结束,或者遇见
}
结束。 - class定义的成员没有使用访问限定符修饰,class内默认使用
private
修饰,struct内默认使用pulic
修饰。- 被限制在了类中,而不能在外部使用相当于一种保护,是不希望类中被🔒锁上的成员被修改。一般不提供外部使用的成员变量使用
private和protected
修饰,提供外部使用的成员函数使用pulic
修饰。
- 被限制在了类中,而不能在外部使用相当于一种保护,是不希望类中被🔒锁上的成员被修改。一般不提供外部使用的成员变量使用
1.3类域
类定义了一个新的作用域,类域影响了编译器的查找规则,类的所有成员都在类域中,想要访问类域的成员使用
::
域作用限定符指出成员属于哪个类。
如同上述的将函数声明放在类里面,函数定义在类外面实现。想要在类外实现类函数的定义,就必须使用上::
域作用限定符
使用 Stack::
,Stack指明了Init函数属于Stack类域
void Stack::Init(int capacity)
{_next = (int*)malloc(sizeof(int) * capacity);_top = 0;_capacity = capacity;
}
声明和定义分离,需要指定类域
二、实例化
2.1变量的声明和定义
变量的声明是不占空间内存的,只是告诉编译器存在这么个东西;而定义是在内存中开辟空间。
以下变量是声明还是定义?
class Data
{int _year;//声明int _month;//声明int _day;//声明
};
int val;//定义
int main()
{int n;//定义return 0;
}
声明就相当于别人给你的口头承诺,至于会不会实现那是另一回事,只是他告诉你有这么个事,而定义就相当于别人实实在在的帮你做完了事,不局限于声明。
对象的实例化:
对象实例化,就是使用类类型创建变量的过程,类的成员变量只是一个声明,还没有为其开辟空间,类是对对象的一个描述,需要那些功能,那些变量实现一个对象,就会在类中一一实现。
就好比如一个塑料瓶子,有了一个瓶子的模型,需要大量生成塑料瓶的时候都会按照这个模型生产。类和对象也如同这样,使用类将一个需要描述的对象的功能,框架等一一在类中实现,在需要使用的时候使用类创建一个对象。
同理一个类可以实例化多个对象,就像一个冰棍的模型可以生产许多颗冰棍。实例化的对象会占据实际的物理空间,存储数据。
2.2类的大小
该如何去计算一个类的大小,它实际上与之前学习过的计算结构体大小规则一致。
结构体内存对齐的原则:
-
结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处(上述两个结构体c1偏移量为0的原因)
-
其它成员变量要对齐到对齐数的整数倍的地址处。
- 对齐数:编译器默认的第一个对齐数 与 该成员变量大小的较小值。
- vs中默认—8
- linux中gcc 没有默认对齐数,对齐数就是成员自身大小。
-
结构体中每个成员变量都有一个对齐数。
-
结构体总大小为最大对齐数(结构体中最大对齐数)的整数倍。
-
如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员中的对齐数)的整数倍。
那,咱就使用对齐规则尝试计算类的大小
class Data
{int _year;int _month;int _day;
};
可以发现,使用对齐规则计算后的大小为12个字节。
但类中是有函数的定义,计算类的大小时需要将类成员函数也一起计算吗?事实上并不需要。
#include <iostream>
using std::cout;
using std::endl;class Data
{
public:void Init(int year = 2024, int month = 6, int day = 6){_year = year;_month = month;_day = day;}void print(){cout << _year << "/" << _month << "/" << _day << endl;}
private:int _year;int _month;int _day;
};
int main()
{printf("%zd\n", sizeof(Data));return 0;
}
可以发现,在类中定义了两个类成员函数后,计算的大小还是12个字节,符合上述通过对齐规则计算后的结果。
类没有为类成员函数开辟内存空间,那它们是存储在哪里。
类成员函数与类成员变量分开存储的。在计算类的大小是只考虑成员变量。使用同一个类实例化的对象,对应每一个对象的成员变量来说都是存放在不同的空间中来管理数据,而每一个对象的成员函数 例如Init
,都需调用相同的函数,没有必要为每一个对象都开辟空间存放 Init
函数,这样会造成大量的空间浪费。
成员函数都有一块公共区域来存放,对于每一个对象来说是公有的,大家可以一块使用。
计算空类的大小(面试):
class S{};
计算一个空类,它的大小肯定不会为0
int main()
{S s;printf("%zd\n", sizeof(s));return 0;
}
在main函数里定义了类S对象s,它是通过类的大小开辟空间,若一个空类的大小为0,定义空类的时候编译器就会报错。
编译器给空类一个字节类唯一标识这个类的对象,这一个字节不存储有效数据,为一个占位符,标识对象被实例化。
三、this指针
Date类中有Init和print两个成员函数,函数体内没有为不同对象进行区别,在使用day1、day2调用
Init
函数时,函数是如何区别是day1还是day2的。这里就通过C++提供的隐含的this指针来解决。
- 编译器编译后,类的成员函数默认都会在形参的第一个位置,增加一个当前类类型的指针,叫this指针。Date类中实现的
Init
函数原型为void Init(Data* const this, int year int month, int day)
- this作为一个关键字存在,不能显示的写在形参中,编译器会自动完成。规定
- 在函数内可以显示的用this指针,可以给它加上,也可以不加。不添加并不代表不存在this指针
- 外界无法传入当前对象的地址给到被调用的成员函数,使用this指针接受当前对象的地址,,this指针就会通过不同的地址去找到内存中对应的成员变量。
- this指针只能在成员函数内部使用,因为它是作为一个成员函数的形参,若是没有传递给当前对象地址的话,那么它的指向是不确定的。
- this指针作为函数参数,是不会存储在类中,而是随着函数栈帧的开辟,存放在栈区。
class Date
{
public://void Init(Data* const this, int year, int month, int day)void Init(int year, int month, int day){_year = year;_month = month;_day = day;//this->_year = year;//this->_month = month;//this->_day = day;}void print(){cout << _year << "/" << _month << "/" << _day << endl;}private:int _year;int _month;int _day;
};
int main()
{Date day1;Date day2;day1.Init(2024, 5, 20); day1.print();return 0;
}
通过编译器调试,可以发现的确存在this指针来接收了day1传递的地址。
小考题
下面程序编译运行后的结果是:
A、编译报错 B、运行崩溃 C、正常运行
#include<iostream>
using namespace std;
class A
{
public:void Print(){cout << "A::Print()" << endl;cout << _a << endl;}
private:int _a;
};
int main()
{A* p = nullptr;p->Print();return 0;
}
答案是B,主函数内部不存在空指针的解引用,Print不存在对象里面,是在一块公共区域,若 p->_a
这样写,就会出现空指针的解引用, _a
是存在对象内部的,就像结构体里空链表对结构体成员的访问。
- 成员函数的指针是在编译时确定的,没有存放在对象中,虽然写成了
p->Print()
,但是没有解引用。
问题出在Print
函数内,Print
函数的this指针接收了传递过来的空指针,而又使用 this->_a
,对空指针进行了解引用,从而造成程序崩溃。
相关文章:

谈对象系列:C++类和对象
文章目录 一、类的定义1.1类定义的格式类的两种定义方法结构体: 1.2访问限定符1.3类域 二、实例化2.1变量的声明和定义2.2类的大小计算空类的大小(面试): 三、this指针小考题 一、类的定义 1.1类定义的格式 使用class关键字&…...

设计模式20-备忘录模式
设计模式20-备忘录 动机定义与结构定义结构 C代码推导优缺点应用场景总结备忘录模式和序列化备忘录模式1. **动机**2. **实现方式**3. **应用场景**4. **优点**5. **缺点** 序列化1. **动机**2. **实现方式**3. **应用场景**4. **优点**5. **缺点** 对比总结 动机 在软件构建过…...

绘制echarts-liquidfill水球图
文章目录 一、效果图二、步骤1.安装插件2.引入2.主要代码2.素材图片 总结 一、效果图 二、步骤 1.安装插件 npm install echarts npm install echarts-liquidfillecharts5的版本与echarts-liquidfill3兼容,echarts4的版本与echarts-liquidfill2兼容,安装的时候需要…...

应急响应:D盾的简单使用.
什么是应急响应. 一个组织为了 应对 各种网络安全 意外事件 的发生 所做的准备 以及在 事件发生后 所采取的措施 。说白了就是别人攻击你了,你怎么把这个攻击还原,看看别人是怎么攻击的,然后你如何去处理,这就是应急响应。 D盾功…...

c语言第14天笔记
通过指针引用数组 数组元素的指针 数组指针:数组中的第一个元素的地址,也就是数组的首地址。 指针数组:用来存放数组元素地址的数组,称之为指针数组。 注意:虽然我们定义了一个指针变量接收了数组地址,但…...

服装行业QMS中的来料检验:常见问题解析与解决策略
在服装行业的来料检验过程中,常会遇到一系列问题,这些问题可能影响到原材料的质量,进而影响最终产品的品质。以下将详细介绍来料检验的常见问题及相应的解决方法: 一、常见问题 外观瑕疵 问题描述:原材料表面存在污渍…...

健身动作AI识别,仰卧起坐计数(含UI界面)
用Python和Mediapipe打造,让你的运动效果一目了然! 【技术揭秘】 利用Mediapipe的人体姿态估计,实时捕捉关键点,精确识别动作。 每一帧的关键点坐标和角度都被详细记录,为动作分析提供数据支持。 支持自定义动作训练&a…...

GitHub开源金融系统:Actual
Actual:电子金融,本地优先,自由开源- 精选真开源,释放新价值。 概览 Actual的创新之处在于其对个人财务管理的全面考虑,它不仅仅是一个简单的记账工具,而是一个综合性的理财解决方案。它的本地优先设计意味…...

【学习笔记】Day 7
一、进度概述 1、DL-FWI基础入门培训笔记 2、inversionnet_train 试运行——未成功 二、详情 1、InversionNet: 深度学习实现的反演 InversionNet构建了一个具有编码器-解码器结构的卷积神经网络,以模拟地震数据与地下速度结构的对应关系。 (一…...
网络中特殊的 IP 地址
特殊网络 IP 127.0.0.1 127.0.0.1 是本机回送地址,发送到 127.0.0.1 的数据或者从 127.0.0.1 返回的数据只会在本机进行传输, 而不进行外部网络传输。 主要有以下两个作用: 测试本机网络 当我们可以 ping 通 127.0.0.1 的时候, 则说明本机的网卡以及 tc…...
ASP 表单处理入门指南
ASP 表单处理入门指南 简介 ASP(Active Server Pages)是一种由微软开发的服务器端脚本环境,用于动态生成交互性网页。它允许开发者结合HTML、VBScript或JScript脚本语言来创建和运行动态网页或Web应用程序。本文将重点介绍如何使用ASP来处理表单数据,包括表单的创建、数据…...

极米RS10Plus性价比高吗?7款4-6K价位投影仪测评哪款最好
通常家庭想买个投影仪都会选择4-6K这个价位段的投影仪,3K以下的投影配置太低,6K以上的价格略高,4-6K价位段的中高端投影仪正好满足大部分家庭的使用需求。正好极米投影在8月份上新了一款Plus版本的长焦投影:极米RS10Plusÿ…...
RocketMQ怎么对文件进行读写的?
RocketMQ 对文件的读写主要依赖于其底层的存储机制,核心组件是 CommitLog 和 ConsumeQueue,并且通过 MappedFile 类来进行高效的文件操作。以下是 RocketMQ 文件读写的详细介绍: 1. CommitLog CommitLog 是 RocketMQ 的核心存储文件&#x…...
智慧宠物护理:智能听诊器引领健康监测新潮流
在宠物健康科技的浪潮中,智能听诊器的诞生标志着宠物健康管理迈向了智能化的新纪元。广州坎普利智能信息科技有限公司的创新产品,正为宠物主人和他们的毛茸茸伙伴带来前所未有的关怀体验。 创新特点 这款智能听诊器,以其前沿科技和人性化设…...

SRE工程师第2天:我只要截图功能 而不是打开微信
大家好,我是watchpoints 别想太多,只管去提问,所有问题,都会有答案 watchpoints是我github用户名 , 也是我的wechat 用户名,如果我有讲不明白 欢迎提问 什么是SRE(Site Reliability Engineer) 和…...

【RunnerGo】离线安装成功版本
目录 一、下载 二、解压安装包 三、修改安装配置 3.1 编辑修改安装参数(我没有改,默认安装即可) 3.2 安装目录结构说明 四、执行安装 五、检查服务并使用 六、访问 前言:最近在调研一个新工具,发现RunnerGo&…...

AI 手机的技术展望
某某领导问到我,AI手机这个产业发展如何?对于,地方科技园区,应该如何发展相关产业?我一时还真说不上来,于是,查了一下资料,大概应对了一下。 一:AI手机的定义 首先&…...
实战 Springboot2 集成Redis 哨兵模式、集群模式、缓存管理、Lettuce拓扑刷新
redis搭建集群模式、Cluster模式(6节点,3主3从集群模式,添加删除节点)_redis cluster节点带数据增减-CSDN博客 Linux部署Redis哨兵集群 一主两从三哨兵(这里使用Redis6,其它版本类似)_linux red…...
MYSQL--binlog和redo log
前言 MySQL日志 MySQL日志主要包括错误日志、查询日志、慢查询日志、事务日志、二进制日志几大类。其中比较重要的就是二进制日志binlog(归档日志)、事务日志redo log(重做日志)和undo log(回滚日志)。 这篇…...

R语言医疗数据分析笔记
分组因子又是什么意思,分组因子和数组的区别是什么 举个实际的例子 分组因子 分组因子是分类变量,用于将数据分成不同组以便于比较或分析。例如,在一项研究中,研究对象的性别(男性和女性)可以视为一个分组…...

Python_day47
作业:对比不同卷积层热图可视化的结果 一、不同卷积层的特征特性 卷积层类型特征类型特征抽象程度对输入的依赖程度低层卷积层(如第 1 - 3 层)边缘、纹理、颜色、简单形状等基础特征低高,直接与输入像素关联中层卷积层(…...
Svelte 核心语法详解:Vue/React 开发者如何快速上手?
在很多地方早就听到过svelte的大名了,不少工具都有针对svelte的配置插件,比如vite \ unocss \ svelte. 虽然还没使用过,但是发现它的star82.9k数很高哦,学习一下它与众不同的魔法。 这名字有点别扭,好几次都写错。 sve…...
基于Flask框架的前后端分离项目开发流程是怎样的?
基于Flask框架的前后端分离项目开发流程可分为需求分析、架构设计、并行开发、集成测试和部署上线五个阶段。以下是详细步骤和技术要点: 一、需求分析与规划 1. 明确项目边界 功能范围:确定核心功能(如用户认证、数据管理、支付流程&#…...
Ansible自动化运维全解析:从设计哲学到实战演进
一、Ansible的设计哲学:简单即正义 在DevOps工具链中,Ansible以其"无代理架构(Agentless)"设计独树一帜。这个用Python编写的自动化引擎,通过SSH协议与目标主机通信,彻底摒弃了传统配置管理工具…...
互联网大厂Java求职面试:云原生架构下的微服务网关与可观测性设计
互联网大厂Java求职面试:云原生架构下的微服务网关与可观测性设计 郑薪苦怀着忐忑的心情走进了会议室,对面坐着的是某大厂的技术总监张总,一位在云原生领域有着深厚积累的专家。 第一轮面试:微服务网关的设计挑战 张总…...

关于华为仓颉编程语言
文章目录 一、基本概况二、技术特点1. 多范式编程2. 原生智能化3. 高性能与安全4. 全场景兼容 三、编译器与开发工具四、语言相似性对比五、行业应用实例总结 最近经常看到这个东西,于是搜了一下,整理了一些内容,水一篇,以后慢慢研…...

软件工程:如何做好软件产品
1、什么是产品 从项目到产品 产品:满足行业共性需求的标准产品。即要能够做到配置化的开发,用同一款产品最大限度地满足不同客户的需求,同时让产品具有可以快速响应客户需求变化的能力。 好的产品一定吸收了多个项目的共性,一定是…...

飞牛使用Docker部署Tailscale 内网穿透教程
之前发过使用docker部署Tailscale的教程,不过是一年前的事情了,今天再重新发表一遍,这次使用compose部署更加方便,教程也会更加详细一点,希望对有需要的朋友有所帮助! 对于大部分用户来说,白嫖 …...
AI 模型分类全解:特性与选择指南
人工智能(AI)技术正以前所未有的速度改变着我们的生活和工作方式。AI 模型作为实现人工智能的核心组件,种类繁多,功能各异。从简单的线性回归模型到复杂的深度学习网络,从文本生成到图像识别,AI 模型的应用…...
UDP与TCP的区别是什么?
UDP和TCP是互联网通信中最常用的两种传输层协议,它们在数据传输方式、可靠性、速度和适用场景等方面存在显著差异。本文将围绕UDP与TCP的核心区别展开详细分析,包括连接方式、数据传输机制、传输效率以及各自适合的应用场景,帮助开发者和网络…...