您的位置:首页 > 编程语言 > C语言/C++

详解C++类中的6个默认成员函数

2017-08-08 12:37 387 查看

构造函数

定义

成员变量为私有的,要对他们进行初始化,必须用一个共有成员函数来进行。同时这个函数应该有且仅在定义对象时自动执行一次,这时调用的函数称为构造函数(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:
Dat
d19b
e()//无参构造函数
{
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表明在函数体中不能改变对象的成员,函数的返回值是指向常对象的指针。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息