ppt网站建设,网页打包成小程序,做网站 中介,学校网页设计方案目录
一、拷贝构造函数
1、定义
2、特征
3、内置与自定义类型
4、const修饰参数
5、默认生成
浅拷贝
深拷贝
6、总结
二、运算符重载
1、定义
2、判断是否相等
3、比较大小
4、赋值
5、总结 一、拷贝构造函数
1、定义
拷贝构造函数#xff1a;只有单个形参…目录
一、拷贝构造函数
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(d1);return 0;
} 使用Func1传值传参 在Func1(d1)处按F11会直接跳转到拷贝构造。 拷贝构造结束会进行Func1函数。 如果用Func2引用传参就不需要拷贝构造了。 直接跳转Func2函数。 此外还可以使用以前C语言中常用的指针传参但这种方法有点啰嗦。 Func3(d1);
4、const修饰参数 拷贝构造一般会加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都指向同一块空间这样会导致两个问题
插入和删除数据会互相影响比如st1先插入三个数据st1的size为3这时st2要插入数据但st2的size为0如果插入数据则会造成st1插入的数据被覆盖同时在析构st1时对st1的空间进行释放由于st1和st2的成员变量*array都指向同一块空间这时st2再插入数据会造成对野指针的访问。程序销毁*_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、定义 C为了增强代码的可读性引入了运算符重载运算符重载是具有特殊函数名的函数也具有其返回值类型函数名字以及参数列表其返回值类型与参数列表与普通的函数类似。 函数名字为关键字operator后面接需要重载的运算符符号。函数原型返回值类型 operator操作符(参数列表) 注意
不能通过连接其他符号来创建新的操作符比如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 _day d._day;}
private:int _year;int _month;int _day;
};
int main()
{Date d1(2023, 1, 1);Date d2(2023, 1, 1);
//operator内置只能使用这种形式(d1 d2cout (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); 判断小于等于我们可以借助隐藏的this指针使用运算符重载嵌套的方式在判断小于等于中分别调用判断小于和判断等于的运算符重载。 // 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、总结 用户没有显式实现时编译器会生成一个默认赋值运算符重载以值的方式逐字节拷贝。 注意内置类型成员变量是直接赋值的而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值所以日期类就不需要写运算符重载函数而类似用栈实现队列则需要。 注意如果类中未涉及到资源管理赋值运算符是否实现都可以一旦涉及到资源管理则必须要实现。