1. 类与对象进阶概念解析
在掌握了C++类与对象的基础语法后,我们需要深入探讨面向对象编程中更高级的特性。构造函数和析构函数是类设计中最重要的成员函数之一,它们分别负责对象的初始化和清理工作。
构造函数的特点是:
- 与类同名
- 没有返回类型
- 可以重载
- 创建对象时自动调用
一个典型的构造函数实现示例:
cpp复制class Student {
public:
// 默认构造函数
Student() {
name = "Unknown";
age = 0;
}
// 带参数的构造函数
Student(string n, int a) {
name = n;
age = a;
}
private:
string name;
int age;
};
注意:如果类中没有定义任何构造函数,编译器会自动生成一个默认构造函数。但一旦定义了任何构造函数,编译器就不会再生成默认构造函数。
1.1 拷贝构造函数详解
拷贝构造函数是一种特殊的构造函数,它使用同类对象来初始化新创建的对象。其函数原型通常为:ClassName(const ClassName &obj)
拷贝构造函数的典型应用场景包括:
- 通过另一个对象初始化新对象
- 对象作为函数参数传递
- 对象作为函数返回值
实现示例:
cpp复制class Point {
public:
Point(int x, int y) : x(x), y(y) {}
// 拷贝构造函数
Point(const Point &p) {
x = p.x;
y = p.y;
cout << "拷贝构造函数被调用" << endl;
}
private:
int x, y;
};
提示:现代C++中,移动语义可以优化不必要的拷贝操作,但在理解拷贝构造函数的基础上学习移动语义会更容易。
2. 对象成员与静态成员
2.1 对象成员初始化
当一个类的成员是另一个类的对象时,我们需要特别注意初始化顺序。对象成员的初始化发生在包含它的类构造函数体执行之前。
cpp复制class Engine {
public:
Engine(int p) : power(p) {}
private:
int power;
};
class Car {
public:
Car(int e, int w) : engine(e), wheels(w) {}
private:
Engine engine;
int wheels;
};
在这个例子中,Engine的构造函数会在Car的构造函数体执行前被调用。
2.2 静态成员特性
静态成员属于类而不是对象,所有对象共享同一份静态成员。静态成员变量需要在类外单独定义和初始化。
cpp复制class Counter {
public:
Counter() { count++; }
static int getCount() { return count; }
private:
static int count; // 声明
};
int Counter::count = 0; // 定义并初始化
静态成员函数的特点:
- 只能访问静态成员
- 没有this指针
- 可以通过类名直接调用
3. 友元与运算符重载
3.1 友元函数和友元类
友元打破了封装性,但提供了必要的灵活性。友元可以访问类的私有成员。
cpp复制class Box {
friend void printWidth(Box box);
friend class BoxPrinter;
private:
double width;
public:
Box(double w) : width(w) {}
};
void printWidth(Box box) {
cout << "Width: " << box.width << endl;
}
class BoxPrinter {
public:
void print(Box box) {
cout << "Box width: " << box.width << endl;
}
};
注意事项:过度使用友元会破坏封装性,应谨慎使用。通常只在需要两个类紧密协作时才考虑使用友元。
3.2 运算符重载实践
运算符重载使得我们可以为自定义类型定义运算符的行为。重载的运算符本质上是特殊命名的函数。
cpp复制class Complex {
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);
}
// 重载<<运算符(通常声明为友元)
friend ostream& operator<<(ostream& os, const Complex& c);
private:
double real, imag;
};
ostream& operator<<(ostream& os, const Complex& c) {
os << "(" << c.real << ", " << c.imag << ")";
return os;
}
可重载的运算符包括:
- 算术运算符:+ - * / %
- 关系运算符:== != > < >= <=
- 逻辑运算符:&& || !
- 赋值运算符:= += -= 等
- 下标运算符:[]
- 函数调用运算符:()
- 指针运算符:-> *
- 自增自减:++ --
不可重载的运算符:
- 成员访问运算符:.
- 成员指针运算符:.*
- 作用域解析运算符:::
- 条件运算符:?:
- sizeof运算符
4. 对象数组与this指针
4.1 对象数组的创建与使用
对象数组的每个元素都会调用构造函数。如果没有提供初始化列表,每个元素都会调用默认构造函数。
cpp复制class Point {
public:
Point() { cout << "默认构造函数" << endl; }
Point(int x, int y) : x(x), y(y) {
cout << "带参构造函数" << endl;
}
private:
int x, y;
};
// 创建对象数组
Point points1[3]; // 调用3次默认构造函数
Point points2[3] = {Point(1,2), Point(3,4)}; // 前两个调用带参构造,第三个调用默认构造
4.2 this指针的本质
this指针是一个隐含的指针,指向当前对象实例。它在以下场景特别有用:
- 区分成员变量和局部变量
- 从成员函数返回当前对象
- 在成员函数中传递当前对象
cpp复制class Test {
int x;
public:
Test(int x) {
this->x = x; // 使用this区分同名变量
}
Test& setX(int x) {
this->x = x;
return *this; // 返回当前对象引用
}
};
5. 常对象与成员函数
5.1 常对象的使用
常对象的数据成员在对象生命周期内不能被修改。常对象只能调用常成员函数。
cpp复制class Date {
public:
Date(int y, int m, int d) : year(y), month(m), day(d) {}
void display() const; // 常成员函数
void setYear(int y) { year = y; }
private:
int year, month, day;
};
void Date::display() const {
cout << year << "-" << month << "-" << day << endl;
// year = 2023; // 错误:不能在常成员函数中修改成员
}
int main() {
const Date d1(2020, 1, 1); // 常对象
d1.display(); // 正确
// d1.setYear(2023); // 错误:常对象不能调用非常成员函数
}
5.2 常成员函数的特点
常成员函数承诺不会修改对象的状态。声明方式是在函数参数列表后加const关键字。
常成员函数的特点:
- 可以访问所有成员变量,但不能修改非mutable成员
- 可以被常对象和非常对象调用
- 不能调用非常成员函数
- 可以与同名非常成员函数构成重载
cpp复制class Array {
public:
int get(int i) const { return data[i]; } // 常成员函数
int& get(int i) { return data[i]; } // 非常成员函数
private:
int data[10];
};
在实际项目中,合理使用const可以提高代码的安全性和可读性。我个人的经验是:对于不修改对象状态的成员函数,都应该声明为const。这样可以让代码的意图更清晰,也能避免一些潜在的错误。
