前言
我相信大家都是从C语言跨入到C++的,C语言时一个面向过程的一门高级语言,而C++则是面向对象的一门高级语言。
可是面向对象这个词,一个一个字都懂,但是组合在一起是不是就不认了呢,来,
让我们看看是怎么样去面对的对象
面向过程和面向对象初步认识
C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。
是不是对面向过程了解了些,来看C++
C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。
用上述的洗衣服来讲:
人具有的属性是负责把衣服和洗衣服倒入洗衣机,而洗衣服的属性就是用来清洗衣服,洗衣机的属性则是负责洗衣服,这四个对象互相交互,这就是C++面向对象。
类
在C语言结构体中只能定义变量,在C++中,结构体内不仅可以定义变量,也可以定义函数,但是在C++中更喜欢用class来代替。
类的定义
class className //空类
{
// 类体:由成员函数和成员变量组成
}; // 一定要注意后面的分号
class为定义类的关键字,className为类的名字,{}中为类的主体
- 注意,跟C语言的struct的定义一样后面要加分号,不能省略
类中的内容称为类的成员: 类中的变量称为类的属性或成员变量;
类中的函数称为类的方法或者成员函数。
类的6个默认成员函数
如果一个类中什么成员都没有,那么就叫空类。
既然空类都称"空"了,就真的什么都没有了吗?并不是,任何类在什么都不写的时候,编译器会自动生产以下6个默认成员函数
默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。
构造函数
通过下面的日期类,可以更好的给大家讲解
#include <iostream>
class Date {
public:
void DateInit(int year, int month, int day) {
_year = year;
_month = month;
_day = day;
}
void Print() {
printf("%d-%d-%d\n", _year, _month, _day);
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
d1.DateInit(2022, 2, 16);
d1.Print();
Date d2;
d2.DateInit(2023, 2, 17);
d2.Print();
return 0;
}
对于Date类,通过上面中的DateInit初始化Date的成员变量。
那要是没有写这个DateInit函数,是不是Date 就不能初始化嘛?
实践出真知,我们将DateInit函数注释掉,再来运行一遍
怎么是随机值呢?
原来是编译器自动会生成初始化函数,而这个函数就叫默认构造函数
但是,这都初始化的什么鬼?哈哈,先把这个问题放放。
概念
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用。能够保证每个数据成员都有一个合适的初始值,并且在对象的生命周期内只调用一次。
构造函数的意义:能够保证对象被初始化。
特性
-
构造函数的函数名和类名是相同的
-
构造函数无返回值
-
构造函数可以重载
-
会在对象实例化时自动调用对象定义出来。
来看下面的代码:
#include <iostream>
class Date {
public:
/* 无参构造函数 */
Date() {
_year = 1970;
_month = 1;
_day = 1;
}
/* 带参构造函数 */
Date(int year, int month, int day) {
_year = year;
_month = month;
_day = day;
}
void Print() {
printf("%d-%d-%d\n", _year, _month, _day);
}
private:
int _year;
int _month;
int _day;
};
int main(void)
{
Date d1; // 对象实例化,此时触发构造,调用无参构造函数
d1.Print();
Date d2(2023, 2, 16); // 对象实例化,此时触发构造,调用带参构造函数
d2.Print();
return 0;
}
运行结果:
上面的中有两种构造函数,一种是带参的构造函数,另一种是无参的构造函数,无参时让类实例化会自动调用无参的构造函数,若传参数时,则会调用带参的构造函数
- 1.注 意: 这里需要注意传无参的时候,Date实例化时,出现Date d3(),这时编译报错,这是因为编译器以为是函数声明,则是声明了d3函数,该函数无参,返回一个日期类型的对象
- 2.注意:这里要调用带参的构造函数,需要传三个参数,若是设了缺省值,也要遵循缺省参数从右往左依次缺省
默认构造函数
如果你没有自己定义构造函数(类中未显式定义),C++ 编译器会自动生成一个无参的默认构造函数。当然,如果你自己定义了,编译器就不会帮你生成了。
无参构造函数、全缺省构造函数都被称为默认构造函数,并且默认构造函数只能有一个!
所以你要是都定义了,语法上他们两个可以同时存在,但是如果有对象定义去调用就会报错。
默认构造函数对内置类型和自定义类型差别
还记得前面的这个随机值嘛
是不是感觉这里编译器生成的默认构造函数好像并没有什么卵用
在讲清楚之前,还得先搞懂内置类型和自定义类型。
内置类型就是语法已经定义好的类型:如 int / char…,
自定义类型就是我们使用 class / struct / union / 自己定义的类型。
接下来,再定义一个类Time就能讲清楚这个问题了
#include<iostream>
using std::cout;
using std::endl;
class Time {
public:
Time()
{
cout << "Time()" << endl;
_hour = 0;
_minute = 0;
_second = 0;
}
private:
int _hour;
int _minute;
int _second;
};
class Date {
public:
/* 无参构造函数 */
Date() {
_year = 1970;
_month = 1;
_day = 1;
}
void Print() {
printf("%d-%d-%d\n", _year, _month, _day);
}
private:
int _year;
int _month;
int _day;
Time _t;
};
int main()
{
Date d1;// 对象实例化,此时触发构造,调用无参构造函数
return 0;
}
运行结果:
结果:对自定义类型处理,会自动调用自己的默认构造函数
在这里发现没有对内置类型和自定义类型统一处理,不处理内置类型成员变量,只处理自定义类型成员变量。
析构函数
构造函数的出现,仍让一个对象初始化,而析构函数的出现就是让一个对象销毁。
- 析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
特性
析构函数是特殊的成员函数,其特征如下:
- 析构函数名是在类名前加上字符 ~。
- 无参数无返回值类型。
- 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
- 对象生命周期结束时,C++编译系统系统自动调用析构函数。
现在用一个Stack类来给大家讲述
都知道栈创建完后,需要主动去释放并清理内存空间的
析构函数的用法:
#include<iostream>
#include<stdlib.h>
using namespace std;
typedef int StackDataType;
class Stack {
public:
// 构造函数 默认给4(利用缺省参数)
Stack(int capacity = 4)
{
_array = (StackDataType*)malloc(sizeof(StackDateType) * capacity);
if (_array == NULL) {
cout << "Malloc Failed!" << endl;
exit(-1);
}
_top = 0;
_capacity = capacity;
}
// 析构函数
~Stack()
{ // 这里就用的上析构函数了,我们需要清理开辟的内存空间(防止内存泄漏)
free(_array);
_array = nullptr;
_top = _capacity = 0;
}
private:
int* _array;
size_t _top;
size_t _capacity;
};
int main(void)
{
Stack s1;
Stack s2(10);
return 0;
}
保证了栈被定义出来就一定被初始化,用完后会自动销毁。以后就不会有忘记调用 destroy 而导致内存泄露的惨案了,这里的析构函数就可以充当销毁的作用。
- 在这里定义了s1和s2,那是先析构 s1 还是先析构 s2?
其实是先析构s2后析构s1,根据栈的原理嘛,先进后出,后进先出 - 现在将自己写的析构函数注释后,编译器生成的析构函数会帮我们destory嘛
答案是不会!结果和构造函数才不多
对于自定义类型的成员变量不作处理,对于自定义类型的成员变量会去调用它的析构函数。
默认析构函数
自动产生的析构函数就真的一点没用了嘛?
其实在一些情况下还是有点用的,有的时候内置类型不需要去销毁且定义了自定义类型,这个时候就不用自己去定义析构函数