类模板与派生1

一、类模板与具体化类的关系

1. 核心概念

类模板是对 "数据类型" 的抽象,允许定义通用类,其成员变量 / 函数的类型可任意指定。

  • 定义语法

    cpp 复制代码
    template<class T>  // 或 template<typename T>,二者基本等价
    class 类名 {
        T member;  // 使用 T 作为类型占位符
    };
  • 原代码示例

    cs 复制代码
    template<class 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. 具体化类(模板实例化)

类模板本身不是具体类,需用具体类型填充模板参数,编译器才会生成针对该类型的具体类(即 "具体化类")。

  • 实例化语法

    复制代码
    类名<具体类型> 对象名;
  • 原代码示例

    复制代码
    Array<int> arr1(10);   // 生成 Array<int> 具体类
    Array<string> arr2(5);  // 生成 Array<string> 具体类
  • 面试重点:模板的 "两次编译"

    1. 第一次编译:检查模板本身的语法错误(如拼写、分号),不生成具体代码。
    2. 第二次编译:实例化时(如 Array<int>),用具体类型替换 T,生成类型相关的具体代码并再次检查。→ 这就是为什么模板代码通常放在头文件中(实例化时需看到完整定义)。

3. 类模板的特化(面试高频考点)

当通用模板对某些特殊类型(如指针、引用)处理不佳时,可提供 "定制化实现",即模板特化

全特化(所有模板参数指定具体类型)

针对 Array<char*> 特化,解决浅拷贝问题:

cpp 复制代码
template<>
class Array<char*> {  // 全特化: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;
    }
};
偏特化(部分模板参数指定类型,或对参数限制)

针对任意指针类型的偏特化:

复制代码
template<class T>
class Array<T*> {  // 偏特化:T 是指针类型
private:
    T** p;
    int size, idx;
public:
    // 类似全特化的深拷贝实现,适用于任意指针类型
};

4. 类模板中的静态成员(面试考点)

类模板的静态成员不是所有具体化类共享,而是每个具体化类有独立的静态成员:

cpp 复制代码
template<class T>
class Test {
public:
    static int count;
    Test() { count++; }
};
template<class T> int Test<T>::count = 0;

int main() {
    Test<int> t1, t2;
    Test<string> t3;
    cout << Test<int>::count << endl;   // 输出 2
    cout << Test<string>::count << endl; // 输出 1
    return 0;
}

5. 类模板的友元函数(面试易错点)

类模板的友元函数若也是模板,需正确前向声明:

cpp 复制代码
template<class T> class Array;  // 前向声明类模板
template<class E> ostream& operator<<(ostream&, Array<E>&); // 前向声明友元函数模板

template<class T>
class Array {
    template<class E>
    friend ostream& operator<<(ostream&, Array<E>&); // 声明友元
private:
    T* p;
    int size, idx;
};

template<class E>
ostream& operator<<(ostream& out, Array<E>& 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 继承后
public public protected private
protected protected protected private
private 不可访问 不可访问 不可访问
  • 原代码示例

    cpp 复制代码
    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 基类指针时,若基类析构非虚,只会调用基类析构,导致派生类资源泄漏!

    解决方法:将基类析构声明为 virtual

    cpp 复制代码
    class 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 覆盖基类虚函数,实现多态
隐藏 基类和派生类(不同作用域) 相同 可同可不同 派生类同名函数隐藏基类的
  • 示例

    cpp 复制代码
    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. 多态与虚函数的底层实现(字节跳动核心考点)

多态分为编译时多态 (重载、模板)和运行时多态(虚函数重写)。

运行时多态的条件
  1. 基类有虚函数(virtual 修饰)。
  2. 派生类重写基类虚函数(函数名、参数、返回值相同,C++11 可加 override 检查)。
  3. 用基类指针 / 引用指向派生类对象。
底层原理:虚函数表(vtable)与虚指针(vptr)
  • 每个含虚函数的类,编译器生成一个虚函数表(vtable),存储该类所有虚函数的地址。

  • 每个对象含一个隐藏的虚指针(vptr),指向该类的 vtable。

  • 示例内存布局

    cpp 复制代码
    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]
  • 面试常问

    1. 虚函数表是每个类一个还是每个对象一个?→ 每个类一个,同一类的所有对象共享。
    2. 构造函数可以是虚函数吗?→ 不可以,构造时 vptr 未初始化,无法实现多态。
    3. 静态函数可以是虚函数吗?→ 不可以,静态函数无 this 指针,无法通过 vptr 调用。

5. 纯虚函数与抽象类(面试考点)

  • 纯虚函数 :基类中声明但不实现的虚函数,语法:virtual 返回值 函数名(参数) = 0;

  • 抽象类 :含至少一个纯虚函数的类,不能实例化对象

  • 派生类必须实现所有纯虚函数,否则也是抽象类。

  • 示例

    cpp 复制代码
    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 虚继承 A(virtual public A),D 中仅保留一份 A 的成员。

  • 示例

    cpp 复制代码
    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;
    }
相关推荐
:1212 小时前
java面试基础2
java·开发语言·面试
云烟成雨TD2 小时前
Spring AI Alibaba 1.x 系列【30】Nacos Skill Registry 的底层设计与实现
java·人工智能·spring
北辰屿风2 小时前
宝塔部署tomcat项目,nginx负载均衡代理访问报错404问题
java·tomcat
鱼鳞_2 小时前
Java学习笔记_Day37(网络编程)
java·网络·笔记·学习
Metaphor6922 小时前
使用 Python 合并 PDF 文件
java·python·pdf
我是无敌小恐龙2 小时前
Java SE 零基础入门Day03 数组核心详解(定义+内存+遍历+算法+实战案例)
java·开发语言·数据结构·人工智能·算法·aigc·动态规划
甘露寺2 小时前
深入理解并发模型:从 Python 的 async/await 到 Java 的虚拟线程与线程池机制
java·开发语言·网络
郝学胜-神的一滴2 小时前
深入理解 epoll_wait:高性能 IO 多路复用核心解密
linux·服务器·开发语言·c++·网络协议