1、初始化列表
构造函数体赋值:在构造函数体内通过赋值操作设置成员变量的值,本质是先默认初始化再赋值。
初始化列表:在对象创建时直接调用成员变量的构造函数,一步完成初始化。以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。
// 构造函数体赋值(低效)
Date(int year, int month, int day) {
_year = year; // 赋值操作
_month = month;
_day = day;
}
// 初始化列表(高效)
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
必须使用初始化列表的场景:
- const成员变量
- 引用成员变量
- 自定义成员类型(且该类没有默认构造函数时)
class A
{
public:
A(int a)
:_a(a)
{}
private:
int _a;
};
class B
{
public:
B(int a, int ref)
:_aobj(a)
,_ref(ref)
,_n(10)
{}
private:
A _aobj; // 没有默认构造函数
int& _ref; // 引用
const int _n; // const
};
注意:成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
比如:
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 随机值
}
_a2在_a1之前声明,所以_a2会比_a1先初始化。
1.1、explicit关键字
构造函数不仅可以构造与初始化对象,对于接收单个参数的构造函数,还具有类型转换的作用。但是用explicit修饰构造函数,将会禁止构造函数的隐式转换。
比如:
class Date
{
public:
// explicit修饰构造函数,禁止了单参构造函数类型转换的作
// 用
explicit Date(int year)
: _year(year)
{}
};
int main()
{
Date d1(2022);
d1 = 2023;// 用一个整形变量给日期类型对象赋值
// 实际编译器背后会用2023构造一个无名对象,最后用无名对象给d1对象进行赋值
return 0;
}
2、static成员
概念:声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化
2.1 特性
1. 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
2. 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
3. 类静态成员可用 类名::静态成员 或者 对象.静态成员 来访问
4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
5. 静态成员也是类的成员,受public、protected、private 访问限定符的限制
那么,可以用static的特性来计算一个类里面创建出多少个对象:
class A
{
public:
A()
{
++_scount;
}
A(const A& t)
{
++_scount;
}
~A()
{
--_scount;
}
static int GetACount()
{
return _scount;
}
private:
static int _scount;
};
int A::_scount = 0;
void TestA()
{
cout << A::GetACount() << endl;// 0
A a1, a2;
A a3(a1);
cout << A::GetACount() << endl;// 3
}
-
静态成员函数能否调用非静态成员?
不能,因为缺少this
指针。 -
非静态成员函数能否调用静态成员?
可以,静态成员属于类,与对象无关。
3、友元(友元函数和友元类)
友元是一种突破封装的方式。当两个类之间有非常紧密的关系,且需要频繁访问彼此的私有成员时,友元可以简化代码并提高效率。
3.1、友元函数
友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。
友元函数重载<<运算符:
class Date
{
friend ostream& operator<<(ostream& out, const Date& d);//声明友元
public:
Date(int year = 1900, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
private:
int _year, _month, _day;
};
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "-" << d._month << "-" << d._day;
return out;
}
- 友元函数可访问类的私有和保护成员,但不是类的成员函数
- 友元函数不能用const修饰
- 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
- 一个函数可以是多个类的友元函数
3.2、友元类
友元类的所有成员函数都能访问另一个类的私有成员。
class Time
{
friend class Date; // Date是Time的友元类
public:
Time(int h = 0, int m = 0, int s = 0)
: _hour(h)
, _minute(m)
, _second(s)
{}
private:
int _hour, _minute, _second;
};
class Date
{
public:
void SetTime(int h, int m, int s)
{
_t._hour = h; // 直接访问Time的私有成员
_t._minute = m;
_t._second = s;
}
private:
Time _t;
};
注意事项:
-
友元关系是单向的(A是B的友元,B不一定是A的友元)
-
慎用友元,避免破坏封装性
4、内部类
一个类定义在一个类的内部,就叫内部类。
- 定义在类内部的独立类,与外部类无继承关系
- 内部类天生是外部类的友元,可直接访问外部类的私有成员
- 外部类不是内部类的友元
比如:
class Outer
{
public:
class Inner
{
public:
void Show(const Outer& o)
{
cout << o._data; // 访问外部类私有成员
}
};
private:
int _data = 42;
};
int main()
{
Outer::Inner inner;
Outer outer;
inner.Show(outer); // 输出42
}
5、有名对象和匿名对象
有名对象 ---- 生命周期在当前函数局部域
匿名对象 ---- 生命周期在当前行
#include <iostream>
using namespace std;
class Date
{
private:
int _year;
int _month;
int _day;
public:
Date(int year, int month, int day)
: _year(year), _month(month), _day(day)
{}
};
int main()
{
Date(2023, 10, 11); // 匿名对象
const Date& today = Date(2023, 10, 11); // 匿名对象绑定到 const 引用
today.Print(); // 通过引用访问匿名对象
return 0;
}