谈对象系列:C++类和对象

文章目录

  • 一、类的定义
    • 1.1类定义的格式
      • 类的两种定义方法
      • 结构体:
    • 1.2访问限定符
    • 1.3类域
  • 二、实例化
    • 2.1变量的声明和定义
    • 2.2类的大小
      • 计算空类的大小(面试):
  • 三、this指针
      • 小考题

一、类的定义

1.1类定义的格式

使用class关键字,定义类,calssName是类名,{}中为类的主体,最后的分号 ;可别忘了加上。

类的名称,就是类的类型

  • 类定义的函数,属于inline内联函数,但具体展开还是得看编译器的选择。
class calssName
{//成员函数 //成员变量
};
int main()
{tag st1;//类名,就是类型
}

成员变量(member)的特殊标识:

  1. 在变量名前加上下划线
  2. 在变慢名前加上字母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访问限定符

可以发现,在上述实现类中,使用了 pulicprivate这两个是访问限定符,用于限定对类成员访问的权限

  • 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,对空指针进行了解引用,从而造成程序崩溃。

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

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

相关文章

使用开源 LLM 充当 LangChain 智能体

太长不看版 开源 LLM 现已达到一定的性能水平&#xff0c;可堪作为智能体工作流的推理引擎。在我们的测试基准上&#xff0c;Mixtral 甚至已超越 GPT-3.5&#xff0c;而且我们还可以通过微调轻松地进一步提高其性能。 引言 经由因果语言建模任务训练出的大语言模型&#xff…

【电控笔记z56】ADRC回路设计(与smo比较)

用在IPM ADRC 估测反电动势 参数变动 : 内部扰动 SMO : 有高频成分 需要低通滤波器滤去 - 需要补偿延迟 两轴同步旋转坐标下做adrc adrc适合去做变化速度比较低的扰动 ADRC : 估测高速变化的扰动 , 需要修改估测器 电机模型 Ld不等于Lq 式7如下蓝色框图 eso等效成一个纯积分…

Stable Diffusion绘画 | 提示词格式

推荐格式 提升画质的提示词与画风的提示词&#xff0c;对整体画面影响较大&#xff0c;建议在首行填写 画质词画风词画面主体描述环境、场景、灯光、构图Lora负面词 画质词 常规画质词&#xff1a; (masterpiece:1.2),best quality,highres,extremely detailed CG,perfect…

Jenkins部署java项目

文章目录 引言I Jenkins 配置系统配置Maven 配置添加gitea凭据II 新建部署任务(maven)构建触发器构建环境Post StepsIII Jar包部署为linux系统服务创建systemd服务创建jar启动脚本IV java激活指定环境的Profile文件命令行指定配置指定环境下的nacos配置Dockerfile指定具体环境…

kmp算法(c++)

kmp算法的简单介绍 从主串中快速找到与要找的串的相同位置 如果使用暴力算法去求解这个问题&#xff0c;时间复杂度为O(i*j) > 很大 kmp算法则是对这类问题的优化 因整理过于麻烦&#xff0c;&#xff0c;详细的介绍可以参照这篇博客&#xff0c;&#xff0c;花时间看完就明…

第二十一节、敌人追击状态的转换

一、物理检测中的Boxcast 1、检测敌人Bool 当不知道一个函数的返回值是什么的时候 定义一个var变量 就知道了 二、状态切换 1、switch用法 2、新的语法糖写法

【MySQL】数据基本的增删改查操作

新增数据&#xff08;Create&#xff09; 在MySQL中&#xff0c;增加数据的操作主要使用 INSERT 语句。下面我们将分为两部分&#xff1a;单行数据插入和多行数据插入。 一、单行数据插入 全列插入&#xff1a; 当你要插入一行数据到表中并且要提供所有列的值时&#xff0c;可…

jmeter-beanshell学习16-自定义函数

之前写了一个从文件获取指定数据&#xff0c;用的时候发现不太好用&#xff0c;写了一大段&#xff0c;只能取出一个数&#xff0c;再想取另一个数&#xff0c;再粘一大段。太不好看了&#xff0c;就想到了函数。查了一下确实可以写。 public int test(a,b){return ab; } ctes…

剖析HTML 元素——WEB开发系列02

HTML元素是构成HTML文档结构的基本单位&#xff0c;定义了页面上的不同部分和内容。HTML元素可以包含不同类型的内容&#xff0c;如文本、图片、链接、表格等&#xff0c;每种元素都有其特定的用途和语义。通过组合和嵌套不同的HTML元素&#xff0c;可以创建复杂的网页结构和布…

java之如何爬取本地数据(利用正则表达式)

public class RegexDemo4 {public static void main(String[] args) {String s"程序员学习java&#xff0c;""电话&#xff1a;181512516758&#xff0c;18512508907" "或者联系邮箱&#xff1a;boniuitcast.cn&#xff0c;""座机电话&…

脱胎于 S 语言的R语言,Ross Ihaka 和 Robert Gentleman 和社区的力量让 R 在学术界与研究机构放光彩

R语言从一门用于统计学教学的编程语言&#xff0c;发展成为全球数据科学领域的重要工具&#xff0c;离不开其强大的功能、丰富的社区资源和开源精神。这些都离不开Ross Ihaka 和 Robert Gentleman 和 社区的力量。 在1990年代初&#xff0c;新西兰奥克兰大学的统计学教授Ross I…

6.3.面向对象技术-设计模式

设计模式 设计模式创建型模型速记口诀 结构型设计模式速记口诀 行为型设计模式速记口诀 练习题 设计模式 上午2-4分&#xff0c;记忆点很多 要具体了解推荐看书籍《大话设计模式》 架构模式&#xff1a;软件设计中的高层决策&#xff0c;例如C/S结构就属于架构模式&#xff0…

Dopple Labs 选择 Zilliz Cloud 作为安全高效的向量数据库

一直以来&#xff0c;我都十分赞同采用通用的标准来评估机器学习领域的技术。向量数据库领域也是如此。Zilliz 发布的性能测试对我有着很大的帮助。 ——Sam Butler Dopple.AI 机器学习总监 01.Dopple AI简介 Dopple Labs Inc. 是 Dopple.AI 的原厂&#xff0c;通过提供创新…

关于进程间通信的练习

1> 使用有名管道实现,一个进程用于给另一个进程发消息,另一个进程收到消息后,展示到终端上,并且将消息保存到文件上 一份 create.c #include<myhead.h>int main(int argc, const char *argv[]) {//创建一个管道文件if(mkfifo("./linux",0664)-1){perror(&qu…

RabbitMQ docker安装

后台配置文件 rabbitmq:image: rabbitmq:latestcontainer_name: rabbitmqports:- "5672:5672" # RabbitMQ server port- "15672:15672" # RabbitMQ management console portenvironment:RABBITMQ_DEFAULT_USER: adminRABBITMQ_DEFAULT_PASS: admin 若要打…

磁盘无法访问的危机与解救:数之寻软件的数据恢复之旅

在数字时代&#xff0c;磁盘作为数据存储的核心&#xff0c;承载着我们的工作文档、珍贵照片、个人视频等无价之宝。然而&#xff0c;当您试图访问某个磁盘时&#xff0c;却遭遇了“磁盘无法访问”的提示&#xff0c;这无疑是一场突如其来的数据危机。本文将深入探讨磁盘无法访…

【Kubernetes】k8s集群资源调度

目录 一、k8s的List-Watch机制 二、scheduler的调度过程 三、指定节点调度Pod 1.通过nodeName调度Pod 2.通过节点标签选择器调度Pod 3.通过亲和性调度Pod 1&#xff09;节点亲和性 2&#xff09;Pod 亲和性 四、污点(Taint) 和 容忍(Tolerations) 1.污点(Taint) 2.…

运行pytorch报异常处理

一、问题现象及初步定位&#xff1a; 找不到指定的模块。 Error loading "D:\software\python3\Lib\site-packages\torch\lib\fbgemm.dll 此处缺少.dll文件&#xff0c;首先下载文件依赖分析工具 Dependencies https://github.com/lucasg/Dependencies/tree/v1.11.1 之后下…

【大模型学习】多模态大模型进行偏好优化

一、简介 训练模型以理解并预测人类偏好是一项复杂的任务。传统方法如SFT&#xff08;监督微调&#xff09;通常需要较高的成本&#xff0c;因为这些算法需要对数据进行特定标签的标注。偏好优化&#xff08;Preference Optimization&#xff09;作为一种替代方案&#xff0c;…

【多线程-从零开始-捌】阻塞队列,消费者生产者模型

什么是阻塞队列 阻塞队里是在普通的队列&#xff08;先进先出队列&#xff09;基础上&#xff0c;做出了扩充 线程安全 标准库中原有的队列 Queue 和其子类&#xff0c;默认都是线程不安全的 具有阻塞特性 如果队列为空&#xff0c;进行出队列操作&#xff0c;此时就会出现阻…