目录
2.普通对象取地址重载函数和const 修饰的对象据地址重载函数
自定义类型成员必须在初始化列表中初始化(且该类没有默认构造函数时)
承接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判断
结论:初始化列表不能解决所有的初始化问题,即不是所有的成员变量都能使用初始化列表初始化