C++基础---类和对象(上)

news2024/10/1 8:00:34

1.类的定义

C++程序设计允许程序员使用类(class)定义特定程序中的数据类型。这些数据类型的实例被称为对象 ,这些实例可以包含程序员定义的成员变量、常量、成员函数,以及重载的运算符。语法上,类似C中结构体(struct)的扩展,C中结构体不能包含函数以及重载的运算符。

1.1类的定义格式

  • class为定义类的关键字,Stack为类的名字,{}中为类的主体,注意类定义结束时后面分号不能省略。类体中内容称为类的成员:类中的变量称为类的属性成员变量; 类中的函数称为类的方法或者成员函数
  • 为了区分成员变量,一般习惯上成员变量会加一个特殊标识,如成员变量前面或者后面加_ 或者 m开头,注意C++中这个并不是强制的,只是一些惯例,具体看公司的要求。
  • C++中struct也可以定义类,C++兼容C中struct的用法,同时struct升级成了类,明显的变化是
  • struct中可以定义函数,一般情况下我们还是推荐用class定义类。
  • 定义在类面的成员函数默认为inline
#include<iostream>
#include<assert.h>

using namespace std;

class Stack {
public:
	// 成员函数
	void Init(int n = 4)
	{
		array = (int*)malloc(sizeof(int) * n);
		if (nullptr == array)
		{
			perror("malloc fail");
			return;
		}
		capacity = n;
		top = 0;
	}
	void Push(int x)
	{
		if (top == capacity)
		{
			int* tmp = (int*)malloc(sizeof(int) * 2 * capacity);
			if (tmp != NULL)
			{
				array = tmp;
				capacity = 2 * capacity;
			}
		}
		array[top++] = x;
	}
	int Top()
	{
		assert(top > 0);
		return array[top - 1];
	}
	void Destroy()
	{
		free(array);
		array = nullptr;
		top = capacity = 0;
	}
private:
	// 成员变量
	int* array;
	size_t capacity;
	size_t top;

};
int main()
{
	Stack test;
	test.Init();
	test.Push(1);
	test.Push(2);
	test.Push(3);
	cout << test.Top() << endl;
	test.Destroy();
	return 0;
}
#include<iostream>
using namespace std;
class Date
{
public:
	void date(int year, int month, int day)
	{
		// 为了区分成员变量,一般习惯上成员变量
		// 会加一个特殊标识,如_ 或者 m开头
		_year = year;
		_month = month;
		_day = day;
		cout << _year << '/' << _month << '/' << _day << endl;
	}

	int _year;
	int _month;
	int _day;
};
int main()
{
	Date now;
	now.date(2024, 9, 28);
	return 0;
}
#include<iostream>
using namespace std;
// C++升级struct升级成了类
// 1、类里面可以定义函数
// 2、struct名称就可以代表类型

// C++兼容C中struct的用法
typedef struct ListNodeC
{
	struct ListNodeC* next;
	int val;
}LTNode;
// 不再需要typedef,ListNodeCPP就可以代表类型
struct ListNodeCPP
{
	void Init(int x)
	{
		next = nullptr;
		val = x;
	}
	ListNodeCPP* next;
	int val;
};
int main()
{

	return 0;
}

1.2访问限定符

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

1.3类域

  • 类定义了一个新的作用域,类的所有成员都在类的作用域中,在类体外定义成员时,需要使用 :: 作用域操作符指明成员属于哪个类域。
  • 类域影响的是编译的查找规则,下面程序中Init如果不指定类域Stack,那么编译器就把Init当成全局函数,那么编译时,找不到array等成员的声明/定义在哪里,就会报错。指定类域Stack,就是知道Init是成员函数,当前域找不到的array等成员,就会到类域中去查找。
#include<iostream>
using namespace std;
class Stack
{
public:
	// 成员函数声明
	void Init(int n = 4);
private:
	// 成员变量
	int* array;
	size_t capacity;
	size_t top;
};
// 声明和定义分离,需要指定类域
// 成员函数定义
void Stack::Init(int n)
{
	array = (int*)malloc(sizeof(int) * n);
	if (nullptr == array)
	{
		perror("malloc申请空间失败");
		return;
	}
	capacity = n;
	top = 0;
}
int main()
{
	Stack st;
	st.Init();
	return 0;
}

2.实例化

2.1实例化概念

  • 用类类型物理内存中创建对象的过程,称为类实例化出对象
  • 类是对象进行一种抽象描述,是一个模型一样的东西,限定了类有哪些成员变量,这些成员变量只是声明没有分配空间用类实例化出对象时,才会分配空间
  • 一个类可以实例化出多个对象,实例化出的对象占用实际的物理空间,存储类成员变量。打个比方:类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图,设计图规划了有多少个房间,房间大小功能等,但是并没有实体的建筑存在,也不能住人,用设计图修建出房子,房子才能住人。同样类就像设计图一样,不能存储数据,实例化出的对象分配物理内存存储数据。

#include<iostream>
using namespace std;
class Date
{
public:
	void date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
		cout << _year << '/' << _month << '/' << _day << endl;
	}
	//这里只是声明,不是定义,没有开空间
	int _year;
	int _month;
	int _day;
};
int main()
{
	// 实例化出 d1 d2对象
	Date d1;
	Date d2;
	d1.date(2022, 9, 28);
	d2.date(2023,9,28);
	return 0;
}

 2.2对象大小

分析一下类对象中哪些成员呢?类实例化出的每个对象,都有独立的数据空间,所以对象中肯定包含成员变量,那么成员函数是否包含呢?首先函数被编译后是一段指令,对象中没办法存储,这些指令存储在一个单独的区域(代码段),那么对象中非要存储的话,只能是成员函数的指针。再分析一下,对象中是否有存储指针的必要呢,Date实例化d1和d2两个对象,d1和d2都有各自独立的成员变量_year/_month/_day存储各自的数据,但是d1和d2的成员函数Init/Print指针却是一样的,存储在对象中就浪费了。如果用Date实例化100个对象,那么成员函数指针就重复存储100次,太浪费了。这里需要再额外哆嗦一下,其实函数指针是不需要存储的函数指针是一个地址,调用函数被编译成汇编指令[call 地址], 其实编译器在编译链接时,就要找到函数的地址,不是在运行找,只有动态多态是在运行时找,就需要存储函数地址。

上面我们分析了对象中只存储成员变量,C++规定类实例化的对象也要符合内存对齐的规则:

  • 第一个成员在与结构体偏移量为0的地址处
  • 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处
  • 注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值
  • VS中默认的对齐数为8
  • 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍
  • 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍
#include<iostream>
using namespace std;
// 计算一下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;
	cout << sizeof(b) << endl;
	cout << sizeof(c) << endl;
	return 0;
}

 上面的程序运行后,没有成员变量的B和C类对象的大小是1,为什么没有成员变量还要给1个字节呢?因为如果一个字节都不给,怎么表示对象存在过呢!所以这里给1字节,纯粹是为了占位标识对象存在

3.this指针

  • Date类中有 Init 与 Print 两个成员函数,函数体中没有关于不同对象的区分,那当d1调用Init和Print函数时,该函数是如何知道应该访问的是d1对象还是d2对象呢?那么这里就要看C++给了一个隐含的this指针解决这里的问题
  • 编译器编译后,类的成员函数默认都会在形参第一个位置,增加一个当前类类型的指针,叫做this指针。比如Date类的Init的真实原型为, void Init(Date* const this, int year,int month, int day)
  • 类的成员函数中访问成员变量本质都是通过this指针访问的,如Init函数中给_year赋值, this->_year = year;
  • C++规定不能在实参和形参的位置显示的写this指针(编译时编译器会处理),但是可以在函数体内显示使用this指针。
#include<iostream>
using namespace std;
class Date
{
public:
	//void Init(Date* const this, int year, int month, int day)
	void Init(int year, int month, int day)
	{
		// 编译报错:error C2106: “=”: 左操作数必须为左值
		// this = nullptr;
		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类实例化出对象d1和d2
	Date d1;
	Date d2;
	// d1.Init(&d1, 2024, 3, 31);
	d1.Init(2024, 3, 31);
	d1.Print();
	d2.Init(2024, 7, 5);
	d2.Print();
	return 0;
}

4.经典题目分析

4.1

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

 虽然A类型的对象被实例化成nullptr,但是并没有对其解引用,所以不会有问题,程序正常运行。

4.2

#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;
}

同样的,A类型的p对象还是被实例化成nullptr,但是在调用Print函数的时候,访问了类A中的成员变量,就对p指针解引用了,传参给this指针,然后this指向_a的时候,发生了解引用,运行崩溃。

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

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

相关文章

【机器学习】任务六:分类算法(支持向量机(SVM)在线性可分与不可分数据中的应用与可视化分析)

目录 1.知识简介 2.SVM 支持向量机模型训练与可视化报告 2.1 导入本项目所需的模块和包 &#xff08;1&#xff09;目的 &#xff08;2&#xff09;代码实现 &#xff08;3&#xff09;代码解释 2.2 随机生成线性可分的数据并可视化 &#xff08;1&#xff09;目的 &a…

[数据集][目标检测]电力场景防震锤缺陷检测数据集VOC+YOLO格式705张1类别

重要说明&#xff1a;防震锤缺陷图片太难找&#xff0c;数据集里面存在大量单一场景图片&#xff0c;请仔细查看图片预览谨慎下载&#xff0c;此外数据集均为小目标检测&#xff0c;如果训练map偏低属于正常现象 数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径…

特殊的 BASE64 1

这个一看题目就是base64编码表被改了. 知识点&#xff1a; Base64编码使用一个包含64个字符的字符集&#xff0c;这些字符分别是&#xff1a; 大写字母A-Z小写字母a-z数字0-9特殊字符和/ 这些字符分别对应64个不同的6位二进制数。在补零后形成的8位字节中&#xff0c;实际使用…

c++primier第十二章类和动态内存

本章内容包括&#xff1a; 对类成员使用动态内存分配隐式和显式地复制构造函数隐式和显式地重载赋值操作符在构造函数中使用new所必须完成的工作使用静态类成员 将布局new操作符用于对象使用指向对象的指针实现队列抽象数据类型(ADT) 动态内存和类 复习范例和静态类成员 首…

ASP.NET Zero 多租户介绍

ASP.NET Zero 是一个基于 ASP.NET Core 的应用程序框架&#xff0c;它提供了多租户支持&#xff0c;以下是关于 ASP.NET Zero 多租户的介绍&#xff1a; 一、多租户概念 多租户是一种软件架构模式&#xff0c;允许多个客户&#xff08;租户&#xff09;共享同一套软件应用程序…

联邦学习(三只决策和大数据分析)(学习笔记)

联邦学习模型按照数据重叠形式分成了三类&#xff0c;分别是横向联邦学习、纵向联邦学习和联邦迁移学习。 横向联邦学习模型针对特征一致但ID不一致的数据&#xff1b; 纵向联邦学习模型针对ID一致但特征不一致的数据&#xff1b; 联邦迁移学习模型针对ID和特征都不一致的数据。…

Linux网络操作命令与函数全面总结

1. 引言 Linux作为服务器和开发平台&#xff0c;网络操作是其核心功能之一。本文旨在全面总结Linux系统中的网络操作方法&#xff0c;包括命令行工具和编程接口&#xff0c;帮助读者深入理解Linux网络管理的机制。 2. 命令行工具 2.1 ping 命令 ping 命令用于测试网络连接和…

实验OSPF路由协议(课内实验)

实验1&#xff1a;OSPF路由协议 实验目的及要求&#xff1a; 通过实验&#xff0c;能够理解链路状态型路由协议OSPF协议的工作原理&#xff0c;掌握如何实现单区域 OSPFv2配置指令&#xff0c;能够熟练的应用各种OSPF协议相关的配置指令完善网络设计。掌握验证OSPFv2网络连接…

软件开发人员绩效考核方案(参考)

1、产品&运营绩效考核表 2、开发绩效考核表 3、测试绩效考核表 4、CPI指标库 软件全套资料部分文档清单&#xff1a; 工作安排任务书&#xff0c;可行性分析报告&#xff0c;立项申请审批表&#xff0c;产品需求规格说明书&#xff0c;需求调研计划&#xff0c;用户需求调查…

像AI一样思考

我感觉每个人都应该通过Coze平台 https://www.coze.cn/ 创建一个属于自己的Bot。 为什么要创建Bot&#xff1f; 很多时候我们的思维会受各种因素的影响&#xff0c;如感情、知识盲区、心态等&#xff0c;最终导致我们做的决定缺乏逻辑或者考虑不全面。但AI不一样&#xff0c;…

armbian安装docker

最近又搞了台瑞莎Radxa 3E &#xff0c;从零开始部署unbuntu环境&#xff0c;发现是真曲折啊&#xff0c;虽然有点前车之鉴了 在Armbian上安装Docker&#xff0c;可以按照以下步骤操作&#xff1a; 1、更新软件包列表&#xff1a; sudo apt-get update 2、安装必要的软件包…

leetcode|刷算法 线段树原理以及模板

线段树出现的题目特征 线段树使用的题目。每次操作都要得到返回结果的。 比如 699. 掉落的方块 - 力扣&#xff08;LeetCode&#xff09; 2286. 以组为单位订音乐会的门票 - 力扣&#xff08;LeetCode&#xff09; 1845. 座位预约管理系统 - 力扣&#xff08;LeetCode&#…

【韩顺平Java笔记】第4章:运算符

文章目录 61. 上一章总结62. 算术运算符介绍62.1 运算符介绍62.2 算术运算符介绍62.3 算术运算符一览 63. 算术运算符使用64. 算术运算符练习165. 算术运算符练习266. 67. 算术运算符练习3,468. 关系运算符介绍68.1 关系运算符介绍68.2 关系运算符一览 69. 关系运算符使用70. 逻…

2024暄桐“静定的滋养”|静坐篇之林曦老师聊静坐

我们都喜爱“静”&#xff0c;它是一种因能量充足而带来的稳定放松的状态。 正在报名中的暄桐《2024书法课程 第五阶“静定的滋养” | 从书法之美到生活之美——林曦老师的线上直播书法课》&#xff0c;除了书法进阶部分的内容之外&#xff0c;读书部分正是帮助我们加强对静定的…

Windows应急响应-PcShare远控木马

文章目录 应急背景木马查杀1.查看异常连接2.查看进程3.查看服务定位到注册表 开始查杀 入侵排查1.账户排查2.开机自启3.服务4.计划任务5.网络情况6.进程排查重启再排查一遍 应急背景 曲某今天想要装一款软件&#xff0c;通过网上搜索看到非官方网站进入后直接下载下来后进行安…

毕业设计选题:基于ssm+vue+uniapp的购物系统小程序

开发语言&#xff1a;Java框架&#xff1a;ssmuniappJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;M…

DSPy101

DSPy 介绍 DSPy&#xff08;Declarative Self-improved Language Programs in Python&#xff09; 是一个用于系统化和增强在流水线内使用语言模型的框架&#xff0c;它通过数据驱动和意图驱动的系统来优化大型语言模型&#xff08;LLM&#xff09;的使用。 DSPy 的核心是模块…

我的笔记本电脑之前可以直接用音量键调节音量,后来需要fn键加音量键才能调节,这是为什么?

我的笔记本电脑之前可以直接用音量键调节音量&#xff0c;后来需要fn键加音量键才能调节&#xff0c;这是为什么&#xff1f; 直接按 FnEsc就能解除Fn的锁定

信息安全数学基础(24)模为奇数的平方剩余与平方非剩余

前言 在信息安全数学基础中&#xff0c;模为奇数的平方剩余与平方非剩余是数论中的一个重要概念&#xff0c;特别是在密码学和安全协议中扮演着关键角色。当模数为奇数时&#xff0c;我们通常关注的是模为奇素数的平方剩余与平方非剩余&#xff0c;因为奇合数的情况更为复杂且…

【数学分析笔记】第4章第2节 导数的意义和性质(2)

4. 微分 4.2 导数的意义与性质 4.2.3 单侧导数 f ′ ( x ) lim ⁡ Δ x → 0 f ( x Δ x ) − f ( x ) Δ x lim ⁡ x → x 0 f ( x ) − f ( x 0 ) x − x 0 f(x)\lim\limits_{\Delta x\to 0}\frac{f(x\Delta x)-f(x)}{\Delta x}\lim\limits_{x\to x_0}\frac{f(x)-f(x_0)…