类和对象(下)

个人主页流年如梦

专栏《零基础轻松入门C语言》 《数据结构:从入门到掌握》 《C++编程研习录》

文章目录

前言

本文是 C++ 类和对象系列终结篇,详细讲解初始化列表、explicit、static静态成员、友元、内部类、匿名对象及编译器优化等核心知识点。内容通俗易懂、贴合考试与面试,帮你彻底掌握类的高级特性,夯实面向对象编程基础

一.再探构造函数:初始化列表

1.1什么是初始化列表

初始化列表是构造函数真正初始化成员变量的地方
语法 :以冒号 : 开始,后面跟 成员(值),逗号 , 分隔

例如;

cpp 复制代码
class Date
{
public:
    // 初始化列表写法
    Date(int year, int month, int day)
        : _year(year)
        , _month(month)
        , _day(day)
    {}
};

1.2必须用初始化列表的 3 种成员

以下成员必须在初始化列表初始化,否则编译报错:

  1. 引用成员变量(必须初始化)
  2. const 成员变量(必须初始化)
  3. 没有默认构造函数的自定义类型
cpp 复制代码
class Time
{
public:
    Time(int hour)
        : _hour(hour)
    {}
private:
    int _hour;
};

class Date
{
public:
    Date(int& x)
        : _t(12)    // 无默认构造,必须初始化
        , _ref(x)   // 引用必须初始化
        , _n(10)    // const 必须初始化
    {}
private:
    Time _t;
    int& _ref;
    const int _n;
};

1.3初始化列表的执行规则

  1. 初始化顺序 = 类中声明顺序,和列表写的顺序无关!
  2. 每个成员只会初始化一次
  3. 没写在列表里的成员:
    声明带缺省值 --> 用缺省值
    内置类型 --> 随机值(看编译器)
    自定义类型 --> 调用默认构造

1.4C++11 成员变量声明缺省值

cpp 复制代码
class Date
{
private:
    // 声明时给缺省值,初始化列表会用
    int _year = 1;
    int _month = 1;
    int _day = 1;
};

1.5 经典面试题

cpp 复制代码
class A
{
public:
    A(int a)
        : _a1(a)
        , _a2(_a1)
    {}
    void Print() {
        cout << _a1 << " " << _a2 << endl;
    }
private:
    int _a2 = 2;
    int _a1 = 2;
};

// 输出:1 随机值
// 因为声明顺序是 _a2 先,再 _a1
// 初始化列表先初始化 _a2(_a1),但 _a1 还没初始化

二.类型转换与 explicit 关键字

2.1单参数构造函数支持隐式类型转换

cpp 复制代码
class A
{
public:
    A(int a1)
        : _a1(a1)
    {}
};

int main()
{
    A aa1 = 1;   // 隐式转换:1 --> 构造临时对象 --> 拷贝构造 → 优化为直接构造
    const A& aa2 = 1;
}

2.2explicit 禁止隐式转换

explicit 后,不允许隐式类型转换,更安全

cpp 复制代码
class A
{
public:
    explicit A(int a1)
        : _a1(a1)
    {}
};

int main()
{
    // A aa1 = 1; 错误,不允许隐式转换
    A aa1(1);   // 正确
}

2.3C++11 多参数隐式转换

cpp 复制代码
A aa3 = { 2, 2 };

三.static 静态成员

3.1静态成员变量

  1. 所有对象共享
  2. 存在静态区,不属于任何对象
  3. 必须在类外初始化
  4. 不能给声明缺省值
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;

3.2静态成员函数

  1. 没有 this 指针
  2. 只能访问静态成员
  3. 不能访问非静态成员(无 this
  4. 访问方式:类名::函数()对象.函数()

3.3经典应用:统计对象个数

cpp 复制代码
int main()
{
    cout << A::GetACount() << endl;
    A a1, a2;
    A a3(a1);
    cout << A::GetACount() << endl;
}

3.4面试题:求 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 arr[n];
        return Sum::GetRet();
    }
};

四.友元(friend)

友元可以访问类的私有成员,但会破坏封装,要慎用

4.1友元函数

cpp 复制代码
class A;
class B;

// 友元函数可以访问两个类的私有成员
void func(const A& aa, const B& bb);

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;
}

4.2友元类

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

class B
{
public:
    void func(const A& aa)
    {
        cout << aa._a1 << endl; // 可访问 A 私有
    }
};

4.3友元特性

  1. 友元是单向
  2. 友元不能传递
  3. 友元不考虑访问限定符
  4. 破坏封装,增加耦合度

五.内部类

5.1定义

类内部定义的类叫内部类,内部类天生就是外部类的友元

cpp 复制代码
class A
{
private:
    static int _k;
    int _h = 1;
public:
    // 内部类
    class B
    {
    public:
        void foo(const A& a)
        {
            cout << _k << endl;
            cout << a._h << endl;
        }
    };
};

int A::_k = 1;

5.2内部类特点

  1. 内部类独立,不属于外部对象
  2. sizeof(外部类) 不算内部类
  3. 受类域与访问限定限制
  4. 天生是外部类的友元
  5. 适合 "专属工具类"

5.3内部类经典题:1+2+...+n

cpp 复制代码
class Solution {
private:
    class Sum
    {
    public:
        Sum()
        {
            _ret += _i;
            ++_i;
        }
    };
    static int _i;
    static int _ret;
public:
    int Sum_Solution(int n) {
        Sum arr[n];
        return _ret;
    }
};

int Solution::_i = 1;
int Solution::_ret = 0;

六.匿名对象

6.1定义

类名() 就是匿名对象,没有名字

生命周期:当前一行,执行完立即析构

cpp 复制代码
class A
{
public:
    A(int a = 0) { cout << "A()" << endl; }
    ~A() { cout << "~A()" << endl; }
};

int main()
{
    A(); // 匿名对象,这一行结束就析构
    A(1);

    // 匿名对象调用函数非常方便
    Solution().Sum_Solution(10);
}

6.2优点

  1. 不用取名字
  2. 临时用一次最方便
  3. 表达式内直接使用

七.对象拷贝时的编译器优化

编译器会合并拷贝构造,减少不必要的拷贝

7.1常见优化场景

  1. A aa = 1;

    构造 + 拷贝构造 --> 优化为直接构造

  2. f1(1);

    构造 + 拷贝 --> 优化为直接构造

  3. f1(A(2));

    构造 + 拷贝 --> 优化为直接构造

  4. A aa2 = f2();

    局部构造 + 返回拷贝 + 接收拷贝 --> 优化为直接构造 aa2

7.2关闭优化(Linux g++)

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

7.3小结

  1. 同一个表达式内连续拷贝 --> 一定会优化
  2. 赋值无法被优化
  3. 不同编译器优化程度不同

🎯总结

  1. 初始化列表 :成员真正初始化的地方,引用 / const / 无默认构造必须用
  2. 初始化顺序:按声明顺序,不是列表顺序
  3. explicit:禁止单参数构造隐式类型转换,更安全
  4. static :静态成员属于类,共享,类外初始化,无 this
  5. 友元:可访问私有,单向、不传递、破坏封装
  6. 内部类:天生友元,独立,不属于外部对象
  7. 匿名对象:生命周期一行,临时使用方便
  8. 编译器优化:连续拷贝会合并,赋值无法优化

⚠️易错点

  1. 初始化列表顺序与声明顺序一致,不是书写顺序
  2. 引用 / const / 无默认构造对象必须初始化列表
  3. 静态成员变量必须类外初始化
  4. 静态函数无 this,不能访问非静态成员
  5. 友元单向、不传递
  6. 内部类独立,不算进外部类大小
  7. 匿名对象生命周期只有一行
  8. 拷贝构造在一个表达式里会被编译器优化合并

👀 关注 我们一路同行,从入门到大师,慢慢沉淀、稳步成长
❤️ 点赞 鼓励原创,让优质内容被更多人看见
⭐ 收藏 收好核心知识点与实战技巧,需要时随时查阅
💬 评论 分享你的疑问或踩坑经历,一起交流避坑、共同进步

相关推荐
NE_STOP8 分钟前
Vide Coding--AI编程工具的选择
java
LDR00618 分钟前
Type-C 快充全面升级!LDR6601 赋能个人护理便携电机,重塑剃须刀 / 理发器新体验
c语言·开发语言
雪碧聊技术21 分钟前
Tree.js是什么?一文讲透
开发语言·javascript·ecmascript
码云数智-园园31 分钟前
C++20 Modules 模块详解
java·开发语言·spring
程序员黑豆33 分钟前
JDK 下载安装与配置详细教程
java·前端·ai编程
小宇宙Zz1 小时前
Maven依赖冲突
java·服务器·maven
swordbob1 小时前
NIO的channel中什么是 fd(File Descriptor,文件描述符)
java·开发语言·nio
咖啡八杯1 小时前
GoF设计模式——享元模式
java·spring·设计模式·享元模式
十五喵源码网2 小时前
基于springboot2+vue2的租房管理系统
java·毕业设计·springboot·论文笔记
摇滚侠2 小时前
IDEA 创建 Java 项目 手动整合 SSM 框架
java·ide·intellij-idea