C++ 面向对象程序设计教程
目录
- 引言:为什么是 C++ 和面向对象?
- 第一部分:C++ 基础回顾
- 1 第一个 C++ 程序
- 2 变量、数据类型与常量
- 3 运算符与表达式
- 4 流程控制
- 5 函数
- 6 指针与引用
- 第二部分:面向对象编程核心概念
- 1 结构体 vs. 类
- 2 封装
- 3 访问修饰符:
public,private,protected - 4 方法与成员函数
- 5 构造函数与析构函数
- 6
this指针
- 第三部分:面向对象三大特性
- 1 封装(深入)
- 2 继承
- 2.1 基类与派生类
- 2.2 访问控制与继承方式
- 2.3 构造函数与析构函数的执行顺序
- 3 多态
- 3.1 虚函数
- 3.2 纯虚函数与抽象类
- 3.3
override关键字
- 第四部分:进阶 OOP 与 C++ 特性
- 1 运算符重载
- 2 友元函数与友元类
- 3 静态成员
- 4 单继承与多继承
- 5 虚继承(解决菱形继承问题)
- 第五部分:现代 C++ 与 OOP
- 1 C++11/14/17 新特性对 OOP 的增强
- 2
final关键字 - 3 委托构造函数
- 第六部分:综合实例
1 设计一个简单的银行账户系统
- 总结与学习建议
引言:为什么是 C++ 和面向对象?
C++ 是一种静态类型的、编译式的、通用的、大小写敏感的、不规则的编程语言,支持过程化编程、面向对象编程和泛型编程,它继承了 C 语言的强大性能,并在此基础上增加了面向对象的特性,使其成为系统软件、游戏开发、高性能服务器等领域的首选语言。
面向对象编程 是一种编程范式,它将数据和处理数据的方法组织成“对象”,相比面向过程的编程(如 C 语言),OOP 提供了以下优势:
- 模块化:程序被分解为独立的对象,易于理解和维护。
- 封装:隐藏对象的内部实现细节,只暴露必要的接口,提高了安全性。
- 代码重用:通过继承,可以创建新类来扩展现有类的功能,避免了重复代码。
- 灵活性:通过多态,可以用统一的方式处理不同类型的对象,提高了代码的扩展性。
第一部分:C++ 基础回顾
这部分假设你已经了解 C 语言的基础知识,我们将快速过一遍 C++ 的核心语法。
1 第一个 C++ 程序
#include <iostream> // 标准输入输出流库
int main() {
std::cout << "Hello, World!" << std::endl; // 输出字符串到控制台
return 0; // 程序正常结束
}
#include <iostream>:包含头文件,以便使用std::cout(标准输出) 和std::endl(换行并刷新缓冲区)。int main():程序的入口点。std::cout:与 C 语言的printf类似,用于输出。std::endl:相当于\n,但会强制刷新输出流,确保内容立即显示。
2 变量、数据类型与常量
int age = 25; double price = 99.99; char grade = 'A'; bool isStudent = true; const double PI = 3.14159; // const 关键字定义常量
3 运算符与表达式
C++ 继承了 C 语言的几乎所有运算符,并增加了 new 和 delete 用于动态内存管理。
4 流程控制
if-else, switch, for, while, do-while 等与 C 语言基本相同。
5 函数
// 函数声明
int add(int a, int b);
// 函数定义
int add(int a, int b) {
return a + b;
}
6 指针与引用
- 指针:存储一个变量的内存地址。
int var = 10; int* ptr = &var; // ptr 指向 var 的地址 *ptr = 20; // 通过指针修改 var 的值
- 引用:变量的别名,必须在定义时初始化,之后不能再引用其他变量。
int var = 10; int& ref = var; // ref 是 var 的别名 ref = 20; // 修改 ref 就是修改 var
第二部分:面向对象编程核心概念
1 结构体 vs. 类
在 C++ 中,struct 和 class 的主要区别在于默认的访问权限:
struct:默认成员是public的。class:默认成员是private的。
功能上,它们几乎可以互换,在 OOP 中,我们通常使用 class 来定义对象。
2 封装
封装是 OOP 的基石,它将数据(成员变量)和操作数据的方法(成员函数)捆绑在一起,并对外部隐藏对象的内部状态。
3 访问修饰符
public:可以被任何外部代码访问。private:只能被该类的成员函数和友元访问。protected:可以被该类的成员函数、友元以及其派生类的成员函数访问。
4 方法与成员函数
类中的函数称为成员函数或方法。
class Car {
public:
// 成员函数
void start() {
std::cout << "Car started." << std::endl;
}
};
5 构造函数与析构函数
- 构造函数:在创建对象时自动调用的特殊函数,用于初始化成员变量,没有返回类型,名称与类名相同。
class Car { public: Car() { // 默认构造函数 std::cout << "Car object created." << std::endl; } }; - 析构函数:在对象生命周期结束时(如离开作用域)自动调用的特殊函数,用于释放资源,名称为
~ClassName(),没有参数和返回值。class Car { public: ~Car() { // 析构函数 std::cout << "Car object destroyed." << std::endl; } };
6 this 指针
this 是一个特殊的指针,它指向当前对象本身,当成员函数需要访问当前对象的成员变量或调用其他成员函数时,可以使用 this。
class MyClass {
public:
void setData(int value) {
this->data = value; // this->data 是成员变量,data 是参数
}
private:
int data;
};
第三部分:面向对象三大特性
1 封装(深入)
封装不仅仅是 private 和 public,它还意味着通过公共的接口(public 方法)来间接操作私有数据,而不是直接暴露数据。
优点:
- 数据安全:防止外部代码随意修改对象内部状态。
- 易于维护:内部实现可以改变,只要公共接口不变,使用该类的代码就不需要修改。
2 继承
继承允许我们创建一个新类(派生类/子类),它继承一个已有类(基类/父类)的属性和方法,这实现了代码的重用和层次化建模。
2.1 基类与派生类
// 基类
class Animal {
public:
void eat() {
std::cout << "This animal is eating." << std::endl;
}
};
// 派生类
class Dog : public Animal { // Dog 公有继承自 Animal
public:
void bark() {
std::cout << "Woof! Woof!" << std::endl;
}
};
int main() {
Dog myDog;
myDog.eat(); // 继承自 Animal 的方法
myDog.bark(); // Dog 自己的方法
return 0;
}
2.2 访问控制与继承方式
| 继承方式 | 基类 public 成员 |
基类 protected 成员 |
基类 private 成员 |
|---|---|---|---|
public |
public |
protected |
不可访问 |
protected |
protected |
protected |
不可访问 |
private |
private |
private |
不可访问 |
2.3 构造函数与析构函数的执行顺序
创建派生类对象时:
- 调用基类的构造函数。
- 调用派生类的构造函数。
销毁派生类对象时:
- 调用派生类的析构函数。
- 调用基类的析构函数。
3 多态
多态意味着“多种形态”,在 C++ 中,它允许我们使用一个基类的指针或引用来调用派生类的重写方法。
3.1 虚函数
要实现多态,基类中的函数必须被声明为 virtual。
class Shape {
public:
// 虚函数
virtual void draw() {
std::cout << "Drawing a generic shape." << std::endl;
}
virtual ~Shape() {} // 虚析构函数,确保派生类析构函数能被正确调用
};
class Circle : public Shape {
public:
// 重写虚函数
void draw() override { // C++11 推荐使用 override 关键字
std::cout << "Drawing a circle." << std::endl;
}
};
int main() {
Shape* shape1 = new Circle();
shape1->draw(); // 输出 "Drawing a circle.",多态发生!
delete shape1; // 正确调用 Circle 的析构函数,然后是 Shape 的
return 0;
}
关键点:
- 当通过基类指针调用一个虚函数时,程序会根据指针实际指向的对象类型来决定调用哪个版本的函数,而不是根据指针的类型。
- 这被称为动态绑定或晚期绑定。
3.2 纯虚函数与抽象类
如果一个类中至少有一个纯虚函数,那么这个类就称为抽象类,抽象类不能被实例化(不能创建对象),只能作为基类被继承。
class Shape {
public:
// 纯虚函数,用 = 0 表示
virtual void draw() = 0;
// ... 其他成员
};
// 以下代码会编译错误!
// Shape s; // 错误:抽象类不能实例化
class Circle : public Shape {
public:
void draw() override { // 必须实现所有纯虚函数,否则 Circle 也成为抽象类
std::cout << "Drawing a circle." << std::endl;
}
};
3.3 override 关键字
C++11 引入了 override 关键字,将它放在派生类的成员函数声明后,可以明确表示该函数旨在重写基类的虚函数。
- 优点:如果基类没有对应的虚函数,编译器会报错,避免了因拼写错误或签名不匹配导致的多态失效。
第四部分:进阶 OOP 与 C++ 特性
1 运算符重载
允许你为自定义类型(类)重新定义 C++ 内置运算符的行为。
class Complex {
public:
Complex(double r, double i) : real(r), imag(i) {}
// 重载 + 运算符
Complex operator+(const Complex& other) const {
return Complex(this->real + other.real, this->imag + other.imag);
}
void print() {
std::cout << real << " + " << imag << "i" << std::endl;
}
private:
double real, imag;
};
int main() {
Complex c1(3.0, 4.0);
Complex c2(1.0, 2.0);
Complex c3 = c1 + c2; // 调用 operator+
c3.print(); // 输出 4 + 6i
return 0;
}
2 友元函数与友元类
友元函数或友元类可以访问一个类的 private 和 protected 成员,这破坏了封装性,应谨慎使用。
class MyClass {
friend void friendFunction(MyClass& obj); // 声明友元函数
private:
int secret;
};
// 友元函数的定义
void friendFunction(MyClass& obj) {
obj.secret = 100; // 可以访问私有成员
}
3 静态成员
使用 static 关键字声明的成员属于类本身,而不是类的某个特定实例。
- 静态成员变量:所有对象共享同一份拷贝,必须在类外进行初始化。
- 静态成员函数:没有
this指针,只能访问静态成员变量。
class Counter {
public:
static int count; // 静态成员变量声明
void increment() {
count++;
}
};
int Counter::count = 0; // 静态成员变量定义和初始化
int main() {
Counter c1, c2;
c1.increment();
c2.increment();
std::cout << "Total count: " << Counter::count << std::endl; // 输出 2
return 0;
}
4 单继承与多继承
- 单继承:一个派生类只有一个直接基类,这是最常见的继承形式。
- 多继承:一个派生类有多个直接基类。
// 多继承示例
class A { public: void a() {} };
class B { public: void b() {} };
class C : public A, public B { public: void c() {} };
int main() {
C obj;
obj.a();
obj.b();
obj.c();
return 0;
}
5 虚继承(解决菱形继承问题)
当多继承形成“菱形”结构时,会导致数据冗余和访问歧义,虚继承可以解决这个问题。
A
/ \
B C
\ /
D
class A { public: int data; };
class B : virtual public A {}; // 虚继承
class C : virtual public A {}; // 虚继承
class D : public B, public C {};
int main() {
D obj;
obj.data = 10; // 不会产生歧义,A 的 data 在 D 中只有一份拷贝
return 0;
}
第五部分:现代 C++ 与 OOP
1 C++11/14/17 新特性对 OOP 的增强
auto关键字:简化变量声明,特别是在处理迭代器或复杂类型时。- 范围
for循环:更简洁地遍历容器。 - Lambda 表达式:可以定义匿名的、内联的函数对象,常用于 STL 算法。
- 智能指针 (
std::unique_ptr,std::shared_ptr):自动管理内存,极大减少了手动new/delete带来的内存泄漏风险。
2 final 关键字
防止类被继承或防止虚函数被进一步重写。
class Base {
public:
virtual void foo() final; // foo 不能被重写
};
class Derived : public Base {
// void foo() override; // 错误!不能重写 final 函数
};
class AnotherDerived final : public Base { // AnotherDerived 不能被继承
// ...
};
3 委托构造函数
一个构造函数可以调用同一个类的其他构造函数来初始化对象,避免代码重复。
class MyClass {
public:
int x, y;
MyClass(int x_val) : x(x_val), y(0) {} // 构造函数 1
MyClass(int x_val, int y_val) : MyClass(x_val) { // 构造函数 2 委托给构造函数 1
this->y = y_val;
}
};
第六部分:综合实例
设计一个简单的银行账户系统
#include <iostream>
#include <string>
// 抽象基类:账户
class Account {
protected:
std::string accountNumber;
std::string ownerName;
double balance;
public:
Account(const std::string& num, const std::string& name, double initialBalance)
: accountNumber(num), ownerName(name), balance(initialBalance) {
std::cout << "Account " << accountNumber << " created for " << ownerName << "." << std::endl;
}
virtual ~Account() {
std::cout << "Account " << accountNumber << " destroyed." << std::endl;
}
// 纯虚函数,使得 Account 成为抽象类
virtual void deposit(double amount) = 0;
virtual void withdraw(double amount) = 0;
virtual void display() const {
std::cout << "Account Number: " << accountNumber << std::endl;
std::cout << "Owner: " << ownerName << std::endl;
std::cout << "Balance: $" << balance << std::endl;
}
};
// 派生类:储蓄账户
class SavingsAccount : public Account {
private:
double interestRate;
public:
SavingsAccount(const std::string& num, const std::string& name, double initialBalance, double rate)
: Account(num, name, initialBalance), interestRate(rate) {}
void deposit(double amount) override {
balance += amount;
std::cout << "Deposited $" << amount << " into Savings Account." << std::endl;
}
void withdraw(double amount) override {
if (balance >= amount) {
balance -= amount;
std::cout << "Withdrew $" << amount << " from Savings Account." << std::endl;
} else {
std::cout << "Insufficient funds for withdrawal." << std::endl;
}
}
void addInterest() {
double interest = balance * interestRate;
balance += interest;
std::cout << "Interest of $" << interest << " added." << std::endl;
}
};
// 派生类:支票账户
class CheckingAccount : public Account {
private:
double overdraftLimit;
public:
CheckingAccount(const std::string& num, const std::string& name, double initialBalance, double limit)
: Account(num, name, initialBalance), overdraftLimit(limit) {}
void deposit(double amount) override {
balance += amount;
std::cout << "Deposited $" << amount << " into Checking Account." << std::endl;
}
void withdraw(double amount) override {
if (balance + overdraftLimit >= amount) {
balance -= amount;
std::cout << "Withdrew $" << amount << " from Checking Account." << std::endl;
} else {
std::cout << "Exceeded overdraft limit. Withdrawal failed." << std::endl;
}
}
};
int main() {
// 使用基类指针管理派生类对象,体现多态
Account* accounts[2];
accounts[0] = new SavingsAccount("S123", "Alice", 1000.0, 0.05);
accounts[1] = new CheckingAccount("C456", "Bob", 500.0, 200.0);
for (int i = 0; i < 2; ++i) {
accounts[i]->display();
accounts[i]->deposit(200.0);
accounts[i]->withdraw(150.0);
std::cout << "-------------------" << std::endl;
}
// 调用 SavingsAccount 特有的方法
SavingsAccount* sa = dynamic_cast<SavingsAccount*>(accounts[0]);
if (sa) {
sa->addInterest();
}
accounts[0]->display();
// 释放内存
for (int i = 0; i < 2; ++i) {
delete accounts[i];
}
return 0;
}
总结与学习建议
C++ 的面向对象功能非常强大但也非常复杂,学习路径建议如下:
- 打好基础:确保你对 C++ 的基本语法(变量、循环、函数、指针)有扎实的理解。
- 掌握核心:花大量时间理解封装、继承、多态这三大特性,多态是 OOP 的精髓,务必通过虚函数和抽象类彻底搞懂。
- 动手实践:不要只看代码,一定要自己动手编写,从设计简单的类开始,如
Student,Book,Date等,然后逐步尝试使用继承和多态。 - 学习现代 C++:熟悉 C++11 及以后的新标准,它们能让你的代码更安全、更简洁、更高效。
- 阅读优秀代码:阅读一些开源项目的源码(如 Google Test,fmt 等),学习大师们是如何运用 OOP 设计模式的。
- 理解设计模式:在掌握 OOP 基础后,学习一些经典的设计模式(如单例、工厂、观察者等),这是将 OOP 思想应用到实际项目中的桥梁。
祝你学习顺利!
