1. 类和对象核心概念回顾
在C++编程中,类和对象是最基础也是最重要的概念之一。类可以看作是一个自定义的数据类型,它封装了数据(成员变量)和操作这些数据的方法(成员函数)。而对象则是类的具体实例,就像根据设计图纸建造出来的房子。
提示:理解类和对象的关系,就像理解"设计图"和"实物"的关系。类定义了结构,对象是具体的实现。
在之前的文章中,我们已经探讨了类的基本定义、构造函数、析构函数等基础内容。现在,让我们深入探讨类和对象的高级特性,为这个系列画上圆满的句号。
2. 类的静态成员
2.1 静态成员变量
静态成员变量是属于类本身的,而不是属于某个特定对象的。这意味着所有该类的对象共享同一个静态成员变量。
cpp复制class Counter {
public:
static int count; // 声明静态成员变量
Counter() { count++; }
~Counter() { count--; }
};
int Counter::count = 0; // 定义并初始化静态成员变量
在这个例子中,无论创建多少个Counter对象,它们都共享同一个count变量。每次创建对象时count增加,销毁对象时count减少。
注意:静态成员变量必须在类外进行定义和初始化,否则会导致链接错误。
2.2 静态成员函数
静态成员函数也是属于类而非对象的,它只能访问静态成员变量,不能访问非静态成员变量。
cpp复制class MathUtils {
public:
static double square(double x) {
return x * x;
}
};
// 调用方式
double result = MathUtils::square(5.0);
静态成员函数常用于工具类中,它们不需要对象实例就能调用,非常方便。
3. 友元函数和友元类
3.1 友元函数
友元函数不是类的成员函数,但它可以访问类的私有成员。这在某些特定场景下非常有用。
cpp复制class Box {
private:
double width;
public:
friend void printWidth(Box box); // 声明友元函数
void setWidth(double wid);
};
// 友元函数的实现
void printWidth(Box box) {
cout << "Width of box: " << box.width << endl;
}
3.2 友元类
一个类可以声明另一个类为它的友元类,这样友元类的所有成员函数都可以访问该类的私有成员。
cpp复制class A {
private:
int secret;
public:
friend class B; // 声明B为友元类
};
class B {
public:
void showSecret(A a) {
cout << "A's secret is: " << a.secret << endl;
}
};
注意事项:友元关系破坏了封装性,应谨慎使用。只有在确实需要让外部函数或类访问私有成员时才使用。
4. 运算符重载
4.1 基本概念
运算符重载允许我们为自定义类型定义运算符的行为。这使得代码更加直观和易读。
cpp复制class Complex {
private:
double real, imag;
public:
Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) {}
// 重载+运算符
Complex operator+(const Complex& rhs) {
return Complex(real + rhs.real, imag + rhs.imag);
}
};
4.2 常用运算符重载示例
cpp复制// 重载<<运算符用于输出
ostream& operator<<(ostream& os, const Complex& c) {
os << "(" << c.real << ", " << c.imag << "i)";
return os;
}
// 重载==运算符用于比较
bool operator==(const Complex& lhs, const Complex& rhs) {
return lhs.real == rhs.real && lhs.imag == rhs.imag;
}
实操心得:重载运算符时,应保持运算符的原始语义。例如,+运算符应该执行加法操作,而不是减法或其他操作。
5. 继承与多态
5.1 继承基础
继承是面向对象编程的三大特性之一(封装、继承、多态),它允许我们基于现有类创建新类。
cpp复制class Shape {
protected:
int width, height;
public:
void setWidth(int w) { width = w; }
void setHeight(int h) { height = h; }
};
class Rectangle: public Shape {
public:
int getArea() { return width * height; }
};
5.2 多态与虚函数
多态允许我们通过基类指针或引用来调用派生类的函数,这是通过虚函数实现的。
cpp复制class Shape {
public:
virtual double area() = 0; // 纯虚函数
};
class Circle : public Shape {
private:
double radius;
public:
Circle(double r) : radius(r) {}
double area() override { return 3.14159 * radius * radius; }
};
class Square : public Shape {
private:
double side;
public:
Square(double s) : side(s) {}
double area() override { return side * side; }
};
注意事项:含有纯虚函数的类称为抽象类,不能实例化。派生类必须实现所有纯虚函数才能实例化。
6. 类的其他高级特性
6.1 拷贝构造函数与赋值运算符
拷贝构造函数和赋值运算符控制着对象的拷贝行为。如果不定义,编译器会生成默认版本。
cpp复制class MyString {
private:
char* str;
public:
// 构造函数
MyString(const char* s = "") {
str = new char[strlen(s) + 1];
strcpy(str, s);
}
// 拷贝构造函数
MyString(const MyString& other) {
str = new char[strlen(other.str) + 1];
strcpy(str, other.str);
}
// 赋值运算符
MyString& operator=(const MyString& rhs) {
if (this != &rhs) {
delete[] str;
str = new char[strlen(rhs.str) + 1];
strcpy(str, rhs.str);
}
return *this;
}
~MyString() { delete[] str; }
};
6.2 移动语义与右值引用
C++11引入了移动语义,可以避免不必要的拷贝,提高性能。
cpp复制class MyString {
// ... 其他成员同上 ...
// 移动构造函数
MyString(MyString&& other) noexcept : str(other.str) {
other.str = nullptr;
}
// 移动赋值运算符
MyString& operator=(MyString&& rhs) noexcept {
if (this != &rhs) {
delete[] str;
str = rhs.str;
rhs.str = nullptr;
}
return *this;
}
};
7. 类设计的最佳实践
7.1 五大原则(SOLID)
- 单一职责原则(SRP):一个类应该只有一个引起它变化的原因。
- 开放封闭原则(OCP):软件实体应该对扩展开放,对修改关闭。
- 里氏替换原则(LSP):派生类必须能够替换它们的基类。
- 接口隔离原则(ISP):不应该强迫客户端依赖它们不使用的接口。
- 依赖倒置原则(DIP):高层模块不应该依赖低层模块,两者都应该依赖抽象。
7.2 其他设计建议
- 优先使用组合而非继承
- 保持类的小而专注
- 最小化类的公开接口
- 避免"全能类"(God Class)
- 合理使用const成员函数
8. 常见问题与解决方案
8.1 对象切片问题
当派生类对象被赋值给基类对象时,会发生对象切片,派生类特有的部分会被"切掉"。
cpp复制class Base { /*...*/ };
class Derived : public Base { /*...*/ };
Derived d;
Base b = d; // 对象切片发生
解决方案:使用指针或引用,或者实现克隆模式。
8.2 菱形继承问题
多重继承可能导致同一个基类被继承多次。
cpp复制class A {};
class B : public A {};
class C : public A {};
class D : public B, public C {}; // A被继承两次
解决方案:使用虚继承。
cpp复制class B : virtual public A {};
class C : virtual public A {};
class D : public B, public C {}; // A现在只被继承一次
8.3 虚析构函数问题
如果基类的析构函数不是虚的,通过基类指针删除派生类对象会导致未定义行为。
cpp复制class Base {
public:
~Base() {} // 非虚析构函数
};
class Derived : public Base {
public:
~Derived() {}
};
Base* ptr = new Derived();
delete ptr; // 未定义行为,Derived的析构函数不会被调用
解决方案:总是为基类声明虚析构函数。
cpp复制virtual ~Base() {}
9. 性能优化技巧
9.1 内联函数
对于小型、频繁调用的成员函数,可以声明为内联函数以减少函数调用开销。
cpp复制class Point {
private:
int x, y;
public:
inline int getX() const { return x; }
inline int getY() const { return y; }
};
9.2 返回值优化(RVO)
现代编译器可以优化某些情况下的临时对象构造。
cpp复制Matrix operator+(const Matrix& a, const Matrix& b) {
Matrix result; // 可能被RVO优化掉
// ... 计算 ...
return result;
}
9.3 小对象优化
对于小型对象,可以考虑直接在栈上分配,避免堆分配的开销。
10. 现代C++中的类特性
10.1 默认和删除的函数
C++11允许显式指定使用默认实现或删除函数。
cpp复制class NonCopyable {
public:
NonCopyable() = default;
NonCopyable(const NonCopyable&) = delete;
NonCopyable& operator=(const NonCopyable&) = delete;
};
10.2 override和final关键字
override确保函数确实重写了基类的虚函数,final防止函数被进一步重写。
cpp复制class Base {
public:
virtual void foo() {}
};
class Derived : public Base {
public:
void foo() override {} // 明确表示重写
};
class FinalDerived : public Derived {
public:
void foo() final {} // 不能再被重写
};
10.3 委托构造函数
构造函数可以调用同一个类的其他构造函数。
cpp复制class MyClass {
int a, b;
public:
MyClass(int x) : a(x), b(0) {}
MyClass() : MyClass(0) {} // 委托给上面的构造函数
};
在实际项目中,我发现合理使用现代C++特性可以显著提高代码的可读性和安全性。特别是override和final关键字,它们能在编译期捕获许多潜在的错误,而不是等到运行时才发现问题。