【C++初阶】类与对象(上) --- 类的概念和this指针

在这里插入图片描述

👦个人主页:@Weraphael
✍🏻作者简介:目前学习C++和算法
✈️专栏:C++航路
🐋 希望大家多多支持,咱一起进步!😁
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注✨


一、类的引入

  • 类其实在C语言的结构体就已经接触过,只不过 C++把结构体升级成了类。此时,类名即是类型
  • C语言的结构体只能定义成员变量,而在C++中,结构体内 不仅可以定义变量,还可以定义函数
#include <stdlib.h>
#include <iostream>
using namespace std;

struct Stack
{
	// 成员函数
	void Init(int defaultCapacity = 4)
	{
		a = (int*)malloc(sizeof(int) * capacity);
		if (nullptr == a)
		{
			return;
		}
		capacity = defaultCapacity;
		top = 0;
	}

	void Push(int x)
	{
		a[top++] = x;
	}

	void Destroy()
	{
		free(a);
		a = nullptr;
		top = capacity;
	}

	// 成员变量
	int* a;
	int top;
	int capacity;
};

int main()
{
	Stack st2;
	st2.Init(20);
	
	st2.Push(1);
	
	st2.Destroy();
	return 0;
}

二、类的定义

2.1 类的定义

但以上结构体的定义,在C++中更喜欢用class来代替

【语法模板】

class className
{
	// 类体:是由成员函数和成员变量组成

}; // 分号不能省略
  • class类的关键字className类名(可以根据实际情况取),{}中为类的主体。注意:类定义结束时后面分号不能省略
  • 类体中内容称为类的成员:类的变量称为成员变量;类中的函数称为成员函数

2.2 类的两种定义方式

  1. 如果在类中定义的成员函数,编译器可能会将其当成内联函数处理。但是是否真的是内联函数还是取决于编辑器。

  2. 类也可以让声明和定义分离。类声明放在.h文件中,成员函数定义放在.cpp文件中,注意:在类外定义成员时,需要使用作用域限定符::指明成员属于哪个类域。

在这里插入图片描述

建议大家尽量使用第二种定义类的方法 —> 声明和定义分离

三、类的访问限定符及封装

3.1 访问限定符

#include <stdlib.h>
#include <iostream>
using namespace std;

class Stack
{
	// 成员函数
	void Init(int defaultCapacity = 4)
	{
		a = (int*)malloc(sizeof(int) * capacity);
		if (nullptr == a)
		{
			return;
		}
		capacity = defaultCapacity;
		top = 0;
	}

	void Push(int x)
	{
		a[top++] = x;
	}

	void Destroy()
	{
		free(a);
		a = nullptr;
		top = capacity;
	}

	// 成员变量
	int* a;
	int top;
	int capacity;
};

int main()
{
	Stack st2;
	st2.Init();
	st2.Push(1);
	st2.Push(2);
	st2.Push(3);
	st2.Push(4);

	st2.Destroy();

	return 0;
}

注意:以上代码是编译不过的,原因是:成员函数没有访问的权限(不可访问)

在这里插入图片描述

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

在这里插入图片描述

【访问限定符说明】

  • public修饰的成员在类外可以直接被访问
    在这里插入图片描述

  • protectedprivate修饰的成员在类外不能直接被访问(此处protectedprivate是类似的,直到继承才会有所差别)

  • 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
    在这里插入图片描述

  • 注意:如果class不加访问限定符,默认访问权限为private(私有),而struct默认访问权限为public(公有)

3.2 面试题 — C++中struct和class的区别是什么

  • 由于C++需要兼容C语言,所以C++struct可以当成结构体使用。
  • 另外C++struct还可以用来定义类。和class定义类是一样的,区别是struct定义的类默认访问权限是publicclass定义的类默认访问权限是private
  • 在继承和模板参数列表位置,structclass也有区别(继承章节再谈)

3.3 封装

  • 将属性(变量)和方法(函数)进行有机结合。隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
  • 封装本质上是一种管理,让用户更方便使用类。

比如:对于电脑这样一个复杂的设备,提供给用户的就只有开关机键、通过键盘输入,显示器,USB插孔等,让用户和计算机进行交互,完成日常事务。但实际上电脑真正工作的却是CPU、显卡、内存等一些硬件元件。对于用户而言,只需要知道,怎么开机、怎么通过键盘和鼠标与计算机进行交互即可。因此计算机厂商在出厂时,在外部套上壳子,将内部实现细节隐藏起来,仅仅对外提供开关机、鼠标以及键盘插孔等,让用户可以与计算机进行交互即可。

3.4 面试题 — 面向对象的三大特性

  • 封装
  • 继承
  • 多态

四、类的实例化

  • 类是对对象进行描述的,就像设计图一样,就好比设计图可以建很多房子,但设计图不能住人。所以类并没有分配实际的内存空间

在这里插入图片描述

  • 用类名创建对象的过程,称为类的实例化。
class Date
{
public:
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;  // 声明
	int _month; // 声明
	int _day;   // 声明
};

int main()
{
	Date d; // 类的实例化
	
	return 0;
}
  • 一个类可以实例化出多个对象,实例化出的对象是占用实际的物理空间,存储类的【成员变量】

这就要牵扯类的存储点击跳转

五、类对象模型

5.1 如何计算类对象的大小

计算A的大小

#include <iostream>
using namespace std;

class A
{
public:
	void PrintA()
	{
		cout << _a << endl;
	}
private:
	char _a;
};

int main()
{
	cout << "A的大小为:" << sizeof(A) << endl;

	return 0;
}

【输出结果】

在这里插入图片描述

通过结果似乎可以看出:A的大小似乎只算上了成员变量。但是类既有成员变量,又有成员函数,难道计算类的大小不算上成员函数了吗?那我们继续往下看类对象是如何存储的

5.2 类对象的存储方式

储存方式:只存储成员变量,而成员函数存放在公共的代码段

在这里插入图片描述
这样存储的原因是:

  1. 类的成员变量是在每个对象中都是独立的一份,将成员变量放在私有部分的主要目的是:为了保护这些成员变量,防止外部直接访问和修改,保证代码的安全性和稳定性。所以需要为每个对象分配内存来存储它们。

  2. 成员函数属于类的公共代码段,它们在所有对象中都是相同的,相同的东西没必要每个对象都存储一份。因此可以在代码段中只保存一份,供所有对象共享。

  3. 将成员函数放在公有部分还可以方便地实现类的封装性,使类的使用者不需要了解类的内部实现细节,只需要调用公有函数即可完成相应的操作。

所以,当计算类的大小时,成员函数并不会占用类对象的内存空间。因此,计算类大小时只需要考虑成员变量的大小 即可。

接下来,我们再来分别计算以下类大小

#include <iostream>
using namespace std;

// 类中既有成员变量,又有成员函数
class A1 
{
public:
	void f1() 
	{ ; }
private:
	int a;
};

// 类中仅有成员函数
class A2 
{
public:
	void f2() 
	{ ;}
};

// 类中什么都没有---空类
class A3
{};

int main()
{
	cout << "A1的大小为:" << sizeof(A1) << endl;
	cout << "A2的大小为:" << sizeof(A2) << endl;
	cout << "A3的大小为:" << sizeof(A3) << endl;

	return 0;
}

【输出结果】

在这里插入图片描述

A1大小为4还好说,那为什么类中仅有成员函数空类的大小不应该是0吗?为什么是1呢?

一个类实例化出来的对象,如果是空类或没有成员变量,也必须至少占用一个字节的内存空间。如果一个对象是0字节,那么这个对象就不存在内存中,也就没有意义。

5.3 面试题

  1. 结构体怎么对齐? —> 点击跳转

  2. 为什么要进行内存对齐

对齐的目的是:为了提高内存访问的效率。处理器CPU在读取时,通常按照固定大小的块进行操作,如果没有进行对齐,会导致读取次数增加,因而效率低。

  1. 如何让结构体按照指定的对齐参数进行对齐?能否按照3、4、5即任意字节对齐? —> 点击跳转,查看【修改默认对齐数】

  2. 什么是大小端?如何测试某台机器是大端还是小端?—> 法一:点击跳转 or 法二:点击跳转

六、this指针

6.1 this指针的引出

在这里插入图片描述

对于上述类,会有这样的一个问题:Date类中有 InitPrint两个成员函数,它们没有存储在对象中,而是存储在公共代码段的。那么对于对象d1d2来说,它们调用的是同一个函数InitPrint,那么为什么最后打印的结果会是不同的呢?

其实这里存在 隐式指针叫 this指针它指向当前对象的地址当一个对象调用它的成员函数时,编译器会隐式自动地将该对象的地址作为第一个参数传递给成员函数。因此,在一个类的成员函数内部,可以使用 this指针来访问该对象的成员变量和成员函数。

因此,实际上面的代码的本质是这样的:

在这里插入图片描述

6.2 this指针的特性

  1. this指针不能在形参和实参显示传递,但可以直接在成员函数内部使用

【错误示范】

在这里插入图片描述

  1. this指针的类型:类名* const thisconst修饰this,即成员函数中,不能修改this

在这里插入图片描述

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

在这里插入图片描述

6.3 面试题

  1. this指针存在哪里?

this是存在 里的。原因是:由于编译器会隐式地将该对象的地址作为第一个参数传递给成员函数,那么this是作为形参,而实参传给形参要经历一个过程叫压栈,所以形参是作为栈上的一个变量,所以this指针和普通参数一样存在函数调用的栈帧里

  1. this指针可以为空吗?

this指针不应为空。this指针是一个隐含的指向当前对象的指针,在成员函数内部使用。它指向调用成员函数的对象实例。因此,如果this指针为空,那么对象一定是无实例(不存在),当然了,如果从出题人的角度,this指针可以为空(就是来坑你的),请看下面的题目:

【例子】

问:以下程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行

#include <iostream>
using namespace std;

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

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

	return 0;
}

p调用Print,不会发生解引用。可能有人会觉得这里p不是空指针嘛,空指针解引用不应该会出错?还是那句话,对象里面只存储成员变量,而成员函数存储在公共代码段,它就跟普通的函数调用一样,去找到函数的地址即可,即call地址。然后p会作为实参传递给this指针。虽然this指针是个空指针,但是函数内没有对this指针解引用。所以上述代码正常运行

#include <iostream>
using namespace std;

class A
{
public:
	void PrintA()
	{
		cout << _a << endl;
	}
private:
	int _a;
};
int main()
{
	A* p = nullptr;
	p->PrintA();
	return 0;
}

p调用Print,不会发生解引用。p会作为实参传递给this指针。由于this指针是空的,但是函数内要访问成员变量_a,其代码本质是this->_a,而成员变量_a是存储在对象里的,对空指针进行的使用,导致运行崩溃

  • 7
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值