1.构造函数
2.析构函数
3.拷贝构造函数
4. 赋值操作符重载
5. 默认拷贝构造与赋值运算符重载的问题
6. const成员函数
7. 取地址及const取地址操作符重载
在一个空类中如果什么成员都没有,依然会有6个默认的成员函数
1.构造函数
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员 都有 一个合适的初始值,并且在对象的生命周期内只调用一次。
构造函数的特点
1.函数名与类名相同。
2.无返回值。
3.构造对象的时候系统会自动调用构造函数。
4.可以重载。
5.可以在类中定义,也可以在类外定义。
6.如果类中没有给出构造函数,编译器会自动产生一个缺省的构造函数,如果类中有构造函数,编译器就不会产生缺省构造函数。
7.全缺省的构造函数和无参的构造函数只能有一个,否则调用的时候就会产生冲突。
8.没有this指针。因为构造函数才是创建对象的,没有创建对象就不会有对象的首地址。
//给出一个日期类
class Date
{
public:
Date()//无参构造
{}
Date(int year = 1900, int month = 1, int day = 1)//全缺省的构造函数
{
m_year = year;
m_month = month;
m_day = day;
}
//注意无参构造和全缺省的构造智能有一个,否则调用时会出现混乱
Date(int year, int month, int day)//带参数构造
{
_year = year;
_month = month;
_day = day;
}
void my_printf()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
};
void Test()
{
Date d1;//通过无参构造创建对象时不用带括号,否则就是一个函数声明。
Date d2(2018, 9, 1);
d2.my_printf();
}
int main()
{
Test();
system("pause");
return 0;
}
2.析构函数
- 析构函数函数名是在类名加上字符~。
- 无参数无返回值(但有this指针)
- 一个类有且只有一个析构函数,所以肯定不能重载。若未显示定义,系统会自动生成 缺省的析构函数
- 对象生命周期结束时,C++编译系统系统自动调用析构函数。
- 注意析构函数体内并不是删除对象,而是做一些清理工作。(比如我们在构造函数中动态开辟过一段空间,函数结束后需要释放,而系统自动生成的析构函数才不管内存释放呢,所以需要人为地写出析构函数)
注意:对象生命周期结束后,后构造的对象先释放。
typedef int DataType;
class SeqList
{
public:
SeqList(int capacity = 10)
{
_pData = (DataType*)malloc(capacity * sizeof(DataType));
assert(_pData);
_size = 0;
_capacity = capacity;
}
~SeqList()
{
if (_pData)
{
free(_pData); // 释放堆上的空间
_pData = NULL; // 将指针置为空
_capacity = 0;
_size = 0;
}
}
private:
DataType* _pData;
size_t _size;
size_t _capacity;
};
int main()
{
SeqList s1;
}
//当涉及系统资源的时候析构最好自己给出
3.拷贝构造函数
首先对于普通类型的对象来说,它们之间的复制是很简单的, 而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员变量。
下面看一个类对象拷贝的简单例子。
#include<iostream>
using namespace std;
class CExample
{
private:
int a;
public:
//构造函数
CExample(int b)
{
a = b;
printf("constructor is called\n");
}
//拷贝构造函数
CExample(const CExample & c)
{
a = c.a;
printf("copy constructor is called\n");
}
//析构函数
~CExample()
{
cout << "destructor is called\n";
}
void Show()
{
cout << a << endl;
}
};
int main()
{
CExample A(100);
CExample B = A;
B.Show();
return 0;
}
上面这段代码就是将创建的对象A复制给B,对于相同类型的对象我们可以使用拷贝构造函数来进行复制CExample(const CExample & c)就是我们实现的拷贝构造函数
拷贝构造函数的特点
- 拷贝构造函数是构造函数的一个重载形式,也是一种特殊的构造函数,因此函数名就必须和类名相同。
- 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用
- 若未显示定义,系统生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷 贝,这种拷贝我们叫做浅拷贝,或者值拷贝,既然系统会默认生成,那么我们就不需要自己去实现呢?当然不是。后面我们会对于深浅拷贝做详细的解释。
拷贝构造函数的调用时机
1.当类对象作为参数进行函数传参时调用
void Fun(CExample c )
2.当类对象作为函数的返回值的时候
CExample Fun()
{
CExample temp(0);
return temp;
}
3.当对类对象进行初始化的时候
CExample A(100);
CExample B=A;
关于深浅拷贝
所谓浅拷贝,指的是在对象复制时,只对对象中的数据成员进行简单的赋值,默认拷贝构造函数执行的也是浅拷贝。大多情况下“浅拷贝”已经能很好地工作了,但是一旦对象存在了动态成员,那么浅拷贝就会出问题了,让我们考虑如下一段代码:
#include<iostream>
#include<assert.h>
using namespace std;
class Rect
{
public:
Rect()
{
p=new int(100);
}
~Rect()
{
assert(p!=NULL);
delete p;
}
private:
int width;
int height;
int *p;
};
int main()
{
Rect R1;
Rect R2(R1);
return 0;
}
上面的代码在运行时会出现错误,当使用默认拷贝函数(浅拷贝)对对象进行复制时出现错误。当创建R1时系统在堆上分配了空间,当使用R1复制R2时只是将p的值复制的过去,也就是两个p指向了同一块堆空间,在析构函数调用时就会对同一块空间释放两次,这就是错误原因。我们要的不是两个p有相同的值,而是两个p所指向的空间有相同的值
深拷贝
直接上代码
#include<iostream>
#include<assert.h>
using namespace std;
class Rect
{
public:
Rect()
{
p=new int(100);
}
Rect(const Rect &r)
{
width=r.width;
height=r.height;
*p=*(r.p);
}
~Rect()
{
assert(p!=NULL);
delete p;
}
private:
int width;
int height;
int *p;
};
int main()
{
Rect R1;
Rect R2(R1);
return 0;
}
上述代码就是一个深拷贝的代码,所谓深拷贝不仅仅时进行简单的复制,而时重新动态分配空间,此时R1,R2分别指向不同的地址空间。
拷贝构造函数小结
两种拷贝方式:深、浅拷贝
当我们再对类对象进行等号赋值时,会调用拷贝构造函数,如果我们没有显示定义的情况下,系统会默认生成拷贝构造函数–浅拷贝。当类成员仅仅只有值拷贝时,系统生成的拷贝构造函数都可以完成拷贝,但是当类的成员有指针时,若采用浅拷贝,在拷贝时两个指针就会指向同一块地址空间,当对象快结束时,体统会调用两次析构函数,将指针所指的空间释放两次,就会出现错误。这时就必须采用深拷贝。
深拷贝和浅拷贝的区别就在于深拷贝会在对上新申请空间来存储数据。这样就不会对内存进行二次释放。
拷贝构造函数的作用
作用就是进行对象的复制,用一个对象的实例对该对象的另一个是例进行初始化
C++构造函数以及析构函数的若干面试问题
Q1:构造函数能否重载,析构函数能否重载,为什么?
A1:构造函数可以,析构函数不可以。
Q2:析构函数为什么一般情况下要声明为虚函数?
A2:虚函数是实现多态的基础,当我们通过基类的指针是析构子类对象时候,如果不定义成虚函数,那只调用基类的析构函数,子类的析构函数将不会被调用。如 果定义为虚函数,则子类父类的析构函数都会被调用。
Q3:什么情况下必须定义拷贝构造函数?
A3:当类的对象用于函数值传递时(值参数,返回类对象),拷贝构造函数会被调用。如果对象复制并非简单的值拷贝,那就必须定义拷贝构造函数。例如大的堆 栈数据拷贝。如果定义了拷贝构造函数,那也必须重载赋值操作符。