类和对象 01【C++】

目录

  • 一、 类的引入
    • 1. 类的定义
    • 2. 类的访问限定符及封装
      • (1) 访问限定符
      • (2) 封装
    • 3. 类的实例化
    • 4. 类对象模型
      • (1) 计算类对象的大小
      • (2) 类对象的存储方式
    • 5. this指针
  • 二、 类的6个默认成员函数
    • 1. 构造函数
    • 2. 析构函数
    • 3. 拷贝构造函数
    • 4. 赋值运算符重载
    • 5. 取地址重载
    • 6. const取地址重载

前言:

C语言是面向过程的,关注的是过程,逐步解决问题的过程。

C++是基于面向对象的,关注的是对象,将一件事情分解成不同的对象,靠对象之间的交互完成。

一、 类的引入

C语言中的结构体里面只能定义变量,而C++的结构体里面不仅可以定义变量,也可以定义函数。

struct Node 
{//成员变量int a;int b;//成员函数void Print() {cout << "C++ class" << endl;}
};

1. 类的定义

class className //className 是类名
{//类的内容 由 成员变量和成员函数组成//...};	//注意这里的分号

在这里插入图片描述

在成员函数(类的方法)中,根据人为的一些规则来区分形参和成员变量

例如: 定义日期类的时候

class Date
{void Init(int year, int month, int day){year = year;	//这里我们就会比较容易搞混month = month;day = day;}int year;int month;int day;
};

所以为方便区分参数和成员变量,我们通常会给成员变量前加上一个下划线 _成员变量 (其他方式也可以) 来区分

class Date
{void Init(int year, int month, int day){_year = year;_month = month;_day = day;}int _year;	//通过 _成员变量 来区分int _month;int _day;
};

2. 类的访问限定符及封装

(1) 访问限定符

在这里插入图片描述

  • public 修饰的成员可以在类的外面直接被访问
  • protect 和 private 修饰的成员在类外面不能直接被访问
  • 访问权限的作用域:从访问限定符开始直到下一个访问限定符出现为止
  • 如果后面没有访问限定符,作用域就到 } (即类的结束)
  • 在没有写访问限定符时,class的默认访问为private,struct 的默认访问为public

例如:

class Date
{void Init(int year, int month, int day){_year = year;_month = month;_day = day;}int _year;	//通过 _成员变量 来区分int _month;int _day;
};int main() 
{Date d1;d1.Init(2024,1,31);		//error class的默认访问为privatereturn 0;
}

改正:加上public

class Date
{
public:void Init(int year, int month, int day){_year = year;_month = month;_day = day;}int _year;	//通过 _成员变量 来区分int _month;int _day;
};

上述的成员变量和成员函数都可以访问到了。

C++ 中 struck 也是可以上访问限定符的

int main() 
{// struct Date d;// class Date d; 这样写也可以Date d1;	//但是一般为了方便会把这里的class给省略return 0;
}

关于类的定义两种方式:

  1. 声明和定义全部放在类体中,当成员函数如果在类中定义,编译器可能会将其当成内联函数处理
  2. 类的声明放在 . h 文件按中,成员函数定义放在 . cpp 文件中

在第二种方法中 涉及到 (命名空间)类域

类的作用域: 类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员时,需要使用 ::作用域操作符指明成员属于哪个类域

//test.h
// 声明
class Date
{
private://成员变量int _year;	//通过 _成员变量 来区分int _month;int _day;
public://成员函数void Init(int year, int month, int day);void Print_Date();
};//test.cpp
//函数定义
void Date::Init(int year, int month, int day)	//注意这里的Date类域,说明是Date类中的成员函数
{_year = year;_month = month;_day = day;
}void Date::Print_Date() //加上Date类域
{cout << _year << "/" << _month << "/" << _day << endl;
}// 上述 先去局部去找,再去全局找,最后去Date类中找

C++中struct 和 class 的区别:

相同点:类的成员都可以有成员函数

不同点:struct 定义类的默认访问为 public (兼容C语言) ;class 定义类的默认访问为 private。

注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别。

(2) 封装

面向对象的三大特性:封装、继承、多态

封装:将数据和操作数据的方法进行有机的结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。

在C++语言中实现封装,可以通过类将数据以及操作数据的方法进行有机结合,通过访问权限来隐藏对象内部实现细节,控制哪些方法可以在类外部直接被使用。

3. 类的实例化

用类的类型创建对象的过程,称为类的实例化。通过实例化,我们可以得到一个具有类定义属性和方法的对象,并且可以对这个对象进行操作。

  • 声明出一个类并没有分配实际的内存空间
  • 一个类可以实例化出多个对象,实例化出的对象,占用实际的物理空间,存储类成员变量
  • 设计出类但没有进行实例化并不占内存空间
	Date._day;	// error Date类是没有空间,只有实例化出对象才有具体年龄
//实例化对象的正确方法Date d1;	//定义对象,实例化

( 定义的一个标志 是:开空间 )

4. 类对象模型

(1) 计算类对象的大小

class A 
{
public:void fu1() {}
private:int _a;
};class B 
{
public:void fu1() {}
};class C 
{};
int main() 
{A a;B b;C c;cout << sizeof(a) << endl;	// 4cout << sizeof(b) << endl;	// 1cout << sizeof(c) << endl;	// 1return 0;
}

通过计算类的对象,一个类的大小,就是该类成员变量之和,前提在内存对齐下

空类的大小是1,编译器给空类一个字节来标识这个类的对象

结构体内存对齐规则:

  • 首先第一个成员在 结构体偏移量为0的地址处
  • 其他成员变量要对齐到(对齐数)的整数倍处,对齐数 = 编译器默认对齐数大小 与 该成员大小 之中的较小值
  • 结构体的总体大小为:最大对齐数( 就是所有变量类型最大者 与 默认默认对齐数中较小的一方) 的整数倍
  • 如果嵌套了结构体,嵌套的结构体对齐到自己最大对齐数 的整数倍,结构体的整体大小就是所有最大对齐数(包括嵌套结构体的对齐数) 的整数倍

内存对齐:会导致空间浪费,为什么要内存对齐呢?请猛击

(2) 类对象的存储方式

	Date d1;Date d2;d1._day;d2._day;//定义出的变量地址不同d1.Print_Date();d2.Print_Date();// d1 与 d2 的 Print_Date地址相同

在这里插入图片描述

存储方式

在这里插入图片描述

例题:思考下方运行结果

	Date a1;Date* p1 = &a1;p1->f1();Date* p2 = nullptr;p2->f1();//A、编译错误//B、运行错误//C、运行正常

答案是 运行正常,因为 虽然有箭头,但没有进行解引用( f1并没有在p2指向的内存空间里面,而是在公共代码区)

5. this指针

先看下方问题:

在这里插入图片描述

这里我们首先要知道

在这里插入图片描述

所以去访问实例对象的,那么C++又是怎样分出d1和d2的呢?

C++中通过引入this指针解决该问题,C++编译器给每个"非静态的成员函数"增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),函数体中所有"成员变量"的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要传递,编译器自动完成。

编译器处理

在这里插入图片描述

所以是通过隐藏的this指针来进行区分d1和d2的

this指针存在哪里? 我们发现this指针出现在函数的形参中,又形参和局部变量 存在栈帧里面,所以this指针存在栈里面

// 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{public:void Print(){cout << "Print()" << endl;}private:int _a;
};
int main()
{A* p = nullptr;p->Print();return 0;
}//答案是 C 正常运行

原因是:Print函数在公共代码区,不在p指向的内存空间中,所以并没有触发p指针解引用去内存找,Print函数地址在编译的时候就有了。

如果 (p->_a++; 这里会造成 B、运行崩溃)

如果(*p).Print(); 答案是 正常运行和p->Print();一样

// 下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{public:void Print(){cout << _a << endl;}private:int _a;
};
int main()
{A* p = nullptr;p->Print();return 0;
}//答案是 B

this指针的传递是没有问题的,但是空指针的访问会导致运行崩溃

小总结

  • Date this 其实是 Date const this** ,即this指针指向的内容可以进行修改,但是this指针本身是不可以改变的,不能给this指针赋值

  • 隐藏的this指针我们不能显示写出来,但是在函数内部可以用

    	void Print_Date(){cout << this->_year << "/" << this->_month << "/" << this->_day << endl;}//包括void Init(int year, int month, int day){this->_year = year;this->_month = month;this->_day = day;}
    
  • this指真本质上是"成员函数"的形参,当对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针

  • this指针是"成员函数"第一个隐含的指针形参,

  • 一般情况由编译器通过 ecx 寄存器自动传递,不需要用户传递

  • 隐藏的this指针我们不能显示写出来,但是在函数内部可以用

    	void Print_Date(){cout << this->_year << "/" << this->_month << "/" << this->_day << endl;}//包括void Init(int year, int month, int day){this->_year = year;this->_month = month;this->_day = day;}
    
  • this指真本质上是"成员函数"的形参,当对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针

  • this指针是"成员函数"第一个隐含的指针形参,

  • 一般情况由编译器通过 ecx 寄存器自动传递,不需要用户传递

二、 类的6个默认成员函数

1. 构造函数

当一个类什么成员都没有的时候称为空类
实际上空类并不是什么的都没有,当类在什么都没有写时,编译器会自动生成6个默认成员函数。
默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。

class Date{};

构造函数是一个特殊的成员函数构造函数的函数名和类名相同,创建类对象时,由编译器自动调用,以保证每个数据成员都有一个合适的初始值,并且在该对象的整个生命周期内只调用一次。

构造函数虽然名字叫构造,但实际上主要任务并不是开空间创建对象,而是初始化对象

注意:

  • 函数名与类名相同
  • 没有返回值(返回类型不用写)
  • 对象实例化时编译器自动调用对应的构造函数
  • 构造函数可以重载
#include<iostream>using namespace std;class Date
{
public://无参的构造函数Date()	//特殊的成员函数{_year = 1;_month = 2;_day = 3;}//带参数的构造函数Date(int year,int month,int day){_year = year;_month = month;_day = day;}void Print_Date(){cout << _year << "/" << _month << "/" << _day << endl;}
private:int _year;int _month;int _day;
};int main()
{Date d1;	//对象实例化的时候 编译器自动调用无参构造函数d1.Print_Date();Date d2(1,1,1);	//调用带参的构造函数d2.Print_Date();return 0;
}

在这里插入图片描述
无参构造Date d1(); 不能这样的的原因是:不能与函数声明区分开 Date func(); 这样无参数的函数声明区分开,而带参是可以区分开的 因为Date func(int…);这里是有类型的

注意
C++规定对象实例化的时候必须调用构造函数,如果类中没有显示定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显示定义了,编译器将不再生成。

对象实例化需要调用相应的构造函数吗,对于上述的两个构造函数其实也是可以通过缺省函数写成一个

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

当没有显示定义构造函数的时候

	Date d1;	d1.Print_Date();

打印
在这里插入图片描述
发现打印结果竟然是随机值(没有初始化)
原因是:
C++把类型分为 内置类型(基本类型) 和 自定义类型。内置类型就是语言提供的数据类型,如:int,float…;自定义类型 使用 class/struct/union 等自己定义的类型。C++98规定默认生成的构造函数对内置类型不做处理,自定义类型回去调用自己的默认构造


class Time 
{
public:Time() {cout << "Time" << endl;_hour = 0;_minute = 0;_second = 0;}
private:int _hour;int _minute;int _second;
};class Date
{
public:void Print_Date(){cout << _year << "/" << _month << "/" << _day << endl;}private://内置类型(基本类型)int _year = 1;	//类声明时给值int _month = 1;int _day = 1 ;//自定义类型Time _t;};int main()
{Date d1;d1.Print_Date();return 0;
}

打印:
在这里插入图片描述

默认生成的构造函数对内置类型不做处理,自定义类型回去调用自己的默认构造
然而,内置类型没有初始化
所以 在C++11中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值

在这里插入图片描述
分析一个类型成员和初始化需求:

  • 需要写构造函数我们就自己写;不需要时就是用编译器自己生成的
  • 对于绝大多数情况下都需要我们自己实现构造函数

严格上讲默认构造函数有三种:
第一个是编译器默认生成的,第二种是无参构造函数,第三种是全缺省构造函数

class Date 
{
public:void Init(int year,int month,int day){_year = year;_month = month;_day = day;}void Print_Date() {cout << _year << "/" << _month << "/" << _day << endl;}
private:int _year;int _month;int _day;
};
int main()
{Date d1;d1.Print_Date();return 0;
}

当编译上述代码时:
在这里插入图片描述
因为写了构造函数,编译器就不会自动生成相应的默认构造函数,因为对于无参和全缺省只能存在一个,一般情况下,建议提供全缺省构造函数

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

2. 析构函数

(1) 析构函数的概念
析构函数:析构函数不是对对象本身销毁,局部对象销毁工作是由编译器完成的,而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作

(2) 析构函数时特殊的成员函数,其特征如下:

  • 析构函数名是在类名前加上字符~
~Date(){}	//析构函数
  • 析构函数没有参数,没有返回值类型
  • 一个类只能有一个析构函数;若没有显示定义,系统会自动生成默认的析构函数;注意析构函数不能重载
  • 当对象生命周期结束时,C++编译时系统会自动调用析构函数。
    调用析构函数的小细节:
    因为局部变量和函数一般是在函数栈帧里面的,即析构函数,后定义的先析构
class Date 
{
public:Date(int year = 1){_year = year;}~Date(){cout << "~Date()->" <<_year<< endl;	//调用析构的时候}
private:int _year;int _month;int _day;
};
int main()
{Date d1(1); //析构函数 后 定义的 先 析构Date d2(2);return 0;
}

打印结果:
在这里插入图片描述
根据打印结果也发现,后定义的先进行析构

	Date d1(1); //析构函数 后 定义的 先 析构Date d2(2);static Date d3(3);

打印结果:
在这里插入图片描述
上述增加了一个 static Date d3(3); 却没有按照后定义的先进行析构,以为它们存放的区域是不同的,static Date d3(3);(存放在静态区的)其生命周期是全局的,所以main函数销毁的时候会调用d3的析构函数,当然在调用d3的析构前,需要先去销毁局部的
再次修改一下:

void func() 
{Date d3(3);	//局部的static Date d4(4);	//全局的
}
int main()
{Date d1(1); //析构函数 后 定义的 先 析构Date d2(2);func();return 0;
}

打印结果:
在这里插入图片描述
析构,先局部再全局
小总结:
析构函数的调用顺序:
局部对象(后定义的先析构) --> 局部的静态对象 --> 全局对象(后定义的先析构)

  • 默认生成的析构函数和构造函数类似
    内置类型不做处理,自定义类型的成员去调用它的析构函数
  • 局部成员,即函数需要进入函数栈帧,所以析构函数是后定义的先析构( 满足后进先出 )

分析下方代码:

#include<iostream>using namespace std;class Time 
{
public:~Time() {cout << "Time()" << endl;}
private:int _hour;int _minute;int _second;
};
class Date 
{
public:Date(int year = 10, int month = 10, int day = 10){_year = year;_month = month;_day = day;}void Print_Date() {cout << _year << "/" << _month << "/" << _day << endl;}private://内置类型int _year = 1;int _month = 1;int _day = 1;//自定义类型Time _t;
};int main() 
{Date d1;return 0;
}

程序运行结果:

Time()

发现main方法中没有直接创建Time类对象,最后却调用了Time类的析构函数
原因是:在main函数中创建了Date对象 d1,而d1中包含4个成员变量,其中_year,_month,_day是内置类型成员,而_t是Time类对象,所以在d1销毁时,要将的d1中包含的Time类的_t对象进行销毁,所以要调用Time类的析构函数。
所以不能直接调用Time类的析构函数,首先是编译器先生成一个Date类的默认的析构函数,进而在其内部调用Time类的析构函数。
要保证其内部每个定义对象都可以正确销毁

3. 拷贝构造函数

在类和对象中,我们可能会想在创建对象时,可否创建一个 与 已经存在对象一模一样的新对象呢?
拷贝构造函数:在C++中,拷贝构造函数是一种特殊的构造函数,用于创建并初始化一个对象,作为另一个已经存在对象的副本。当创建对象需要基于已存在对象的内容时,拷贝构造函数就会被调用。
例如:

#include<iostream>
using namespace std;
class Date 
{
public:Date(int year = 1,int month =1,int day = 1){_year = year;_month = month;_day = day;}void Print_Date(){cout << _year << "/" << _month << "/" << _day << endl;}//将d1拷贝构造给d2Date(Date & d)	//拷贝构造,这里必须使用引用,Date(Date d)会导致无穷递归的{_year = d._year;_month = d._month;_day = d._day;}
private:int _year = 1;int _month = 1;int _day = 1;
};int main() 
{Date d1(1970,1,1);Date d2(d1);	//拷贝构造用一个相同类型的其他对象(即,日期类 类型对象)进行构造return 0;
}

调试查看结果发现;
在这里插入图片描述
完成了拷贝构造

上述的拷贝构造函数形参为什么使用引用呢?
因为,C++中,对于自定义的类型传值传参时都会调用拷贝构造

首先先看 传值 传参 和 传引用 传参

关于传值传参调用拷贝构造
在这里插入图片描述
传引用传参就不用调用拷贝构造函数
在这里插入图片描述

当使用传值传参的时候,再去调用拷贝构造引起的无穷递归的原因是:
调用拷贝构造,要先传参,这里因为是传值传参,会形成一个新的拷贝构造。当按值传递对象到函数(包括拷贝构造函数)时,C++默认会调用拷贝构造函数来创建函数参数的副本。如果拷贝构造函数的实现本身又试图使用按值传递的方式来复制其参数,这就会导致无穷递归。
如图:
在这里插入图片描述
所以 使用引用可以解决无穷递归问题
在这里插入图片描述
当我们使用引用后,但是不想因为引用取别名去修改原来的值,所以使用了const进行常量修饰,相当于缩小了权限

	Date(const Date &d)	//const进行修饰,让指向的内容只能读,不能修改{this->_year = d._year;this->_month = d._month;this->_day = d._day;}

拷贝构造与普通构造函数的一个区别:
先看下方代码:

//拷贝构造与构造的一个区别
#include<iostream>using namespace std;class Date 
{
public:void Print_Date() {cout << _year << "/" << _month << "/" << _day << endl;}//构造函数Date(int year = 1,int month = 1,int day = 1) {_year = year;_month = month;_day = day;}private:int _year;int _month;int _day;
};int main() 
{Date d1(1970,1,1);Date d2(d1);d1.Print_Date();d2.Print_Date();return 0;
}

打印结果;
在这里插入图片描述
上述代码中并没有写拷贝构造,调用d2的Print_Date时候发现,打印结果和d1的一样
我们知道,拷贝构造也是默认成员函数,当我们没有手动写时,编译器会生成一个默认成员函数。
当没有写任何的构造函数,编译器会默认生成;写了任何构造函数,编译器不在生成构造函数。
构造函数:编译自动生成的,对于内置类型不做处理,自定义类型去调用它的默认构造函数。C++11的补丁 内置类型成员变量在类中声明时可以给默认值
拷贝构造函数:
对默认生成的内置类型进行处理
对于自定义类型,当写了拷贝构造函数,没有写构造函数发现;
在这里插入图片描述
我们可以使用

	//强制生成构造函数Time() = default;
class Time 
{
public:~Time() {cout << "~Time() " << endl;};//强制生成构造函数Time() = default;//拷贝构造函数Time(const Time& d){cout << "Time(const Time& d)" << endl;_hour = d._hour;_minute = d._minute;_second = d._second;}
private:int _hour;int _minute;int _second;
};

小总结:

  • 拷贝构造函数是构造函数的一个重载形式
  • 拷贝构造函数的参数只有一个,必须是类 类型对象的引用,如果使用传值的方式编译器直接报错(引发了无穷递归)
  • 当没有显示定义,编译器会生成默认的拷贝构造函数,内置类型是按照字节序方式直接拷贝的,而自定义类型是调用相应的拷贝构造函数完成的。
  • 像上述日期类是没必要显示实现拷贝构造函数的,但是像malloc动态开辟空间(在堆上)需要我们显示实现拷贝构造函数。
    注意:在(堆空间)不能浅拷贝(会造成二次free,容易出错),需要进行深拷贝。
    例如:
    浅拷贝:
    在这里插入图片描述
    深拷贝:
//深拷贝Stack(const Stack& s) {DataType* tmp = (DataType*)malloc(s._capacity*(sizeof(DataType)));if (tmp == nullptr){perror("malloc fail");exit(-1);}memcpy(tmp,s._a,s.size*(sizeof(DataType)));_a = tmp;_size = s.size;_capacity = s._capacity;}

在这里插入图片描述

4. 赋值运算符重载

前言:
先看一段代码:

#include<iostream>
using namespace std;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 DateEqual(const Date& x,const Date& y) 
{return x._year == y._year&& x._month == y._month&& x._day == y._day;
}bool DateLess(const Date& x,const Date& y) 
{if (x._year < y._year) {return true;}else if (x._year == y._year){if (x._month < y._month) {return true;}else if (x._month == y._month){//if (x._day < y._day) //{//	return true;//}return x._day < y._day;}}return false;
}int main() 
{Date d1(1970,2,1);Date d2(1970,2,8);DateEqual(d1,d2);	//比较日期是否相等DateLess(d1,d2);	//判断d1对象的日期是否小cout << DateEqual(d1, d2) << endl << DateLess(d1, d2) << endl;return 0;
}

上述代码,我们发现,对于自定义类型的比较,我们需要函数来实现,但是实现这些函数存在一些问题,给函数取名的问题,会造成代码的可读性变差。

C++为了增加代码的可读性,增加了运算符重载,运算符重载是具有特殊函数名的函数。有和普通函数类似的返回值类型,函数名字,参数列表

// 返回值类型 operator操作符(参数列表...){}
#include<iostream>
using namespace std;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;
};bool operator==(const Date& x, const Date& y)
{return x._year == y._year&& x._month == y._month&& x._day == y._day;
}bool operator<(const Date& x, const Date& y)
{if (x._year < y._year){return true;}else if (x._year == y._year){if (x._month < y._month){return true;}else if (x._month == y._month){return x._day < y._day;}}return false;
}int main()
{Date d1(1970, 2, 1);Date d2(1970, 2, 8);//运算符重载  返回值类型 operator操作符(参数列表...){}//参数顺序最好保持一致cout << operator==(d1,d2) << endl; //cout << ( d1 == d2 )<< endl;cout << operator<(d1, d2) << endl; //cout << ( d1 < d2 ) << endl;cout << ( d1 == d2 )<< endl;	//注意要加上括号,因为<<优先级比较高	cout << ( d1 < d2 ) << endl;//发现上述自定义类型也是可以使用运算符的return 0;
}

在这里插入图片描述
注意:上述的内置类型变成了公有,因为函数实现在类的外面

当然运算符重载函数可以放到类里面
看下方代码

#include<iostream>
using namespace std;
class Date 
{
public:Date(int year = 1,int month = 1,int day = 1){_year = year;_month = month;_day = day;}bool operator==(const Date& y){return _year == y._year&& _month == y._month&& _day == y._day;}bool operator<(const Date& y){if (_year < y._year){return true;}else if (_year == y._year) {if (_month < y._month) {return true;}else if (_month == y._month){return _day < y._day;}}return false;}
private:int _year;int _month;int _day;
};
int main()
{Date d1(1970,1,1);Date d2(2000,1,1);cout << d1.operator==(d2) << endl;cout << d1.operator<(d2) << endl;cout << (d1 == d2) << endl;	//cout << d1.operator==(d2) << endl;cout << (d1 < d2) << endl;	//cout << d1.operator<(d2) << endl;return 0;
}

在这里插入图片描述
注意:

  • 重载操作符必须有一个类 类型参数
  • 作为类成员函数重载时,形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
  • .* :: sizeof ?: . 以上五个运算符不能重载

上述的 .* 操作符的一个使用介绍:

class ob 
{
public:void func() {cout << "void func()" << endl;}
};
//我们平时 typedef void(*)() pobfunc;  
//函数指针和数组指针需要特殊定义
typedef void (ob::*pobfunc)();	//函数指针(成员函数指针)
int main() 
{pobfunc p = &ob::func;	//注意这里的取地址&ob temp;//对于普通的函数指针我们可以(*p)();去调用函数(temp.*p)();	//这里使用 .*是去调用成员函数 (目的是把this传过去)return 0;
}

赋值运算符重载

	d1 = d2;		//已经存在的对象,一个拷贝赋值给另一个,重载运算符

赋值运算符重载格式

Ⅰ 参数类型: const T&

对于内置类型的连续赋值每次赋值都会有返回值
赋值运算符重载有返回值类型: T& ,有返回值类型的目的是为了支持连续赋值

	//...Date& operator=(const Date& d){_year = d._year;_month = d._month;_day = d._day;return *this;	//注意这里返回的是*this,不是this(因为this是指针)}

在这里插入图片描述
如果上述使用 operator=返回Date,编译器可能需要创建一个临时对象来返回结果,这样可能会导致不必要的拷贝操作,尤其对于在大型对象的情况下,甚至有可能会影响到程序的运行。

所以使用引用Date&返回,可以避免复制这些对象,避免不必要的拷贝,支持连续赋值操作

Ⅲ 检测是否自己个自己赋值:

	//可能会出现自己给自己赋值的情况//d1 = d1;

所以为了避免自己给自己赋值的情况:

	Date operator=(const Date& d){if (this != &d)	//防止自己给自己赋值,注意这里是取地址{_year = d._year;_month = d._month;_day = d._day;}return *this;	//注意这里返回的是*this,不是this(因为this是指针)}

返回*this:要进行链式赋值

当没有显示实现赋值运算符重载时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。
注:内置类型 成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。与拷贝构造类似

赋值运算符只能重载成类的成员函数,而 不能 重载 成 全局函数

因为:赋值运算符重载,当类中没有手动写的时候,编译器会生成一个默认的,此时类外手动生成的赋值重载全局函数,编译器调用的时候,不知道去调用哪一个。所以故赋值运算符重载只能是类的成员函数。

在这里插入图片描述
在这里插入图片描述
建议:写+的时候,去复用+=

5. 取地址重载

这个默认成员函数一般不用手动实现,由编译器默认生成

手动生成:

//取地址重载
//一般情况不需要我们手动实现,由编译器默认生成
class A 
{
public:A* operator&(){return this;//返回隐藏this指针}
};
int main() 
{A a1;cout << &a1 << endl;
}

特殊情况:不想要别人拿到我的地址

class A 
{
public:A* operator&(){//return this;//返回隐藏this指针return nullptr;	//注意这里返回空指针}const A* operator&() const{return this;}
};
int main() 
{A a1;const A a2;cout << &a1 << endl;cout << &a2 << endl;
}

在这里插入图片描述
上述的普通地址就获取不到了

6. const取地址重载

这个默认成员函数一般不用手动实现,由编译器默认生成

那么 手动生成:

//取地址重载
//一般情况不需要我们手动实现,由编译器默认生成
class A 
{
public:const A* operator&() const //第二个const修饰的是this{return this;}};
int main() 
{const A a2;cout << &a2 << endl;
}

特殊情况:
让const取地址重载返回假的地址

class A 
{
public:A* operator&(){return this;//返回隐藏this指针//return nullptr;}const A* operator&() const{return (const A*)(0x0012ff40);}
};
int main() 
{A a1;const A a2;cout << &a1 << endl;cout << &a2 << endl;
}
	const A* operator&() const{int a = 1;return (const A*)&a;//return (const A*)(0x0012ff40);}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/261206.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

蓝桥杯:C++队列、优先队列、链表

C普通队列 算法竞赛中一般用静态数组来模拟队列&#xff0c;或者使用STL queue。使用C的STL queue时&#xff0c;由于不用自己管理队列&#xff0c;因此代码很简洁。队列的部分操作如下。 C优先队列 很多算法需要用到一种特殊的队列&#xff1a;优先队列。它的特点是最优数据…

前端canvas项目实战——简历制作网站(四)——右侧属性栏(线条端点样式)

目录 前言一、效果展示二、实现步骤1. 实现线条和端点的组装模块2. 修复一个fabric自身的bug3. 实现属性栏中的编辑模块4. 把UI操作和画布更新连接起来 三、Show u the code后记 前言 上一篇博文中&#xff0c;我们扩充了线条对象&#xff08;fabric.Line&#xff09;的属性列…

构建企业数据安全的根基:深入解析数据安全治理能力评估与实践框架

随着数字化转型深入各行各业&#xff0c;数据安全已成为企业不可或缺的重要议题。在这一背景下&#xff0c;有效的数据安全治理框架成为确保企业数据安全的基石。 一、数据安全治理框架 中国互联网协会于 2021 年发布 T/SC-0011-2021《数据安全治理能力评估方法》&#xff0c…

windows10|音视频剪辑|FFMPEG录屏和网络推流源初步的生成

前言&#xff1a; FFMPEG的功能强大是毋庸置疑的&#xff0c;那么录屏的需求大家在某些时候大家可能是非常需要的&#xff0c;例如&#xff0c;现有的项目需要演示&#xff0c;因此录制一段演示视频&#xff1b;亦或者做内容分发直播的&#xff0c;比如游戏主播&#xff0c;需…

MySQL 学习记录 1

原文&#xff1a;https://blog.iyatt.com/?p12631 1 前言 去年年初报考 3 月的计算机二级&#xff08;C 语言&#xff09;【https://blog.iyatt.com/?p9266 】考过了&#xff0c;这次打算报考 3 月的计算机三级&#xff08;数据库&#xff09;。数据库这一块&#xff0c;很…

Kubernetes(K8s)的基础概念

K8s的概念 K8S 的全称为 Kubernetes (K12345678S) &#xff08;简化全称&#xff09; Kubernetes 是一个可移植、可扩展的开源平台&#xff0c;用于 管理容器化工作负载和服务&#xff0c;有助于声明式配置和自动化。它拥有庞大且快速发展的生态系统。Kubernetes 服务、支持和…

SQL防止注入工具类,可能用于SQL注入的字符有哪些

SQL注入是一种攻击技术&#xff0c;攻击者试图通过在输入中注入恶意的SQL代码来干扰应用程序的数据库查询。为了防止SQL注入&#xff0c;你需要了解可能用于注入的一些常见字符和技术。以下是一些常见的SQL注入字符和技术&#xff1a; 单引号 ​&#xff1a; 攻击者可能会尝试…

【前端工程化面试题】webpack proxy的工作原理,为什么能解决跨域问题

在 webpack 的配置文件 webpack.config.js 中有一个配置项 devServer 里面有一个属性是 proxy&#xff0c;这里面可以配置代理服务器&#xff0c;解决跨域问题&#xff0c;请参考官网。 一般来说 webpack 的代理就是说的开发服务器 webpack-dev-server。 其实不光是 webpack 其…

线阵相机之帧超时

1 帧超时的效果 在帧超时时间内相机若未采集完一张图像所需的行数&#xff0c;则相机会直接完成这张图像的采集&#xff0c;并自动将缺失行数补黑出图&#xff0c;机制有以下几种选择&#xff1a; 1. 丢弃整张补黑的图像 2. 保留补黑部分出图 3.丢弃补黑部分出图

爬虫知识--02

免费代理池搭建 # 代理有免费和收费代理 # 代理有http代理和https代理 # 匿名度&#xff1a; 高匿&#xff1a;隐藏访问者ip 透明&#xff1a;服务端能拿到访问者ip 作为后端&#xff0c;如何拿到使用代理人的ip 请求头中&#xff1a;x-forwor…

⭐北邮复试刷题106. 从中序与后序遍历序列构造二叉树__递归分治 (力扣每日一题)

106. 从中序与后序遍历序列构造二叉树 给定两个整数数组 inorder 和 postorder &#xff0c;其中 inorder 是二叉树的中序遍历&#xff0c; postorder 是同一棵树的后序遍历&#xff0c;请你构造并返回这颗 二叉树 。 示例 1: 输入&#xff1a;inorder [9,3,15,20,7], postor…

人工智能深度学习

目录 人工智能 深度学习 机器学习 神经网络 机器学习的范围 模式识别 数据挖掘 统计学习 计算机视觉 语音识别 自然语言处理 机器学习的方法 回归算法 神经网络 SVM&#xff08;支持向量机&#xff09; 聚类算法 降维算法 推荐算法 其他 机器学习的分类 机器…

基于vue的个性化推荐餐饮系统Springboot

项目&#xff1a;基于vue的个性化推荐餐饮系统Springboot 摘要 现代信息化社会下的数据管理对活动的重要性越来越为明显&#xff0c;人们出门可以通过网络进行交流、信息咨询、查询等操作。网络化生活对人们通过网上购物也有了非常大的考验&#xff0c;通过网上进行点餐的人也…

C# Winfrom实现的肺炎全国疫情实时信息图

运行结果&#xff1a; using System; using System.Drawing; using System.Text; using NSoup; using NSoup.Nodes; using System.IO; using System.Net; using System.Text.RegularExpressions; using System.Windows.Forms;namespace Pneumonia {public partial class MainFo…

C#开发AGV地图编辑软件

C#自己开发AGV地图编辑软件&#xff1a; 1、自由添加和删除站点、停车位、小车、运行路径。 2、编辑得地图以XML文件保存。 3、导入编辑好地图的XML文件。 4、程序都是源码&#xff0c;可以直接在此基础上进行二次开发。 下载链接&#xff1a;https://download.csdn.net/d…

【Pytorch深度学习开发实践学习】B站刘二大人课程笔记整理lecture04反向传播

lecture04反向传播 课程网址 Pytorch深度学习实践 部分课件内容&#xff1a; import torchx_data [1.0,2.0,3.0] y_data [2.0,4.0,6.0] w torch.tensor([1.0]) w.requires_grad Truedef forward(x):return x*wdef loss(x,y):y_pred forward(x)return (y_pred-y)**2…

19个Web前端交互式3D JavaScript框架和库

JavaScript &#xff08;JS&#xff09; 是一种轻量级的解释&#xff08;或即时编译&#xff09;编程语言&#xff0c;是世界上最流行的编程语言。JavaScript 是一种基于原型的多范式、单线程的动态语言&#xff0c;支持面向对象、命令式和声明式&#xff08;例如函数式编程&am…

Spring最新核心高频面试题(持续更新)

1 什么是Spring框架 Spring框架是一个开源的Java应用程序开发框架&#xff0c;它提供了很多工具和功能&#xff0c;可以帮助开发者更快地构建企业级应用程序。通过使用Spring框架&#xff0c;开发者可以更加轻松地开发Java应用程序&#xff0c;并且可以更加灵活地组织和管理应…

OpenAI全新发布文生视频模型:Sora!

OpenAI官网原文链接&#xff1a;https://openai.com/research/video-generation-models-as-world-simulators#fn-20 我们探索视频数据生成模型的大规模训练。具体来说&#xff0c;我们在可变持续时间、分辨率和宽高比的视频和图像上联合训练文本条件扩散模型。我们利用对视频和…

【Vuforia+Unity】AR03-圆柱体物体识别

1.创建数据库模型 这个是让我们把生活中类似圆柱体和圆锥体的物体进行AR识别所选择的模型 Bottom Diameter:底部直径 Top Diameter:顶部直径 Side Length:圆柱侧面长度 请注意&#xff0c;您不必上传所有三个部分的图片&#xff0c;但您需要先为侧面曲面关联一个图像&#…