🔑🔑博客主页:阿客不是客
🍓🍓系列专栏:从C语言到C++语言的渐深学习
欢迎来到泊舟小课堂
😘博客制作不易欢迎各位👍点赞+⭐收藏+➕关注
一、初始化列表
1.1 引入
我们知道,常量必须在定义时初始化,不初始化就会出问题,因为常量只有一次初始化的机会,就是在定义的时候。
那我们来思考一个问题,const 修饰的常量应该在哪进行初始化?
private:
/* 声明部分 */
int _year;
int _month;
int _day;
const int _N = 10; // 这一块是声明,我们不应该在这初始化。
};
在定义里面?初始化要在空间上给值,你这里有空间吗?你没空间啊!
基于这种原因,C++ 就搞出了一个叫做 初始化列表 (Initializer List) 的东西。
1.2 定义
我们之前学习创建对象时,编译器通过调用构造函数,给对象赋初值。
初始化列表作用与构造函数类似:它是在构造函数中以一个冒号开始,接着是以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号中的初始化式。
下面我们还是以一个日期类来示范:
class Date
{
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{
//...
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d(2024,10,20);
d.Print();
return 0;
}
2.3 注意事项
①每个成员变量再初始化列表中只能出现一次,即 初始化只能初始化一次。
② 必须在定义时就初始化的成员变量,要在初始化列表初始化。
类中包含以下成员,必须放在初始化列表位置进行初始化:
- const成员变量 const int _N;
- 引用成员变量 int& ref;
- 没有默认构造函数的自定义类型成员变量 A _aa;
③ 尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。
内置类型的成员,在函数体和初始化列表初始化都可以,
自定义类型的成员,建议在初始化列表初始化,这样更高效。
④ 成员变量在类中的声明顺序就是在初始化列表中的初始化顺序,与其在初始化列表中出现的顺序无关。
class A
{
public:
A(int a)
: _a1(a)
, _a2(_a1) //先进行初始化,但此时_a1未进行初始化是随机值
{}
void Print()
{
cout << _a1 << " " << _a2 << endl;
}
private:
int _a2;
int _a1;
};
int main()
{
A aa(1);
aa.Print();
return 0;
}
但是我们知道初始化只能初始化一次。所以如果使用初始化列表进行初始化,则在定义中给的缺省值在进行初始化时无效,在调用的时候有效
二、类型转换
在C语言中,对于隐式类型转换:
int main(void)
{
double d = 1.1;
int i = d;
return 0;
}
2.1 内置类型
在发生隐式类型转换时,如果都是内置类型就会先开辟一个临时变量,再将右操作数强制类型转换为左操作数的类型,最后用这个临时变量对左操作数进行赋值。注意:这个临时变量具有常性,不可修改。
int main()
{
double j = 1.1;
int i = j;//隐式类型转换
int& a = j;//error
const int& b = j;//ok
return 0;
}
因为临时变量具有常性,所以无法被修改,而引用则是可以进行修改。如果赋值给普通引用就会造成权限的放大,所以只能用常引用。
2.2 自定义类型
class Date {
public:
Date(int year)
: _year(year)
{
;
}
private:
int _year;
};
int main(void)
{
Date d1(2022);
Date d2 = 2022; // 隐式类型转换
return 0;
}
❓ 这里是隐式类型的转换,为什么支持一个整型转换成日期类相关的类型呢?
整型和日期类本来是没有关系的,但是你支持一个单参数的构造函数后,整型就可以去构造一个日期类的对象,这个日期类的对象自然可以赋值给他了。
本来用 2022 构造成一个临时对象 Date(2022) ,在用这个对象拷贝构造 d2,但是 C++ 编译器在连续的一个过程中,编译器为了提高效率,多个构造会被优化,合二为一。
所以这里被优化成,直接就是一个构造了。
如果你不想让这种 "转换" 发生,C++ 提供了一种关键字 —— explicit
2.3 explicit 关键字
构造函数不仅可以构造和初始化对象,对于单个参数的构造函数,还具有类型转换的作用,用 explicit 关键字修饰构造函数,可以禁止单参构造函数的隐式类型转换。
class Date {
public:
explicit Date(int year)
: _year(year)
{
;
}
private:
int _year;
};
三、类的静态成员(static)
3.1 定义
声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数。
class A
{
public:
static int Print()//静态成员函数
{
cout << "Print()" << endl;
}
private:
static int _a;//静态成员变量
};
静态成员变量属于整个类,所有对象,生命周期在整个程序运行期间。
3.2 静态成员函数的特点
- 静态成员变量为所有类对象所共享,不属于某个具体的对象,不存在对象中,存放在静态区。
- ⽤static修饰的成员函数,称之为静态成员函数,静态成员函数没有this指针。
- 静态成员也是类的成员,受public、protected、private 访问限定符的限制。
3.3 静态成员函数的访问
突破类域就可以访问静态成员,可以通过类名::静态成员 或者 对象.静态成员 来访问静态成员变量和静态成员函数。
3.3.1 静态成员函数的访问
class A
{
public:
static void Print()//静态成员函数
{
cout << "Print()" << endl;
}
private:
static int _a;//静态成员变量
};
int main()
{
A a;
a.Print();
A::Print();
return 0;
}
3.3.2 静态成员的访问
💬 如果它是公有的,我们就可以在类外对它进行访问:
class A
{
public:
A(int a = 0)
:_a(a)
{
_sCount++;
}
int GetCount()
{
return _sCount;
}
//private:
int _a;
static int _sCount;
};
int A::_sCount = 0;//类外定义
int main(void)
{
A a1;
A a2 = 1;
cout << A::_sCount << endl; // 使用类域对它进行访问
cout << a1._sCount << endl;
return 0;
}
但是如果它是私有的,我们可以提供一个公有的成员函数。我们写一个公有的 GetCount 成员函数,让它返回 _sCount 的值,这样我们就可以在类外调用该函数,就可以访问到它了。
注意:
- 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明。
- 静态成员函数中可以访问其他的静态成员,但是不能访问⾮静态的,因为没有this指针。
- ⾮静态的成员函数,可以访问任意的静态成员变量和静态成员函数。
- 静态成员变量不能在声明位置给缺省值初始化,因为缺省值是个构造函数初始化列表的,静态成员变量不属于某个对象,不⾛构造函数初始化列表。
四、友元
4.1 友元的定义
友元分为:友元函数和友元类。
友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,能不用就不用!
"友元就像是黄牛,破坏了管理规则 "
4.2 友元函数
有时在类外我们需要访问类中的数据,但由于访问限定符的限制并不能访问私有成员。这时如果一定要访问的话就需要借助我们的友元函数,它的用法十分简单,只用在类中加入 friend + 函数声明
class Date
{
friend void Print(const Date& d);//友元函数
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
void Print(const Date&d)
{
cout << d._year << "-" << d._month << "-" << d._day << endl;
}
📌 注意事项:
- 友元函数是可访问类的私有和保护成员,但不是类的成员函数
- 友元函数不能用const修饰
- 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
- 一个函数可以是多个类的友元函数
- 友元函数的调用与普通函数的调用原理相同
4.3 友元类
除了友元函数外,还有一种友元类。友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
friend class 类名;
class Time
{
friend class Date; // 声明日期类为时间类的友元类,则在日期类中就直接访问Time类
public:
Time(int hour = 0, int minute = 0, int second = 0)
: _hour(hour)
, _minute(minute)
, _second(second)
{}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
void SetTimeOfDate(int hour, int minute, int second)
{
// 直接访问时间类私有的成员变量
_t._hour = hour;
_t._minute = minute;
_t._second = second;
}
private:
int _year;
int _month;
int _day;
Time _t;
};
📌 注意事项:
- 友元关系是单向的,不具有交换性。
- 友元关系不具有传递性(朋友的朋友不一定是朋友)。
- 如果 B 是 A 的友元,C 是 B 的友元,则不能说明 C 是 A 的友元。
五、内部类
5.1 定义
如果在 类中定义
类,我们称
是
的内部类。
class A
{
public:
class B
{
;
};
private:
//......
};
5.2 内部类的特性
内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。
并且内部类就是外部类的友元类。内部类可以通过外部类的对象参数来访问外部类中的所有成员,像极了殖民行为。
class A {
private:
static int _s_a1;
int _a2;
public:
class B { // B天生就是A的友元
public:
void foo(const A& a) {
cout << _s_a1 << endl; // ✅
cout << a._a2 << endl; // ✅
}
private:
int _b1;
};
};
5.3 有关内部类的内存
❓ 我们用 sizeof 计算 A 类的大小,得到的结果会是什么?
#include <iostream>
using namespace std;
class A
{
private:
int _a1;
public:
class B
{
private:
int _b1;
};
};
int main(void)
{
cout << "A的大小为: " << sizeof(A) << endl;
return 0;
}
这是为什么呢?
① 内部类 B 和在全局定义是基本一样的,它只是受外部类 A 类域的限制,定义在 A 的类域中。
② 内部类 B 天生就是外部类 A 的友元,也就是 B 中可以访问 A 的私有,A 不能访问 B 的私有(或保护)。所以,A 类型的对象里没有 B,跟 B 没什么关系,计算 sizeof 当然也不会带上B。
六、匿名对象
匿名对象与C语言中的匿名结构体类似,只有类名,作用域只在匿名对象声明的一行。
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A(int a)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
}
int main()
{
A aa1;
//不能这么定义对象,因为编译器⽆法识别下⾯是⼀个函数声明,还是对象定义
//A aa1();
A();
A(1);
return 0;
}
七、编译器优化
下面我们将介绍一些编译器对自定义类型常见的优化,我们还是以日期类举例:
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
cout << "Date" << endl;
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)
{
cout << "Date(const Date& d)" << endl;
_year = d._year;
_month = d._month;
_day = d._day;
}
void Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
~Date()
{
cout << "~Date()" << endl;
_year = _month = _day = 0;
}
private:
int _year;
int _month;
int _day;
};
Date func2()
{
Date aa;
return aa;
}
- 直接构造(Date 临时变量(2024))+拷贝构造(Date d = 临时变量)=>直接构造(Date d(2024))
- 直接构造(Date aa)+拷贝构造(Date 临时变量 = aa)+拷贝构造(Date d = 临时变量)=>直接构造(Date d(aa._year, aa._month, aa._day)
- 直接构造(Date d)+直接构造(Date aa)+拷贝构造(Date 临时变量 = aa)+赋值重载(d = 临时变量)=>直接构造(Date aa)+直接构造Date d(aa._year, aa._month, aa._day)