一、构造函数
1、概念:是一个特殊成员函数,名字与类名相同,创建类类型对象时,由编译器自动调用,在对象的生命周期内只且只调用一次,以保证数据成员都有一个合适的初始值。
2、特征
(1)函数名与类名相同
(2)没有返回值
(3)对象构造(对象实例化)时系统自动调用对应的构造函数
(4)构造函数可以重载
(5)构造函数可以在类中定义,也可以在类外定义
(6)如果类定义中没有给出构造函数,则C++编译器会自动产生一个缺省的构造函数;但只要我们定义了一个构造函数,系统就不会生成缺省的构造函数
(7)无参的构造函数和全缺省的构造函数都认为是缺省的构造函数,并且缺省的构造函数只能有一个
a、无参的构造函数
Date()
{}
b、带参的构造函数
Date(int year, int month , int day)
{
_year = year;
_month = month;
_day = day;
}
c、缺省参数的构造函数
Date(int year = 2018, int month = 3, int day = 15)
{
_year = year;
_month = month;
_day = day;
}
d、半缺省的构造函数
Date(int year, int month = 3)
{
_year = year;
_month = month;
_day = 15;
}
3、作用
(1)构建对象
(2)初始化对象
(3)类型转换
二、拷贝构造函数
1、概念:创建对象时使用已经存在的同类对象来进行初始化,这时所用的函数叫拷贝构造函数。拷贝构造函数是特殊的构造函数。
2、特征
(1)拷贝构造函数其实就是构造函数的重载
(2)拷贝构造函数的参数必须使用引用传参,使用传值方式会引发无穷递归调用
(3)如果没有显示定义,系统会默认缺省的拷贝构造函数。缺省的拷贝构造函数会依次拷贝类成员进行初始化
3、使用场景
(1)对象实例化对象
Date d1(2018, 3, 15);
Date d2(d1);
(2)传值方式作为函数的参数
void Test(const Date date)
{}
(3)传值方式作为函数的返回值
Date Test()
{
Date date;
return date;
}
4、实现
Date(const Date& date)
{
_year = date._year;
_month = date._month;
_day = date._day;
}
三、析构函数
1、概念:与构造函数的功能相反,在对象被销毁时由编译器自动调用,完成类的一些资源清理和汕尾工作。
2、特征
(1)析构函数名在类名(即构造函数名)加上字符~
(2)析构函数无参数、无返回值
(3)一个类有且只有一个析构函数。若未显示定义,系统会自动生成缺省的析构函数
(4)对象生命周期结束时,C++编译系统会自动调用析构函数
(5)析构函数体内并不是删除对象,而是做一些清理工作
面试题
怎样理解这里的清理工作?
答:清理的是动态开辟在堆上的内存。
运算符重载
1、功能:增加程序的可读性
2、特征
(1)operator+合法的运算符 构成函数名(重载运算符的函数名:operator<)
(2)重载运算符以后,不能改变运算符的优先级、结合性、操作数个数
面试题
5个C++不能重载的运算符:.* :: sizeof ?: .
四、赋值运算符重载
1、赋值运算符重载与拷贝构造函数比较
(1)拷贝构造函数是创建的对象,使用一个已经存在的对象来初始化这个准备创建的对象
(2)赋值运算符的重载是对一个已经存在的对象进行拷贝赋值
2、实现
Date& operator=(const Date& date)
{
if (this != &date)
{
_year = date._year;
_month = date._month;
_day = date._day;
}
return *this;
}
问题
1、为什么oerator=要用Date&类型的返回值,而不是void?
答:因为Date& 返回的是已经拷贝好了的当前对象,而void返回的是当前对象的地址。我们想要的是拷贝好了的值。
2、if条件判断的是什么?
答:if条件判断的是当前对象和传过来的date是否相等。
3、重载运算符的调用及编译器的处理
深入探索构造函数
类成员变量的初始化方式:
(1)初始化列表
(2)构造函数体内进行赋值
初始化列表以一个冒号开始,接着一个逗号分隔数据列表,每个数据成员都放在一个括号内进行初始化。
#include<iostream>
using namespace std;
class Time
{
public:
Time()
{
cout << "Time()" << endl;
_hour = 0;
_minute = 0;
_second = 0;
}
Time(const Time& t)
{
cout << "Time(const Time& t)" << endl;
_hour = t._hour;
_minute = t._minute;
_second = t._second;
}
private:
int _hour;//小时
int _minute;//分钟
int _second;//秒
};
class Date
{
public:
Date(int year , int month, int day,const Time& t)
{
cout << "Date()-已初始化列表" << endl;
_year = year;
_month = month;
_day = day;
_t = t;
}
Date(int year, int month, int day, const Time& t)
:_year(year)
,_month(month)
,_day(day)
,_t (t)
{
cout << "Date()-未初始化列表" << endl;
}
private:
int _year;//年
int _month;//月
int _day;//日
Time _t;
};
int main()
{
Time t1;
Date d1(2018, 3, 15, t1);
system("pause");
return 0;
}
所以尽量使用初始化列表进行初始化,因为它更高效
必须放在初始化列表里的成员变量:
(1)常量成员变量(const数据成员)
(2)引用类型成员变量
(3)类类型成员变量(没有缺省构造函数的类成员变量)自定义类型的成员变量
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year, int month, int day)
:_day(day)
,_year(year)
,_month(month)
{
cout << "Date()-未初始化列表" << endl;
}
void Display()
{
cout << "year:" << _year << endl;
cout << "month:" << _month << endl;
cout << "day:" << _day << endl;
}
private:
int _year;//年
int _month;//月
int _day;//日
};
int main()
{
Date d1(2018, 3, 15);
d1.Display();
system("pause");
return 0;
}
成员变量按声明顺序依次初始化,而不是初始化列表出现的顺序
尽量将成员变量声明的顺序和初始化列表中出现的顺序写成一致的
五、const修饰类成员
在成员函数后面加const,const修饰this指针所指向的对象,保证了const成员函数的对象在成员函数内不会被改变
class Date
{
public:
Date(int year, int month, int day)
:_day(day)
,_year(year)
,_month(month)
{
cout << "Date()-未初始化列表" << endl;
}
void Display()
{
cout << "Display()" << endl;
cout << "year:" << _year << endl;
cout << "month:" << _month << endl;
cout << "day:" << _day << endl;
}
void Display()const
{
cout << "Display() const" << endl;
cout << "year:" << _year << endl;
cout << "month:" << _month << endl;
cout << "day:" << _day << endl;
}
private:
int _year;//年
int _month;//月
int _day;//日
};
int main()
{
Date d1(2018, 3, 15);
d1.Display();
const Date d2(2018, 3, 16);
d2.Display();
system("pause");
return 0;
}
const使用场景:
(1)const修饰形参,一般和引用同时使用
(2)const修饰返回值
(3)const修饰类数据成员,必须在构造函数的初始化列表中使用
(4)const修饰类成员函数,实际修饰隐含的this指针,表示在类中不可以对类的任何成员进行修改
(5)在const修饰的成员函数中要对类的某个数据成员进行修改,该数据成员定义声明是必须加mutable关键字
面试题:
(1)const对象可以调用const成员函数
不可以调用非const成员函数
(2)非const对象可以调用非const成员函数,也可以调用const成员函数
(3)const成员函数内可以调用其他的const成员函数
不可以调用其他非const成员函数
(4)非const成员函数内可以调用其他非const成员函数,也可以调用其他const成员函数
取地址运算符
不取地址的方法:
(1)返回空
Date* opertor&()
{
return NULL;
}
const Date* opertor&()const
{
return NULL;
}
(2)只声明不定义
Date* opertor&();
宏
优点:
宏常量:增强可维护性
宏函数:增强效率(没有压栈)
缺点:
(1)不方便调试
(2)没有类型安全的检查
(3)可读性差、可维护性差(宏函数)、容易出错
#define Swap(a,b)\//\为换行符
{\
int tmp = a; \
a = b; \
b = tmp;\
}
六、inline函数
以inline修饰的函数叫内联函数,编译时C++编译器会调用内联函数的地方展开,没有函数压栈的开销,内联函数提升程序运行效率
1、inline是一种以空间换时间的做法,省去调用函数额外的开销。所以代码很长或有循环、递归的函数不适宜用内联函数
2、inline对于编译器而言只是一个建议,编译器会自动优化。如果定义为inline的函数体内有循环、递归,编译器优化时会忽略掉内联函数
3、inline必须函数定义在一起,才能成为内联函数,仅仅将inline放在声明前是没用的
4、定义在类内的成员函数默认定义为内联函数
class Date
{
public:
Date(int year, int month, int day)
:_year(year)
, _month(month)
, _day(day)
{}
void Show()//定义在类内的函数默认为内联函数
{
cout << _year << "-" << _month << "-" << _day << endl;
}
inline void Display();
private:
int _year;
int _month;
int _day;
};
inline void Date::Display()//成员函数定义为内联
{
cout << _year << "-" << _month << "-" << _day << endl;
}
inline void test()//全局函数定义为内联
{}