Qt 方向实训 · 第 3 / 4 天 · 讲:练 ≈ 1:2.7
模块 A · 讲授 35min
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
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
仿照 class_template_basics.cpp,
写一个 Pair<T1, T2> 或简易 Stack<T>(push/pop/top,
栈空时的行为要明确定义)。
用 Day2 写的多态类层次(基类指针数组),对每个元素做
dynamic_cast 判断具体类型,转型失败(返回 nullptr)的分支要写出来,
不能假设转型一定成功。参照 dynamic_cast_demo.cpp。
阅读 class_template_derives_class.cpp 与 class_template_derives_template.cpp, 写一句话总结「普通类继承类模板」与「类模板继承类模板」在写法上的区别。
模块 B · 讲授 35min(三块内容合并速览)
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"; } }
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
// 谓词(函数对象)驱动算法
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.cpp、 p01/ch09/s04/predicate.cpp
模块 B · 练习 95min(任选其二,第三个留参考)
给 Day1/Day2 写的类加一个自定义异常类(继承 std::exception),
非法参数(如负数年龄)抛出异常,调用方 catch 后打印信息、程序不崩溃。
参照 custom_exception_class.cpp。
用 vector 存一组「学生成绩」,用 sort +
自定义谓词按成绩排序,再用 find_if 找出所有不及格的学生并打印。
参照 predicate.cpp。
参照 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 容器/算法解决实际小问题的程序。
明日预告:Day 4 —— Qt 桌面全景(1 天快速建立骨架认知 + 动手做一个可跑的小工具)