前引:在C++语言长达三十余年的演进历程中,每一次标准更新都在试图平衡性能与抽象、控制与安全之间的微妙关系。从C++11引入的"现代C++"范式开始,开发者得以在保留底层控制能力的同时,借助语言特性大幅提升代码的可维护性与安全性。本文聚焦于类与对象,初始化列表与构造函数究竟有何关系!const又是如何解决隐藏的被修改问题的~
目录
自动识别—探讨
难道编译器可以自动识别内置类型真的是“自动”吗?
这是因为C中需要一律先指明类型,再去打印出来;而C++中是在库里面帮我把准备工作都做完了,我们就可以直接使用了,例如:
因此我们可以判断,如果需要编译器自动识别自定义类型,那么需要函数重载完成
我们可以看到流插入、流提取的类型是:
流插入识别自定义类型
下面我们通过成员函数重载的方式来实现流插入,但是我们看到如果流插入作参数是无法通过编译
St2 << cout;
void operator<<(ostream& out);
void Timedate::operator<<(ostream& out)
{
out << _year << "年" << _month << "月" << _day << "日" << endl;
}
双操作数的运算符规定:
第一个参数是左操作数,第二个参数是右操作数。而运算符重载函数的第一个形参被 this 指针隐式占用了,ostream类对象的引用在第二个形参位置,所以在调用运算符重载时,只能是this指针指向的对象在运算符的左边,cout对象在运算符的右边,但这又不符合留提取的含义
这样我们跟库里面的实现是不同的,也感觉到不适。解决方法是在成员函数外面实现,使用友元
void operator<<(ostream& out, Timedate& St2)
{
out << St2._year << "年" << St2._month << "月" << St2._day << "日" << endl;
}
友元:对外面定义的函数声明前面加上“friend”即可,这样可以解决无法访问私有成员的问题,友 元设置的位置只要在类里面就可以,无论是私有还是公共
下面我们看实际效果:
流提取识别自定义类型
有了上面的经验,我们这里使用友元快速实现流提取
函数声明:
void operator>>(istream& in, Timedate& St2);
函数实现:
void operator>>(istream& in, Timedate& St2)
{
in >> St2._year >> St2._month >> St2._day;
}
效果展示:
总结
我们用成员函数去实现流插入、提取自定义类型是不符合使用习惯的,因为 this 指针占用了第一个参数位置;因此需要考虑参数位置问题,这就得在类外实现,同时友元解决访问类私有成员问题
连续流插入
可以看到我们自定义识别是无法连续的,这是因为函数实现时我们指定了类型是 void
对于一般的赋值顺序是:从右往左
对于流提取、插入顺序是:从左往右(如:cout<<St2 然后 cout<<St1)
因此我们需要改变函数的返回值问题!
函数声明:
friend ostream& operator<<(ostream& out, Timedate& St2);
函数实现:
ostream& operator<<(ostream& out, Timedate& St2)
{
out << St2._year << "年" << St2._month << "月" << St2._day << "日" << endl;
return out;
}
效果展示:
连续流提取
原理和上面的一样,提取顺序也是从左往右,我们直接实现!
函数声明:
istream& operator>>(istream& in, Timedate& St2);
函数实现:
istream& operator>>(istream& in, Timedate& St2)
{
in >> St2._year >> St2._month >> St2._day;
return in;
}
const成员
问题
将const修饰的“成员函数”称之为const成员函数
const修饰类成员函数,实际修饰该成员函数 隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。这样一定安全了吗?
例如:
下面这种虽然不能对St2进行修改,但是是可以对 *this 指向进行修改的
bool operator==(const Timedate St2);
解决方法
对于不需要更改的隐含参数(*this),我们可以在函数声明和定义后面加上 const
例如:
初始化列表
引入
在实例化对象时,不管是编译器还是我们自己,会使用构造函数给成员变量一个合适的初始值。
但是经过构造函数之后,我们还不能将其称为成员变量的初始化:
构造函数中的语句只能称为赋初值,而不能称作初始化
因为初始化只能初始化一次,而构造函数体内可以多次赋值
初始化列表
什么是初始化列表:
初始化列表是构造函数的一部分,用于在对象创建时直接初始化成员变量
初始化列表与构造函数关系:
构造函数初始化有两种方式:函数体赋值、初始化列表
所以二者结合才是完整的初始化哦!
如何写初始化列表:
(1)以一个冒号开始,接着是一个以逗号分隔的数据成员列表
(2)每个 "成员变量" 后面跟一个放在括号中的初始值或表达式
class Mytime
{
public:
Mytime(int year, int month, int day)
:_year(year)
,_month(month)
,_day(day)
{
//构造函数体功能部分
}
private:
int _year;
int _month;
int _day;
};
必须使用初始化列表的情景
(1)const成员初始化
const 成员变量必须在声明时初始化,且之后不能修改
class Mytime
{
public:
Mytime(int year, int month, int day)
:_year(year)
,_month(month)
,_day(day)
{
//构造函数体功能部分
}
private:
const int _year;
int _month;
int _day;
};
否则就会报错:
(2)引用成员
引用必须在声明时绑定到某个对象
class Mytime
{
public:
Mytime(int year, int month, int day)
:_year(year)
,_month(month)
,_day(day)
{
//构造函数体功能部分
}
private:
//const成员
const int _year;
//引用
int& _month;
int _day;
};
但是我们给初始化时传来的参数用引用类型 ,原因如下:
(1)引用是变量的别名,加上引用就直接是外部的值给成员里面的引用进行赋值
(引用可以赋值引用)
原因:引用的赋值实际是对原对象的赋值,并不修改原来的绑定关系 (多个引用可指向同一象)
(2)如果不用引用,那么初始化函数结束,变量也就销毁了,引用无对象了
正确写法应该是:
(3)无默认构造函数的类型成员
首先我们知道尽管自己不写构造函数,编译器也会调用自己的构造函数,但是它区分内置、自定义
在初始化时:默认构造不会对自定义类型进行处理
class Mytime
{
public:
Mytime(int year, int month)
:_year(year)
, _month(month)
, newnode(((Mytime*)malloc(sizeof(Mytime) * 5)))
{
//构造函数体功能部分
//对空间进行判断
if (newnode == nullptr)
{
perror("空间失败\n");
return;
}
}
private:
//const成员
const int _year;
//引用
int& _month;
//自定义成员
Mytime* newnode;
};
初始化顺序
初始化列表中成员的初始化顺序与类中成员声明的顺序是一致的,而非初始化列表顺序
例如:
效率比较
对于自定义类型:
初始化列表:直接调用成员的构造函数
构造函数体内赋值:先调用默认构造函数,再进行赋值
对于内置类型:
无明显差异
【雾非雾】期待与你的下次相遇!