二、类和对象(上)

目录

1. 类的定义

2. 类的访问限定符及类域

2.1 类的访问限定符

2.2 类域

 3. 类的实例化

3.1 实例化概念

3.2 对象大小 

4. this指针 

4.1 this指针的引入

4.2 this指针的特性


1. 类的定义

在C语言中,我们经常使用到结构体来存储数据,在C++中,我们采用类来代替结构体,与C语言不同的是类中可以定义变量,也可以定义函数。

class Date
{


	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}



	int _year;
	int _month;
	int _day;
};

在C++中既可以用struct来定义类,也可以用class来定义(更推荐用class来定义)。其中类中的元素成为类的成员,类中的变量称为类的属性或成员变量,类中的函数称为类的方法或成员函数。 

struct ListNode
{
public:
	void Init(int x)
	{
		val = x;
		next = nullptr;
	}
private:
	int val;
	ListNode* next;
};

定义在类中的函数默认inline。看代码我们发现多了两个新的东西:public和private。我们接着往下看。 

2. 类的访问限定符及类域

2.1 类的访问限定符

C++实现封装的方式:用类将对象的属性和方法结合在一起,让对象更加完善,通过访问权限选择性地将其接口提供给外部的用户使用。 

说明:

1.public修饰的成员在类外可直接访问;protected和private修饰的成员在类外不可以直接被访问。

2.访问权限作用域从该访问限定符出现的位置开始一直到下一个访问限定符出现为止,如果后面没有访问限定符,作用域到}即类结束。

3.class定义成员没有被访问限定符修饰时默认为private,struct则为public。 

2.2 类域

 类定义了一个新的作用域,类的成员都在类的作用域中,在类外定义成员时,需要使用::作用域操作符指明成员属于哪个类域。

class Stack
{
public:
	//成员函数
	void Init(int n = 4);
private:
	//成员变量
	int* array;
	size_t top;
	size_t capacity;
};

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

int main()
{
	Stack s1;
	s1.Init();
	return 0;
}

类域影响的是编译时的查找规则。在上面程序中如果Init函数不指定类域Stack,那么编译器会将它当作全局函数,编译时,招不到top等成员的声明和定义在哪里,编译就会报错。指定类域Stack后,在当前域中找不到top等成员时,就会去相对应的类域中寻找。 

 3. 类的实例化

3.1 实例化概念

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

1. 类是一个模型一样的东西,限定了类有哪些成员变量,这些成员变量只是声明,没有分配空间用类实例化对象时,才会分配空间

2. 一个类可以实例化出多个对象,实例化出的对象占用实际的物理空间,存储类成员变量。

3. 类实例化出对象就像现实中使用建筑图建造房子,类就是设计图,设计出需要什么东西,但并没有实体的建筑存在,不能存储数据,实例化出的对象分配物理内存存储数据。

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, 7, 9);
	d1.Print();

	d2.Init(2024, 7, 10);
	d2.Print();

	return 0;
}

3.2 对象大小 

 如何计算类对象的大小

class A
{
public:
	void Print()
	{
		cout << _ch << endl;
	}

private:
	char _ch;
	int _i;
};

类中既可以有成员变量,又可以有成员函数,那么一个类的对象中包含了什么,如何计算一个类的大小。想要解决这个问题,我们要清楚类在内存中的存储方式。 

每个对象中的成员变量是不同的,但调用同一份函数,如果按照 此种方式存储,当一个类创建出多个对象时,每个对象中都保存一份代码,相同代码保存多次,浪费空间。因此,类在内存中的存储方式如上图,类中只保存成员变量,成员函数放在公共代码区。

//既有成员变量,也有成员函数
class A
{
public:
	void Print()
	{
		cout << _ch << endl;
	}

private:
	char _ch;
	int _i;
};
//只有成员函数
class B
{
public:
	void func()
	{

	}
};
//空类
class C
{

};

int main()
{
	A a;
	B b;
	C c;

	cout << sizeof(a) << endl;    //8
	cout << sizeof(b) << endl;    //1
	cout << sizeof(c) << endl;    //1
	return 0;
}

实例化对象的大小与我们在C语言中学过的结构体大小相似,都要考虑内存对齐 。对于内存对齐有以下规则:

1. 第⼀个成员在与结构体偏移量为0的地址处。

2. 其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处。

3. 对⻬数 = 编译器默认的⼀个对⻬数与该成员⼤⼩的较⼩值。(VS中默认对齐数为8)

4. 结构体总⼤⼩为:最⼤对⻬数(所有变量类型最⼤者与默认对⻬参数取最⼩)的整数倍。

5. 如果嵌套了结构体的情况,嵌套的结构体对⻬到⾃⼰的最⼤对⻬数的整数倍处,结构体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体的对⻬数)的整数倍。

我们看到没有成员变量的B和C类对象的大小是1,这个比较特殊,对于空类,编译器给了空类一个字节来唯一标识这个类(为了占位标识对象存在)。 

4. this指针 

4.1 this指针的引入

 首先定义一个日期类Date 

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, 7, 9);
	d1.Print();

	d2.Init(2024, 7, 10);
	d2.Print();

	return 0;
}

Date类中Init和Print两个成员函数,但函数体中没有关于不同对象的区分,当d1调用Init和Print函数时,函数如何知道应该访问的是d1对象还是d2对象呢? 

C++为了解决这个问题,引入了一个隐式的this指针来解决这个问题。即:C++编译器给每个非静态的成员函数增加了一个隐藏的指针参数,让该指针指向函数运行时调用该函数的对象,在函数体中所有成员变量的操作,都是通过指针去访问。这些操作对用户是透明的,不需要用户自己来传递。 

4.2 this指针的特性

1. this指针的类型为:类类型* const。

2. this指针只能在函数体内显示使用,不能在实参和形参的位置显示的写this指针。

3. this指针本质上是一个成员函数的形参,是对象调用成员函数时,将对象地址作为实参传递给this形参,所以对象中不存储this指针。

4. this指针是成员函数第一个隐含的指针形参,一般情况下在编译后,由编译器通过寄存器自动传递,不需要用户传递。 

class Date
{
public:
	void Init(int year, int month, int day)
	{
		this->_year = year;
		this->_month = month;
		this->_day = day;
	}

	void Print()
	{
		cout << this->_year << "/" << this->_month << "/" << this->_day << endl;
	}

private:
	//只是声明,并没有开空间。
	int _year;
	int _month;
	int _day;
};

注意:this指针不能为空。

这里报错的原因是什么呢?首先看a是一个空指针,但并非对象是空指针就一定会报错,更主要的原因是Show函数中this->_a,对指针a进行了访问,而空指针不能访问。下面我们看a->Print()会不会报错 

可以发现并没有报错,因为在Print中并没有对指针的访问。 

this指针存储在内存的栈区中。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值