C++第四讲:类和对象(下)

C++第四讲:类和对象(下)

这一章是类和对象的进阶收尾,重点解决 "构造函数怎么写更规范"、"类的特殊成员怎么用"、"编译器背后做了什么优化" 这几个核心问题,也是面试高频考点。


一、再探构造函数:初始化列表(必学)

1. 为什么需要初始化列表

之前我们在构造函数函数体内赋值 ,但这不是真正的 "初始化",而是 "赋值"。C++ 提供了初始化列表 ,是成员变量定义时初始化的地方,效率更高,且有些场景必须用它。

2. 语法格式

cpp 复制代码
类名(参数列表)
: 成员1(初始值1), 成员2(初始值2), ...
{
    // 函数体(可选,用于其他逻辑)
}

3. 必须使用初始化列表的 3 种场景(必考)

引用成员变量

const 成员变量

没有默认构造函数的自定义类型成员

cpp 复制代码
class Time
{
public:
    // 只有带参构造,没有默认构造
    Time(int hour) : _hour(hour) {}
private:
    int _hour;
};
​
class Date
{
public:
    Date(int& x, int year, int month, int day)
    : _year(year)    // 普通成员也可以用初始化列表
    , _t(12)         // 必须:Time没有默认构造
    , _ref(x)        // 必须:引用必须初始化
    , _n(10)         // 必须:const必须初始化
    {
        // 这里不能给_t、_ref、_n赋值,会编译报错
    }
private:
    int _year;
    Time _t;         // 自定义类型,无默认构造
    int& _ref;       // 引用
    const int _n;    // const常量
};

4. 关键规则

  1. 初始化顺序 = 成员在类中的声明顺序,和初始化列表的先后顺序无关(高频坑)

    cpp 复制代码
    class A
    {
    public:
        A(int a) : _a1(a), _a2(_a1) {} // 先初始化_a2,再初始化_a1
        void Print() { cout << _a1 << " " << _a2 << endl; }
    private:
        int _a2 = 2; // 先声明_a2
        int _a1 = 2; // 后声明_a1
    };
    ​
    int main()
    {
        A aa(1);
        aa.Print(); // 输出:1 随机值(_a2先初始化,此时_a1还没赋值)
        return 0;
    }
  2. C++11 支持成员声明时给缺省值

    缺省值会传给初始化列表,如果初始化列表没显式写这个成员,就用缺省值:

    cpp 复制代码
    class Date
    {
    private:
        int _year = 2025; // 缺省值
        int _month = 5;
        int _day;
    };
  3. 无论是否显式写初始化列表,所有成员都会走初始化列表

    • 显式写了:用列表里的值初始化

    • 没写:有缺省值用缺省值,没有则内置类型随机值,自定义类型调用默认构造


二、隐式类型转换与 explicit

1. 什么是隐式类型转换

C++ 允许内置类型 → 类类型 的隐式转换,前提是类有对应参数的构造函数

cpp 复制代码
class A
{
public:
    A(int a) : _a(a) {}
private:
    int _a;
};
​
int main()
{
    A aa1 = 1; // 隐式转换:1 → 构造临时A对象 → 拷贝构造aa1
               // 编译器优化:直接构造aa1(省略拷贝)
    return 0;
}

2. explicit 关键字:禁止隐式转换

在构造函数前加explicit,就只能显式构造对象,不能隐式转换:

cpp 复制代码
class A
{
public:
    explicit A(int a) : _a(a) {}
private:
    int _a;
};
​
int main()
{
    // A aa1 = 1; ❌ 编译报错:禁止隐式转换
    A aa2(1);    // ✅ 显式构造
    return 0;
}

3. 多参数隐式转换(C++11)

cpp 复制代码
class A
{
public:
    A(int a1, int a2) : _a1(a1), _a2(a2) {}
private:
    int _a1;
    int _a2;
};
​
int main()
{
    A aa = {1, 2}; // C++11支持多参数隐式转换
    return 0;
}

三、static 静态成员(面试高频)

1. 静态成员变量

  • static修饰,属于整个类,不属于某个对象

  • 所有对象共享同一份静态变量,存在静态区

  • 必须在类外初始化(不能在声明时给缺省值)

cpp 复制代码
class A
{
public:
    A() { ++_count; }
    A(const A&) { ++_count; }
    ~A() { --_count; }
​
    // 静态成员函数
    static int GetCount() { return _count; }
private:
    static int _count; // 类内声明
};
​
// 类外初始化(必须写)
int A::_count = 0;
​
int main()
{
    cout << A::GetCount() << endl; // 0
    A a1, a2;
    A a3(a1);
    cout << A::GetCount() << endl; // 3
    return 0;
}

2. 静态成员函数

  • 没有this指针

  • 只能访问静态成员变量和静态成员函数(不能访问非静态,因为没有 this)

  • 调用方式:类名::静态函数()对象.静态函数()

3. 核心特点总结

对比项 普通成员 静态成员
所属 单个对象 整个类
存储 对象内存 静态区
访问 需要对象 类名直接访问
this 指针

四、友元(突破封装)

1. 作用

允许外部函数或类访问另一个类的私有 / 保护成员,是封装的 "例外"。

2. 友元函数

cpp 复制代码
class Date
{
    // 声明友元函数
    friend ostream& operator<<(ostream& out, const Date& d);
private:
    int _year = 2025;
    int _month = 5;
    int _day = 1;
};

// 全局函数,访问Date的私有成员
ostream& operator<<(ostream& out, const Date& d)
{
    out << d._year << "-" << d._month << "-" << d._day;
    return out;
}

3. 友元类

cpp 复制代码
class A
{
    // 声明B是A的友元类
    friend class B;
private:
    int _a = 1;
};

class B
{
public:
    void PrintA(const A& aa)
    {
        cout << aa._a << endl; // B可以访问A的私有成员
    }
};

4. 注意事项

  • 友元是单向的:A 是 B 的友元,不代表 B 是 A 的友元

  • 友元不能传递:A 是 B 的友元,B 是 C 的友元,不代表 A 是 C 的友元

  • 友元破坏封装,尽量少用(必要时才用,比如重载 <<和>>)


五、内部类

1. 定义

一个类定义在另一个类的内部,叫做内部类。

cpp 复制代码
class A
{
public:
    // B是A的内部类
    class B
    {
    public:
        void func(const A& a)
        {
            cout << a._h << endl; // B默认是A的友元,可以访问私有成员
        }
    };
private:
    int _h = 10;
};

int main()
{
    A::B b; // 内部类的定义方式
    A aa;
    b.func(aa);
    return 0;
}

2. 核心特点

  1. 内部类是独立的类,外部类对象不包含内部类对象

    cpp 复制代码
    cout << sizeof(A) << endl; // 输出4(只有_h),不包含B的大小
  2. 内部类默认是外部类的友元

  3. 受外部类的访问限定符限制:如果 B 定义在 private 下,外部就不能使用 B


六、匿名对象

1. 定义

没有名字的对象,生命周期只有当前一行

cpp 复制代码
class A
{
public:
    A(int a) : _a(a) { cout << "构造" << endl; }
    ~A() { cout << "析构" << endl; }
    void Print() { cout << _a << endl; }
private:
    int _a;
};

int main()
{
    A(10).Print(); // 匿名对象:构造→调用Print→析构(同一行完成)
    // 下一行匿名对象已经销毁
    return 0;
}

2. 使用场景

临时调用一个对象的成员函数,不需要保存对象:

cpp 复制代码
// 调用Solution的Sum_Solution函数,不需要保存Solution对象
int sum = Solution().Sum_Solution(100);

七、对象拷贝的编译器优化

现代编译器会自动优化不必要的拷贝构造,提高效率。

1. 常见优化场景

场景 1:隐式类型转换

cpp 复制代码
void f(A aa) {}
f(1); // 构造临时A + 拷贝构造 → 优化为直接构造

场景 2:匿名对象传参

cpp 复制代码
f(A(2)); // 构造匿名对象 + 拷贝构造 → 优化为直接构造

场景 3:传值返回

cpp 复制代码
A f2()
{
    A aa;
    return aa; // 构造aa + 拷贝构造临时对象 → 优化为直接构造返回值
}

A aa2 = f2(); // 临时对象 + 拷贝构造aa2 → 优化为直接构造aa2

2. 关闭优化(调试用)

Linux 下编译时加参数:

cpp 复制代码
g++ test.cpp -fno-elide-constructors

八、本章必背核心结论

  1. 初始化列表是成员变量定义的地方,引用、const、无默认构造的成员必须用它

  2. 初始化顺序只看声明顺序,和列表顺序无关

  3. explicit禁止构造函数的隐式类型转换

  4. 静态成员属于类,静态变量必须类外初始化,静态函数没有 this 指针

  5. 友元是单向的、不可传递的,破坏封装

  6. 匿名对象生命周期只有一行,用于临时调用

  7. 编译器会优化连续的构造 + 拷贝构造为直接构造

相关推荐
Rabitebla1 小时前
vector 的骨架:三根指针、模板陷阱与迭代器失效的第一现场
开发语言·数据结构·c++·算法
代码不停1 小时前
BFS解决floodfill算法题目练习
算法·宽度优先
上弦月-编程2 小时前
C语言指针从入门到实战
java·jvm·算法
WL_Aurora2 小时前
Python 算法基础篇之树和二叉树
python·算法
txzrxz2 小时前
关于前缀和
算法·动态规划·图论
杨连江2 小时前
载流子矩阵限域束缚实现常温常压超导的理论与结构设计
算法
做cv的小昊2 小时前
【TJU】研究生应用统计学课程笔记(6)——第二章 参数估计(2.4 区间估计)
人工智能·笔记·线性代数·算法·机器学习·数学建模·概率论
普贤莲花2 小时前
【2026年第18周---写于20260501】---舍得
程序人生·算法·leetcode
2zcode2 小时前
基于深度学习的口腔疾病图像识别系统(UI界面+改进算法+数据集+训练代码)
人工智能·深度学习·算法