【C++】类和对象(上篇)

❤️欢迎来到我的博客❤️

这里是目录

  • 前言
  • 类的定义
    • 访问限定符
    • 封装
    • 类的实例化
    • 类对象大小
  • this指针

前言

面向过程更关注的是过程,关注的是求解解决这个问题的步骤,通过函数调用逐步解决问题
面向对象关注的是对象,将一件事拆分成不同的对象,靠对象之间的交互完成

C语言中,结构体中只能定义变量,在C++中,结构体内不仅可以定义变量,也可以定义函数
在C++中结构体被升级成了类,但是由于C++兼容C所以C的语法在C++同样适用
类会被是当成一个域,所以在不同的类中可以出现同名函数

类里面是可以定义函数的

struct Stack
{
	//成员函数
	void Print()
	{
		cout << "test" << endl;
	}

	//成员变量
	int* a;
	int top;
	int capacity;
};

调用方法

int main()
{	
	//因为C++兼容C语言所以C语法同样适用
	struct Stack st1;
	st1.Print();

	Stack st2;
	st2.Print();

	return 0;
}

上面结构体的定义,在C++中更喜欢用class来代替

类的定义

class Stack
{
	//成员函数
	void Print()
	{
		cout << "test" << endl;
	}

	//成员变量
	int* a;
	int top;
	int capacity;
};

但是上面的结构体在改为class时会出现报错
在这里插入图片描述
原因是因为C++对成员给了一些限定

public :公有——类外可以直接访问
private:私有——类外不可以直接访问
protecter:与private类似——类外不可以直接被访问(在继承中会用到)

一般情况下如果我们希望我们的函数或变量被别人访问那我们就需要加上public: ,不希望被别人访问就加上private:protecter:
struct也可以使用访问限定符,struct如果不写访问限定符默认是公有的,而class默认是私有的

class Stack
{
//在遇到下一个访问限定符之前都是公有
//如果没有下一个访问限定符作用域就到}结束
public:
	//成员函数
	void Print()
	{
		cout << "test" << endl;
	}
	
//下面为私有(不能被访问)
private:
	//成员变量
	int* a;
	int top;
	int capacity;
};

声明和定义分离:
声明放在.h文件中,类的定义放在.cpp文件中

//test1.h
//需要指定类域,这样编译器才知道这个函数是类里面的一个成员函数的定义
void Stack::Print()
{
	cout << "test" << endl;
}

//test.cpp
#include "test1.h"

class Stack
{
public:
	//成员函数
	void Print();

	//成员变量
	int* _a;
	int _top;
	int _capacity;
};

但是需要注意的是成员函数如果在类中定义,编译器可能会将其当成内联函数处理
而内联函数不能声明和定义分离,所以一般长函数才需要声明和定义分开,而短函数直接在类中定义即可

访问限定符

public :公有——类外可以直接访问
private:私有——类外不可以直接访问
protecter:与private类似——类外不可以直接被访问(在继承中会用到)

说明:

  1. public修饰的成员在类外可以直接被访问
  2. protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)
  3. 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
  4. class的默认访问权限为private,struct为public(因为struct要兼容C)
  5. 如果后面没有访问限定符,作用域就到}即类结束

封装

封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互
封装是为了更好的管理和保护数据,避免对数据进行破坏,不想给别人看的就变成私有,想给别人看的就变成公有
封装的本质是一种更好的管理,让用户更方便使用类
在C++语言中实现封装,可以通过类将数据以及操作数据的方法进行有机结合,通过访问权限来隐藏对象内部实现细节,控制哪些方法可以在类外部直接被使用

类的实例化

用类类型创建对象的过程,称为类的实例化

class Date
{
public:
	//成员变量
	int _year;		//声明
	int _month;
	int _day;
};

int main()
{	
	//类实例化对象/对象定义
	//开了空间才算定义,没开空间算声明
	Date day1;
	Date day2;

	//不能,类访问_year是声明,_year不能存数据,可以理解为设计图不能住人
	Date::_year = 2023;

	//可以(公有情况下)因为day1是建造出的房子,开了空间
	day1._year = 2023;

	return 0;
}

类就像一个房屋设计图,类实例化对象就像是使用设计图造出房子,只设计出需要用到什么东西,但没有实体的建筑存在,实例化出的对象才有实体的空间才能实际存储数据,占用物理空间

类对象大小

类中既可以有成员变量,又可以有成员函数,那么一个类的对象中包含了什么?如何计算一个类的大小?

class A
{
public:
	void PrintA()
	{
		cout << _a << endl;
	}
private:
	int _a;
	int _b;
	int _c;
 };

int main()
{	
	A a;
	cout << sizeof(a) << endl;

	return 0;
}

运行效果:
在这里插入图片描述
从结果可以看到,对象的内存大小只算成员变量,不算成员函数

结构体对齐规则:

第一个成员在与结构体偏移量为0的地址处
其他成员变量要对齐到对齐数的整数倍的地址处,对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值,VS默认对齐数为8
结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍
如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍

int main()
{	
	A a1;
	A a2;

	//a1 a2访问的不是同一个数据
	a1._a = 0;
	a2._a = 1;
	//a1 a2调用的是同一个函数
	a1.PrintA();
	a2.PrintA();
	
	return 0;
}

每个对象中的变量是不同的,但是都调用同一份函数,所以没有必要在每一个对象中都放一个函数,相同代码保存多次,浪费空间,所以成员函数会被保存在一个公共的区域(不存在对象里面)
结论:一个类的大小,实际就是该类中”成员变量”之和 (需要注意内存对齐)

下面这两种情况的大小是多少?

class A1
{
	//只有成员函数
public:
	void PrintA()
	{
		cout << "A" << endl;
	}
 };

class A2
{
	//空类
};

运行结果:
在这里插入图片描述
从之前的结果来看这里应该是0,可实际的结果为什么是1呢
我们可以想象,有一个小区里面有很多的房子,可是有2栋房子没有住人,那小区需不需要给这2栋房子留空间呢?肯定是需要的,如果啥都不留,我们怎么知道这2栋房子也在这个小区呢

还有一种就是下面这种情况

int main()
{	
	A2 a1;
	A2 a2;
	cout << &a1 << endl;
	cout << &a2 << endl;

	return 0;
}

如果空间为0那么a1 a2没有地址,没有地址的话我们怎么区分a1 a2,所以没有成员变量的类/空类需要1字节(为了占位,表示对象存在)不存储有效数据

this指针

class Date
{
public:
	void Init(int y, int m, int d)
	{
		_year = y;
		_month = m;
		_day = d;
	}
	
	void Print()
	{
		cout << _year << " " << _month << " " << _day << endl;
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date day1;
	Date day2;
	
	day1.Init(2023,10,31);
	day2.Init(2023,11,1);
	day1.Print();
	day2.Print();

	return 0;
}

类中有 Init 与 Print 两个成员函数,函数体中没有关于不同对象的区分,那当dya1调用 Init 函数时,该函数是如何知道应该设置day1对象,而不是设置day2对象呢?

这里面就有一个隐含的this指针,代码运行的时候编译器会把函数处理成这个样子

//编译器对成员函数的处理
void Print(Date* this)
{
	cout << this->_year << " " << this->_month << " " << this->_day << endl;
}

调用的地方也会进行处理

day1.Print(&day1);
day2.Print(&day2);

他们调用的是同一个函数,但是他们的形参不同(其他函数也同理)
this是形参,所以this指针跟普通参数一样存在函数调用的栈帧里面(vs对this指针传递做了优化,对象地址是放在ecx寄存器,ecx存储this指针的值)

注:我们不能在形参和实参中显式传递,但是可以在函数内部显式使用
如:

//不能写成void Print(Date* this)
void Print()
{
	cout << this << endl;
	cout << this->_year << " " << this->_month << " " << this->_day << endl;
}

在这里插入图片描述
this指针的特性

this指针的类型:类类型* const,即成员函数中,不能给this指针赋值
只能在“成员函数”的内部使用
this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针
this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递


以上就是本篇文章的全部内容了,希望大家看完能有所收获

❤️创作不易,点个赞吧❤️
  • 9
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值