一、6个默认成员函数:
二、构造函数:
1.前言:
在我们用自定义的 结构体/类 写各种数据结构时,不管我们定义的是什么样的数据结构,我们必须要做的就是初始化。经常写的人都知道,我们在构建好数据结构准备创建变量使用时,经常会忘记初始化,因为初始化对于我们来说并不是必要的步骤(实际上初始化很重要),之所以说不必要,是因为这个功能并不像 “申请空间”、“删除数据”、“获取元素”等功能那么直观,所以我们常常会想不起来调用初始化函数。而构造函数很好的解决了这个小(大)麻烦。
2.概念:
构造函数是一个特殊的成员函数,函数名字与类相同,在类对象创建时会自动调用(不需要手动调用),并给成员变量赋予合适的初始值,在对象整个生命周期内只调用一次。
3.特性:
构造函数实际上就是对类对象进行初始化
①函数名与类名相同
②函数无返回值
③构造函数可以重载(默认构造函数只能有一个 -- 下文会介绍)
④对象实例化时编译器自动调用构造函数
4.创建类对象格式:
class Date
{
public:
//无参构造函数
Date()
{}
//有参构造函数
Date(int year, int month, int day)
{
_year = year;
_month = month
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1; //调用无参构造函数
Date d2(2008,8,8) //调用带参构造函数
return 0;
}
在调用无参构造函数时要注意:创建好类对象后,后面不要加 () ,因为这样会形成函数声明。
5.默认构造函数:
我们有时不需要自行创建构造函数(显式定义构造函数),编译器也同样可以调用无参构造函数,这种函数就成为默认构造函数,若显示定义了构造函数,则这种默认构造函数不生成。但是默认构造函数不仅仅是这一种,默认构造函数还包括:全缺省构造函数、无参构造函数。但是!默认构造函数只能存在一个!!!(大家先记住这个结论,稍后给大家讲解原因)
①默认构造函数使用规则:
C++将类型分为 内置类型和自定义类型。内置类型就是语言提供的数据类型,如:char、int、long等等,自定义类型就是用户自行定义的类型,如:calss、struct等等。编译器在无显式定义的构造函数时,调用默认构造函数初始化时的判别机制是:对 内置类型 放养(赋予随机值),对 自定义类型的成员变量 则调用它的默认成员函数。
Class Time
{
public:
Time()
{
Cout<<”Time()”<<endl;
//内置类型的成员变量在类中声明时可以给默认值
_hour = 0;
_minute = 0;
_seond = 0;
}
private:
int _hour;
int _minute;
int _seond:
}
class Date
{
private:
int _year;
int _month;
int _day;
Time _t;
};
int main()
{
Date d;
return 0;
}
***Q:为啥默认构造函数只能有一个呢?
***A:首先,默认构造函数分为:①无参的构造函数、②全缺省的构造函数、③无显式定义构造函数时的默认构造函数 。我们可以先解释 ③无显式定义构造函数时的默认构造函数,正是因为没有显示定义的构造函数才产生了此默认构造函数,那么剩下的就是 无参构造函数 和 全缺省构造函数 只能存在一个:因为我们在使用类创建对象时,调用无参构造函数不必传参,而调用全缺省构造函数也可以不传参,这样就会产生分歧,使编译器无从下手,不知道该调用哪一个构造函数。综上所述,默认构造函数只能存在一个。
三、析构函数:
1.前言:
学了构造函数,我们知道一个对象是如何被初始化的。那对应肯定也会有一个函数是让对象消失的!所以,析构函数应运而生。这里要说明的是,析构函数只是对象销毁中的一个环节,并不是销毁整个对象,析构函数只是对对象中申请的资源进行销毁,对象本身的销毁是由编译器完成的。
2.特性:
①析构函数的函数名是类名,然后在前面加上~
②析构函数无参数无返回类型
③一个类只能有一个析构函数,若未显示定义,则编译器会自动生成一个默认的析构函数。析构函数不能重载!
④对象的生命周期结束时,C++系统会自动调用析构函数
从上述代码我们可以得知:一个名为Date的类中包含4个成员变量:3个内置类型的变量和1个类型为Time类的_t对象,那么我们用Date类创建变量后,程序终止时,我们可以看到调用了Time类的析构函数,过程是这样子的:类为Date的对象S在进行销毁时,内置类型由系统自行销毁,而_t是Time类的对象,那么在对这个成员变量销毁时需要调用这个对象对应类的析构函数 -- Time类的析构函数。
3.注意事项:
如果类中没有申请资源,析构函数可以不写,可以直接使用编译器生成的默认析构函数。但是有申请资源情况发生时,一定要写,否则会造成资源泄露
四、拷贝构造函数:
1.概念:
拷贝构造函数只有单个形参,这个形参是对本类类型类对象的引用(一般用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用
2.特征:
①拷贝构造函数是构造函数的一个重载形式
②拷贝构造函数只有一个参数:是对本类类型对象的引用(传值直接会报错,下文解释)
③若未显式定义拷贝构造函数,则编译器会生成默认拷贝构造函数,默认拷贝构造函数是按照内存存储字节序拷贝的 --- 浅拷贝
3.默认拷贝构造函数:
若不再使用自定义的拷贝构造函数,只使用默认拷贝构造函数,这样可行吗?
回答是否定的,当然不可行!默认拷贝构造函数也可以理解为 按值拷贝,对于像日期类的类还好说,因为类中的成员变量都是内置类型的,但是如果类中的成员变量是指针呢?那就会有2个对象同时指向同一块空间,那么程序运行结束调用析构函数时,同一块空间就会被反复释放,这样会导致程序崩溃
五、赋值运算符重载:
1.运算符重载:
运算符重载是具有特殊函数名的函数,也有其返回值类型、参数、函数名字
①格式:
返回值类型 operator操作符(参数)
②注意事项:
**重载操作符必须有类类型的参数
**对于内置类型的操作符,重载后含义不能改变,例如:内置的整形 + ,不能改变含义
** .☆ sizeof . :: ?: 这5个运算符不能重载
③小试牛刀:
class Date
{
public:
//构造函数 -- 初始化
Date(int year,int month, int day)
{
_year = year;
_month = month;
_day = day;
}
//运算符重载
bool operator==(const Date& D)
{
return ((_year == D._year)&&(_month == D._month)&&(_day == D._day))
}
private:
int _year;
int _month;
int _day;
}
int main()
{
Date S1(2000,2,2);
Date S2(1999,9,9);
cout<<(S1 == S2)<<endl;
return 0;
}
2.赋值运算符重载:
①格式:
**参数类型:const T& -- 使用引用可以提高传递效率(返回值类型同理)
**返回(*this):赋值
②赋值运算符位置:
赋值运算符只能重载成类的成员函数,不能重载成全局函数 ---- 赋值运算符如果没有在类中显式定义,那么编译器会自动生成一个默认的,如果在类的外面对赋值运算符冲在了,那么将会产生冲突。
③小试牛刀:
class Date
{
public:
//构造函数 -- 初始化
Date(int year,int month, int day)
{
_year = year;
_month = month;
_day = day;
}
//运算符重载 =
Date& operator=(const Date& D)
{
_year = D._year;
_month = D._month;
_day = D._day;
}
private:
int _year;
int _month;
int _day;
}
int main()
{
Date S1(2000,2,2);
Date S2(1999,9,9);
S1 = S2;
cout<<S1<<endl;
return 0;
}
④ 浅拷贝与深拷贝:
与拷贝构造函数类似,如果用户对于涉及到资源管理的对象依靠编译器提供的默认赋值运算符重载,那么将会导致 内存泄露 和 连续释放同一块空间 导致程序崩溃。