写在前面:大家好!我是【AI 菌】,一枚爱弹吉他的程序员。我
热爱AI、热爱分享、热爱开源
! 这博客是我对学习的一点总结与思考。如果您也对深度学习、机器视觉、算法、C++、Python
感兴趣,可以关注我的动态,我们一起学习,一起进步~
我的博客地址为:【AI 菌】的博客
文章目录
- 1. 类与对象
- (1) 类
- (2) 对象
- (3) 句点运算符(.)
- (4) 指针运算符 (->)
- 2. public 和 private
- 3. 构造函数
- (1) 声明与实现
- (2) 如何使用构造函数
- (3) 重载构造函数
- (4) 带默认值的构造函数
- 4. 析构函数
1. 类与对象
假设我们要编写一个程序,来模拟人,如下图这样。
首先你可能想到的是,人具有一些属性,比如:姓名、年龄、性别、兴趣爱好等;
除此之外,还能做一些事情,比如:自我介绍、吃饭、锻炼、购物等,我们可以把做的事情统称为方法。
于是,在程序中,我们可以定义一种结构,这种结构里包含人的属性以及做的事情(方法),从而能够简单的模仿一个人。这种结构就是类。
注:方法就是属于该类的函数。
(1) 类
类的结构如上图所示,它包括属性和方法两个部分。
在使用类时,我们先要对其声明。在声明类时,需要记住以下三点:
- 使用关键字class,在class后定义一个类名
- 在{ }里分别放置若干属性、方法
- 注意结尾要加分号;
下面举例声明一个简单的类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 在自由存储区中实例化的,或者有指向对象的指针。就可以使用指针运算符 (->) 来访问成员属性和方法。
- 对象使用 new 在自由存储区中实例化时,使用 (->) 来访问成员。
Human* pJack = new Human()
pJack->Age = 18;
pJack->Introduce();
delete pTom;
- 当指针指向对象时,使用 (->) 来访问成员。
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.在类中声明
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)
{
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("Tom0", 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天养成计划】
由于水平有限,博客中难免会有一些错误,有纰漏之处恳请各位大佬不吝赐教!