初识类和对象

1. 类的定义

1.1. 类的格式

class className { };这是类的基本定义,和C语言结构体定义类似, C++struct也可以定义类,C++兼容Cstruct的用法,同时struct升级成了类,明显的变化是struct中可以定义函数,⼀般情况下我们还是推荐⽤class定义类

  • 类体中内容称为类的成员:类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者成员函数
  • 为了区分成员变量,一般会在变量名开头或者结尾加上_进行标识
  • 定义在类里面的成员函数默认为inline
// 定义一个类
class stuInfo
{
public:
	// 将实现接口公开
	// 成员函数
private:
	// 将信息数据隐藏
	unsigned int _age;
	unsigned int _numArr[8];
	char* _nameArr[10];
};

再比如:实现一个简单的栈

// 实现一个简单的栈
class Stack
{
public:
	// 成员函数
	void Init(int n = 4)
	{
		_valArr = (int*)malloc(sizeof(int) * n);
		if (_valArr == nullptr)
		{
			perror("malloc err!");
			return;
		}
		_top = 0;
		_capacity = n;
	}

	void push(int inPut = 1)
	{
		_valArr[_top++] = inPut;
	}

	size_t top()
	{
		assert(_top > 0);
		return _valArr[_top - 1];
	}

	void Destroy()
	{
		assert(_valArr);
		free(_valArr);
		_valArr = nullptr;
		_capacity = _top = 0;
	}

private:
	size_t _top;
	size_t _capacity;
	int* _valArr;
};

int main()
{
	Stack st;
	st.Init(12);

	st.push(0);
	st.push(1);
	st.push(2);
	st.push(3);

	st.top();

	st.Destroy();

	return 0;
}

1.2. 访问限定符

在这里插入图片描述

在上面的代码中,已经出现了publicprivate

  • public:修饰的成员在类外可以访问
    private/protected:受保护,修饰的成员不能在类外访问
  • 访问权限作用域:从第一个访问限定符出现的位置到下一个访问限定符出现的位置为一个作用域,若没有下一个访问限定符,则到}结束
  • class的类没有public默认都是被保护的,struct的类默认是公开的,是为了向下兼容C
  • 这体现了封装的思想:将数据和操作数据的方法结合,隐藏对象的属性和实现细节,仅对外公开接口和对象进行交互。通俗的说:数据和方法包在一起,然后通过访问限定符,保护不想让别人看得到,公开可以让别人看到的

1.3. 类域

  • 类定义了⼀个新的作⽤域,类的所有成员都在类的作用域中,在类体外定义成员时,需要使用 :: 域作用操作符指明成员属于哪个类域。
  • 类域影响的是编译的查找规则,下⾯程序中Init如果不指定类域Stack,那么编译器就把Init当成全局函数,那么编译时,找不到_valArr等成员的声明/定义在哪里,就会报错。指定类域Stack,就是知道Init是成员函数,当前域找不到的_valArr等成员,就会到类域中去查找。
class Stack
{
public:
	// 成员函数
	void Init(int n = 4);
	void push(int inPut = 1);
	size_t top();
	void Destroy();
private:
	size_t _top;
	size_t _capacity;
	int* _valArr;
};
// 声明和定义分离
	// 缺省参数声明给
	void Stack::Init(int n)
	{
		_valArr = (int*)malloc(sizeof(int) * n);
		if (_valArr == nullptr)
		{
			perror("malloc err!");
			return;
		}
		_top = 0;
		_capacity = n;
	}

	void Stack::push(int inPut)
	{
		_valArr[_top++] = inPut;
	}

	size_t Stack::top()
	{
		assert(_top > 0);
		return _valArr[_top - 1];
	}

	void Stack::Destroy()
	{
		assert(_valArr);
		free(_valArr);
		_valArr = nullptr;
		_capacity = _top = 0;
	}
int main()
{
	Stack st;
	st.Init(12);

	st.push(0);
	st.push(1);
	st.push(2);
	st.push(3);

	st.top();

	st.Destroy();

	return 0;
}

2. 实例化

2.1. 实例化概念

  • 用类类型在物理内存中创建对象的过程,称为类实例化出对象
  • 类是对象进行一种抽象描述,是⼀个模型,限定了类有哪些成员变量,这些成员变量只是声明,没有分配空间,⽤类实例化出对象时,才会分配空间
class Date
{
public:
	void Init(size_t year, size_t month, size_t day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
private:
	// 声明
	size_t _year;
	size_t _month;
	size_t _day;
};

int main()
{
	// 实例化
	Date d1;
	d1.Init(2025, 7, 3);
	d1.Print();

	return 0;	 
}

在这里插入图片描述
在这里插入图片描述

  • ⼀个类可以实例化出多个对象,实例化出的对象占用实际的物理空间,存储类成员变量。举个例子:小米YU7的设计图纸是一个类,而每台小米YU7就是设计图纸实例化出的对象,1个类对应n个对象。
    在这里插入图片描述

2.2. 类的大小

分析一下类对象里的成员:

  • 成员变量: 类实例化出的每个对象,都有独立的内存空间,计算类的大小时一定会考虑到
  • 成员函数:函数被编译后时一段指令,对象中没办法存储,这些指令单独存储在代码段,若非要存储,则可以通过指针。
    但是有用指针的必要吗?
    在这里插入图片描述

Person实例化出两个对象p1p2p1p2都有各自独立的成员变量_name _gender _age存储各自的数据,但是p1p2成员函数PrintPersonInfo SetPersonInfo的指针却是一样的
在这里插入图片描述

这里调用的是同一个函数,如果用Person实例化出100个对象,按照方式一,指针存储相同的地址存储100次,太浪费了,所以采用方式二,将函数地址存到公共代码区,所以得出结论:成员函数不参与类大小的计算
类的大小的计算遵循结构体内存对齐原则

  1. 结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处
  2. 其他成员变量要对齐到对齐数的整数倍的地址处
  • 对齐数=编译器默认的一个对齐数与该成员变量大小的较小值
  • VS默认为8
  • Linux中没有默认对齐数 gcc默认对齐数就是成员自身大小
  1. 结构体总大小为最大对齐数(结构体中每个成员变量都有一个对齐数,所有对齐数中最大的)的整数倍
  2. 如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。
// 计算⼀下A/B/C实例化的对象是多⼤?
// 8
class A
{
public:
	void Print()
	{
		cout << _ch << endl;
	}
private:
	char _ch;
	int _i;
};
// 1
class B
{
public:
	void Print()
	{
		//...
	}
};
// 1
class C
{
};
int main() 
{
	A a;
	B b;
	C c;
	cout << sizeof(a) << endl;
	cout << sizeof(b) << endl;
	cout << sizeof(c) << endl;
	return 0;
}

注意:空类大小默认为1字节,这个字节不存储有效数据,只表明类被定义了

3. this指针

  • Date类实例化出两个对象d1d2d1d2怎么区分成员函数Print()Init()来进行调用呢,C++引入了this指针来解决
  • 编译器编译之后,会在成员函数形参的第一个位置增加一个当前类类型的指针,称为this指针void Print(Date* const this),注意:这个指针无法修改;形参和实参的位置不能显式写出 this指针,编译器会默认生成,但是在函数体内可以显式写
  • 类的成员函数访问成员变量,通过this指针来实现,例如:this->_year = year
using namespace std;
class Date
{
public:
	// void Init(Date* const this, size_t year, size_t month, size_t day)
	void Init(size_t year, size_t month, size_t day)
	{
		// this->_year = year
		_year = year;
		_month = month;
		_day = day;
	}
	// void Print(Date* const this)
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	size_t _year;
	size_t _month;
	size_t _day;
};

int main()
{
	Date d1;
	d1.Init(2025, 7, 5);
	Date d2;
	d2.Init(2008, 1, 1);
	d1.Print();
	d2.Print();
}

再来看两个程序,问该程序是正常运行,还是编译错误,还是运行崩溃

#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类型实例化出一个p指针,这个指针为空,然后p指针调用Print(),回到Print()执行函数体,正常输出A::Print(),这里没有解引用空指针,所以程序正常运行

#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指针,这个指针为空,然后p指针调用Print(),回到Print()执行函数体,正常输出A::Print(),而继续下一行p->_a空指针解引用了,运行崩溃
在这里插入图片描述

this指针存在栈区和ecx寄存器中

评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值