在 C++ 面向对象编程中,类与对象的进阶特性是写出高效、规范代码的关键。本文将聚焦构造函数细节、静态成员、友元、内部类、匿名对象及编译器优化等核心知识点,结合实例拆解原理,帮你彻底吃透这些易混淆的重点。
一、再谈构造函数:初始化列表的核心规则
构造函数是对象创建的 "蓝图",而初始化列表则是对象成员初始化的核心战场,掌握以下规则能避免大部分编译报错:
1. 初始化列表的本质
每个构造函数都隐含初始化列表,哪怕你没显式写出 ------所有成员变量都会通过初始化列表完成初始化,构造函数体中的赋值只是后续修改,并非真正的初始化。
初始化列表的语法格式:
cpp
类名(参数列表)
: 成员变量1(初始值1), 成员变量2(初始值2), ...
{
// 构造函数体(可选赋值操作)
}
2. 必须在初始化列表初始化的成员
以下三类成员因 "必须在定义时初始化" 的特性,强制要求在初始化列表中显式初始化:
- const 成员变量(如
const int _n):常量一旦定义无法修改- 引用成员变量(如
int& _ref):引用必须绑定初始对象- 无默认构造的自定义类型成员(如
Time _t):编译器无法自动调用默认构造
示例代码:
cpp
class Time {
public:
Time(int hour) : _hour(hour) {} // 无默认构造
private:
int _hour;
};
class Date {
public:
Date(int& xx, int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
, _n(5) // const成员
, _ref(xx) // 引用成员
, _t(1) // 无默认构造的自定义类型
{}
private:
int _year;
int _month;
int _day;
const int _n;
int& _ref;
Time _t;
};
3. 初始化顺序的关键注意
初始化列表的初始化顺序完全遵循成员变量在类中的声明顺序,与列表中的书写顺序无关。建议声明顺序与列表顺序保持一致,避免逻辑错误:
cpp
class A {
public:
A(int a)
: _a1(a)
, _a2(_a1) // 声明顺序是_a2在前,_a1在后,实际先初始化_a2
{}
private:
int _a2 = 2; // 先声明,先初始化
int _a1 = 2; // 后声明,后初始化
};
// 输出:_a1=1,_a2=随机值(初始化_a2时_a1尚未初始化)
4. 成员变量的缺省值规则
C++11 支持在成员声明时指定缺省值,该值的作用是:当成员未在初始化列表显式初始化时,自动使用缺省值。注意这并非定义(仅声明阶段),内存分配仍在对象创建时进行:
cpp
class Date {
private:
int _year = 1; // 缺省值,初始化列表未写时使用
int _month = 1;
int _day = 1;
};
二、static 成员
static 修饰的成员属于整个类,而非单个对象,是实现类级共享数据的核心工具。
1. 静态成员变量的核心特性
- 存储位置:位于静态区,不占用对象内存(
sizeof(类)不计入静态成员)- 初始化:必须在类外初始化(类内仅声明),且不走构造函数初始化列表
- 共享性:所有对象共享同一实例,修改一个对象的静态成员会影响所有对象
- 访问权限:受 public/protected/private 限制,突破类域即可访问(
类名::成员或对象.成员)
示例代码:
cpp
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; // 类外初始化
// 访问示例
cout << A::GetACount() << endl; // 0(无需创建对象)
A a1, a2;
cout << a1.GetACount() << endl; // 2(对象访问)
2. 静态成员函数的限制
- 无 this 指针,无法访问非静态成员(非静态成员依赖具体对象)
- 可访问其他静态成员(静态成员属于类,全局唯一)
- 非静态成员函数可访问静态成员(拥有 this 指针,可间接访问类级资源)
3. 经典实战:静态成员实现累加求和
求 1+2+...+n,不使用循环、判断等关键字
cpp
class Sum {
public:
Sum() { _ret += _i; ++_i; }
static int GetRet() { return _ret; }
private:
static int _i; // 累加计数器
static int _ret; // 累加结果
};
int Sum::_i = 1;
int Sum::_ret = 0;
class Solution {
public:
int Sum_Solution(int n) {
Sum a[n]; // 创建n个对象,触发n次构造累加
return Sum::GetRet();
}
};
三、友元
友元提供了一种突破类访问权限的方式,允许外部函数或类访问私有 / 保护成员,但会破坏封装,需谨慎使用。
1. 友元函数
- 声明方式:在类内添加
friend 函数声明,不受访问限定符限制- 特性:不是类的成员函数,可访问多个类的私有成员
示例:
cpp
class B; // 前置声明
class A {
friend void func(const A& aa, const B& bb); // 友元声明
private:
int _a1 = 1;
};
class B {
friend void func(const A& aa, const B& bb);
private:
int _b1 = 3;
};
void func(const A& aa, const B& bb) {
cout << aa._a1 << endl; // 合法访问私有成员
cout << bb._b1 << endl;
}
2. 友元类
- 声明方式:
friend class 类名;- 特性:友元类的所有成员函数都可访问当前类的私有成员,关系单向且不可传递
示例:
cpp
class A {
friend class B; // B是A的友元,A不是B的友元
private:
int _a1 = 1;
};
class B {
public:
void func(const A& aa) {
cout << aa._a1 << endl; // 合法访问
}
};
四、内部类
内部类是定义在另一个类内部的类,本质是独立的类,仅受外部类的类域和访问权限限制。
核心特性
- 独立性:外部类对象不包含内部类成员,
sizeof(外部类)不计入内部类- 友元关系:内部类默认是外部类的友元,可访问外部类的所有成员
- 访问限制:内部类的访问权限由外部类的访问限定符控制(如 private 内部类仅外部类可用)
示例:
cpp
class A {
private:
static int _k;
int _h = 1;
public:
class B { // 内部类,默认是A的友元
public:
void foo(const A& a) {
cout << _k << endl; // 访问外部类静态成员
cout << a._h << endl; // 访问外部类非静态成员
}
};
};
int A::_k = 1;
// 使用方式
A::B b; // 需通过外部类类域访问
A aa;
b.foo(aa);
五、匿名对象
匿名对象是无名称的对象,语法为类名(实参),核心特点是生命周期仅当前行,适用于临时使用的场景。
示例:
cpp
class A {
public:
A(int a = 0) : _a(a) {}
~A() { cout << "~A()" << endl; }
private:
int _a;
};
int main() {
A(1); // 匿名对象,行尾自动析构
Solution().Sum_Solution(10); // 临时对象调用成员函数,无需定义变量
}
六、对象拷贝的编译器优化
现代编译器会在不影响正确性的前提下,省略传参和返回值过程中的无意义拷贝,核心是优化连续的拷贝构造操作。
优化规则
- 优化场景:连续的 "构造 + 拷贝构造" 可合并为一次构造
- 不可优化:赋值重载(
=)无法优化,需经历 "构造 + 拷贝构造 + 赋值" 流程- 关闭优化:Linux 下使用
g++ test.cpp -fno-elideconstructors编译,可观察完整拷贝流程
示例代码:
cpp
A f2() { A aa; return aa; }
// 可优化:连续拷贝构造合并为一次构造
A aa2 = f2();
// 不可优化:赋值重载无法省略
A aa1;
aa1 = f2();
七、类型转换
C++ 支持内置类型与类类型的隐式转换,核心依赖对应构造函数:
- 隐式转换:当类有单参数构造函数时,内置类型可自动转换为类对象
- 禁止转换:在构造函数前加
explicit关键字,可禁用隐式转换- 多参数转换:C++11 支持
A aa = {1,2};形式的多参数隐式转换
示例:
cpp
class A {
public:
// explicit A(int a) // 禁用隐式转换
A(int a = 0) : _a1(a) {}
};
A aa1 = 1; // 隐式转换:1→临时对象→aa1(优化为直接构造)
const A& raa2 = 2; // 临时对象具有常性,需const引用接收