一. 初始化列表
位于构造函数的参数列表和函数体之间, 格式如下 :
(冒号): 成员(初始化值/表达式) , 成员2(初始化值/表达式) …
想必大家对初始化一定不陌生吧, 就是定义一个变量的时候给一个初始值
1. 先看看怎么写
下面直接给出一个具体的日期类为例
里面的细节下面会说, 不必惊慌
一定要分清楚变量在哪声明, 在哪定义, 在哪是初始化, 在哪是赋值
class Date {
public:
Date(int y = 2000, int m = 1, int d = 1)
//初始化列表
: _year(y)
, _month(m)
, _day(d)
, _a(y)
, _b(y)
, _time(y)
{
//这里是赋值
_year = y;
_month = m;
_day = d;
//这里是给_time重新赋值, 不是初始化
_time = 10;
}
private:
//这里是成员变量声明的地方, 定义的地方在初始化列表
int _year;
int _month;
int _day;
int& _a; // 引用在定义时必须初始化
const int _b; // const变量在定义时必须初始化
Time _time;
};
2. 必须在初始化列表初始化的3种变量
1. 引用成员变量
2. const成员变量
3. 没有默认构造函数的自定义类型变量
那为什么必须要在初始化列表初始化呢?
不妨回忆一下前两种变量
引用变量在定义时必须初始化, 而且之后不能改变指向
const变量在定义时必须初始化, 之后不能修改变量的值
这么一说就很清晰了吧
对于自定义成员, 最好在初始化列表中初始化, 即使不在初始化列表中显式初始化, 编译器也会在初始化列表中自动调用自定义成员的默认构造完成初始化
其他成员变量可以不在初始化列表中初始化, 但是我们一般还是写在初始化列表中
这是一个很好的习惯
3. 成员变量在初始化列表中的初始化顺序 : 和声明顺序一致, 与其在列表中的顺序无关
class A {
public:
A(int a)
:_a1(a)
,_a2(_a1)
{}
void print() {
cout << _a1 << " " << _a2 << endl;
}
private:
int _a2;
int _a1;
};
int main() {
A aa(1);
aa.print();
}
上述代码的输入结果是什么呢?
我们可以看到是1和随机值, 这就说明初始化的顺序问题
所以我们为了避免不必要的麻烦, 一般初始化列表的顺序要和声明顺序保持一致
二. static成员
在成员变量或者成员函数前面加上static关键字修饰, 就是static成员
首先说静态成员变量
静态成员变量是所有对象共享的成员变量(相当于类域中的全局变量)
static成员变量不在对象模型中存放, 一般放在数据段,
不能在初始化列表中初始化, 必须在类外初始化
然后再看看静态成员函数
静态成员函数不具有this指针, 因为他是共享的嘛, 也不属于任何的对象
因为他没有this指针, 所以不能访问任何的非静态成员
也就是说在static函数中访问不到普通的成员变量和成员函数
所以静态成员函数不能调用普通成员函数 (因为没有this指针),
反之普通成员函数可以调用静态成员函数(因为我有this指针, 只是你用不到而已)
下面给出一个具体的栗子
class date {
public:
date(int y = 2020, int m = 2, int d = 22)
: _year(y)
, _month(m)
, _day(d)
{
_cnt++;
}
//静态成员函数
//不能访问非静态成员, 没有this指针 (访问静态成员时, 对象可能不存在, 那么和对象强相关的东西都不存在)
//也调不动非静态成员函数, 因为没有this指针, 参数不够
static int getCount() {
return _cnt;
}
private:
//C++11的初始化方式: 相当于给一个缺省值
//最后一个选择 (备胎)
int _year = 1;
int _month = 1;
int _day = 1;
//静态成员变量, 所有对象共享此成员变量(相当于类域中的全局变量), 可以看成类成员
//静态成员不在对象模型中, 一般存放在数据段, 不能在初始化列表中初始化
public:
static int _cnt;
};
//静态成员必须在类外初始化
int date::_cnt = 0;
下面我们来看一下静态成员的访问方式
有两种方式:
1. 通过对象访问
2. 类名+作用域限定符
因为静态成员是独立于对象存在的, 因此在使用第二种方法时不必考虑对象存不存在
下面给出测试用例
void test4() {
date d;
date* p = &d;
date d2;
//静态成员变量访问方式:
//1. 对象访问
cout << &d._cnt << endl;
cout << &d2._cnt << endl;
cout << p->_cnt << endl;
//2. 类名 + 作用域限定符
//访问静态成员变量不需要考虑对象存不存在
cout << &date::_cnt << endl;
cout << date::_cnt << endl;
//静态成员函数也有两种访问方式, 同上
}
结果如下:
我们可以看到对象 d 和 d2 的_cnt地址一样, 可证明静态数据的特性
静态成员到这就说的差不多啦~
接下来插播一段, C++11中的初始化方式
相信细心的小伙伴已经发现了, 在上面给的date类中, 在year, month, day声明的时候我给了一个值, 这个就是C++11标准中的新的初始化方式
就是这里 :
这相当于给缺省值, 但为什么说他是备胎呢?
因为编译器在初始化时会先找初始化列表, 如果没有初始化列表, 会看构造函数中有没有缺省值, 如果还没有, 这时才会到这来看有没有值…你说他是不是备胎~ 😃
三. 友元函数和友元类
我们先给一个场景
class DATE {
public:
DATE(int y, int m, int d) {
_y = y;
_m = m;
_d = d;
}
void printDATE() {
cout << _y << "-" << _m << "-" << _d << endl;
}
ostream& operator<< (ostream& _cout) {
_cout << _y << "-" << _m << "-" << _d << endl;
return _cout;
}
private:
int _y;
int _m;
int _d;
};
这里我们每次都调用printDATE函数打印, 而为了方便使用, 我们写了一个<<运算符重载函数, 大家可以想一想, 他真的方便了吗?
我们该如何调用他呢?
如下 :
d << cout << endl;
那么肯定有人不理解了, 在这里还原一下
它的原形如下:
d在前面因为要this指针始终占用第一个参数,
要传入d的this那么他就必须放在左边
d.operator<<(cout);
但是这样就和我们平时的写法相反, 非常别扭, 我还不如调用之前的函数呢, 你这整的花里胡哨的…
既然这个顺序别扭, 那我们来整个不别扭的
怎么整呢, 那就定义一个普通函数, 普通函数我们可以自己规定参数的位置
具体如下 :
//输出运算符重载函数
ostream& operator<< (ostream& _cout, const DATE& d) {
_cout << d._y << "-" << d._m << "-" << d._d << endl;
return _cout;
}
这样的话, 我们就可以正常使用输出了.
但是此时又有一个问题, 普通函数是访问不到类中的私有成员的, 所以我们只能把变量都设为public , 但这样是很危险的, 非常不安全
所谓鱼与熊掌不可兼得, 但话又说回来了, 小孩子才做选择, 我全都要 ! !
那有没有办法二者兼得呢?
那本期天降猛男就来了 , 他就是友元
友元函数:
使用方式: friend + 函数声明
在类内部任何地方都可以声明, 在类外定义
友元函数仍然是一个普通函数, 不是类的成员函数
友元函数可以访问类的所有成员
注意: 友元是一种突破封装等等语法, 所以不建议多使用
下面给出刚才栗子的友元版
class DATE {
public:
//声明友元函数
friend ostream& operator<< (ostream& _cout, const DATE& d);
friend istream& operator>> (istream& _cin, DATE& d);
DATE(int y, int m, int d) {
_y = y;
_m = m;
_d = d;
}
void printDATE() {
cout << _y << "-" << _m << "-" << _d << endl;
//因为DATE是time的友元类, 所以在DATE类中可以访问time类中的
//函数和变量
cout << _t._h << "-" << _t._m << "-" << _t._s << endl;
}
private:
int _y;
int _m;
int _d;
time _t;
};
ostream& operator<< (ostream& _cout, const DATE& d) {
_cout << d._y << "-" << d._m << "-" << d._d << endl;
return _cout;
}
istream& operator>> (istream& _cin, DATE& d) {
_cin >> d._y >> d._m >> d._d;
return _cin;
}
使用友元函数之后, 就可以丝滑的使用输入输出了~
void test6() {
DATE d(2020, 10, 22);
d.printDATE();
cin >> d;
cout << d << endl;
}
接下来我们来看友元类
友元类:
类中所有成员函数都是友元类的友元函数
友元关系是单向的, 且不能传递 (朋友的朋友, 我不认识也很正常)
这里我给出上面DATE类的友元类栗子
class time {
//DATE是time的友元类 : DATE类中的所有成员函数是time类的友元函数
//所以上面DATE类中的printDATE函数就可以随意访问time类的成员
friend class DATE;
private:
int _h = 24;
int _m = 0;
int _s = 0;
};
ok, 友元说到这也就差不多了…
最后我们来看一种比友元更加亲密的关系: 内部类
内部类
顾名思义, 内部类就是在类内部再定义一个类
内部类天然是外部类的友元类, 可以访问外部类的所有成员
内部类可以直接访问外部类的static成员, 不需要通过对象和类名
sizeof(外部类) = 外部类
但是内部类是独立于外部类存在的, 外部类不能访问内部类的私有成员
class a {
public:
a(int a = 1)
:_a(a)
{}
//内部类
//天然是外部类的友元类, 可以访问外部类的所有成员
//注意: 普通友元函数, static成员还是需要通过对象或者类名访问
class b {
public:
void printa(const a& a) {
// 普通成员: 通过对象访问
cout << a._a << endl;
// static成员: 直接访问
cout << _s << endl;
}
private:
int _b;
};
//外部类访问不到内部类的私有成员, 内部类是独立于外部类存在的
private:
int _a;
static int _s;
};
int a::_s = 100;
void test7() {
cout << sizeof(a::b) << endl; //4
cout << sizeof(a) << endl; //4 静态成员变量不在对象模型中存放, 放在静态数据区
}
到这里就结束了~