【C++养成计划】类与对象 || 构造函数 || 析构函数(Day10)

写在前面:大家好!我是【AI 菌】,一枚爱弹吉他的程序员。我热爱AI、热爱分享、热爱开源! 这博客是我对学习的一点总结与思考。如果您也对 深度学习、机器视觉、算法、C++、Python 感兴趣,可以关注我的动态,我们一起学习,一起进步~
我的博客地址为:【AI 菌】的博客


1. 类与对象

假设我们要编写一个程序,来模拟人,如下图这样。

首先你可能想到的是,人具有一些属性,比如:姓名、年龄、性别、兴趣爱好等;

除此之外,还能做一些事情,比如:自我介绍、吃饭、锻炼、购物等,我们可以把做的事情统称为方法。
在这里插入图片描述
于是,在程序中,我们可以定义一种结构,这种结构里包含人的属性以及做的事情(方法),从而能够简单的模仿一个人。这种结构就是类。
注:方法就是属于该类的函数。

(1) 类

类的结构如上图所示,它包括属性和方法两个部分。
在使用类时,我们先要对其声明。在声明类时,需要记住以下三点:

  1. 使用关键字class,在class后定义一个类名
  2. 在{ }里分别放置若干属性、方法
  3. 注意结尾要加分号;

下面举例声明一个简单的类Human:

class Human
{
	//属性:
	string Name;
	int Age;
	string Gender;
	//方法:
	void Introduce();
	void eat(string food);
}

通过上面的例子,我们学会了用关键字class,创建一个简单的类Human。在这里,也可以称Human是我们自己创建的一个数据类型,并在其中封装了相应的属性和函数。
注:封装是指将数据以及使用他们的方法进行逻辑组合,这是面向对象编程的重要特征。

(2) 对象

类相当于蓝图,只是声明类并不会对程序的执行有何影响,就如同声明一个函数而不去调用它一样。在程序执行时,对象是类的化身。要使用类的功能,通常需要根据类实例化一个对象,并通过对象访问类的属性和方法。

类似于声明一个变量,实例化一个Human的对象如下:

int a;  //声明一个变量
Human Jack;  //实例化一个对象Jack

同样也可以使用 new 为Human对象动态地分配内存。

回忆一下,我们在 《面试官:指针都不会,我们不需要你这样的人!》中,学习了如何用 new 为int类型的数据动态分配内存。

int* pNum = new int;
delete pNum;

在这里,Human作为我们自己创建的数据类型。那么使用 new 为 Human 对象动态地分配内存如下:

Human* pHuman = new Human(); //动态分配内存给Human对象。new返回分配的内存地址给指针pHuman,也可以说pHuman指向分配的内存
delete pHuman; 

(3) 句点运算符(.)

使用 句点运算符(.) 可以访问属性和成员函数。
比如,还是上面的Human类,我们先实例化一个对象Jack。然后就可以通过 (.) 访问它的属性和成员函数。

Human Jack;
Jack.Age = 18;  //访问属性
Jack.Introduce();  //访问成员函数

除此之外,当有一个指针pJack,它指向Human类的一个实例对象Jack,也可以通过 (.) 来访问成员:

Human* pJack = new Human();
(*pTom).Introduce()

(4) 指针运算符 (->)

如果对象是使用 new 在自由存储区中实例化的,或者有指向对象的指针。就可以使用指针运算符 (->) 来访问成员属性和方法。

  1. 对象使用 new 在自由存储区中实例化时,使用 (->) 来访问成员。
Human* pJack = new Human()
pJack->Age = 18;
pJack->Introduce();
delete pTom; 
  1. 当指针指向对象时,使用 (->) 来访问成员。
Human Jack;
Human* pJack = &Jack;
pJack->Age = 18;
pJack->Introduce()

2. public 和 private

每个人都会有很多个人信息,其中有些可以公开,有些隐私不能公开。那么在类中,我们也可以通过关键字public、private来指定哪些属性和方法是公有的,哪些是私有的。
当类属性和方法声明为公有时,就可以通过对象直接获取它们。当声明为私有时,就只能在类的内部(或其友元)中访问。
下面举一个例子:在现实中,很多人不想公开自己的实际的年龄,因此Huaman类中Age是一个私有成员。当你想向外指出的年龄比实际小3岁时,就可以在公有的方法SetAge中,将岁数减3。

#include<iostream>
using namespace std;

class Human
{
private:
	int Age;
public:
	void GetAge(int InputAge)
	{
		Age = InputAge;
	}
	int SetAge()
	{
		return (Age-3);
	}		
};
int main()
{
	Human Tom;
	Tom.GetAge(20);
	
	Human Jack;
	Jack.GetAge(18);
	
	cout<<"Tom的年龄是:"<<Tom.SetAge()<<endl;
	cout<<"Jack的年龄是:"<<Jack.SetAge()<<endl;
	return 0;
} 

运行结果:
在这里插入图片描述
在程序中,如果直接通过 Tom.Age 来访问Age,编译时会报错。因为Age是私有成员。

3. 构造函数

简单来说,构造函数是一种特殊的函数(方法),在创建对象时被调用

(1) 声明与实现

构造函数有两个特点:

  1. 函数名与类名相同。
  2. 函数不返回任何值。

构造函数有两种声明方式:既可在类中,也可以在类外。

1.在类中声明
class Human
{
public:
	Human()
	{
		函数体;
	}
};

2.在类外声明
class Human
{
public:
	Human();  //构造函数声明
};
Human::Human()  //定义构造函数
{
	函数体;
}

程序中,(::) 被称为作用域运算符

(2) 如何使用构造函数

构造函数总是在创建对象时被调用,这让构造函数成为类成员变量初始化的理想场所。
下面举个例子:使用构造函数初始化类成员变量

#include<iostream>
using namespace std;

class Human
{
private:
	string Name;
	int Age;
	
public:
	Human()
	{
		cout<<"构造函数:初始化变量Age"<<endl;
		Age=0;
	}
	void SetName(string InputName)
	{
		Name = InputName;
	} 
	void SetAge(int InputAge)
	{
		Age = InputAge;
	}
	void Introduce()
	{
		cout<<"I am " + Name<< " and am "<<Age<<" years old"<<endl;
	}
};

int main()
{
	Human Man;
	Man.SetName("Jack");
	Man.SetAge(18);
	
	Man.Introduce();
	return 0; 
}

运行结果:
在这里插入图片描述
可见,我们可以使用构造函数,用来初始化变量。

(3) 重载构造函数

与普通函数一样,构造函数也能重载。因此可创建一个将姓名作为参数的构造函数。如下所示:

class Human
{
public:
	Human()
	{
		默认构造函数;
	}
	Human(string InputName)
	{
		重载构造函数;
	}
}

下面举一个实际的例子:定义了两个不同的重载构造函数,在创建Human对象时要提供姓名或年龄:

#include<iostream>
using namespace std;

class Human
{
private:
	string Name;
	int Age;
	
public:
	Human()
	{
		cout<<"调用构造函数:初始化变量Age"<<endl;
		Age=0;
	}
	Human(string InputName)
	{
		cout<<"调用重载构造函数1:初始化变量Name"<<endl;
		Age = 0; 
		Name= InputName; 
	}
	Human(string InputName, int InputAge)
	{
		Name = InputName;
		Age = InputAge;
		cout<<"调用重载构造函数2:初始化变量Age、Name"<<endl;
	}
	void SetName(string InputName)
	{
		Name = InputName;
	} 
	void SetAge(int InputAge)
	{
		Age = InputAge;
	}
	void Introduce()
	{
		cout<<"I am " + Name<< " and am "<<Age<<" years old"<<endl<<endl;;
	}
};

int main()
{
	Human Man;  //实例化对象
	Man.SetName("Jack");
	Man.SetAge(18);
	Man.Introduce();
	
	Human Woman("Lucy");  //实例化对象,并提供了一个参数
	Woman.SetAge(16);
	Woman.Introduce();
	
	Human Kid("Tom", 6);   //实例化对象,并提供了两个参数
	Kid.Introduce();
	return 0; 
}

运行结果:
在这里插入图片描述
这上面这个例子中,一共声明了三个构造函数:1个默认构造函数、2个重载构造函数。

那么,在实例化一个对象时,到底是自动调用哪一个构造函数呢?
从上面的例子可以得出答案:依据实例化对象时提供的参数,编译器会自动调用相应的构造函数。比如实例化时没有参数,就调用默认构造函数;有1个参数Name,则调用重载构造函数1;有两个参数Name和Age,则调用重载构造函数2。

这个问题再拓展一下:当类中没有默认构造函数,那么在实例化对象时,必须提供对应的参数Name或Age。更具提供参数,选择调用重载构造函数1或2。

注:如果对重载函数还不太熟悉的同学,得先加加餐:【C++养成计划】深入浅出——函数(Day6)

(4) 带默认值的构造函数

和普通函数一样,构造函数也可以带参数。如下所示:

class Human()
{
private:
	string Name;
	int Age;
public:
	Human(string InputName, int InputAge=18)
	{
		Name = InputName;
		Age = InputAge;
	}
};

这样的话,我们在实例化对象的时候,就要两种方式:

Human Man("Tom");
Human Woman("Lucy", 16); //这时16就会覆盖默认值

4. 析构函数

与构造函数一样,析构函数也是一种特殊的函数。不同的是,析构函数在对象销毁时自动被调用。

声明方式和构造函数也近似,只是在前面加了一个波浪号(~)。如下所示:

1.类内声明
class Human
{
public:
	~Human()
	{
		析构函数;
	}
}2.类外声明
class Human
{
public:
	~Human();
};
Human::~Human()
{
	构造函数;
}

何时使用析构函数
每当对象不再在作用域内或通过delete被删除,进而被销毁时,都将调用析构函数。这使得析构函数是重置变量、释放动态分配内存和其他资源的理想场所。

小结:
在对象创建时,编译器会自动调用构造函数;对象销毁时,将自动调用构造函数。
注:构造函数、析构函数都是编译器自动调用的。


相关文章我都放这里了【C++21天养成计划】

由于水平有限,博客中难免会有一些错误,有纰漏之处恳请各位大佬不吝赐教!

猜你喜欢

转载自blog.csdn.net/wjinjie/article/details/106431927