C++从入门到实战(十)类和对象(最终部分)static成员,内部类,匿名对象与对象拷贝时的编译器优化详解
前言
- 在上一节的博客中,我们深入探讨了 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 指针 |
访问权限 | 受 public 、protected 、private 访问限定符限制 |
受 public 、protected 、private 访问限定符限制 |
可访问的成员 | 无此概念 | 可以访问其他静态成员,不能访问非静态成员 |
访问方式 | 通过 类名::静态成员变量 或 对象.静态成员变量 访问 |
通过 类名::静态成员函数() 或 对象.静态成员函数() 访问 |
二、内部类(C++实践中不爱用,java爱用,了解即可)
- 如果一个类定义在另一个类的内部,这个内部类就叫做 内部类。它是一个独立的类,与全局类相比,仅受外部类的类域限制和访问限定符约束。
2.1 内部类的特点
特点 | 说明 |
---|---|
独立类 | 内部类是独立的类,与外部类无继承关系,但受外部类类域限制。 |
不包含在外部对象中 | 外部类的对象不会包含内部类的对象,两者是独立的内存空间。 |
默认友元类 | 内部类默认是外部类的友元类,可以直接访问外部类的私有成员。 |
访问权限控制 | 内部类可以放在 public 、protected 、private 中,限制外部访问。 |
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;
}
关键细节
友元关系:
内部类默认是外部类的友元,因此可以访问外部类的所有成员(包括私有成员)。访问内部类:
- 若内部类在
public
中:外部类名::内部类名
。 - 若内部类在
private
/protected
中:只能在外部类内部或友元中使用。
- 若内部类在
与静态成员的区别:
- 内部类是独立类,而静态成员是类的属性或方法。
- 内部类需要实例化后才能使用,静态成员可直接通过类名访问。
三、匿名对象
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(具名返回值优化)
重点提炼
static成员核心:
- 静态成员变量必须在类外初始化,初始化时无需重复static关键字。
- 静态成员函数不能调用非静态成员,因其没有this指针。
内部类设计原则:
- 内部类作为外部类的默认友元,可访问其所有成员。
- 内部类定义位置影响其可见性(public/private)。
对象生命周期管理:
- 匿名对象适合只使用一次的场景,避免资源浪费。
- 编译器优化技术(RVO/NRVO)可消除拷贝构造,提升性能。
性能优化实践:
- 优先使用引用传递代替值传递,减少拷贝开销。
- 在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
非常感谢您的阅读,喜欢的话记得三连哦 |