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

news2024/11/24 6:10:33

目录

  • 类的引入
  • 类的定义
  • 类的访问限定符和封装
  • 对象的实例化
  • 类对象的大小
  • 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.coloradmin.cn/o/766386.html

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

相关文章

【Linux系统 学习笔记】Linux线程互斥 线程安全 可重入 不可重入 死锁

目录 Linux 线程互斥进程线程间互斥相关背景和概念互斥量互斥量的接口互斥量实现原理探究 可重入与线程安全概念常见的线程不安全的情况常见的线程安全的情况常见不可重入的情况常见可重入的情况可重入与线程安全联系可重入与线程安全区别 死锁死锁四个必要条件避免死锁 Linux …

【代码随想录13】前 K 个高频元素

题目 给定一个非空的整数数组&#xff0c;返回其中出现频率前 k 高的元素。 示例 1: 输入: nums [1,1,1,2,2,3], k 2输出: [1,2] 示例 2: 输入: nums [1], k 1输出: [1] 提示&#xff1a; 你可以假设给定的 k 总是合理的&#xff0c;且 1 ≤ k ≤ 数组中不相同的元素…

黑客学习笔记(自学)

一、首先&#xff0c;什么是黑客&#xff1f; 黑客泛指IT技术主攻渗透窃取攻击技术的电脑高手&#xff0c;现阶段黑客所需要掌握的远远不止这些。 二、为什么要学习黑客技术&#xff1f; 其实&#xff0c;网络信息空间安全已经成为海陆空之外的第四大战场&#xff0c;除了国…

C#(六十)之Convert类 和 Parse方法的区别

Convert数据类型转换类&#xff0c;从接触C#开始&#xff0c;就一直在用&#xff0c;这篇日志坐下深入的了解。 Convert类常用的类型转换方法 方法 说明 Convert.ToInt32() 转换为整型(int) Convert.ToChar() 转换为字符型(char) Convert.ToString() 转换为字符串型(st…

优化CSS重置过程:探索CSS层叠技术的应用与优势

目录 下面是正文~~ CSS重置方法 方法的结合 合并方法的问题 通用移除样式 顺序很重要 CSS 优先级 我们的CSS特异性冲突 CSS Layers 来拯救 Sass 预处理器支持 浏览器支持 总结 这篇文章介绍了一种名为CSS层叠的技术&#xff0c;用于优化CSS重置过程。它解释了CSS重…

网络安全(黑客技术)最全面的学习笔记

学网络安全如何成为一名黑客呢&#xff1f; 整合了全知识点及学习框架&#xff0c;本篇零基础依然适用&#xff01; 本篇涵盖内容及其全面&#xff0c;强烈推荐收藏&#xff01; 一、学习网络安全会遇到什么问题呢&#xff1f; 1、学习基础内容多时间长 学习基础语言太多&…

基于MATLAB的无人机遥感数据预处理与农林植被性状估算教程

详情点击链接&#xff1a;基于MATLAB的无人机遥感数据预处理与农林植被性状估算前言 遥感技术作为一种空间大数据手段&#xff0c;能够从多时、多维、多地等角度&#xff0c;获取大量的农情数据。数据具有面状、实时、非接触、无伤检测等显著优势&#xff0c;是智慧农业必须采…

初中级PHP程序员如何进阶学习?

如果你是一个以PHP为主的开发人员&#xff0c;只会依赖现成的框架进行增删改查&#xff0c;想提高自己又不知道从何下手&#xff0c;你可以花点时间研究一下我这个开源项目&#xff1a;酷瓜云课堂&#xff0c;这个项目以PHPJS 为主&#xff0c;负责主要的业务逻辑&#xff0c;部…

基于遗传算法的新能源电动汽车充电桩与路径选择MATLAB程序

主要内容&#xff1a; 根据城市间的距离&#xff0c;规划新能源汽车的行驶路径。要求行驶距离最短。 部分代码&#xff1a; %% 加载数据 %%遗传参数 load zby;%个城市坐标位置 NIND50; %种群大小 MAXGEN200; Pc0.9; %交叉概率 Pm0.2; %变异概率 GGAP0.…

初识Redis——Redis概述、安装、基本操作

目录 一、NoSQL介绍 1.1什么是NoSQL 1.2为什么会出现NoSQL技术 1.3NoSQL的类别 1.4传统的ACID是什么 1.5 CAP 1.5.1 经典CAP图 1.5.4 什么是BASE 二、Redis概述 2.1 什么是Redis 2.2 Redis能干什么 2.3 Redis的特点 2.4 Redis与memcached对比 2.5 Redis的安装 2.6 Docker安装 三…

基于Redisson的Redis结合布隆过滤器使用

一、场景 缓存穿透问题 一般情况下&#xff0c;先查询Redis缓存&#xff0c;如果Redis中没有&#xff0c;再查询MySQL。当某一时刻访问redis的大量key都在redis中不存在时&#xff0c;所有查询都要访问数据库&#xff0c;造成数据库压力顿时上升&#xff0c;这就是缓存穿透。…

【Python基础】- break和continue语句

在Python中&#xff0c;break和continue是用于控制循环语句的特殊关键字。 break语句用于跳出当前的循环&#xff08;for循环或while循环&#xff09;&#xff0c;并继续执行紧接着的循环外的代码。它通常用于满足某个条件时提前结束循环。例如&#xff0c;考虑以下示例&#…

《啊哈算法》第三章--枚举 与 暴力

文章目录 前言一、坑爹的奥数二、炸弹人三、火柴棍等式四、全排列总结 前言 前面我们学习了排序和栈 队列 链表&#xff0c;本节就学习暴力枚举的思想。 一、坑爹的奥数 题目1 □3 x 6528 3□ x 8256&#xff0c;在 □ 里填入相同数字使等式成立 代码如下 #include<ios…

PDF在线转PPT,不用下载软件网页在线即可转换!

PDF是我们经常在办公中使用的文件格式&#xff0c;它的兼容性和安全性使得它成为了传输文件的首选。而PPT则是我们常用的演示文稿格式&#xff0c;无论是在学校还是在公司&#xff0c;我们都需要制作演讲和汇报的PPT文件。由于这两种文件格式的重要性&#xff0c;我们经常需要进…

python的魔法函数

一、介绍 在Python中&#xff0c;魔法函数是以双下划线__开头和结尾的特殊函数。它们在类定义中用于实现特定的行为&#xff0c;例如运算符重载、属性访问、迭代等。 以下是一些常见的Python魔法函数&#xff1a; __init__: 这是一个特殊的构造函数&#xff0c;在创建类的实例…

JDBC中的Statement,PreparedStatement和CallableStatement

一旦获得连接&#xff0c;我们就可以与数据库进行交互。JDBC Statement、 CallableStatement 和 PreparedStatement 接口定义了方法和属性&#xff0c;这些方法和属性使您能够发送 SQL 命令并从数据库接收数据。 它们还定义了有助于弥合数据库中使用的 Java 和 SQL 数据类型之…

【阿Q送书第二期】《高并发架构实战:从需求分析到系统设计》

#挑战Open AI&#xff01;马斯克宣布成立xAI&#xff0c;你怎么看&#xff1f;# 文章目录 你想成为架构师嘛&#xff1f;架构经验高并发高并发架构实战特点值得推荐赠书规则 你想成为架构师嘛&#xff1f; 很多软件工程师的职业规划是成为架构师&#xff0c;但是要成为架构师很…

C语言-ubuntu下的命令

目录 linux命令 【1】打开关闭终端 【2】终端 【3】ls命令 【4】cd 切换路径 【5】新建 【6】删除 【7】复制 【8】移动 【9】常用快捷键 【10】vi编辑器 【11】简单编程步骤 任务&#xff1a; linux命令 【1】打开关闭终端 打开终端&#xff1a; 1. 直接点击 …

【1】Vite+Vue3 登录功能

一、介绍 在当今前端开发的领域里&#xff0c;快速、高效的项目构建工具以及使用最新技术栈是非常关键的。ViteVue3 组合为一体的项目实战示例专栏将带领你深入了解和掌握这一最新的前端开发工具和框架。 作为下一代前端构建工具&#xff0c;Vite 在开发中的启动速度和热重载…

sqlserver 获取根据特定符号分割字符串

CREATE function Get_StrArrayStrOfIndex (str varchar(1024), --要分割的字符串split varchar(10), --分隔符号index int --取第几个元素 ) returns varchar(1024) as begindeclare location intdeclare start intdeclare next intdeclare seed intset strltrim(rtrim(str))…