C++ 学习教程,从入门到精通,C++对象和类 — 详细知识点总结(10)

C++对象和类 --- 详细知识点总结


1. 过程性编程和面向对象编程

知识点说明

过程性编程(Procedural Programming):程序由一系列函数组成,数据和函数是分离的。函数操作传入的数据,数据本身没有"行为"。

面向对象编程(Object-Oriented Programming, OOP):将数据和操作数据的函数捆绑在一起,形成一个"对象"。数据受到保护,只能通过特定的函数来访问。

为什么学?

现实世界就是由"对象"组成的。用OOP的思想写代码,更贴近现实,代码更容易维护和复用。

通俗案例

假设你要管理银行账户:

cpp 复制代码
// ===== 过程性编程风格 =====
struct Account {
    char name[50];
    double balance;
};

void deposit(Account &acc, double amount) {
    acc.balance += amount;
}

void withdraw(Account &acc, double amount) {
    if (amount <= acc.balance)
        acc.balance -= amount;
}

// 使用
Account myAcc;
strcpy(myAcc.name, "张三");
myAcc.balance = 1000;
deposit(myAcc, 500);
cpp 复制代码
// ===== 面向对象编程风格 =====
class Account {
private:
    char name[50];
    double balance;
public:
    Account(const char *n, double b) {
        strcpy(name, n);
        balance = b;
    }
    void deposit(double amount) {
        balance += amount;
    }
    void withdraw(double amount) {
        if (amount <= balance)
            balance -= amount;
    }
    double getBalance() { return balance; }
};

// 使用
Account myAcc("张三", 1000);
myAcc.deposit(500);

区别一目了然 :OOP中,deposit和withdraw属于Account对象本身,数据和行为在一起,代码更整洁。


2. 抽象和类

知识点说明

抽象:抓住事物的关键特征,忽略不必要的细节。比如描述一辆车,你只需要说品牌、颜色、速度,而不需要关心轮胎的分子结构。

类(Class):是创建对象的蓝图/模板。它定义了一类事物的共同属性(数据成员)和行为(成员函数)。

为什么学?

抽象是OOP的基石。如果不理解抽象,就无法合理地设计类。类是C++中实现OOP的核心工具。

通俗案例

设计一个"狗"的类:

cpp 复制代码
class Dog {
private:
    char name[20];   // 名字 --- 属性
    int age;          // 年龄 --- 属性
    char breed[20];   // 品种 --- 属性

public:
    // 行为
    void bark() {
        cout << name << "说:汪汪!" << endl;
    }

    void sit() {
        cout << name << "坐下了" << endl;
    }

    int getAge() {
        return age;
    }
};

抽象体现:我们只关注名字、年龄、品种,忽略了狗有多少根毛、心脏跳多快这些细节。


3. 类型是什么

知识点说明

在C++中,类型 不仅仅是int、double这些基本类型。类型规定了:

特征 说明
内存占用 类型决定了占用多少字节
可执行的操作 比如int可以加减乘除,但你不能对int调用.c_str()
数据解释方式 同样的4个字节,可以被解释为int或float

类也是一种自定义类型。定义了一个类,就相当于创造了一个全新的类型。

为什么学?

理解类型的本质,才能理解为什么类可以像int一样用来声明变量(创建对象),也才能理解为什么类型转换很重要。

通俗案例

cpp 复制代码
int x = 42;         // x是int类型,占4字节,可以加减
double y = 3.14;    // y是double类型,占8字节

// 类也是一种类型
Dog myDog;           // Dog是自定义类型,myDog是一个对象
// 你不能写 Dog + Dog,因为没定义这个操作
// 但你可以写 myDog.bark(),因为Dog类定义了bark()

4. C++中的类

知识点说明

C++的类有三大部分:

复制代码
class 类名 {
private:      // 私有成员 --- 只有本类的函数能访问
    数据成员;
    成员函数;

protected:    // 保护成员 --- 本类和子类能访问(后续章节详讲)

public:       // 公有成员 --- 任何人都能访问
    数据成员;
    成员函数;
};

访问控制是OOP的核心安全机制:外部代码不能直接修改对象的内部数据,必须通过公有接口来操作。

为什么学?

如果不掌握类的定义方式,就无法用C++进行任何OOP开发。这是C++的核心语法。

通俗案例

cpp 复制代码
class Circle {
private:
    double radius;    // 外部不能直接访问

public:
    // 设置半径(公有接口)
    void setRadius(double r) {
        if (r > 0)    // 可以在这里做合法性检查!
            radius = r;
    }

    // 计算面积
    double getArea() {
        return 3.14159 * radius * radius;
    }

    // 获取半径
    double getRadius() {
        return radius;
    }
};

// 使用
Circle c;
c.setRadius(5.0);        // 正确:通过公有接口设置
cout << c.getArea();     // 输出 78.5397
// c.radius = -100;      // 错误!radius是private的

为什么要private? 如果radius是public的,任何人都可以设置成-100,导致程序出错。通过private + 公有函数,我们可以加验证逻辑。

类的声明与定义分离(头文件+实现文件)

cpp 复制代码
// circle.h --- 类声明
class Circle {
private:
    double radius;
public:
    void setRadius(double r);
    double getArea();
};

// circle.cpp --- 类实现
#include "circle.h"
void Circle::setRadius(double r) {
    if (r > 0) radius = r;
}
double Circle::getArea() {
    return 3.14159 * radius * radius;
}

为什么要分离? 大型项目中,声明放头文件,实现放源文件,修改实现不需要重新编译所有使用该类的文件。


5. 实现类成员函数

知识点说明

类的成员函数有两种写法:

  1. 在类内部直接定义:函数体写在类声明里面(适用于短小函数)
  2. 在类外部用作用域解析符::定义:声明和实现分离(推荐,尤其对较大函数)
cpp 复制代码
// 方式1:类内定义
class MyClass {
public:
    void sayHello() {
        cout << "Hello!" << endl;
    }
};

// 方式2:类外定义(推荐)
class MyClass {
public:
    void sayHello();  // 只声明
};

// 类外实现
void MyClass::sayHello() {
    cout << "Hello!" << endl;
}

::的作用 :告诉编译器,sayHello不是普通函数,而是属于MyClass类的成员函数。

为什么学?

类外定义是工程实践中的标准做法,使得代码结构清晰、易维护。

通俗案例

cpp 复制代码
class Student {
private:
    char name[30];
    float score;
public:
    void setInfo(const char *n, float s);   // 声明
    void display();                           // 声明
    bool isPassed();                          // 声明
};

// 实现
void Student::setInfo(const char *n, float s) {
    strcpy(name, n);
    score = s;
}

void Student::display() {
    cout << "姓名:" << name << ",成绩:" << score << endl;
}

bool Student::isPassed() {
    return score >= 60.0;
}

// 使用
Student s;
s.setInfo("李明", 85.5);
s.display();       // 姓名:李明,成绩:85.5
if (s.isPassed())
    cout << "及格了!" << endl;

6. 使用类

知识点说明

使用类主要通过以下方式:

方式 说明 示例
声明单个对象 创建一个对象 Circle c;
声明对象指针 通过指针操作对象 Circle *p = new Circle;
声明对象引用 给对象起别名 Circle &ref = c;
声明对象数组 创建多个对象 Circle circles[5];

为什么学?

实际项目中,对象的创建和使用无处不在,掌握各种声明方式才能灵活编程。

通俗案例

cpp 复制代码
Circle c1;                 // 栈上创建对象
c1.setRadius(3.0);

Circle *p = new Circle();  // 堆上创建对象
p->setRadius(5.0);         // 指针用 -> 访问成员
cout << p->getArea();
delete p;                  // 记得释放!

Circle c2;
Circle &ref = c2;          // 引用,ref就是c2的别名
ref.setRadius(7.0);        // 等同于 c2.setRadius(7.0);

对象数组:

cpp 复制代码
Circle circles[3];           // 创建3个Circle对象
circles[0].setRadius(1.0);
circles[1].setRadius(2.0);
circles[2].setRadius(3.0);

for (int i = 0; i < 3; i++) {
    cout << "圆" << i << "的面积:" << circles[i].getArea() << endl;
}

7. 修改实现

知识点说明

**"修改实现"**是指:改变类内部成员函数的实现方式,但不改变对外的接口(函数签名)。这是OOP的重要优势之一。

比如你一开始用数组存储数据,后来改用链表,只要对外接口不变,使用这个类的代码完全不需要修改。

为什么学?

体现OOP的封装优势:内部实现可以自由优化,不影响外部使用者。

通俗案例

cpp 复制代码
// ===== 旧实现:用数组存储学生成绩 =====
class GradeBook {
private:
    int scores[100];
    int count;
public:
    void addScore(int s) {
        scores[count++] = s;
    }
    double getAverage() {
        int sum = 0;
        for (int i = 0; i < count; i++)
            sum += scores[i];
        return (double)sum / count;
    }
};

// ===== 修改后的实现:改用vector =====
class GradeBook {
private:
    vector<int> scores;   // 从数组换成了vector
public:
    void addScore(int s) {
        scores.push_back(s);
    }
    double getAverage() {
        int sum = 0;
        for (int i = 0; i < scores.size(); i++)
            sum += scores[i];
        return (double)sum / scores.size();
    }
};

// 外部使用方式完全不变!
GradeBook book;
book.addScore(90);
book.addScore(85);
cout << book.getAverage();

8. 类的构造函数和析构函数 --- 声明和定义构造函数

知识点说明

构造函数 是一种特殊的成员函数,在创建对象时自动调用,用于初始化对象的数据成员。

构造函数的特征:

  • 函数名与类名相同
  • 没有返回值(连void都没有)
  • 在对象创建时自动调用
  • 可以有参数,也可以没有参数

析构函数 在对象销毁时自动调用,用于清理资源。

为什么学?

没有构造函数,你创建对象后必须手动调用init()之类的函数来初始化,容易忘记导致bug。构造函数保证对象一诞生就是合法的、初始化好的状态。

通俗案例

cpp 复制代码
class Time {
private:
    int hours;
    int minutes;
    int seconds;

public:
    // 构造函数
    Time(int h, int m, int s) {
        hours = h;
        minutes = m;
        seconds = s;
    }

    void display() {
        cout << hours << ":" << minutes << ":" << seconds << endl;
    }
};

// 创建对象时直接传参数,构造函数自动被调用
Time t1(10, 30, 45);   // 直接初始化好了!
t1.display();            // 输出 10:30:45

Time t2(23, 59, 59);   // 另一个对象
t2.display();

没有构造函数会怎样?

cpp 复制代码
class Time {
    int hours, minutes, seconds;
public:
    void display() {
        cout << hours << ":" << minutes << ":" << seconds << endl;
    }
};

Time t;
t.display();  // 输出随机值!hours, minutes, seconds 都是垃圾值

这就是为什么构造函数如此重要。


9. 类的构造函数和析构函数 --- 使用构造函数

知识点说明

构造函数有三种初始化方式:

方式1:常规函数体赋值

cpp 复制代码
Time::Time(int h, int m, int s) {
    hours = h;      // 这是赋值,不是初始化
    minutes = m;
    seconds = s;
}

方式2:成员初始化列表(推荐)

cpp 复制代码
Time::Time(int h, int m, int s) : hours(h), minutes(m), seconds(s) {
    // 函数体可以为空或写其他逻辑
}

方式3:默认参数

cpp 复制代码
Time::Time(int h = 0, int m = 0, int s = 0) : hours(h), minutes(m), seconds(s) {}

成员初始化列表的优势 :对于const成员和引用成员,必须使用初始化列表,因为它们不能在函数体内赋值。

为什么学?

初始化列表是更高效的做法,尤其对于类类型的成员(避免先默认构造再赋值的两步操作)。

通俗案例

cpp 复制代码
class Rectangle {
private:
    const double width;   // const成员,不能在函数体内赋值!
    const double height;

public:
    // 必须使用初始化列表
    Rectangle(double w, double h) : width(w), height(h) {
        // width = w;   // 错误!const不能赋值
    }

    double getArea() { return width * height; }
};

Rectangle r(5.0, 3.0);
cout << r.getArea();  // 15

使用默认参数的构造函数:

cpp 复制代码
class Color {
    int r, g, b;
public:
    // 使用默认参数,可以灵活创建对象
    Color(int red = 0, int green = 0, int blue = 0)
        : r(red), g(green), b(blue) {}
    
    void show() {
        cout << "(" << r << "," << g << "," << b << ")" << endl;
    }
};

Color c1;           // 调用 Color(0,0,0) → (0,0,0)
Color c2(255);      // 调用 Color(255,0,0) → (255,0,0)
Color c3(0,255,0);  // 调用 Color(0,255,0) → (0,255,0)

10. 默认构造函数

知识点说明

默认构造函数:不带任何参数的构造函数。当你创建对象时不提供任何参数时,就调用默认构造函数。

cpp 复制代码
Time t;         // 调用默认构造函数
Time t(10,30);  // 调用带参数的构造函数

重要规则:

  • 如果你没有定义任何构造函数,编译器会自动提供一个默认构造函数(但它什么都不做)
  • 如果你定义了任何构造函数 ,编译器就不再自动提供默认构造函数了
  • 默认构造函数可以自己写,也可以让编译器生成
cpp 复制代码
class Good {
public:
    Good() { cout << "默认构造" << endl; }    // 你自己写的
    Good(int x) { cout << "带参构造" << endl; }
};

Good g1;         // OK,调用 Good()
Good g2(42);     // OK,调用 Good(int)

class Bad {
public:
    Bad(int x) { cout << "只有带参构造" << endl; }
};

// Bad b1;       // 错误!没有默认构造函数,编译器也不提供
Bad b2(42);      // OK

为什么学?

很多场景需要无参创建对象(比如创建数组、容器存储)。如果没有默认构造函数,很多STL容器就无法使用你的类。

通俗案例

cpp 复制代码
class Score {
private:
    int value;
public:
    // 默认构造函数:初始化为0
    Score() : value(0) {
        cout << "默认构造,成绩初始化为0" << endl;
    }

    Score(int v) : value(v) {
        cout << "成绩初始化为" << v << endl;
    }

    void show() { cout << "成绩:" << value << endl; }
};

Score s1;        // 输出:默认构造,成绩初始化为0
s1.show();       // 成绩:0

Score s2(95);    // 输出:成绩初始化为95
s2.show();       // 成绩:95

// 创建对象数组 ------ 必须有默认构造函数!
Score scores[5]; // 每个元素都调用默认构造函数

11. 析构函数

知识点说明

析构函数在对象被销毁时自动调用,用于释放资源(内存、关闭文件等)。

析构函数的特征:

  • 函数名是 ~类名(波浪号 + 类名)
  • 没有参数,没有返回值
  • 只有一个,不能重载
  • 对象离开作用域时自动调用
cpp 复制代码
class DynArray {
private:
    int *data;
    int size;
public:
    DynArray(int s) {
        size = s;
        data = new int[s];     // 构造函数:分配内存
        cout << "分配了" << s << "个整数的空间" << endl;
    }

    ~DynArray() {
        delete[] data;          // 析构函数:释放内存
        cout << "释放了内存" << endl;
    }
};

void demo() {
    DynArray arr(100);  // 构造:分配了100个整数的空间
    // 使用arr...
}  // arr离开作用域,自动调用析构函数:释放了内存

为什么学?

不写析构函数会导致内存泄漏 。动态分配的内存如果不用delete释放,程序运行时间越长,占用内存越多,最终可能崩溃。

构造函数和析构函数的调用顺序

cpp 复制代码
class A {
public:
    A()  { cout << "A构造" << endl; }
    ~A() { cout << "A析构" << endl; }
};

class B {
public:
    B()  { cout << "B构造" << endl; }
    ~B() { cout << "B析构" << endl; }
};

void demo() {
    A a;
    B b;
    cout << "---使用中---" << endl;
}

输出:

复制代码
A构造
B构造
---使用中---
B析构    ← 后构造的先析构(栈的后进先出)
A析构

规则 :构造顺序按声明顺序,析构顺序与构造顺序相反。


12. 改进Stock类(综合案例)

知识点说明

这是教材中一个综合练习案例,将前面所有知识点整合到一个实际的类中。

cpp 复制代码
class Stock {
private:
    char company[30];   // 公司名称
    long shares;         // 持有股数
    double share_val;    // 每股价格
    double total_val;    // 总价值

    void setTotal() {
        total_val = shares * share_val;
    }

public:
    // 构造函数
    Stock();                              // 默认构造函数
    Stock(const char *co, long n, double pr);  // 带参构造函数
    ~Stock();                             // 析构函数

    // 成员函数
    void buy(long num, double price);
    void sell(long num, double price);
    void update(double price);
    void show() const;   // const成员函数(承诺不修改对象)
};

// 默认构造函数
Stock::Stock() {
    strcpy(company, "no name");
    shares = 0;
    share_val = 0.0;
    total_val = 0.0;
    cout << "默认构造函数被调用" << endl;
}

// 带参构造函数
Stock::Stock(const char *co, long n, double pr) {
    strncpy(company, co, 29);
    company[29] = '\0';
    shares = n;
    share_val = pr;
    setTotal();
    cout << "构造函数使用 " << company << " 初始化" << endl;
}

// 析构函数
Stock::~Stock() {
    cout << "析构函数调用," << company << " 对象销毁" << endl;
}

// 购买股票
void Stock::buy(long num, double price) {
    shares += num;
    share_val = price;
    setTotal();
}

// 卖出股票
void Stock::sell(long num, double price) {
    if (num > shares) {
        cout << "卖出数量超过持有量!" << endl;
        return;
    }
    shares -= num;
    share_val = price;
    setTotal();
}

// 更新价格
void Stock::update(double price) {
    share_val = price;
    setTotal();
}

// 显示信息
void Stock::show() const {   // const表示不修改任何成员
    cout << "公司:" << company << endl;
    cout << "持股:" << shares << "股" << endl;
    cout << "单价:" << share_val << endl;
    cout << "总值:" << total_val << endl;
}

const成员函数 :加在函数声明末尾,表示这个函数不会修改任何数据成员。这样用const对象也可以调用它。

cpp 复制代码
const Stock mystery;  // const对象
// mystery.buy(10, 5.0);  // 错误!buy可能修改数据
mystery.show();          // 正确!show是const函数

13. this指针

知识点说明

this是一个特殊的指针,指向调用成员函数的对象本身。

每个成员函数(非static)都有一个隐藏的this指针参数。当你写obj.func()时,编译器实际上处理为func(&obj),this就指向obj。

为什么学?

当成员函数的参数名和数据成员名相同时,必须用this来区分。此外,this还可以用于链式调用和返回对象引用。

通俗案例

cpp 复制代码
class Box {
private:
    double length;
public:
    Box(double length) {
        // 参数length和成员length同名,怎么区分?
        this->length = length;   // this->length 是成员,length是参数
    }

    // 链式调用:每次操作都返回对象本身
    Box& setLength(double l) {
        this->length = l;
        return *this;   // 返回当前对象的引用
    }

    Box& setWidth(double w) {
        // 假设还有width成员...
        return *this;
    }

    void show() {
        cout << "长度:" << length << endl;
    }
};

Box box(10.0);
box.setLength(20.0).setWidth(30.0);  // 链式调用!
// 等价于:box.setLength(20.0); box.setWidth(30.0);
box.show();  // 长度:20

this的本质:

cpp 复制代码
// 你写的
void Box::show() {
    cout << length << endl;
}

// 编译器实际理解的
void Box::show(Box *this) {
    cout << this->length << endl;
}

// 调用时
box.show();
// 编译器实际理解为:
// Box::show(&box);

14. 对象数组

知识点说明

和普通数组类似,可以创建一个数组,每个元素都是一个对象。

如果类有默认构造函数,可以直接创建数组:

cpp 复制代码
Stock stocks[5];   // 调用5次默认构造函数

如果只有带参构造函数,需要用初始化列表:

cpp 复制代码
Stock stocks[3] = {
    Stock("公司A", 100, 5.0),
    Stock("公司B", 200, 3.0),
    Stock("公司C", 150, 7.0)
};

为什么学?

批量管理对象时,对象数组比逐个创建更方便。比如管理一个班级的学生、一支股票组合等。

通俗案例

cpp 复制代码
class Pet {
private:
    char name[20];
    int age;
public:
    Pet() {
        strcpy(name, "无名");
        age = 0;
    }

    Pet(const char *n, int a) {
        strcpy(name, n);
        age = a;
    }

    void show() {
        cout << name << "," << age << "岁" << endl;
    }
};

// 使用默认构造函数创建数组
Pet pets[4];   // 4只"无名",0岁的宠物

// 用初始化列表赋值
Pet pets2[3] = {
    Pet("旺财", 3),
    Pet("小花", 2),
    Pet("小白", 1)
};

// 遍历显示
for (int i = 0; i < 3; i++) {
    pets2[i].show();
}
// 输出:
// 旺财,3岁
// 小花,2岁
// 小白,1岁

new创建动态对象数组:

cpp 复制代码
Pet *p = new Pet[3];   // 在堆上创建3个对象
p[0] = Pet("大黄", 5);
p[1] = Pet("橘子", 3);
p[2] = Pet("黑豆", 4);

p[1].show();   // 橘子,3岁

delete[] p;     // 释放,每个对象的析构函数都会被调用

15. 类作用域

知识点说明

类作用域:类中定义的成员(数据成员和成员函数)只在类的范围内可见。

类作用域的特点:

  1. 类成员名只在类中唯一,不会和其他类的同名成员冲突
  2. 在类外访问成员必须用::或.或->
  3. 类声明中的成员函数可以使用类中的所有名字(包括后面声明的成员)
  4. 类外定义的成员函数,如果要使用类成员,必须加限定
cpp 复制代码
class Box {
private:
    int x;          // 类作用域
public:
    void func() {
        x = 10;    // OK,可以直接访问x
    }
};

// 类外定义
void Box::func() {
    x = 10;        // OK,加了Box::限定,知道x是谁
}

类作用域中的声明顺序问题:

cpp 复制代码
class Example {
    int x;
    void f() {
        x = 10;     // OK!编译器看到的是整个类,即使g在后面声明
    }
    void g();
    int y;
};

为什么学?

理解作用域才能正确处理变量名冲突,理解为什么有些地方能访问成员,有些地方不能。

通俗案例

cpp 复制代码
class Student {
private:
    int id;
public:
    void setId(int id) {
        // 参数id和成员id同名
        // 在类作用域内,不加限定默认指成员
        this->id = id;   // 但最好显式用this区分
    }
};

int main() {
    Student s;
    // int id = 100;    // 局部变量id
    s.setId(200);       // 这里不能直接访问s.id,因为id是private的

    // 作用域的区别
    // main中:s.id 不可见(private)
    // Student::setId中:id 可见(类作用域内可访问所有成员)
}

16. 作用域为类的常量

知识点说明

有时候需要在类中定义常量(比如数组大小、最大容量等),有几种方法:

方法1:使用enum(经典方法,C++98)

cpp 复制代码
class Array {
private:
    enum { SIZE = 100 };   // 匿名枚举,SIZE = 100
    int data[SIZE];        // 可以用作数组大小
};

方法2:使用static const(C++11推荐)

cpp 复制代码
class Array {
private:
    static const int SIZE = 100;
    int data[SIZE];
};

为什么不能直接在类中定义const成员?

cpp 复制代码
class Array {
    const int SIZE = 100;  // C++11前不允许,C++11可以在声明时初始化
    int data[SIZE];        // SIZE不是编译期常量,不能做数组大小
};

原因 :类的const成员在对象创建时才初始化,而数组大小需要在编译时就确定。enum和static const是编译期常量。

为什么学?

定义类中需要的常量值(数组大小、最大值等),使代码可读且易于维护。修改一处即可全局生效。

通俗案例

cpp 复制代码
class GameBoard {
private:
    enum { ROWS = 10, COLS = 20 };  // 棋盘大小
    char grid[ROWS][COLS];           // 棋盘数据

public:
    GameBoard() {
        // 初始化为空
        for (int i = 0; i < ROWS; i++)
            for (int j = 0; j < COLS; j++)
                grid[i][j] = '.';
    }

    void show() {
        for (int i = 0; i < ROWS; i++) {
            for (int j = 0; j < COLS; j++)
                cout << grid[i][j];
            cout << endl;
        }
    }
};

17. 作用域内枚举(C++11)

知识点说明

传统的enum中,枚举值会泄漏到外部作用域,容易造成命名冲突:

cpp 复制代码
// 传统enum ------ 有问题!
enum Color { RED, GREEN, BLUE };       // RED等进入外部作用域
enum Traffic { RED, YELLOW, GREEN };   // 冲突!RED和GREEN重复了

C++11引入了作用域内枚举(enum class),枚举值被限制在枚举类型的作用域内:

cpp 复制代码
enum class Color { RED, GREEN, BLUE };
enum class Traffic { RED, YELLOW, GREEN };

Color c = Color::RED;          // 必须用 Color::RED
Traffic t = Traffic::RED;      // 没有冲突!

// 不能直接比较不同类型
// if (c == t) { }   // 编译错误!类型不同

作用域内枚举的优势:

  1. 避免命名冲突
  2. 强类型,不同类型不能隐式转换
  3. 可以指定底层类型:enum class Small : char { A, B };

为什么学?

在大型项目中,避免命名冲突非常重要。C++11的enum class是现代C++的最佳实践。

通俗案例

cpp 复制代码
// 游戏开发中,不同系统都有状态枚举
enum class HeroState { IDLE, RUNNING, JUMPING, ATTACKING };
enum class EnemyState  { PATROL, CHASE, ATTACK, DEAD };
enum class UIState     { MENU, PLAYING, PAUSED, GAMEOVER };

// 使用时非常清晰
HeroState hero = HeroState::IDLE;
EnemyState enemy = EnemyState::PATROL;

// 不同状态不能混用
// if (hero == enemy) { }   // 编译错误,类型安全!

// 配合switch使用
switch (hero) {
    case HeroState::IDLE:
        cout << "英雄待机中" << endl;
        break;
    case HeroState::RUNNING:
        cout << "英雄在奔跑" << endl;
        break;
    // ...
}

18. 抽象数据类型(ADT)

知识点说明

抽象数据类型(Abstract Data Type):只描述数据的逻辑特性和操作接口,不涉及具体实现。

ADT的三层结构:

  1. 属性(数据):对象有什么信息
  2. 操作(接口):能对对象做什么
  3. 隐藏实现细节:外部不知道也不需要知道内部怎么做的

为什么学?

ADT是软件工程的核心思想。设计一个好的ADT,可以让代码模块化、可复用、易维护。

通俗案例:栈(Stack)

栈是一个经典的ADT------后进先出(LIFO)

cpp 复制代码
class Stack {
private:
    int data[100];     // 实现方式是数组(也可以换成链表,外部不知道也不关心)
    int top;            // 栈顶指针

public:
    Stack() : top(-1) {}

    // 接口:压栈
    bool push(int value) {
        if (top >= 99) {
            cout << "栈已满!" << endl;
            return false;
        }
        data[++top] = value;
        return true;
    }

    // 接口:弹栈
    bool pop(int &value) {
        if (top < 0) {
            cout << "栈为空!" << endl;
            return false;
        }
        value = data[top--];
        return true;
    }

    // 接口:查看栈顶
    bool peek(int &value) const {
        if (top < 0) return false;
        value = data[top];
        return true;
    }

    bool isEmpty() const { return top < 0; }
    bool isFull() const { return top >= 99; }
};

使用者不需要知道内部是数组还是链表:

cpp 复制代码
Stack s;
s.push(10);
s.push(20);
s.push(30);

int val;
s.pop(val);     // val = 30(后进先出)
s.pop(val);     // val = 20
s.pop(val);     // val = 10

// 如果将来改成链表实现,s.push/pop的调用方式完全不变

另一个案例:字符串类

cpp 复制代码
class MyString {
private:
    char *str;
    int length;

public:
    // 构造函数
    MyString(const char *s = "") {
        length = strlen(s);
        str = new char[length + 1];
        strcpy(str, s);
    }

    // 析构函数
    ~MyString() {
        delete[] str;
    }

    // 接口:获取长度
    int getLength() const { return length; }

    // 接口:显示
    void display() const { cout << str << endl; }

    // 接口:拼接
    MyString concat(const MyString &other) const {
        char *newStr = new char[length + other.length + 1];
        strcpy(newStr, str);
        strcat(newStr, other.str);
        MyString result(newStr);
        delete[] newStr;
        return result;
    }
};

19. 复习题要点总结

核心概念速查表

概念 一句话解释
类 创建对象的蓝图,定义属性和行为
对象 类的一个实例
数据成员 类中的变量(属性)
成员函数 类中的函数(行为)
访问控制 private(私有)、protected(保护)、public(公有)
构造函数 对象创建时自动调用,用于初始化
默认构造函数 无参构造函数,Time t;时调用
析构函数 对象销毁时自动调用,用于清理
this指针 指向当前对象的指针
const成员函数 不修改数据成员的函数
作用域内枚举 enum class,枚举值限定在类型作用域内
ADT 只定义接口,不暴露实现的数据类型

20. 编程练习要点(10.10)

常见练习类型

练习1:设计一个完整的类

cpp 复制代码
// 设计一个银行账户类
class BankAccount {
private:
    char owner[50];
    double balance;

public:
    BankAccount(const char *name = "Unknown", double bal = 0.0);
    void deposit(double amount);
    void withdraw(double amount);
    void display() const;
    double getBalance() const;
};

练习2:对象数组操作

cpp 复制代码
// 设计一个类,用对象数组存储和处理数据
class Student {
private:
    char name[30];
    float scores[3];   // 三门课成绩

public:
    Student();
    Student(const char *n, float s1, float s2, float s3);
    float average() const;
    void display() const;
};

// 创建学生数组,找出平均分最高的学生
Student class1[5] = {
    Student("张三", 80, 90, 85),
    Student("李四", 70, 75, 80),
    // ...
};

int findBest(Student arr[], int size) {
    int bestIdx = 0;
    for (int i = 1; i < size; i++) {
        if (arr[i].average() > arr[bestIdx].average())
            bestIdx = i;
    }
    return bestIdx;
}

练习3:栈的实现(ADT)

cpp 复制代码
class StackADT {
private:
    double data[100];
    int topIndex;

public:
    StackADT();
    bool push(double value);
    bool pop(double &value);
    bool isEmpty() const;
    bool isFull() const;
    double peek() const;
};

全章知识体系总结

复制代码
C++对象和类
├── 过程性编程 vs 面向对象编程
│   └── 数据与函数的分离 vs 绑定
├── 抽象和类
│   └── 抓关键特征,忽略细节
├── 类型
│   └── 类是一种自定义类型
├── C++中的类
│   ├── private / protected / public
│   ├── 数据成员 + 成员函数
│   └── 类声明与实现分离
├── 构造函数
│   ├── 带参构造函数
│   ├── 默认构造函数(无参)
│   ├── 成员初始化列表(推荐)
│   └── 默认参数
├── 析构函数
│   ├── ~类名()
│   ├── 释放资源
│   └── 调用顺序与构造相反
├── this指针
│   ├── 指向当前对象
│   └── 解决同名问题、链式调用
├── 对象数组
│   └── 需要默认构造函数或初始化列表
├── 类作用域
│   └── 成员名的可见性规则
├── 类的常量
│   ├── enum(匿名枚举)
│   └── static const(C++11)
├── 作用域内枚举(enum class)
│   └── 类型安全,避免命名冲突
└── 抽象数据类型(ADT)
    └── 只暴露接口,隐藏实现

学习建议 :先理解每个概念的"为什么",再记"怎么做"。构造函数和析构函数是本章重中之重,务必多写代码练习。this指针、const成员函数、作用域内枚举是进阶内容,在后续学习中会反复用到。

相关推荐
顺顺 尼1 小时前
linux 进程信号(上)
linux·c++·算法
郑州光合科技余经理2 小时前
同城电商系统:库存变更怎么同步到订单
java·开发语言·前端·后端·uni-app·php·ai编程
New农民工2 小时前
倒立摆训练秘籍:理解训练参数
学习
思茂信息3 小时前
CST软件BCI仿真模型及仿真案例
开发语言·单片机·嵌入式硬件·算法·emc
L@ncor3 小时前
第五章 基于低代码平台的智能体搭建 · 学习笔记(Coze / Dify / FastGPT / n8n)
笔记·学习·低代码·agent·prompt工程
海上小飞龙3 小时前
改一个数,右边全得重算,这题怎么扛住两万次查询
java·c++·python
阿里嘎多学长3 小时前
2026-09-28 GitHub 热点项目精选
开发语言·程序员·github·代码托管
Persistent的粽子!3 小时前
双指针算法:最大盛水容器
c++·算法·leetcode
迪丽热爱3 小时前
多媒体应用20-903(补)
学习