WHCSRL 技术网

类与对象(上篇)

前言

面向过程和面向对象的初步认识

  1. C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。
  2. C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。

这里要特别说明一下,C++是基于面向对象的,而不是面向对象,因为既可以进行C语言的过程化程序设计,又可以进行以抽象数据类型为特点的基于对象的程序设计,以及面向对象的程序设计(只能说基于面向对象)

类的引入

C语言中,结构体中只能定义变量,在C++中,结构体内不仅可以定义变量,也可以定义函数。

#include<iostream>
using namespace std;
struct Student
{
void SetStudentInfo(const char* name, const char* gender, int age)
{
	strcpy(_name, name);
	strcpy(_gender, gender);
	_age = age;
}
void PrintStudentInfo()
{
	cout<<_name<<" "<<_gender<<" "<<_age<<endl;
}
	char _name[20];
	char _gender[3];
	int _age;
};
int main()
{
	Student s;
	s.SetStudentInfo("Peter", "男", 18);
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

但在C++中,用class来代替struct。

类的定义

class是关键字,className是类的名字,{ }中存放成员函数、成员变量

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

类的两种定义方式:

  1. 声明和定义全部放在类体中,需要注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理(内联函数忘记的可以看上一篇博客)。

在这里插入图片描述

  1. 声明放在.h文件中,类的定义放在.cpp文件中

在这里插入图片描述
一般采用方式 2,声明和定义分开。

类的访问限定符及封装

访问限定符

C++有三种访问限定符
在这里插入图片描述

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

注意:访问限定符只在编译时有用,当数据映射到内存后没有任何访问限定符上的区别

封装

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

  • 常见面试题:C++中struct和class的区别是什么?

答案:C++需要兼容C语言,所以C++中struct可以当成结构体去使用。另外C++中struct还可以用来定义类。和class是定义类是一样的,区别是struct的成员默认访问方式是public,class是的成员默认访问方式是private

面向对象的三大特性:封装、继承、多态(主要是这三个,还有抽象等)。
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节(private),仅对外公开接口来和对象进行交互。

对一个类进行封装之后,我们可以通过接口来和对象进行交互,而不是直接拿出其中的成员变量

类的作用域

类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员,需要使用 :: 作用域解析符指明成员属于哪个类域。

class Person
{
//公有
public:
	void PrintPersonInfo();
//私有
private:
	char _name[20];
	char _gender[3];
	int _age;
};
// 通过 类名 + 作用域符 :: 表示这个函数是Person类中的
void Person::PrintPersonInfo()
{
cout<<_name<<" "_gender<<" "<<_age<<endl;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

类的实例化

类的实例化:用类的类型创建一个对象

  1. 类相当于一张图纸,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它。
  2. 一个类可以实例化出多个对象,类似房屋,实例化出的对象占用实际的物理空间,存储类成员变量。

在这里插入图片描述

在这里插入图片描述

类对象模型

如何计算类的大小

我们先来看类对象的存储方式:
在这里插入图片描述
对于类对象存储方式一:每个对象中成员变量是不同的,但是调用同一份函数,如果按照此种方式存储,当一个类创建多个对象时,每个对象中都会保存一份代码,相同代码保存多次,浪费空间

所以计算机选择存储方式设计二,将成员函数放在公共代码区,避免浪费空间。
注意:类对象的大小求解的规则跟结构体内存对齐规则一样

我们知道了类对象是如何存储,接下来我们来求类对象的大小,我们先来看一个例子?

// 类中既有成员变量,又有成员函数
class A1 {
public:
	void f1(){}
private:
	int _a;
};
// 类中仅有成员函数
class A2 {
public:
	void f2() {}
};
// 类中什么都没有---空类
class A3
{};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

我们已经知道了类对象的存储方式,可轻松得到类 A1 对象的字节大小,但是我们却不知道 A2 对象的字节大小和 A3 对象的字节大小。我们先来看一条规定。

规定:如果一个类没有成员,那么他的对象需要给1byte进行占位标识对象存在,这1byte不存储有效数据。

这样我们就知道了为什么结果如下图所示。
在这里插入图片描述
总结:一个类的大小,实际就是该类中”成员变量”之和,当然也要进行内存对齐注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类

this指针

首先我们来看一个例子:

#include<iostream>
using namespace std;
class Date
{
public:
	void Display()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
	void SetDate(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};
int main()
{
	Date d1, d2;
	d1.SetDate(2018, 5, 1);
	d2.SetDate(2018, 7, 1);
	d1.Display();
	d2.Display();
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

这里定义了d1和d2两个日期对象,那当s1调用SetDate函数时,该函数是如何知道应该设置s1对象,而不是设置s2对象呢?有读者可能会说我们眼睛看的呀,但编译器怎么知道?

  • 其实是这样的,函数第一个参数为this指针,只不过编译器优化了,不让我们看见。

在这里插入图片描述
我们在来看这个规则,就一清二楚了:

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

this指针的特性

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

在这里插入图片描述
经典面试题

  1. this指针存在哪里?

答案:this指针既然是形参,那么它存在栈帧中

  1. this指针可以为空吗?

先来看看下面的程序

问题 1. 下面程序能编译通过吗?(针对p->show()函数
问题 2. 下面程序会崩溃吗?在哪里崩溃(针对p->printA()函数

class A
{
public:
	void PrintA()
	{
		cout << _a << endl;
	}
	void Show()
	{
		cout << "Show()" << endl;
	}
private:
	int _a;
};
int main()
{
	A* p = nullptr;
	//问题1
	p->Show();
	//问题2
	p->PrintA();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

问题 1 答案:正常运行
问题 2 答案:p->PrintA() 崩溃

解答:
在这里插入图片描述
所以面试题 2 的问题this指针是否为空?

要看this指针是否存在解引用

在这里插入图片描述
借的一位小比特的图,哈哈哈

推荐阅读