C++ 类与对象深入解析
C++ 类与对象基础概念
类的定义与声明
在 C++ 中,类(Class)是一种用户自定义的数据类型,它将数据成员(变量)和成员函数(函数)封装在一起。类的声明语法如下:
class ClassName {
// 访问修饰符
private:
// 私有数据成员和成员函数
int privateData;
void privateFunction();
public:
// 公有数据成员和成员函数
int publicData;
void publicFunction();
protected:
// 受保护数据成员和成员函数
int protectedData;
void protectedFunction();
};
这里,ClassName
是类的名称。类体中使用访问修饰符(private
、public
、protected
)来控制对成员的访问权限。private
成员只能在类的内部被访问,public
成员可以在类的外部被访问,protected
成员在类的内部以及派生类中可以被访问。
对象的创建与初始化
对象是类的实例。一旦定义了类,就可以创建该类的对象。例如:
ClassName obj;
对象在创建时可以进行初始化。对于简单的类,可以在定义对象时直接初始化公有数据成员:
class Point {
public:
int x;
int y;
};
Point p = {10, 20};
对于更复杂的类,特别是包含私有数据成员的类,通常使用构造函数来进行初始化。
构造函数与析构函数
构造函数
构造函数是一种特殊的成员函数,用于在创建对象时初始化对象的成员变量。构造函数的名称与类名相同,没有返回类型(包括 void
)。例如:
class Rectangle {
private:
int width;
int height;
public:
// 构造函数
Rectangle(int w, int h) {
width = w;
height = h;
}
int getArea() {
return width * height;
}
};
可以这样创建 Rectangle
对象:
Rectangle rect(5, 10);
int area = rect.getArea();
构造函数可以有默认参数,这使得在创建对象时可以有更多的灵活性:
class Circle {
private:
double radius;
public:
// 带有默认参数的构造函数
Circle(double r = 1.0) {
radius = r;
}
double getArea() {
return 3.14159 * radius * radius;
}
};
这样就可以创建带有默认半径或者指定半径的 Circle
对象:
Circle c1; // 使用默认半径 1.0
Circle c2(5.0);
析构函数
析构函数与构造函数相反,用于在对象销毁时释放资源。析构函数的名称是在类名前加上波浪线(~
),同样没有返回类型。例如:
class DynamicArray {
private:
int *arr;
int size;
public:
DynamicArray(int s) {
size = s;
arr = new int[size];
}
~DynamicArray() {
delete[] arr;
}
};
在这个例子中,构造函数分配了动态内存,而析构函数释放了这些内存,以防止内存泄漏。
类的访问修饰符
私有成员(private)
私有成员是类中最严格的访问级别。只有类的成员函数可以访问私有成员。例如:
class BankAccount {
private:
double balance;
public:
BankAccount(double initialBalance) {
balance = initialBalance;
}
void deposit(double amount) {
balance += amount;
}
bool withdraw(double amount) {
if (balance >= amount) {
balance -= amount;
return true;
}
return false;
}
double getBalance() {
return balance;
}
};
在 BankAccount
类中,balance
是私有成员。外部代码不能直接访问 balance
,只能通过公有成员函数 deposit
、withdraw
和 getBalance
来间接操作 balance
。这样可以保证数据的安全性和一致性。
公有成员(public)
公有成员可以在类的外部被访问。通常,公有成员函数用于提供对私有数据成员的访问接口,以及执行对象的主要操作。例如,在 BankAccount
类中,deposit
、withdraw
和 getBalance
函数是公有成员,外部代码可以调用这些函数来与 BankAccount
对象进行交互:
BankAccount account(1000.0);
account.deposit(500.0);
bool success = account.withdraw(200.0);
double currentBalance = account.getBalance();
受保护成员(protected)
受保护成员在类的内部以及派生类中可以被访问,但在类的外部不能被访问。受保护成员主要用于继承机制,当一个类派生自另一个类时,派生类可以访问基类的受保护成员。例如:
class Shape {
protected:
int x;
int y;
public:
Shape(int a, int b) : x(a), y(b) {}
};
class Rectangle : public Shape {
private:
int width;
int height;
public:
Rectangle(int a, int b, int w, int h) : Shape(a, b), width(w), height(h) {}
int getArea() {
return width * height;
}
};
在这个例子中,Shape
类的 x
和 y
是受保护成员。Rectangle
类派生自 Shape
类,Rectangle
的成员函数可以访问 Shape
的受保护成员 x
和 y
。
类的继承
继承的概念与语法
继承是面向对象编程的重要特性之一,它允许一个类(派生类)从另一个类(基类)获取属性和行为。通过继承,派生类可以复用基类的代码,并且可以添加新的成员或者重写基类的成员。继承的语法如下:
class BaseClass {
// 基类成员
};
class DerivedClass : accessModifier BaseClass {
// 派生类成员
};
这里,accessModifier
可以是 public
、private
或 protected
,它决定了基类成员在派生类中的访问权限。例如,使用 public
继承:
class Animal {
public:
void eat() {
std::cout << "Animal is eating." << std::endl;
}
};
class Dog : public Animal {
public:
void bark() {
std::cout << "Dog is barking." << std::endl;
}
};
在这个例子中,Dog
类继承自 Animal
类。Dog
类不仅拥有自己的 bark
函数,还继承了 Animal
类的 eat
函数。
Dog myDog;
myDog.eat(); // 调用基类的 eat 函数
myDog.bark(); // 调用派生类的 bark 函数
继承中的访问权限
当使用 public
继承时,基类的 public
成员在派生类中仍然是 public
,基类的 protected
成员在派生类中仍然是 protected
,基类的 private
成员在派生类中不可访问。
使用 private
继承时,基类的 public
和 protected
成员在派生类中都变成 private
,基类的 private
成员在派生类中仍然不可访问。
使用 protected
继承时,基类的 public
和 protected
成员在派生类中都变成 protected
,基类的 private
成员在派生类中仍然不可访问。
例如,对于 private
继承:
class Base {
public:
int publicData;
protected:
int protectedData;
private:
int privateData;
};
class Derived : private Base {
public:
void accessMembers() {
publicData = 10; // 可以访问,因为在派生类中变成 private
protectedData = 20; // 可以访问,因为在派生类中变成 private
// privateData = 30; // 错误,不能访问基类的 private 成员
}
};
多态性
多态性是面向对象编程的另一个重要特性,它允许通过基类的指针或引用调用派生类的函数。C++ 中实现多态性主要通过虚函数和函数重写。
虚函数
虚函数是在基类中声明为 virtual
的成员函数。当派生类重写了虚函数时,通过基类指针或引用调用该函数,会根据对象的实际类型来决定调用哪个版本的函数。例如:
class Shape {
public:
virtual double getArea() {
return 0.0;
}
};
class Circle : public Shape {
private:
double radius;
public:
Circle(double r) : radius(r) {}
double getArea() override {
return 3.14159 * radius * radius;
}
};
class Rectangle : public Shape {
private:
int width;
int height;
public:
Rectangle(int w, int h) : width(w), height(h) {}
double getArea() override {
return width * height;
}
};
这里,Shape
类的 getArea
函数是虚函数。Circle
和 Rectangle
类重写了 getArea
函数。可以通过以下方式实现多态调用:
Shape *shapes[2];
shapes[0] = new Circle(5.0);
shapes[1] = new Rectangle(4, 6);
for (int i = 0; i < 2; ++i) {
std::cout << "Area: " << shapes[i]->getArea() << std::endl;
}
for (int i = 0; i < 2; ++i) {
delete shapes[i];
}
在这个例子中,shapes
数组包含了 Shape
指针,但是实际指向的是 Circle
和 Rectangle
对象。当调用 getArea
函数时,会根据对象的实际类型调用相应的函数版本。
纯虚函数与抽象类
纯虚函数是在声明时赋值为 0
的虚函数。包含纯虚函数的类称为抽象类,抽象类不能直接创建对象。例如:
class AbstractShape {
public:
virtual double getArea() = 0;
};
class Triangle : public AbstractShape {
private:
double base;
double height;
public:
Triangle(double b, double h) : base(b), height(h) {}
double getArea() override {
return 0.5 * base * height;
}
};
在这个例子中,AbstractShape
类是抽象类,因为它包含纯虚函数 getArea
。Triangle
类继承自 AbstractShape
并实现了 getArea
函数,因此可以创建 Triangle
对象。
// AbstractShape absShape; // 错误,不能创建抽象类对象
Triangle tri(4, 5);
double area = tri.getArea();
运算符重载
运算符重载的概念与语法
运算符重载允许为用户定义的类定义运算符的行为。通过运算符重载,可以使类对象像内置数据类型一样使用运算符。运算符重载是通过定义特殊的成员函数来实现的,这些函数的名称以 operator
关键字开头,后面跟着要重载的运算符。例如,为 Complex
类重载 +
运算符:
class Complex {
private:
double real;
double imag;
public:
Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) {}
Complex operator+(const Complex& other) {
return Complex(real + other.real, imag + other.imag);
}
void print() {
std::cout << real << " + " << imag << "i" << std::endl;
}
};
这样就可以使用 +
运算符来相加两个 Complex
对象:
Complex c1(1, 2);
Complex c2(3, 4);
Complex result = c1 + c2;
result.print();
一元运算符重载
一元运算符只对一个操作数进行操作。例如,重载 ++
运算符(前置和后置):
class Counter {
private:
int value;
public:
Counter(int v = 0) : value(v) {}
// 前置 ++
Counter& operator++() {
value++;
return *this;
}
// 后置 ++
Counter operator++(int) {
Counter temp = *this;
value++;
return temp;
}
int getValue() {
return value;
}
};
可以这样使用重载的 ++
运算符:
Counter c(5);
Counter preIncremented = ++c;
Counter postIncremented = c++;
std::cout << "Pre - incremented: " << preIncremented.getValue() << std::endl;
std::cout << "Post - incremented: " << postIncremented.getValue() << std::endl;
二元运算符重载
二元运算符对两个操作数进行操作。除了前面提到的 +
运算符,还可以重载其他二元运算符,如 -
、*
、/
等。例如,为 Complex
类重载 -
运算符:
class Complex {
private:
double real;
double imag;
public:
Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) {}
Complex operator-(const Complex& other) {
return Complex(real - other.real, imag - other.imag);
}
void print() {
std::cout << real << " + " << imag << "i" << std::endl;
}
};
使用 -
运算符:
Complex c1(5, 3);
Complex c2(2, 1);
Complex result = c1 - c2;
result.print();
友元函数与运算符重载
有时候,需要重载一些运算符,使得运算符的左侧操作数不是类对象。例如,重载 <<
运算符用于输出自定义类对象到 std::cout
。这时可以使用友元函数。友元函数不是类的成员函数,但可以访问类的私有成员。例如:
class Point {
private:
int x;
int y;
public:
Point(int a, int b) : x(a), y(b) {}
friend std::ostream& operator<<(std::ostream& os, const Point& p) {
os << "(" << p.x << ", " << p.y << ")";
return os;
}
};
使用 <<
运算符输出 Point
对象:
Point p(10, 20);
std::cout << p << std::endl;
类模板
模板的基本概念
模板是 C++ 中一种强大的代码复用机制,它允许编写通用的代码,而不必为不同的数据类型编写重复的代码。类模板是用于创建类的模板。通过类模板,可以定义一个通用的类,在实例化时指定具体的数据类型。例如,定义一个简单的 Stack
类模板:
template <typename T>
class Stack {
private:
T *data;
int top;
int capacity;
public:
Stack(int cap = 10) : capacity(cap), top(-1) {
data = new T[capacity];
}
~Stack() {
delete[] data;
}
void push(T value) {
if (top == capacity - 1) {
// 处理栈满的情况
}
data[++top] = value;
}
T pop() {
if (top == -1) {
// 处理栈空的情况
}
return data[top--];
}
bool isEmpty() {
return top == -1;
}
};
这里,typename T
表示一个类型参数,在实例化 Stack
类时,需要指定具体的数据类型来替换 T
。
类模板的实例化
可以通过以下方式实例化 Stack
类模板:
Stack<int> intStack(5);
intStack.push(10);
int value = intStack.pop();
Stack<double> doubleStack;
doubleStack.push(3.14);
double dValue = doubleStack.pop();
在这两个例子中,分别实例化了 Stack<int>
和 Stack<double>
,创建了针对 int
和 double
类型的栈。
模板特化
模板特化允许为特定的数据类型提供专门的实现。例如,对于 Stack
类模板,可以为 bool
类型提供一个特化版本,因为 bool
类型的存储和操作可能与其他类型不同:
template <>
class Stack<bool> {
private:
bool *data;
int top;
int capacity;
public:
Stack(int cap = 10) : capacity(cap), top(-1) {
data = new bool[capacity];
}
~Stack() {
delete[] data;
}
void push(bool value) {
if (top == capacity - 1) {
// 处理栈满的情况
}
data[++top] = value;
}
bool pop() {
if (top == -1) {
// 处理栈空的情况
}
return data[top--];
}
bool isEmpty() {
return top == -1;
}
};
这样,当实例化 Stack<bool>
时,就会使用这个特化版本的 Stack
类。
通过深入理解 C++ 的类与对象相关知识,包括类的定义、继承、多态、运算符重载和类模板等,开发者可以编写出更加灵活、高效和可维护的面向对象程序。这些特性使得 C++ 成为一种功能强大的编程语言,适用于各种规模和领域的软件开发项目。无论是系统级编程、游戏开发还是大型企业级应用,C++ 的类与对象机制都能提供坚实的基础。