杰瑞科技汇

c 面向对象程序设计教程

C++ 面向对象程序设计教程

目录

  1. 引言:为什么是 C++ 和面向对象?
  2. 第一部分:C++ 基础回顾
    • 1 第一个 C++ 程序
    • 2 变量、数据类型与常量
    • 3 运算符与表达式
    • 4 流程控制
    • 5 函数
    • 6 指针与引用
  3. 第二部分:面向对象编程核心概念
    • 1 结构体 vs. 类
    • 2 封装
    • 3 访问修饰符:public, private, protected
    • 4 方法与成员函数
    • 5 构造函数与析构函数
    • 6 this 指针
  4. 第三部分:面向对象三大特性
    • 1 封装(深入)
    • 2 继承
      • 2.1 基类与派生类
      • 2.2 访问控制与继承方式
      • 2.3 构造函数与析构函数的执行顺序
    • 3 多态
      • 3.1 虚函数
      • 3.2 纯虚函数与抽象类
      • 3.3 override 关键字
  5. 第四部分:进阶 OOP 与 C++ 特性
    • 1 运算符重载
    • 2 友元函数与友元类
    • 3 静态成员
    • 4 单继承与多继承
    • 5 虚继承(解决菱形继承问题)
  6. 第五部分:现代 C++ 与 OOP
    • 1 C++11/14/17 新特性对 OOP 的增强
    • 2 final 关键字
    • 3 委托构造函数
  7. 第六部分:综合实例

    1 设计一个简单的银行账户系统

  8. 总结与学习建议

引言:为什么是 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 语言的几乎所有运算符,并增加了 newdelete 用于动态内存管理。

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++ 中,structclass 的主要区别在于默认的访问权限:

  • 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 封装(深入)

封装不仅仅是 privatepublic,它还意味着通过公共的接口(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 构造函数与析构函数的执行顺序

创建派生类对象时:

  1. 调用基类的构造函数。
  2. 调用派生类的构造函数。

销毁派生类对象时:

  1. 调用派生类的析构函数。
  2. 调用基类的析构函数。

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 友元函数与友元类

友元函数或友元类可以访问一个类的 privateprotected 成员,这破坏了封装性,应谨慎使用。

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++ 的面向对象功能非常强大但也非常复杂,学习路径建议如下:

  1. 打好基础:确保你对 C++ 的基本语法(变量、循环、函数、指针)有扎实的理解。
  2. 掌握核心:花大量时间理解封装、继承、多态这三大特性,多态是 OOP 的精髓,务必通过虚函数和抽象类彻底搞懂。
  3. 动手实践:不要只看代码,一定要自己动手编写,从设计简单的类开始,如 Student, Book, Date 等,然后逐步尝试使用继承和多态。
  4. 学习现代 C++:熟悉 C++11 及以后的新标准,它们能让你的代码更安全、更简洁、更高效。
  5. 阅读优秀代码:阅读一些开源项目的源码(如 Google Test,fmt 等),学习大师们是如何运用 OOP 设计模式的。
  6. 理解设计模式:在掌握 OOP 基础后,学习一些经典的设计模式(如单例、工厂、观察者等),这是将 OOP 思想应用到实际项目中的桥梁。

祝你学习顺利!

分享:
扫描分享到社交APP
上一篇
下一篇