谈对象系列: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语言医疗数据分析笔记
分组因子又是什么意思,分组因子和数组的区别是什么 举个实际的例子 分组因子 分组因子是分类变量,用于将数据分成不同组以便于比较或分析。例如,在一项研究中,研究对象的性别(男性和女性)可以视为一个分组…...
mongodb源码分析session执行handleRequest命令find过程
mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程,并且验证connection是否超过限制ASIOSession和connection是循环接受客户端命令,把数据流转换成Message,状态转变流程是:State::Created 》 St…...
Opencv中的addweighted函数
一.addweighted函数作用 addweighted()是OpenCV库中用于图像处理的函数,主要功能是将两个输入图像(尺寸和类型相同)按照指定的权重进行加权叠加(图像融合),并添加一个标量值&#x…...
条件运算符
C中的三目运算符(也称条件运算符,英文:ternary operator)是一种简洁的条件选择语句,语法如下: 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true,则整个表达式的结果为“表达式1”…...
服务器硬防的应用场景都有哪些?
服务器硬防是指一种通过硬件设备层面的安全措施来防御服务器系统受到网络攻击的方式,避免服务器受到各种恶意攻击和网络威胁,那么,服务器硬防通常都会应用在哪些场景当中呢? 硬防服务器中一般会配备入侵检测系统和预防系统&#x…...
Spring Boot面试题精选汇总
🤟致敬读者 🟩感谢阅读🟦笑口常开🟪生日快乐⬛早点睡觉 📘博主相关 🟧博主信息🟨博客首页🟫专栏推荐🟥活动信息 文章目录 Spring Boot面试题精选汇总⚙️ **一、核心概…...
linux 下常用变更-8
1、删除普通用户 查询用户初始UID和GIDls -l /home/ ###家目录中查看UID cat /etc/group ###此文件查看GID删除用户1.编辑文件 /etc/passwd 找到对应的行,YW343:x:0:0::/home/YW343:/bin/bash 2.将标红的位置修改为用户对应初始UID和GID: YW3…...
unix/linux,sudo,其发展历程详细时间线、由来、历史背景
sudo 的诞生和演化,本身就是一部 Unix/Linux 系统管理哲学变迁的微缩史。来,让我们拨开时间的迷雾,一同探寻 sudo 那波澜壮阔(也颇为实用主义)的发展历程。 历史背景:su的时代与困境 ( 20 世纪 70 年代 - 80 年代初) 在 sudo 出现之前,Unix 系统管理员和需要特权操作的…...
12.找到字符串中所有字母异位词
🧠 题目解析 题目描述: 给定两个字符串 s 和 p,找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义: 若两个字符串包含的字符种类和出现次数完全相同,顺序无所谓,则互为…...
(转)什么是DockerCompose?它有什么作用?
一、什么是DockerCompose? DockerCompose可以基于Compose文件帮我们快速的部署分布式应用,而无需手动一个个创建和运行容器。 Compose文件是一个文本文件,通过指令定义集群中的每个容器如何运行。 DockerCompose就是把DockerFile转换成指令去运行。 …...
OpenLayers 分屏对比(地图联动)
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 地图分屏对比在WebGIS开发中是很常见的功能,和卷帘图层不一样的是,分屏对比是在各个地图中添加相同或者不同的图层进行对比查看。…...
