本文主要内容:
- 初始化列表
- 静态成员,成员函数
- 友元
1.初始化列表
今天我们来进行类和对象的收尾,相比于前面的构造函数和析构函数来说,这篇文章的内容相对是比较好理解的。我们这篇博客就来提一提静态成员,成员函数还有前面稍微提到的初始化列表。
所谓的初始化列表就是成员变量被定义的地方!你可能会有疑问,在构造一个对象的时候,整个对象都出来了,为什么还要特意给成员变量找一个定义的地方。有这个疑问是很正常的。来看看下面这段代码:
class Demo
{
private:
int _a;
const int _b;//const常量必须在定义的时候初始化
int& _rc;//引用也必须在定义的时候初始化
};
正是因为有的类的成员变量必须在定义的时候就要初始化,所以才必须找一个定义的地方。而成员定义的地方正好就是初始化列表,所以这个Demo类的构造函数可以这么写:
class Demo
{public:
Demo(int a=4)
: _a(4)
, _b(3)
, _rc(a)
{}
private:
int _a;
const int _b;
int& _rc;
};
也就是说,每一次调用构造函数,所有的成员变量都会走一次初始化列表。不仅如此,初始化列表还可以应用在下面这种情况:
//初始化列表解决无法生成默认构造函数的问题
class A
{ public:
A(int x)
{
_x = x;
}
private:
int _x;
};
class Demo
{public:
Demo(int a=4)
: _a(4)
, _b(3)
, _rc(a)
,_aa(5)
{}
private:
int _a;
const int _b;
int& _rc;
A _aa;
};
本来正常情况下,我们是没有办法提供Demo类的默认构造函数,因为类A没有默认构造函数,但是利用初始化列表我们就可以很好解决这个问题。
另外,初始化列表的初始化顺序只和类成员的声明顺序有关,而和初始化顺序无关
class B
{
public:
B()
:_b1(4)
,_b2(_b1)
{}
private:
int _b2;
int _b1;
};
int main()
{ B b;
return 0;
}
打开监视窗口,观察到如下的信息:
如果初始化列表里面的顺序来看,最后_b1,_b2的值应该都被初始化成4,但是很明显这里的_b2却是随机值。因为初始化列表的初始化是按照成员声明的顺序来的!所以说这里先用_b1初始化_b2,因此_b2是随机值!在使用初始化列表的时候要特别注意这一点。
2.静态成员,静态成员函数
前面我们类所拥有的成员都是依赖于实例化对象的。那么在C++里面还提供了一种特殊的机制---->静态成员,静态成员函数,那么这两个东西是不需要依赖对象---->可以理解成,静态成员和静态成员函数为所有类对象所共有的。
我们可以来看一看具体的静态成员怎么定义:
class C
{
private:
int _a;
static char _c;
};
那么静态成员的初始化比较特殊,由于静态成员的特殊性,因此静态成员并不会在初始化列表定义。我们必须显式自己初始化静态成员。
//这里为了演示,把静态成员放出来
class C
{ private:
int _a;
public:
static char _c;
};
//静态成员必须类外初始化,因为静态成员不会走初始化列表
C::_c=1;
讲完了静态成员,我们在来看看静态成员函数,语法声明也是在成员函数前面加一个static关键字。
class Demo
{
public:
//这就是一个静态的成员函数
static void show()
{
cout<<"hello world!"<<endl;
}
};
访问静态成员函数的方法有两种:
1.使用实例化对象访问
2.使用类名::函数名进行访问
思考两个问题:
1.普通成员函数能否调用静态成员函数?
2.静态成员函数能否调用普通成员函数?
答案是:普通成员函数可以调用静态成员函数,而静态成员函数不能普通成员函数。因为,静态成员函数没有this指针!
3.友元
以日期类为例,有的时候我们希望通过键盘输入来构造日期类对象,那么我们就需要对日期类进行流提取运算符的重载,假如我们把流提取运算符重载成成员函数
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
}
Date& operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
//标准库流对象
istream& operator>>(istream& in)
{
in>>_year>>_month>>_day;
return in;
}
private:
int _year;
int _month;
int _day;
};
//具体调用
int main()
{
Date d1;
//cin>>d1是错误的
d1>>cin;//这个是正确调用!
return 0;
}
出现这种情况的原因就是:两个操作数的函数,编译器会自动把左操作数作为第一个参数。而成员函数的第一个参数永远是this指针,我们没办法更改!所以为了我们能够写法更加自然和易于理解,最好能重载全局的流提取运算符。但是又有一个问题。私有的成员变量外部无法直接访问。因此C++提供了一种特殊的机制来解决---->友元
我们先来看一看怎么使用友元来使得访问私有成员:
class Date
{
public:
//友元声明---->friend关键字
friend istream& operator>>(Date& d);
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
}
Date& operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
private:
int _year;
int _month;
int _day;
};
//重载全局的流提取
istream& operator(Date& d)
{
in>>d._year>>d._month>>d._day;
return in;
}
经过上面的操作以后,我们就可以使用cin来提取日期类的信息了!
友元有以下几个注意的地方:
- 友元关系是单向的,A是B的友元,但是B的友元不是A!
- 友元没有传递性!
- 友元会破坏封装,尽量避免使用友元
总结
- 初始化列表是成员定义的地方
- 静态成员,函数是属于类的,静态成员函数没有this指针
- 友元的简单使用
本文的主要内容就到这里,如有不足支持还望指出,希望大家一起共同进步!