探索 C++ 类 核心概念与不同场景下的使用技巧
目录
- 一、初始化列表:对象成员的 “诞生地”
- 1. 初始化列表的基本用法
- 2. 必须用初始化列表的三种场景
- 3. 初始化顺序注意事项
- 二、C++ 的类型转换:不止于隐式转换
- 1. 隐式类型转换(自动发生)
- 2. explicit 关键字:禁止隐式转换
- 三、static 静态成员:属于类的 “共享资源”
- 1. 静态成员变量
- 2. 静态成员函数
- 四、友元:打破封装的 “特殊通道”
- 1. 友元函数
- 2. 友元类
- 3. 应用场景
- 五、内部类:类中的 “小类”
- 1. 基本特性
- 2. 用法示例
- 3. 应用场景
- 六、匿名对象:用完即毁的 “临时对象”
- 1. 基本用法
- 2. 应用场景
- 七、对象拷贝时的编译器优化
- 1. 场景 1:返回值优化
- 2. 场景 2:连续构造 + 拷贝优化
- 3、优化的限制与注意事项
在掌握了类和对象的基础后,总会遇到一些 “进阶概念”。这些概念看似零散,却能极大提升代码的效率和可读性。今天我们就系统讲解几个核心知识点:初始化列表、类型转换、static 静态成员、友元、内部类、匿名对象以及对象拷贝的编译器优化。
一、初始化列表:对象成员的 “诞生地”
在 C++ 中,类的成员变量初始化有两种方式:构造函数内赋值和初始化列表。但很多人不知道,初始化列表才是成员变量真正的 “诞生地”。
1. 初始化列表的基本用法
初始化列表位于构造函数参数列表后,用冒号:开头,成员变量之间用逗号分隔:
class Student
{public:// 初始化列表初始化成员变量Student(int a, string n) : age(a), name(n) {// 这里可以写其他逻辑,但成员已经初始化完成}
private:int age;string name;
};
2. 必须用初始化列表的三种场景
const 成员变量:const 变量必须在定义时初始化,无法在构造函数内赋值
class A
{public:// 正确:初始化列表初始化const成员A(int n) : num(n){} // 错误:构造函数内不能给const变量赋值// A(int n) { num = n; }
private:const int num;
};
引用成员变量:引用必须在定义时绑定对象,同样无法后期赋值
class B
{
public:B(int& r) : ref(r) // 必须用初始化列表{}
private:int& ref;
};
自定义类型成员(无默认构造函数):如果成员变量的类没有默认构造函数,必须在初始化列表显式调用其构造函数
class C
{
public:C(int x) {} // 没有默认构造函数
};class D
{
public:// 必须显式调用C的构造函数D(int x) : c(x) {}
private:C c;
};
3. 初始化顺序注意事项
初始化列表的顺序不取决于书写顺序,而是取决于成员变量在类中的声明顺序。例如:
class E
{
public:// 实际初始化顺序:先a(声明在前),再bE(int x) : b(x), a(b) { // 这里a会被初始化为随机值(b此时还未初始化)}
private:int a;int b;
};
建议:初始化列表的顺序与成员声明顺序保持一致,避免逻辑错误。
二、C++ 的类型转换:不止于隐式转换
C++ 提供了多种类型转换方式,合理使用能让代码更清晰,避免隐蔽错误。
1. 隐式类型转换(自动发生)
当两种类型兼容时,编译器会自动进行转换:
int a = 10;
double b = a; // 隐式转换:int -> doubleclass Int
{
public:Int(int n) : num(n) {} // 单参数构造函数,可用于隐式转换
private:int num;
};
Int c = 20; // 隐式转换:int -> Int(通过Int(int)构造函数)
2. explicit 关键字:禁止隐式转换
如果想阻止单参数构造函数的隐式转换,可加explicit:
class Int
{
public:explicit Int(int n) : num(n) {}
};
// 错误:explicit禁止了int到Int的隐式转换
// Int c = 20;
Int d(20); // 正确:直接调用构造函数
三、static 静态成员:属于类的 “共享资源”
static修饰的成员变量 / 函数不属于某个对象,而是属于整个类,被所有对象共享。
1. 静态成员变量
特点:存储在全局区,生命周期与程序一致,必须在类外初始化
用法:
class Counter
{
public:Counter() { count++; }static int getCount() // 静态成员函数{ return count; }
private:static int count; // 声明静态成员变量
};
// 类外初始化(必须做!)
int Counter::count = 0; int main()
{Counter c1, c2;cout << Counter::getCount(); // 输出2(两个对象被创建)return 0;
}
2. 静态成员函数
- 只能访问静态成员变量 / 函数,不能访问非静态成员(没有this指针)
- 调用方式:类名::函数名() 或 对象.函数名()
- 常用于工具类(如Math::max())或统计类(如计数功能)
四、友元:打破封装的 “特殊通道”
友元机制允许类外的函数或其他类访问当前类的私有成员,是对封装的灵活补充(但需谨慎使用,避免过度破坏封装)。
1. 友元函数
普通函数声明为类的友元:
class Date
{friend void printDate(Date d); // 声明友元函数
public:Date(int y) : year(y) {}
private:int year;
};
// 友元函数可以访问Date的私有成员
void printDate(Date d)
{cout << d.year;
}
2. 友元类
一个类声明为另一个类的友元:
class A
{
friend class B; // B是A的友元类
private:int x;
};
class B
{
public:void setA(A& a, int val) {a.x = val; // B可以访问A的私有成员}
};
3. 应用场景
- 运算符重载(如<<运算符需要访问类的私有成员)
- 两个类关系密切,需要互相访问私有成员(如链表的Node类和List类)
五、内部类:类中的 “小类”
定义在另一个类内部的类称为内部类,也叫嵌套类。
1. 基本特性
- 内部类是独立的类,不属于外部类,也不共享外部类的成员
- 内部类可以直接访问外部类的静态成员(包括私有)
- 外部类需要通过内部类的对象才能访问内部类的成员
2. 用法示例
class Outer
{
private:static int outStatic;int outMem;
public:// 内部类class Inner {private:int inMem;public:void func(Outer& o) {// 可以访问外部类的静态成员cout << outStatic; // 可以通过外部类对象访问非静态成员cout << o.outMem; }};
};
int Outer::outStatic = 10;int main()
{Outer::Inner inner; // 创建内部类对象Outer outer;inner.func(outer);return 0;
}
3. 应用场景
内部类只被外部类使用,避免污染全局命名空间(如链表的Node作为List的内部类)
实现 “私有” 类,隐藏实现细节
六、匿名对象:用完即毁的 “临时对象”
没有名字的对象称为匿名对象,生命周期仅在当前行。
1. 基本用法
class Printer
{
public:void print() { cout << "打印中..."; }
};int main()
{// 匿名对象:直接调用构造函数,没有名字Printer().print(); // 输出"打印中...",语句结束后对象销毁return 0;
}
2. 应用场景
临时需要一个对象调用某个方法,无需复用
作为函数参数传递(减少一次拷贝):
void func(Printer p)
{ p.print();
}
func(Printer()); // 直接传匿名对象,比先创建命名对象更高效
七、对象拷贝时的编译器优化
编译器会在特定场景下对对象拷贝进行优化,C++ 标准允许的一种 “按需省略” 对象拷贝 / 移动构造的优化机制,其核心目的是通过减少不必要的临时对象创建和销毁,这种优化不依赖于具体编译器实现,编译器也可以合法地省略这些操作。减少不必要的临时对象创建,提升程序运行效率。
1. 场景 1:返回值优化
当函数返回一个临时对象时,编译器可能直接在目标位置构造对象,跳过拷贝:
class MyClass
{
public:MyClass() { cout << "构造"; }MyClass(const MyClass&) { cout << "拷贝构造"; }
};MyClass createObj()
{return MyClass(); // 返回临时对象
}
int main()
{MyClass obj = createObj(); // 实际输出:"构造"(编译器优化,跳过了拷贝)return 0;
}
2. 场景 2:连续构造 + 拷贝优化
当用匿名对象初始化新对象时,编译器会合并为一次构造:
MyClass obj = MyClass();
// 输出:"构造"(而非"构造+拷贝构造")
3、优化的限制与注意事项
- 不影响代码逻辑的正确性即使编译器省略了拷贝 / 移动构造,代码的逻辑行为仍需符合 “假设拷贝发生” 的预期。例如,不能依赖拷贝构造函数的副作用(如计数、日志)来控制程序流程,因为这些副作用可能被省略。
- 与移动语义的关系当优化条件不满足时(如返回值类型与局部对象类型不同),编译器会优先尝试移动构造(而非拷贝构造),移动语义是优化的 “备选方案”,而优化是比移动更彻底的效率提升。
总结
以上这些知识点是 C++ 面向对象编程的重要补充:
初始化列表是成员初始化的 “正规军”,尤其对 const、引用等必须使用
类型转换需注意隐式转换的风险,合理使用explicit和命名转换
static 成员适合实现类级别的共享功能
友元是封装的 “例外通道”,需谨慎使用
内部类用于隐藏实现细节,减少命名污染
匿名对象和编译器优化能让代码更简洁高效
建议结合实际代码练习,逐步体会这些特性的用法和优势,才能真正掌握 C++ 的精髓。
感谢您的阅读,如果本篇文章对您有帮助,希望能给博主"一键三连"哦!
C++类(上)默认构造和运算符重载
力扣(LeetCode) ——611. 有效三角形的个数(C++)