CD23.【C++ Dev】类和对象(14) 取地址重载函数和初始化列表(上)

目录

1.CD22文章的4个问题的分析

2.普通对象取地址重载函数和const 修饰的对象据地址重载函数

代码示例

提问

分析

应用场景

3.构造函数中的初始化列表

成员函数初始化的两种方法

方法1:函数体内赋值

方法2:初始化列表

格式

代码示例

注意事项

引用成员变量必须在初始化列表中初始化

提醒:注意"野引用"问题

const成员变量必须在初始化列表中初始化

自定义类型成员必须在初始化列表中初始化(且该类没有默认构造函数时) 

分析

 提问

4.初始化列表的应用场景

模拟实现栈


承接CD22.【C++ Dev】类和对象(13) 流提取运算符的重载和const成员文章

1.CD22文章的4个问题的分析

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

分析:

1.const对象表明对象成员变量不可以修改,即*this不可以修改,调用非const成员函数时,会导致权限放大,因此不可以

2.非const对象表明对象成员变量可以修改,即*this可以修改,调用const成员函数,权限缩小,没有问题

3.不可以,如果const成员函数调用了一个非const的成员函数,相当于就通过这个const的成员函数可以修改成员变量,权限放大,这是非法的,因此不可以

4.可以,权限缩小,没有问题

2.普通对象取地址重载函数和const 修饰的对象据地址重载函数

之前在CD13.【C++ Dev】类和对象(4) 构造函数和析构函数文章中概括过六个默认成员函数,

代码示例

仍然以日期类对象为例:

由标题可知,重载的是operator&

class Date
{
public:
	Date* operator&()
	{
		return this;
	}
		
	const Date* operator&()const
	{
		return this;
	}
private:
	int _year; 
	int _month;
	int _day; 
};

(一般不用手动定义 ,编译器默认会生成) 

提问

测试以下代码,问&d1和&d2分别调用了哪个operator&函数?

int main()
{
	Date d1;
	const Date d2;
	cout << &d1 << endl;
	cout << &d2 << endl;
}

分析

可以看返回值,Date* operator&()返回值没有用const修饰,与d1的类型对应,因此为&d1调用的operator&

const Date* operator&()const)返回值用const修饰,与d2的类型对应,而且const对象他的this指针也是const修饰的,只能调用const的&重载,因此为&d2调用的operator&,

可以对两个operator&里面添加打印的测试信息:

Date* operator&()
{
	cout << "Date* operator&()" << endl;
	return this;
}
		
const Date* operator&()const
{
	cout << "const Date* operator&()const" << endl;
	return this;
}

运行结果:与分析的内容一致

应用场景

如果不想让其他成员获得对象的实际地址,可以返回空指针,保护地址,如下:

Date* operator&()
{
	return nullptr;
}
		
const Date* operator&()const
{
	return nullptr;
}

运行结果:

3.构造函数中的初始化列表

先明确: 初始化列表是构造函数的一部分

成员函数初始化的两种方法

方法1:函数体内赋值

class Date
{
public:
	Date(int year, int month, int day)
	{
        //函数体内赋值
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};

方法2:初始化列表

格式

以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式

代码示例
Date(int year, int month, int day)
	:_year(year)
    ,_month(month)
    ,_day(day)
{
}

写法上比函数体内赋值要简洁,运行发现也是能成功初始化的:

注意事项

1.初始化只能初始化一次,因此每个成员变量在初始化列表中只能出现一次

2.必须在初始化列表中初始化的:

1.引用成员变量

2.const成员变量

3.自定义类型成员(且该类没有默认构造函数时)

下面着重解释第2点:

引用成员变量必须在初始化列表中初始化

如果不在初始化列表中初始化,例如以下代码:

class Myclass
{
public:
	Myclass(int data)
	{
		_refernece = data;
	}
private:
	 int& _refernece;
};

会报以下错误:  

从引用的特性上来分析:

引用必须在声明时就绑定到一个有效的对象上,并且一旦绑定,就不能再重新绑定到其他对象.这种特性决定了它不能像普通变量那样在构造函数体中赋值,而必须在对象创建时就完成绑定

 改成下面这样就行:

#include <iostream>
using namespace std;
class Myclass
{
public:
	Myclass(int& data)
		: _reference(data) //引用成员变量必须在初始化列表中初始化
	{
	}
	int& GetRef()
	{
		return _reference;
	}
private:
	int& _reference;
};

int main()
{
	static int val = 1;
	Myclass myclass(val);
	cout << myclass.GetRef() << endl;
	return 0;
}

运行结果:

提醒:注意"野引用"问题

为防止出现"野引用"问题,Myclass(int& data)必须使用引用传参,如果为int类型(即Myclass(int data)),局部变量data出了作用域会销毁!

const成员变量必须在初始化列表中初始化

如果不在初始化列表中初始化,例如以下代码:

class Myclass
{
public:
	Myclass()
	{
		_val = 1;
	}
private:
	const int _val;
};

会报以下错误:

用const的语法来说明这一点:const成员变量必须在对象创建时就获得一个初始值,并且不能在构造函数体中被赋值(这样会违反语法)

下面这样写是对的:

class Myclass
{
public:
	Myclass()
		:_val(1)
	{
	}
private:
	const int _val;
};

自定义类型成员必须在初始化列表中初始化(且该类没有默认构造函数时) 

例如下面的错误代码:

#include <iostream>
using namespace std;
class MyClass2
{
public:
    MyClass2(int value) : data(value)
    {
        cout << "MyClass2(int value) : data(value)" << endl;
    }

private:
    int data;
};

class MyClass1
{
public:
    MyClass1(int value)
    { 
        myobj2 = MyClass2(value);
    }

private:
    MyClass2 myobj2;
};

int main() 
{
    MyClass1 myobj1(0xFF); // 创建 MyClass 对象
    return 0;
}

分析

MyClass1包含一个MyClass2类型的成员,且MyClass2是自定义类型,但MyClass2(int value) : data(value)不是默认构造函数,其需要接收参数value

因此myobj2 = MyClass2(value);初始化myobj2是错误的

对构造函数Myclass1做如下修改即可:

    MyClass1(int value)
        :myobj2(value)//自定义类型成员必须在初始化列表中初始化(且该类没有默认构造函数时) 
    { 
    }

运行结果:

结论:所有类型的成员变量都是在初始化列表中初始化的,换句话说:必须在定义的时候初始化,对象的成员定义的位置在初始化列表

 提问

下方代码的myobj的成员val1和val2是在初始化列表中初始化吗?

class MyClass
{
public:
    MyClass()
    { 
    }
    int val1 = 1;
    int val2 = 2;
};

int main() 
{
    MyClass myobj;
    cout << myobj.val1 << endl;
    cout << myobj.val2 << endl;
    return 0;
}

分析:

不要被假象迷惑

class MyClass
{
public:
    MyClass()
    { 
    }
    int val1 = 1;//缺省参数
    int val2 = 2;//缺省参数
};

1和2其实是缺省参数(知识点参见CD13.【C++ Dev】类和对象(4) 构造函数和析构函数文章的成员变量声明时的缺省值),不是为val1和val2赋值!

成员变量声明时的缺省值写法是默认初始化,相当于在初始化列表给的默认初始值

如果不使用默认初始值,可以这样写:

class MyClass
{
public:
    MyClass()
        :val1(10)
        ,val2(20)
    { 
    }
    int val1 = 1;
    int val2 = 2;
};

val1和val2显式初始化,缺省值没有用

4.初始化列表的应用场景

模拟实现栈

class MyStack
{
	MyStack(int capacity = 10)
		:_top(0)
		, _capacity(capacity)
	{
		_ptr = (int*)malloc(sizeof(int) * capacity);
		if (_ptr == nullptr)
		{
			perror("malloc fail");
			exit(EXIT_FAILURE);
		}
	}
	//栈的其他成员函数省略
private:
	int* _ptr;
	int _capacity;
	int _top;
};

不推荐的写法:

MyStack(int capacity = 10)
	:_top(0)
	, _capacity(capacity)
	, _ptr((int*)malloc(sizeof(int)* capacity))
{
}

_ptr可能为空,为了避免此情况,需要再函数体内写if判断

结论:初始化列表不能解决所有的初始化问题,即不是所有的成员变量都能使用初始化列表初始化

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhangcoder

赠人玫瑰手有余香,感谢支持~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值