【 C++ 】 类和对象的学习

前言:

😘我的主页:OMGmyhair-CSDN博客

目录

引言:

一、类的作用域

二、计算类对象的大小

三、this指针

this指针❓1

this指针❓2

this指针❓3


引言:

通过类我们可以对数据和方法进行封装

封装的意义:

1.将属性和行为作为一个整体,表现事物

2.类在设计时,可以把属性和行为放在不同的权限下,加以控制

在c++中,类与结构体差不多,它们唯一的区别是,结构体的默认权限是公有public,而类的默认权限是私有private。从下图可以看到,我们不能对class的成员变量进行访问。


一、类的作用域

当我们定义了一个类时,这个类也定义了一个新的作用域,类的所有成员都在这个作用域中。当我们在类外定义成员时,我们要用上::作用域操作符告诉编译器这个函数属于哪个作用域,否则编译器在编译时,不会到类域中去查找而是在全局或者局部查找,此时可能会产生错误。

我们举个例子帮助理解:

class Person
{
public:
	void SetAge(int age)
	{
		_age = age;
	}
	void PrintAge()
	{
		cout << _age << endl;
	}
private:
	int _age;
};

int main()
{
	Person lisi;
	lisi.SetAge(18);
	lisi.PrintAge();
	return 0;
}

上面代码中,当我们想要将PrintAge函数定义在类外时,必须用::作用域操作符指定这个函数的作用域。要不然,编译器会将这个函数当作全局函数,在编译到_age时,不会到类域中去查找,因此编译器上下前后都找不到_age,此时就发生了问题。

正确做法:

报错做法:

❗值得一提的是,C++ 标准规定所有在类定义中定义的成员函数都是内联的。但是当类中的成员函数定义和声明分离是就不是内联了。

❗这里要注意一点,类中的成员变量是声明而不是定义,定义需要给变量开空间,而声明不需要给变量开空间。而这一点会在后面的对象实例化更详细的讲解。

例如像下面的写法是错误的,因为_age只有声明,没有定义,没有为其开空间,我们无法使用。可以看到,即使我将_age变为公有的,我们仍然无法进行访问。


二、计算类对象的大小

当我们用类实例化出一个对象时,此时就会为成员变量开辟空间,我们得以进行访问。

你是否会好奇这一个对象实例化后,它的大小是多少呢?

你可能会认为这个对象的内存是由成员变量和成员函数组成,但是我们仔细想想,让这些对象不同的东西就是成员变量,而那些成员函数不管对象怎么变,它们的代码都是一样的,所以我们不需要浪费空间每次示例化一个对象都为这个对象存储一套代码。

这时候我们知道了对象的空间由成员变量组成,那怎么去计算呢?这里对象也符合内存对齐规则,类的对象内存计算方式与结构体相同。

点击关于结构体知识点的总结_结构体变量赋值知识点总结-CSDN博客你可以得到详细的内存对齐规则内容以及如何计算。

现在我们已经了解了对象的大小如何计算,也知道了对象的内存放的是成员变量,那么以下这种情况如何计算呢?

class Person
{
public:
	void Print()
	{
		cout << "age" << endl;
	}
};

int main()
{
	Person lisi;
	return 0;
}

现在没有成员变量,那么lisi这个对象的大小为0吗?那岂不是这个对象不存在?我们用sizeof计算一下:

为了表示这个对象存在,会给这个对象开辟一个字节空间进行占位。

❗所以,没有成员变量的类实例化出来的对象大小为1字节,这1字节不存储任何东西,单纯用于占位。


三、this指针

this指针指向被调用的成员函数所属的对象

this指针是隐含每一个非静态成员函数内的指针

用途:

1.当形参和成员变量同名时,可用this指针来区分

2.在类的非静态成员函数中返回对象本身,可使用return *this;

首先,上一份代码:

class Person
{
public:
	void Print()
	{
		cout << _age << endl;
	}

	int _age;
};

int main()
{
	Person lisi;
	Person wfive;
	lisi._age = 20;
	wfive._age = 25;
	lisi.Print();
	wfive.Print();
	return 0;
}

你是否会好奇,Print函数是怎么知道谁在调用它呢?当lisi调用它时,它是怎么知道自己该输出lisi的年龄而不是wfive的呢?

这里我们就要引入一个东西,它就是this指针。

其实编译器编译后,成员函数会默认在形参的第一个位置添加该类类型的指针,其实真实的Print函数长这样:

真实的:

class Person
{
public:
	void Print(Person * const this)
	{
		cout << this->_age << endl;
	}

	int _age;
};

int main()
{
	Person lisi;
	Person wfive;
	lisi._age = 20;
	wfive._age = 25;
	lisi.Print(&lisi);
	wfive.Print(&wfive);
	return 0;
}

每次对象在调用成员函数时,都会偷偷将对象自己的地址传过去,这一成员函数就能知道是谁在调用它,当lisi调用时Print函数也得以知道自己该打印lisi的年龄。

虽然真实的成员函数和调用是上面那样,但是你不需要在实参和形参显式地写出this指针,那样编译器会报错。但是我们可以在函数体中显式地使用this指针。

并且this指针本身不能被修改,因为const修饰的是this,即this不能再指向别的地址。

如果是Person const * this,那么*this不能改变,但是this能指向别的地址。

现在用几个问题加深我们对this指针的理解:

this指针❓1

this指针存在内存哪个区域的 ()

A. 栈 B.堆 C.静态区 D.常量区 E.对象⾥⾯

解答:

首先我们知道对象里面只存储成员变量,因此排除E。

这个问题答案是A,this指针是形参,因此放在栈帧里面,但是这个答案也不完全正确。在VS中,this指针存放在寄存器ecx中,因为this指针要频繁使用,所以放在寄存器中更快。

this指针❓2

下⾯程序编译运⾏结果是() A、编译报错 B、运⾏崩溃 C、正常运⾏

class A
{
public:
	void Print()
	{
		cout << "A::Print()" << endl;
	}
private:
	int _a;
};

int main()
{
	A* p = nullptr;
	p->Print();
	return 0;
}

解答:

首先,Print函数会传对象的地址,而这里对象的地址就是空指针。我们结合下面的代码帮助理解:

A a;
A* pa = &a;//pa==&a  *pa==a

A*const this=&a;//A*const this =pa;

因此p就是nullptr,而p就是对象的地址,nullptr被传给了this指针,this就是空指针。我们可以将Print中的this打印出来看看:

在Print函数中,我们并没有使用this指针,不存在解引用空指针的问题,所以它可以正常运行。

正确答案为C

this指针❓3

下⾯程序编译运⾏结果是() A、编译报错 B、运⾏崩溃 C、正常运⾏

class A
{
public:
	void Print()
	{
		cout << "A::Print()" << endl;
        cout << _a << endl;
	}
private:
	int _a;
};

int main()
{
	A* p = nullptr;
	p->Print();
	return 0;
}

解答:

答案选B

因为在Print中,我们进行了对空指针的解引用,从上一题我们知道了这种情况下this为空指针,而cout<<_a<<endl这一行代码实际上是cout<<this->_a<<endl,在这一过程中发生了对this指针也就是空指针的解引用。因此程序运行崩溃。



如果这篇文章有帮助到你,请留下您珍贵的点赞、收藏+评论,这对于我将是莫大的鼓励!学海无涯,共勉!😘😊😗💕💕😗😊😘



  • 34
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值