上篇文章我总结到了析构函数,接下来我们来看看剩下的两个函数:
如果一个构造函数的第一个参数是自身类类型的引用,且任何额外的参数都有默认值,则此构造函数也可叫做拷贝构造函数,也就是说拷贝构造函数就是一个特殊的构造函数。
拷贝构造的特点:
我们来看下面的代码:
我们创建了一个日期类的date类,第一个函数是他的构造函数,第二个则是他的拷贝构造函数,那么这两个函数有何区别呢?我们来看下方的代码:
int main() { //date d1(); 错误写法,可能是函数声明 date d1; date d2 = d1; //date d2(d1); d1.print(); d2.print(); return 0; }
首先我们在实例化对象时,不能写date d1();这种,因为编译器不清楚这是否是函数声明。后两行代码分别创建了d1,d2对象,他们两个创建的方法是不一样的,d1去调用第一个构造函数,d2去调用拷贝构造函数,而我们函数实现功能就是创建一个和d1值一样的d2。写成date d2(d1)和date d2 = d1是一样的,编译器都会去调用拷贝构造。
那么对于一个只有内置类型的日期类date,如果我们不写拷贝构造函数,那么像上方代码还能完成date d2 = d1 操作吗?答案是可以的,因为我们在拷贝构造函数特点第四点已经讲到。那么拷贝函数到底有什么用呢?
class Stack { public: Stack() :_a((int*)malloc(sizeof(int)*10)) ,_top(0) ,_capacity(10) { if (_a == nullptr) { perror("malloc fail"); return; } } ~Stack() { free(_a); _a = nullptr; _top = _capacity = 0; } private: int* _a; int _top; int _capacity; };
我们来看上面的代码,这是一个Stack类的栈,如果我们简单的在main函数里面完成下面的操作:
Stack st1; Stack st2 = st1;,那么我们的程序就会报错,因为对于Stack类,在私有成员中有一个int* _a的成员,我们在初始化列表时已经初始化_a,如果我们不写拷贝构造函数,因为是浅拷贝,那么st2中的_a将与st1中的_a,是同一块地址,调用析构函数时,就会析构两遍,我们不可能将同一块地址free两次,因此就会报错。
因此我们对于一个类,显示实现了析构并释放了资源,我们就需要写拷贝构造函数:
class Stack { public: Stack() :_a((int*)malloc(sizeof(int)*10)) ,_top(0) ,_capacity(10) { if (_a == nullptr) { perror("malloc fail"); return; } } Stack(const Stack& s) :_a((int*)malloc(sizeof(int) * s._capacity)) ,_top(s._top) ,_capacity(s._capacity) { for (int i = 0; i < _top; i++) { _a[i] = s._a[i]; } } ~Stack() { free(_a); _a = nullptr; _top = _capacity = 0; } private: int* _a; int _top; int _capacity; };
我们都知道如果我们定义两个int类型,我们可以比较他们是否相等,或者比较出哪一个int比较大,我们也可以进行加减乘除运算等。那么我们实现了日期类date能否像int那样呢?答案是可以的,C++支持我们使用运算符重载来完成这件事。
bool date::operator<(const Date& d)const { if (_year < d._year) { return true; } else if (_year == d._year) { if (_month < d._month) { return true; } else if (_month == d._month) { if (_day < d._day) { return true; } } } return false; }
这是一个简单的<运算符重载,类似于一个函数,只不过这个函数名为operator并且在名后面加上需要重载的运算符即可,其他的地方与函数基本一致。这样我们就可以比较两个日期类的大小了。
运算符重载的特点:
以上内容如有错误欢迎批评指正!!