前言:
在 C++ 面向对象编程中,类与对象是代码组织的核心,但想要真正驾驭它,需要深入理解那些"藏在背后"的进阶特性。从再探构造函数的细节,到对象拷贝时的编译优化,每一个知识点都能让你写出更高效、更优雅的代码。
一、再探构造函数:不止是"初始化"
构造函数是创建对象时自动调用的特殊函数,但它的作用远不止给成员变量赋值:
-
默认构造函数:如果没有显式定义任何构造函数,编译器会自动生成一个空的默认构造函数;但如果定义了带参数的构造函数,默认构造函数会"消失",需要手动声明。
-
拷贝构造函数:用一个已存在的对象初始化新对象时调用,默认的拷贝构造函数会做"浅拷贝"------只复制成员变量的值,对于指针等资源会导致"双重释放"问题,需要手动实现"深拷贝"。
-
移动构造函数(C++11+):当对象是"临时值"(如函数返回的局部对象)时,移动构造函数会"接管"原对象的资源,避免不必要的拷贝,大幅提升效率。
二、类型转换:让对象"灵活变身"
C++ 允许类之间的隐式或显式类型转换,主要通过两种方式实现:
-
构造函数转换:如果一个构造函数只接受一个参数(或除第一个参数外其他参数都有默认值),它可以将该类型的值隐式转换为类对象。
-
类型转换函数:在类中定义一个无参数、无返回值声明(但需要返回目标类型)的函数,格式为 operator 目标类型() ,用于将类对象转换为其他类型。
示例:类型转换函数
class MyInt {
private:
int value;
public:
MyInt(int v) : value(v) {}
// 转换为 int 类型
operator int() const {
return value;
}
};
int main() {
MyInt a(10);
int b = a; // 自动调用 operator int()
cout << b << endl; // 输出 10
return 0;
}
三、static 成员:属于类的"共享资源"
static 关键字可以修饰类的成员变量和成员函数:
-
static 成员变量:不属于任何对象,而是属于整个类,所有对象共享同一份值;需要在类外单独初始化,格式为 类型 类名::成员变量名 = 初始值 。
-
static 成员函数:没有 this 指针,只能访问 static 成员变量和其他 static 成员函数,不能访问非 static 成员;可以通过类名直接调用,无需创建对象。
class Counter {
private:
static int count; // 声明 static 成员变量
public:
Counter() { count++; }
static int getCount() { // static 成员函数
return count;
}
};int Counter::count = 0; // 初始化 static 成员变量
int main() {
Counter a, b, c;
cout << Counter::getCount() << endl; // 输出 3
return 0;
}
四、友元:打破封装的"特殊通道"
五、内部类:类里的"小类"
在一个类的内部定义另一个类,称为内部类(也叫嵌套类):
-
内部类是一个独立的类,不属于外部类的成员,只是作用域被限制在外部类中;
-
内部类可以直接访问外部类的 static 成员,但不能直接访问外部类的非 static 成员,需要通过外部类的对象来访问;
-
外部类不能直接访问内部类的成员,需要创建内部类的对象。
示例:内部类的定义
class Outer {
private:
int outerValue = 10;
public:
class Inner { // 内部类
public:
void showOuter(Outer& o) {
cout << o.outerValue << endl; // 访问外部类的非 static 成员
}
};
};
int main() {
Outer o;
Outer::Inner i;
i.showOuter(o); // 输出 10
return 0;
}
六、匿名对象:"即用即弃"的临时对象
匿名对象是没有名字的对象,通常用于临时调用成员函数或作为函数参数:
-
匿名对象的生命周期只在当前行,执行完当前行后会被销毁;
-
可以简化代码,避免创建不必要的命名对象。
示例:匿名对象的使用
class Printer {
public:
void print(const char* str) {
cout << str << endl;
}
};
int main() {
Printer().print("Hello, Anonymous!"); // 匿名对象调用成员函数
return 0;
}