当前位置: 首页 > news >正文

C++从入门到实战(十)类和对象(最终部分)static成员,内部类,匿名对象与对象拷贝时的编译器优化详解

C++从入门到实战(十)类和对象(最终部分)static成员,内部类,匿名对象与对象拷贝时的编译器优化详解

  • 前言
  • 一、static成员
    • 1. 什么是 static 成员
    • 2. 静态成员变量
    • 3. 静态成员函数
    • 4. 访问限定符的影响
  • 二、内部类(C++实践中不爱用,java爱用,了解即可)
    • 2.1 内部类的特点
    • 2.2 内部类的应用场景
  • 三、匿名对象
    • 3.1 什么是匿名对象
  • 四、对象拷贝时的编译器优化(了解即可)
    • 4.2 拷贝构造函数和赋值运算符
    • 4.2 编译器优化示例
  • 总结(核心概念速记):
    • 知识图谱


前言

  • 在上一节的博客中,我们深入探讨了 C++ 初始化列表、类型转换与友元机制,掌握了对象初始化的高级技巧和类间访问权限的控制方法。

我的个人主页,欢迎来阅读我的其他文章
https://blog.csdn.net/2402_83322742?spm=1011.2415.3001.5343
我的C++知识文章专栏
欢迎来阅读指出不足
https://blog.csdn.net/2402_83322742/category_12880513.html?spm=1001.2014.3001.5482

  • 这篇博客将聚焦于 类和对象的终极核心内容,涵盖static 成员、内部类、匿名对象与编译器优化等高级主题。
  • 这些知识不仅是 C++ 面向对象编程的精髓,更是大厂面试高频考点

一、static成员

1. 什么是 static 成员

  • 在 C++ 里,static 成员是被 static 关键字修饰的类成员。
  • 它涵盖静态成员变量和静态成员函数
  • 和普通的类成员不同,static 成员是为类的所有对象所共享的,并非属于某个特定的对象

2. 静态成员变量

特点:

  • 所有类对象共同使用。
  • 不在对象里,而是存于静态区。
  • 必须在类外进行初始化。
  • 不能在声明的地方设置缺省值,因为缺省值是用在构造函数初始化列表的,而静态成员变量不属于任何对象,不经过构造函数初始化列表。

访问方式:可以借助类名 :: 静态成员或者对象 . 静态成员来访问。

#include <iostream>

class MyClass {
public:
    // 声明静态成员变量
    static int staticVar;
};

// 在类外初始化静态成员变量
int MyClass::staticVar = 10;

int main() {
    // 通过类名访问静态成员变量
    std::cout << "通过类名访问静态成员变量: " << MyClass::staticVar << std::endl;

    MyClass obj1, obj2;
    // 通过对象访问静态成员变量
    std::cout << "通过对象 obj1 访问静态成员变量: " << obj1.staticVar << std::endl;
    std::cout << "通过对象 obj2 访问静态成员变量: " << obj2.staticVar << std::endl;

    // 修改静态成员变量的值
    obj1.staticVar = 20;
    std::cout << "修改后通过类名访问静态成员变量: " << MyClass::staticVar << std::endl;
    std::cout << "修改后通过对象 obj2 访问静态成员变量: " << obj2.staticVar << std::endl;

    return 0;
}

在这里插入图片描述

3. 静态成员函数

特点

  • 没有 this 指针
  • 能够访问其他的静态成员,不过无法访问非静态成员,原因是没有 this 指针
  • 非静态成员函数可以访问任意的静态成员变量和静态成员函数。

#include <iostream>
class MyClass {
public:
    static int staticVar;
    int nonStaticVar=20;

    static void staticFunction() {
        std::cout << "静态成员函数访问静态成员变量: " << staticVar << std::endl;
        // 下面这行代码会报错,因为静态成员函数不能访问非静态成员
        // std::cout << nonStaticVar << std::endl; 
    }

    // 非静态成员函数
    void nonStaticFunction() {
        std::cout << "非静态成员函数访问静态成员变量: " << staticVar << std::endl;
        staticFunction();
    }
};

// 在类外初始化静态成员变量
int MyClass::staticVar = 10;

int main() {
    // 通过类名调用静态成员函数
    MyClass::staticFunction();

    MyClass obj;
    // 通过对象调用静态成员函数
    obj.staticFunction();
    // 调用非静态成员函数
    obj.nonStaticFunction();

    return 0;
}

在这里插入图片描述

4. 访问限定符的影响

  • 静态成员也属于类的成员,会受到 public、protected、private 访问限定符的约束。
  • 要是静态成员被声明为 private,那就只能在类的内部访问。
对比项静态成员变量静态成员函数
存储位置静态区代码区(函数都存储在此)
所属对象为所有类对象所共享,不属于某个具体对象为所有类对象所共享,不属于某个具体对象
初始化必须在类外进行初始化,不能在声明位置给缺省值初始化无需额外初始化
this 指针无此概念没有 this 指针
访问权限publicprotectedprivate 访问限定符限制publicprotectedprivate 访问限定符限制
可访问的成员无此概念可以访问其他静态成员,不能访问非静态成员
访问方式通过 类名::静态成员变量对象.静态成员变量 访问通过 类名::静态成员函数()对象.静态成员函数() 访问

二、内部类(C++实践中不爱用,java爱用,了解即可)

  • 如果一个类定义在另一个类的内部,这个内部类就叫做 内部类。它是一个独立的类,与全局类相比,仅受外部类的类域限制和访问限定符约束。

2.1 内部类的特点

特点说明
独立类内部类是独立的类,与外部类无继承关系,但受外部类类域限制。
不包含在外部对象中外部类的对象不会包含内部类的对象,两者是独立的内存空间。
默认友元类内部类默认是外部类的友元类,可以直接访问外部类的私有成员。
访问权限控制内部类可以放在 publicprotectedprivate 中,限制外部访问。

2.2 内部类的应用场景

  • 当两个类 紧密关联 且内部类仅为外部类服务时(如链表节点类专为链表类服务),可将内部类设计为外部类的私有成员,实现封装和隐藏。

代码示例

#include <iostream>

class Outer {
private:
    int privateVar = 10; // 外部类的私有成员

public:
    // 内部类定义在 public 中,外部可访问
    class Inner {
    public:
        void accessOuterPrivate(Outer& outer) {
            // 内部类作为友元,直接访问外部类的私有成员
            std::cout << "内部类访问外部类私有成员: " << outer.privateVar << std::endl;
        }
    };

    // 内部类定义在 private 中,外部不可直接访问
    class PrivateInner {
    public:
        void doSomething() { std::cout << "私有内部类" << std::endl; }
    };
};

int main() {
    // 实例化内部类(需通过外部类类名访问)
    Outer::Inner inner;
    Outer outer;
    inner.accessOuterPrivate(outer); // 输出:内部类访问外部类私有成员: 10

    // 尝试访问 private 内部类(会报错)
    // Outer::PrivateInner pInner; // 编译错误:PrivateInner 是私有的

    return 0;
}

关键细节

  1. 友元关系
    内部类默认是外部类的友元,因此可以访问外部类的所有成员(包括私有成员)。

  2. 访问内部类

    • 若内部类在 public 中:外部类名::内部类名
    • 若内部类在 private/protected 中:只能在外部类内部或友元中使用。
  3. 与静态成员的区别

    • 内部类是独立类,而静态成员是类的属性或方法。
    • 内部类需要实例化后才能使用,静态成员可直接通过类名访问。

三、匿名对象

3.1 什么是匿名对象

  • 在 C++ 里,我们通常会创建一个有名字的对象,之后通过这个名字来使用该对象
  • 不过有时候,我们只需要临时用一下对象,用完就不再需要它了,这时就可以创建匿名对象
  • 匿名对象就是没有名字的对象,它的生命周期仅在创建它的那一行代码里,代码执行完这一行,匿名对象就会被销毁。
#include <iostream>

// 定义一个简单的类
class Calculator {
public:
    // 加法方法
    int add(int a, int b) {
        return a + b;
    }
    // 减法方法
    int subtract(int a, int b) {
        return a - b;
    }
};

int main() {
    // 有名对象的使用
    Calculator calc;
    int result1 = calc.add(5, 3);
    std::cout << "有名对象计算结果: " << result1 << std::endl;

    // 匿名对象的使用
    int result2 = Calculator().add(5, 3);
    std::cout << "匿名对象计算结果: " << result2 << std::endl;

    return 0;
}

在这里插入图片描述

  • 匿名对象适合那种只需要临时使用一次,之后就不再需要的场景。
  • 比如,只需要调用一次对象的方法,而且不需要保留这个对象供后续使用。
  • 因为匿名对象没有名字,所以没办法在其他代码行再次使用它。要是需要多次使用同一个对象,就得创建有名对象。

四、对象拷贝时的编译器优化(了解即可)

  • 在 C++ 中,对象的拷贝操作(如拷贝构造函数和赋值运算符的使用)可能会带来一定的性能开销,尤其是在处理大型对象或者频繁进行拷贝操作时。
  • 现代编译器为了提高程序的执行效率,会在不影响程序正确性的前提下,尽可能地减少一些不必要的拷贝操作。

4.2 拷贝构造函数和赋值运算符

在深入了解编译器优化之前,我们先来简单回顾一下拷贝构造函数和赋值运算符

  • 拷贝构造函数用于创建一个新对象,该对象是另一个同类型对象的副本。赋值运算符则用于将一个对象的值赋给另一个已经存在的对象。

以下是一个简单的类,包含拷贝构造函数和赋值运算符

#include <iostream>

class MyClass {
public:
    // 构造函数
    MyClass(int value) : data(value) {
        std::cout << "Constructor called with value: " << data << std::endl;
    }

    // 拷贝构造函数
    MyClass(const MyClass& other) : data(other.data) {
        std::cout << "Copy constructor called with value: " << data << std::endl;
    }

    // 赋值运算符
    MyClass& operator=(const MyClass& other) {
        if (this != &other) {
            data = other.data;
        }
        std::cout << "Assignment operator called with value: " << data << std::endl;
        return *this;
    }

    // 析构函数
    ~MyClass() {
        std::cout << "Destructor called with value: " << data << std::endl;
    }

private:
    int data;
};

4.2 编译器优化示例

示例 1:返回值优化(RVO,Return Value Optimization)

返回值优化是一种常见的编译器优化技术,它可以避免在函数返回对象时进行不必要的拷贝。

MyClass createObject() {
    return MyClass(42);
}

int main() {
    MyClass obj = createObject();
    return 0;
}
  • 在这个示例中,createObject 函数返回一个 MyClass 对象。
  • 按照正常的逻辑,会先创建一个临时对象,然后将这个临时对象拷贝给 main 函数中的 obj
  • 但是,编译器可能会进行返回值优化,直接在 obj 的内存位置上构造对象,从而避免了拷贝操作。

输出结果
在开启优化的编译器中,可能只会看到一次构造函数的调用,而不会看到拷贝构造函数的调用。

Constructor called with value: 42
Destructor called with value: 42

示例 2:具名返回值优化(NRVO,Named Return Value Optimization)

  • 具名返回值优化与返回值优化类似,只不过返回的对象是一个具名对象。
MyClass createNamedObject() {
    MyClass temp(42);
    return temp;
}

int main() {
    MyClass obj = createNamedObject();
    return 0;
}

同样,编译器可能会进行具名返回值优化,直接在 obj 的内存位置上构造对象,避免了拷贝操作。

输出结果
在开启优化的编译器中,可能只会看到一次构造函数的调用,而不会看到拷贝构造函数的调用。

Constructor called with value: 42
Destructor called with value: 42

注意事项

  • 编译器优化并不是标准规定的行为,不同的编译器可能会有不同的优化策略。有些编译器可能会在默认情况下开启优化,而有些则需要手动指定优化选项(如 -O1-O2-O3 等)。
  • 虽然编译器优化可以提高程序的性能,但在编写代码时,我们仍然应该遵循良好的编程习惯,避免不必要的拷贝操作。

总结(核心概念速记):

核心概念速记
类和对象(终章) = 共享机制(static) + 封装强化(内部类) + 性能优化(匿名对象与拷贝优化)

  • static成员特性

    • 共享存储:静态成员变量存于静态区,所有对象共享。
    • 无this指针:静态成员函数只能访问静态成员,无法操作非静态数据。
    • 初始化规则:静态成员变量必须在类外初始化,且不能在声明时赋默认值。
  • 内部类设计哲学

    • 默认友元:内部类可直接访问外部类私有成员,实现深度封装。
    • 独立存在:内部类对象不包含在外部类对象中,需显式实例化。
  • 对象生命周期优化

    • 匿名对象:临时使用场景下的高效选择,用完即销毁。
    • 编译器优化:RVO/NRVO技术消除不必要的拷贝构造,提升性能。

关键技术对比表

技术点核心特性典型应用场景
静态成员变量共享存储、类外初始化、无this指针统计类实例数量、全局配置参数
静态成员函数无this指针、只能访问静态成员工具类方法、单例模式实现
内部类默认友元、独立类、受访问权限控制链表节点类、状态机实现
匿名对象无名称、临时使用、生命周期短一次性方法调用、函数参数传递
编译器优化RVO/NRVO消除拷贝构造、-O2/-O3选项生效返回值优化、性能敏感代码

知识图谱

C++类和对象(终章)  
├─ static成员  
│  ├─ 静态成员变量(共享存储、类外初始化)  
│  └─ 静态成员函数(无this指针、访问限制)  
├─ 内部类  
│  ├─ 默认友元关系  
│  ├─ 独立内存空间  
│  └─ 访问权限控制(public/private)  
├─ 匿名对象  
│  ├─ 临时使用场景  
│  └─ 与具名对象对比  
└─ 编译器优化  
   ├─ RVO(返回值优化)  
   └─ NRVO(具名返回值优化)  

重点提炼

  1. static成员核心

    • 静态成员变量必须在类外初始化,初始化时无需重复static关键字。
    • 静态成员函数不能调用非静态成员,因其没有this指针。
  2. 内部类设计原则

    • 内部类作为外部类的默认友元,可访问其所有成员。
    • 内部类定义位置影响其可见性(public/private)。
  3. 对象生命周期管理

    • 匿名对象适合只使用一次的场景,避免资源浪费。
    • 编译器优化技术(RVO/NRVO)可消除拷贝构造,提升性能。
  4. 性能优化实践

    • 优先使用引用传递代替值传递,减少拷贝开销。
    • 在release版本中开启优化选项(如GCC的-O2),充分发挥编译器优化能力。

典型错误场景

// 错误1:静态成员变量在类内初始化  
class MyClass {  
    static int var = 0; // ❌ 错误,必须在类外初始化  
};  

// 错误2:静态成员函数访问非静态成员  
class MyClass {  
    int data;  
    static void func() {  
        data = 10; // ❌ 错误,静态函数无this指针  
    }  
};  

// 错误3:匿名对象重复使用  
MyClass().doSomething(); // ✅ 正确,临时使用  
MyClass().getResult();   // ✅ 正确,但两次创建匿名对象  

技术演进脉络

C++类机制演进 —— 普通成员 → static成员 → 内部类 → 智能指针管理对象  
   ↓          ↓          ↓           ↓  
功能增强 —— 独立存储 → 全局共享 → 深度封装 → 自动资源管理  

以上就是这篇博客的全部内容,下一篇我们将继续探索更多精彩内容。

我的个人主页,欢迎来阅读我的其他文章
https://blog.csdn.net/2402_83322742?spm=1011.2415.3001.5343
我的C++知识文章专栏
欢迎来阅读指出不足
https://blog.csdn.net/2402_83322742/category_12880513.html?spm=1001.2014.3001.5482

非常感谢您的阅读,喜欢的话记得三连哦

在这里插入图片描述


文章转载自:
http://cancrine.pzdurr.cn
http://actuality.pzdurr.cn
http://academical.pzdurr.cn
http://ameliorable.pzdurr.cn
http://bta.pzdurr.cn
http://blithely.pzdurr.cn
http://backwater.pzdurr.cn
http://alated.pzdurr.cn
http://annabergite.pzdurr.cn
http://bacterium.pzdurr.cn
http://autunite.pzdurr.cn
http://belfried.pzdurr.cn
http://antimonyl.pzdurr.cn
http://cavy.pzdurr.cn
http://auxin.pzdurr.cn
http://ahull.pzdurr.cn
http://chowder.pzdurr.cn
http://amphictyonic.pzdurr.cn
http://aspectual.pzdurr.cn
http://cdrom.pzdurr.cn
http://american.pzdurr.cn
http://backroom.pzdurr.cn
http://chromonemal.pzdurr.cn
http://apparatus.pzdurr.cn
http://breast.pzdurr.cn
http://bayard.pzdurr.cn
http://badmintoon.pzdurr.cn
http://aristocratism.pzdurr.cn
http://carcinoid.pzdurr.cn
http://battleplane.pzdurr.cn
http://www.dtcms.com/a/110306.html

相关文章:

  • LeetCode 891 -- 贡献度思想
  • 【爬虫】网易云音乐评论数据爬取
  • nodejs、socket.io、express + 实时线上聊天系统(自用笔记)
  • 若依——基于AI+若依框架的实战项目(实战篇(下))
  • 大模型中的参数规模与显卡匹配
  • forms实现俄罗斯方块
  • 什么是数据仓库
  • 从效率瓶颈到智能飞跃:AI工具如何重塑 Java开发模式
  • 敏捷开发:以人为本的高效开发模式
  • Dify工作流中如何去除deepseek-r1思考内容
  • html+css+js 实现一个贪吃蛇小游戏
  • TypeScript String:深入理解与使用
  • uniapp开发app 上传视频切片上传
  • 基于51单片机的智能停车场proteus仿真
  • IM基本设计思路与有序ID的重要性
  • 关税核爆72小时!跨境矩阵防御战紧急打响
  • ACF导电粒子聚集问题阐述及依据聚集位置制定规避措施
  • 生物化学笔记:医学免疫学原理12 黏膜免疫系统 + 抗原性物质含片(“糖丸”的原理)免疫调节
  • 1g内存电脑sqlite能支持多少并发
  • Android Java 实现事件总线
  • 【ubuntu24.04】挂载windows的共享文件夹
  • 软件工程面试题(二十二)
  • # 使用 Dlib 和 OpenCV 实现人脸关键点检测
  • CTF类题目复现总结-hashcat 1
  • 推理场景的存力范式迁移
  • AI本地部署之ragflow
  • Educational Codeforces Round 172 (Rated for Div. 2)
  • ros2--gazebo--launch
  • Ubuntu离线安装mysql
  • 处理JWT Token失效需求