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. 实现类成员函数
知识点说明
类的成员函数有两种写法:
- 在类内部直接定义:函数体写在类声明里面(适用于短小函数)
- 在类外部用作用域解析符
::定义:声明和实现分离(推荐,尤其对较大函数)
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. 类作用域
知识点说明
类作用域:类中定义的成员(数据成员和成员函数)只在类的范围内可见。
类作用域的特点:
- 类成员名只在类中唯一,不会和其他类的同名成员冲突
- 在类外访问成员必须用
::或.或-> - 类声明中的成员函数可以使用类中的所有名字(包括后面声明的成员)
- 类外定义的成员函数,如果要使用类成员,必须加限定
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) { } // 编译错误!类型不同
作用域内枚举的优势:
- 避免命名冲突
- 强类型,不同类型不能隐式转换
- 可以指定底层类型:
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的三层结构:
- 属性(数据):对象有什么信息
- 操作(接口):能对对象做什么
- 隐藏实现细节:外部不知道也不需要知道内部怎么做的
为什么学?
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成员函数、作用域内枚举是进阶内容,在后续学习中会反复用到。