C++ 类和对象【上篇】(类的定义+类对象模型+this指针)

前言:本篇文章会对C++类和对象中的一部分内容进行介绍,由于类和对象部分内容较多,所以关于类和对象会分三篇文章进行介绍,这是第一篇,本文会主要介绍C++中类的定义、类的作用域、访问限定符以及this指针等内容,会通过代码进行演示,便于对概念加深理解.

🏞️1. 认识面向过程和面向对象

1.在C语言中,我们解决问题时采用了面向过程的思想,关注的是解决问题的过程,分析出求解问题的步骤,通过函数调用逐步的解决问题.

2.在C++中,我们将引入一种新的思想:面向对象,即在C++中,我们将使用面向对象的思想去解决问题,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成.

例如:在一个外卖系统中,我们可以简单解释面向对象与面向过程的区别
在面向过程中:我们可以看到,整个外系统被分成一个个小过程逐一完成.

在面向对象中,整个系统被分为三个对象:商家,骑手,买家,这三个对象各自有自己做的事情,并且彼此之间相互配合一并完成.

在这里插入图片描述
这里只是简单了解面向对象与面向过程,如果想要更深的理解还需在以后的学习和工作中慢慢体会.

⛺2. C++中类的引入

📖2.1 类的引入

我们在C语言中,学习过结构体,即我们可以使用结构体类型来抽象出一个实体,例如:我们可以使用结构体类型来抽象出一个学生类型:

struct studet
{
	char name[20]; //姓名
	int age; //年龄
	long grade; //成绩
	char sex[5]; //性别
	char id[20]; //学号
};

但是在C语言中,结构体中只能定义变量,但在C++中,结构体重不仅可以定义变量,也可以定义函数.

struct student
{
    void StudentInit(const char* name, const char* gender, int age)
    {
        strcpy(_name, name);
        strcpy(_sex, gender);
        _age = age;
    }

    void PrintInfo()
    {
        cout << "姓名:" << _name << endl;
        cout << "性别:" << _sex << endl;
        cout << "年龄:" << _age << endl;
    }


    char _name[20];
    int _age;
    long _grade;
    char _sex[5];
    char _id[20];
};

我们在一个结构体中既定义了变量,又定义了函数.
在C++中,一般使用class来代替struct.

📖2.2 类的定义

在C++中,我们将类似于C语言这样的结构体类型称作.

class className
{
	 
	 //类体:由成员函数和成员变量构成
	 
}; //分号不能省略!!!

其中,class为定义类的关键字,可类比C语言中定义结构体类型的关键字structclassName为类名,在{}中为类的主体,类中的元素称为成员.

类成员:类中定义的数据称为类的属性或者成员变量,类中定义的函数称为成员函数.

类有两种定义方式:

1.声明与定义均在类体中:

class Person
{
public:   //与private均为访问限定符,文章下面会讲解
    void Show()   //显示人的信息
    {
        cout <<"姓名:"<< _name << endl;
        cout <<"性别:"<< _sex << endl;
        cout <<"年龄:"<< _age << endl;
    }
private:
    char* _name;
    char* _sex;
    int _age;
};

即将类中的成员变量成员函数声明与定义均放在类体中.

注意:如果将成员函数放在类体中定义,编译器将会把它当成内联函数处理

2.声明与定义分离:即声明放在.h文件中,定义放在.cpp文件中.

//放在.h文件中
class Person
{
public:
    void Show();   //显示人的信息

private:
    char* _name;
    char* _sex;
    int _age;
};
//放在.cpp文件中
#include"Person.h"

void Person::Show()
{
    cout << "姓名:" << _name << endl;
    cout << "性别:" << _sex << endl;
    cout << "年龄:" << _age << endl;
}

即将声明放在头文件中,定义放在.cpp文件中.

通常,我们更希望采用第二种方式.

🌁3. 类的访问限定符

在前面,我提到了publicprivate叫做类的访问限定符.那类的访问限定符是什么,为什么要有它们?

类中有三个访问限定符:

public(公有)protected(保护)private(私有)

为什么要有这三个访问限定符呢?

class Stack
{
public:
	void StackTop() //取栈顶元素
	{
		
	}
private:
	int* _a;
	int _capacity;
	int _top ;
};

假如我们定义了一个这样的顺序栈类,顺序栈的底层是一个动态开辟的数组,在我们自己写的栈类中,我们定义了一些接口供用户使用.

例如StackTop()取栈顶的元素,但是我们供用户去使用这个栈时,不希望用户去改变底层的元素,比如我们在实现栈时,栈顶top既可以从0开始,也可以从-1开始,但是,用户使用的时候并不知道,所以如果用户随意的去操作底层的数组,那么可能就会带来问题.

所以我们需要访问限定符来解决这样的问题,我们需要选择性的将类中的元素供用户访问,例如,我们用private来修饰一些类自身的属性或者不想让用户直接访问的数据,用户就不能在外部随意访问类中被private修饰的数据.

public来修饰想让用户直接访问的数据.

访问限定符的格式即访问限定符 + :
访问限定符的作用域从此限定符开始下一个限定符出现.

访问限定符说明:

  • public修饰的成员在类外可以直接被访问
  • protectedprivate修饰的成员在类外不能直接被访问
  • 如果不写访问限定符,class定义的类默认权限为私有,struct默认为公有

我们来看一个面试题:
C++中struct和class的区别是什么?

答:C++兼容C语言,所以C++中struct可以当作结构体使用. C++中也可以用struct来定义类,和class定义的类是相同的,区别就是:如果不写访问限定符,class定义的类默认权限为私有,struct默认为公有.

🌅4. 封装

我们在一开始学习C++时,应该就会看到书上或者老师提到一个问题:

面向对象的三大特性:封装,继承,多态

在类和对象这里,我们只了解封装特性:

封装:将数据和操作数据的方法进行有机结合隐藏对象的属性和实现细节,仅仅对外公开接口来和对象进行交互.

怎么去理解呢?

这就好比我们在日常生活中,我们有时可能会去到旅游景点游玩时,如果这些旅游景点不加以任何的限制,任何人任何时间可以随意进出并且可以随意做任何事情,那这样景区可能就会遭到破坏,所以我们就设立了栅栏还有门将景区圈起来,这就是一种封装,但景区是给人逛的,也不能不让任何人进入,于是我们就设立了门票及一些游玩规则,可以通过购买门票进入景区内游玩.

其实,类的封装也是一样,我们把类的数据和方法封装在一起,不让别人访问的,我们用protected/private修饰,想让外部访问的,用public修饰成员方法让外部访问一些内部的属性.

所以,封装本质上是一种管理

🌠5. 类的作用域

定义了一个新的作用域,类的所有成员都在类的作用域中,当在类外定义成员时,需要加上:作用域限定符.

class Person
{
public:
    void Show();   //显示人的信息
private:
    char* _name;
    char* _sex;
    int _age;
};

void Person::Show() //在类外定义类成员,加上 : 作用域限定符
{
    cout << "姓名:" << _name << endl;
    cout << "性别:" << _sex << endl;
    cout << "年龄:" << _age << endl;
}

类的作用域{}即结束.

🌌6. 类的实例化

  1. 当我们定义了一个类时,此时的类只是一个类型,即类类型,类中成员变量也只是一个声明,并没有创建出实体,就好比我们有int类型,在使用时我们需要用int类型去创建变量.类也是如此,类可以通过实例化出对象来创建实体.

在这里插入图片描述

  1. 类就好比一个模型,我们可以通过这个模型创造出来不同许许多多的实体,就例如我们会用图纸来盖房子一样,我们可以用一张图纸去建很多不同的房屋建筑.

图纸就好比类,房屋就像对象,类作为模型可以实例化出多个不同的对象.

类实例化出的对象占用实际的物理空间存储类成员变量

3.类实例化对象的方式与在C语言中定义结构体变量很类似,只不过,在C++中,可以省略前面的classstruct.

class Person
{
public:
    void Show()  //显示人的信息
    {
        cout << "姓名:" << _name << endl;
        cout << "性别:" << _sex << endl;
        cout << "年龄:" << _age << endl;
    }
public:   //由于下面需要访问成员,所以在这里暂时将类属性设成公有,但一般不要这么做
    const char* _name;
    const char* _sex;
    int _age;
};

void Test()
{
    Person p1;  //用类实例化对象  p就称为Person实例化出的对象
    
    p1._name = "张三";
    p1._sex = "男";
    p1._age = 10;
	p1.Show();
	
    Person p2;  //一个类可以实例化出多个不同的对象
    
    p2._name = "李四";
    p2._sex = "男";
    p2._age = 13;
}

⛵7. 类对象模型

📖7.1 类对象的存储方式

对于类对象的存储方式,在C++中,当类实例化出对象时,对象中只保存成员变量,成员函数放在公共代码区.

这样做有什么好处呢?

当我们实例化出多个对象时,每个对象的成员属性不同,但是在与外部交互时,会调用同一个函数,如果将函数在每个对象中都存储一份时,就会浪费空间.
在这里插入图片描述

利用这种存储方式,可以节省不必要的空间,提高效率.

📖7.2 类对象的大小

类中既有成员变量,又可以有成员函数,那如何计算一个类的大小?

我们刚才已经了解,类中的成员函数会放在公共代码区,供所有对象公有,所以在计算类对象的大小时,其实计算的就是成员变量所占空间的大小.

接下来我们用几个实例来分析:

class A
{
//既有成员函数,也有成员变量
public:
	void show()
	{

	}
private:
	char _a;
};

根据我们所分析,只计算成员变量的大小,即大小为1

class A
{
//只有成员函数,没有成员变量
public:
	void show()
	{

	}
};

如果按照我们所想的,这个类的大小应该为0,但是当我们在编译器上用sizeof计算它的大小时
在这里插入图片描述
答案为1,这是为什么?

其实,如果类中没有成员变量,编译器也会为类分配一个字节大小的空间,来标记这是一个存在的类.

class A
{

};

所以,即便类为空时,也会有一个字节的大小.

🌇8. this指针

📖8.1 this指针的定义

我们知道,通过一个类可以实例化出多个对象,而且各个函数共用一份成员函数,但是,每个对象的属性是不同的,当不同对象分别调用公共的成员函数访问对象内部属性时,编译器怎么知道该去访问哪个对象的属性呢?

class A
{
public:
    //如何知道传进来的a是初始化给a1还是a2
	void Init(int a)
	{
		_a = a;
	}

	void show()
	{
		cout << _a << endl;
	}
private:
	int _a;
};

int main()
{
	A a1;
	A a2;
    
	a1.Init(10);
	a2.Init(20);

	a1.show();
	a2.show();

	return 0;
}

我们运行这段代码,并打开反汇编:
在这里插入图片描述
我们发现,在调用Init()函数时,编译器将a1的地址也传了进去.
也就是说,在函数内部,是通过指针的形式来访问成员变量,这样就可以将不同的对象区分.

//真实的调用其实是这样的
a1.show(&a1);
a2.show(&a2);

这就是C++中的this指针

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

//实质上,这里有一个指向当前对象的指针,这里只是做演示,this指针不能用户自己写
void Init(A* this, int a) 
{
	this->_a = a;
}

📖8.2 this指针的特性

  • this指针的类型:类类型* const 例如:上面的A
类型为: A* const this;

this的指针的指向不能修改.

  • 只能在成员函数内部使用
this指针是成员函数的参数,故作用域仅在函数内部,只能在成员函数内部使用
  • this指针本质上其实是一个成员函数的形参,是对象调用成员函数时,将对象地址作为实参传递给 this形参。所以对象中不存储this指针
  • this指针一般是成员函数的第一个形参,一般由编译器通过ecx寄存器自动传递,不需要用户自己传递.

到这里,我想问读者一个问题,this指针可以为空吗?

class A
{
public:
	void Init(int a)
	{
		_a = a;
	}

	void show()
	{
		
	}
private:
	int _a;
};

int main()
{
	A *a = nullptr;

	a->Init(10);
	a->show();

	return 0;
}

有这样一段代码,编译器运行时会报错吗?错误出在哪里?

在C++中,nullptr可以调用成员函数的,前提是该成员函数内没有访问成员变量.

在调用成员函数时,由于成员函数被放在了公共代码区,所以当我们在调用时,编译器会直接找到相应函数的地址调用该函数,a指针为nullptr只是让传进去的this指针变为空指针,并不会对函数调用造成影响.所以a->show()这句代码是没有问题的.

但在a->Init(10)中,函数调用正常,但在Init函数中,通过this指针访问了对象的成员变量this->_a = a,但此时的this指针为nullptr,那这就引发了错误.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

沉默.@

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值