C++类和对象(上)

C++类和对象(上),第1张

目录

C++ struct的升级

类的定义

 访问限定符

类域

类的声明和定义分离

C++类的一些约定

封装

类对象模型

隐含的this指针

 this指针两道经典面试题


上一篇博客我向大家介绍了面向过程和面向对象的一些区别,并捎带着引入了一点点类和对象的概念,今天我们正式来看一下类和对象。

https://blog.csdn.net/SAKURAjinx/article/details/126990400

C++ struct的升级

为了引入类,C++先拿C语言的struct开刀了。

首先C++的struct还是兼容C的用法,但同时也将其升级成了类。也就是说struct里面不仅可以放以前那一套(属性),还可以放入方法(函数)了。

struct stu
{
	int name;
	int age;
	char ID[19];
	//...
	void StuReturnSch(stu* ps)
	{

	}
	void StuRegisterSch(stu* ps)
	{

	}
};

struct teacher
{
	int name;
	int age;
	char ID[19];
	//...
	void TeaReturnTea(teacher* pt)
	{
		
	}
	void TeaRegisterTea(teacher* pt)
	{

	}
};

并且因为struct升级成了类,所以stu ,teacher 是类名,结构体实参命名可以不加struct,直接用类名。

struct teacher p1;
teacher p2;

类的定义

 一开始C++还只是升级了struct,但后来研发者感觉这种方式不太好,就用class代替了struct。

当然,struct还是可以用,并且功能和class大同小异,但是后来程序员一般还是使用class,这成为一种通用标准了,Java里面类也是用的class。   并且struct还兼容C语言那一套,在其他一些场景也有新的应用。

类里面的变量和函数不需要按 “变量在上,函数在下” 的方式书写,可以穿插,可以倒过来写。

但是一般都不会胡乱穿插书写,毕竟代码除了自己,其他程序员也可能会看到的,最好还是变量放一起,函数放一起,方便阅读。

C语言里面变量要放上面,函数要放下面,是因为C语言编译器碰到未知量,是向上查找,不会向下。并且C的变量和函数没有必然的关联性,它们是分离开的。

C++变量和函数都在一个域里面,有必然的联系,因此可以上函数下变量的方式书写。

 访问限定符

 C++有封装、继承、多态,为了更好地控制封装,C++定义了访问限定符。

1、protected 和 private 都是私有,但是有区别,前期我们先不讲它们的区别。

2、public修饰的成员类外面也可以访问,protected 和 private 修饰的成员类外面不能访问。

3、不管是public还是protected 和 private 修饰,类里面都可以访问该成员。

4、class里没用访问限定符修饰的成员,默认私有; struct正相反,默认公有。

类域

class里面的域叫类域。

之前C++命名空间域也是一个域。

stu::age = 20;

问大家一个问题,类里面可以这样像命名空间一样访问吗?

不可以。因为class 里面是声明,没有定义,自然不存在变量空间,因此也没有age的空间,不能访问。

要访问age,先在外部构建对象,再用对象访问成员变量,如下所示:

class stu
{
public:
	char name[20];
	int age;
	char ID[19];
	//...
	void StuReturnSch(stu* ps)
	{

	}
	void StuRegisterSch(stu* ps)
	{

	}
};

class teacher
{
public:
	char name[20];
	int age;
	char ID[19];
	//...
	void TeaReturnTea(teacher* pt)
	{
		
	}
	void TeaRegisterTea(teacher* pt)
	{

	}
};

int main()
{
	stu p1;
	teacher p2;
	p1.age = 20;
	return 0;
}

如果是静态的,可以用上面那种方式访问,后面再讲。

类的声明和定义分离

声明和定义分离的原因:

1、增强可读性。比如公司里需要你加入一个项目,你要迅速了解项目的基本内容,可以查看项目程序的声明及注释。  如果声明和定义在一起,阅读、查找起来就很吃力。

2、增强代码安全性,防止源代码泄漏。

先来看一种声明和定义分离的情况:

project.h

#include

class stu
{
public:
	char name[20];
	int age;
	char ID[19];
	void ReturnSch(stu* ps);
	void RegisterSch(stu* ps);
};

class teacher
{
public:
	char name[20];
	int age;
	char ID[19];
	void ReturnSch(teacher* pt);
	void RegisterSch(teacher* pt);
};

project.cpp

#include"project.h"

void ReturnSch(stu* ps)
{

}
void RegisterSch(stu* ps)
{

}

void ReturnSch(teacher* pt)
{

}
void RegisterSch(teacher* pt)
{

}

test.c

#include"project.h"

int main()
{
	stu p1;
	teacher p2;

	return 0;
}

我们发现一个问题,project.cpp里面,函数定义时,分不清哪个是stu的哪个是teacher的。

因此,此时要加上类的限定。

void stu::ReturnSch(stu* ps)
{

}
void stu::RegisterSch(stu* ps)
{

}

void teacher::ReturnSch(teacher* pt)
{

}
void teacher::RegisterSch(teacher* pt)
{

}

另外,如果在类里面定义函数,那么编译器会默认函数是内联的。

C++类的一些约定

我们在类里定义变量时,通常会在前面或后面加 以示区分。

如:int _year;     int year_;

还有:int  m_year;    int mYear

具体以公司惯例为主。

封装

面向对象有三大特性:封装、继承、多态。当然还有一些其他特性(如反射等),但这三个是最主要的。

我们现在所学的类就是封装的一大体现。其本质就是把一系列相关的东西都封到一个地方。

类里面有公有和私有,想给外面使用的就设为公有(如成员函数),不想给外面使用的就设为私有(如成员变量)。

以数据结构的栈为例C语言实现的时候相对自由一些,它的数据和方法(变量和函数)不是在一起的,是分离的。 而C++则是将数据和方法都封装在了一个类下。

 左边C语言因为相对自由,没有封装的约束,也就可以通过访问变量随意修改成员。比如访问栈顶数据,本来需要调用StackTop函数,但是因为这个函数实现就两行太过简单,很多人直接就访问成员变量,这样会出现问题(因为不知道top初始设计是-1还是0)。

C++就不存在上述问题,因为封装在一起,数据是私有的,无法从外部访问,必须调用方法(成员函数),这样就增加了安全性,能更好地管理。

类对象模型

如何计算对象的大小

 计算类的大小时,和之前讲的结构体内存对齐那一块一样,都涉及到内存对齐规则,不清楚的老铁可以看一下我的博客:自定义类型————结构体、位段、枚举类型、联合体_SAKURAjinx的博客-CSDN博客

 先说结论,这里类A的大小不是8,而是1.  很多老铁会把函数也算进大小里面,其实大佬在设计的时候没有将函数算进去,只计算对象的大小

这里大佬在设计存储方式的时候有三种想法:

 第一种:将对象和方法都存进去。

这种显然不好,太浪费空间了。pass

 第二种:存对象及函数的地址。

毫无疑问,对象肯定都要存的,将函数放到一起,用一个指针指向它,保存这个地址就可以访问所有函数了。

 第三种是大佬选择的方法,只存对象,将函数放到一块公共代码区,每个对象想访问就可以访问到。

方法二比方法三多存了一个地址,其实是没必要的,函数都在公共代码区了,对象都能轻易调用,没有必要特地保存一个地址取找这些函数。

既然函数不算进类的大小,那如果类里面没有对象呢?


#include
using namespace std;
class A1
{};
class A2
{
public:
	void func() { }
};
int main()
{
	cout << sizeof(A1) << endl;
	cout << sizeof(A2) << endl;

	return 0;
}

 不论是类里面只有函数没有变量,还是什么都没有,编译器默认给的大小是1,不是0

也就是说编译器会给1个字节占位,它不存储有效数据,仅标识类存在

因为如果需要取类的地址,连空间都没有就不行了。

隐含的this指针
class Date
{
public:
	void Init(int year = 1,int month = 1,int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << " " << _month << " " << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;
	Date d2;
	d1.Init(2022,1,1);
	d2.Init(2022,1,2);
	d1.Print();
	d2.Print();
	return 0;
}

this指针是关键字,只能叫this,不能用that...代替。

我们用日期类来引出隐含的this指针,看上面代码,结果如下图:

上面已经讲过了,对象是去类里面找,但是函数不是去类里面找的,是去公共代码区找的,不同的对象调用的是同一个,那为什么结果会不同呢?

这就印证了this指针的存在。其实类里面有一个隐含的this指针,它指向调用函数的对象。

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

 d1调用,传给this指针的就是d1 的地址; d2调用,传给this指针的就是d2 的地址。

因此,结果才会不同。

this指针是隐含的、默认的,不需要我们自己加上。在函数参数里加会报错。

虽然定义和传递都不能加this,但是我们可以用,像在函数体里面用 this->就没问题。

正因如此,在类外部不能直接用类访问里面的函数:

 因为类里的函数要传对象的地址给this指针,但是这里没有定义对象,只有类名,编译器不知道传什么给this,就报错了。

其实this指针接收实参传过来的地址时,会加个const修饰:

 这里const修饰的是this指针本身,而非它指向的值(解引用访问)。

也就是说 this 指针是不能修改的,this = nullptr(X)

 this指针两道经典面试题

一、this指针存在哪里?

两种经典错误解答:1、在常量取区(代码段)

                                  2、在对象里

答案是存在栈帧中。因为this指针是形参。

二、

 乍看之下,好像两道题是一样的,但其实有坑在里面。

很多人以为p是空指针,空指针解引用访问肯定会崩溃,所以都选了B。

这里就要结合我们上面讲的,p是空指针不假,但是注意:print函数的地址是在公共代码区的(代码段),而非在对象里,编译器去找print函数不会去对象里找,而是直接去代码段,所以这里不发生解引用。p是A的指针,直接传给print函数的this指针,指针可以为空,不会报错,因此正常运行,第一题选C

第二题传指针之前都是一样的,当p指针传给this后,this是nullptr,还原编译器的写法就是:

cout << this->_a << endl;

空指针this解引用访问_a就会崩溃,所以第二题选B

欢迎分享,转载请注明来源:内存溢出

原文地址: https://www.outofmemory.cn/langs/3002752.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-09-27
下一篇 2022-09-27

发表评论

登录后才能评论

评论列表(0条)

保存