类和对象(4)
(本文是《类和对象》的收尾)
一.构造函数初始化的逻辑
1.构造函数的初始化列表使用说明 初始化列表是以冒号开头、逗号分隔成员变量的初始化方式,格式为: 构造函数() : 成员1(初始值), 成员2(表达式) { ... } 。
如下图,以Date为例
①. 初始化列表格式
- 构造函数名(参数列表) : 成员变量1(初始值1), 成员变量2(初始值2), ...
- 在这段代码中, Date(int& ret, int year = 1, int month = 1, int day = 1) 是构造函数,冒号 : 后面是初始化列表。
- 例如 :_ret(ret) , _ret 是类 Date 中的成员变量(引用类型),括号里的 ret 是构造函数传入的参数,通过这种方式用传入参数对成员变量进行初始化 。
- 又如 ,_year(year) , _year 是类的成员变量,用构造函数传入的 year 参数对其初始化。
②. 特殊成员的初始化
- 这里的 _ret 是引用成员变量,引用成员变量必须在初始化列表中初始化,这是符合初始化列表使用规则的。因为引用必须在定义时初始化,不能在构造函数体内再赋值。
2.核心规则与注意事项 - 使用限制:
- 引用成员、 const 成员、无默认构造函数的类类型成员,必须通过初始化列表初始化,否则编译报错。
- 每个成员变量在初始化列表中只能出现一次,其本质是成员变量的定义初始化位置。
- C++11 缺省值机制:
- 成员变量可在声明时指定缺省值,若初始化列表未显式初始化该成员,则自动使用此缺省值。
- 性能与初始化逻辑:
- 建议优先使用初始化列表:未在列表中显式初始化的成员,仍会通过初始化列表完成初始化:
- 内置类型:若声明时无缺省值,是否初始化由编译器决定(C++未规定);
- 自定义类型:调用其默认构造函数,若无则编译报错。
- 初始化顺序由成员在类中声明的顺序决定,与初始化列表中的顺序无关,建议两者保持一致以避免混淆。
1. Time 类 初始化: Time 类有构造函数 Time(int hour) ,通过成员初始化列表 : _hour**our) ,将传入的参数 hour 赋给成员变量 _hour ,在构造对象时就确定了 _hour 的值,同时构造函数内输出 "Time()" ,提示构造函数执行。
2. Date 类 初始化: Date 类的构造函数 Date() ,利用初始化列表 : _month(2) ,把成员变量 _month 初始化为 2 ,并在构造函数中输出 "Date()" 。
缺省值:类中 _year 和 _month 等成员变量声明时给了缺省值(如 int _year = 1; ),这不是直接初始化。若初始化列表未对其显式初始化,就会按缺省值初始化。这里 _month 在初始化列表已显式初始化, _year 未在初始化列表处理,按缺省值 1 初始化 , _day 没缺省值也没在初始化列表初始化,值不确定(输出为 0 )。
总结 ①每个构造函数隐含初始化列表,所有成员变量均通过此列表完成初始化;
②显式使用初始化列表可更高效、准确地控制成员初始化逻辑,尤其适用于有特殊初始化要求的成员(如引用、 const 、无默认构造的类类型)。
看一段特殊的代码:
以上代码出现这种结果是因为成员变量初始化顺序是按声明顺序。代码里 _a2 声明在前,初始化 _a2(_a1) 时 _a1 还未初始化 , _a2 未按缺省值 2 初始化,成了未初始化的随机值(这里是 -858993460 ) ,之后 _a1 按构造函数初始化列表被初始化为 1 ,所以输出 1 -858993460 。 应调整成员变量声明顺序,让 _a1 在前,或合理设置初始化逻辑来避免此类问题。
二.C++类型转换
1.类型转换
- C++支持内置类型隐式类型转换为类类型对象,需要有相关内置类型为参数的构造函数。
- 构造函数前面加explicit就不再支持隐式类型转换。
- 类类型的对象之间也可以隐式转换,需要相应的构造函数支持。
2.类型转换实例
1.单参数类型转换
在以上C++ 代码 Date d1 = 2025; 中,存在隐式类型转换机制。 Date 类定义了单参数构造函数 Date(int year) ,当执行该语句时,编译器会自动调用此构造函数,把 int 类型的 2025 转换为 Date 类型对象 d1 。
2.多参数类型转换
以上代码 1. 构造函数实现多参数转换: Date 类定义了多参数构造函数 Date(int year, int month, int day) ,当执行 Date d1 = { 2025, 5, 12 }; 时,编译器会调用此构造函数。它将三个 int 类型的实参 2025 、 5 、 12 分别转换并赋值给类的私有成员变量 _year 、 _month 、 _day ,完成从 int 类型数据到 Date 类对象成员变量的类型转换与初始化。
2. 列表初始化与转换:这里使用了花括号初始化列表的形式 Date d1 = { 2025, 5, 12 }; ,这是 C++11 引入的统一初始化语法。对于类类型对象,只要存在合适的构造函数(此处的多参数构造函数),就能通过这种方式将对应类型(这里是 int 类型)的初始化列表中的值,按照顺序转换并传递给构造函数,进而初始化对象的成员变量。
3.类类型和类类型之间的转换
以上代码:
1. 类 Date 构造函数用于类型转换: Date 类有构造函数 Date(const A& a) ,它以类 A 的常引用为参数。当执行 Date d1 = aa; 时,会调用该构造函数,将类 A 类型对象 aa 转换为 Date 类型对象 d1 ,通过调用 a.get() 获取值来初始化 Date 类的 _year 成员变量。
2. 隐式类类型转换:此构造函数使得从 A 类对象到 Date 类对象的隐式转换成为可能。只要存在这样适配的构造函数,编译器就能自动进行这种类型转换操作 ,但也可能带来意外转换问题,必要时可加 explicit 修饰构造函数阻止隐式转换。
4.C语言和C++类型转换对比
3.static修饰的类成员
1.static成员
静态成员变量 - 被 static 修饰的成员变量即静态成员变量,它必须在类外初始化。这是因为静态成员变量为所有类对象共享,不隶属于单个对象,存储于静态区 。
静态成员函数 - 由 static 修饰的成员函数是静态成员函数,其没有 this 指针。由于缺少 this 指针,静态成员函数只能访问其他静态成员,无法访问非静态成员。
- 与之相对,非静态成员函数可自由访问静态成员变量与静态成员函数。
静态成员访问 - 突破类作用域即可访问静态成员,可通过“类名::静态成员” 或 “对象.静态成员” 方式进行,且静态成员同样受 public 、 protected 、 private 访问限定符约束。
- 需注意,静态成员变量不能在声明处用缺省值初始化,因其不依赖特定对象,不遵循构造函数初始化列表规则。
2.static使用实例
以上代码中 静态成员变量 _Count
1. 声明与初始化:在类 A 中用 static 修饰声明了 int _Count ,这是静态成员变量,它为所有 A 类对象共享。在类外进行了初始化 int A::_Count = 0; ,符合静态成员变量需在类外初始化的规则。
2. 使用与计数逻辑:在类的构造函数 A() 中,执行 _Count++ ,每创建一个对象, _Count 就自增 1 ;拷贝构造函数 A(const A& a) 中, ++_Count ,每次执行拷贝构造也使 _Count 增加 1 ;析构函数 ~A() 里, --_Count ,对象销毁时 _Count 自减 1 。最终 aal.Print() 输出的 3 ,是因为经过对象创建、拷贝构造等操作后, _Count 累计增加到了 3 。
静态成员变量的特性体现
1. 共享性: _Count 不属于任何单个 A 类对象,无论创建多少个 A 类对象,都共享这一个 _Count 变量,用于记录对象相关的数量变化(此处类似对象创建和拷贝的计数 )。
2. 访问规则:尽管 _Count 是 private 权限,但在类内成员函数(如构造、析构、拷贝构造、 Print 函数 )中可以正常访问和操作,体现了静态成员同样受访问限定符约束的特性。
3.练习题static的使用
计算1+2+3+.....+n
这是一段 C++ 代码,定义了 Solution 类,其中定义了内部类Sum 类。 Sum 类的构造函数通过操作静态成员变量 _ret 和 _i 来实现累加逻辑。 Solution 类的 Sum_Solution 函数创建 Sum 类对象数组,触发构造函数执行,最终返回累加结果。巧妙运用类的嵌套和静态成员,展现了独特的编程思路与数据处理方式 。
四.友元
1.友元
友元概述
友元是 C++ 中突破类访问限定符封装的机制,分为友元函数和友元类 。在函数声明或类声明前加 friend 关键字,并将其声明置于某个类定义内部,即可确立友元关系。
友元函数特性
- 访问权限:外部友元函数能够访问类的私有和保护成员。需注意,友元函数并非类的成员函数,只是借助声明获得特殊访问权。
- 声明灵活性:友元函数在类定义中的声明位置不受访问限定符约束,可在任意位置声明。而且,一个函数可以同时成为多个类的友元函数 。
友元类特性
- 成员函数权限:友元类中的所有成员函数,皆可作为另一个类的友元函数,进而访问该类的私有和保护成员。
- 关系属性:友元类关系具有单向性,无交换性 ,比如 A 类是 B 类的友元,B 类不一定是 A 类的友元;同时也不具备传递性,若 A 是 B 的友元,B 是 C 的友元,A 并非自动成为 C 的友元。
应用考量
友元在某些场景能带来便利,比如方便实现特定功能的跨类操作。但它会增加类之间的耦合度,破坏类的封装性,因此在实际编程中应谨慎、适度使用。
五.内部类
内部类定义
若一个类定义于另一个类的内部,此为内部类 。内部类是独立的类,与全局定义的类相比,仅受外部类的类域及访问限定符约束。外部类对象并不包含内部类对象。
友元关系特性
内部类默认作为外部类的友元类 ,这赋予内部类可访问外部类私有和保护成员的权限,便于二者间交互协作。
封装应用场景
内部类也是一种封装手段。当 A 类与 B 类联系紧密,且 A 类主要为 B 类所用时,可将 A 类设计为 B 类的内部类。若置于 private 或 protected 区域,A 类就成为 B 类专属内部类,在类外其他地方无法使用,增强了代码的安全性与内聚性 。
友元与内部类对比