C++——类和对象1

1.类的定义

1.1 类定义格式

  • class为定义类的关键字,Stack为类的名字,{ }中的内容是类的主题为了,注意类定义结束时后面的分号不能省略。类体中的内容称为类的成员:类中的变量称为类的属性或成员变量;类中的函数称为类的方法或者成员函数。
  • 为了区分成员变量,一般习惯上成员变量会加上一个特殊标识,如成员变量前面或者后面加上_或者是m开头,注意C++中这个并不是强制的,只是一些惯例,具体看公司的要求。
  • C++中struct也可以定义类,C++兼容C中的struct的用法,同时struct升级成了类,明显的变化是struct中可以定义函数,一般情况下我们还是推荐用class定义类。
  • 定义在类里面的成员函数默认为inline。
//定义一个类
class Date
{void Init(int year, int month, int day){_year = year;_month = month;_day = day;}//为了好区分到底是成员变量还是参数,一般在成员变量前面加上_或是mint _year;int _month;int _day;};//C++中兼容C语言中的struct的用法
typedef struct ListNodeC
{struct ListNodeC* next;int val;
}ListNode;//在C++中struct升级为类了
//不再需要typedef,ListNodeCPP就可以代表类型struct ListNodeCPP
{void Init(int x){next = nullptr;val = x;}ListNodeCPP* next;int val;
};int main()
{ListNode node1;struct ListNodeC node2;ListNodeCPP node3;return 0;
}//class类如果没有访问限定符的修饰,默认是private
//struct类如果没有访问限定符的修饰,默认是publicclass Stack 
{//默认在类里面的成员函数都是内联,但是展不展开是编译器的事情
public:void Init(int n=4){arr = (int*)malloc(sizeof(int) * n);if (arr == nullptr){perror("realloc fail!\n");return;}capacity = n;top = 0;}void Push(int x){// ...扩容arr[top++] = x;}int Top(){assert(top > 0);return arr[top - 1];}void Destroy(){free(arr);arr = nullptr;top = capacity = 0;}private:int* arr;size_t top;size_t capacity;
};int main()
{Date d1;//error C2248 : “Date::Init” : 无法访问 private 成员(在“Date”类中声明)d1.Init(2024, 11, 11);//如果想要Init被访问在Date类加上public,就可以被访问了//或者是将class类改为struct类Stack st1;st1.Init(10);st1.Push(1);st1.Push(1);st1.Push(1);//int top = st1->arr[st1->top - 1]; C中实现top方法会更加自由,不能进行更好的管理int top=st1.Top();//C++的模式下会更加规范,会更好的进行管理,因为私有成员不能访问,只能访问公有函数st1.Destroy();return 0;
}

1.2 访问限定符

  • C++一种实现防撞的方式,用类将对象的属性和方法结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用。
  • publi修饰的成员在类外可以直接被访问;protected 和 private 修饰的成员在类外不能直接被访问,protected 和 private 是一样的,在之后的继承章节才能体现出它们的区别。
  • 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现为止,如果后面没有访问限定符,作用域就到 } 即类结束。
  • class定义成员没有被访问限定符修饰时默认为private,struct默认为public。
  • 一般成员变量都会被限制为private/protected,需要给别人使用的成员函数会为public。

1.3 类域

  • 类定义一个新的作用域,类的所有成员都在类的作用域中,在类体外定义成员时,需要使用 :: 作用域操作符指明成员属于哪个类域。
  • 类域影响的是编译的查找规则,下面程序中Init如果不指定类域Stack,那么编译器就把Init当成全局函数,那么编译时,找不到array等成员的声明/定义在哪里,就会报错。指定类域Stack,就是知道Init是成员函数,当前域找不到的array等成员,就会到类域中去查找。
//Stack.h#include<iostream>
using namespace std;
class Stack
{
public://成员函数  唯一不同的:只声明不定义void Init(int n = 4);//缺省参数:(int n = 4)不能在定义和声明的地方同时给,只能在声明的地方给
private:int* arr; //声明——不开空间size_t top;size_t capacity;
};
//Stack.cpp#define _CRT_SECURE_NO_WARNINGS 1#include"Stack.h"//声明和定义分离,需要指定类域
//加上 Stack:: 类域,就不会报错,arr会到Stack这个域里面去找,否则arr不知道去哪里找 
//不加上Stack:: 类域,“arr”: 未声明的标识符//error C2065 : “capacity”: 未声明的标识符//error C2065 : “top”: 未声明的标识符
void Stack::Init(int n)  
{//这里的Init还是符合只能在类里面使用,不是在类外面,只是类的声明和定义分开了而已//还是符合私有的在类里面使用不能在类外面使用arr = (int*)malloc(sizeof(int) * n);if (arr == nullptr){perror("realloc fail!\n");return;}capacity = n;top = 0;
}
//Test.cppint main()
{Stack st;st.Init();return 0;
}

类域的作用:

不同的域中可以定义同样的函数,隔离出了类和类之间的命名冲突

2. 实例化

2.1 实例化概念

  • 用类型在物理内存中创建对象的过程,称为类实例化出的对象。
  • 类是对象进行一种抽象描述,是一个模型一样的东西,限定了类有哪些成员变量,这些成员变量只是声明,没有分配空间,用类实例化出对象时,才会分配空间。
  • 一个类可以实例化出多个对象,实例化出的对象占用实际的物理空间,存储类成员变量。例如:
  • 类实例化处对象就像是现实中使用建筑设计图建造出房子,类就像是设计图,设计图规划了多少个房间,房间大小功能,但是没有实体的建筑存在,也不能住人,用设计图修建出房子,房子才能住人。同样类就像是设计图一样,不能存储数据,实例化出的对象分配物理内存存储数据。
//Stack.h#include<iostream>
#include<assert.h>
using namespace std;
class Stack
{
public://成员函数  唯一不同的:只声明不定义void Init(int n = 4);//缺省参数:(int n = 4)不能在定义和声明的地方同时给,只能在声明的地方给
//private:int* arr; //声明——不开空间size_t top;size_t capacity;
};
//类实例化对象——对象是实实在在需要内存的,在内存上面存储数据的#include"Stack.h"
int main()
{//类实例化对象// 对象定义,也就是成员变量的定义,因为成员变量是对象的一部分//定义——开空间,定义不等于初始化Stack st1; //这就是类实例化对象Stack st2; //这就是类实例化对象Stack st3; //这就是类实例化对象//st1、st2、st3才有空间//st1.top = 1; (将成员变量改为public,否则不能访问)虽然这样是正确的,因为定义之后有空间了,        但是这样的写法不规范,因为top是私有的,//要想改变的话,通过公有的函数来进行改变,这就使C++更加规范//Stack::top = 1;//top没有空间,不能这样写//对象不一定有声明,但是类是必须要有声明的,如果类没有声明的话,怎么能用类去实例化对象呢return 0;
}

2.2 对象大小

对象的大小 —— 只计算成员变量,不计算成员函数,遵循内存对齐原则

内存对齐规则

  • 第一个成员在与结构体偏移量为0的地址处。
  • 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
  • 注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
  • VS中默认的对齐数是8
  • 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
  • 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是在所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
#include"Stack.h"
int main()
{//定义——开空间,定义不等于初始化Stack st1; //类实例化对象Stack st2; Stack st3; cout << sizeof(st1) << endl;    //12  x86cout << sizeof(Stack) << endl;  //12  x86st1.top = 1;  //(将成员变量改为public,否则不能访问)st1.Init();  // call 函数地址(不需要存在对象里面)st2.top = 2; //(将成员变量改为public,否则不能访问)st2.Init();return 0;
}

运行结果:

为什么运行出的结果都是 12 ???

调试 ---> 转到反汇编

通过调试,转到反汇编,可以看见st1.Init(); 和 st2.Init();调用的都是相同的地CA104Bh)
而 st1 和 st2 各自开空间存储自己的成员变量,指向的不是同一块地址
成员函数的地址不在对象里面,那成员函数的地址又是声明时候确定的呢??
函数的地址是在编译的时候确定的

所以:

对象的大小 —— 只计算成员变量,不计算成员函数,遵循内存对齐原则

// 计算一下A/B/C实例化的对象是多大?
class A
{
public:void Print(){cout << _ch << endl;}
private:char _ch;int _i;
};class B
{
public:void Print(){//...}
};class C
{
};int main()
{A a;B b;C c;cout << sizeof(a) << endl;  //8cout << sizeof(b) << endl;  //1cout << sizeof(c) << endl;  //1return 0;
}

运行结果:

分析:

  1. 类A遵循内存对齐原则,所以大小为:8
  2. B和C一样:成员函数不占空间,为什么是 1 ???

系统设的机制,B和C都是空类,虽然B有成员函数,成员函数不占空间,1 是为了占位,表示对象存在过,如果1个字节都不给的话,怎么能表示这个对象定义出来了呢?如果一个字节都不开,地址用怎样给呢?这里开一个字节不存储有效数据,纯粹是为了占位,表示这个对象还在,那为什么不是 2、3呢?肯定是越少越好嘛,节省空间

3. this 指针 

  • Date类的d1和d2涉及到的Init和Print函数都是相同的空间,但是打印出来的值为什么是不同的呢??难道是因为 Init 传了参数,Print 没有传参数 ??? —— 答案不是这样的,引出 this 指针
  • 编译器编译后,类的成员函数默认都会在形参第⼀个位置,增加⼀个当前类类型的指针,叫做 this 指针。实际上:void Init(const Date* this,int year, int month, int day)
  • 类的成员函数中访问成员变量,本质都是通过this指针访问的,如Init函数中给_year赋值,this- >_year = year;
  • C++规定不能在实参和形参的位置显示的写this指针(编译时编译器会处理),但是不能在实参或形参的位置显示的加,但是可以在函数里面使用 why????  ---->之后会提到
class Date
{
public://实际上:void Init(const Date* this,int year, int month, int day)void Init(int year, int month, int day){this->_year = year; //_year访问的是344行的_year吗?不是,因为没空间,编译语法设计的方面//用一个变量和函数要找到他的出处,出处可以是定义,也可以是声明,意味着这个变量和和函数是你自己定义的,在编译的时候找到他的声明就够了this->_month = month; //但是在打印和初始化设计的是实实际际的空间this->_day = day;}//实际上:void Print(const Date* this)void Print(){cout << _year << "/" << _month << "/" << _day << endl;}//为了好区分到底是成员变量还是参数,一般在成员变量前面加上_或是m
private:int _year;  //344行int _month;int _day;};int main()
{Date d1;Date d2;d1.Init(2024, 7,3);d2.Init(2024, 4,27);//  d1.Print(&d1);  d1.Print();     //    2024 / 7 / 3//  d2.Print(&d2); d2.Print();     //    2024 / 4 / 27}

分析:    

 这里的d1和d2涉及到的Init和Print函数都是相同的空间,但是打印出来的值为什么是不同的呢??
难道是 Init 传了参数,Print 没有传参数 ??? —— 引出 this 指针
隐含的this 指针 —— 隐含的指的是:编译器会在成员函数的实参和形参的位置加一个this指针的参数
void Init(int year, int month, int day)看见的是3个参数,实际是4个参数
void Print()看见的是0个参数,实际上是1个参数,实际上是编译器加的
void Init(const Date* this,int year, int month, int day)
void Print(const Date* this)
在变量访问的地方会在成员变量前面自动加上 this->
所以在调用同一个函数,访问到了不同的变量
在调用d1.Print(); 和 d2.Print();的时候分别将d1和d2的地址传过去了
所以用this指针在调用同一个函数,访问到了不同的变量,但是不能在实参或形参的位置显示的加,但是可以在函数里面使用 why????  ---->之后会提到

相应题目:

1.下面程序编译运行结果是()

A.编译报错                             B.运行崩溃                                   C.正常运行

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

运行结果:

可以看见代码并没有问题,正常运行,所以选C

分析:

p->Print(); 

从汇编的角度:call Print(地址),地址是在编译的时候确定的
虽然这里有一个 -> 但是却没有解引用,不是说看见箭头就是解引用,要去看它实际转换成的动作是什么,实际的动作是调用函数,在不在对象里面??—不在

p的作用是:
1.编译器调用成员函数,编译器要知道Print()成员函数从哪里调的,用对象的指针去调用就知道他是他(类)的成员函数,编译的时候符合语法找Print的出处,就到类里面去找
2.调用成员函数要传递this指针,相当于将p传给了this,但是this指针是一个空指针,是不会报错的
所以从始至终都有空指针,但是并没有进行解引用,所以不会报错

2.下面程序编译运行结果是()

A.编译报错                             B.运行崩溃                                   C.正常运行

class A
{
public:void Print(){cout << "A::Print()" << endl;cout << _a << endl;  //  cout << this->_a << endl;  空指针的访问 }
private:int _a;
};int main()
{A* p = nullptr;p->Print();return 0;
}

运行结果:

代码和上一道题几乎一样,但是可以看见运行有问题,只是因为对空指针进行了解引用操作

分析:

this 指针是一个空指针,用代码来验证一下:

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

运行结果:

上面的运行结果验证了 this 确实是空指针,所以下面的代码会报错,因为不能对空指针进行进引用操作

cout << _a << endl;

cout << this->_a << endl;  空指针的访问 

考点:

1.对象里面没有存储成员函数的指针
2.隐含的传this指针,谁传过去?对象——就传对象的地址,指针——直接就是指针传过去,成员变量前面要加this:this->_a

3.所以这道题选A

上面的两个例子初不初始化都无所谓,大不了就是随机值,跟初始化没有关系

3.this指针存在内存哪个区域中()

A.栈  B.堆   C.静态区  D.常量区    E.对象里面

分析:

所以这道题 A.栈

拓展:

4. C实现Stack核心代码  和  C++实现Satck核心代码对比:

面向对象显著的三大特性:封装、继承、多态(当然还有其它的特性),下面是对封装的初步介绍:

  • C+中的数据和函数都放到了类里面,通过访问限定符进行了限制,不能再随意的通过对象直接修改数据,这就是C++封装的一种体现,这个是最重要的变化。这里的封装的本质是一种更严格规范的管理,避免出现访问修改的问题。
  • C++中会有一些相对方便的语法,比如Init给的缺省参数会方便很多,成员函数每次不需要传对象地址,因为this指针隐含的传递了,方便了许多。
C语言面向的过程,更加注重过程,更注重如何实现某个函数;而C++面向的是对象,更关注类和类、对象和对象之间的关系,面向对象像是在更关注模拟现实世界

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

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

相关文章

动手学Agent:Agent设计模式——构建有效Agent的7种模型

Agent本身的定义也不是绝对的&#xff0c;从LLM到最高等级的Agent&#xff0c;中间是有大量灰度地带的&#xff0c;在Anthropic看来&#xff0c;Agent可以以多种方式定义&#xff0c;有些人将完全自主系统定义为Agent&#xff0c;而另一些团队则将预定义的工作流程定义为Agent。…

Windows 下 .venv 激活脚本深度定制:同时注入 PyTorch 调试日志与国内网络加速通道——从“能跑”到“好调”的完整工程化方案

Windows 下 .venv 激活脚本深度定制&#xff1a;同时注入 PyTorch 调试日志与国内网络加速通道 ——从“能跑”到“好调”的完整工程化方案 一、为什么非得改激活脚本&#xff1f; 重复劳动最耗时 每次打开终端都要敲四五行 set/export&#xff0c;人脑就是不可靠的剪贴板。 环…

[BX]和loop指令,debug和masm汇编编译器对指令的不同处理,循环,大小寄存器的包含关系,操作数据长度与寄存器的关系,段前缀

[bx]是什么[bx]这个表达方式和[0]很像&#xff0c;他们俩的功能也很像。之前就提到了&#xff0c;[0]表示一个内存单元&#xff0c;他的偏移地址是0。从这边我们可以引出内存单元的定义&#xff1a;要有内存单元的地址&#xff0c;要有内存单元的长度&#xff08;类型&#xff…

域格YM310 X09移芯CAT1模组HTTPS连接服务器

HTTPS连接服务器 本文档介绍了HTTPS连接服务器的大致流程&#xff0c;测试服务器为httpbin.org。 HTTPS连接服务器流程 创建证书文件 创建一个文件 ATFSCREATE<filename>参数&#xff1a;<filename> 文件名 写入CA证书 ATFSWRITE<filename>,<mode&…

【ManiSkill】常见envs学习笔记

1. StackCube-v1 用于模拟机器人在桌面场景中将红色立方体&#xff08;cubeA&#xff09;堆叠到绿色立方体&#xff08;cubeB&#xff09;上的操作。该任务强调精确抓取、放置和稳定性控制。成功条件包括红色立方体稳定堆叠在绿色立方体上且不被机器人抓取。 参数 (Arguments…

Java 网络编程全解析

前言&#xff1a;网络编程的意义与价值 前言&#xff1a;网络编程的意义与价值 在当今互联网时代&#xff0c;网络编程是软件开发的核心技能之一。无论是桌面应用、移动应用还是企业级系统&#xff0c;几乎都需要与网络交互。Java 作为一门跨平台的编程语言&#xff0c;提供了完…

HarmonyOS应用拉起系列(三):如何直接拉起腾讯/百度/高德地图进行导航

在鸿蒙应用开发中&#xff0c;经常需要跳转第三方地图应用&#xff08;如 腾讯地图、百度地图、高德地图&#xff09;进行导航。无论是出行类 App、物流类 App&#xff0c;还是线下活动类应用&#xff0c;都存在“跳转地图导航”的实际需求。写完HarmonyOS应用拉起系列一和二后…

PCGrad解决多任务冲突

论文解读&#xff1a;"Gradient Surgery for Multi-Task Learning" 1. 论文标题直译 Gradient Surgery: 梯度手术for Multi-Task Learning: 应用于多任务学习 合在一起就是&#xff1a;为多任务学习量身定制的梯度手术。这个名字非常形象地概括了它的核心思想。 …

Nvidia显卡架构解析与cuda应用生态浅析

文章目录 0. Nvidia显卡简介 一、主要显卡系列 二、主要GPU架构与代表产品 1.main 1.1 CUDA 13.0 的重大变化 1.2 V100 的硬件短板已显现 1.3 这意味着什么? 1.4 写在后面 彩蛋:V100 0. Nvidia显卡简介 一、主要显卡系列 GeForce 系列(消费级) 用途:游戏、创作、日常图形…

开发指南:使用 MQTTNet 库构建 .Net 物联网 MQTT 应用程序

一、背景介绍 随着物联网的兴起&#xff0c;.Net 框架在构建物联网应用程序方面变得越来越流行。微软的 .Net Core 和 .Net 框架为开发人员提供了一组工具和库&#xff0c;以构建可以在 Raspberry Pi、HummingBoard、BeagleBoard、Pine A64 等平台上运行的物联网应用程序。 MQT…

突破性能瓶颈:基于腾讯云EdgeOne的AI图片生成器全球加速实践

1. 项目背景与挑战 1.1 开发背景 随着AIGC技术爆发&#xff0c;我们团队决定开发一款多模型支持的AI图片生成器&#xff0c;主要解决以下痛点&#xff1a; 不同AI模型的参数规范不统一生成结果难以系统化管理缺乏企业级的安全水印方案全球用户访问延迟高&#xff0c;中国用户…

一、Java 基础入门:从 0 到 1 认识 Java(详细笔记)

1.1 Java 语言简介与发展历程 Java 是一门面向对象的高级编程语言&#xff0c;以“跨平台、安全、稳定”为核心特性&#xff0c;自诞生以来长期占据编程语言排行榜前列&#xff0c;广泛应用于后端开发、移动端开发、大数据等领域。 1.1.1 起源与核心人物 起源背景&#xff1…

uniapp:根据目的地经纬度,名称,唤起高德/百度地图来导航,兼容App,H5,小程序

1、需要自行申请高德地图的key,配置manifest.json 2、MapSelector选择组件封装 <template><view><u-action-sheet :list="mapList" v-model="show" @click="changeMap"></u-action-sheet></view> </template&…

我对 WPF 动摇时的选择:.NET Framework 4.6.2+WPF+Islands+UWP+CompostionApi

目录 NET Framework 4.6.2的最大亮点 为什么固守462不升级 WPF-开发体验的巅峰 为什么对WPF动摇了 基于IslandsUWP的滤镜尝试 总结 NET Framework 4.6.2的最大亮点 安全性能大提升&#xff1a; 默认启用TLS1.2协议&#xff0c;更安全&#xff0c;它为后续的版本提供了重…

SpringBoot大文件下载失败解决方案

SpringBoot大文件下载失败解决方案 后端以文件流方式给前端接收下载文件,文件过大时出现下载失败的情况或者打开后提示文件损坏,实际是字节未完全读取写入。 针对大文件下载失败的情况,以下是详细的解决方案: 大文件下载失败的主要原因 内存溢出:一次性加载大文件到内存…

torch.gather

torch.gather 介绍 torch.gather(input, dim, index, *, sparse_gradFalse, outNone) → Tensor 沿由 dim 指定的轴收集值。 对于三维张量&#xff0c;输出按如下方式确定&#xff1a; out[i][j][k] input[index[i][j][k]][j][k] # 如果 dim 0 out[i][j][k] input[i][i…

Golang | http/server Gin框架简述

http/server http指的是Golang中的net/http包&#xff0c;这里用的是1.23.10。 概览 http包的作用文档里写的很简明&#xff1a;Package http provides HTTP client and server implementations. 主要是提供http的客户端和服务端&#xff0c;也就是能作为客户端发http请求&a…

Vision Transformer (ViT) :Transformer在computer vision领域的应用(三)

Experiment 上来的一段话就概括了整章的内容。 We evaluate the representation learning capabilities of ResNet, Vision Transformer (ViT), and the hybrid. 章节的一开头就说明了,对比的模型就是 ResNet,CNN领域中的代码模型。 ViT。 上一篇中提到的Hybrid模型,也就是…

5-12 WPS JS宏 Range数组规范性测试

Range()数组是JS宏中不缺少的组成部分,了解Range()数组的特性必不可少,下面我们一起测试一下各种Range()数组。 1.Range()数组特性 单元格区域:Range("a2:m2")与Range("a2","m2")的类型都是:Range/Object,功能都为单元格区域,功能…

uniapp微信小程序保存海报到手机相册canvas

在uniapp中实现微信小程序保存海报到手机相册&#xff0c;主要涉及Canvas绘制和图片保存。以下是关键步骤和代码示例&#xff1a; 一、关键代码展示&#xff1a; 1. 模板配置&#xff1a;页面展示该海报&#xff0c;可直接查看&#xff0c;也可下载保存到手机相册&#xff0c;h…