【C++】零基础入门 · 第 13 节:类与对象基础

前面我们用 struct 定义过结构体,把相关的数据打包在一起。但结构体只能存数据,不能定义行为。(class)是 C++ 面向对象编程的核心------它把数据和操作数据的函数封装在一起,形成一个「对象」。

1. 从结构体到类

1.1 结构体的局限

cpp 复制代码
struct Student {
    string name;
    int age;
    double score;
};

// 使用时,数据和操作是分离的
Student s = {"Alice", 20, 95.5};
cout << s.name << " " << s.score << endl;

// 如果要判断是否及格,需要写一个独立函数
bool isPass(const Student& s) {
    return s.score >= 60;
}

问题是:isPassStudent 没有任何语言层面的关联,代码组织很松散。

1.2 类的定义

类把数据(成员变量)和操作(成员函数)封装在一起:

cpp 复制代码
class Student {
public:
    // 成员变量
    string name;
    int age;
    double score;

    // 成员函数
    bool isPass() const {
        return score >= 60;
    }

    void printInfo() const {
        cout << "姓名:" << name
             << ",年龄:" << age
             << ",成绩:" << score << endl;
    }
};

int main() {
    Student s;
    s.name = "Alice";
    s.age = 20;
    s.score = 95.5;

    s.printInfo();  // 输出:姓名:Alice,年龄:20,成绩:95.5
    cout << (s.isPass() ? "及格" : "不及格") << endl;
}

1.3 class vs struct

在 C++ 中,classstruct 几乎完全一样,唯一的区别是默认访问权限

cpp 复制代码
struct MyStruct {
    int x;  // 默认 public
};

class MyClass {
    int x;  // 默认 private
};

惯例

  • 只有数据、没有行为的简单结构 → 用 struct
  • 有封装、有行为的复杂类型 → 用 class

2. 访问控制

2.1 三种访问权限

cpp 复制代码
class Account {
public:
    // 公有:任何地方都能访问
    string owner;

    void deposit(double amount) {
        balance += amount;
    }

protected:
    // 保护:本类和子类能访问,外部不能
    string accountType;

private:
    // 私有:只有本类的成员函数能访问
    double balance = 0;

    void internalTransfer() {
        // 只有类内部能调用
    }
};
访问权限 类内部 子类 外部
public
protected
private

2.2 为什么要封装?

cpp 复制代码
class BankAccount {
private:
    double balance;  // 私有:防止外部直接修改

public:
    // 通过公有接口操作,可以加入验证逻辑
    void deposit(double amount) {
        if (amount <= 0) {
            cout << "存款金额必须为正数!" << endl;
            return;
        }
        balance += amount;
    }

    bool withdraw(double amount) {
        if (amount > balance) {
            cout << "余额不足!" << endl;
            return false;
        }
        balance -= amount;
        return true;
    }

    double getBalance() const {
        return balance;  // 只读访问
    }
};

如果 balance 是 public 的,外部代码可以直接写 account.balance = -1000000,绕过所有验证。封装就是把数据藏起来,只暴露安全的操作接口

3. 构造函数与析构函数

3.1 构造函数

构造函数在对象创建时自动调用,用于初始化成员变量:

cpp 复制代码
class Student {
private:
    string name;
    int age;
    double score;

public:
    // 构造函数:与类同名,没有返回值
    Student(const string& n, int a, double s) {
        name = n;
        age = a;
        score = s;
    }

    void printInfo() const {
        cout << name << "," << age << " 岁,成绩:" << score << endl;
    }
};

int main() {
    Student s("Alice", 20, 95.5);  // 调用构造函数
    s.printInfo();
}

3.2 初始化列表

更高效的初始化方式是使用初始化列表

cpp 复制代码
class Student {
private:
    string name;
    int age;
    double score;

public:
    // 初始化列表:在构造函数体执行之前就完成初始化
    Student(const string& n, int a, double s)
        : name(n), age(a), score(s) {
        // 构造函数体(可以为空)
    }
};

初始化列表 vs 赋值

  • 初始化列表:直接调用成员的构造函数,一步到位
  • 赋值:先调用默认构造函数创建对象,再赋值,多了一步

对于 stringvector 等类类型,初始化列表效率更高。对于 intdouble 等基本类型,两者没有区别。

3.3 默认构造函数

如果不提供任何参数就能调用的构造函数,就是默认构造函数:

cpp 复制代码
class Point {
private:
    int x, y;

public:
    // 默认构造函数
    Point() : x(0), y(0) {}

    // 带参数的构造函数
    Point(int x, int y) : x(x), y(y) {}
};

Point p1;          // 调用默认构造函数,x=0, y=0
Point p2(3, 4);    // 调用带参数的构造函数

如果你定义了任何构造函数,编译器就不会自动生成默认构造函数。如果你还需要默认构造函数,需要显式定义。

3.4 构造函数重载

一个类可以有多个构造函数(重载):

cpp 复制代码
class Rectangle {
private:
    double width, height;

public:
    // 默认构造:正方形
    Rectangle() : width(1), height(1) {}

    // 带一个参数:正方形
    Rectangle(double size) : width(size), height(size) {}

    // 带两个参数:长方形
    Rectangle(double w, double h) : width(w), height(h) {}

    double area() const {
        return width * height;
    }
};

Rectangle r1;        // 面积 = 1
Rectangle r2(5);     // 面积 = 25(正方形)
Rectangle r3(3, 4);  // 面积 = 12(长方形)

3.5 析构函数

析构函数在对象销毁时自动调用,用于释放资源:

cpp 复制代码
class DynamicArray {
private:
    int* data;
    int size;

public:
    DynamicArray(int n) : size(n) {
        data = new int[n];  // 动态分配内存
        cout << "构造:分配了 " << n << " 个 int 的内存" << endl;
    }

    ~DynamicArray() {  // 析构函数:波浪号 + 类名
        delete[] data;  // 释放内存
        cout << "析构:释放了内存" << endl;
    }

    void set(int index, int value) {
        if (index >= 0 && index < size) {
            data[index] = value;
        }
    }

    int get(int index) const {
        if (index >= 0 && index < size) {
            return data[index];
        }
        return -1;
    }
};

int main() {
    {
        DynamicArray arr(5);  // 调用构造函数
        arr.set(0, 42);
        cout << arr.get(0) << endl;
    }  // arr 离开作用域,自动调用析构函数

    return 0;
}

输出

复制代码
构造:分配了 5 个 int 的内存
42
析构:释放了内存

4. this 指针

每个成员函数都有一个隐含的 this 指针,指向调用该函数的对象:

cpp 复制代码
class Counter {
private:
    int count;

public:
    Counter(int count) : count(count) {}  // 参数名和成员变量同名

    // 当参数名和成员变量同名时,用 this 区分
    void setCount(int count) {
        this->count = count;  // this->count 是成员变量,count 是参数
    }

    Counter& increment() {
        count++;
        return *this;  // 返回自身引用,支持链式调用
    }

    void print() const {
        cout << "count = " << count << endl;
    }
};

int main() {
    Counter c(0);
    c.increment().increment().increment();  // 链式调用
    c.print();  // 输出:count = 3
}

5. 静态成员

5.1 静态成员变量

静态成员变量属于而不是某个对象,所有对象共享同一份:

cpp 复制代码
class Student {
private:
    string name;
    static int totalCount;  // 静态成员变量声明

public:
    Student(const string& n) : name(n) {
        totalCount++;  // 每创建一个对象,计数 +1
    }

    ~Student() {
        totalCount--;
    }

    static int getCount() {  // 静态成员函数
        return totalCount;
    }
};

// 静态成员变量需要在类外定义
int Student::totalCount = 0;

int main() {
    Student s1("Alice");
    Student s2("Bob");
    cout << "学生数量:" << Student::getCount() << endl;  // 输出 2

    {
        Student s3("Charlie");
        cout << "学生数量:" << Student::getCount() << endl;  // 输出 3
    }  // s3 销毁

    cout << "学生数量:" << Student::getCount() << endl;  // 输出 2
}

5.2 静态成员函数

静态成员函数没有 this 指针,只能访问静态成员变量:

cpp 复制代码
class MathUtils {
public:
    static int max(int a, int b) {
        return a > b ? a : b;
    }

    static int min(int a, int b) {
        return a < b ? a : b;
    }
};

// 通过类名直接调用,不需要创建对象
int result = MathUtils::max(10, 20);

6. 友元

有时候你需要让某个外部函数或其他类访问本类的私有成员。friend 关键字就是为此设计的:

cpp 复制代码
class Box {
private:
    double width;

public:
    Box(double w) : width(w) {}

    // 声明友元函数
    friend void printWidth(const Box& b);
};

// 友元函数可以访问私有成员
void printWidth(const Box& b) {
    cout << "宽度:" << b.width << endl;  // ✅ 可以访问 private
}

注意:友元破坏了封装性,应该谨慎使用。

7. 完整示例:简易银行账户

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

class BankAccount {
private:
    string owner;
    double balance;
    static int accountCount;

public:
    // 构造函数
    BankAccount(const string& name, double initial = 0)
        : owner(name), balance(initial) {
        accountCount++;
        cout << "账户创建成功:" << owner << endl;
    }

    // 析构函数
    ~BankAccount() {
        accountCount--;
        cout << "账户注销:" << owner << endl;
    }

    // 存款
    void deposit(double amount) {
        if (amount <= 0) {
            cout << "存款金额必须为正数!" << endl;
            return;
        }
        balance += amount;
        cout << owner << " 存入 " << amount << " 元,余额:" << balance << " 元" << endl;
    }

    // 取款
    bool withdraw(double amount) {
        if (amount <= 0) {
            cout << "取款金额必须为正数!" << endl;
            return false;
        }
        if (amount > balance) {
            cout << "余额不足!当前余额:" << balance << " 元" << endl;
            return false;
        }
        balance -= amount;
        cout << owner << " 取出 " << amount << " 元,余额:" << balance << " 元" << endl;
        return true;
    }

    // 转账
    bool transfer(BankAccount& to, double amount) {
        if (withdraw(amount)) {
            to.deposit(amount);
            cout << "转账成功:" << owner << " → " << to.owner << ",金额:" << amount << " 元" << endl;
            return true;
        }
        return false;
    }

    // 查询余额
    double getBalance() const {
        return balance;
    }

    // 获取账户数量
    static int getAccountCount() {
        return accountCount;
    }

    // 打印信息
    void printInfo() const {
        cout << "账户:" << owner << ",余额:" << balance << " 元" << endl;
    }
};

int BankAccount::accountCount = 0;

int main() {
    BankAccount alice("Alice", 1000);
    BankAccount bob("Bob", 500);

    alice.deposit(200);
    bob.withdraw(100);
    alice.transfer(bob, 300);

    cout << "\n--- 账户信息 ---" << endl;
    alice.printInfo();
    bob.printInfo();
    cout << "总账户数:" << BankAccount::getAccountCount() << endl;

    return 0;
}

输出

复制代码
账户创建成功:Alice
账户创建成功:Bob
Alice 存入 200 元,余额:1200 元
Bob 取出 100 元,余额:400 元
Alice 取出 300 元,余额:900 元
Bob 存入 300 元,余额:700 元
转账成功:Alice → Bob,金额:300 元

--- 账户信息 ---
账户:Alice,余额:900 元
账户:Bob,余额:700 元
总账户数:2
账户注销:Bob
账户注销:Alice

8. 小结

本节我们学习了 C++ 类与对象的基础:

  • 把数据和操作封装在一起,是面向对象的基本单元
  • 访问控制(public、protected、private)保护数据不被随意修改
  • 构造函数 在对象创建时初始化,析构函数在对象销毁时清理资源
  • 初始化列表比赋值更高效
  • this 指针指向当前对象,支持链式调用
  • 静态成员属于类而非对象,所有对象共享

学习建议

  • 理解封装的意义:隐藏实现细节,暴露安全接口
  • 养成使用初始化列表的习惯
  • 如果类管理了动态资源(new),一定要写析构函数
  • 不修改对象的成员函数都声明为 const

下一节我们将学习运算符重载------让你的自定义类型像内置类型一样使用运算符。

相关推荐
码不停蹄的玄黓1 小时前
Java 生产者-消费者模型详解
java·开发语言·python
LONGZETECH1 小时前
软硬协同+故障注入:无人机仿真维修与操控仿真底层算法逻辑拆解
大数据·c语言·算法·3d·unity·无人机
Lsk_Smion1 小时前
力扣实训 _ [543].二叉树的直径 _ [23].合并K个升序列表
数据结构·算法·leetcode
笨蛋不要掉眼泪1 小时前
Java并发编程:Executors框架类深度解析
java·开发语言·并发
南极企鹅1 小时前
深入理解 MVCC:数据库并发控制的基石
java·数据库·mysql
凯瑟琳.奥古斯特2 小时前
力扣1235:加权区间调度最优解
java·python·算法·leetcode·职场和发展
想不到ID了2 小时前
第八篇: 登录注册功能实现
java·javascript
耶叶2 小时前
餐厅出入最少人数问题:贪心算法
算法·贪心算法