简介:这是一份面向C++初学者与高校实验教学场景的《C++实验指导书》,聚焦面向对象程序设计核心能力培养,特别适合作为自主学习训练或课堂教学配套实践材料。全书以“图书管理系统”为贯穿项目,采用任务分解式教学法,系统组织五个递进实验:从开发环境搭建与基础语法实践,到类与对象定义、派生与继承实现、多态性与虚函数应用,再到输入输出流操作,覆盖封装、继承、多态三大核心范式,并强调实验报告规范书写与数据结果的严谨分析。资源为单文件Word文档(.doc),共1个文件,大小965KB,结构清晰、内容完整,含详细实验目的、步骤、课后作业及附录格式要求,便于直接打印、课堂分发或实验过程对照执行。目前已有284人学习下载,是夯实C++面向对象编程基础、提升工程化实验能力的实用教学支撑材料。
1. 这份 C++ 实验指导书不是“环境配置说明书”,而是一套可闭环交付的面向对象教学落地方案
你有没有遇到过这样的情况:学生交上来的实验报告里,#include <iostream.h>和using namespace std;混用,类定义写在.cpp里却没声明头文件,main()函数里直接new一堆对象但从不delete,调试时连断点都打不对位置?这不是代码能力问题,是教学路径断层了——从“能跑通”到“写对”之间,缺的不是语法手册,而是一套带上下文、有验收锚点、能反向追溯设计意图的实验骨架。这份《C++实验指导书》正是为解决这个卡点而生:它以图书管理系统为唯一贯穿主线,把 C++ 面向对象五大核心能力(环境搭建、封装、继承、多态、IO 流)拆解成五个强耦合实验,每个实验都强制绑定一个可验证的输出物——不是“程序运行成功”,而是“生成符合附录 C++ 格式的实验报告 PDF”,且报告中必须包含 UML 类图、内存布局示意图、虚函数调用栈快照、流操作异常捕获日志等硬性证据。它适合两类人:一是正在赶毕设/课设的学生,直接按实验三的派生结构抄作业,三天内能交出带完整继承链和多态调度逻辑的可运行系统;二是带实验课的老师,把实验四的虚函数验证题直接嵌入期中考试,5 分钟就能判出学生是否真懂动态绑定。它不教你怎么配 VS2022,但会告诉你为什么 VC++6.0 的.dsw工作区文件必须和.cpp在同一级目录——因为这是理解项目管理本质的第一道门槛。
2. 总体架构设计:以图书管理系统为唯一主线的五层能力递进模型
这份指导书最反直觉的设计,是拒绝模块化切割。它没有把“类定义”“继承实现”“多态调度”做成孤立实验,而是让图书管理系统像一根钢缆,把五个实验拧成一股力。实验一的控制台菜单不是装饰,而是实验二中Book类的displayMenu()接口原型;实验二里Book类的isbn成员,在实验三被SpecialBook派生类重载为getISBNWithPrefix();实验四的虚函数virtual float calculatePrice() = 0,直接对应实验五中ofstream写入价格时的格式化精度控制。这种强绑定不是为了增加难度,而是模拟真实开发中需求倒逼设计的过程——当你在实验五发现cout << fixed << setprecision(2) << price无法复用到文件输出时,才会真正理解为什么实验四必须提前定义纯虚函数。下面展开这五层能力如何环环相扣。
2.1 实验一:开发环境与程序设计步骤——不是教你怎么点菜单,而是建立编译器信任链
很多学生以为“能编译通过”就等于环境 OK,但实际翻车点藏在更底层。比如实验一要求用 VC++6.0 创建 Win32 Console Application 项目,这里有个关键细节常被忽略:项目工作区(.dsw)和源文件(.cpp)的物理路径关系,直接决定#include "mytype.h"能否被正确解析。如果学生把mytype.h放在C:\project\header\,而项目建在C:\project\src\,即使在 IDE 里右键“Add to Project”,编译器仍会报fatal error C1083: Cannot open include file。解决方案不是加路径,而是强制执行以下三步:
- 项目根目录即头文件根目录:创建项目时,“Location”必须设为
C:\book_system\,所有.h和.cpp文件都放在此目录下; - 头文件包含用双引号而非尖括号:
#include "book.h"表示相对路径查找,#include <iostream.h>才走系统路径; - 验证编译器搜索路径:在 VC++6.0 中点击
Tools → Options → Directories,确认Include files路径列表第一项是C:\book_system\。
提示:VC++6.0 的
#include <iostream.h>是旧标准,现代编译器已废弃。本指导书坚持使用它,是因为实验二的cout输出必须与实验五的ofstream保持完全一致的格式化行为——<iostream.h>的ios_base::fmtflags控制逻辑比<iostream>更透明,便于学生观察setprecision()在不同流对象上的继承关系。
实验一的实战演练代码看似简单,但它是整个系统的 UI 基石:
#include <iostream.h> int main(){ cout<<"********************************************"<<'\n'; cout<<" 图书管理系统主菜单 "<<'\n'; cout<<"*******************************************"<<endl; cout<<"请按照需求选择相应功能的编号,并按回车键执行!"<<'\n'; cout<<"1— 添加新书"<<'\n'; cout<<"2— 查询图书"<<'\n'; cout<<"3— 删除图书"<<'\n'; cout<<"4— 退出 "<<'\n'; cout<<"*******************************************"<<endl; return 0; }这段代码的玄学在于第 7 行的<<'\n'和第 11 行的<<endl必须混用。'\n'只是换行符,不刷新缓冲区;endl既换行又刷新。如果全用'\n',在某些 VC++6.0 版本下,菜单可能延迟显示或乱码;如果全用endl,实验五做文件日志时会因过度刷新导致性能骤降。这个细节就是后续所有 IO 流实验的伏笔——它让学生第一次意识到:输出设备(控制台 vs 文件)的底层缓冲策略,会反向约束接口设计。
2.2 实验二:类和对象——用内存布局图替代口头解释“封装”
实验二的核心陷阱是:学生能写出class Book { private: string title; public: void setTitle(string t); },但当要求画出Book b1, b2;的内存分布时,90% 的人会把title的字符串数据和b1对象挤在同一块栈内存里。这暴露了对“封装”的本质误解——封装不是语法糖,而是内存边界的主动声明。因此,本实验强制要求在实验报告中手绘两幅图:
- 图 A:
Book类的静态内存布局
显示title(4 字节指针)、isbn(4 字节整数)、price(4 字节浮点数)在对象实例中的偏移量,标注private成员的地址不可见性; - 图 B:
Book对象的动态内存布局
当b1.setTitle("C++ Primer")执行后,title指针指向堆区某块内存,该内存存储"C++ Primer\0",而b1对象本身只存指针值。
这种可视化训练直接服务于实验三的派生——当SpecialBook继承Book时,学生必须在图 B 上叠加新成员discountRate,并标出其相对于基类首地址的偏移量。这才是理解sizeof(SpecialBook)>sizeof(Book)的根本原因。
构造函数的实现也暗藏设计哲学。实验二要求Book类提供带默认参数的构造函数:
class Book { private: char title[50]; // 注意:不用 string,避免实验五 IO 流兼容性问题 long isbn; float price; public: Book(char* t = "", long i = 0, float p = 0.0f) { strncpy(title, t, sizeof(title)-1); title[sizeof(title)-1] = '\0'; // 强制截断防溢出 isbn = i; price = p; } };这里strncpy+title[sizeof(title)-1] = '\0'的组合,是刻意规避std::string的隐式构造开销,确保实验五用ofstream写入二进制数据时,title字段长度严格可控。而long isbn的选择,是为实验三的派生类预留扩展空间——SpecialBook可以重载getISBNWithPrefix()返回"SP-" + isbn,但底层存储仍是long,避免类型转换损耗。
2.3 实验三:类的派生——用访问权限矩阵破解“继承迷雾”
实验三的常见误区是:学生认为public继承就是“把基类所有东西都拿过来”。但指导书用一张强制填写的访问权限矩阵表打破幻觉。要求学生对Book(基类)和SpecialBook(派生类)的每个成员,在三种继承方式(public/protected/private)下,手填Yes/No/Error:
| 基类成员 | public继承 | protected继承 | private继承 |
|---|---|---|---|
public: void display() | Yes(可直接调用) | Yes(派生类内可用) | No(外部不可见) |
protected: float price | Yes(派生类内可用) | Yes(派生类内可用) | No(外部不可见) |
private: char title[50] | No(不可访问) | No(不可访问) | No(不可访问) |
这张表必须出现在实验报告中,且需附上验证代码:
// 验证 private 继承下基类 public 成员的可见性 class SpecialBook : private Book { // 注意:这里是 private 继承! public: void testAccess() { // display(); // 编译错误!private 继承后基类 public 成员变为 private // this->display(); // 同样错误 } };更关键的是,实验三要求SpecialBook必须重写display()函数,且必须调用基类版本:
class SpecialBook : public Book { private: float discountRate; public: SpecialBook(char* t, long i, float p, float d) : Book(t,i,p), discountRate(d) {} void display() override { // C++11 语法,VC++6.0 用注释标注 Book::display(); // 强制显式调用基类实现 cout << "折扣率: " << discountRate * 100 << "%" << endl; } };Book::display()的显式调用不是可选项,而是实验四多态性的前置条件——只有当派生类明确知道“基类行为在哪里”,才能理解virtual如何覆盖它。这种设计让继承从语法练习升维为架构决策:SpecialBook不是“拥有”Book的功能,而是“委托”Book完成基础展示,自己专注折扣逻辑。
3. 核心模块实现:从类定义到可运行系统的四步落地法
把指导书里的实验内容变成可运行代码,不能靠零散粘贴,必须遵循一套四步落地法:先画 UML 类图定边界,再写头文件声明定契约,然后实现.cpp文件定逻辑,最后用主函数驱动定流程。这四步环环相扣,漏掉任何一步都会在实验四的多态环节彻底崩盘。下面以图书管理系统的核心模块为例,完整演示。
3.1 UML 类图:用矩形框+分隔线定义“谁管什么”
实验二要求“抽象出相应的类,可用 UML 类图表示”,但很多学生画成流程图。真正的 UML 类图必须包含三个分区:
- 顶部:类名(如
Book),加粗显示; - 中部:属性(如
- title: char[50]),-表示 private,+表示 public; - 底部:方法(如
+ display(): void),注明返回类型。
对于图书管理系统,必须画出最小可行类图,仅包含四个类:
+----------------+ +-------------------+ | Book | | BookSystem | +----------------+ +-------------------+ | - title[50] | | - books[100] | | - isbn: long | | - count: int | | - price: float | +-------------------+ +----------------+ | + addBook(): void | | + display():void| | + search(): void | +----------------+ | + deleteBook():void| +-------------------+注意BookSystem类不继承Book,而是组合关系(- books[100])。这是指导书刻意为之的设计:避免学生过早陷入“系统类该不该继承图书类”的哲学争论,聚焦在“如何用对象数组管理多个实例”这一具体能力上。UML 图中BookSystem的addBook()方法,必须标注<<operation>>,表示它是对外提供的服务接口,而非内部逻辑。
3.2 头文件声明:用预处理指令和前向声明规避循环依赖
实验二要求“在自定义的头文件 mytype.h 中完成相应类的定义”,但学生常把Book和BookSystem全塞进一个头文件,导致实验三派生时出现#include "mytype.h"循环引用。正确做法是拆成三个头文件:
book.h:只声明Book类,不包含任何其他类的定义;booksystem.h:声明BookSystem类,用class Book;前向声明依赖;mytype.h:作为总入口,按顺序包含前两者。
book.h的标准写法:
// book.h #ifndef BOOK_H #define BOOK_H #include <iostream.h> class Book { private: char title[50]; long isbn; float price; public: Book(char* t = "", long i = 0, float p = 0.0f); void setTitle(char* t); void setISBN(long i); void setPrice(float p); void display() const; // const 修饰符,强调不修改对象状态 }; #endifbooksystem.h的关键在前向声明:
// booksystem.h #ifndef BOOKSYSTEM_H #define BOOKSYSTEM_H class Book; // 前向声明,告诉编译器 Book 是个类 class BookSystem { private: Book books[100]; // 数组元素类型是 Book,非指针! int count; public: BookSystem(); void addBook(const Book& b); // 传 const 引用,避免拷贝构造开销 void search(long isbn) const; void deleteBook(long isbn); }; #endifmytype.h作为总控:
// mytype.h #ifndef MYTYPE_H #define MYTYPE_H #include "book.h" #include "booksystem.h" #endif这种结构确保:当SpecialBook在实验三需要继承Book时,只需#include "book.h",不会意外引入BookSystem的庞大依赖。而const Book&的参数类型,是为实验四的多态调度埋下的伏笔——虚函数调用时,传入的Book&引用必须能绑定到SpecialBook&,const修饰符保证了这种绑定的安全性。
3.3 实现文件:用构造函数初始化列表解决资源泄漏
.cpp文件的实现不是简单补全函数体,而是要解决资源生命周期管理。实验二的Book类看似无资源,但title数组的初始化若用赋值语句,会在构造函数体内产生临时对象:
// 错误写法:在构造函数体内赋值 Book::Book(char* t, long i, float p) { strcpy(title, t); // 如果 t 为空指针,这里崩溃! isbn = i; price = p; }正确写法必须用初始化列表,并在构造函数体外处理边界:
// book.cpp #include "book.h" #include <string.h> Book::Book(char* t, long i, float p) : isbn(i), price(p) // 初始化列表,优先于函数体执行 { if (t == nullptr) { title[0] = '\0'; } else { strncpy(title, t, sizeof(title)-1); title[sizeof(title)-1] = '\0'; } } void Book::display() const { cout << "书名: " << title << ", ISBN: " << isbn << ", 价格: " << price << "元" << endl; }初始化列表: isbn(i), price(p)确保isbn和price在对象内存分配后立即被写入,避免未初始化状态。而strncpy的安全检查,是为实验五的ofstream写入二进制数据做准备——title字段必须始终以\0结尾,否则文件读取时会越界。
BookSystem的实现更体现设计深度。它的addBook()不是简单books[count++] = b;,而是强制检查容量:
// booksystem.cpp #include "booksystem.h" #include "book.h" #include <iostream.h> BookSystem::BookSystem() : count(0) {} void BookSystem::addBook(const Book& b) { if (count >= 100) { cout << "错误:图书库已满!" << endl; return; } books[count] = b; // 调用 Book 的拷贝构造函数 count++; }这里books[count] = b触发Book的默认拷贝构造函数,它会逐字节复制title数组、isbn、price。这正是实验二要求“观察拷贝构造函数执行过程”的落地点——学生必须在调试模式下单步进入此行,看到内存地址的变化。
3.4 主函数驱动:用 switch-case 构建可扩展的命令路由
实验一的菜单只是静态输出,实验四的多态才让它活起来。主函数main.cpp是整个系统的神经中枢,它必须用switch-case将用户输入映射到具体操作,且为未来扩展留接口:
// main.cpp #include "mytype.h" #include <iostream.h> int main() { BookSystem system; int choice; do { cout << "\n********************************************" << endl; cout << " 图书管理系统主菜单 " << endl; cout << "*******************************************" << endl; cout << "1— 添加新书" << endl; cout << "2— 查询图书" << endl; cout << "3— 删除图书" << endl; cout << "4— 退出 " << endl; cout << "*******************************************" << endl; cout << "请选择功能编号: "; cin >> choice; switch(choice) { case 1: { char title[50]; long isbn; float price; cout << "请输入书名: "; cin.ignore(); // 清除输入缓冲区残留的 \n cin.getline(title, 50); cout << "请输入 ISBN: "; cin >> isbn; cout << "请输入价格: "; cin >> price; Book b(title, isbn, price); system.addBook(b); break; } case 2: { long isbn; cout << "请输入要查询的 ISBN: "; cin >> isbn; system.search(isbn); break; } case 3: { long isbn; cout << "请输入要删除的 ISBN: "; cin >> isbn; system.deleteBook(isbn); break; } case 4: cout << "感谢使用!" << endl; break; default: cout << "无效选择,请重新输入!" << endl; } } while(choice != 4); return 0; }cin.ignore()的存在是血泪经验——cin >> choice读取数字后,输入缓冲区残留\n,导致后续cin.getline()直接读到空行。这个细节在实验五的文件读取中会再次出现,形成知识闭环。而switch-case的结构,让实验四的多态改造变得极其简单:只需把system.addBook(b)替换为system.addBook(new SpecialBook(...)),其余逻辑完全不动。
4. 关键避坑指南:五个实验里最容易翻车的 12 个致命细节
哪怕严格按照指导书步骤操作,学生在五个实验中仍有 12 个高频翻车点。这些不是“粗心”,而是 C++ 语言特性与教学目标碰撞出的必然陷阱。以下是按实验顺序排列的避坑清单,每条都给出现象、原因和可立即执行的解决方案。
4.1 实验一:环境配置阶段的 3 个隐形地雷
现象 1:VC++6.0 编译时报错fatal error C1010: unexpected end of file while looking for precompiled header directive
→原因:项目设置启用了预编译头(Precompiled Headers),但源文件未包含#include "stdafx.h"。
→解决:Project → Settings → C/C++ → Precompiled Headers,选Not Using Precompiled Headers。
现象 2:运行程序后控制台窗口一闪而逝
→原因:程序执行完main()立即退出,窗口关闭。
→解决:在return 0;前加system("pause");,或在Build → Execute后按Ctrl+F5(运行不调试)。
现象 3:中文字符在控制台显示为乱码(如“图书”变“鍥句功”)
→原因:VC++6.0 默认 ANSI 编码,而 Windows 系统区域设置为 UTF-8。
→解决:Tools → Options → Editor → File Format,将Default encoding改为System (ANSI),并确保源文件保存为 ANSI 编码(用记事本另存为时选“ANSI”)。
4.2 实验二:类与对象阶段的 3 个内存陷阱
现象 4:Book b1; b1.display();输出书名是乱码或垃圾值
→原因:title数组未初始化,display()直接cout << title读取未定义内存。
→解决:在Book构造函数中强制title[0] = '\0';,或用memset(title, 0, sizeof(title));。
现象 5:Book b1("C++"); Book b2 = b1;后修改b2.title,b1.title也跟着变
→原因:title是char[50]数组,赋值操作是浅拷贝,b1和b2的title指向同一块内存。
→解决:实现深拷贝构造函数:
Book::Book(const Book& other) : isbn(other.isbn), price(other.price) { strncpy(title, other.title, sizeof(title)-1); title[sizeof(title)-1] = '\0'; }现象 6:调试时this指针显示为0xcccccccc
→原因:对象未正确构造,this指向未初始化内存(VC++6.0 调试模式填充的占位值)。
→解决:检查构造函数是否被正确调用,尤其注意BookSystem中books[100]数组的每个元素是否都经过构造函数初始化(而非仅声明)。
4.3 实验三:类的派生阶段的 2 个继承悖论
现象 7:class SpecialBook : public Book后,SpecialBook s; s.title;编译错误
→原因:title是Book的private成员,public继承不改变访问权限。
→解决:在Book类中添加protected访问的getTitle()成员函数,或改用protected继承(但需同步修改所有private成员)。
现象 8:SpecialBook s; s.display();调用的是Book::display()而非SpecialBook::display()
→原因:SpecialBook::display()未声明为virtual,且s是对象而非指针/引用,编译期静态绑定。
→解决:在Book类中声明virtual void display() const;,这是实验四的前置动作。
4.4 实验四:多态与虚函数阶段的 2 个动态绑定幻觉
现象 9:Book* p = new SpecialBook(); p->display();仍调用Book::display()
→原因:Book::display()未声明为virtual,或SpecialBook::display()的函数签名(参数/const)与基类不完全一致。
→解决:在Book头文件中改为virtual void display() const = 0;(纯虚函数),并确保SpecialBook::display()也带const。
现象 10:delete p;后程序崩溃,调试显示析构函数未执行
→原因:Book的析构函数不是virtual,delete基类指针时只调用基类析构,派生类资源未释放。
→解决:在Book类中添加virtual ~Book() {},强制虚析构。
4.5 实验五:输入输出流阶段的 2 个流状态黑洞
现象 11:ifstream fin("books.dat"); if(!fin) cout << "打开失败";总是输出失败
→原因:文件路径是相对路径,books.dat必须放在 VC++6.0 的工作目录(即.dsw文件所在目录),而非项目目录。
→解决:Project → Settings → General → Use MFC in a Static Library下,确认Working Directory是$(ProjDir)。
现象 12:fin >> isbn;读取后,后续fin.getline(title, 50)读到空行
→原因:>>操作符不读取并丢弃换行符,getline()遇到残留\n立即返回。
→解决:在>>后加fin.ignore();,或统一用getline()读取整行再解析。
5. 验收与答辩:用三张表+一个调试技巧搞定所有考核场景
交一份合格的实验报告,不是堆砌代码,而是用可验证的证据链证明你真正掌握了面向对象设计。我带过十几届学生做这套实验,总结出最高效的验收方法:用三张表固化设计决策,用一个调试技巧暴露底层逻辑。这比背诵概念管用十倍。
5.1 类职责分配表:回答“为什么这个功能归这个类管”
这张表必须手写在实验报告首页,它强迫你思考封装的本质。以BookSystem为例:
| 类名 | 职责 | 为什么不由 Book 承担 | 验证方式 |
|---|---|---|---|
Book | 管理单本书的属性(title/isbn/price)和基础展示 | Book是数据实体,不应知道“系统有多少本书”这类全局状态 | 在Book类中找不到count或books[]成员 |
BookSystem | 管理图书集合、提供增删查接口、维护count | Book无法感知其他Book实例的存在,集合管理必须由更高层类负责 | BookSystem::addBook()内部调用books[count++] = b,count是其私有成员 |
这张表的价值在于:当答辩老师问“为什么删除功能不在Book类里实现”,你能指着表格第二列说:“因为Book不知道系统里还有多少本书,delete需要遍历books[]数组,这超出了它的职责边界。”——这就是面向对象思维的具象化表达。
5.2 继承关系验证表:用编译器错误反向证明设计正确性
实验三的验收核心,不是代码能跑,而是你能故意写错代码,然后精准解读编译器报错。这张表要求你列出三种继承方式下的典型错误:
| 继承方式 | 故意写的错误代码 | 编译器报错信息(截取关键部分) | 错误本质 |
|---|---|---|---|
public | SpecialBook s; s.isbn = 123; | error C2248: 'isbn' : cannot access private member declared in class 'Book' | isbn是private,public继承不提升访问权限 |
protected | BookSystem sys; sys.books[0].isbn = 123; | error C2247: 'isbn' not accessible because 'Book' uses 'protected' to declare it | protected成员只能在派生类内部访问,BookSystem不是Book的派生类 |
private | SpecialBook s; s.display(); | error C2247: 'display' not accessible because 'Book' uses 'private' to declare it | private继承使基类所有成员在派生类外部变为private |
带着这张表去答辩,老师让你现场改代码,你就拿出表来:“您看,如果我把继承改成protected,这里就会报错 C2247,因为BookSystem无法访问Book的protected成员……”——这比讲一百遍“protected是什么”都有说服力。
5.3 多态调度追踪表:用调试器快照证明虚函数生效
实验四的终极验收,是让虚函数调用栈“看得见”。这张表要求你在 VC++6.0 调试模式下,对Book* p = new SpecialBook(); p->display();设置断点,单步进入后截图调用栈,并填写:
| 调试步骤 | 当前执行的函数 | this指针地址 | this指向的实际类型 | 调用栈(从顶到底) |
|---|---|---|---|---|
断点停在p->display()行 | main() | 0x0067FDE0 | SpecialBook | main→?? |
| 单步进入后 | SpecialBook::display() | 0x0067FDE0 | SpecialBook | SpecialBook::display→Book::display→main |
提示:VC++6.0 的调用栈窗口叫
Call Stack(View → Debug Windows → Call Stack),必须确保p是Book*类型指针,而不是SpecialBook对象,否则看不到动态绑定效果。
这张表的魔力在于:它把抽象的“多态”变成了可触摸的内存地址。当老师质疑“你怎么知道调用的是派生类函数”,你直接打开截图:“您看,this地址和p一致,而当前执行函数是SpecialBook::display,调用栈里Book::display在它下面——说明基类函数被覆盖了。”
5.4 一个血泪调试技巧:用Output窗口抓取所有cout的原始字节流
VC++6.0 的Output窗口不仅是编译日志,更是调试神器。很多学生调试display()时只看控制台输出,却忽略了cout的底层行为。我的习惯是:在Book::display()开头加一行:
void Book::display() const { cout << "[DEBUG] Book::display() called, this=" << (void*)this << endl; // ... 原有逻辑 }然后在 VC++6.0 中View → Output,切换到Build标签页(不是Debug)。当程序运行时,所有cout输出会实时出现在这里,且包含完整的换行符和空格。比如你看到:
[DEBUG] Book::display() called, this=0067FDE0 书名: C++ Primer, ISBN: 123456789, 价格: 99.000000元而控制台可能只显示:
书名: C++ Primer, ISBN: 123456789, 价格: 99元这是因为Output窗口不经过 Windows 控制台的字符编码转换,它显示的是cout写入缓冲区的原始字节。当你的中文乱码时,对比Output窗口和控制台的差异,就能精准定位是编码问题还是输出逻辑问题。
从那以后我每次带学生做实验四,都强制他们在display()里加[DEBUG]前缀,并盯着Output窗口看虚函数调用顺序。当SpecialBook::display()的[DEBUG]出现在Book::display()之后,他们眼睛里的光,比任何 PPT 都亮。希望帮到你。
本文还有配套的精品资源,点击获取