Bootstrap

C++必问知识点精华总结(建议收藏)

摘要

近期回顾、复习C++的一些笔记,记录了C++中比较重要的一些知识点,方便日后自己查缺补漏(未记录基础语法等基础部分),分享于此希望也能帮助到大家解决有关C++部分的疑问。

适合人群:有C/C++基础,想要进一步巩固自己C++知识体系的筒子

C++知识真的好杂,感觉总是在打破自己定义的规则,硬着头继续学!

小建议:有的问题,站在编译器的角度去考虑往往就更好理解。

注:PDF版本文章底部自取

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

封装:可以实现类对部分成员和方法进行隐藏,只暴露想要暴露的细节。

​ -把变量(属性)和方法(操作)合成一个整体,封装在一个类中

​ -对变量和方法进行访问控制[设置权限]

继承:类与类之间的关系,作用是可以避免公共部分代码的重复开发,减少代码和数据冗余

多态:即“一个接口有多种方法[形态]",程序运行时才决定调用的函数


命名空间namespace作用及注意点

C语言通过static关键字来控制标识符只得在当前源文件可见,C++还可以通过namespace命名空间实现。

命名空间通常解决在多人合作过程种出现的标识符重复问题

注意:

  1. 命名空间只能写在全局中

  2. 命名空间可以进行嵌套

  3. 命名空间是开放的,可随时加入,但加入后的成员作用域在其加入后

    namespace A{
    	int a;
    }
    //在命名空间A中加入int c;
    namespace A{
        int c;//在此之后,命令空间A中的c才能被使用
    }
    
  4. 通过作用域运算符::来访问不同命名空间A::cd的成员

    ::作用域运算符也可以解决、局部变量与全局变量的重名问题

    //全局变量
    int a = 10;
    void test(){
    	int a = 20;//局部变量
    	cout << "局部变量a:" << a << endl;//打印局部变量a
    	cout << "全局变量a:" << ::a << endl;//打印全局变量a
    }
    
  5. 匿名命名空间作用

    namespace {
        int a; //相当于static int a;
    }
    //限制其只能在本文件被使用
    
  6. 可以取别名

    namespace newname = oldname;
    cout<<newname::a<<endl;
    
  7. 一个.h文件下有两个命名空间,且具有相同的成员时,在.c文件使用或定义时,需要加上作用域符::

    不然会出现链接问题如:无法解析的外部命令


using 声明 与 编译指令

using声明:让命名空间中某个标识符直接使用,但同时也可能造成二义性

namespace A{
    int a;
    int b;
}
void test(){
    //using是可以让命名空间中某个标识符直接使用
    using A::a;
    cout<<a<<endl;//using声明后可以直接使用
    int a = 10; //错误语句!!!同名冲突
}
注意:如果命名空间包含一组用相同名字重载的函数,using声明就声明了这个重载函数的所有集合。

using 编译指令:让命名空间中的所有标识符都可以直接使用

namespace A{
    int a = 1;
    int b;
}
namespace B{
    int a = 2;
    int b;
}
void test(){
    //using 编译指令
    using namespace A;
    cout<<a<<endl;//可以直接使用,打印1
    int a = 10; //此时不会冲突!
    //相当于命名空间中的a为全局变量,此处的为局部变量,覆盖了全局变量a,不会冲突
    cout<<a<<endl;//打印10
}
void test2(){
	using namespace A;
    using namespace B;
	cout<<a<<endl;//错误!!此时又会冲突了,不知道调用A还是B的成员a
}

struct 类型的加强

  1. C中定义结构体变量需要加上struct关键字,C++不需要。
  2. C中的结构体只能定义成员变量,不能定义成员函数。C++即可以定义成员变量,也可以定义成员函数。
struct a{
    char c;
    int dd;
};//此时没有分配空间
a my_a{'a',10};//此时分配空间了

C语言中const修饰的全局变量不能直接修改,也不能间接修改,但修饰的局部变量不能直接修改,可以间接修改。修饰的全局变量具有外部链接属性,即可以extern const int a使用。

C++中的编译器可能会在编译阶段优化const的变量,从而提高效率。修饰的全局变量具有内部链接属性,只能在本文件内使用。

const int a = 10;//在c++中没有内存

const int b = 20;
int * p = (int*)&bb;//进行了取址,所以有空间
*p = 200;
cout<<"b:"<<b<<endl;//打印20 因为编译器进行了优化,等价于 cout<<"b:"<<20<<endl,即发生了常量折叠 
//如果不想被优化,需要加上volatile修饰,或者用变量给const修饰的局部变量赋值
cout<<"*p:"<<*p<<endl;//打印200
//两者地址是一样的
cout<<"b地址:"<<&b<<endl;
cout<<"p指向的地址:"<<p<<endl;

尽量用const代替#define,不是一定要,是尽量

  1. #define没有数据类型,const修饰的变量有数据类型,会进行类型检查
  2. const有作用域,而#define不重视作用域,默认定义处到文件结尾,不能限定常量的作用范围。宏常量可以有命名空间吗?

引用(非常重要!)

语法:type &ref = val,此处的&不是取地址操作符,起的是标识作用 [引用是什么?–给空间取别名]

本质:内部实现是一个常指针type * const ref = &val[即不能改变指针指向],因此引用所占用的空间大小与指针相同

使用场景:

  1. 作为函数参数传递数据,节约空间
  2. 作为函数返回值,但是注意不要返回局部变量的引用 [因为局部变量在函数结束后内存会被回收]

注意:

  1. 必须在声明引用变量时进行初始化
  2. 初始化后不能改变
  3. 不能有NULL引用,必须确保引用是和一块合法的存储单元关联
  4. 如果函数当左值,那么该函数必须返回引用**[针对使用场景的第2条]**

建立数组的引用:

int arr[] = {1,2,3,4,5};
//方法1: 
//定义数组类型 
typedef int(MY_ARR)[5];
//建立引用
MY_ARR &  arr2 = arr;

//方法2:
//直接定义引用
int (&arr3)[5] = arr;

//方法3:
typedef int (&MY_ARR1)[5];
MY_ARR1 arr4 = arr

指针的引用(难点):

c语言中如果想改变一个指针的指向而不是它所指向的内容,函数声明可能这样:void fun(int**);,这样很麻烦,传参还要传指针的地址

char * p  = "hello!";
char* &p1 = p;
cout<<p1<<endl;

struct Teacher{
	int mAge;
};
//指针间接修改teacher的年龄
void AllocateAndInitByPointer(Teacher** teacher){
	*teacher = (Teacher*)malloc(sizeof(Teacher));
	(*teacher)->mAge = 200;  
}
//引用修改teacher年龄
void AllocateAndInitByReference(Teacher*& teacher){
	teacher->mAge = 300;
}
void test(){
	//创建Teacher
	Teacher* teacher = NULL;
	//指针间接赋值
	AllocateAndInitByPointer(&teacher);
	cout << "AllocateAndInitByPointer:" << teacher->mAge << endl;
	//引用赋值,将teacher本身传到ChangeAgeByReference函数中
	AllocateAndInitByReference(teacher);
	cout << "AllocateAndInitByReference:" << teacher->mAge << endl;
	free(teacher);
}

常量引用:

格式:const type &ref = val;

注意:

  1. 字面量不能赋值给引用,但是能够赋值给常量引用

    const int &ref3 = 10;
    //编译器会把上面代码变为:int tmp = 10;const int &ref3 = tmp;
    
  2. const修饰的引用,不能修改


内联函数:出现的原因是想解决#define的一些缺陷

格式:在函数前加上inline,即向编译器申请成为内联函数,因此内联仅仅只是给编译器一个建议。[最终决定权不在程序员,由编译器决定,因此不可控]

没有加inline的函数也有可能成为内联函数

什么时候不会成为内联函数:

  1. 有过多的条件判断语句
  2. 存在循环语句
  3. 函数体过大
  4. 对函数进行取址操作

内联函数的好处:

  1. 有宏函数的效率,但规避了其缺点
  2. 类内部定义的成员函数默认加上inline

函数的默认参数:[增加函数的灵活性]

注意:

  1. 函数的默认参数从左向右,如果一个参数设置了默认参数,那么这个参数之后的参数都必须设置默认参数。

  2. 如果函数声明和函数定义分开写,函数声明和函数定义不能同时设置默认参数。

    void func(int a,int b = 10);
    void func(int a,int b){
        
    }
    

函数的占位参数:[应用于运算符重载时区分前++和后++]

void func(int a,int){
    
}
func(10,20);
//占位参数也可以有默认值
void func2(int a,int = 1){
    
}
func2(10);

函数重载:允许函数名相同

实现条件:

  1. 同一个作用域
  2. 参数个数不同
  3. 参数类型不同
  4. 参数顺序不同

函数重载没有 返回值 的相关要求。为什么呢?

当编译器能从上下文中确定唯一的函数的时,如int ret = func(),这个当然是没有问题的。然而,我们在编写程序过程中可以忽略他的返回值。那么这个时候,假如一个函数为void func(int x);另一个为int func(int x);当我们直接调用func(10),这个时候编译器就不确定调用那个函数。所以在c++中禁止使用返回值作为重载的条件。

调用重载函数时的注意事项:

  1. 严格的类型匹配,若类型不匹配则尝试转换,转换成功就调用,失败就报错。

  2. 函数重载和函数默认参数一起使用时需要注意二义性问题出现

    void func(int a,int b= 10){
        
    }
    void func(int a){
        
    }
    a = 10;
    func(a);//出现二义性
    

函数重载的原理:汇编过程取别名

编译器为了实现函数重载,也是默认为我们做了一些幕后的工作,编译器用不同的参数类型来修饰不同的函数名,比如void func();编译器可能会将函数名修饰成_func,当编译器碰到void func(int x),编译器可能将函数名修饰为_func_int,当编译器碰到void func(int x,char c),编译器可能会将函数名修饰为_func_int_char


class

structclass的区别?

class默认访问权限为private,struct默认访问权限为public.

访问权限:private、protected、public

  1. 类内没有访问权限限制,均都可访问
  2. 公有public可以在对象外部访问,私有private和保护protected可以在对象外部访问
  3. 子类的类内可以访问父类的保护权限protected成员

类的静态成员变量static

注意点:

  1. 静态成员变量必须在类中声明,在类外定义

    AAAA::static_a = 100;//类外定义
    
  2. 静态数据成员不属于某个对象,在为对象分配空间中不包括静态成员所占空间[即静态成员变量属于类,不属于对象,所有对象共享]

  3. 静态数据成员可以通过类名或者对象名来访问

  4. 生命周期为整个程序,作用域在类内

类的静态成员函数:

注意点:

  1. 静态成员函数只能访问静态成员变量
  2. 静态成员函数也有访问权限
  3. 普通成员函数可访问静态成员变量、也可以访问非静态成员变量

定义静态const数据成员时,最好在类内部初始化

为什么要出现静态成员变量?

节省空间,属于类,所有对象共享。

静态成员应用的一个实例: 单例模式----一个类只能实例化一个对象

单例模式常常用于只需要一个对象的场景中

思路:
-把无参构造函数和拷贝构造函数私有化
-定义一个类内的静态成员指针,在类外初始化时,new一个对象
-把指针的权限设置为私有,然后提供一个静态成员函数让外面获取这个指针
例子:
用单例模式,模拟公司员工使用打印机场景,打印机可以打印员工要输出的内容,并且可以累积打印机使用次数。
#include <iostream>
#include <string>
using namespace std;
//打印机类
class Printer {
public:
	void printInfo(string info) {
		cout << "当前是第" << printTimes << "次打印...." << endl;
		cout << "打印内容如为:  " << info << endl;
		printTimes++;
	}
	//静态成员函数才能访问静态变量
	static Printer* getObject() {
		return pter;
	}
private:
	//私有化,防止用户进行普通构造和拷贝构造
	Printer() {
		printTimes = 0;
	}
	Printer(const Printer& other) {
	}
private:
	//指向当前类的指针,静态成员变量
	static Printer* pter;
	int printTimes;
};
//在类外进行一次初始化
//为什么这里能进行一个构造[初始化]呢?
//因为有Printer::作用域,编译器把它当作了在类内,而在类内没有访问权限限制
Printer* Printer::pter = new Printer;

int main()
{
	Printer* pA = Printer::getObject();
	pA->printInfo("C++真tm有意思!");
	pA->printInfo("C++真tm难!");
	Printer* pB = Printer::getObject();
	pB->printInfo("我觉得C++很简单!");
	cout << "pA:" << pA << "   pB:" << pB << endl;
	system("pause");
	return 0;
}

输出内容如下:

当前是第0次打印....
打印内容如为:  C++真tm有意思!
当前是第1次打印....
打印内容如为:  C++真tm难!
当前是第2次打印....
打印内容如为:  我觉得C++很简单!
pA:010D13B0   pB:010D13B0

可见pA和pB是同一个对象~


构造函数和析构函数:

实例化对象时,内部做了两件事:分配空间和调用构造函数进行初始化;在对象销毁前,编译器调用析构函数[释放该对象申请的堆空间等操作]。

注意:

  1. 构造函数和析构函数必须是公有的。构造函数私有时,实例不了对象。
  2. 构造函数可以重载,析构函数不行。
  3. 构造函数和析构函数没有返回值。
  4. 编译器会默认提供默认构造函数和默认析构函数

拷贝构造函数:用一个已有的对象去初始化另外一个对象

拷贝构造函数的情况:

  1. 对象以值传递的方式传给函数参数

  2. 用一个对象初始化另一个对象

  3. 函数局部对象以值传递的方式从函数返回

    -VS Debug模式返回的对象和局部对象地址不同

    -VS Release模式返回的对象和局部对象地址相同

class AAAA {
private:
	int a;
public:
	AAAA() {
		a = 999;
		cout << "无参构造函数" << endl;
	}
	AAAA(int data) {
		a = data;
		cout << "有参构造函数" << endl;
	}
	AAAA(const AAAA& s) {
		a = s.a;
		cout << "拷贝构造函数" << endl;
	}
};
//测试函数
void test() {
	AAAA a1;		//调用无参构造函数
	AAAA a2(a1);	 // 调用拷贝构造函数
	AAAA a3 = a1;     //调用拷贝构造函数
	AAAA(10);		//调用有参构造函数,这是一个匿名对象
	AAAA a4 = 10;     //调用有参构造函数,进行了隐式转换,等价于A s = A(10); 如果不想要优化,则使用 explicit 关键字
   //explicit 只能放在构造函数前面,构造函数只有一个参数或其他参数为默认参数时
	AAAA a5(AAAA(200));    //调用有参构造函数
      AAAA a6 = AAAA(200);  //调用有参构造函数
      AAAA a7 = AAAA(); 	//调用无参构造函数
}

注意:

  1. 编译器也提供了默认拷贝构造函数,进行成员变量的简单拷贝。

  2. 拷贝构造函数的形参要用引用。因为编译器会把形参的值传递看成一个拷贝构造函数,造成死循环构造函数,而引用只是创建了一个别名。

  3. 不能调用拷贝构造函数去初始化匿名对象

    AAAA t1;
    //会报错error C2086:“Teacher t1”: 重定义
    AAAA(t1);  //此时等价于 AAAA t1;
    
  4. 匿名对象的生命周期在当前行

  5. 如果匿名对象有东西接,那么就不是匿名对象AAAA a6 = AAAA(200);

编译器提供构造函数规则:

  1. 默认情况下,C++编译器至少为我们写的类增加3个函数
    • 默认构造函数(无参,函数体为空)
    • 默认析构函数(无参,函数体为空)
    • 默认拷贝构造函数,对类中非静态成员属性简单值拷贝
  2. 如果用户定义拷贝构造函数,C++不会再提供任何默认构造函数
  3. 如果用户定义了普通构造(非拷贝),C++不在提供默认无参构造,但是会提供默认拷贝构造

多个对象的构造和析构:

  1. 如果类有成员对象,那么先调用成员对象的构造函数,再调用本身的构造函数,析构函数调用顺序相反;
  2. 成员对象的构造函数调用和定于顺序一样
  3. 必须保证成员对象的构造和析构能被调用

初始化列表:

  1. 如果使用了初始化列表,那么所有的构造函数都要使用初始化列表
  2. 初始化列表只能使用于构造函数

对象的深、浅拷贝:

默认的拷贝构造函数进行了浅拷贝,简单的赋值操作

浅拷贝问题:调用默认拷贝构造函数后,新对象和旧对象指向同一块空间,进行析构函数时被释放了两次,引发错误。

使用深拷贝解决:

class  person{
public:
    person(char * Name,int Age){
        name = (char *)malloc(sizeof(strlen(Name)+1));
        strcpy(name,Name);
        age = Age;
    }
    person(const person & p){
        name = (char *)malloc(sizeof(strlen(P.name)+1));
        strcpy(name,P.name);
        age = P.age;
     }
    ~person(){
    	if(name != NULL){
            free(name);
        }
    }
 private:
    char * name;
    int age;
}

C++对象模型

  1. 空类大小为1
  2. 类的成员函数不占类大小[在公共函数代码区],静态成员不占类大小。普通成员变量占用类的
  3. 类的成员函数和成员变量时分开存储的

在这里插入图片描述

this指针:一种隐含指针,指向被调的成员函数所属对象

注意:

  1. 每个对象都有一个隐藏this指针,但不属于对象(不影响sizeof(对象)的结果),是编译器添加的。
  2. 编译器会把this指针传入成员函数内。
  3. 静态成员函数中不能使用this指针[因为静态成员不属于对象,属于类]
  4. this指针不能改变[说明this类型是常指针 class const * p]

C++编译器对普通成员函数的内部处理:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ajiToNkl-1627189637084)(F:\Typora\Picture\Cpp\2021-07-23_233143.jpg)]

this指针的使用:

  1. 当形参和成员变量同名时,可用this指针来区分
  2. 在类的非静态成员函数中返回对象本身,可使用return *this

常函数与常对象

const修饰成员函数:在成员函数的后面加上const,使函数成为常函数

//const修饰成员函数
class Person{
public:
	Person(){
		this->mAge = 0;
		this->mID = 0;
	}
	//在函数括号后面加上const,修饰成员变量不可修改,除了mutable变量
	void Operate() const{
		//this->mAge = 200; //mAge不可修改
		this->mID = 10; //const Person* const tihs;
	}
	void ShowPerson(){
		cout << "ID:" << mID << " mAge:" << mAge << endl;
	}
private:
	int mAge;
	mutable int mID;//被mutable修饰时可以在常函数中被修改
};

注意:

  1. const修饰的成员函数时,const修饰this指针指向的内存区域,成员函数体内不可以修改本类中的任何普通成员变量。

    this指针类型: Person * const this;
    在常成员函数中的this指针类型被改为: const Person * const this;
    
  2. 当成员变量类型符前用mutable修饰时例外。

const修饰对象,使其成为了常对象。

const Person  MY;

注意:

  1. 常对象,不能调用普通成员函数,只能调用const的成员函数。
  2. 常对象可访问const非const数据成员,但是不能修改,除非成员用mutable修饰。

常函数和常对象有什么用呢?

如果当前成员函数不打算修改成员变量,就最好定义为常函数

友元:使得全局函数、成员函数和类可以访问私有成员,即给一个函数、或者某个类拥有特殊权限

特点:

  1. 有元函数不是类的成员函数,没有this指针
  2. friend关键字只出现在声明处
  3. 其他类、类成员函数、全局函数都可声明为友元
  4. 友元函数可访问对象任意成员属性,包括私有属性
  5. 一定程度上破坏了类的封装性
class Building;
class MyFriend{
public:
	void LookAtBedRoom(Building& building);
	void PlayInBedRoom(Building& building);
};
void MyFriend::LookAtBedRoom(Building& building){
	cout << "我的朋友参观" << building.mBedroom << endl;
}
void MyFriend::PlayInBedRoom(Building& building){
	cout << "我的朋友玩耍在" << building.mBedroom << endl;
}

class Building{
	//全局函数做为友元函数
	friend void CleanBedRoom(Building& building);
#if 0
	//将类的成员函数做为友元函数
	friend void MyFriend::LookAtBedRoom(Building& building);
	friend void MyFriend::PlayInBedRoom(Building& building);
#else	
	//将某个类作为友元类
	friend class MyFriend;
#endif

public:
	Building();
public://公有成员变量
	string mSittingRoom;
private://私有成员变量
	string mBedroom;
};
//构造函数
Building::Building(){
	this->mSittingRoom = "客厅";
	this->mBedroom = "卧室";
}
//友元全局函数
void CleanBedRoom(Building& building){
	cout << "友元全局函数访问" << building.mBedroom << endl;
}

int main(){

	Building building;
	MyFriend myfriend;
	 
	CleanBedRoom(building);
	myfriend.LookAtBedRoom(building);
	myfriend.PlayInBedRoom(building);

	system("pause");
	return EXIT_SUCCESS;
}

注意:

  1. 友元关系不能被继承

    你爸爸的朋友不一定是你的朋友
    
  2. 友元关系是单向的,类A是类B的朋友,但类B不一定是类A的朋友。

    你把他当朋友,但他不一定把你当朋友
    
  3. 友元关系不具有传递性。类B是类A的朋友,类C是类B的朋友,但类C不一定是类A的朋友。

    你朋友的朋友不一定是你的朋友
    

运算符重载:目的是为了简化语法,只是另一种函数调用的方式

注意:

  1. 运算符重载不能改变本来寓意,不能改变基础类型寓意
  2. 不能改变运算符优先级,不能改变运算符的参数个数

语法:

定义重载的运算符就像定义函数,只是该函数的名字是operator@,这里的@代表了被重载的运算符。函数的参数中参数个数取决于两个因素。

运算符是一元(一个参数)的还是二元(两个参数);

运算符被定义为全局函数(对于一元是一个参数,对于二元是两个参数)还是成员函数(对于一元没有参数,对于二元是一个参数-此时该类的对象用作左耳参数)

在这里插入图片描述

除了赋值号=外,基类中被重载的操作符都将被派生类继承。

=, [], () 和 ->操作符只能通过成员函数进行重载

<<>>操作符最好通过友元函数进行重载

不要重载 &&|| 操作符,因为无法实现短路规则

运算符重载过程:

-编译器看到两个对象的运算操作,那么编译器就回去寻找有没有对于的operator重载函数

-编译器检查参数是否对应

-没有问题的话编译器就会执行该函数

+、-、*、/号运算符重载

以一个复数对象的+、-、*、/运算作为例子

//ComplexNUm.h
#pragma once
#include<iostream>
using namespace std;
class ComplexNum
{
public:
	ComplexNum();
	ComplexNum(double r, double i);
	ComplexNum(const ComplexNum& other);
	void Printcomplex();
    //三个成员函数重载运算符
	ComplexNum operator+(ComplexNum& other) {
		ComplexNum tmp(this->real + other.real, this->image + other.image);
		return tmp;
	}
	ComplexNum operator*(ComplexNum& other) {
		ComplexNum tmp(this->real * other.real - this->image * other.image, this->real * other.real + this->image * other.image);
		return tmp;
	}
	ComplexNum operator/(ComplexNum& other) {
		double mul = other.real * other.real + other.image * other.image;
		ComplexNum tmp((this->real * other.real + this->image * other.image) / mul, (this->image * other.real - this->real * other.image) / mul);
		return tmp;
	}
public:
	double real;
	double image;
};
//ComplexNUm.cpp
#include "ComplexNUm.h"
ComplexNum::ComplexNum() {
	real = 0.0;
	image = 0.0;
}
ComplexNum::ComplexNum(double r, double i) {
	real = r;
	image = i;
}
ComplexNum::ComplexNum(const ComplexNum& other) {
	real = other.real;
	image = other.image;
}
void ComplexNum::Printcomplex() {
	if (image >= 0)
		cout << real << "+" << image << "i" << endl;
	else
		cout << real << "" << image << "i" << endl;
}
//全局重载运算符
ComplexNum operator-(ComplexNum& my, ComplexNum& other) {
	ComplexNum tmp(my.real - other.real, my.image - other.image);
	return tmp;
}

左移右移运算符重载

<<注意点:

  1. 形参和实参是一个对象
  2. ostream形参和返回值要引用,因为ostream中把拷贝构造函数私有了
  3. 如果要和endl一起使用,那么必须返回ostream的对象

对与cout<<endl; endl是一个函数,作为参数传入了operator<<(endl)

//重载<<是为了方便打印对象
ostream &operator<<(ostream &out,Person & m){
    cout<<"name"<<m.name<<" age:"<<m.age<<endl;
    return out;
}
//如果Person类的name、age属性为private,可以将其重载运算符函数定义为友元函数
istream & operator>>(istream &in,Person & m){
    cin>>m.name>>m.age;
    return in;
}

赋值=运算符的重载

默认的赋值运算符重载函数进行了简单的赋值操作[可能会出现内存泄露和同一块内存被释放两次],如下

在这里插入图片描述

因此需要重写赋值运算符重载函数

//返回Student是为了防止 s1 = s2 = s3的错误;
Student & operator=(const Studen &stu){
    //因为不能确定this指向的空间能否装下stu内数据,需要先释放,再重新申请
     if(this->pName != NULL){
         delete[] this->pName;
         this->pName = NULL;
     }
    //重新申请空间
    this->pName = new char [strlen(stu.pName) + 1];
    strcpy(this->pNmae,stu.pName);
    
    return *this;
}
//上述:为什么要返回引用呢?没有引用则会调用拷贝构造
对于s1 = s2 = s3,如果返回的是值,则会产生新的对象,则赋值运算符本来的意义就错了

关系运算符重载

bool operator==(Student & stu){
    return this->id == stu.id;
}
// > < 等关系运算符同理

前++和后++运算符重载 [运用到了占位参数]

//前置++
Person & operator++(){
    ++this->id;
    return *this;
}

//后置++
Person operator++(int ){//占位参数,必须是int
    Person tmp (*this);//拷贝构造 ,产生了新对象
    this->id++;//修改this
    return tmp;//返回的是tmp
}
//后置++不能返回引用,因为tmp为局部对象
//优先前置++,因为效率高,不需要产生新的对象

数组下标重载

int &operator[](int index){
    if(this->mSize <= index)
    	this->mSize++;
    return this->arr[index];
}

指针运算符重载

class Smartptr{
 public:
    Smartptr(){
            
        }
    Smartptr(Person * p){
        person = p;
    }
    Person & operator*(){
        return *(this->person);
    }
    Person * operator->(){
        return this->person;
    }
private:
    Person * person;
}
Person * p = new Person();
SmartPtr smp(p);
//假设Person类有printfinfo;方法,则可以直接使用->间接调用
smp->printfinfo();
(*smp).printfinfo();

重载函数调用符号

void operator()(){
    cout<<"我其实不是个函数"<<endl;
}

为什么要重载函数调用符号?

方便代码维护:写成全局重载函数,大家都可以调用

作为算法的策略

方便有权限的调用函数

后面内容太多,太多图片需要上传…太麻烦了,需要PDF文档的链接如下自己拿:
链接:https://pan.baidu.com/s/1Wb8G6gB2jVbZhqtNAcO5yA
提取码:enay

如有帮助求点赞,谢谢!

;