0
点赞
收藏
分享

微信扫一扫

1.什么是Angular?

大自然在召唤 2023-11-20 阅读 61

目录

一、拷贝构造函数

1、定义

2、特征

3、内置与自定义类型 

4、const修饰参数

5、默认生成

浅拷贝

深拷贝

6、总结

二、运算符重载

1、定义 

2、判断是否相等

3、比较大小

4、赋值

5、总结


一、拷贝构造函数

1、定义

拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存

在的类类型对象创建新对象时由编译器自动调用。

2、特征

  • 拷贝构造函数是构造函数的一个重载形式。
  • 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
// 拷贝构造
Date(const Date& d)
{

d._year = _year;
d._month = _month;
d._day = _day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
date d1(2023, 2, 3);
date d2(d1);

return 0;
}

如果不加&引用符号,编译器会报错。 

 使用拷贝构造也可以用这种方式:

Date d3 = d1;

3、内置与自定义类型 


用栈实现队列的时候,可能一会对st1进行析构一会对st2进行析构,成员变量指针_a指向的空间不能析构两次,而且如果分别对st1和st2赋初值,后赋值的会覆盖先赋值的数据。所以就要求它们不能指向同一块空间,各自要有各自的空间,所以C++规定:自定义类型的拷贝和传参,需要调用拷贝构造,对于栈这种需要深拷贝的拷贝构造(后续学习),现阶段只需要知道需要拷贝构造即可。

class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}

// 拷贝构造
// Date d2(d1);
Date(Date& d)
{
d._year = _year;
d._month = _month;
d._day = _day;
}
private:
int _year;
int _month;
int _day;
};

// 传值传参
void Func1(Date d)
{

}
// 传引用传参
void Func2(Date& d)
{

}
// 传指针
void Func3(Date* d)
{

}
int main()
{
Date d1(2023, 2, 3);
Func1(d1);

//Func2(d1);
//Func3(
return 0;
}

在Func1(d1)处按F11会直接跳转到拷贝构造。 

拷贝构造结束会进行Func1函数。 

直接跳转Func2函数。 

Func3(

4、const修饰参数

	Date(Date& d)
{
d._year = _year;
d._month = _month;
d._day = _day;
}

加上const可以避免拷贝方向错误时,原数据被修改,所以一般习惯参数前加上const。

	Date(const Date& d)
{
_year = d->_year;
_month = d->_month;
_day = d->_day;
}

此外,如果被拷贝的变量是被const修饰,如果拷贝构造的参数不被const修饰,会造成引用的权限扩大,所以一定要用const修饰参数。

5、默认生成

浅拷贝

若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按

字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。

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(2023, 11, 20);
d1.Print();
Date d2(d1);
d2.Print();
return 0;
}

通过输出结果我们可以发现,没写拷贝函数,编译器会自动对内置类型拷贝。

如果是自定义类型呢?程序会怎么处理?

typedef int DataType;
class Stack
{
public:
Stack(size_t capacity = 10)
{
cout << "Stack(size_t capacity = 10)" << endl;

_array = (DataType*)malloc(capacity * sizeof(DataType));
if (nullptr == _array)
{
perror("malloc申请空间失败");
exit(-1);
}

_size = 0;
_capacity = capacity;
}
void Push(const DataType& data)
{
// CheckCapacity();
_array[_size] = data;
_size++;
}
~Stack()
{
cout << "~Stack()" << endl;
if (_array)
{
free(_array);
_array = nullptr;
_capacity = 0;
_size = 0;
}
}
private:
DataType* _array;
size_t _size;
size_t _capacity;
};

int main()
{
Stack st1;
Stack st2(st1);

return 0;
}

 输出后程序会报错:

如果按照内置类型的方式对自定义类型进行拷贝, 两个Stack类的变量st1和st2的成员变量*array都指向同一块空间,这样会导致两个问题:

  1. 插入和删除数据会互相影响,比如st1先插入三个数据,st1的size为3,这时st2要插入数据,但st2的size为0,如果插入数据则会造成st1插入的数据被覆盖;同时,在析构st1时,对st1的空间进行释放,由于st1和st2的成员变量*array都指向同一块空间,这时st2再插入数据会造成对野指针的访问。
  2. 程序销毁*_array的空间时,会析构两次,程序会崩溃。

深拷贝

	Stack(const Stack& st)
{
_array = (DataType*)malloc(sizeof(DataType) * st._capacity);
if (_array == nullptr)
{
perror("malloc fail");
exit(-1);
}
memcpy(_array, st._array, sizeof(DataType) * st._size);
_size = st._size;
_capacity = st._capacity;
}

st2成功实现了拷贝构造,st2与st1的地址不同,它们不在指向同一空间了。

class MyQueue
{
public:
// 默认生成构造
// 默认生成析构
// 默认生成拷贝构造

private:
Stack _pushST;
Stack _popST;
int _size = 0;//缺省值处理
};
  •  两个Stack类型的成员变量初始化调用默认生成构造,销毁调用它的析构函数,拷贝使用它的拷贝构造。
  • 整型变量_size初始化通过缺省值处理,内置类型出磊之后会自动销毁,不需要析构处理,可以使用默认生成拷贝构造。

6、总结

拷贝构造函数典型调用场景:

  • 使用已存在对象创建新对象
  • 函数参数类型为类类型对象
  • 函数返回值类型为类类型对象

默认生成拷贝构造:

  • 内置类型完成浅拷贝/值拷贝(按字节一个一个拷贝)
  • 自定义类型去调用这个成员的拷贝构造。

二、运算符重载

1、定义 

注意:

  • 不能通过连接其他符号来创建新的操作符:比如operator@
  • 重载操作符必须有一个类类型参数
  • 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
  • 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
  • .*  ::  sizeof  ? :  . 注意以上5个运算符不能重载。

我们通过代码一点一点理解:

class Date
{
public:
Date(int year = 1900, 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, 1, 1);
Date d2(2023, 1, 1);
//下面两种使用方式均可
operator==(d1, d2);
d1 == d2;//编译器会转换成去调用operator==(d1,d2);
return 0;
}

如果要输出函数返回值,方式如下:

cout << (d1 == d2) << endl;//必须加括号,保证优先级
cout << operator==(d1, d2) << endl;

这里会发现,如果运算符重载成全局的就需要成员变量是公有的,否则会报错。

那么问题来了,封装性如何保证?

我们可以选择后面学习的友元处理,现阶段直接把运算符重载函数重载成成员函数即可。

2、判断是否相等

class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//运算符重载
// bool operator==(Date* this, const Date& d)
// 这里需要注意的是,左操作数是this,指向调用函数的对象
bool operator==(const Date& d)
{
return _year == d._year
&& _month == d._month
&= d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2023, 1, 1);
Date d2(2023, 1, 1);
//operator内置只能使用这种形式(d1 == d2,
cout << (d1 == d2) << endl;
return 0;
}

3、比较大小

	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;
}
}

还可以写成这种简便形式,但上述较长的方式更直观。

        return _year < d._year
|| (_year == d._year && _month < d._month)
|| (_year == d._year && _month == d._month && _day < d._day);
// d1 <= d2
bool operator<=(const Date& d)
{
return *this < d || *this == d;
}
// d1 > d2
bool operator>(const Date& d)
{
return !(*this <= d);
}

bool operator>=(const Date& d)
{
return !(*this < d);
}

bool operator!=(const Date& d)
{
return !(*this == d);
}

 4、赋值

class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
void operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2023, 6, 6);
Date d2(1, 1, 1);

d2 = d1;
d1.Print();
d2.Print();

return 0;
}

 operator=就是我们的赋值运算符重载,如果我们不使用引用传值,那自定义类型会调用拷贝构造,我们使用引用传值,就避免了这些拷贝操作。

 

如果三个数赋值呢?

d3 = d2 = d1;

 根据赋值操作符的右结合性,d1赋值给d2需要一个返回值才能对d3赋值。

 

	Date& operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;

return *this;
}
	Date& operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}

上面就是赋值运算符的完整版了。

5、总结

用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝

举报

相关推荐

1. 到底什么是架构

0 条评论