拷贝构造函数
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
- 拷贝构造函数是构造函数的一个重载形式。
函数名相同,参数不同
先定义一个类
class Date { public: Date(int year = 1, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } Date(const Date& d)//拷贝构造函数 { _year = d._year; _month = d._month; _day = d._day; } void Print() { cout << _year << "-" << _month << "-" << _day << endl; } private: // 内置类型 int _year; int _month; int _day; };
int main() { Date d1(2024, 1, 28); Date d2(d1); d1.Print(); d2.Print(); return 0; }
使用拷贝构造函数将d1的值拷给d2.
为了更好的理解,我们可以先定义两个函数
void func1(Date d) { } void func2(Date& rd) { } int main() { Date d1(2024, 1, 28); func1(d1); func2(d1); return 0; }
在调试过程中我们发现调用func1的时候,他会调用类的拷贝构造函数,如果调用func2函数的话,我们发现他不会去调用拷贝构造函数,而C++规定自定义的类型都会调用拷贝构造
这里有两个问题?
1.为什么构造拷贝函数要用传引用?
上面的例子也说明了,直接传类对象的话,他会自动的去递归找类对象的拷贝构造函数,如果拷贝构造函数的参数也是类对象的话,他又会去递归找拷贝构造函数,形成无穷递归.而传引用实际就是他的拷贝构造函数.
2.为什么拷贝构造函数的参数要加const?
因为我们可能会出现以下情况
Date( Date& d)//拷贝构造函数 { d._year = this->_year; d._month = this->_month; d._day = this->_day; /* _year = d._year; _month = d._month; _day = d._day;*/ }
如果出现颠倒,编译器也不会报错
本来要把d2修改成d1,但是搞反,d1就会被修改成d2的值.
- 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。
- 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象将内置类型按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
#include<iostream> using namespace std; class Date { public: Date(int year = 1, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } void Print() { cout << _year << "-" << _month << "-" << _day << endl; } private: // 内置类型 int _year; int _month; int _day; }; int main() { Date d1(2024, 1, 28); Date d2(d1); d1.Print(); d2.Print(); return 0; }
我们删除掉拷贝构造函数,看编译器会不会自动生成
编译器的确在我们没有定义拷贝构造函数会自动生成来默认拷贝构造函数进行拷贝.
而自定义类型需要调用他自己的默认拷贝函数.
因为自定义类型中可能会设计到申请内存,不能使用浅拷贝(单单只是值的拷贝)
- 编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗?当然像日期类这样的类是没必要的。那么下面的类呢?验证一下试试?
#include<iostream> #include <stdlib.h> using namespace std; typedef int DataType; class Stack { public: Stack(size_t capacity = 10) { _array = (DataType*)malloc(capacity * sizeof(DataType)); if (nullptr == _array) { perror("malloc申请空间失败"); return; } _size = 0; _capacity = capacity; } void Push(const DataType& data) { // CheckCapacity(); _array[_size] = data; _size++; } ~Stack() { if (_array) { free(_array); _array = nullptr; _capacity = 0; _size = 0; } } private: DataType* _array; size_t _size; size_t _capacity; }; int main() { Stack s1; s1.Push(1); s1.Push(2); s1.Push(3); s1.Push(4); Stack s2(s1); return 0; }
注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。
运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
注意:
1不能通过连接其他符号来创建新的操作符:比如operator@
2重载操作符必须有一个类类型参数
3用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义
4作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
5 .* :: sizeof ?: . 注意以上5个运算符不能重载。这个经常在笔试选择题中出
现。
这里有几个问题?
1.运算符重载函数可以定义在全局吗?
如果定义在全局,编译器会在类中找不到,由于他是默认成员函数,编译器会自己生成一个,会出现冲突,和全局的.原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。
2.赋值运算符重载,检测是否自己给自己赋值返回.*this :要复合连续赋值的含义.
3.用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。这里的拷贝还是属于浅拷贝,如果类成员变量中有栈类型,赋值就会有问题,两个指针指向同一块空间.析构两个类对象会出现问题.
4.为了防止自己给自己赋值的情况
修改前:
Date& Date:: operator=(const Date& d) { _year = d._year; _month = d._month; _day = d._day; return *this; }
修改后:
Date& Date:: operator=(const Date& d) { if (this != &d)//这里是地址 { _year = d._year; _month = d._month; _day = d._day; } return *this; }
5.为什么有些类成员函数返回时用传引用返回,有些是传值返回?
Date& Date::operator++()//前置++ { *this = *this + 1; return *this; } Date Date:: operator++(int)//后置++ { Date tmp = *this; *this=*this+ 1; return tmp; }
因为一个是临时变量,出作用域要被销毁,所以不能通过引用返回,这个之前讲过在引用那节.
6.后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递
日期类的实现
date.cpp
#include "date.h" Date::Date(int year, int month, int day) { _year = year; _month = month; _day = day; } void Date:: print() { cout << _year << "." << _month << "." << _day<<endl; } Date::Date(const Date& d) { _year = d._year; _month = d._month; _day = d._day; } Date::~Date() { _year = 0; _month = 0; _day = 0; } int Date:: GetMonthDay(int year, int month) { int arr[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 }; if (month == 2 && (year % 4 == 0 && year % 100 != 0 || year % 400 == 0)) { return 29; } return arr[month]; } Date& Date:: operator=(const Date& d) { if (this != &d) { _year = d._year; _month = d._month; _day = d._day; } return *this; } Date & Date::operator+=(int day) { _day+= day; while (_day > GetMonthDay(_year,_month)) { _day -= GetMonthDay(_year, _month); _month++; if (_month == 13) { _month = 0; } } return *this; } Date Date:: operator+(int day) { Date tmp = *this; tmp._day += day; while (tmp._day > GetMonthDay(tmp._year, tmp._month)) { tmp._day -= GetMonthDay(tmp._year, tmp._month); tmp._month++; if (tmp._month == 13) { tmp._month = 0; } } return tmp; } Date& Date:: operator-=(int day) { _day -= day; while (_day <= 0) { _month--; if (_month == 0) { _year--; _month = 12; } _day += GetMonthDay(_year, _month); } return *this; } Date Date::operator-(int day) { Date tmp = *this; tmp -= (day); return tmp; } bool Date:: operator>(const Date& d) { 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; } bool Date::operator==(const Date& d) { return (_year == d._year) && (_month == d._month) &&( _day == d._day); } bool Date:: operator >= (const Date& d) { return *this > (d) || *this == (d); } bool Date:: operator < (const Date& d) { return !(*this >= (d)); } bool Date::operator <= (const Date& d) { return !(*this>(d)); } bool Date:: operator != (const Date& d) { return !(*this==(d)); } Date& Date::operator++() { *this = *this + 1; return *this; } Date Date:: operator++(int) { Date tmp = *this; *this=*this+ 1; return tmp; } Date Date::operator--(int) { Date tmp =*this; *this=*this-1; return tmp; } Date& Date:: operator--() { *this = *this - 1; return *this; } int Date::operator-(const Date& d) { Date max = *this; Date min = d; int flag = 1; if (min > (max)) { max = d; min = *this; flag = -1; } int n = 0; while (max != min) { n++; min++; } return flag*n; }
date.h
#pragma once #include<iostream> using namespace std; class Date { public: // 获取某年某月的天数 int GetMonthDay(int year, int month); // 全缺省的构造函数 Date(int year = 1900, int month = 1, int day = 1); // 拷贝构造函数 // d2(d1) Date(const Date& d); // 赋值运算符重载 // d2 = d3 -> d2.operator=(&d2, d3) Date& operator=(const Date& d); // 析构函数 ~Date(); // 日期+=天数 Date& operator+=(int day); // 日期+天数 Date operator+(int day); // 日期-天数 Date operator-(int day); // 日期-=天数 Date& operator-=(int day); // 前置++ Date& operator++(); // 后置++ Date operator++(int); // 后置-- Date operator--(int); // 前置-- Date& operator--(); // >运算符重载 bool operator>(const Date& d); // ==运算符重载 bool operator==(const Date& d); // >=运算符重载 bool operator >= (const Date& d); // <运算符重载 bool operator < (const Date& d); // <=运算符重载 bool operator <= (const Date& d); // !=运算符重载 bool operator != (const Date& d); // 日期-日期 返回天数 int operator-(const Date& d); void print(); private: int _year; int _month; int _day; };