目录
1、类的 6 个默认类成员函数
class Date {};
一般情况下,类成员变量均设置为私有或保护,而类成员函数则要分类讨论:若想开源的话就设置成公用,若不想开源的话,就设置成私有或保护,但大多数情况下,类成员函数都会设置成公用的,所以在此处的默认类成员函数均以设置成公用为例,即:无论是自己显式的在类体中实现的还是我们不写编译器自动生成的,都是 公有 的、
注意:
此处所说的类的 6 个默认类成员函数中均包含有 this 指针,这 6 个类成员函数只能在类体内进行 显式 的实现、
2、构造函数
2.1、概念
对于以下的日期类:
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
class Date
{
public:
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;//年
int _month;//月
int _day; //日
};
int main()
{
Date d1;
//d1.Init(2022, 5, 15);
//若此处不进行赋值,则直接打印出来的就是随机值,或者在赋值之前就进行了打印的操作,也会出现随机值、
//除此之外,未初始化可能在一些情况下会导致程序崩溃掉,忘记初始化操作是一件非常正常的事情,所以在 C++ 中添加了构造函数来弥补这一缺陷、
d1.Print();
return 0;
}
2.2、特性
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
class Date
{
public:
//构造函数、
Date()
{
_year = 2022;
_month = 7;
_day = 7;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;//年
int _month;//月
int _day; //日
};
int main()
{
Date d1;
d1.Print(); //2022-7-7
return 0;
}
在 C 语言中,若调用函数不写返回类型的话,则默认的是整型 int 类型,此时,当写 return时,只能返回整型 int 类型的数据,而在 C++ 中,普通的调用函数若不写返回类型的话,就会报错,但是,C++ 中的类里的构造函数是不存在返回值的,构造函数不写返回类型是不会默认为 int类型的,构造函数的返回类型也不能写 void 类型,必须空着,在构造函数的函数体内可以存在 return ,但是不能返回任何数据,这是因为构造函数不存在返回值,注意不要混淆,当我们在类体内定义和声明函数的时候,该函数的函数名一定不可以与默认类成员函数的函数名相同、
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
class Date
{
public:
//构造函数可以重载、
Date() //1.无参构造函数、
{
_year = 2022;
_month = 7;
_day = 7;
}
Date(int year,int month,int day) //2.带参构造函数、
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;//年
int _month;//月
int _day; //日
};
int main()
{
Date d1;
//调用无参构造函数,此时对象的后面没有参数,当调用无参构造函数时,只能写成此时这种情况,不能
//写成 Date d1(); ,否则编译器会报错说是未定义(未声明和定义)对象,因为编译器无法区分到底是在调
//用无参构造函数还是在进行函数的声明,此处报错的原因是编译器把该操作看做是函数的声明了,则就没
//有进行对象的声明和定义,所以在执行 d1.Print(); 时就会报错、
d1.Print(); //2022-7-7
Date d2(2022,7,7); //调用带参的构造函数,此时参数跟在对象的后面、
d2.Print();
return 0;
}
构造函数的存在就保证了实例化出来的对象一定会进行初始化或赋值,因为在实例化对象的过程中,编译器就会自动的调用构造函数、
5、如果类中没有显式声明和定义构造函数,则 C++ 编译器会自动生成一个无参的默认(即是默认又是默认)构造函数,且该无参的默认(即是默认又是默认)构造函数的函数体为空,一旦用户显式声明和定义构造函数的话,则编译器将不再自动生成、
6、无参的构造函数和全缺省的带参构造函数都称为默认构造函数,并且默认构造函数只能有一个、
注意:
无参构造函数、全缺省的带参构造函数、我们没写而编译器自动生成的构造函数(默认构造函数),都可以认为是默认构造函数、
注意:
并不是只有我们不写,编译器自动生成的构造函数(默认构造函数)才叫做默认构造函数,而是我们不需要传参数(实参),编译器就能够自动调用的都看做是默认构造函数,在此要注意,默认构造函数和前面所讲的默认类成员函数的概念要区分开,不要混淆、
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
class Date
{
public:
//构造函数可以重载、
Date() //1.无参构造函数、
{
_year = 2022;
_month = 7;
_day = 7;
}
//2.带参构造函数(全缺省,写成半缺省也是可以构成函数重载的)、
Date(int year=1, int month=1, int day=1)
{
_year = year;
_month = month;
_day = day;
}
//上述两者能够构成函数重载,在语法上是可以的,能够构成函数重载,可以成功编译、
//通过 main 函数中的分析可得,当使用构造函数时,只需要写一个全缺省的带参构造函数即可解决
//问题,当然,我们也可以根据具体情况使用半缺省的带参构造函数、
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;//年
int _month;//月
int _day; //日
};
int main()
{
Date d1; //错误、
//此时,当没有实参进行传参时,只能写成 Date d1; 的情形,那么此时编译器不清楚到底是调用无参
//构造函数还是调用全缺省的带参构造函数,就会出现歧义、
d1.Print();
return 0;
}
比如下面的代码中程序就会报错:
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1; //报错,没有合适的默认(蓝色)构造函数可以使用、
return 0;
}
此时程序就会出现问题,报错为:没有合适的默认构造函数可以使用、
因为我们自己显示的在类体中声明和定义了构造函数(非默认构造函数),所以编译器将不再自动生成无参的默认(即是默认又是默认)构造函数,此时就会报错、
//三种默认(蓝色)构造函数、
//1.没写时编译器自动生成的无参构造函数(即是蓝色又是紫色)、
类名()//实际不可见、
{
//1.对于内置类型的类成员变量不进行处理、
//2.对于自定义类型的类成员变量会自动调用其构造函数来进行初始化或赋值、
}
//2.无参构造函数、
类名()
{
//可以对内置类型的类成员变量进行赋值、
_year = 0;
_month = 0;
_day = 0;
//即使在该无参构造函数的函数体内不对自定义类型的类成员变量进行处理,但编译器也是可以自动对
//自定义类型的类成员变量通过自动调用其构造函数进行初始化或赋值,就是在所谓的初始化列表中对他进
//行了处理、
}
//3.全缺省带参构造函数、
类名(int year = 0, int month = 0, int day = 0)
{
//可以对内置类型的类成员变量进行赋值、
_year = year;
_month = month;
_day = day;
//即使在该全缺省带参构造函数的函数体内不对自定义类型的类成员变量进行处理,但编译器也是可以
//自动对自定义类型的类成员变量通过自动调用其构造函数进行初始化或赋值,就是在所谓的初始化列表中
//对他进行了处理、
}
7、
关于编译器自动生成的无参的默认(即是默认又是默认)构造函数,很多人会有疑惑:在类体中,我们不显示的实现构造函数(包括默认的和非默认的构造函数)的情况下,编译器会自动的生成默认(即是默认又是默认)的无参构造函数,如果类体中只有内置类型的类成员变量的话,则此时编译器自动生成的无参的默认(即是默认又是默认)构造函数看起来并没有起到什么作用,如下所示,对象 d1 调用了编译器自动生成的无参的默认(即是默认又是默认)构造函数,但是对象 d1 的_year/_month/_day,依旧是随机值,也就说在这里编译器自动生成的无参的默认(即是默认又是默认)构造函数并没有什么用?
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
class Date
{
public:
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
d1.Print(); //随机值、
return 0;
}
解答:
C++ 把类型分成内置类型(基本类型)和自定义类型,所谓内置类型就是语法已经定义好的类型:如 int/char/double…等和所有的指针类型;自定义类型就是我们使用关键字:使用class/struct/union 等自己定义的类型,看看下面的程序,就会发现编译器自动生成的无参的默认(即是默认又是默认)构造函数对于内置类型的类成员变量不做处理,而对于自定义类型的类成员变量才会处理、
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
class Time
{
public:
Time() //默认(蓝色)构造函数
{
_hour = 0;
_minute = 0;
_second = 0;
}
int _hour;
int _minute;
int _second;
};
class Date
{
public:
//当我们在Date类内不显示的写构造函数(默认和非默认,蓝色)的时候,编译器会自动生成一个无参的
//默认(即是默认(蓝色)又是默认(紫色))构造函数、
//内置类型/基本类型: int/char/double.. 和所有的指针类型、
//自定义类型: 由关键字class/struct/union..等自己定义的类型、
//编译器自动生成的无参的默认(即是默认(蓝色)又是默认(紫色))构造函数对于内置类型的类成员变
//量不会做处理,而这三者在Date类里都是声明,在实例化对象 d1 时才会定义(可以理解成声明和定义),
//这三者均属于对象 d1 ,而对象 d1 又是局部的,故这三者的数值打印出来就是随机值、
//此时,类成员变量 _time 的类型是自定义类型,所以会被编译器自动生成的无参的默认(即是默认(
//蓝色)又是默认(紫色))构造函数进行处理,具体处理方法就是: 自动调用该自定义类型的类成员变量的
//构造函数进行处理的,此处所讲的构造函数包括默认(蓝色)构造函数和非默认(蓝色)构造函数,目前而言
//,我们只考虑调用该自定义类型的类成员变量的默认(蓝色)构造函数来进行处理,至于调用该自定义类型
//的类成员变量的非默认(蓝色)构造函数的方法在后面的 初始化列表 再进行阐述、
//如果把Time类中的默认(蓝色)构造函数改为非默认(蓝色)构造函数的话,而在Date类中,仍执行
//Time _time;(此处没传实参)的话,那么也是自动调用该自定义类型的类成员变量_time的默认构造函数(
//蓝色)进行初始化,但是由于把Time类中的默认(蓝色)构造函数改为非默认(蓝色)构造函数,所以,就会报
//错说是不存在合适的默认(蓝色)构造函数可以使用,故,自动调用不成功,则此时若想在Time类体中换为
//非默认(蓝色)构造函数后的基础上成功运行的话,就必须对Date类中进行部分修改操作,具体方法在后面
//的 初始化列表 中会有讲解、
void Print()
{
cout << _year << "->" << _month << "->" << _day << endl;
cout << _time._hour << "->" << _time._minute << "->" << _time._second << endl;
}
private:
//内置类型的类成员变量、
int _year;
int _month;
int _day;
//自定义类型的类成员变量、
Time _time; //只是声明,并不是在实例化对象(声明和定义)、
};
int main()
{
Date d1;
d1.Print();
//-858993460-> - 858993460-> - 858993460
//0->0->0
return 0;
}
//若在类体中不显示的实现构造函数(默认和非默认,蓝色)的话,编译器会自动生成一个无参的默认(
//即是默认(蓝色)又是默认(紫色))构造函数,如果类体中存在内置类型的类成员变量的话,此时编译器自
//动生成的无参的默认(即是默认(蓝色)又是默认(紫色))构造函数就不能够处理这些内置类型的类成员
//变量,所以就无法进行正确的初始化或赋值,那么此时就需要自己手动的在类体中显示的实现构造函数(默
//认和非默认,蓝色)来对这些内置类型的类成员变量进行初始化或赋值,而不使用编译器自动生成的无参的
//默认(即是默认(蓝色)又是默认(紫色))构造函数,除此之外,若需要显示传参(实参)进行初始化的话,此
//时也需要在类体中显示的实现构造函数(默认和非默认,蓝色),而不使用编译器自动生成的无参的默认(
//即是默认(蓝色)又是默认(紫色))构造函数,当某一个类体中只有自定义类型的类成员变量的话,此时直
//接使用编译器自动生成的无参的默认(即是默认(蓝色)又是默认(紫色))构造函数就可以完成初始化或赋
//值了,就不再需要手动的在类体中显示的实现构造函数(默认和非默认,蓝色)了、
注意:若在类体中不显示的实现构造函数( 默认 和非 默认 )的话,编译器会自动生成一个无参的默认(即是 默认 又是 默认 )构造函数,如果类体中存在内置类型的类成员变量的话,此时编译器自动生成的无参的默认(即是 默认 又是 默认 )构造函数就不能够处理这些内置类型的类成员变量,所以就无法进行正确的初始化或赋值,那么此时就需要自己手动的在类体中显示的实现构造函数( 默认 和非 默认 )来对这些内置类型的类成员变量进行初始化或赋值,而不使用编译器自动生成的无参的默认(即是 默认 又是 默认 )构造函数,除此之外,若需要显示传参(实参)进行初始化的话,此时也需要在类体中显示的实现构造函数( 默认 和非 默认 ),而不使用编译器自动生成的无参的默认(即是 默认 又是 默认 )构造函数,当某一个类体中只有自定义类型的类成员变量的话,此时直接使用编译器自动生成的无参的默认(即是 默认 又是 默认 )构造函数就可以完成初始化或赋值了,就不再需要手动的在类体中显示的实现构造函数( 默认 和非 默认 )了、
总结:
一般情况下,一个 C++ 类中,都要自己写( 默认 和非 默认 )构造函数,一般只有少数情况可以让编译器自动生成无参的默认(即是 默认 又是 默认 )构造函数:1、类体中都是自定义类型的类成员变量,并且这些类成员变量都提供了默认构造函数(以默认的为主,非默认的话需要初始化列表,要是使用编译器自动生成的无参的默认(即是默认又是默认)构造函数仍然对内置类型的类成员变量不进行处理)、
2、如果存在内置类型的类成员变量,并且这些内置类型的类成员变量在类体中声明时给了缺省值,也可以使用编译器自动生成的无参的默认(即是默认又是默认)构造函数,但是这种方法不灵活,要想改变内置类型的类成员变量的值的话只能通过改变这些内置类型的类成员变量在类体中声明时所给的缺省值,所以该方法不够灵活,当然这种情况相对来说比较少、
3、使用 C++ 库里的 STL 数据结构时可以使用编译器自动生成的无参的默认(即是默认又是默认)构造函数、注意:构造函数(默认和非默认,紫色和蓝色)必须声明和定义在 public 权限中,否则我们无法在主函数中(类外)进行直接访问、
class Time
{
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
Date()
{
_year = 0;
_month = 0;
_day = 0;
_time._hour = 0;
_time.minute = 0;
_time.second = 0;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
cout << _time._hour << '-' << _time._minute << "-" << _time._second << endl;
}
private:
int _year;
int _month;
int _day;
Time _time;
};
在上面的例子中,我们试图在 Date 类中的构造函数中将 _time._hour 、 _time._minute 、_time._second 进行初始化,但这是无法实现的,因为在 Time 类中,我们将这三个类成员变量声明为私有权限,在类外无法进行直接访问,所以自然也就不能在 Date 类中的构造函数中进行初始化、
结论:
各个类中应当有自己的构造函数来初始化自己的类成员变量,我们不应该像上述这样去做,因为可能会受到权限的限制,即使另一个类中声明的类成员变量的权限是 public ,我们也最好不像上述这样去做,最好不要使用这种方法、
拓展:
一、
#define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> using namespace std; class Time { public: Time(int x) //非默认(蓝色)构造函数 { _hour = x; _minute = 0; _second = 0; } int _hour; int _minute; int _second; }; class Date { public: //如果把Time类中的默认(蓝色)构造函数改为非默认(蓝色)构造函数的话,而在Date类中,仍执行 //Time _time;(此处没有传实参)的话,那么也是自动调用该自定义类型的类成员变量_time的默认(蓝色) //构造函数进行初始化或赋值,但是由于把Time类中的默认(蓝色)构造函数改为非默认(蓝色)构造函数, //所以,就会报错说是不存在合适的默认(蓝色)构造函数来使用,故,自动调用不成功,此时若想在Time类体 //中换为非默认(蓝色)构造函数后的基础上成功运行的话,就必须在Date类中进行部分修改操作,具体方法 //在后面的初始化列表中会有讲解,大致方法就如下所示: //初始化列表、 Date() :_time(10) {} void Print() { cout << _year << "->" << _month << "->" << _day << endl; cout << _time._hour << "->" << _time._minute << "->" << _time._second << endl; } private: //内置类型的类成员变量、 int _year; int _month; int _day; //自定义类型的类成员变量、 Time _time; //只是声明,并不是在实例化对象(声明和定义)、 }; int main() { Date d1; d1.Print(); //-858993460-> - 858993460-> - 858993460 //10->0->0 return 0; }
二、
#define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> using namespace std; class Time { public: Time(int x =1) //带参全缺省构造函数、 { _hour = x; _minute = 0; _second =0 ; } int _hour; int _minute; int _second; }; class Date { public: //下面的初始化列表操作不仅对Time类的带参构造函数(非全缺省,也非半缺省)起作用,还对Time类的 //带参全缺省构造函数起作用,还对Time类的带参半缺省构造函数起作用、 //初始化列表、 Date() :_time(10) {} void Print() { cout << _year << "->" << _month << "->" << _day << endl; cout << _time._hour << "->" << _time._minute << "->" << _time._second << endl; } private: //内置类型的类成员变量、 int _year; int _month; int _day; //自定义类型的类成员变量、 Time _time; //只是声明,并不是在实例化对象(声明和定义)、 }; int main() { Date d1; d1.Print(); //-858993460-> - 858993460-> - 858993460 //10->0->0 return 0; }
C++ 11中有关编译器自动生成的无参的默认(即是默认又是默认)构造函数的补丁:
如果某一个类体中不显示的实现构造函数(默认和非默认)的话,则 C++ 编译器会自动生成一个无参的默认(即是默认又是默认)构造函数,它会对内置类型的类成员变量不进行处理,会对自定义类型的类成员变量进行处理,当该类体中既有自定义类型的类成员变量,还有内置类型的类成员变量时,若使用编译器自动生成的无参的默认(即是默认又是默认)构造函数的话,是没有办法对类体中的内置类型的类成员变量进行处理的,但是可以对自定义类型的类成员变量进行处理,那么此时只能手动的在类体中显示的实现构造函数(默认和非默认)来初始化内置类型的类成员变量,那么,如果不想通过该方法的话,有没有其他的方法能够解决这个问题呢,答案是有的,这就需要 C++11 中新添加的有关编译器自动生成的无参的默认(即是默认又是默认)构造函数的补丁来解决,其工作原理为:在类体中对内置类型的类成员变量声明时进行赋值缺省值,只是给编译器自动生成的无参的默认(即是默认又是默认)构造函数进行使用的,此处不是以前所谓的初始化,这是因为,类体中的类成员变量(包括此处所谓的内置类型的类成员变量)均是声明,而不是定义(可以理解为声明和定义),所以不能叫做初始化,因为初始化是指在定义(可以理解为声明和定义)时进行的赋值操作才能叫做初始化,对于部分(该自定义的类的类体1中自动调用的构造函数除形参列表中第一个位置上隐藏的 this 指针外,只有单个参数时)自定义类型的类成员对象在类体2中声明时也可以进行赋缺省值的操作,具体见 C++ 类和对象下的博客、
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
class Time
{
public:
Time()
{
_hour = 0;
_minute = 0;
_second = 0;
}
int _hour;
int _minute;
int _second;
};
class Date
{
public:
void Print()
{
cout << _year << "->" << _month << "->" << _day << endl;
cout << _time._hour << "->" << _time._minute << "->" << _time._second << endl;
}
private:
//C++11中有关编译器自动生成的无参的默认(蓝色和紫色)构造函数的补丁、
int _year =2022;
int _month =7;
int _day =11;
Time _time;
//此处的类成员变量均是声明,不是定义(可以理解成声明和定义),均不为他们开辟内存空间、
};
int main()
{
Date d1;
d1.Print();
//2022->7->11
//0->0->0
return 0;
}
拓展:
虽然该模块叫做 C++11 中有关编译器自动生成的无参的默认(即是默认又是默认)构造函数的补丁,但是并不是说只有我们不写,编译器自动生成无参的默认(即是默认又是默认)构造函数时,此处的在类体中的内置类型的类成员变量的缺省值才起作用,当我们在类体中显示实现构造函数(默认和非默认)时,该缺省值仍然起作用,但要注意的是,当我们在类体中显示实现的构造函数(默认和非默认)的函数体中也对该类体中的同一个内置类型的类成员变量进行赋值时,要知道的是,类体中内置类型的类成员变量的缺省值的优先级是最低的,只有当在类体中显示实现的构造函数(默认和非默认)的函数体内并没有对某一个内置类型的类成员变量赋值时或者是在类体中不显示的实现构造函数(默认和非默认)时,才会采用类体里面内置类型的类成员变量后面的缺省值、
#define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> using namespace std; class Time { public: Time() { _hour = 0; _minute = 0; _second = 0; } int _hour; int _minute; int _second; }; class Date { public: //当在类体中显示的实现构造函数(默认和非默认,蓝色)时,在该构造函数的函数体内只对该类体中的 //内置类型的类成员变量进行操作即可,不需要在函数体内对该类体中的自定义类型的类成员变量进行处理 //,对于自定义类型的类成员变量仍通过自动调用其构造函数进行初始化或赋值、 //1、 //Date(int year=1)//带参全缺省的构造函数(属于默认(蓝色)构造函数)、 //{ // _year = year; //} // Date d1; -> 打印结果: 1->7->11 0->0->0 //2、 //Date(int year)//带参的构造函数(属于非默认(蓝色)构造函数)、 //{ // _year = year; //} // Date d1(2); -> 打印结果: 2->7->11 0->0->0 //3、 //Date()//无参的构造函数(属于默认(蓝色)构造函数)、 //{ // _year=3; //} // Date d1; -> 打印结果: 3->7->11 0->0->0 void Print() { cout << _year << "->" << _month << "->" << _day << endl; cout << _time._hour << "->" << _time._minute << "->" << _time._second << endl; } private: int _year =2022; int _month =7; int _day =11; Time _time; //此处的类成员变量均是声明,不是定义(可以理解成声明和定义),均不为他们开辟内存空间、 }; int main() { Date d1; d1.Print(); return 0; }
8、类成员变量的命名风格
类成员变量的命名风格:我们一般习惯在类成员变量的前面加一个_
,或者在后面加也可以,或者在类成员变量的前面加一个m_ 或者在类成员变量前面加一个m,并把类成员变量的变量名的第一个字母大写,其中,m是(member的意思),一般采取第一种方式,这是为了防止下面情况的出现:
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
class Date
{
public:
//代码可以成功编译,因为此处的year = year;是指构造函数的形参赋给形参,故没有语法错误,可以
//成功编译,并不是构造函数的形参赋给某一个对象的类成员变量year,这是因为在此编译器要满足就近
//原则,在执行year = year;时,编译器要去寻找左边的year,要遵循就近原则,即先从构造函数的函数体内
//去寻找year,此时是可以找到的,故就是所谓的构造函数的形参赋给形参,如果在该构造函数体内找不到,
//才会去类体(类域)中,构造函数函数体外去寻找year,就比如执行_month = month;和_day = day;时,在
//构造函数的函数体中找不到_month和_day,就会去类体(类域)中,构造函数外去寻找,此时就可以找
//到_month和_day了,但是要注意的是,当执行_month = month;和_day = day;时,_month和_day并不是
//指的我们在类体中看到的_month和_day,这是因为,类体中的类成员变量都是声明,不为其开辟内存空间,
//现在对类成员变量的声明赋值是不可以的,因为他们没有实际的内存空间,所以当执行_month = month;
//和_day = day;时,实际上赋值给的_month和_day而是某个对象中的类成员变量_month和_day,当在构造
//函数的函数体中找不到_month和_day,就会去类体(类域)中,构造函数函数体外去寻找,此时就可以找
//到_month和_day,就会告诉编译器这两者一定是属于某个对象的,所以编译器会进行替换,如下所示:
Date(int year, int month, int day)
{
year = year;
_month = month; //this->_month = month;
_day = day; //this->_day = day;
//而执行year = year;时,是不会进行替换的,因为编译器认为是构造函数的形参赋值给形参,
//当然,如果我们手动的把year=year;改成:this->year = year;的话,那么编译器就不再遵循就近原则,
//而是直接认为是把形参year赋值给某一个对象中的类成员变量year,此时是可以完成初始化的,但是由于
//我们在类体中一般不显示的写出this,故当对类成员变量命名的时候要在他们前面加上_ ,这样就不会出
//现歧义了、
}
void Print()
{
cout << year << "-" << _month << "-" << _day << endl;
}
private:
int year;
int _month;
int _day;
};
int main()
{
Date d1(2022,7,12);
d1.Print();
return 0;
}
3、析构函数
3.1、概念
析构函数:与构造函数功能相反,析构函数不是完成对象的销毁;对象销毁工作是由编译器完成的,而对象在销毁时(生命周期结束时)会自动调用析构函数,完成类的一些资源清理工作,所谓资源清理是指清理掉像:动态开辟(堆区),new(堆区),fopen(硬盘)出来的等资源,这些资源需要处理、
问:为什么要有析构函数?为什么析构函数有时候我们还要自己显式的在类体中进行声明和定义呢?
答:当类对象在销毁之前(生命周期结束之前),需要将该类对象中的类成员变量所指向的在堆区上动态开辟的内存空间释放掉,然后再销毁该类对象,如果直接销毁类对象,那么该类对象中的类成员变量在堆区上动态开辟的内存空间仍存在(此处认为该内存空间不会自动被释放,只能由程序员手动释放),并且我们将再也找不到这一块内存空间,这就造成了内存泄漏,为了避免我们在销毁类对象之前忘记对该类对象中的类成员变量所指向的在堆区上动态开辟的内存空间进行释放,所以 C++ 中引入了析构函数的概念,析构函数可以解决这个问题、
其次,编译器自动生成的默认(即是默认又是默认)析构函数只会对自定义类型的类成员变量进行处理,对内置类型的类成员变量不进行处理,而当内置类型的类成员变量如果有动态开辟/new/fopen出来的资源的话,编译器自动生成的默认(即是默认又是默认)构造函数是不会进行处理的,但是这些资源需要被清理掉,所以就需要我们显示的在类体中声明和定义析构函数,其实相当于我们在面向过程中写的 Destory 函数,构造函数有点类似于 Init 函数、
3.2、特性
析构函数是特殊的类成员函数,其特征如下:1、析构函数的函数名含有类名,且在类名前加上字符~,即析构函数的函数名为: ~类名 、2、无参数(除了隐藏的 this 指针之外),无返回值,则析构函数不能构成函数重载,且不写 void 、3、一个类中有且只有一个析构函数,若未在类体中显式声明和定义,则系统会自动生成一个 默认 的析构函数,且该 默认 析构函数的函数体为空,由于析构函数没有参数,故析构函数都是 默认 析构函数、4、对象的生命周期结束时,C++ 编译器系统会自动调用析构函数,且只会调用一次、
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <assert.h>
using namespace std;
class Date
{
public:
Date(int year = 0, int month = 0, int day = 0)
{
_year = year;
_month = month;
_day = day;
arr = (int*)malloc(sizeof(int)* 10);
assert(arr);
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
~Date()
{
cout << "~Date()函数的调用" << endl;
free(arr);//将堆区上(动态开辟)的那块内存空间释放回收、
arr = NULL;
}
private:
int _year;
int _month;
int _day;
int* arr;
};
int main()
{
Date d1;
return 0;
}
5、声明和定义的对象的构造与析构的顺序:
关于构造的顺序是:先声明和定义的对象则先进行构造,后声明和定义的对象则后进行构造;而关于析构的顺序则是:先声明和定义的对象则后进行析构,后声明和定义的对象则先进行析构;即:析构的顺序和数据结构中的栈保持一致,析构顺序和构造顺序 总是 相反的,例如:
int main()
{
Date d1;
Date d2;
return 0;
}
//数据结构中的栈和进程地址空间中的栈区,两者都遵循先进后出,后进先出的原则、
//但是析构顺序和构造顺序 总是 相反的,并不是只有上面这种局部对象d1和d2时才相反、
像上面这样进行对象的声明和定义,则对象 d2 先析构,对象 d1 后析构,而对象 d1 先构造,d2 后构造,嵌套的类中的析构顺序也是如此,我们来看下面这个代码:
6、编译器自动生成的默认(即是默认又是默认)析构函数,对内置类型的类成员变量将不做处理,但是会对自定类型的类成员变量进行处理,通过自动调用该自定义类型的类成员变量的析构函数进行处理、
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <assert.h>
using namespace std;
class Time
{
public:
Time()
{
_hour = 0;
_minute = 0;
_second = 0;
}
~Time()
{
cout << "~Time()函数的调用" << endl;
}
//使用编译器自动生成的默认(蓝色和紫色)析构函数也是可以的,因为该类中的类成员变量均是内置
//类型的,且都不需要处理其资源、
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
Date(int year = 0, int month = 0, int day = 0)
{
_year = year;
_month = month;
_day = day;
arr = (int*)malloc(sizeof(int)* 10);
assert(arr);
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
~Date()
{
cout << "~Date()函数的调用" << endl;
free(arr);
arr = NULL;
}
private:
//内置类型的类成员变量、
int _year;
int _month;
int _day;
//析构函数的调用顺序只看对象,此处的arr不是对象,所以不考虑其调用析构函数的顺序、
int* arr;
//自定义类型的类成员变量、
Time _time;
//若不在Date类中显示的实现析构函数的话,则编译器会自动生成一个默认的(蓝色和紫色)析构函数,
//其对内置类型的类成员变量不做处理,而对自定义类型的类成员变量进行处理,但是由于int* arr是在堆
//区上动态开辟出来的,所以它的资源需要被清除,而由于它属于内置类型的类成员变量,所以目前没有办法
//清除它的资源,故只能在Date类中显示的实现析构函数用来处理内置类型且是在堆区上动态内存开辟出来
//的类成员变量arr,至于内置类型的类成员变量:_year/_month/_day均不需要清除其资源,所以在显示实
//现的析构函数中只对内置类型的类成员变量arr进行处理即可,同理,自定义类型的类成员变量仍是通过自
//动调用其析构函数进行处理,这一点与构造函数是一样的、
};
int main()
{
Date d1;
return 0;
}
//~Date()函数的调用
//~Time()函数的调用
//Date类中声明了Time类类型的(自定义类型)类成员变量,所以要先调用Date析构函数,然后再调
//用Time析构函数、
例题: