【C++】掌握C++类的六个默认成员函数:实现高效内存管理与对象操作

news2024/12/23 20:54:08

在这里插入图片描述

C++语法相关知识点可以通过点击以下链接进行学习一起加油!
命名空间缺省参数与函数重载C++相关特性类和对象-上篇

本篇为大家分享下在C++学习中较为具有挑战与难度,同时也是很重要知识。掌握C++类的六个默认成员函数,使得在模拟实现STL中容器过程得心应手。

请添加图片描述
Alt
🌈个人主页:是店小二呀
🌈C语言笔记专栏:C语言笔记
🌈C++笔记专栏: C++笔记
🌈初阶数据结构笔记专栏: 初阶数据结构笔记
🌈Linux笔记专栏: Linux笔记

🌈喜欢的诗句:无人扶我青云志 我自踏雪至山巅 请添加图片描述

文章目录

  • 一、类的六个默认成员函数
  • 二、构造函数
    • 2.1 构造函数概念
    • 2.2 构造函数分类
    • 2.3 构造函数对于内置/自定义类型处理方式
    • 2.4 编译器默认生成构造函数意义及相关问题
    • 2.5 不对内置类型处理
  • 三、析构函数
    • 3.1 析构函数概念
    • 3.2 验证是否会自动调用析构函数
    • 3.3 析构函数处理顺序
    • 3.4 调用类中类的析构函数细节
  • 四、拷贝构造函数
    • 4.1 拷贝构造函数概念
    • 4.2 关于对拷贝构造疑问
    • 4.4 浅拷贝与深拷贝
      • 4.4.1 浅拷贝
      • 4.4.2 深拷贝
    • 4.5 拷贝构造函数典型调用场景
  • 五、运算符重载
    • 5.1 运算符重载函数概念
    • 5.2 运算符重载使用场景
    • 5.3 访问类内成员
  • 六、 赋值运算重载
    • 6.1 判断拷贝构造函数与赋值运算重载
    • 6.2 关于连续赋值
    • 6.3 自己给自己赋值
    • 6.4 赋值运算符重载不能重载为全局函数
    • 6.5 赋值运算符中深拷贝
  • 七、前置++与后置++运算符重载
  • 八、const成员函数
    • 8.1 函数定义添加const修饰
    • 8.2 const修饰全部函数可行?
  • 九、&取地址及const取地址操作符重载

一、类的六个默认成员函数

默认成员函数是指用户没有显式实现,编译器会自动生成的成员函数称为默认成员函数。

对于空类,并不是什么都没有,编译器会自动默认生成以下六个默认成员函数

在这里插入图片描述

二、构造函数

2.1 构造函数概念

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

构造函数目的:默认构造函数是为了解决创建对象,忘记对其对象进行初始化操作,同时解决麻烦地调用Init函数。

造函数虽然名称叫构造,但是目的不是开辟空间创建对象,而是对象初始化

构造函数特性:

  1. 函数名与类名相同
  2. 无返回值
  3. 对象实例化时,编译器自动调用对应的构造函数
  4. 构造函数支持函数重载

2.2 构造函数分类

无参构造函数与全缺省构造函数、忘记显示写构造函数,编译器默认生成构造函数都称为默认构造函数,在使用过程中默认构造函数只能调用其中一种,这里推荐调用全缺省构造函数

class Date
{
public:
	//1.无参构造函数
	Date()
	{
		_year = 2024;
		_day = 6;
	}
	//2.带参构造函数
	Date(int year,int month,int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;//调用无参构造函数
	Date d2(2024, 3, 6);//调用有参构造函数
	d1.Print();
	d2.Print();
	Date d3();//未调用原型函数(是否有意用变量定义?)
	return 0;
}

关于Date d3(void)报错,由于编译器很难区分对象实例化是调用无参构造函数还是函数声明。为了避免混洗这两种情况,要求对象实例化调用无参构造函数,不允许添加括号

对于无参构造与有参构造,无参构造需要函数内部设置好的数值,而有参构造采用外部实参数值。对于这里两种情况可以考虑合并为全缺省的构造函数。虽然编译器支持全缺省构造函数与无参构造函数同时出现,语法上允许这种行为,但是调用构成中会存在歧义,编译器无法区分(有多种初始化方式,在条件允许实现一个全缺省最好用,比较灵活控制参数

2.3 构造函数对于内置/自定义类型处理方式

C/C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类型(int/char/double ),自定义类型就是自己通过关键字定义的类型(struct /class/union)

在这里插入图片描述

对于内置与自定义类型处理:

  • 对内置类型不做处理
  • 对自定义类型的成员,会去调用他们的默认构造(无参构造函数、全缺省构造函数、我们没有写编译器默认生成的构成函数)

2.4 编译器默认生成构造函数意义及相关问题

提出疑问:从编译结果来看,无论是显示构造函数或编译器默认生成构造函数,对于内置类型初始化处理为随机值。虽然完成每个对象初始化,但是这些初始化的数值对于我们来说并没有多大意义,是否可以认为编译器默认生成构造函数没有意义呢?同时是否可以认为既然默认生成构造函数,我们什么事情都不用做了呢?

给出回答:我们从对于内置与自定义类型处理上来看,编译器虽然对于内置类型初始化数值为随机值,但是确保了内置类型完成了初始化操作,避免了缺乏构造函数而导致的编译错误。同时我们需要知道无论是内置类型或者是自定义类型,数据都是需要我们自己处理,只不过是间接和直接而已(套娃:所谓的自定义类型不过是包含内置类型,其中可能还有自定义类型,但是自定义类型最后一定是内置类型,是内置类型都需要人去设置处理)

对于编译器默认生成构造函数还有很有价值的,比如在MyQueue里面定义 stack s1stack s2,这里会调用默认构造,完成对象s1s2的初始化(虽然内部还是需要手动设置,但是调用MyQueue就会很爽)

2.5 不对内置类型处理

不对内置类型做处理是语言设计过程中遗留下来问题,在C++11中对于内置类型是否处理有了争执,当然内置类型不处理也可能有它的原因,对此C++11还是保持对内置类型不处理的态度,但是打了补丁,即是:内置类型成员变量在类中声明事可以给缺省值

请添加图片描述


三、析构函数

3.1 析构函数概念

析构函数与构造函数功能相反,该函数任务并不是完成对象本身销毁(局部对象的销毁时由编译器完成),而是对象在销毁时自动调用析构函数,完成对象中资源的清理工作

这里资源一般指动态开辟的资源,如果没有析构函数进行处理,而是单纯地开辟和销毁对象。没有考虑对象内部申请的动态空间,导致内存泄漏(对象是存储在栈帧上,是由系统进行处理的,也称为自动变量)

从图中也可以观察到动态开辟的资源没有释放掉

请添加图片描述

析构函数特性:

  1. 析构函数名为同类名前加上字符~
  2. 无参数无返回值类型,导致析构函数不支持重载函数
  3. 一个类只能有一个析构函数。若未显式定义,系统会在自动生成默认的析构函数。
  4. 对象生命周期结束时,C++编译系统自动调用析构函数

3.2 验证是否会自动调用析构函数

在这里插入图片描述

析构函数对于内置与自定义类型的处理方式(调用析构函数中this指针存储对象的地址)

请添加图片描述

对于内置与自定义类型处理:

  • 内置类型不处理
  • 自定义类型成员,调用对应的析构函数

3.3 析构函数处理顺序

关于析构函数顺序涉及到函数栈帧,不知道你们是否注意到上面打印顺序跟栈特性是相关的。那么可以得出两点【先定义、先构造】【后定义、先析构】

class Date
{
public:
	Date(int year=1)
	{
		_year = year;
	}
	~Date()
	{
		cout << "~Date()->" <<_year<< endl;
	}
private:
	int _year;
	int _month;
	int _day;

};

Date d5(5);//全局对象
static Date d6(6);//全局对象

void func()
{
	Date d3(3);//局部变量
	static Date d4(4);//局部的静态
}
int main()
{
	Date d1(1);//局部变量
	Date d2(2);//局部变量
	func();
	return 0;
}

请添加图片描述

从中得到结论:

1.局部对象(后定义先析构)

2.局部的静态

3.全局对象(后定义先析构)

析构函数清理细节

class Time
{
public:
    ~Time()
    {
    cout << "~Time()" << endl;
    }
private:
    int _hour;
    int _minute;
    int _second;
};
class Date
{
private:
    // 基本类型(内置类型)
    int _year = 1970;
    int _month = 1;
    int _day = 1;
    // 自定义类型
    Time _t;
};
int main()
{
    Date d;
    return 0;
}

提问:默认析构函数对内置类型不处理,我就想让析构函数对内置类型进行处理,怎么办?

  1. 对于这个问题,我们可以采用显式析构函数,里面的逻辑是自己设计的,可以要求对内置类型进行操作,但是这样子没有价值。
  2. 内置类型不需要进行资源清除,同时将内置类型全部设置为0,同样没有完成清除的任务,对此在程序结束后,系统会自动回收内置类型的空间,不需要我们多此一举

3.4 调用类中类的析构函数细节

d对象的销毁时,要将其内部包含的Time类的_t对象销毁,但是这里不是直接调用Time类的析构函数。因为实际要释放的是Date类对象,对此调用Date类对象对应的析构函数(编译器默认生成的析构函数),目的是在其内部调用Time。(没有直接调用Time类析构函数,通过Date类中析构函数间接调用)

小结:

  1. 内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收
  2. 创建哪个类的对象,则调用该类的析构函数,销毁那个类的对象,则调用该类的析构函数
  3. 关于析构函数是否显示写,主要是看是否存在资源申请,并不是每个类都需要析构。
  4. 析构函数可以显示调用,但是可能会用引发不安全行为,需要小心调用

四、拷贝构造函数

4.1 拷贝构造函数概念

拷贝构造函数指只存在单个形参,该形参是本类类型对象的引用(一般常用const修饰),用已存在的类类型对象创建新对象(该过程编译器自动调用)

class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
    _year = year;
    _month = month;
    _day = day;
}
// Date(const Date& d) // 正确写法
Date(const Date d) // 错误写法:编译报错,会引发无穷递归
{
    _year = d._year;
    _month = d._month;
    _day = d._day;
}
private:
    int _year;
    int _month;
    int _day;
};
int main()
{
    Date d1;
    Date d2(d1);
    return 0;
}

拷贝构造函数特性:

  1. 拷贝构造函数本身属于构造函数一种重载,同类型对象进行初始化
  2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用(编译器可能会强制检查)

4.2 关于对拷贝构造疑问

1.拷贝构造函数为什么只有一个参数?

拷贝构造函数需要拷贝对象参数即可,由于存在this指针,将调用对象地址传进来(编译器会自动处理)

2.为什么传值会引发无穷递归调用呢?是否可以提前写个返回条件进行拦截呢?可以使用指针类型进行接收吗?

通过函数栈帧中学习,传值过程需要开辟空间去拷贝实参数据,这里就需要调用拷贝函数。导致了传值需要调用拷贝构造,调用拷贝构造需要传值的套娃当中。

请添加图片描述

对于返回条件拦截,实际上这里压根没有进去函数体,返回条件都用不上。指针是可以,但是指针不适合这里。使用引用给实参取别名,指向对象共占用一块内存空间,就不需要拷贝数据去调用拷贝函数,减少拷贝次数

3.使用const修饰引用

  1. 使用const修饰的引用意味着我们不会修改传入的对象。保证被拷贝对象不会被修改,可以及时地报错检查是否位置放反。
  2. 如果拷贝构造传的是const修饰的变量,并且拷贝构造函数 参数部分没使用const修饰,就会造成权限放大

4.4 浅拷贝与深拷贝

4.4.1 浅拷贝

若未显示定义,编译器会生成默认的拷贝构造函数。默认的拷贝构造函数对象按照内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝(值拷贝)

class Time
{
public:
    Time()
    {
        _hour = 1;
        _minute = 1;
        _second = 1;
    }
Time(const Time& t)
{
    _hour = t._hour;
    _minute = t._minute;
    _second = t._second;
    cout << "Time::Time(const Time&)" << endl;
}
private:
    int _hour;
    int _minute;
    int _second;
};
class Date
{
private:
    // 基本类型(内置类型)
    int _year = 1970;
    int _month = 1;
    int _day = 1;
    // 自定义类型
    Time _t;
};
int main()
{
    Date d1;
    // 用已经存在的d1拷贝构造d2,此处会调用Date类的拷贝构造函数
    // 但Date类并没有显式定义拷贝构造函数,则编译器会给Date类生成一个默认的拷贝构造函数
    Date d2(d1);
return 0;
}

拷贝构造是构造函数一种特殊形式,如果存在拷贝构造,编译器不会默认生成构造函数。但是可以使用函数名 = default强制编译器生成,但是此场景在大多数在类中类。

拷贝构造对于内置类型和自定义类型处理方式:

  • 内置类型按照字节方式直接拷贝
  • 自定义类型是调用其他拷贝构造函数完成拷贝

4.4.2 深拷贝

既然编译器默认生成的默认拷贝构造,本身可以实现内置类型按照字节方式直接拷贝,那么自己是否还需要实现显式拷贝构造吗?

在不同场景下,有不同场景的处理办法。接下来如果继续使用浅拷贝程序就会崩溃掉,就需要使用深拷贝解决。

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(s1);
    return 0;
}

请添加图片描述

报错原因:从图中来看,两个对象同时指向同一块空间,当对同一块空间重复析构就会报错。

请添加图片描述

由于浅拷贝是按照字节方式直接拷贝,所以只需对于array深拷贝处理,开辟等大空间,更换指向。对此解决两个对象指向同一块空间的问题,在生命周期结束时,会自动调用对应析构函数释放资源(数据拷贝到新空间,将指向转为指向新空间)

Stack(const Stack& st)
{
    _array = (DataType*)malloc(st._capacity * sizeof(DataType));
    if (nullptr == _array)
    {
        perror("malloc申请空间失败");
        return;
    }
    memcpy(_array, st._array, st._size * sizeof(DataType));//要记得把原来的数据拷贝过去
    _size = st._size;
    _capacity = st._capacity;
}

关于是否显示写拷贝构造函数

  • 类中没有涉及资源申请,拷贝构造是否写都是可以
  • 类中一旦涉及资源申请,拷贝构造一定要写,否则就是浅拷贝

4.5 拷贝构造函数典型调用场景

  1. 使用已存在对象创建新对象
  2. 函数参数类型为类类型对象
  3. 函数返回值类型为类类型对象
class Date
{
public:
    Date(int year, int minute, int day)
    {
        cout << "Date(int,int,int):" << this << endl;
    }
    Date(const Date& d)
    {
        cout << "Date(const Date& d):" << this << endl;
    }
    ~Date()
    {
        cout << "~Date():" << this << endl;
}
private:
    int _year;
    int _month;
    int _day;
};
Date Test(Date d)
{
    Date temp(d);
    return temp;
}
int main()
{
    Date d1(2022,1,13);
    Test(d1);
    return 0;
}

请添加图片描述

为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用尽量使用引用。


五、运算符重载

关于给函数取名这件事。如果需要实现一个函数,就需要为函数起名字,这件事情很依赖写代码人的素养。

5.1 运算符重载函数概念

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

函数名字为:关键字operator后面接需要重载的运算符符号。

函数原型:返回值类型 operator操作符(参数列表)

该函数注意点:

  • 不能通过连接其他符号来创建新的操作符:比如operator@(需要是C/C++语法中存在)
  • 重载操作符必须有一个类类型参数(不能去重载运算符改变内置类型的行为)
  • 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义
  • 作为类成员函数重载时,其形参看起来比操作数数目少一个,因为成员函数的第一个参数为隐藏的this指针
  • .* :: sizeof ?: .注意以上运算符不能重载。这个经常在笔试选择题中出现(注意第一个不是*, *是可以重载的)
  • 并不是运算符都是需要重载的,需要看是否有存在的意义,参数部分需要对应顺序

5.2 运算符重载使用场景

祖师爷设置运算符重载的长期目标:自定义类型也可也使用运算符,同时这里编译器可以调用这两个对象,是原因存在this指针。但是可以更简单就是下面的写法(效果是等价的,同时注意优先级的问题)

class Date
{
public:
	Date(int year = 2024, int month = 3, int day = 9)
	{
		_year = year;
		_month = month;
		_day = day;
	}
//private:
	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;
};

请添加图片描述

在 C++ 中,d1 < d2operator<(d1, d2) 之间,它们涉及不同的概念和语法。d1 < d2是调用d1.operator(d2)operator<(d1, d2)调用非成员函数。

5.3 访问类内成员

以上可以访问到类内成员,是由于注释了private访问限定符。如果类外面不能随便访问成员,有什么办法可以解决呢?

两种解决办法:

  • 在类中提供Get函数 int Getname(){return _name};
  • 在类里面定义该函数,就可以使用类内成员

这里采用第二种方式:

在类里面定义该函数,这样子该函数有隐含this指针,只需要传一个参数就行。就可以这样子写d1.operator<(d2)等价于operator(const this*d1,d2),同时在类内部定义就可以使用private去保护成员变量,完成了封装。

请添加图片描述

关于这两种写法都是可以的,编译器知道会调用这个函数。并且第一种写法不会转为第二种写法再调用,而是直接调用对应的函数,中间步骤省略

六、 赋值运算重载

赋值运算符重载格式:

  1. 参数类型:const typename &传递引用可以提高传参效率
  2. 返回值类型:typename&返回引用可以减少拷贝,提高返回的效率,有返回值目的是为了支持连续赋值
  3. 检查是否存在自己给自己赋值
  4. **返回*this:**要复合连续赋值的含义

6.1 判断拷贝构造函数与赋值运算重载

class Date
{
    public:
    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;
    }
    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(2024, 1, 23);
    Date d2 = (2024, 2, 28);
    d2 = d1;
    Date d3 = d1;
    return 0;
}

无论是赋值运算符重载还是拷贝构造函数都可以完成对内置类型处理,那么如何识别属于赋值还是拷贝呢?那么d2 = d1是赋值还是拷贝,Date d3 = d1;赋值还是拷贝?

判断方法:

  • 拷贝构造:同类型定义对象需要初始化要创建对象
  • 赋值运算符重载:已经存在的对象,一个拷贝赋值给另一个

6.2 关于连续赋值

int main()
{
	Date d1(2024, 1, 23);
	Date d2(2024, 2, 28);
	Date d3;

	int i; int j = 10;
	i = j = 20;
	
	d3 = d2 = d1;
	return 0;
}
  • 内置类型:连续赋值是从右到左,这里先处理j=20表达式,返回临时变量存储返回值,再跟i赋值。
  • 自定义类型:连续赋值,先处理d2=d1这里就会调用赋值运算符重载。连续赋值返回值需要改为Date,并且返回对象*this
	Date operator=(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
		return *this;
	}

这种写法不推荐由于返回值传值返回先存储到寄存器中,传值不会返回对象本身,而是会返回他的拷贝。如果是同类,就需要调用拷贝构造。无论如何会导致浪费,不如使用引用做返回值,减少拷贝次数。这也是指针跟引用差异。


	Date& operator=(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
		return *this;
	}

6.3 自己给自己赋值

可能会存在一种情况,自己给自己赋值(一般人都不这么干,主要是白干),对此一般会加给判断语句

Date& operator=(const Date& d)
{
	if(this!=&d)
    {
		_year=d._year;
        _month=d._month;
        _day=d._day;
    }
    return *this;
}

6.4 赋值运算符重载不能重载为全局函数

赋值运算符重载跟拷贝构造类似,如果不显式实现,编译器会生成一个默认的赋值运算符重载,此时用户再类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突,故而赋值运算符只能是类的成员函数(其他运算符函数可以重载为全局函数)

请添加图片描述

  • 特性:用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝

请添加图片描述

默认生成赋值运算符重载对于内置类型与自定义类型处理方式

  • 内置类型成员变量直接赋值的
  • 自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值

6.5 赋值运算符中深拷贝

既然编译器生成的默认赋值运算符重载已经可以完成字节序的值拷贝,还需要自己实现吗?

接下来如果继续使用浅拷贝程序就会崩溃掉,就需要使用深拷贝解决。因为这里两个对象调用同一个函数,对同一块空间进行free,重复free会报错

// 这里会发现下面的程序会崩溃掉?这里就需要我们以后讲的深拷贝去解决。
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;
} 

请添加图片描述

小结:

  • 如果类中未涉及到资源管理,赋值运算符是否实现都是可以的
  • 如果类中涉及到资源管理,赋值运算符则必须实现

七、前置++与后置++运算符重载

前置++和后置++ 都这样子写,编译器是无法区分的。对此需要进行特殊处理(解决语法逻辑不自洽)。虽然++operator可以解决问题,但是C++给出其他解决方式

//++d1
Date operator++();
//d1++
Date operator++();

C++规定后置++重载时,多增加一个int类型的参数(用来完成重载,没有实际意义),但是调用函数时该参数不用传参,编译器会自动传递。

//前置++
Date& operator++()
{
    _day += 1;
    return *this;
}
//后置++
Date operator++(int)
{
    Date temp(*this);
    _day += 1;
    return temp;
}

八、const成员函数

const修饰的"成员函数"称之为const成员函数,const修饰类成员函数,实际修饰改成员隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改

请添加图片描述

问题:

  1. cosnt对象可以调用非const成员函数吗?
  2. 非const对象可以调用const成员函数吗?
  3. const成员函数内可以调用其他的非const成员函数吗?
  4. 非const成员函数内可以调用其他的const成员函数吗?

接下来,带着我们的问题进行下去

int main()
{
	Date d1(2024, 1, 31);
	d1.Print();

	const Date d2(2024, 3, 31);
	d2.Print();

	return 0;
}

请添加图片描述

这里原因很显然是d2的权限被放大了(权限可以缩小,但是不能放大)

请添加图片描述

既然const修饰类成员函数,实际修饰改成员隐含的this指针。那么只要const修饰this指针就可以解决权限问题。但是对于我们需要修饰this指向的内容,但是规定在this形参和实参位置不允许显式,也是不能修改的,那么怎么修改呢?

8.1 函数定义添加const修饰

祖师爷给出解决措施,在函数定义地方加个constvoid fname() const,至于为什么不是 const void fname()还是那一句,我们是语法的学习者。这样处理完了之后,对于const对象和非const对象都可以调用该函数

请添加图片描述

8.2 const修饰全部函数可行?

  • 如果对成员变量只进行读访问的函数建议加const,这样const对象和非const对象都可以使用;
  • 如果对成员变量进行读写访问的函数,不能加上const,否则不能修改成员变量(需要修改读写权限)

补充:全局实现的运算符重载函数不存在this指针,而const修饰成员函数是修饰this指针的。那么流插入与流提取不是在类中实现,没有隐含的this指针,不能使用const修饰。

对于上面的几个问题的答案:

  • cosnt对象可以调用非const成员函数吗?(不可以,权限放大)
  • 非const对象可以调用const成员函数吗?(可以,权限缩小)
  • const成员函数内可以调用其他的非const成员函数吗?(不可以,权限放大)
  • 非const成员函数内可以调用其他的const成员函数吗?(可以,权限缩小)

九、&取地址及const取地址操作符重载

这两个默认成员函数一般不用重新定义,编译器会默认生成的

class Date
{
public:
	Date* operator&()
	{
		return this;
	}
	const Date* operator&() const
	{
		return this;
	}
private:
		int _year; // 年
		int _month; // 月
		int _day; // 日
};
int main()
{
	int a = 0;
	const int b = 10;
	cout << &a << endl;
	cout << &b << endl;
	return 0;
}

那么对于返回地址具有选择性,可以指定返回空或者返回一个像模像样的地址(传了假地址都很难发现,写bug小妙招~)

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

以上就是本篇文章的所有内容,在此感谢大家的观看!这里是店小二呀C++笔记,希望对你在学习C++语言旅途中有所帮助!
请添加图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

企业必备:2024年顶尖10款人事管理系统

本篇文章介绍了以下几个工具&#xff1a;Moka、卓望ShineHR、华夏HR云、中智人事、i人事、北森iTalentX、红海云、Zenefits、ICE Hrm、ADempiere。 在选择合适的人事管理系统时&#xff0c;很多企业面临如何找到既可靠又能满足特定需求的平台的难题。每个系统都有其独特之处&am…

前端工程化-03.环境准备

一.前端工程化 既然要实现前端工程化&#xff0c;那就要使用一些现成的工具来帮助我们实现&#xff0c;这个工具就是vue官方提供的脚手架 首先就要下载安装vue脚手架 二.什么是vue脚手架 三.安装NodeJS 1.先安装NodeJS才能安装vue脚手架。 Node.js — 在任何地方运行 Jav…

window.onload、$(document).ready()、Vue.created() 页面加载完成后执行方法

1、JavaScript 的 window.onload 方法 window.onload 方法是在页面所有元素&#xff08;包括图片、样式、链接等&#xff09;加载完成后触发的&#xff0c;在这个事件之前&#xff0c;页面上的所有资源都必须加载完成。因此&#xff0c;如果页面中包含大量的图片或其他资源&am…

【科目结转】财务科目结转

*&---------------------------------------------------------------------* *&程序名称 &#xff1a;ZFI134 *&程序描述 : 9003差异科目结转 &#xff08;批量操作 F.02 / 查询 FB03) *&申请单位 …

qt quick实现的水波纹特效:横向波纹、纵向波纹效果

qml实现的水波纹特效 1.横向波纹效果2.另一种效果&#xff08;纵向波纹&#xff09; 一直以来使用c qt如果要实现一些高级特效比如水波纹效果都难度比较大&#xff0c;但是使用qt quick难度就会小很多。这里借鉴一些网友的思路简单实现一下水波纹效果。主要思路就是波浪的形成是…

Aigtek高压放大器在无线电能传输的应用范围

无线电能传输是一种重要的技术&#xff0c;广泛应用于电力、通信和工业领域。高压放大器作为无线电能传输系统中的关键组件之一&#xff0c;扮演着放大信号、提高传输效率的重要角色。 无线电能传输是一种将电能通过无线电波或磁场从发送器传输到接收器的技术。它可以实现远距离…

web自动化测试Day4

目标 下拉选择框&#xff1b;弹出框&#xff1b;滚动条操作&#xff1b;frame表单切换&#xff1b;多窗口切换&#xff1b;窗口截图、验证码处理 定位下拉框 select选择框 下标从0开始 #通过下标形式访问 #通过value值形式访问 注意事项 实例化select时候&#xff0c;需要…

如何选择正规的调度控制台厂家?

在现代社会&#xff0c;随着各行各业对高效、安全管理的需求日益增长&#xff0c;调度控制台作为监控与指挥的核心设备&#xff0c;其重要性不言而喻。然而&#xff0c;市场上调度控制台厂家众多&#xff0c;产品质量与服务水平参差不齐&#xff0c;如何从中挑选出正规、可靠的…

Java设计模式之中介者模式:解耦对象交互的秘诀!

中介者模式&#xff08;Mediator Pattern&#xff09;是一种行为设计模式&#xff0c;用于降低多个对象或类之间的通信复杂性。通过引入一个中介者对象&#xff0c;这些对象无需显式地相互引用&#xff0c;而是通过中介者对象进行交互&#xff0c;从而减少对象之间的直接交互&a…

minikube 实践练习3 - 扩容/缩容

多实例运行 参考文档&#xff1a;https://kubernetes.io/docs/tutorials/kubernetes-basics/scale/scale-intro/ 1. 创建类型为 LoadBalancer的service [weihengweihengminikube root]$ kubectl get service NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) …

构建openGauss-Lite-5.0.2-openEuler基础镜像

构建opengauss数据库基础镜像&#xff0c;需要克服几个困难&#xff1a; 1、使用root和opengauss普通用户来回切换&#xff1a;如果使用Dockerfile中RUN指令&#xff0c;执行su - user切换不生效。 2、需要设置sysctl内核参数&#xff1b;但是&#xff0c;即便使用了--privil…

SPDK源码剖析一hello_world程序

SPDK初识之hello_world程序分析 首先是hello_world程序整体框架分析 int main(int argc, char **argv) {rc parse_args(argc, argv, &opts);if (spdk_env_init(&opts) < 0) { // spdk环境初始化&#xff0c;最终调用dpdk环境初始化}// 扫描设备&#xff0c;将驱…

KEEPALIVED高可用集群最详解

目录 一、高可用集群 1.1 集群的类型 1.2 实现高可用 1.3 VRRP&#xff1a;Virtual Router Redundancy Protocol 1.3.1 VRRP相关术语 1.5.2 VRRP 相关技术 二、部署KEEPALIVED 2.1 keepalived 简介 2.2 Keepalived 架构 2.3 Keepalived 环境准备 2.3.1 实验环境 2…

酒店民宿小程序开发,提升用户体验,增加收益

近年来&#xff0c;我国旅游业蓬勃发展&#xff0c;推动了酒店民宿的快速发展。目前&#xff0c;酒店行业也结合数字化模式打造出了线上酒店民宿预订小程序。 随着网络时代的到来&#xff0c;大红都开始热衷于在手机上完成各种消费&#xff0c;酒店民宿小程序的开发也顺应了时…

1.1 数据库的定义与作用

欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;欢迎订阅相关专栏&#xff1a; 工&#x1f497;重&#x1f497;hao&#x1f497;&#xff1a;野老杂谈 ⭐️ 全网最全IT互联网公司面试宝典&#xff1a;收集整理全网各大IT互联网公司技术、项目、HR面试真题.…

Linux服务器:Samba配置

Window配置 安装协议 首先确认自己的电脑有误安装SMB协议&#xff0c;安装方法&#xff1a;控制面板→程序功能 然后选择左侧的安装或关闭Window功能 安装就好了。 修改工作组 然后就是记住、或者修改Window账户所在组了 右键我的电脑属性&#xff0c;去找电脑的工作组&am…

【信创】双系统下删除Windows只保留麒麟系统

原文链接&#xff1a;【信创】双系统下删除Windows只保留麒麟系统 Hello&#xff0c;大家好啊&#xff01;今天给大家带来一篇关于在双系统环境下删除Windows操作系统并只保留麒麟系统的文章。在双系统环境中&#xff0c;如果你决定完全转向麒麟系统&#xff0c;删除Windows系统…

BayesPrism 增速 400 倍?!免疫反卷积算法 InstaPrism

生信碱移 BayesPrism 加速 计算细胞类型去卷积是一种重要的分析技术&#xff0c;用于模拟整体基因表达数据的成分异质性。之前小编给大家介绍过一篇子刊文章&#xff0c;其综合比较了多种细胞去卷积算法&#xff08;图1&#xff09;。研究的结果显示&#xff0c;BayesPrism 作…

文献解读-肿瘤测序-第二十八期|《基于Palbociclib的高通量联合药物筛选确定了HPV阴性头颈部鳞状细胞癌的协同治疗选择》

关键词&#xff1a;肿瘤测序&#xff1b;基因测序&#xff1b;变异检测&#xff1b; 文献简介 标题&#xff08;英文&#xff09;&#xff1a;Palbociclib-based high-throughput combination drug screening identifies synergistic therapeutic options in HPV-negative hea…

HTML中的<fieldset>标签元素框的使用

HTML 提供的 <fieldset> 标签用于在表单中分组相关元素。 <fieldset> 标签会在相关元素周围绘制一个框。 <legend> 标签为 fieldset 元素定义标题。 语法如下&#xff1a; <fieldset><legend>标题</legend><!-- 元素内容... -->…