当前位置: 首页 > news >正文

【C++进阶之路】类和对象(中)

文章目录

  • 前言
    • 六大默认成员函数
  • 一.构造函数
    • 性质
    • 默认构造函数
    • 构造函数(需要传参)
  • 二.析构函数
    • 性质
    • 默认析构函数
    • 练习
  • 三.拷贝构造函数
    • 基本性质:
    • 形参必须是引用
    • 默认拷贝构造
      • 浅拷贝
      • 深拷贝
      • 自定义类型
  • 四.赋值运算符重载函数
    • 基本特征
    • 全局的运算符重载函数
    • 局部的运算符重载函数
        • 前置++与后置++的实现
    • 赋值运算符重载函数
    • 日期类(练习)
  • 五.取地址重载和const取地址重载
    • const 成员
    • 取地址重载
    • const取地址重载

前言

六大默认成员函数

在这里插入图片描述

1.构造函数——完成对象成员变量的初始化
2.析构函数——完成空间(主要是堆)的释放
3.拷贝构造——用一个已初始化的对象初始另一个正在初始化的对象
4.赋值重载——用一个已初始化的对象赋值给另一个已经初始化的对象
5.取地址重载——
6.const修饰的取地址重载——

  • 为什么叫默认成员函数呢?
  • 因为即使是空类也会在类里面自动生成这六个函数(默认),这些函数如果定义的话只能在类里面定义,或者在类里面声明,在类外面用作用域限定符进行定义(成员)。

一.构造函数

  • 为了弥补C语言的缺陷——比如在写用括号匹配,我们会注意到一个点,就是一不小心就会忘记初始化两个栈,有构造函数这个问题就得到了很好的解决。

性质

    1. 函数名与类名相同。
    1. 无返回值。
    1. 对象实例化时编译器自动调用对应的构造函数。
    1. 构造函数可以重载。

默认构造函数

举例代码:

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace::std;
class Date
{
public:Date(int year = 1, int month = 1, int day = 1)//类名就是函数名,无返回值{_year = year;_month = month;_day = day;}Date(int year, int month = 1, int day = 1)//构成重载{_year = year;_month = month;_day = day;}
private:int _day;int _month;int _year;
};
int main()
{Date A;//自动调用析构函数//Date A();return 0;
}

调试结果:
在这里插入图片描述

  • 不少观众又要问了——在全缺省的情况下调用函数不是应该写成下面注释的代码吗?
  • 我们乍一看好像是的,如果我们换一种形式呢——Date func(); 像不像一个函数声明呢?那为什么不这样写,其实就是为了与函数的声明进行区分!
  • 到这里其实还不够,我们继续分析!

  • 既然函数参数可以重载我们是否可以这样写呢?
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace::std;
class Date
{
public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}Date(){_year = 1;_month = 1;_day = 1;}
private:int _day;int _month;int _year;
};
int main()
{//Date A;return 0;
}
  • 语法上是支持的,但是如果将注释的代码放开,再进行编译就会报错,因为不知道该调用哪一个构造函数,所以是可以的,但是不支持

  • 既然是默认成员函数,那么不写编译器应该也会帮我们生成一个。
#include<iostream>
using namespace::std;
class Date
{
public:
private:int _day;int _month;int _year;
};
int main()
{Date A;//Date A();return 0;
}

图解:
在这里插入图片描述

  • 这是为什么呢?编译器竟然没有帮我们完成初始化。
  • 语法上对编译器生成的构造函数只是说明了一点:
  • 1.对内置类型不做处理——这里的内置类型指的是编译器原本就有的类型
  • 说明:有的编译器还是会处理的,我们就当做不处理对待。
  • 2.对自定义类型去调用它的默认构造

  • 那我们如果想要不写构造函数的情况下,如何初始化对象的变量呢?
  • C++11引出我们可在类的变量处使用缺省值。
#include<iostream>
using namespace::std;
class Date
{
public:
private:int _day = 1;int _month = 1;int _year = 1;
};
int main()
{Date A;return 0;
}

调试结果:
在这里插入图片描述

  • 很显然编译器帮助我们初始化了对象。

  • 总结:
  • 1 .默认构造函数指的是不传参就能调用的函数。
  • 2 .目前我们知道的默认构造函数要么是全缺省的,要么是无参的,要么就是编译器生成的。

构造函数(需要传参)

using namespace::std;
class Date
{
public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}Date(){_year = 1;_month = 1;_day = 1;}
private:int _day;int _month;int _year;
};
int main()
{Date A(2023,5,3);//这就跟函数调用差不多了,就多了个类型。return 0;
}

调试结果:
在这里插入图片描述

  • 总结:
    1. 一般情况下,内置类型一般都需要写构造函数,不能用编译器生成的。
    1. 自定义类型我们可以考虑不写构造函数,可以让编译器生成,其本质上是使用自定义类型的默认构造函数。
    1. 成员变量可以给缺省值。

二.析构函数

  • 如果在写括号匹配,初始化的问题不容易出错,那么在返回时的销毁就极大概率会出错,但这道编程题即使你不销毁也是可以过的,但作为一名合格的程序员,怎能放任不管?
  • 因此,析构函数就是为了解决这个问题,让程序在销毁时,自动释放空间(主要是堆)。

析构函数的定义:析构函数于构造函数相对应,构造函数是对象创建的时候自动调用的,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成类的一些资源清理工作。

  • 重要的事情说三遍:
    1. 析构函数不完成对象的销毁,只是完成内部资源(堆空间)的清理工作。
    1. 析构函数不完成对象的销毁,只是完成内部资源(堆空间)的清理工作。
    1. 析构函数不完成对象的销毁,只是完成内部资源(堆空间)的清理工作。

性质

    1. 函数名为:~ + 类名
    1. 无参无返回值,不构成重载
    1. 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数
    1. 对象生命周期结束时 自动调用。

我们手写一个栈的初始化和释放

#include<iostream>
using namespace::std;
class Stack
{
public:Stack(int capacity = 4){cout << "Stack()" << endl;int* tmp = (int*)malloc(sizeof(int) * capacity);if (tmp == NULL){perror("malloc fail");exit(-1);}else{_arr = tmp;_top = 0;_capacity = capacity;}}~Stack(){cout << "~Stack()" << endl;free(_arr);_arr = NULL;}
private:int* _arr;int _top;int _capacity;
};
int main()
{Stack stack1;return 0;
}

当我们创建一个栈对象时,会完成对象的初始化,当main函数执行结束时会完成堆空间的释放。

调试看结果:
在这里插入图片描述
此时mainj函数差一步结束:
在这里插入图片描述
继续调试:
在这里插入图片描述
继续执行程序结束,对象才进行释放。

默认析构函数

  • 默认生成的析构函数会不会把动态申请的资源进行释放呢?
#include<iostream>
using namespace::std;
class Stack
{
public:Stack(int capacity = 4){cout << "Stack()" << endl;int* tmp = (int*)malloc(sizeof(int) * capacity);if (tmp == NULL){perror("malloc fail");exit(-1);}else{_arr = tmp;_top = 0;_capacity = capacity;}}
private:int* _arr;int _top;int _capacity;
};
int main()
{Stack stack1;return 0;
}

调试:
在这里插入图片描述

  • 可知:在返回过后,并没有将动态申请的资源进行释放。
  • 因此:默认的构造函数不会对内置类型进行处理

  • 自定义类型呢?

我们定义一个对列:

#include<iostream>
using namespace::std;
class Stack
{
public:Stack(int capacity = 4){cout << "Stack()" << endl;int* tmp = (int*)malloc(sizeof(int) * capacity);if (tmp == NULL){perror("malloc fail");exit(-1);}else{_arr = tmp;_top = 0;_capacity = capacity;}}~Stack(){cout << "~Stack()" << endl;free(_arr);_arr = NULL;}
private:int* _arr;int _top;int _capacity;
};class MyQueue
{Stack stack_push;Stack stack_pop;
};
int main()
{MyQueue Q;return 0;
}

接着进行调试:
在这里插入图片描述

  • 可知:默认生成的析构函数会将其成员的资源进行释放,其本质上是调用其自定义成员的析构函数。
  • 因此:对象全为自定义成员,默认生成的析构函数会去调用其自定义成员的析构函数,完成资源的释放与清理。

  • 总结:
  • 默认生成的析构函数:
    1. 对内置类型不做处理。
    1. 对自定义类型会去调用它的析构函数。

那什么时候用写析构函数?什么时候不用写析构函数呢?

  • 对象有资源需要清理,且管理资源的是内置类型,这时就需要我们自己写析构函数
  • 对象有资源需要清理,但管理资源的是自定义类型,这时就不需要写
  • 对象没有资源需要清理,这时也不需要写

练习

  • 不同声明周期和作用域对象的析构和构造的调用顺序
class A
{
public:A(){cout << "A()" << endl;}~A(){cout << "~A()" << endl;}
};
class B
{
public:B(){cout << "B()" << endl;}~B(){cout << "~B()" << endl;}
};class C
{
public:C(){cout << "C()" << endl;}~C(){cout << "~C()" << endl;}
};class D
{
public:D(){cout << "D()" << endl;}~D(){cout << "~D()" << endl;}
};
class E
{
public:E(){cout << "E()" << endl;}~E(){cout << "~E()" << endl;}
};A a;
B b;
int main()
{C c;D d;static E e;return 0;
}

这里我们列举了三种对象——全局对象,局部对象,static修饰的局部对象
话不多说,开始调试:
在这里插入图片描述

  • 到这可知:程序开始时a就调用了它的构造函数,并且按照语句的顺序进行先后构造
  • 因此:全局对象的构造优先,且遵循语句先后顺序。
    继续调试:

在这里插入图片描述

  • 由此判断局部对象的构造函数调用的顺序后于全局对象,并且也是按照语句的顺序进行构造的。
  • 因此:构造函数的优先级:全局大于局部,且遵循语句的先后顺序。
  • 说明:static 修饰的局部变量也是局部变量。

继续调试:
在这里插入图片描述

  • 可知:不加static修饰局部对象的析构函数的调用顺序与构造顺序相反且比加static的对象优先调用析构函数

接着调试直到程序结束:

在这里插入图片描述

  • 可以看出
    1. 析构函数static在局部之后释放,且顺序与构造函数的顺序相反。
    1. 全局对象最后释放且顺序与构造函数的顺序相反。

  • 总结:
    1. 构造函数调用顺序都是按语句的先后顺序进行调用的。
    1. 全局大于局部
    1. 析构函数的调用顺序都是与构造的调用顺序相反
    1. 优先级:不加static的局部对象>加static的局部对象>全局对象

三.拷贝构造函数

  • 在我们的日常使用时,如果需要一个对象进行修改,但不要破坏原来的对象,这时就需要我们单独拷贝一份出来——有点像后置++的效果,那怎么拷贝呢?
  • 这时我们就引出了拷贝构造函数——拷贝构造函数是构造函数的一个重载形式。

基本性质:

  1. 只有单个形参。 该形参是对本类类型对象的引用**(一般常用const修饰)。**
  2. 无返回值
  3. 类名与函数名相同
  4. 用已存在的类的类型对象创建新对象时由编译器自动调用
  • 说明:拷贝构造完成的是已初始化的对象另一个正在进行初始化对象赋值操作

形参必须是引用

  • 为什么是形参一定是引用而不是拷贝呢?

我们按照语法分析:用已存在的类类型对象创建新对象时由编译器自动调用(拷贝构造的调用条件)。

代码:

#include<iostream>
using namespace::std;
class Date
{
public://构造函数Date(int year = 1949, int month = 10, int day = 1){_year = year;_month = month;_day = day;}//拷贝构造Date(const Date& A){_year = A._year;_month = A._month;_day = A._day;}
private:int _day;int _month;int _year;
};
int main()
{Date d;Date B(d);return 0;
}

如果将拷贝构造的&去掉,编译一下。
在这里插入图片描述

  • 可见语法上会进行强制检查,进行报错。
  • 但如果我们假设不报错进行分析呢?

在这里插入图片描述

  • 可见我们如果这样分析只会——南辕北辙越行越远(只有递没有归)。

  • 还看上面的代码:补充一段
void Fun(Date A)
{}
  • 当我们进行调用这个函数时,也满足拷贝的条件
  • 因此:在传参的时候也会调用拷贝构造。

默认拷贝构造

浅拷贝

#include<iostream>
using namespace::std;
class Date
{
public://构造函数Date(int year = 1949, int month = 10, int day = 1){_year = year;_month = month;_day = day;}
private:int _day;int _month;int _year;
};
int main()
{Date d;Date B(d);return 0;
}

进行调试:
在这里插入图片描述

  • 可见完成了任务,这是否意味着我们就不用写拷贝构造了呢?
  • 并不是,拷贝还分为深拷贝和浅拷贝
  • 默认生成的只完成了浅拷贝,那深拷贝是啥呢?

深拷贝

再给出一份代码:

#include<iostream>
using namespace::std;
class Stack
{
public:Stack(int capacity = 4){cout << "Stack()" << endl;int* tmp = (int*)malloc(sizeof(int) * capacity);if (tmp == NULL){perror("malloc fail");exit(-1);}else{_arr = tmp;_top = 0;_capacity = capacity;}}void PushBack(int data){_arr[_top++] = data;}~Stack(){cout << "~Stack()" << endl;free(_arr);_arr = NULL;}
private:int* _arr;int _top;int _capacity;
};
int main()
{Stack s1;Stack s2(s1);return 0;
}

进行调试:
在这里插入图片描述
其只是简单的把地址copy过去了,但仍没有完成任务。
此代码实现的图解:
在这里插入图片描述

应该实现的图解:
在这里插入图片描述
那这样实现的我们就叫做深拷贝。

如何实现:

  • 借助malloc和memcpy
    代码:
#include<iostream>
using namespace::std;
class Stack
{
public:Stack(int capacity = 4){cout << "Stack()" << endl;int* tmp = (int*)malloc(sizeof(int) * capacity);if (tmp == NULL){perror("malloc fail");exit(-1);}else{_arr = tmp;_top = 0;_capacity = capacity;}}Stack(const Stack& A){int* tmp = (int*)malloc(sizeof(int) * A._capacity);if (tmp == NULL){perror("malloc fail");exit(-1);}else{memcpy(tmp, A._arr, sizeof(int) * A._capacity);_arr = tmp;_top = A._top;_capacity = A._capacity;}}void PushBack(int data){_arr[_top++] = data;}~Stack(){cout << "~Stack()" << endl;free(_arr);_arr = NULL;}
private:int* _arr;int _top;int _capacity;
};
int main()
{Stack s1;Stack s2(s1);return 0;
}

调试一下:
在这里插入图片描述

  • 由此我们的深拷贝就简单的完成了。
    我们回过头进行看写一个函数。
#include<iostream>
using namespace::std;
class Stack
{
public:Stack(int capacity = 4){cout << "Stack()" << endl;int* tmp = (int*)malloc(sizeof(int) * capacity);if (tmp == NULL){perror("malloc fail");exit(-1);}else{_arr = tmp;_top = 0;_capacity = capacity;}}Stack(const Stack& A){int* tmp = (int*)malloc(sizeof(int) * A._capacity);if (tmp == NULL){perror("malloc fail");exit(-1);}else{memcpy(tmp, A._arr, sizeof(int) * A._capacity);_arr = tmp;_top = A._top;_capacity = A._capacity;}}void PushBack(int data){_arr[_top++] = data;}~Stack(){cout << "~Stack()" << endl;free(_arr);_arr = NULL;}
private:int* _arr;int _top;int _capacity;
};
Stack Func()
{static Stack A;return A;
}
int main()
{Stack A = Func();return 0;
}
  • 这里我们写了一个函数,这里返回的是函数里面A对象的一份深拷贝。
  • 假设:_arr开辟的很大,那么我们的开销就会很大,所以一般都是引用返回,并且函数的参数一般都是引用,其目的就是为了避免空间的开销。

自定义类型

对象里面是自定义类型的拷贝构造,如何拷贝呢?

class Date
{
public://构造函数Date(int year = 1949, int month = 10, int day = 1){_year = year;_month = month;_day = day;}//拷贝构造Date(const Date& A){_year = A._year;_month = A._month;_day = A._day;}
private:int _day;int _month;int _year;
};
class Dates
{Date A;Date B;
};
int main()
{Dates A;Dates B(A);return 0;
}

调试:
在这里插入图片描述

  • 因此:默认生成的拷贝构造会调用其类型的拷贝构造进行拷贝。

  • 总结:
    1. 深拷贝需要我们写拷贝构造。
    1. 浅拷贝或者成员全是自定义类型时,我们可以不写拷贝构造。
    1. 拷贝构造是为了完成已初始化的对象对另一个未初始化的对象的拷贝。
    1. 拷贝构造的形参必须是引用,且拷贝构造是构造函数的重载。

四.赋值运算符重载函数

  • 讲赋值运算符之前我们得清楚什么是运算符重载函数。

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。

基本特征

  • 目的:增强代码可读性
  • 基本特征
    1. 具备返回类型与参数以及返回值
    1. 函数名为operate+操作符(必须是已经存在的!)

注意:

    1. *. ——没用过
    1. :: ——作用域限定符
    1. sizeof ——求类型大小
    1. ?: ——三目操作符
    1. .
  • 以上5个运算符不能重载。

全局的运算符重载函数

  • 因为要访问成员,所以为了写成全局的,我们不得不将成员变量公开。
    代码:
#include<iostream>
using namespace::std;
class Date
{
public://构造函数Date(int year = 1949, int month = 10, int day = 1){_year = year;_month = month;_day = day;}//拷贝构造Date(const Date& A){_year = A._year;_month = A._month;_day = A._day;}
//private:int _day;int _month;int _year;
};
bool operator== (const Date& A, const Date& B)
{return A._day == B._day&& A._month == B._month&& A._year == B._year;
}
int main()
{Date A;Date B;cout <<( A == B )<< endl;//这里括号不可以省去,因为流插入的优先级比较高所以我们需要加括号让表达式先计算。return 0;
}

运行结果:
在这里插入图片描述

  • 返回的bool值为1,所以为真,因此相等。

  • 全部的显然不是很好有没有办法写到局部呢?

  • 答案是肯定的,写到类里面不就好了么?


局部的运算符重载函数

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace::std;
class Date
{
public://构造函数Date(int year = 1949, int month = 10, int day = 1){_year = year;_month = month;_day = day;}//拷贝构造Date(const Date& A){_year = A._year;_month = A._month;_day = A._day;}bool operator== (const Date& B){return _day == B._day&& _month == B._month&& _year == B._year;}
private:int _day;int _month;int _year;
};
int main()
{Date A;Date B;cout <<( A == B )<< endl;return 0;
}

我们单独把这里的重载函数列出来:

	bool operator== (const Date& B)//其实就是bool operator== (Date *const this,const Date& B){return _day == B._day&& _month == B._month&& _year == B._year;}
  • 比如这里需要A==B,那么就相当于 A.operator==(B)
  • 表面上缺少了一个参数其实是this指针。
  • 因此:比较n个操作数我们只需要传进去n-1个参数即可。

前置++与后置++的实现

  • 到这我们对一些运算符有了一定的了解,但是当我们实现后置++与前置++时该如何实现呢?
  • 由于++是一元操作符,只对一个对象进行操作,因此实现的函数的参数没有或者说只有一个隐含的this 指针。 这就是我们区分的条件,一个加上一个参数,是后置++,一个不加是前置++。
    实现代码:
#include<iostream>
using namespace::std;
class Date
{
public://构造函数Date(int year = 1949, int month = 10, int day = 1){_year = year;_month = month;_day = day;}//拷贝构造Date(const Date& A){_year = A._year;_month = A._month;_day = A._day;}Date& operator++(int)//加一个参数以示区分,别的到没什么用处,{_day++;return *this;}Date operator++(){Date tmp(*this);_day++;return tmp;}
private:int _day;int _month;int _year;
};

赋值运算符重载函数

  • 说明:赋值运算符重载函数是默认成员函数,不写编译器会自动生成一个,但是其余的运算符重载函数可不是默认成员函数。
#include<iostream>
using namespace::std;
class Date
{
public://构造函数Date(int year = 1949, int month = 10, int day = 1){_year = year;_month = month;_day = day;}//拷贝构造Date(const Date& A){_year = A._year;_month = A._month;_day = A._day;}Date& operator=(const Date& d){if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;}
private:int _day;int _month;int _year;
};int main()
{Date A;Date B(2023,5,3);A = B;return 0;
}
  • *this作为返回值
  • 1.这个对象出了作用域还在
  • 2.返回类型建议是引用

赋值操作符返回类型是左值本身,因此我们返回引用比较合适可以减少空间上的开销。
当赋值为本身时,可以不进行此操作,结果也是一样的。

  • 赋值运算符重载与拷贝构造函数的区别:
    1. 赋值运算符重载——两个已经初始化的对象进行的赋值操作
    1. 拷贝构造函数c——一个初始化的对象对另一个正在初始化的对象性的操作。

看下面的代码:

#include<iostream>
using namespace::std;
class Date
{
public://构造函数Date(int year = 1949, int month = 10, int day = 1){_year = year;_month = month;_day = day;}//拷贝构造Date(const Date& A){_year = A._year;_month = A._month;_day = A._day;}Date& operator=(const Date& d){if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;}
private:int _day;int _month;int _year;
};
int main()
{Date A;Date B = A;//这个代码不是赋值重载而是拷贝构造//这是已初始化的对象对另一个正在初始化的对象的赋值——拷贝构造。return 0;
}
  • 这里的Date B = A; 见到要额外注意——这是调用的是拷贝构造

如何证明:

  • 看汇编代码
    在这里插入图片描述
    这里调用的是同一个函数——由下面是拷贝构造,可推理出上面的代码也是拷贝构造

日期类(练习)

  • 感兴趣可以把时期类实现一下:
    这里先把日期类的代码给出
#include<stdbool.h>
#include<assert.h>
#include<iostream>
using namespace::std;
class Date
{public:// 获取某年某月的天数bool is_leap_year(int year){if (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)){return true;}return false;}int GetMonthDay(int year, int month){int day[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };if (is_leap_year(year) && month == 2){return 29;}else{return day[month];}}//检查一下输入或者赋值的时候是否日期非法bool is_legal_Date(){if (_month >= 1 && _month <= 12 && _day >= 1 && _day <= GetMonthDay(_year,_month)){return true;}else{return false;}}// 全缺省的构造函数Date(int year = 1900, 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;}// 赋值运算符重载Date& operator=(const Date& d){_year = d._year;_month = d._month;_day = d._day;return *this;}// 析构函数~Date(){;}// 日期+=天数Date& operator+=(int day){if (day < 0){return *this -= -day;}_day += day;while (_day >= GetMonthDay(_year, _month)){int tmp = GetMonthDay(_year, _month);_day -= tmp;_month += 1;if (_month == 13){_year += 1;_month = 1;}}return *this;}// 日期+天数Date operator+(int day){Date tmp(*this);tmp += day;return tmp;}// 日期-天数Date operator-(int day){Date tmp(*this);tmp._day -= day;while (tmp._day <= 0)//等于0不能忘了{int tmp1 = GetMonthDay(tmp._year, tmp._month - 1);if (tmp._month == 1){tmp1 = GetMonthDay(tmp._year, 12);}tmp._day += tmp1;tmp._month--;if (tmp._month == 0){tmp._year--;assert(tmp._year);tmp._month = 12;}}return tmp;}// 日期-=天数Date& operator-=(int day){if (day < 0){return *this += -day;}*this = *this - day;return *this;}// 前置++Date& operator++(){*this += 1;return *this;}// 后置++Date operator++(int){Date tmp(*this);*this += 1;return tmp;}// 后置--Date operator--(int){Date tmp(*this);*this -= 1;return tmp;}// 前置--Date& operator--(){*this -= 1;return *this;}// >运算符重载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;}}// ==运算符重载bool operator==(const Date& d){return _year == d._year&& _month == d._month&& _day == d._day;}// >=运算符重载bool operator >= (const Date& d){return *this == d || *this > d;}// <运算符重载bool operator < (const Date& d){return !(*this >= d);}// <=运算符重载bool operator <= (const Date& d){return !(*this > d);}// !=运算符重载bool operator != (const Date& d){return !(*this == d);}// 日期-日期 返回天数int operator-(const Date& d){int day = 0;if (*this >= d){Date tmp(d);while (*this != tmp){++tmp;day++;}return day;}else{Date tmp(*this);while (tmp != d){++tmp;day++;}return -day;}}
private:int _year;int _month;int _day;
};

五.取地址重载和const取地址重载

const 成员

不妨看这样一段代码:

#include<iostream>
using namespace::std;
class Date
{
public:void Print(){cout << _year << _month << _day << endl;}
private:int _year;int _month;int _day;};
int main()
{const Date A;A.Print();return 0;
}

报错原因:
在这里插入图片描述

  • 分析:this 指针是一个类型为 int *const this,还记得const 的修饰规则吗?
  • 举例:
		定义一个变量int * const this因为:const具有就近原则所以:放在thisthis本身不能修改补充:放在*前说明*this不能被修改

再看我们传进去的参数是什么类型的——const Date(说明Date不能修改)

  • 转化为指针就是*this 不能修改,所以应该是——const int* const this。
  • 为什么要这样写呢?——权限不能放大,只能缩小或者平移。
  • 那由于——this 指针不能显示表示,这个const该加哪呢?
  • 祖师爷这样放的:
	void Print() const{cout << _year << _month << _day << endl;}
  • 为什么这样放呢?家人们谁懂啊?
    *猜测: 可能是祖师爷想不到地方放了。。。。

适用场景:

    1. 传参对象有const修饰。
    1. 只要函数内部不对成员变量进行修改。

取地址重载

class Date
{
public :Date* operator&(){return this ;}
private :int _year ; // 年int _month ; // 月int _day ; // 日
};

const取地址重载

class Date
{
public :const Date* operator&()const{return this ;}
private :int _year ; // 年int _month ; // 月int _day ; // 日
};
  • 说明:
  • 一般我们直接用默认编译器生成的就够了
  • 使用场景很少——作为了解即可

相关文章:

【C++进阶之路】类和对象(中)

文章目录 前言六大默认成员函数 一.构造函数性质默认构造函数构造函数(需要传参) 二.析构函数性质默认析构函数练习 三.拷贝构造函数基本性质&#xff1a;形参必须是引用默认拷贝构造浅拷贝深拷贝自定义类型 四.赋值运算符重载函数基本特征全局的运算符重载函数局部的运算符重载…...

AIMD 为什么收敛(tcp reno/cubic 为什么好)

TCP 拥塞控制目标是缓解并解除网络拥塞&#xff0c;让所有流量公平共享带宽&#xff0c;合在一起就是公平收敛。 AIMD(几乎所有与拥塞控制相关的协议或算法都有 AIMD 的影子&#xff0c;包括 RoCE&#xff0c;BBRv2) 为什么收敛&#xff1f;我一般会给出下面的老图&#xff1a;…...

医院智能导诊系统,医院导航解决方案

随着现代医院规模不断扩大&#xff0c;功能区域越来越细化&#xff0c;面对复杂的楼宇结构&#xff0c;集中的就诊人流&#xff0c;患者在就诊中经常会面临找不到目的地的困境&#xff0c;就诊体验变差。针对这个问题&#xff0c;一些面积和规模都比较大的医院&#xff0c;已经…...

【论文复现】基于区块链的分布式光伏就地消纳交易模式研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…...

在滴滴和字节跳动划水4年,过于真实了...

先简单交代一下吧&#xff0c;沅哥是某不知名211的本硕&#xff0c;18年毕业加入滴滴&#xff0c;之后跳槽到了头条&#xff0c;一直从事测试开发相关的工作。之前没有实习经历&#xff0c;算是四年半的工作经验吧。 这四年半之间他完成了一次晋升&#xff0c;换了一家公司&am…...

tensorflow GPU训练环境布置

tensorflow GPU训练环境布置 一、显卡驱动安装1.1 如何处理**Failed to initialize NVML: Driver/library version mismatch的问题**1.2 卸载旧的版本1.3 驱动安装 1.3.1 利用apt 安装1.3.2 手动安装 二、安装CUDA2.1 确定CUDA版本2.2 下载文件1. 找匹配版本2. 选合适的平台 2…...

理解和使用Java中的枚举

枚举是一种特殊的数据类型&#xff0c;用于定义一组具名的常量。Java中的枚举类型可以包含多个枚举常量&#xff0c;每个常量都具有唯一的名称和值。本文将详细介绍Java中的枚举&#xff0c;包括为什么要使用枚举、枚举的好处、如何定义和使用枚举等。 为什么要使用枚举&#…...

C++和Java:哪种语言更适合你

C和Java&#xff1a;哪种语言更适合你 一、引言1 背景介绍2 问题阐述3 目的和意义 二、C与Java的介绍1 C的特点和优缺点2 Java的特点和优缺点3 两种语言的比较4 选择C的理由4.1 适合底层开发的特点4.2高效的编译器和运行速度4.3 自由且灵活的语言风格4.4 良好的内存管理能力 5 …...

FE_Vue学习笔记 框架的执行流程详解

1 分析脚手架结构 &#xff08;1&#xff09;CLI就是 command line interface 的缩写。Vue CLI官网&#xff1a;Vue CLI &#xff08;2&#xff09;安装过程&#xff1a; &#xff08;PS&#xff1a; 提前安装过node.js了&#xff0c;没有安装的可以打开这个&#xff1a;Downl…...

KingbaseES V8R6 等待事件之LWLock Buffer_IO

等待事件含义 当进程同时尝试访问相同页面时&#xff0c;等待其他进程完成其输入/输出(I/O)操作时&#xff0c;会发生LWLock:BufferIO等待事件。其目的是将同一页读取到共享缓冲区中。 每个共享缓冲区都有一个与LWLock:BufferIO等待事件相关联的I/O锁&#xff0c;每次都必须在共…...

桂院导航小程序 静态项目 二次开发教程

Gitee代码仓库&#xff1a;桂院导航小程序 先 假装 大伙都成功安装了静态项目&#xff0c;并能在 微信开发者工具 和 手机 上正确运行。 接着就是 将项目 改成自己的学校。 代码里的注释我就不说明了&#xff0c;有提到 我的学校 的文字都改成你自己的就行 1. 全局 app.json…...

即时通讯APP开发费用成本多少?

移动互联网的发展&#xff0c;为人们的通讯交流提供了非常多的便利&#xff0c;一些即时通讯APP的出现&#xff0c;将人与人的距离再一次缩短。通过即时通讯APP软件&#xff0c;人们可以随时随地了解身边发生的新鲜事物&#xff0c;以及和朋友探讨各类趣事&#xff0c;甚至可以…...

女生学大数据好找工作么

好不好找工作和性别无关&#xff0c;无论你是男生还是女生&#xff0c;找工作的时候首先要看的都是学历&#xff0c;然后是个人能力&#xff0c;其中还有一定的面试经验和简历加分项~ 不要自己先把这个性别限定死&#xff0c;你有能力都能找到工作&#xff0c;不满足企业要求都…...

02-mysql升级篇(rpm方式+压缩包升级)

文章目录 升级方式一、二进制方式安装1、下载mysql-5.7.42安装包&#xff08;mysql-5.7.37升级mysql-5.7.42&#xff09;2、备份数据库、my.cnf文件&#xff0c;停止mysql服务&#xff08;重要&#xff09;3、查看当前数据库版本3、上传 mysql-5.7.42-1.el7.x86_64.rpm-bundle.…...

【Java零基础入门篇】第 ④ 期 - 继承(三)

【Java零基础入门篇】第 ④ 期 - 继承&#xff08;三&#xff09; 博主&#xff1a;命运之光专栏&#xff1a;Java零基础入门 学习目标 1.掌握继承性的主要作用、实现、使用限制&#xff1b; 2.掌握this和super的含义及其用法&#xff1b; 3.掌握方法覆写的操作&#xff1b; 4.…...

Python Selenium搭建UI自动化测试框架

自动化测试是软件测试中非常重要的一部分&#xff0c;可以提高测试效率和测试覆盖率。在UI自动化测试中&#xff0c;Selenium是非常流行的工具。本文将介绍如何使用Python和Selenium搭建UI自动化测试框架。 一、环境准备 在开始搭建UI自动化测试框架之前&#xff0c;需要先安装…...

开发大语言模型需要数据?算法?算力?

开发大语言模型的关键是什么。最近看到不少文章为了流量,甚至连 5G 通讯都说成了是开发大语言模型的关键 其实从前面的原理介绍,不难看出,大语言模型的其中一个关键点是数据。 关键一:数据​ 训练数据主要是所谓的语料库。今天的很多语言模型的语料库主要有以下几种: …...

CSS选择器的常见用法

目录 1、CSS编写方式 2.CSS选择器 1.标签选择器 2.类选择器 3.id选择器 4.后代选择器 3.CSS属性 CSS叫做"层叠样式表",作用就是装饰网页.类似于我们平时所说的化妆。 字体、大小、间距、颜色、位置、边框、背景等等统称为样式&#xff0c;用来描述一个网页。 …...

Oracle EBS修改密码

FNDCPASS修改密码 用户名必须出现在FND_USER或FND_ORACLE_USERID表中。FNDCPASS实用程序和ALLRACLE功能是为应用程序用户/模式设计的。 对于FND_USER或FND_ORACLE_USERID中不存在的用户&#xff0c;可以使用alter命令更改密码。 查询用户是否存在FND_USER或FND_ORACLE_USERI…...

《花雕学AI》33:如何用XMind制作AI思维导图、鱼骨图和组织结构图

思维导图是一种有效的思维工具&#xff0c;它可以帮助我们整理信息&#xff0c;激发创意&#xff0c;提高效率。思维导图是一种以中心主题为核心&#xff0c;以分支结构为形式&#xff0c;以关键词和图像为内容的图形表示法。它可以让我们一目了然地看到知识的层次和逻辑&#…...

【rust】| 06——语言特性 | 所有权

系列文章目录 【rust】| 00——开发环境搭建 【rust】| 01——编译并运行第一个rust程序 【rust】| 02——语法基础 | 变量(不可变?)和常量 【rust】| 03——语法基础 | 数据类型 【rust】| 04——语法基础 | 函数 【rust】| 05——语法基础 | 流程控制 【rust】| 06——语言特…...

AUTOSAR入门

简介 AUTOSAR&#xff08;AUTomotive Open System ARchitecture&#xff09;是一种汽车软件架构标准&#xff0c;由德国大陆、博世、宝马等汽车及零部件制造商共同发起&#xff0c;拥有广泛的行业参与。其目标是为了解决汽车电子和软件系统日益复杂的问题&#xff0c;提高可重…...

运维高可用架构的 6 大常规方案

在介绍高可用架构的方案之前&#xff0c;先说一下什么是高可用架构&#xff0c;高可用架构应具备但不限于以下特征&#xff1a; 主从切换 很好理解&#xff0c;当其中一台机器的服务宕机后&#xff0c;对于服务调用者来说&#xff0c;能够迅速的切换到其他可用服务&#xff0c;…...

Java设计模式-桥接模式

简介 桥接模式&#xff08;Bridge Pattern&#xff09;是一种结构性设计模式&#xff0c;它的主要作用是将抽象部分和实现部分解耦&#xff0c;使它们可以独立变化而不会互相影响。桥接模式最早由GoF&#xff08;Gang of Four&#xff09;提出&#xff0c;在《设计模式》一书中…...

计及N-k安全约束的含光热电站电力系统优化调度模型【IEEE14节点、118节点】(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…...

欧拉函数详解

文章目录 欧拉函数定义性质计算公式求某个数欧拉函数值线性筛求区域内欧拉函数 欧拉函数 定义 在[1,n]的范围内所有与n互质的数字的个数。 我们用 φ ( n ) \varphi(n) φ(n)来表示数字n的欧拉函数的值&#xff0c;例如&#xff1a; φ ( 4 ) 2 \varphi(4)2 φ(4)2&#xf…...

手把手教你如何将安卓手机数据导入iPhone!【详解】

案例&#xff1a;安卓数据导入苹果手机 【大神们&#xff0c;刚换了新的苹果手机&#xff0c;原本的安卓手机数据怎么导入新手机&#xff1f;】 想要换用iPhone&#xff0c;但是又不想丢失安卓手机里的重要数据怎么办&#xff1f;如何将安卓手机数据导入iphone&#xff1f;本文…...

怎么轻松地搞定Win11系统备份任务?

“我是一个电脑小白&#xff0c;不是很懂电脑的一些操作。我刚买了一台新电脑&#xff0c;它装的是Win11系统&#xff0c;我害怕它出现什么问题&#xff0c;听朋友说可以通过备份的方法保护系统&#xff0c;这是真的吗&#xff1f;有谁知道该怎么进行Win11系统备份吗&#xff1…...

MySQL集群

目录 主从复制 主从复制流程&#xff1a; 为什么要有relay log中继日志&#xff1f; 为什么要有主从复制&#xff0c;好处&#xff1f; 实际生产环境中。如果对MySQL数据库的读写都在一台数据库服务器中操作&#xff0c;无论是再安全性、高可用性&#xff0c;还是高并发性等…...

关于Kerberos认证的一些攻击手法学习总结

Kerberos认证流程 前言 本文主要分享最近学习的关于域内Kerberos认证的一些攻击手法&#xff0c;以自我的理解为主&#xff0c;从原理理解切入到基本工具利用来阐述&#xff0c;个人的理解分析较为啰嗦&#xff0c;嫌太兀长的可以跳着看就好&#xff0c;还请各位谅解。如有错误…...