在上一篇内容中,自定义的类里面是存在成员函数的;而这些成员函数都是我们显式定义出来的,而一般情况下,类中还存在几个默认的成员函数:构造函数、析构函数、拷贝构造函数、赋值重载以及取地址重载等,而本节则主要论述前两个函数,构造函数和析构函数!
关于类和对象这部分内容,是有些晦涩的,不易理解的,主要是其中要点过于琐碎,还是应该回归于书本!
在之前我们都是利用Init函数对对象中的数据进行初始化的,而C++中提供了一种较为高效的初始化方式——构造函数。
需要注意的是,构造函数的主要任务是对对象的数据进行初始化操作!
构造函数的名字必须与类名一致,且不能任意命名,以便编译系统识别,且构造函数不具有任何类型不具有返回值!
下面给出一个例子:
class Date
{
private:
int _year;
int _month;
int _day;
public:
Date()
{
_year = 1;
_month = 1;
_day = 1;
}
void Display();
};
void Date :: Display()
{
cout << _year << ":" << _month << ":" << _day << endl;
}
int main()
{
Date d1;
d1.Display();
return 0;
}
其中Date就是一个简易的构造函数!因此对象d1就的数据就被初始化成了1:1:1!
在上述调用过程中,存在以下几点需要注意:
1.在建立类的对象时,类的构造函数会自动调用。在建立对象时系统会为该对象分配存储单元,此时先执行构造函数,就把指定的初值送到有关的数据成员的存储单元中去了。
2.构造函数没有返回值,不需要类型,是定义对象时自动调用不需要人为手动调用!
3.如果用户未定义构造函数,则C++系统会为其生成一个构造函数,只是这个构造函数是空的,也没有参数!
关于第3点,我们不妨将先前的构造函数注释掉,此时则可以很好的观察出这一特性!
所以当我们没有显式定义构造函数的时候,编译器会自动生成1个无参的默认构造函数,显式定义的话,编译器就不再生成了!
Date(int year,int month,int day)
{
_year = year;
_month = month;
_day = day;
}
将构造函数改成这个样子,此时在使用该类去定义对象的时候就需要传递参数了!
int main()
{
Date d1(2022, 10, 4);
d1.Display();
return 0;
}
如果我们将该构造函数改为缺省参数的话,此时传几个参数我们就可以自己决定了,既可以全传也可以不穿,或者传递部分参数。
以下我写一个全缺省的参数的构造函数:
Date(int year=1,int month=1,int day=1)
{
_year = year;
_month = month;
_day = day;
}
构造函数支持重载!
我们知道,函数重载需要保证函数名一致,但是函数的参数类型顺序都要存在差异才可以算的上是函数重载,因为此条性质比较简单,就不进行演示了!
class Date
{
private:
int _year;
int _month;
int _day;
public:
Date()
{
_year = 1;
_month = 1;
_day = 1;
}
Date(int year=1,int month=1,int day=1)
{
_year = year;
_month = month;
_day = day;
}
void Display();
};
此时这种重载是不正确的,编译器会报错,因为当定义对象自动调用构造函数时,编译器并不知道调用哪一个构造函数!因此就存在了二义性!
Box(int a=10,int b=10,int c=10);
Box();
Box(int a,int b);
//.......
当存在以下调用时:
Box box1;
//此时是调用第一个函数还是第二个呢?
Box box2(10,20);
//此时调用第一个函数还是第三个呢?
所以存在默认参数的构造函数时需要注意调用传参时是否存在错误!
默认构造函数:在建立对象的时候不必给出实参的构造函数都被称为默认构造函数!
因此,有了上述概念,我们重新观察日期类:
class Date
{
private:
int _year;
int _month;
int _day;
public:
Date()
{
_year = 1;
_month = 1;
_day = 1;
}
Date(int year=1,int month=1,int day=1)
{
_year = year;
_month = month;
_day = day;
}
void Display();
};
前面提到过,当我们未显式定义构造函数的时候,此时编译器会给我们提供一个构造函数,这个构造函数也被称为默认构造函数,这种构造函数的函数体是空的,参数也没有;所以用户在创建对象的时候如果需要数据成员存在初值,就必须自己定义构造函数!
所以上述代码中两个构造函数都属于默认构造函数!
而一个类中只能存在一个构造函数!
析构函数:是一个特殊的成员函数,他的作用与构造函数相反,它的命名前需要加上“~”,也是没有返回值与类型的!
析构函数的主要作用并不是删除对象,而是在撤销对象占用的内存之前完成的一些清理工作,也就是说当对象要被销毁时或者生命周期结束时会自动调用析构函数!析构函数在一定程度上与构造函数一致,同样是没有函数类型、没有返回值,并且最重要的是没有函数参数的!
class Test
{
private:
int* arr;
int num;
public:
Test(int n);
void Display(int n);
~Test();
};
Test::Test(int n)
{
arr = (int*)calloc(n,sizeof(int));
if (arr == NULL)
{
perror("malloc fail");
return;
}
num = 100;
}
Test::~Test()
{
free(arr);
num = 0;
}
void Test::Display(int n)
{
for (int i = 0; i < n; i++)
{
cout << arr[i] << " ";
}
cout << endl;
}
int main()
{
Test t1(10);
t1.Display(10);
}
测试以上代码,可以得出以下结论:
先构造的后析构,后构造的先析构
class Time
{
public:
Time()
{
cout << "Time()" << endl;
}
~Time()
{
cout << "~Time()" << endl;
_hour = 0;
_minute = 0;
_second = 0;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
private:
// 基本类型(内置类型)
int _year = 1970;
int _month = 1;
int _day = 1;
// 自定义类型
Time _t;
};
int main()
{
Date d1;
Date d2;
return 0;
}
调试这个程序就可以很轻易的看出这一特性!而这一特性也完全与栈的原理一样!
注:
关于构造函数和析构函数也就结束了,而构造函数中还有一种特殊的构造函数,叫做拷贝构造函数,这个在下一篇博客中会介绍!♂️