类模板与派生1
一、类模板与具体化类的关系1. 核心概念类模板是对 “数据类型” 的抽象允许定义通用类其成员变量 / 函数的类型可任意指定。定义语法templateclass T // 或 templatetypename T二者基本等价 class 类名 { T member; // 使用 T 作为类型占位符 };原代码示例templateclass T class Array { private: T* p; // 通用类型指针 int size, idx; public: Array(int max_size) : size(max_size) { p new T[max_size]{ T() }; // 调用 T 的默认构造 idx 0; } void add(T item); // 通用类型参数 };2. 具体化类模板实例化类模板本身不是具体类需用具体类型填充模板参数编译器才会生成针对该类型的具体类即 “具体化类”。实例化语法类名具体类型 对象名;原代码示例Arrayint arr1(10); // 生成 Arrayint 具体类 Arraystring arr2(5); // 生成 Arraystring 具体类面试重点模板的 “两次编译”第一次编译检查模板本身的语法错误如拼写、分号不生成具体代码。第二次编译实例化时如Arrayint用具体类型替换 T生成类型相关的具体代码并再次检查。→ 这就是为什么模板代码通常放在头文件中实例化时需看到完整定义。3. 类模板的特化面试高频考点当通用模板对某些特殊类型如指针、引用处理不佳时可提供 “定制化实现”即模板特化。全特化所有模板参数指定具体类型针对Arraychar*特化解决浅拷贝问题template class Arraychar* { // 全特化T char* private: char** p; int size, idx; public: Array(int max_size) : size(max_size) { p new char*[max_size]{ nullptr }; idx 0; } void add(char* item) { if (idx size) return; // 深拷贝分配新内存并拷贝字符串内容 p[idx] new char[strlen(item) 1]; strcpy(p[idx], item); idx; } ~Array() { for (int i 0; i idx; i) delete[] p[i]; delete[] p; } };偏特化部分模板参数指定类型或对参数限制针对任意指针类型的偏特化templateclass T class ArrayT* { // 偏特化T 是指针类型 private: T** p; int size, idx; public: // 类似全特化的深拷贝实现适用于任意指针类型 };4. 类模板中的静态成员面试考点类模板的静态成员不是所有具体化类共享而是每个具体化类有独立的静态成员templateclass T class Test { public: static int count; Test() { count; } }; templateclass T int TestT::count 0; int main() { Testint t1, t2; Teststring t3; cout Testint::count endl; // 输出 2 cout Teststring::count endl; // 输出 1 return 0; }5. 类模板的友元函数面试易错点类模板的友元函数若也是模板需正确前向声明templateclass T class Array; // 前向声明类模板 templateclass E ostream operator(ostream, ArrayE); // 前向声明友元函数模板 templateclass T class Array { templateclass E friend ostream operator(ostream, ArrayE); // 声明友元 private: T* p; int size, idx; }; templateclass E ostream operator(ostream out, ArrayE arr) { out length: arr.idx , max size: arr.size endl; for (int i 0; i arr.idx; i) out arr.p[i] ; return out; }二、继承关系1. 继承的语法与访问限定面试必问语法class 派生类名 : 继承方式 基类名 { // 派生类成员 };访问权限对比表必须背熟表格基类成员访问限定public 继承后protected 继承后private 继承后publicpublicprotectedprivateprotectedprotectedprotectedprivateprivate不可访问不可访问不可访问原代码示例class Computer { private: float price; // 派生类内部也不可访问 protected: int cid; // 派生类内部可访问 public: Computer(int cid, float price) : cid(cid), price(price) {} void work(string task) { cout cid 正在完成 task endl; } }; class DellComputer : private Computer { // private继承 public: DellComputer(int cid, float price, int memory) : Computer(cid, price) {} void updateCid(int cid) { this-cid cid; } // 可访问基类 protected 成员 };2. 派生类的构造与析构面试重点构造顺序先基类构造 → 再派生类构造。析构顺序先派生类析构 → 再基类析构与构造顺序相反。面试重中之重虚析构函数若用基类指针指向派生类对象delete 基类指针时若基类析构非虚只会调用基类析构导致派生类资源泄漏解决方法将基类析构声明为virtualclass Person { public: virtual ~Person() { cout Person析构 endl; } // 虚析构 }; class Student : public Person { private: int* data; public: Student() { data new int[10]; } ~Student() { delete[] data; cout Student析构 endl; } }; int main() { Person* p new Student(); delete p; // 先调用 Student 析构再调用 Person 析构资源正确释放 return 0; }3. 重写、重载、隐藏的区别面试必问必须区分概念作用域函数名参数列表虚函数要求作用重载同一类的同一作用域相同不同无同名函数的不同实现重写基类和派生类不同作用域相同相同基类需 virtual覆盖基类虚函数实现多态隐藏基类和派生类不同作用域相同可同可不同无派生类同名函数隐藏基类的示例class Base { public: void func(int a) { cout Base::func(int) endl; } // 重载1 void func(double a) { cout Base::func(double) endl; } // 重载2 virtual void virtualFunc() { cout Base::virtualFunc() endl; } }; class Derived : public Base { public: void func(int a) { cout Derived::func(int) endl; } // 隐藏基类 func void virtualFunc() override { cout Derived::virtualFunc() endl; } // 重写 }; int main() { Derived d; d.func(1); // 调用 Derived::func(int)隐藏 d.Base::func(1.5); // 显式调用基类 func(double) Base* p d; p-virtualFunc(); // 多态调用 Derived::virtualFunc() return 0; }4. 多态与虚函数的底层实现字节跳动核心考点多态分为编译时多态重载、模板和运行时多态虚函数重写。运行时多态的条件基类有虚函数virtual修饰。派生类重写基类虚函数函数名、参数、返回值相同C11 可加override检查。用基类指针 / 引用指向派生类对象。底层原理虚函数表vtable与虚指针vptr每个含虚函数的类编译器生成一个虚函数表vtable存储该类所有虚函数的地址。每个对象含一个隐藏的虚指针vptr指向该类的 vtable。示例内存布局class Base { public: virtual void func1() {} virtual void func2() {} }; class Derived : public Base { public: void func1() override {} // 重写 func1 virtual void func3() {} // 新增虚函数 };Base 对象[vptr] → Base vtable: [Base::func1, Base::func2]Derived 对象[vptr] → Derived vtable: [Derived::func1, Base::func2, Derived::func3]面试常问虚函数表是每个类一个还是每个对象一个→ 每个类一个同一类的所有对象共享。构造函数可以是虚函数吗→ 不可以构造时 vptr 未初始化无法实现多态。静态函数可以是虚函数吗→ 不可以静态函数无 this 指针无法通过 vptr 调用。5. 纯虚函数与抽象类面试考点纯虚函数基类中声明但不实现的虚函数语法virtual 返回值 函数名(参数) 0;。抽象类含至少一个纯虚函数的类不能实例化对象。派生类必须实现所有纯虚函数否则也是抽象类。示例class Shape { // 抽象类 public: virtual double getArea() 0; // 纯虚函数 virtual ~Shape() {} }; class Circle : public Shape { private: double r; public: Circle(double r) : r(r) {} double getArea() override { return 3.14 * r * r; } // 实现纯虚函数 }; class Rectangle : public Shape { private: double w, h; public: Rectangle(double w, double h) : w(w), h(h) {} double getArea() override { return w * h; } // 实现纯虚函数 }; int main() { Shape* s1 new Circle(5); Shape* s2 new Rectangle(3, 4); cout s1-getArea() endl; // 多态Circle::getArea() cout s2-getArea() endl; // 多态Rectangle::getArea() delete s1; delete s2; return 0; }6. 菱形继承与虚继承面试难点菱形继承问题基类 A 被 B、C 继承D 继承 B、C导致 D 中有两份 A 的成员二义性 数据冗余。解决方法B、C 虚继承 Avirtual public AD 中仅保留一份 A 的成员。示例class A { public: int a; }; class B : virtual public A {}; // 虚继承 class C : virtual public A {}; // 虚继承 class D : public B, public C {}; int main() { D d; d.a 10; // 无歧义仅一份 a cout d.a endl; // 10 return 0; }