Day 3

泛型与标准库

Qt 方向实训 · 第 3 / 4 天 · 讲:练 ≈ 1:2.7

  • 模块 A(3 课时):模板 + 类型转换
  • 模块 B(3 课时):异常 + IO 流 + STL

模块 A · 讲授 35min

模板 + 类型转换

  • 函数模板 vs 类模板
  • 模板的局限性
  • 四种显式类型转换(static / dynamic / const_cast)

类模板:类型不能自动推导


template<class NameType, class AgeType>
class Person{
public:
    Person(NameType name, AgeType age){ mName = name; mAge = age; }
    void showPerson(){ cout << mName << " " << mAge << endl; }
    NameType mName; AgeType mAge;
};

// Person P1("德玛西亚", 18);         // 非法——类模板不支持类型自动推导
Person<string, int> P1("德玛西亚", 18);  // 必须显式写出模板实参
    

与函数模板最大的区别:函数模板能从实参推导类型,类模板不能——这是常见误用点。 出处:p01/ch05/class_template_basics.cpp

dynamic_cast:运行期检查的向下转型


class Animal { public: virtual void ShowName() = 0; };   // 必须有虚函数(多态类型)
class Dog : public Animal { virtual void ShowName(){ /*...*/ } };
class Other { public: void PrintSomething(){} };          // 与 Animal 无继承关系

Dog* dog01 = new Dog;
Animal* animal02 = dynamic_cast<Animal*>(dog01);   // 子类→父类,合法(其实用不着 dynamic_cast)

// Dog* dog02 = dynamic_cast<Dog*>(animal01);      // 父类→子类:运行期检查,失败返回 nullptr
// Animal* x = dynamic_cast<Animal*>(other);       // 无继承关系:编译期报错
    

dynamic_cast 依赖 RTTI,只对多态类型(含虚函数)生效——这是与 static_cast 最本质的区别。出处:p01/ch06/dynamic_cast_demo.cpp

模块 A · 练习 95min

动手:模板与类型转换

任务卡 1 · 写一个通用容器模板

仿照 class_template_basics.cpp, 写一个 Pair<T1, T2> 或简易 Stack<T>(push/pop/top, 栈空时的行为要明确定义)。

任务卡 2 · dynamic_cast 安全向下转型

用 Day2 写的多态类层次(基类指针数组),对每个元素做 dynamic_cast 判断具体类型,转型失败(返回 nullptr)的分支要写出来, 不能假设转型一定成功。参照 dynamic_cast_demo.cpp

任务卡 3 · 选做:类模板的派生关系

阅读 class_template_derives_class.cppclass_template_derives_template.cpp, 写一句话总结「普通类继承类模板」与「类模板继承类模板」在写法上的区别。

模块 B · 讲授 35min(三块内容合并速览)

异常 + IO 流 + STL

  • 异常:栈解旋、自定义异常类
  • 文件 IO:文本 vs 二进制
  • STL:容器 + 迭代器 + 算法 + 函数对象

栈解旋(stack unwinding)


class Person{
public:
    Person(string name){ mName = name; cout << mName << "对象被创建!\n"; }
    ~Person(){ cout << mName << "对象被析构!\n"; }
    string mName;
};
void TestFunction(){
    Person p1("aaa"), p2("bbb"), p3("ccc");
    throw 10;   // 抛出异常时,p3/p2/p1 按逆序自动析构——这就是「栈解旋」
}
int main(){ try{ TestFunction(); } catch(...){ cout<<"异常被捕获!\n"; } }
    

出处:p01/ch07/stack_unwinding.cpp

自定义异常类:继承 std::exception


class MyOutOfRange : public exception{
public:
    MyOutOfRange(const string errorInfo){ m_Error = errorInfo; }
    virtual const char* what() const noexcept { return m_Error.c_str(); }
    string m_Error;
};
// 多个 catch 分支各自匹配不同的异常类型(都直接继承 exception,彼此是平级、不是父子)
// Person(151) 这里实际只会抛 MyOutOfRange,前两个 catch 分支不会命中——
// 写多个 catch 分支是为了同一段代码能同时应对「标准库异常」和「自定义异常」两种来源
try{ Person p(151); }
catch (out_of_range& e){ cout << e.what(); }      // 若改成 throw out_of_range(...) 会走这里
catch (MyOutOfRange e){ cout << e.what(); }        // 本例实际命中的分支
    

强化点:catch 分支的匹配顺序只在「类型存在继承关系」时才重要(子类 catch 必须写在 父类前面,否则父类分支会先吞掉子类异常);这里三个类型互不继承,顺序其实不影响本例结果。 出处:p01/ch07/custom_exception_class.cpp

二进制文件读写


ofstream osm("person.txt", ios::out | ios::binary);
osm.write((const char*)&p1, sizeof(Person));   // 直接写内存布局,不做任何格式转换
osm.close();

ifstream ism("person.txt", ios::in | ios::binary);
Person p3;
ism.read((char*)&p3, sizeof(Person));          // 按同样的 sizeof 读回——结构必须一致
    

二进制 IO 要求读写两端的结构体内存布局完全一致(含 padding),跨平台/跨编译器 不保证兼容。出处:p01/ch08/binary_file_io.cpp

STL:容器 + 迭代器 + 算法 + 函数对象


// 谓词(函数对象)驱动算法
class GreaterThenFive{ public: bool operator()(int num){ return num > 5; } };
vector<int>::iterator it = find_if(v.begin(), v.end(), GreaterThenFive());

class MyCompare{ public: bool operator()(int a,int b){ return a > b; } };
sort(v.begin(), v.end(), MyCompare());   // 用函数对象改变算法策略——从小到大 → 从大到小
    

STL 的核心思想:容器负责存储,算法负责操作,迭代器是两者之间的胶水, 函数对象(谓词)负责定制算法行为。出处: p01/ch09/s03/vector_iterator.cppp01/ch09/s04/predicate.cpp

模块 B · 练习 95min(任选其二,第三个留参考)

动手:异常 / STL / 文件 IO

任务卡 1 · 给类加异常处理

给 Day1/Day2 写的类加一个自定义异常类(继承 std::exception), 非法参数(如负数年龄)抛出异常,调用方 catch 后打印信息、程序不崩溃。 参照 custom_exception_class.cpp

任务卡 2 · vector + 算法解决实际问题

vector 存一组「学生成绩」,用 sort + 自定义谓词按成绩排序,再用 find_if 找出所有不及格的学生并打印。 参照 predicate.cpp

任务卡 3 · 结构体数组的二进制读写

参照 binary_file_io.cpp,把一个结构体数组 (如 5 个学生记录)写入二进制文件,再读出并逐条打印校验,确认与写入前一致。

当日产出验证

验收标准

cmake --build build --target all_p01_ch05
cmake --build build --target all_p01_ch07
cmake --build build --target all_p01_ch09_s04

全绿;至少 1 个模板类 + 1 个用 STL 容器/算法解决实际小问题的程序。

返回目录