【C++给我学哭了】类和对象(1)

在这里插入图片描述
因为小编能力有限,可能有好多地方和大家学得有点出入,也欢迎大家在评论区里交谈。
那就开始我们今天的学习吧。

【本节目标】

1.面向过程和面向对象初步认识
2.类的引入
3.类的定义
4.类的访问限定符及封装
5.类的作用域
6.类的实例化
7.类的对象大小的计算
8.类成员函数的this指针

在我们学习C语言的时候,我们写代码是面向过程的,什么是过程呢,就好比我们洗衣服,我们是不是要先拿到我们的衣服,然后放进洗衣机里,导入洗衣液,然后洗衣机自动工作,我们拿出洗完的衣服,这个就是面向过程,那在之后的学习C++过程我们就是面向对象,在洗衣服这个过程中,我们的对象可以是洗衣机和我们人类等对象。

这里大家其实可以类比我们生活中点外卖这个过程,点外卖的对象一个就是店家,一个就是我们用户,还有我们的店家,但是我们的外卖是要一个过程的。首先就要打开软件,然后输入我们喜欢吃的东西,一步一步往后面走。

2.类的引入
在C++中祖师爷就最先引入类,类最早就是先从结构体进行修改。

C语言结构体中只能定义变量,在C++中,结构体内不仅可以定义变量,也可以定义函数。比如:
之前在数据结构初阶中,用C语言方式实现的栈,结构体中只能定义变量;现在以C++方式实现,
会发现struct中也可以定义函数

那什么是类呢????

祖师爷最早在结构体上改,我们结构体的定义需要用到struct,但是在C++上可以用class,但是这两个的用法有一些区别,这个我们后面会讲,那我们C++就是把C语言的结构体升级成了类,升级成类就变得更牛逼(废话),我们之前在结构体里就是不能定义函数,只有几个变量,但是升级成类之后就可以在类里写函数得定义和声明。

我们先来一个最简单得结构体,把它升级成类是怎样得。

struct Student
{
	char name[12];
	int age;
	char sex[5];
};

我们普通定义一个学生信息,这就是C语言得结构体,我们学得是C++,所以在这里结构体也升级成了类。

C++是兼容C语言得。

我们这里来演示一下怎么在C++中定义变量吧(在C语言我们喜欢定义一个变量,但是在C++里它有了它的新名字,那就是我们定义的是对象)。

struct Student
{
	char name[12];
	int age;
	char sex[5];
};

int main()
{
	struct Student s1;//C语言的定义变量
	Student s2;//C++定义对象

	return 0;
}

在C语言中struct Student 是它的类型名,但是在C++里就是Student也是类型名。
难道升级成类之后就这吗???

我们说过在C++类中可以定义函数

下面是我们C语言的怎么初始化变量的。

#include<iostream>
using namespace std;
#include<string.h>
struct Student
{
	char name[12];
	int age;
	char sex[5];
};

int main()
{
	struct Student s1;//C语言的定义变量
	Student s2;//C++定义对象
	strcpy(s1.name, "张三");
	s1.age = 10;
	strcpy(s2.sex, "李四");
	strcpy(s2.name, "王五");
	s2.age = 10;
	strcpy(s2.sex, "小宏");
	return 0;
}

在这里插入图片描述
这里可以看到我们的初始化是写在main函数里的,那我们现在是不是可以专门在类里写一个函数来实现初始化

struct Stu
{
	char name[12];
	int age;
	char sex[5];
	void Init(char* name, int age, char* sex)
	{

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

那我们如果在主函数里调用它的时候,就可以像结构体那样对它进行调用,
在C++中我们叫这些函数为成员函数,那些变量,我们就称作成员变量。

但是这里会产生一个小问题,我们在传参的时候,如果在成员函数中进行操作的时候,我们怎么区分形参和成员变量,所以我们在定义的时候可以进行下面的操作。

struct Stu
{
	char _name[12];
	int _age;
	char _sex[5];
	void Init(char* name, int age, char* sex)
	{

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

我们这样在前面加上_就可以很好的区别了,当然还有在后面加上_,反正根据大家的喜好自行操作。

这样我们先来完善一下我们上面写的初始化函数。

struct Stu
{
	char _name[12];
	int _age;
	char _sex[5];
	void Init(char* name, int age, char* sex)
	{
		strcpy(_name, name);
		_age = age;
		strcpy(_sex, sex);
	}
};
int main()
{

	return 0;
}

我们在完善它的打印函数。

struct Stu
{

	void Init(char* name, int age, char* sex)
	{
		strcpy(_name, name);
		_age = age;
		strcpy(_sex, sex);
	}
	void print()
	{
		cout << _name << _age << _sex << endl;
	}
	char _name[12];
	int _age;
	char _sex[5];
	
};
int main()
{
	struct Stu s1;
	
	return 0;
}

那我们现在如何在主函数内进行对它的访问呢,我们结构体怎么访问。它就怎么访问。

在这里插入图片描述

#include<string.h>
struct Stu
{

	void Init(const char* name, int age, const char* sex)
	{
		strcpy(_name, name);
		_age = age;
		strcpy(_sex, sex);
	}
	void print()
	{
		cout << _name <<" " <<_age <<" "<< _sex << endl;
	}
	char _name[12];
	int _age;
	char _sex[5];
	
};
int main()
{
	struct Stu s1;
	s1.Init("张三", 19, "man");
	s1.print();
	return 0;
}

那C++进行升级,我们来看class关键字怎么个事

class

class和struct的用法其实一样,就是它有了访问限定符,那我们先来看看它的基本用法

class className
{
// 类体:由成员函数和成员变量组成
};  // 一定要注意后面的分号

是不是和结构体的用法特别像。我们将之前的代码改成class,看看有什么变化。
在这里插入图片描述
一编译发现怎么哪里都是问题,那这个时候我们就要引出一个东西就是访问限定符。

类的访问限定符及封装

C++实现封装的方式:用类和对象的属性和方法结合在一起,这让对象更加完善(对象其实就是C语言经常指的的变量),通过访问权限选择性的将其接口提供给外部的用户进行使用。

一共有三种访问限定符,前期我们认为protected和private是一样的。
还有就是public,中文的意思就是公有,保护和私有。

【访问限定符说明】

  1. public修饰的成员在类外可以直接被访问
  2. protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)
  3. 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
  4. 如果后面没有访问限定符,作用域就到 } 即类结束。
  5. class的默认访问权限为private,struct为public(因为struct要兼容C)

前期其实我们可以认为strut和class的作用是一样的,但是我们的祖师爷可是不一样的。

注意:在继承和模板参数列表位置,struct和class也有区别.

那对于我们上面的代我们进行改造一下

#include<string.h>
class Stu
{
public:
	void Init(const char* name, int age, const char* sex)
	{
		strcpy(_name, name);
		_age = age;
		strcpy(_sex, sex);
	}
	void print()
	{
		cout << _name <<" " <<_age <<" "<< _sex << endl;
	}
private:
	char _name[12];
	int _age;
	char _sex[5];
	
};
int main()
{
	struct Stu s1;
	s1.Init("张三", 19, "man");
	s1.print();
	return 0;
}

这样我们的代码就可以编译过去了。

我们再来讲讲访问限定符的一些细节。

访问权限作用域从该访问限定符出现的位置直到下一个访问限定符的出现。
如果后面没有再出现访问限定符的话,就是以}这个为标准,它的结束就是作用域的结束。
还有就是我们class默认都是private(私有)所以我们还是自己规定的好,俗话说的好,自己动手丰衣足食。

我们下面在来讨论一下什么是封装

我们生活中朋友生日,但是朋友又在遥远的地方,这个时候我们会打包礼物,然后封装起来一起寄给我们的好朋友,将礼物打包这个过程就有点类似我们的封装

封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来实现对象的交互。
在我们类中我们就可以将数据进行封装到类的里面。
我们也可以设置成公有,当然也可以是私有

那封装的意义是什么呢

封装的意义就是更好的管理我们的数据,我们使用数据的时候,有些东西需要进行改变,但是有些数据我们不想让他们随意乱改变,所以封装的作用就在这里。

封装本质上是一种管理,让用户更方便使用类。比如:对于电脑这样一个复杂的设备,提供给用
户的就只有开关机键、通过键盘输入,显示器,USB插孔等,让用户和计算机进行交互,完成日
常事务。但实际上电脑真正工作的却是CPU、显卡、内存等一些硬件元件。

其实我们用电脑的时候,里面有很多硬件这些,这也是一个封装。

.类的作用域

类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员时,需要使用 ::
作用域操作符指明成员属于哪个类域。
我们在写成员函数的时候,我们可以直接把定义和声明放在类里面,当然我们也可以不这样写。
声明和定义也可以分离,我们可以举个最简单的打印函数。

test.h

class Stu
{
public:
	void Init(const char* name, int age, const char* sex);
	
	void print();
	
private:
	char _name[12];
	int _age;
	char _sex[5];
	
};

test.cpp

void Stu::print()
{
	cout << _name << " " << _age << " " << _sex << endl;
}
void Stu::Init(const char* name, int age, const char* sex)
{

	strcpy(_name, name);
	_age = age;
	strcpy(_sex, sex);
}

当我们声明和定义分开的时候就要在前面加上域解析符。
为什么要这样写,C++规定如果是类外定义成员,就要指定这个是在哪个类域中。
而我们上面这个都是属于Stu这个类域。

class Person
{
public:
	void PrintPersonInfo();
private:
	char _name[20];
	char _gender[3];
	int  _age;
};
// 这里需要指定PrintPersonInfo是属于Person这个类域
void Person::PrintPersonInfo()
{
	cout << _name << " " << _gender << " " << _age << endl;
}

再举个例子,这样大家应该能看明白。
.类的实例化

大家是不是对实例化这个词特别的陌生,但是我如果说实例化就是开空间,这样讲大家会不会就能有点猜测,没错,我们的类是没有空间的!!!!
只有我们通过实例化出来的对象才是有空间的。

在这里我们可以用一个形象的例子来举例,就是我们其实可以把我们的类想象成图纸一样的东西,定义出一个类它就像一个图纸,我们如果拿这个图纸来造我们的房子,这就是相当于一个实例化的过程,所以我们是不是可以拿这个图纸造很多的房子,同样的道理,我们也是可以拿它实例化很多对象。

在这里插入图片描述
我们上面的箭头指向的过程就是实例化。

类对象模型

我们应该如何来计算类对象的空间(我们这里指的是对象,可不要因为上面给带偏了)

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

int main()
{
	cout << sizeof(A) << endl;
	return 0;
}

在这里插入图片描述
上面的代码求出来发现一个问题,那就是我们这里是不是没有加上函数的字节大小,我们可以在加上一些成员变量再来看看效果。

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

int main()
{
	cout << sizeof(A) << endl;
	return 0;
}

在这里插入图片描述
很明显可以看到我们算的时候是没有把成员函数算上的,而且计算出来的结果也是遵循我们结构体对齐的用法,这里个给大家来计算一个,然后把我们之前结构体内存对齐的规则在来给大家来看看。

  1. 第一个成员在与结构体偏移量为0的地址处。
  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
    注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
    VS中默认的对齐数为8
  3. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
  4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整
    体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

那我们来计算一个结构体大小。

在这里插入图片描述
大家可以根据图来看,然后对照规则,实在不理解大家可以会头看或者直接问小编。

所以我们类对象的大小只是看成员变量的大小,这里小编其实也是有一些理解的,就是我们其实前面说过我们成员函数在类里面会直接变成内联函数,内联函数又是存在代码段中的,代码段我们其实可以理解成一块公共的区域,大家都可以用,那我们就不需要再重复有多余的空间来存储相同作用的内联函数,所以我认为这是一个优化。

下面还有给几个特殊的例子来让大家记得一些特例。

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

class B
{
	
};
class C
{

};
int main()
{
	cout << sizeof(A) << endl;
	cout << sizeof(B) << endl;
	cout << sizeof(C) << endl;
	return 0;
}

在这里插入图片描述
在这里我们可以看到类里面没有成员变量,也是会占空间的,这就是标记一个地点,证明它来过,这是一个特例,大家记住就行。

this指针
为了更好的讲解这个内容,我们受搓一个日期类的函数。

#include<iostream>
using namespace std;
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;
	d1.Init(2023, 10, 23);
	d1.print();
	Date d2;
	d2.Init(2023, 10, 29);
	d2.print();
	return 0;
}

在这里插入图片描述
在这里我们需要思考的一个问题就是Date这个类中有print和Init两个成员函数,那我们是怎么区分是d1调用还是d2调用,他们看起来都是一样,他们会不会调用错误呢。我们来看一下他们的反汇编。

在这里插入图片描述
发现如果我们调用的是d1这个对象,发现它们的地址是一样的,好神奇,这是什么原因呢,真相只有一个,出来吧this指针。

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

其实我们在传参数的时候还会多传一个对象过去,那就是该对象的指针,我们又成为this指针,我们在写函数的时候不能手动的写,这是编译器帮我们做的事,而我们可以在函数内部使用,因为他其实也是函数栈帧上的一个指针。
比如其实上面的代码其实我们就可以写成

#include<iostream>
using namespace std;
class Date
{
public:
	void Init(int year, int month, int day)
	{
		this->_year = year;
		this->_month = month;
		this->_day = day;
	}
	void print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;

};
int main()
{

	Date d1;
	d1.Init(2023, 10, 23);
	d1.print();
	Date d2;
	d2.Init(2023, 10, 29);
	d2.print();
	return 0;
}

我们这样写也是ok的,但是我们一般不加上this,心中有this,它就是有this

this指针的特性

  1. this指针的类型:类类型* const,即成员函数中,不能给this指针赋值。
  2. 只能在“成员函数”的内部使用
  3. this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给
    this形参。所以对象中不存储this指针。
  4. this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传
    递,不需要用户传递

时间不早了,我现在这里的时间事10月23的晚上十点半,大家早点休息,我们明天继续学习,加油!!!!!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

在冬天去看海

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

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

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

打赏作者

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

抵扣说明:

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

余额充值