⭐️ 初始化列表
构造函数体内的赋值不能称为初始化,只能将其称为赋值,因为初始化只能初始化一次,而构造函数体内可以多次赋值。
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个成员变量后面跟一个括号,括号里是当前成员变量的初始值或表达式。
例1:
#include <iostream>
using namespace std;
class Date {
public:
Date(int year, int month, int day)
:_year(year)
,_month(month)
,_day(day)
{}
private:
int _year;
int _month;
int _day;
};
int main() {
Date date(2023, 8, 13);
return 0;
}
ps:
每个成员变量在初始化列表只能初始化一次。类中以下成员必须在初始化列表初始化。
- 引用成员变量
const
成员变量- 自定义类型成员(且该类没有默认构造函数的时候)
例2:
#include <iostream>
using namespace std;
class Time {
public:
Time(int hour)
:_hour(hour)
{}
private:
int _hour;
};
class Test {
public:
Test(int num, int ref, int hour)
:_num(num)
,_ref(ref)
,_time(hour)
{}
private:
const int _num;
int& _ref;
Time _time;
};
int main() {
int ref = 20;
Test test(10 , ref , 40);
return 0;
}
ps:
当类中的成员变量有自定义类型的时候,当前类的构造函数会隐含的调用自定义类型成员变量的默认构造来初始化,但是当你不提供默认构造时,编译器就会报错。解决的办法是我们自己可以显示的去调用它的构造函数,像初始化成员变量推荐使用初始化列表,一些逻辑判断可以放在函数体内。对于自定义类型成员变量一定会先使用初始化列表初始化。
成员变量在类中声明的顺序就是在初始化列表中的初始化顺序,和声明的顺序有关,与初始化列表的顺序无关。
例3:
#include <iostream>
using namespace std;
class A {
public:
A(int a)
:_a1(a)
, _a2(_a1)
{}
void Print() {
cout << _a1 << " " << _a2 << endl;
}
private:
// 先声明 _a2 也就是在初始化列表中会先初始化 _a2
int _a2;
int _a1;
};
int main() {
A aa(1);
aa.Print();
return 0;
}
ps:
先声明 _a2
也就是在初始化列表中会先初始化 _a2
,所以在初始化列表中先执行_a2(_a1)
,但是此时 _a1
是随机值,所以 _a2
被赋值成随机值,紧接着是 _a1
初始化执行 _a1(a)
,所以最终 _a1
被赋值为 a
的值。
⭐️ explicit 关键字
构造函数不仅可以初始化对象,对于单个参数或者第一个参数无默认值其余都有默认值的构造函数,还有类型转换的作用。
例4:
class Date {
public:
Date(int year)
:_year(year)
{}
private:
int _year;
};
int main() {
Date d1(2023); // 直接调用构造函数初始化
/*
具有隐式类型转换:
先使用 2022 构造出一个临时对象
再拷贝构造给 d2
但是通常编译器会直接优化成 直接调用构造函数
*/
Date d2 = 2024;
return 0;
}
ps:
当加上 explicit
关键字修饰构造函数,就会禁止了单参的构造函数类型转换的作用。
⭐️ static 成员
声明 static
的类成员称为类的静态成员,类静态成员变量一定要再类外进行初始化,static
修饰的变量和函数都属于整个类。
特性:
- 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
- 静态成员必须在类外定义,定义时不需要添加
static
关键字,类中是声明int Type:: count = 0;
- 类静态成员可以用 类名
::
静态成员 或者对象.
静态成员访问 - 静态成员函数没有隐藏的
this
指针,不能访问任何非静态成员 - 静态成员也受访问限定符的限制
例5:
#include <iostream>
using namespace std;
class Test {
public:
Test() {
count++;
}
static int getCount() {
return count;
}
private:
static int count; // 声明
};
int Test::count = 0; // 初始化
int main() {
Test t1;
Test t2;
cout << Test::getCount() << endl; // 2
return 0;
}
⭐️ 友元
友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明。使用 friend
关键字。
例6:
#include <iostream>
using namespace std;
class Test {
// test函数是 Test 类的友元函数
friend void test(const Test& t);
public:
Test(int count)
: _count(count)
{}
private:
int _count;
};
void test(const Test& t) {
cout << t._count << endl;
}
int main() {
Test t(100);
test(t);
return 0;
}
特性:
- 友元函数可访问类的私有和保护成员,但不是类的成员函数
- 友元函数不能用
const
修饰 - 友元函数可以在类的任何地方声明,不受访问限定符的限制
- 一个函数可以是多个类的友元
- 友元函数的调用与普通函数的调用原理相同
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的私有成员。
- 友元是单向的。
A
是B
的友元,那么A
就可以访问B
的私有成员,反之不行。 - 友元关系不能传递。
B
是A
的友元,C
是B
的友元,但是C
不是A
的友元
class Time {
/*
声明 Date 是 Time 的友元类,在 Date 类中的所有成员函数都可以访问 Time 类的私有保护成员变量
*/
friend class Date;
};
⭐️ 内部类
如果一个类定义在另一个类的内部,这个类就叫做内部类。内部类可以看作是一个独立的类,它不属于外部类,也不能通过外部类的对象去访问内部类的成员,外部类也访问不到内部类的成员。
需要注意的是:
- 内部类天生就是外部类的友元类,内部类可以通过外部类的对象来访问外部类中的成员。
- 内部类可以直接访问外部类中的
static
成员,不需要外部类的对象或类名 sizeof(外部类) = 外部类
,和内部类没有任何关系- 内部类也会受到访问限定符的限制
- 内部类会受到外部类的类域限制
例7:
#include <iostream>
using namespace std;
class A {
private:
static int _k;
int _h = 100;
public:
//B天生就是A的友元
class B {
public:
void foo(const A& a) {
// 可以直接访问外部类的静态变量
cout << _k << endl;
// 可以直接通过外部类对象访问外部类的私有成员
cout << a._h << endl;
}
};
};
int A::_k = 1;
int main() {
// 创建内部类对象 受外部类类域限制
A::B b;
// A() 匿名对象 声明周期就在当前行
b.foo(A());
return 0;
}