在我们C++类中,默认会有6个默认的成员函数,即使我们不写,也至少会有6个成员函数,分别是:构造函数、析构函数、拷贝构造、赋值操作符重载、取地址操作符重载、const修饰的取地址操作符重载。
构造函数
构造函数的功能就是给实例化出来的对象初始化。相当于在对象在被实例化的同时赋予初始值。类也是一种类型,是我们自定义的类型,那我们想要让我们自定义的类型像内置类型一样使用,就肯定离不来我们的默认的成员函数。
对于内置类型,可以在定义时初始化int num = 5
,定义变量num并初始化num为5。那我们的自定义类型也可以,那就要用到我们的构造函数。在我们实例化一个对象时,编译器也会自动调用了我们的默认无参构造函数。
class Date
{
public:
void SetDate(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Display()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d;//调用了默认的无参构造函数
return 0;
}
一个对象的构造函数只能被调用一次,且不能显示调用,也就是说不能通过对象去调用构造函数。
构造函数格式:类名 (参数)
构造函数时可以重载的,当我们自己写了构造函数,编译器就不会帮我们生成一个构造函数。构造函数分为无参构造和有参构造。无参构造和编译器生成的默认构造函数时一样的。由于无参的构造函数并不能很好的初始化对象,所以我们通常写的是有参的构造函数。
//无参的构造函数
Date()
{}
//有参的构造函数
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
我们在创建对象的时候就可以给对象赋初始值:
Date d1; //调用无参构造
Date d2(2021, 2, 3); //调用有参构造
下面我们来测试:
在主函数中创建对象
class Date
{
public:
//无参的构造函数
Date()
{
cout << "Date()" << endl;
}
//有参的构造函数
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
cout << "Date(int, int,int)" << endl;
}
void Display()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
d1.Display();
Date d2(2021, 2, 3);
d2.Display();
return 0;
}
从运行结果可以证明,d1调用了无参构造,d2调用了有参构造。但是我们发现d1对象中的成员都是随机值。为什么会是随机值呢?我们的构造函数对内置类型的成员变量不会处理。那不给成员变量赋初值,那无参构造函数不是就没用了吗?当然不是 ,如果我们类的成员变量中,有自定义类型的成员变量,构造函数会去调用我们自定义类型的默认构造函数,如果没有默认构造函数,编译器就会报错。默认构造函数和构造函数时有区别的,下面会讲到。
注意:以下代码并不是创建对象
//不是创建对象,而是声明了一个函数
//函数名为 d,返回类型为Date类型,参数为空的函数。
Date d();
构造函数中还有一个定义是默认构造函数,默认构造函数就是不用传参数也可以创建对象,默认构造函数有,1、我们自己不写,编译器自动生成的无参构造函数。2、我们自己写了一个无参构造函数。3、在有参构造函数中给形参缺省值(常用常写)
有参带缺省值的默认构造函数:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
cout << "Date(int, int,int)" << endl;
}
总结起来就是,可以不传参数创建对象的类是具有默认构造函数的。但是注意,默认构造函数在一个类中只能出现一个。
总结:
0、一个对象只能调用一次构造函数。
1、构造函数是对象创建的时候编译器会自动调用的默认成员函数。完成的任务是对对象的初始化,非创建对象。
2、构造函数可以重载,分为有参构造和无参构造
3、如果我们没有写构造函数,编译器会自动生成一个无参的构造函数
4、无参的构造函数和全缺省的构造函数都称为默认构造函数,且他们中只能存在一个。
5、无参构造函数只会对自定义类型做初始化,内置类型不做初始化。
6、当成员变量有自定义类型的变量时,所有的构造函数都会自定调用自定义类型的默认构造函数,必须是默认构造函数,如果没有,编译器会报错。
析构函数
析构函数和构造函数的功能类似,析构函数是当一个对象要被销毁时会被调用自身的析构函数,完成类的一些资源清理工作。
析构函数格式:~类名()
析构函数的特点:
1、无参数无返回值
2、一个类只能有一个析构函数,如果没有显示定义,编译器会自动生成一个析构函数。
3、对象生命周期结束编译器会自动调用析构函数
例如在我们的数据结构中,常常会用到一些结构,像栈这种需要结束时释放掉之前malloc开辟的空间,如果不释放就会造成内存泄漏。而在C++中,我们可以将栈的销毁写在析构函数中,这样子就不会忘记调用销毁的函数了。编译器就可以自动调用析构函数完后资源的清理了。
//析构函数
~Stack()
{
if (_sData)
{
free(_sData);
_sData = NULL;
_top = 0;
_capacity = 0;
}
}
但是如果没有涉及到的内存开辟,或者需要释放的类,可以不用写析构函数,使用编译器自动生成的析构函数就可以。自动生成的析构函数有没有作用呢?答案是有的,即使自身没有资源要清理,当存在自定义类型的变量,且该变量需要释放空间。这时候编译器生成的析构函数会自动调用该自定义类型的变量的析构函数。完成释放清理资源的功能。
析构的顺序是后创建的对象先析构
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
cout << _year << "Date()" << endl;
}
~Date()
{
cout << _year << "~Date()" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2020, 2, 3);
Date d2(2021, 2, 3);
return 0;
}
总结:
0、析构函数只能有一个,如果没有显示定义,编译器会自动生成
1、析构函数只能被调用一次,且不能显示调用,只能在对象生命周期结束时会自动调用。
2、当类没有资源释放和清理,可以不写析构函数。
3、编译器默认生成的析构函数并非没有作用,当成员变量存在自定义类型时,会自动调用自定义类型的析构函数。
构造顺序:全局变量->静态变量->普通变量
析构顺序:普通变量->静态变量->全局变量