wordpress主题开发导航制作,常州seo收费,公众号开发是什么,河南省建设集团有限公司网站#x1f525;个人主页#xff1a;Quitecoder
#x1f525;专栏#xff1a;c笔记仓 朋友们大家好#xff0c;本篇文章带大家认识赋值运算符重载#xff0c;const成员#xff0c;取地址及const取地址操作符重载等内容 目录 1.赋值运算符重载1.1运算符重载1.1.1特性#…
个人主页Quitecoder
专栏c笔记仓 朋友们大家好本篇文章带大家认识赋值运算符重载const成员取地址及const取地址操作符重载等内容 目录 1.赋值运算符重载1.1运算符重载1.1.1特性 1.2赋值运算符重载1.3 赋值运算符的其他性质1.4前置和后置重载 2.const成员函数3.取地址及const取地址操作符重载 1.赋值运算符重载
1.1运算符重载 运算符重载是一种编程语言特性它允许开发者为已有的运算符提供自定义的实现。这意味着你可以改变某些运算符在你自定义的类或数据类型上的行为。比如你可以定义加号运算符如何在你自定义的数据结构上进行运算 什么意思呢我们来讲解首先我们定义日期类Date并实例化两个对象
class Date
{
public:Date(int year 1, int month 1, int day 1){_year year;_month month;_day day;}int _year;int _month;int _day;
};
int main()
{Date d1(2018, 9, 26);Date d2(2024, 3, 29);return 0;
}我们如何判断两个年份相等呢
如果是常规方法我们会写一个比较函数来判断是否相同
bool issame(const Date d1, const Date d2)
{return d1._year d2._year d1._month d2._month d1._day d2._day;
}
int main()
{Date d1(2018, 9, 26);Date d2(2024, 3, 29);cout issame(d1, d2) endl;return 0;
}那如果我们想直接通过用d1d2来判断是否相同呢这里就用到了操作符重载 运算符重载是具有特殊函数名的函数也具有其返回值类型函数名字以及参数列表其返回值类型与参数列表与普通的函数类似注意这里说的重载与我们的函数重载不是一个意思 函数名字为关键字operator后面接需要重载的运算符符号
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(2018, 9, 26);Date d2(2024, 3, 29);cout (d1d2) endl;return 0;
}我们发现直接进行判断时调用了比较函数
但是这里是全局的定义的operator这里会发现运算符重载成全局的就需要成员变量是公有的即我的成员不能是private私有的那么封装性如何保证
class Date
{
public:Date(int year 1, int month 1, int day 1){_year year;_month month;_day day;}bool operator(const Date d2){return _year d2._year _month d2._month _day d2._day;}
private:int _year;int _month;int _day;
};bool operator(const Date d2)
{return _year d2._year _month d2._month _day d2._day;
}这部分是Date类中运算符的重载。这个重载让你可以使用来比较两个Date对象是否相等即它们的年、月、日是否都相同
关键点讲解
参数operator函数接受一个类型为const Date的参数d2它是比较操作的右侧操作数。左侧操作数是调用这个函数的对象即this指针指向的对象const关键字参数使用const修饰符和引用传递来保证效率和避免不必要的拷贝同时确保不会修改传入的对象函数体函数体中通过比较两个Date对象的年、月、日字段来决定这两个对象是否相等。如果所有字段都相等则返回true否则返回false
我们接着调用这个函数
int main()
{Date d1(2018, 9, 26);Date d2(2024, 3, 29);cout d1.operator(d2) endl;cout (d1d2) endl;return 0;
}注意 注意这里的顺序d1在前与在后是不同的如果我们写一个小于函数的运算符重载顺序不同意思刚好相反 我们有两种方式进行调用这两种方式是相同的 在上面的讲解之后相信大家对运算符重载有了一定的了解他就是允许自定义对象使用运算符它的返回值是根据运算符来决定的比如完成加减操作我们就返回int类型判断是否大于小于就用bool类型 1.1.1特性
不能通过连接其他符号来创建新的操作符比如operator重载操作符必须有一个类类型参数自定义类型参数用于内置类型的运算符其含义不能改变例如内置的整型不能改变其含义作为类成员函数重载时其形参看起来比操作数数目少1因为成员函数的第一个参数为隐藏的this.* :: sizeof ?: . 注意以上5个运算符不能重载。这个经常在笔试选择题中出现
1.2赋值运算符重载 我们知道拷贝赋值有两种拷贝构造和赋值重载我们看拷贝构造
Date d1(2018, 9, 26);
Date d2(d1);那如果我们用赋值运算符重载呢可以写成下面的形式
d2d1;关键区别
拷贝构造函数在对象创建时使用用于初始化新对象。赋值运算符重载在对象已存在时使用用于将一个对象的值赋给另一个对象目的拷贝构造函数的目的是创建一个新的、状态相同的对象副本。赋值运算符的目的是改变一个已存在对象的状态使其与另一个对象的状态相同拷贝构造函数通常接收一个对同类对象的常引用。赋值运算符重载通常返回对象的引用并接收一个对同类对象的常引用作为参数
我们接下来讲解赋值运算符重载的具体实现来体现上面的特点
能不能直接这么写呢
void operator(const Date d)
{_year d._year;_month d._month;_day d._day;
}这个在单个赋值是可以的那如果我想像c语言一样同时实现多个变量的连续赋值的场景呢 int b;
int c;
bc10;那我们这个函数就无法满足要求了我们该如何修改呢
我们不妨探讨连续赋值的本质
bc10;这里执行步骤
10赋值给cc10这个表达式返回值为左操作数cc再作为bc的有操作数给b赋值返回值为左操作数b 所以我们的自定义类型也要符合这里的行为 所以我们需要对函数进行修改
第一次修改
Date operator(const Date d)
{_year d._year;_month d._month;_day d._day;return *this;
}返回左操作数返回*this
我们这里用的是传值返回意味着这里返回的不是*this返回的是*this的拷贝则需要调用拷贝构造函数 所以我们需要再次修改
第二次修改
Date operator(const Date d)
{_year d._year;_month d._month;_day d._day;return *this;
}我们返回引用
还有一个问题如果自身给自身赋值呢
d1d1;为什么自赋值不行 自赋值在大多数情况下是可以工作的但是在特定的情况下如果没有正确处理它可能会引起错误或意外的行为。考虑自赋值的主要原因是为了确保当对象赋值给自身时程序仍然能够正确、安全地运行 特别是在类中涉及到动态内存管理时不正确处理自赋值可能会导致问题。例如假设一个类内部分配了动态内存如果在赋值操作中首先释放了这块内存预备重新分配而源对象和目标对象实际上是同一个对象那么这个操作实际上会破坏源对象的状态导致未定义行为 我们还需要再次修改一次
Date operator(const Date d)
{if (this ! d){_year d._year;_month d._month;_day d._day;}return *this;
}我们这里判断条件是地址的比较如果地址不相同说明不是同一个对象可以赋值 1.3 赋值运算符的其他性质 赋值运算符只能重载成类的成员函数不能重载成全局函数 我们首先得把成员类型设置为公有的发现还是报错 原因赋值运算符如果不显式实现编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载就和编译器在类中生成的默认赋值运算符重载冲突了故赋值运算符重载只能是类的成员函数 如果我们不写赋值运算符重载编译器是否会默认生成呢 结果是会生成的
所以这里与我们拷贝构造等函数性质一致 用户没有显式实现时编译器会生成一个默认赋值运算符重载以值的方式逐字节拷贝。注意内置类型成员变量是直接赋值的而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值 既然编译器生成的默认赋值运算符重载函数已经可以完成字节序的值拷贝了还需要自己实现吗 答案是需要的如遇到下面的动态内存管理
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;s2 s1;return 0;
}s1对象调用构造函数创建在构造函数中默认申请了10个元素的空间然后存了4个元素1 2 3 4s2对象调用构造函数创建在构造函数中默认申请了10个元素的空间没有存储元素由于Stack没有显式实现赋值运算符重载编译器会以浅拷贝的方式实现一份默认的赋值运算符重载即只要发现Stack的对象之间相互赋值就会将一个对象中内容原封不动拷贝到另一个对象中s2 s1;当s1给s2赋值时编译器会将s1中内容原封不动拷贝到s2中这样会导致两个问题: s2原来的空间丢失了存在内存泄漏s1和s2共享同一份内存空间最后销毁时会导致同一份内存空间释放两次而引起程序崩溃 注意 如果类中未涉及到资源管理赋值运算符是否实现都可以一旦涉及到资源管理则必须要实现。 1.4前置和后置重载
在C中前置和后置运算符都可以被重载以提供用户定义类型比如类的自增功能。它们之间的主要区别在于参数和返回值这影响了它们的使用和效率
前置
前置直接对对象进行自增操作并返回自增后的对象引用。这意味着它在自增后立即返回对象的状态使得操作可以立即反映在对象上
Date operator(){_day 1;return *this;}后置 前置和后置都是一元运算符为了让前置与后置形成能正确重载C规定后置重载时多增加一个int类型的参数但调用函数时该参数不用传递编译器自动传递 注意后置是先使用后1因此需要返回1之前的旧值故需在实现时需要先将this保存一份然后给this1
Date operator(int){Date temp(*this);_day 1;return temp;}temp是临时对象因此只能以值的方式返回不能返回引用
2.const成员函数
假如我们现在定义一个const对象想访问它的Print函数我们发现是调用不了的
class Date
{
public:Date(int year, int month, int day){_year year;_month month;_day day;}void Print(){cout Print() endl;cout year: _year endl;cout month: _month endl;cout day: _day endl endl;}
private:int _year; // 年int _month; // 月int _day; // 日
};这里权限进行放大了接着我们来介绍const成员函数
原来是const Date*而我的this类型是Date*意味着需要将this指针也改为const Date*所以才有了下面的函数 将const修饰的“成员函数”称之为const成员函数const修饰类成员函数实际修饰该成员函数隐含的this指针表明在该成员函数中不能对类的任何成员进行修改内容是只读的 class Date
{
public:Date(int year, int month, int day){_year year;_month month;_day day;}void Print(){cout Print() endl;cout year: _year endl;cout month: _month endl;cout day: _day endl endl;}void Print() const{cout Print()const endl;cout year: _year endl;cout month: _month endl;cout day: _day endl endl;}
private:int _year; // 年int _month; // 月int _day; // 日
};
void Test()
{Date d1(2022,1,13);d1.Print();const Date d2(2022,1,13);d2.Print();
}我们查看结果 如果没有const修饰的函数呢我Date类型的对象能否调用const成员函数呢 class Date
{
public:Date(int year, int month, int day){_year year;_month month;_day day;}void Print() const{cout Print()const endl;cout year: _year endl;cout month: _month endl;cout day: _day endl endl;}
private:int _year; int _month; int _day;
};
void main()
{Date d1(2022, 1, 13);d1.Print();
}可以的这里是权限的缩小
请思考下面的几个问题
const对象可以调用非const成员函数吗 不可以权限放大非const对象可以调用const成员函数吗 可以权限缩小const成员函数内可以调用其它的非const成员函数吗 不可以权限放大非const成员函数内可以调用其它的const成员函数吗可以权限缩小
指针和引用才存在权限放大
3.取地址及const取地址操作符重载
class Date
{
public:Date(int year 1, int month 1, int day 1){_year year;_month month;_day day;}Date* operator(){return this;}const Date* operator()const{return this;}
private:int _year; // 年int _month; // 月int _day; // 日
};这两个运算符一般不需要重载使用编译器生成的默认取地址的重载即可只有特殊情况才需要重载比如想让别人获取到指定的内容 这里是默认成员函数我们删去这两个函数照样可以取地址
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; // 日
};所以我们没有必要深究这个东西究竟有什么用我们只进行简单的语法了解即可
当然如果你想让它普通对象定义后只能返回空值你可以这么写
class Date
{
public:Date(int year 1, int month 1, int day 1){_year year;_month month;_day day;}Date* operator(){return nullptr;}
private:int _year; int _month; int _day;
};日常并没有这种需要
本节内容到此结束感谢大家阅读