当前位置: 首页 > article >正文

【C++】类和对象-上

> 🍃 本系列为初阶C++的内容,如果感兴趣,欢迎订阅🚩
> 🎊个人主页:[小编的个人主页])小编的个人主页
>  🎀   🎉欢迎大家点赞👍收藏⭐文章
> ✌️ 🤞 🤟 🤘 🤙 👈 👉 👆 🖕 👇 ☝️ 👍


目录

🐼前言

🐼类定义

🐼访问限定符

🐼类域

🐼实例化对象

 🐼对象的大小

 🐼this指针


🐼前言

🌟C语言是一门面向过程的语言,在C语言中,我们用结构体来定义复合数据类型的结构。C++在设计中保留了结构体(struct)类型的优点,C++引入了类,类已经包括了结构体类型的所有功能,并且功能更强,更符合面向对象程序设计的要求,并且安全性,规范性,对象的可交互性也更高。在之后的学习中我们会更深有体会。

🐼类定义

🌟我们通过Date这个日期类来认识类:

#include<iostream>
using namespace std;

class Date
{
public:
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	
	// 为了区分成员变量,一般习惯上成员变量
	// 会加一个特殊标识,如_ 或者 m开头
private:
	int _year;
	int _month;
	int _day;
};
  1. ✨在上述的示例中:class为类的关键字,Date为类的名字,{}为类的内容,在类定义后还有一对分号,这点和结构体很相似。 
  2. 类中的变量我们称作属性或成员变量类中的函数我们称为类的行为或成员函数
  3. 在定义成员变量时,我们习惯在成员变量前加_,用来说明是类中的成员变量。
  4. 注意,类中的函数默认是Inline类型的
  5. C++兼容C语言中的结构体struct,只不过在C++中,将struct升级成了类,可以在struct中定义函数。struct名称就可以代表类型。不需要在typedef 。

 如: 

struct ListNode
{
	ListNode* next;//不需要再加struct了
	int data;
};
int main()
{
	ListNode n1;//ListNode就代表类型
	return 0;
}

😾解释:再创建对象n1时,我们没有先将struct ListNode typedef,也没有在next指针的类型像C语言中加struct ListNode,因为 ListNode就代表类型,这也是C++方便的一点.

🐼访问限定符

👀Data类的public和private是干嘛的勒?

  1. ✌️为了程序的规范性,C++采用了一种通过访问权限的选择性供接口给外部用户使用。
  2. 我们不希望外部用户能随意改变属性(成员变量),关键字protected和private修饰的成员在类外不能直接被访关键字public修饰的成员只给公共的方法(成员函数)来供外部用户使用,正如我们上述Date类
  3. 一般成员变量都用private/protected保护,只有给别人用的才用public。
  4. 注意,如果没有外加需求,在类中的成员变量成员函数都是private修饰的,也就是在外部访问,都是没有权限的。struct默认为public。

如:

class Date
{
	//不加访问限定符默认是private
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	
	
	int _year;
	int _month;
	int _day;
};

struct ListNode
{

	void Init(int x)
	{
		next = nullptr;
		val = x;
	}
	ListNode* next;
	int val;
};
int main()
{
	ListNode n1;
	n1.Init(2);//这里就可以调用,struct默认权限是public
	Date d1;
	d1.Init(3,2,1);//不可以从调用,不加访问限定符默认是private
	return 0;
}

编译器报错:“Date::Init”: 无法访问 private 成员(在“Date”类中声明)

  1. 访问权限符的范围为从当前访问限制符开始,到下一个访问限制符结束。

如第一个Date类从public到private中,Init方法类外能直接被访.下面的成员变量外部不可访问。

C++通过访问限定符,实现了⼀种实现封装的方式,用类将对象的属性与方法结合在⼀块,让对象更加完善。

我们只给外部用户使用我们实现的函数的接口,用户只可以调用我们写好的类中的函数,而不可以访问成员变量,而在C语言中,比如取栈顶元素,我们可以直接访问栈顶元素,arr[top-1],这对于程序的安全性就有很大影响,可能用户一不小心就将我们的数据修改了;

正是由于C语言太自由了,在C++中,访问限定符的限制,就让程序变得更加规范,可维护性也更高,更好的规避了这方面的问题。

🐼类域

⭐️在C++中,存在四个域:局部域,全局域,命名空间域,和类域

我们之前分享了命名空间域。类域是一个新的域,在类中存在类域,类中的所有成员都存在类域中,在类外定义类中某个成员时,要用到::作用域操作符指明成员属于哪个类域

当成员函数声明和定义分离时,需要加上域操作符,告诉编译器,当前成员函数在哪个域中,不然编译器会当他为全局变量,但是找不到capacity,top等来自哪里,就会到全局变量域中查找也没有,就会报错。本质上是修改了编译器的查找规则,去类域中查找

我们如果将成员函数声明和定义分离时,加上域操作符。

如:

//Stack.cpp
#define _CRT_SECURE_NO_WARNINGS 1

#include"Stack.h"

// 声明和定义分离,需要指定类域
void Stack::Init(int n)
{
	array = (int*)malloc(sizeof(int) * n);
	if (nullptr == array)
	{
		perror("malloc申请空间失败");
		return;
	}
	capacity = n;
	top = 0;
}


//Stack.h
#pragma once
#include<iostream>
#include<assert.h>
using namespace std;

class Stack
{
public:
	void Init(int n = 4);

private:
		// 
	int* array;
	size_t capacity;
	size_t top;
};

😺解释:

我们只尽兴了类中的成员函数的声明,没有定义。成员函数的定义我们放到了Stack.cpp中来实现,指定类域Stack,就是去Stack中查找Init成员函数当前域找不到的array等成员,就会到类域中去查找。规则:在函数名前,类名+::

🐼实例化对象

  1. ⭐️示例化对象是指,创建一个具有当前类特性的物理对象。在上述我们其实已经创建了类对象,比如Date类的d1,d2就是类实例化出的对象。
  2. 在上述我们定义了类Date,包括成员变量和成员函数,但是这些成员变量,成员函数只是声明,没有分配空间,用类实例化出对象时,才会分配物理空间。比如:我们在建房子时会用图纸,图纸就相当于类,有了图纸,但是我们的房子还没有创建,并没有实体的建筑存在,也不能住人,用设计图修建出房子,房子才能住人。也就是没有根据图纸实例化对象房子。
  3. 类仅仅是对对象的一种抽象描述。⼀个类可以实例化出多个对象,实例化出的对象占用实际的物理空间,存储类成员变量,同样类就像设计图⼀样,不能存储数据,实例化出的对象分配物理内存存储数据。
  4. 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 
     Date d1;
     Date d2;
     d1.Init(2024, 3, 31);
     d1.Print();
     d2.Init(2024, 7, 5);
     d2.Print();
     return 0;
    }
    

 🐼对象的大小

👀在类中,有成员变量和成员函数,我们创建一个类对象,那么这个类对象有多大呢?

值得肯定的是,类实例化一个对象,有独立的数据空间,对象肯定包括成员变量那么成员函数自已有空间吗,我们可以测试一下。

#include<iostream>
using namespace std;


class Date
{
public:
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	
	// 为了区分成员变量,一般习惯上成员变量
	// 会加一个特殊标识,如_ 或者 m开头
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;
	cout << sizeof(d1) << endl;
	cout << sizeof(Date) << endl;
	return 0;
}

☀️运行结果:

     12
     12

  • 我们之前学习了结构体的内存对齐规则,如果不懂,可以阅读什么?你还不知道结构体内存对齐规则-CSDN博客这篇文章。
  • 我们通过对齐规则,类的大小是12,三个成员变量的大小是12,这说明在内存中,对象没法存储成员函数
  • 首先函数被编译后是⼀段指令,对象中没办法存储,这些指令存储在一个单独的区域(代码段),那么对象中非要存储的话,只能是成员函数的指针。再分析一下,对象中是否有存储指针的必要呢,Date实例化d1和d2两个对象,d1和d2都有各自独立的成员变量 _year/_month/_day存储各自的数据,但是d1和d2的成员函数Init/Print指针却是一样的,存储在对象中就浪费了。如果用Date实例化100个对象,那么成员函数指针就重复存储100次,太浪费了。也就是类中的成员函数是放在公共代码区的,对象没法存储。行为是大家共有的
  • 我们再来看一个实例:
    class Date
    {
    public:
    	
    };
    
    
    int main()
    {
    	Date d1;
    	cout << sizeof(d1) << endl;
    	cout << sizeof(Date) << endl;
    	return 0;
    }
    
    

    ☀️运行结果: 1  1

上述结果为什么是1呢?👀明明类中没有成员函数和成员变量。

我们反过来思考,如果一个字节都不给,那编译器怎么知道该对象存在过呢?所以,类中没有成员函数和成员变量默认给1个字节用于占位。

 🐼this指针

⭐️在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;
		_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;
}


🍏在类中,有两个成员函数Init和Print。我们创建了两个对象d1,d2,那当我们调用Init或者Print函数时,编译器如何知道是d1,还是d2调用的函数。其实,C++默认会在形参的第一个位置,加上一个当前类型的指针。Init的形参真正是void Init(Date* const this, int year, int month, int day).会在第一个形参位置默认加上Date* const this,实参实际上将类对象d1的地址传过去这不就和我们在C语言结构体传参对结构体变量操作时一样吗。只不过在C++中,编译器已经帮我们完成了这些事情,C++给了 ⼀个隐含的this指针解决这里的问题。这一切都交给了编译器来完成了,变得十分方便。

🍓类中的成员函数访问成员变量,本质上都是通过this指针来访问的。比如给_month,_day赋值。this->_month = month;this->_day = day;

🌽注意,C++规定,在成员函数形参和实参的部分,我们不能手动添加this指针。因为编译器已将帮我们完成了,不过在成员函数内部,可以显示使用this指针,也可以混用。

this指针场景如:


感谢你看到这里,如果觉得本篇文章对你有帮助,点赞👍收藏 ⭐️吧,你的支持就是我更新的最大动力。⛅️🌈 ☀️


http://www.kler.cn/a/394101.html

相关文章:

  • Nuxt.js 应用中的 schema:beforeWrite 事件钩子详解
  • c++写一个死锁并且自己解锁
  • QQ 小程序已发布,但无法被搜索的解决方案
  • sol机器人pump机器人如何实现盈利的?什么是Pump 扫链机器人?
  • 【嵌入式开发】单片机CAN配置详解
  • AWS认证SAA-C0303每日一题
  • 【RabbitMQ】09-取消超时订单
  • 深入理解 MVC 与其他主流设计模式:架构精髓与实现方法详解
  • 67页PDF |埃森哲_XX集团信息发展规划IT治理优化方案(限免下载)
  • 【go从零单排】Signals、Exit
  • 原生js预览ofd文件
  • 第九部分 :1.STM32之通信接口《精讲》(USART,I2C,SPI,CAN,USB)
  • <Project-23 Navigator Portal> Python flask web 网站导航应用 可编辑界面:添加图片、URL、描述、位置移动
  • UI设计生成器:2024年在线设计工具
  • HCIP-HarmonyOS Application Developer 习题(二十一)
  • springboot接口返回数据给前端,BigDecimal为null但返回前端显示-1
  • CSS响应式布局实现1920屏幕1rem等于100px
  • selenium测试的一些语法
  • WEB攻防-通用漏洞SQL注入MYSQL跨库ACCESS偏移
  • vue3项目中内嵌vuepress工程两种实现方式
  • 构建现代 Python Web 应用的最佳实践:从 FastAPI 到 Tortoise ORM20241113
  • div加4个角边框 css
  • 从0开始学docker (每日更新 24-11-11)
  • 信号保存和信号处理
  • 修改yolo格式的labels类别、删除yolo格式的labels类别
  • redis7.x源码分析:(1) sds动态字符串