构造函数
定义
成员变量为私有的,要对他们进行初始化,必须用一个共有成员函数来进行。同时这个函数应该有且仅在定义对象时自动执行一次,这时调用的函数称为构造函数(constructor)。
特征:
- 函数名与类名相同。
- 无返回值。
- 对象构造(对象实例化)时系统自动调用对应的构造函数。
- 构造函数可以重载。
- 构造函数可以在类中定义,也可以在类外面定义。
- 如果类定义中没有给出构造函数,则C++编译器自动生成一个缺省的构造函数,但只要定义了一个构造函数,系统就不会自动生成缺省的构造函数。
无参的构造函数和全缺省的构造函数都认为是缺省的构造函数,并且缺省的构造函数只能有一个。
代码举例
class Date
{
public:
Date()//无参构造函数
{
_year = 2017;//构造函数函数体内赋值
_month = 8;
_day = 8;
}
Date(int year, int month, int day)//带参构造函数
:_year(year) //使用初始化列表赋值
, _month(month)
, _day(day)
{}
//**缺省的构造函数只能有一个。**
//Date(int year = 1900, int month = 1, int day = 1)//全缺省的构造函数
//{
// _year = year;
// _month = month;
// _day = day;
//}
private:
int _year;//成员变量
int _month;
int _day;
};
补充:构造函数,说来就是给成员变量进行初始化。而初始化却有两种方法:
初始化列表、构造函数函数体内赋值。
初始化列表可以参考另一篇博客:详解初始化列表
成员变量的初始化顺序
成员是按照他们在类中声明的顺序进行初始化的,而不是按照他们在初始化列表出现的顺序初始化的。例如:
class Date
{
public:
Date(int year, int month, int day)//带参构造函数
:_month(month)//第二个初始化
, _year(year) //第一个初始化
, _day(day) //第三个初始化
{}
private:
int _year; //第一个定义
int _month;//第二个定义
int _day; //第三个定义
};
拷贝构造函数
定义
创建对象时使用同类对象来进行初始化,这时所用的构造函数成为拷贝构造函数(Copy Constructor),拷贝构造函数是特殊的构造函数。
特征:
- 拷贝构造函数其实就是一个构造函数的重载。
- 拷贝构造函数的参数必须使用引用,使用传值方式会引发无穷递归调用。(为什么?我们保留疑问,后面解答。)
- 若未显示定义,系统会自动生成默认的拷贝构造函数。缺省的拷贝构造函数会,依次拷贝类成员进行初始化。
代码举例
class Date
{
public:
Date()//无参构造函数
{
_year = 2017;
_month = 8;
_day = 8;
}
Date(const Date& d) //拷贝构造函数
{
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;//成员变量
int _month;
int _day;
};
void DateTest()
{
Date d1; //调用无参的构造函数
Date d2(d1);//调用拷贝构造函数
Date d3 = d1;//调用拷贝构造函数
}
思考:
- 拷贝构造函数的参数必须使用引用,使用传值方式会引发无穷递归调用。为什么?
答:如果采用传值得形式,把形参拷贝到实参会调用拷贝构造函数。那么就会形成无休止的递归。因此C++的标准不允许拷贝构造函数传值参数,而必须是传引用或者常量引用。在Visual Studio和GCC中,都将编译出错。 - 为什么下面的对象可以直接访问类的使用成员??
Date(const Date& d) //拷贝构造函数
{
_year = d._year;
_month = d._month;
_day = d._day;
}
答:
在类的成员函数中可以直接访问同类对象的私有/保护成员。
C++的访问限定符是以类为单位的,也就是说在这个单位内的成员可以相互访问。
什么时候调用拷贝构造函数?
1.当用一个类的对象初始化该类的另一个对象。
void DateTest()
{
Date d1; //调用无参的构造函数
Date d2(d1);//调用拷贝构造函数
Date d3 = d1;//调用拷贝构造函数
}
2.如果函数的形参是类的对象,调用函数时,进行形参和实参结合时。
void fun(Date d)
{}
void Test()
{
Date d1;
fun(d1);//函数的形参为类的对象时,当调用函数时,拷贝构造函数被调用.
}
3.如果函数的返回值是类的对象,函数执行完成返回调用者时。
Date fun()
{
Date d;
return d;//函数的返回值是类的对象,返回函数值时,调用拷贝构造函数
}
析构函数
定义
当一个对象的生命周期结束时,C++编译系统会自动调用一个成员函数,这个特殊的成员函数即为析构函数(destructor)
特征
- 析构函数在类名加上字符~
- 析构函数无参数无返回值
- 一个类有且只有一个析构函数。若未显示定义,系统自动生成缺省的析构函数。
- 对象生命周期结束时,C++编译系统会自动调用析构函数。
- 注意析构函数体内并不是删除对象,而是做一些清理工作。
代码举例
在函数退出前打一个断点,可以看出对象生命周期结束时,会调用析构函数。
class Date
{
public:
Date()//无参构造函数
{
cout << "Date()" <<endl;
}
~Date()//析构函数
{
cout << "~Date()" << endl;
}
private:
int _year;//成员变量
int _month;
int _day;
};
int main()
{
Date d1;
return 0;
}
析构函数完成清理工作:
class Array
{
public:
Array(int size)
{
_ptr = (int *)malloc(size*sizeof (int));
}
// 这里的析构函数需要完成清理工作(释放内存)。
~Array()
{
if (_ptr)
{
free(_ptr);
_ptr = 0;
}
}
private:
int* _ptr;
};
什么时候调用析构函数
1.对象生命周期结束,被销毁时(一般类成员的指针变量与引用都i不自动调用析构函数);
2.delete指向对象的指针时,或delete指向对象的基类类型指针,而其基类虚构函数是虚函数时;
3.对象i是对象o的成员,o的析构函数被调用时,对象i的析构函数也被调用。
变量的声明顺序与析构顺序
成员变量的声明顺序与其析构顺序相反,即,先声明的变量后析构,后声明的变量先析构。
class A
{
public:
A()
{
cout << "A()" << endl;
}
~A()
{
cout << "~A()" << endl;
}
};
class B
{
public:
B()
{
cout << "B()" << endl;
}
~B()
{
cout << "~B()" << endl;
}
};
int main()
{
A a;
B b;
return 0;
}
赋值运算符重载函数
定义
赋值运算符的重载是对一个已存在的对象进行拷贝赋值。
C++的重载运算符,由关键字operator和运算符号共同组成,一般而言C++里只要运算符不含”.”都可以重载。
代码举例
class Date
{
public:
Date()//无参构造函数
{
_year = 2017;
_month = 8;
_day = 8;
}
Date(const Date& d) //拷贝构造函数
{
_year = d._year;
_month = d._month;
_day = d._day;
}
Date& operator = (const Date& d)// 赋值操作符的重载
{
if (this != &d)
{
this->_year = d._year;
this->_month = d._month;
this->_day = d._day;
}
return *this;
}
private:
int _year;//成员变量
int _month;
int _day;
};
分析
1、返回值类型
返回类型一般声明为类型的引用,并在函数结尾时返回实例自身的引用(即*this)。这里主要有两个原因:
- 返回引用可以减少一次拷贝构造和析构函数导致不必要的开销,因为返回值类型不是引用,会创建一个匿名对象,这个匿名对象时个右值,获取return的值。
- 可以实现连续赋值。
2、参数
参数声明为const且是一个引用。
- const 是因为赋值运算,不希望修改原来类的状态,同时可以接受const与非const的参数
- 引用则避免了拷贝构造函数
3、判断是否是传入实例与当前实例是同一个,保证自赋值的安全如果相同,直接返回可以减少不必要的操作,同时防止指向的同一资源一起被销毁。
4、赋值前,释放自身的内存。
取地址操作符重载
Date* operator&()
{
return *this;
}
const修饰的取地址操作符的重载
const Date* operator&() const
{
return *this;
}
函数后边的const表明在函数体中不能改变对象的成员,函数的返回值是指向常对象的指针。