默认成员函数:我们不写,编译器自动生成。我们不写,编译器不会自动生成
默认生成构造和析构:
#include
using namespace std;
class Date
{
public:
Date(int year=1, int month=1, int day=1)
{
_year = year;
_month = month;
_day = day;
}
//拷贝构造,函数名和类名相同
//拷贝构造的参数为什么不能是传值?
//C++自定义类型的成员在这个地方传值需要调用拷贝构造,无穷无尽
//因此自定义类型必须调用拷贝构造,所以要用引用&。最好加const
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;
Date d2(d1);
d1.Print();
d2.Print();//这时改变_year等会改变d1
return 0;
}
以下为不调用拷贝构造时,会默认生成拷贝构造
内置类型会处理,因此日期类不需要自己去写拷贝构造
自定义类型会去调用他的拷贝构造
Stack st1;
Stack st2(st1);
//栈中保持后进先出,后定义的先析构。
//st1变成野指针。
指向同一块空间的问题:
typedef int DataType;
class Stack
{
public:
Stack(size_t capacity = 10)
{
_array = (DataType*)malloc(capacity * sizeof(DataType));
if (nullptr == _array)
{
perror("malloc申请空间失败");
exit(-1);
}
_size = 0;
_capacity = capacity;
}
void Push(const DataType& data)
{
_array[_size] = data;
_size++;
}
Stack(const Stack& st)//深拷贝
{
_array = (DataType*)malloc(sizeof(DataType) * st._capacity);
if (nullptr == _array)
{
perror("malloc申请空间失败");
exit(-1);//直接终止程序
}
//拷贝数组空间上的值
memcpy(_array, st._array, sizeof(DataType) * st._size);
_size = st._size;
_capacity = st._capacity;
}
~Stack()
{
if (_array)
{
free(_array);
_array = nullptr;
_capacity = 0;
_size = 0;
}
}
private:
DataType *_array;
size_t _size;
size_t _capacity;
};
int main()
{
Stack st1;
st1.Push(1);
st1.Push(2);
st1.Push(3);
st1.Push(4);
Stack st2(st1);
//栈中保持后进先出,后定义的先析构。
//没有写拷贝构造,编译器自动生成了一个
return 0;
}
什么情况下需要写拷贝构造呢?
不能用指针来衡量,如果自己实现了析构函数释放了空间,就需要实现拷贝构造。
typedef int DataType;
class Stack
{
public:
Stack(size_t capacity = 10)
{
_array = (DataType*)malloc(capacity * sizeof(DataType));
if (nullptr == _array)
{
perror("malloc申请空间失败");
exit(-1);
}
_size = 0;
_capacity = capacity;
}
void Push(const DataType& data)
{
_array[_size] = data;
_size++;
}
Stack(const Stack& st)
{//拷贝构造对内置类型完成值拷贝或者浅拷贝。
cout << "Stack(const Stack& st)" << endl;
_array = (DataType*)malloc(sizeof(DataType) * st._capacity);
if (nullptr == _array)
{
perror("malloc申请空间失败");
exit(-1);//直接终止程序
}
//拷贝数组空间上的值
memcpy(_array, st._array, sizeof(DataType) * st._size);
_size = st._size;
_capacity = st._capacity;
}
~Stack()
{
if (_array)
{
free(_array);
_array = nullptr;
_capacity = 0;
_size = 0;
}
}
private:
DataType *_array;
size_t _size;
size_t _capacity;
};
//对于自定义类型,不需要写拷贝构造和构造。不写编译器会自动生成构造函数,构造函数符合我们的需求
class MyQueue
{
public:
//默认生成构造和析构
//默认生成拷贝构造
private:
Stack _pushST;
Stack _popST;
int _size = 0;//缺省值,用缺省值处理
};
int main()
{
Stack st1;
st1.Push(1);
st1.Push(2);
st1.Push(3);
st1.Push(4);
Stack st2(st1);
//栈中保持后进先出,后定义的先析构。
//没有写拷贝构造,编译器自动生成了一个
MyQueue q1;
//调用了拷贝构造
MyQueue q2(q1);
return 0;
}
那些场景存在拷贝构造
Date d2(d1);
Date d3=d1;//拷贝构造
传返回值的过程中能用引用就用引用,减少拷贝。除非就是想让他自己调用拷贝构造,拷贝一份独立的出来
参数基本都可以用引用,返回值不一定。局部对象不能用引用。
Date Test(Date d)
{
Date temp(d);
return temp;
}
为了增强程序的可读性,是具有特殊函数名的函数,也有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似
class Date
{
public:
Date(int year=1, int month=1, int day=1)
{
_year = year;
_month = month;
_day = day;
}
//private:
int _year;
int _month;
int _day;
};
bool operator==(const Date& d1, const Date& d2)//运算符重载可以实现在全局。
{
return d1._year == d2._year
&& d1._month == d2._month
&& d1._day == d2._day;
}
int main()
{
Date d1(2023, 9, 14);
Date d2(2023, 9, 14);
cout<<operator==(d1, d2)<<endl;
cout <<( d1 == d2) << endl;//全局函数,转换成调用这个函数operator==(d1,d2);和上一行一样
//运算符优先级<<高于==
return 0;
}
class Date
{
public:
Date(int year=1, int month=1, int day=1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
直接把函数放在类里面。类外面受到访问限定符的限制,放到类里面就解决问题了。但是会报错
其中还有隐藏的参数(2个):this
成员函数调用的方式也不同了。
//d1==d2转换为d1.operator==(d2)
bool operator==(const Date& d)
{
//this:d1;d:d2
return this->_year == d._year
&& _month == d._month
&& _day == d._day;
}
cout<<d1.operator==(d2)<<endl;
cout <<( d1 == d2) << endl;//成员函数转换成调用这个函数d1.operator==(d2);和上一行一样
//b1
bool operator<(const Date& d)
{
if (_year < d._year)
{
return true;
}
else if (_year == d._year && _month < d._month)
{
return true;
}
else if (_year == d._year && _month == d._month && _day < d._day)
{
return true;
}
else
{
return false;
}
}
//b1<=b2复用,根据上面有<有=
bool operator<=(const Date& d)
{
return *this < d || *this == d;
}
//b1>b2,取反。
bool operator>(const Date& d)
{
return !(*this <= d) ;
}
d1=d2;//是一种拷贝
//d1=d2
void operator=(const Date& d)//不用引用不会无穷递归,但会白白走一次拷贝构造,所以最好加上引用
{
_year = d._year;
_month = d._month;
_day = d._day;
}
d3=d2=d1;//编译不通过d1赋值给d2,d2的返回值传给d3
连续赋值,从右往左赋值。i=j=k; k赋值给j,返回j
Date& operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
//*this是d1
return *this;
//出了作用域还在,应该加引用。
//返回值是为了支持连续赋值,保持运算符的特性。
}
d1=d1
自己给自己赋值,可以加一个判断
Date& operator=(const Date& d)//引用
{
if(this!=&d)//取地址,this是左操作数的地址,d是右操作数的别名,地址相同则不用自己给自己赋值
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
+=支持连续赋值,只要支持连续赋值就都有返回值。。
前置++,d1.operator();
后置++,d2.operator(int);
int仅仅是为了占位,和牵制重载区分
//++d1;
Date& Date::operator++()
{
Date tmp(*this);
*this+=1;
return tmp;
}
//d1++
Date Date::operator++(int)
{
Date tmp(*this);
*this+=1;
return tmp;
}
对于内置类型,前置和后置++没有区别
对于自定义类型,**前置++**效率高,后置++还要拷贝