转自:https://blog.csdn.net/aixiaodeshushu/article/details/84657955
文章目录
构造函数
特点
析构函数
特点
拷贝构造函数
特点
赋值运算符重载
特点
const
&及const &重载
在C++98中,如果一个类中什么都没有,简称为空类,系统会自动生成六个默认的成员函数,构造函数、析构函数、拷贝构造函数、赋值运算符重载、&操作符重载、const,在C++11中又加入了控制默认函数(=default , =delete)
首先,大致了解一下六个默认构造函数的作用:
构造函数
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有一个合适的初始值,并且在对象的生命周期内只调用一次
特点
函数名与类型相同
没有返回值(void都不行)
对象实例化时编译器自动调用对应的构造函数
构造函数可以重载
无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个
创建类类型对象时,由编译器自动调用,并且在对象的生命周期内只调用一次
有初始化列表(可带可不带)
如果类中没有显式定义构造函数,编译器会自动生成一个无参的构造函数
不能被const修饰
没有this指针,因为构造函数是创建对象的,都没有对象哪来的this指针
对上述特点详解:
1. 没有返回值
构造函数和析构函数都是非常特殊的成员函数,都没有返回值,这和void是不一样的,void的返回值是空,而不是没有。在程序中创建和销毁一个对象非常特殊,就像人的出生和死亡,而且总是由编译器来来调用这些函数以确保他们的正常执行。如果存在返回值,要么编译器直到如何处理这个返回值,要么就必须由程序员来显式的调用构造函数和析构函数,这样一来,函数的安全性就被破坏了。
2.构造函数的重载
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(2018, 11, 30); //调用带参的构造函数
return 0;
}
注:上述调用无参的构造函数时,对象后面一定不能带括号,否则就成了函数声明
//声明了d3函数,该函数无参,返回一个日期类型的对象
Date d3();
3.无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个
class Date
{
public:
Date()
{}
Date(int year = 2018, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2(2018, 11, 30);
system("pause");
return 0;
}
一编译就会报错
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(2018, 11, 30);
system("pause");
return 0;
}
初始化列表这块这里不做详细解释,以后的博客中会做详细解释
5.不能被const修饰
因为const修饰类的成员函数时,表示该函数不能修改成员变量,而构造函数就是要修改成员变量
析构函数
顾名思义,构造函数是告诉我们一个对象是怎么来的,而析构函数就是告诉我们一个对象是怎么没的
析构函数:与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁的工作是由编译器完成的,而对象在销毁时,自动调用析构函数,完成类的一些资源清理工作
特点
析构函数名是在类名前加上~
没有参数,没有返回值、
一个类有且只有一个析构函数
如果显式没有定义构造函数,则编译器自动生成一个构造函数
对象生命周期解释后,编译器自动调用析构函数
析构函数不是销毁对象,而是完成对象资源的清理工作
如果类中涉及资源的管理,析构函数一定要显式给出
后构造的对象先释放
有this指针
对上述特点详解:
1.一个类只有一个析构函数
因为析构函数没有参数,所以不存在函数重载的问题
2.析构函数不是销毁对象,而是完成对象的清理工作
这里的清理工作指的是如果有动态申请来的空间,析构函数就负责把这部分空间的资源清理,但这个对象直到函数生命周期结束,才会被销毁
3.如果类中涉及资源的管理,析构函数一定要显式给出
因为编译器自己生成的析构函数不会帮你去释放堆上的空间
class SeqList
{
public:
SeqList()
{
_array = NULL;
_capacity = 0;
_size = 0;
}
SeqList(int n, int data)
{
_array = (int *)malloc(sizeof(int) * n);
for (int i = 0; i < n; ++i)
{
_array[i] = data;
}
_capacity = n;
_size = n;
}
~SeqList()
{
if (_array)
{
free(_array);
_capacity = 0;
_size = 0;
}
}
private:
int* _array;
size_t _capacity;
size_t _size;
};
void test()
{
SeqList s1;
SeqList s2(10, 5);
}
int main()
{
test();
system("pause");
return 0;
}
4.后构造的对象先释放
在上述代码中加几句话即可
class SeqList
{
public:
SeqList()
{
cout << this << endl;
_array = NULL;
_capacity = 0;
_size = 0;
}
SeqList(int n, int data)
{
_array = (int *)malloc(sizeof(int) * n);
for (int i = 0; i < n; ++i)
{
_array[i] = data;
}
_capacity = n;
_size = n;
cout << this << endl;
}
~SeqList()
{
cout << this << endl;
if (_array)
{
free(_array);
_capacity = 0;
_size = 0;
}
}
private:
int* _array;
size_t _capacity;
size_t _size;
};
void test()
{
SeqList s1;
SeqList s2(10, 5);
}
int main()
{
test();
system("pause");
return 0;
}
拷贝构造函数
顾名思义,就是创建一个和原对象一模一样的对象,比如双胞胎。
构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),已经存在的类型对象创建新对象时由编译器自动调用
特点
只有一个形参
拷贝构造函数是构造函数的一种重载形式
若显示未定义,系统会默认生成拷贝构造函数(浅拷贝)
涉及资源管理的问题,一定要显示定义拷贝构造函数
拷贝构造函数的参数必须使用引用,如果使用传值会无穷递归调用
拷贝构造函数可以连续赋值
对上述特点详解:
1.若显示未定义,系统会默认生成拷贝构造函数(浅拷贝)
class Date
{
public:
Date()
{}
Date(int year, int month, int day)
:_year(year)
,_month(month)
,_day(day)
{
cout << this << endl;
}
~Date()
{
cout << this << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2108,12,12);
Date d2(d1);
system("pause");
return 0;
}
对于我们的日期类,不存在资源管理的问题。所以不会出现问题
但如果是链表尼?存在资源的管理
class SeqList
{
public:
SeqList(int n, int data)
{
_array = (int *)malloc(sizeof(int) * n);
for (int i = 0; i < n; ++i)
{
_array[i] = data;
}
_capacity = n;
_size = n;
}
~SeqList()
{
if (_array)
{
free(_array);
_capacity = 0;
_size = 0;
}
}
private:
int* _array;
size_t _capacity;
size_t _size;
};
void test()
{
SeqList s1(10, 5);
SeqList s2(s1);
}
为什么会这样??
因为我们编译器自己生成的拷贝构造函数时浅拷贝的方式,也叫值拷贝。
浅拷贝:按内存存储字节序完成拷贝
通俗易懂的说就是,你给我什么东西,我就拷贝什么,你给我地址,我就拷贝地址
所以,如果类中涉及资源的管理,就一定要显示定义拷贝构造函数
2.拷贝构造函数的参数必须使用引用,如果使用传值会无穷递归调用
如果是传值,首先编译器都不会通过
其次,如果传值,就必须要创建一个临时对象。
3.拷贝构造函数可以连续赋值
int a = 10;
int b = 20;
int c = 30;
a = b = c;
对于上述的连续赋值,有没有考虑过是谁赋值谁,然后载赋值谁?
先 c 赋值给 b ,然后 c 再赋值给 a
class Date
{
public:
Date()
{}
Date(int year, int month, int day)
:_year(year)
,_month(month)
,_day(day)
{
}
~Date()
{
}
Date(const Date& d)
{
_year = d._day;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2108,12,12);
Date d2;
Date d3;
d3 = d2 = d1;
system("pause");
return 0;
}
赋值运算符重载
这里直介绍 = 运算符的重载
特点
参数类型最好是引用,可以提高效率
检测是否存在自己给自己加赋值的情况
一个类中如果显式没有给出赋值运算符的重载,编译器会自己生成,同样地,生成的也是按照浅拷贝的方式进行赋值
至少要有一个自定义类型(类类型)的参数
class Date
{
public:
Date()
{}
Date(int year, int month, int day)
:_year(year)
,_month(month)
,_day(day)
{
}
~Date()
{
}
Date(const Date& d)
{
_year = d._day;
_month = d._month;
_day = d._day;
}
Date& operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this; //为了连续赋值
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2108,12,12);
Date d2;
Date d3;
d3 = d2 = d1;
system("pause");
return 0;
}
为什么一定要有一个自定义类型的参数??
因为如果都是内置类型的参数,比如两个int ,= 完全就可以不用重载,就可以很好的进行赋值。自己再重载,岂不是画蛇添足。
const
将const修饰的类成员函数称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明该成员函数中不能对类的任何成员进行修改
class Date
{
public:
Date(int year, int month, int day)
:_year(year)
,_month(month)
,_day(day)
{}
//表示类成员变量都不可以被修改
void Display() const
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2108,12,12);
d1.Display();
system("pause");
return 0;
}
如果必须要修改类成员变量,可以加上multanle关键字
class Date
{
public:
Date(int year, int month, int day)
:_year(year)
,_month(month)
,_day(day)
{}
void Display() const
{
_year += 1;
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
mutable int _year;
int _month;
int _day;
};
int main()
{
Date d1(2108,12,12);
d1.Display();
system("pause");
return 0;
}
&及const &重载
class Date
{
public:
Date* operator&()
{
return this;
}
//因为对象被const修饰不能更改,所以返回值也要被const修饰
const Date* operator&() const
{
return this;
}
private:
int _year;
int _month;
int _day;
};