【C++】继承与模板

news2024/10/28 18:38:37

继承

1.继承的概念

概念:继承(inheritace)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称之为派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。继承是类设计层次的复用

//	   派生类  继承方式 基类
class Student:public Person{   
public:
	int _name;
    int _sex;
}
  • 代码复用:通过继承,派生类可以复用基类的代码,减少重复编写相同功能的需要。
  • 扩展性:派生类可以在基类的基础上添加新的成员或方法,扩展功能。
  • 层次结构:继承建立了类之间的“is-a”关系,形成了一个类层次结构,有助于组织和理解代码。

2.继承方式和访问限定符

特征public继承protected继承private继承
基类的public成员变成派生类的public成员派生类的protected成员派生类的private成员
基类的protected成员变成派生类的protected成员派生类的protected成员派生类的private成员
基类的private成员变成只能通过基类接口访问,派生类中不可见只能通过基类接口访问,派生类中不可见只能通过基类接口访问,派生类中不可见
能否隐式向上转换是(但只能在派生类中)

注意:

  • 基类的private成员派生类不可见(无法直接允许访问),但可以使用基类publicprotected成员函数间接访问。
  • 使用关键字class时默认继承方式是private;使用struct时默认继承方式是public最好显示的写出继承方式,以提高代码的可读性和可维护性。
  • public > protected > private,继承方式权限只能缩小不能放大,如基类的public成员的遇到protected继承方式就成了派生类的protected成员。
  • 访问限定符在基类中没有体现,在派生类中才产生区别,这也就是访问限定符产生的原因。
struct BaseStruct {
public:
    int publicMember;
protected:
    int protectedMember;
private:
    int privateMember;
};

class DerivedClass : BaseStruct { // 默认 private 继承
public:
    void accessMembers() {
        publicMember = 1;      // private 继承,基类的 public 成员变为 private
        protectedMember = 2;   // private 继承,基类的 protected 成员变为 private
        // privateMember = 3;  // 无法访问
    }
};

class DerivedPublic : public BaseStruct { // public 继承
public:
    void accessMembers() {
        publicMember = 1;      // 保持 public
        protectedMember = 2;   // 保持 protected
        // privateMember = 3;  // 无法访问
    }
};

3.基类和派生类对象赋值转换

3.1 赋值兼容规则:

  • 子类对象可以赋值给父类对象、指针或引用(称为”切割“或”切片“)。在这种赋值过程中,派生类特有的成员将被忽略,只保留基类部分。
  • 基类对象不能直接赋值给派生对象,因为基类对象不包含派生类新增的成员。
class Person{
public:
    string _name;
    int _sex;
    int _age;
};
class Student : public Person{
public:
    int _No;
};
int main(){
	Student s;
    s.name = "Alice";
    s.age = 20;
    s._No = 12345;
    
	Person p;
    p = s; // 切割,只复制基类部分
    std::cout << p.name << ", " << p.age << std::endl; // 输出:Alice, 20

    Person* ptr = &s; // 多态,ptr 指向 Student 对象
    Person& ref = s;  // 引用,ref 引用 Student 对象  
    
    return 0;
}

image-20240715143458274

3.2 指针和引用的转换:

  • 指针转换

    • 向上转型(Upcasting):将派生类指针转换为基类指针,是隐式且安全的。

    • 向下转型(Downcasting):将基类指针转换为派生类指针,需要使用 dynamic_cast 进行类型检查,确保转换的安全性。

  • 引用转换

  • 类似于指针转换,向上转型是隐式的,而向下转型需要显式的类型转换。

int main() {
    Student s;
    s.name = "Bob";
    s.age = 22;
    s.studentID = 67890;

    Person* basePtr = &s; // 向上转型,隐式转换
    basePtr->introduce(); // 调用基类方法

    // 向下转型,需要使用 dynamic_cast
    Student* derivedPtr = dynamic_cast<Student*>(basePtr);
    if (derivedPtr) {
        derivedPtr->study(); // 调用派生类方法
    }

    return 0;
}

4.继承作用域与成员隐藏

  • 作用域独立:继承中的基类和派生类都有各自独立的作用域,成员隐藏仅在派生类作用域内有效。
  • 成员隐藏只要成员名称相同,无论类型或参数列表,派生类成员都会重定义(隐藏)基类的成员。

解决函数的隐藏与重载

class A {
public:
    virtual void fun() {
        cout << "A::fun()" << endl;
    }
};

class B : public A {
public:
    void fun(int i) { // 重载,不隐藏基类的 fun()
        cout << "B::fun(int): " << i << endl;
    }

    void callBaseFun() {
        A::fun(); // 显式调用基类的 fun()
    }

    void fun() override { // 重定义
        cout << "B::fun()" << endl;
    }
};

int main() {
    B b;
    b.fun();          // 调用 B::fun()
    b.fun(10);        // 调用 B::fun(int)
    b.callBaseFun();  // 调用 A::fun()

    return 0;
}
  • B::fun(int i):重载了 A::fun(),但不隐藏基类的 fun(),在同一作用域内(派生类B中)。
  • B::fun():重定义了 A::fun(),提供了新的实现。

也可以通过using A::fun引入基类的fun(),从而调用A类的fun方法。

重载、重写和重定义的区别

特性重载(Overloading)重写(Overriding)重定义(Hiding)
作用域同一类内派生类和基类之间派生类和基类之间
是否需要继承
关键字virtualoverride
参数列表必须不同必须相同可以不同
返回类型可以不同必须相同(或协变)可以不同
调用时间编译时决定运行时决定编译时决定
用途提供同名函数的不同版本实现多态性派生类中隐藏基类同名函数

5.派生类的默认成员函数

当创建派生类时,编译器会自动为其生成一些默认的成员函数,包括构造函数、拷贝构造函数、赋值运算符和析构函数。这些默认成员函数在大多数情况下是足够的,但在特定需求下,程序员可以显式地定义或删除它们。

默认成员函数的行为

  1. 构造函数
    • 默认构造函数:派生类的构造函数必须调用基类的构造函数以初始化基类部分。如果基类没有默认构造函数,派生类构造函数必须在初始化列表中显式调用基类的其他构造函数。
    • 拷贝构造函数:自动调用基类的拷贝构造函数,完成基类部分的拷贝初始化。
  2. 赋值运算符(operator=
    • 自动调用基类的赋值运算符,完成基类部分的赋值。
  3. 析构函数
    • 派生类的析构函数在执行完自己的清理工作后,自动调用基类的析构函数,确保基类部分的资源得到正确释放。
    • 虚析构函数:如果基类的析构函数是虚函数(virtual),可以确保通过基类指针删除派生类对象时,派生类的析构函数被正确调用,避免资源泄漏。

image-20241027160711419

QQ_1721094131676

#include <iostream>
using namespace std;

class Base {
public:
    Base() { cout << "Base Constructor" << endl; }
    Base(const Base&) { cout << "Base Copy Constructor" << endl; }
    Base& operator=(const Base&) { 
        cout << "Base Assignment Operator" << endl; 
        return *this; 
    }
    virtual ~Base() { cout << "Base Destructor" << endl; }
};

class Derived : public Base {
public:
    Derived() { cout << "Derived Constructor" << endl; }
    Derived(const Derived& d) : Base(d) { cout << "Derived Copy Constructor" << endl; }
    Derived& operator=(const Derived& d) { 
        Base::operator=(d); 
        cout << "Derived Assignment Operator" << endl; 
        return *this; 
    }
    ~Derived() { cout << "Derived Destructor" << endl; }
};

int main() {
    Derived d1;
    Derived d2 = d1; // 拷贝构造
    d2 = d1;         // 赋值运算符
    return 0;
}

/*
输出:
Base Constructor
Derived Constructor
Base Copy Constructor
Derived Copy Constructor
Base Assignment Operator
Derived Assignment Operator
Derived Destructor
Base Destructor
Derived Destructor
Base Destructor
*/

6.友元与静态成员

  • 友元关系不能继承:基类的友元在派生类中不是友元,派生类需要重新声明友元关系。
class Base {
private:
    int secret;
    friend class FriendClass;
};

class Derived : public Base {
    // FriendClass 不是 Derived 的友元
};
  • 静态成员:基类定义了static静态成员,则整个继承体系里面只有一个这样的成员,所有派生类共享这一个静态成员。
class Base {
public:
   static int staticValue;
};

int Base::staticValue = 0;

class Derived1 : public Base {};
class Derived2 : public Base {};

int main() {
   Derived1::staticValue = 5;
   cout << Derived2::staticValue << endl; // 输出:5
   return 0;
}

7.多继承

优点

  • 功能整合:允许派生类同时拥有多个基类的功能,适用于需要结合多种特性的复杂类。
  • 灵活性:提供了更大的设计灵活性,适用于多维度的类层次结构。

缺点

  • 复杂性增加:类层次结构更加复杂,增加了理解和维护的难度。
  • 名称冲突:多个基类中可能存在同名成员,导致名称冲突和二义性。
  • 菱形继承问题:多个基类继承自同一个祖先类,导致数据冗余和二义性。
7.1 单继承与多继承
  • 单继承:一个子类只有一个直接父类时这个继承关系为单继承。
class Person
class Student:public Person
class PostGraduate:public Student
  • 多继承:一个子类有两个或以上的直接父类时成为多继承。
class Teacher
class Student
class Assistant:public Student,public Teacher
7.2 菱形继承

菱形继承:菱形继承是多集成的一种特殊情况。

class Person
class Teacher:public Person
class Student:public Person
class Assistant:public Student,public Teacher

菱形继承的问题

  • 数据冗余:派生类中存在多个基类的副本,导致数据冗余。例如,Assistant 类中会有两份 Person 的成员。
  • 二义性:当访问祖先类的成员时,编译器无法确定访问哪一个基类的成员,导致二义性错误。

img

class Person {
public:
    string _name; 	// 姓名
};
class Student : public Person {
protected:
    int _num; 	//学号
};
class Teacher : public Person {
    int _id;	//职工号   
};
class Assistant : public Student, public Teacher {
protected:
    string _majorCourse;	// 主修课程
};
void Test() {
    // 这样会有二义性无法明确知道访问的是哪一个
    Assistant a;
    // 编译错误:二义性,无法确定是 Student::Person::name 还是 Teacher::Person::name
    //a._name = "peter";

    // 需要显示指定访问那个父类的成员可以解决二义性问题
    // 但是数据冗余问题无法解决
    a.Student::_name = "XXX";
    a.Teacher::_name = "YYY";
}

8.虚拟继承

为了解决菱形继承带来的数据冗余和二义性问题,C++ 引入了虚继承(Virtual Inheritance)。通过虚继承,派生类共享基类的唯一实例,消除数据冗余和二义性。

如在上面Student和Teacher在继承Person时使用虚继承即可解决问题:

class Person {
public:
    std::string name;
};

class Student : virtual public Person {
protected:
    int studentID;
};

class Teacher : virtual public Person {
protected:
    int employeeID;
};

class Assistant : public Student, public Teacher {
protected:
    std::string majorCourse;
};

int main() {
    Assistant a;
    a.name = "Charlie"; // 唯一的 Person::name 成员
    return 0;
}

下面是一个直接继承的例子:

注:由于x86指针采用4字节对齐方式,x64采用8字节对齐方式。方便起见以下全用x86为例

class A {
public:
	int _a;
};
class B : public A {
public:
	int _b;
};
class C : public A {
public:
	int _c;
};
class D : public B, public C {
public:
	int _d;
};
void Test(){
    D d;
    // sizeof(B)=8; sizeof(C)=8
 	// sizeof(D) = sizeof(B)+sizeof(C)+sizeof(_d) = 20
 	cout << "sizeof(d)=" << sizeof(d) << endl;

 	d.B::_a = 1;
	d.C::_a = 2;
 	d._b = 3;
 	d._c = 4;
 	d._d = 5;
    // 编译报错:_a 不明确,需要指明是那个父类下连带的属性,如上
    // d._a = 6;
}

image-20240716161623437

从图中可以见得数据中存在两个_a,造成了数据的冗余和二义性;

下面看一下虚继承的示例:

class A {
public:
	int _a;
};
class B : virtual public A {
public:
	int _b;
};
class C : virtual public A {
public:
	int _c;
};
class D : public B, public C {
public:
	int _d;
};
void Test(){
    D d;
 	cout << "sizeof(d)=" << sizeof(d) << endl;

 	d.B::_a = 1;
	d.C::_a = 2;
 	d._b = 3;
 	d._c = 4;
 	d._d = 5;
    d._a = 6;
}

image-20240716170952184

8.1 虚拟继承工作原理
  • 虚基表指针(VBPtr):编译器为每个类对象添加一个虚基表指针,用于指向虚基类的信息。这与虚函数表指针(Vptr)不同,用于虚函数的多态性。
  • 虚基表(VBTable):虚基表记录虚基类的位置信息和偏移量,确保在派生类中正确访问共享基类成员。
虚拟指针的“三板斧”

由上面例子我们能够发现:但虚拟继承时,原本的父类成员会被替换为一个虚基表指针,这个指针指向一张虚基表,虚基表里存放虚基类与虚基表指针的偏移量。要注意和虚函数表区分开来。

正常继承:(下图中展示的是x64环境下的)

正常继承

虚拟继承:

img

编译器处理虚继承的方法是:编译器在处理虚继承(虚函数)时,会给每个派生类添加一个隐藏成员类似:

class A{
public:
    void *vptr;// 续集表指针 --> 4个字节
    ...
}

同时在编译器会给向派生类的构造函数中安插一条赋值语句 来为vptr赋值,类似:

A(){
    vptr = &A::vftable;
}

虚基类存储在最后继承它的派生类

9.继承与组合

继承和组合都是实现类复用和构建复杂对象的手段,但它们在设计哲学和应用场景上有所不同。

9.1 继承(Inheritance)
  • 关系:表示“is-a”关系,即派生类是基类的一种。例如,StudentPerson
  • 复用方式:派生类通过继承基类的成员,实现代码的复用和扩展。
  • 继承是一种白箱复用,父类对子类基本是透明的,但是它一定程度破坏了父类的封装
  • 优点:
    • 简单直接,适用于明确的类型层次结构。
    • 允许派生类直接访问基类的 publicprotected 成员。
  • 缺点:
    • 高耦合,派生类依赖基类的实现细节。
    • 破坏基类的封装性,基类的改变可能影响派生类。
8.2 组合(Composition)
  • 关系:表示“has-a”关系,即一个类包含另一个类的对象。例如,Car 有一个 Engine
  • 复用方式:通过在类中包含成员对象,实现功能的复用和扩展。
  • 组合式一种黑箱复用,C对D是不透明的,C保持着他的封装。
  • 优点:
    • 低耦合,类之间的依赖关系较弱。
    • 保持了各自类的封装性和独立性。
    • 更加灵活,可以在运行时动态组合不同的组件。
  • 缺点:
    • 需要通过成员对象的接口间接访问功能,可能增加代码复杂性。

示例

  • 继承示例:
// 继承示例
class Engine {
public:
    void start() {
        std::cout << "Engine started." << std::endl;
    }
};

class Car : public Engine { // Car is-a Engine(不符合实际逻辑,仅为示例)
public:
    void drive() {
        start(); // 直接访问 Engine 的成员
        std::cout << "Car is driving." << std::endl;
    }
};

int main() {
    Car car;
    car.start(); // 直接调用 Engine 的方法
    car.drive();
    return 0;
}
/*
输出:
Engine started.
Engine started.
Car is driving.
*/
  • 组合示例:
// 组合示例
class Engine {
public:
    void start() {
        std::cout << "Engine started." << std::endl;
    }
};

class Car {
private:
    Engine engine; // Car has-a Engine
public:
    void drive() {
        engine.start(); // 通过 Engine 的接口访问
        std::cout << "Car is driving." << std::endl;
    }
};

int main() {
    Car car;
    // car.start(); // 错误,Engine 的 start() 是私有的
    car.drive();
    return 0;
}
/*
输出:
Engine started.
Car is driving.
*/

组合的类耦合度更低,而继承的类是一种高耦合。

最佳实践

  • 优先使用组合:当类之间的关系不明确或“has-a”关系更符合实际需求时,优先选择组合。
  • 谨慎使用继承:仅在明确需要“is-a”关系并且派生类确实需要基类的功能时,才使用继承。

面试题

C++的缺陷有哪些?
  1. 复杂性
    • C++ 的语法和特性非常丰富,这使得学习曲线陡峭,容易出错。
    • 复杂的模板和泛型编程可能导致编译错误难以理解。
  2. 内存管理
    • 手动管理内存(如使用 newdelete)容易导致内存泄漏和野指针问题。
    • 虽然 C++11 引入了智能指针(如 unique_ptrshared_ptr),但仍然需要开发者谨慎使用。
  3. 多继承
    • 多继承可能导致复杂的对象模型和潜在的二义性问题。
    • 菱形继承问题是一个典型的多继承问题,需要使用虚继承来解决。
  4. 性能和效率
    • 虽然 C++ 通常被认为是高性能的语言,但某些高级特性(如虚函数、RTTI)可能会引入额外的开销。
    • 优化代码需要深入理解编译器和硬件特性。
  5. 缺乏内置的垃圾回收机制
    • C++ 没有内置的垃圾回收机制,需要手动管理内存,增加了开发复杂度。
  6. 标准库的局限性
    • 标准库虽然强大,但某些领域(如网络编程、GUI 开发)的支持相对薄弱。
    • 第三方库的质量和兼容性参差不齐。
什么是菱形继承?菱形继承的问题是什么?如何解决?虚继承的原理是什么?
  1. 什么是菱形继承?

    菱形继承是一种多继承的情况,其中派生类从两个基类派生,而这两个基类又共同派生自同一个基类。这种继承结构形成了一个菱形的形状。

class A {
public:
    int value;
};
class B : public A {};
class C : public A {};
class D : public B, public C {};
  1. 菱形继承的问题是什么?

    (1) 二义性:

    • 由于 DBC 继承,而 BC 都有一个 A 的子对象,D 会有两个 A 的子对象。
    • 当尝试访问 A 的成员时,编译器无法确定应该使用哪个 A 的子对象,导致二义性问题。

    (2) 对象模型复杂:

    • 菱形继承会导致对象模型变得复杂,增加内存开销和管理难度。
  2. 如何解决菱形继承的问题?

    使用虚继承可以解决菱形继承带来的二义性和对象模型复杂的问题。

class A {
public:
    int value;
};
class B : virtual public A {};
class C : virtual public A {};
class D : public B, public C {};
  1. 虚继承的原理?

    (1) 单一子对象:

    • 虚继承确保派生类中只有一个基类的子对象,而不是多个。
    • 在上述例子中,D 只有一个 A 的子对象,而不是两个。

    (2) 初始化顺序:

    • 虚基类的构造函数会在最派生类的构造函数中被调用,而不是在中间派生类的构造函数中被调用。
    • 这确保了虚基类的子对象在所有派生类的子对象之前被初始化。

    (3) 内存布局:

    • 虚继承会导致对象的内存布局更加复杂,因为编译器需要在对象中添加额外的指针来管理虚基类的子对象。
    • 这可能会引入一定的性能开销。

模版

1.概念和工作原理

1.1 概念:

模版(Template)是C++中实现泛型变成的核心机制。它运行程序员编写与类型无关的代码,通过实例化时指定具体类型,从而实现代码的复用和灵活性。模版主要分为函数模版类模版。模版的设计旨在在不牺牲性能的前提下,实现代码的高度复用。通过在编译时生成针对不同类型的代码,模版避免了运行时的多态开销,同时保持了类型安全。这种编译时多态性与运行时多态性(如虚函数)形成鲜明对比,各有优劣。

1.2 工作原理:

模版在C++中通过实例化机制工作。当编译器遇到模板的使用时,会根据提供的类型参数生成具体的函数或类。这一过程发生在编译期,确保了生成的代码在类型上是正确的。

编译器在编译阶段根据模板参数生成响应的代码,这意味着每个不同的模板参数组合都会生成独立的代码实例。这种机制带了以下优点:

  • 类型安全: 所有类型检查在编译期完成,避免了运行时错误。
  • 性能优化:生成的代码针对特定类型进行了优化,消除了不必要的抽象层。

但同时也存在一些缺点:

  • 编译时间增加:大量的模版实例化可能导致编译时间显著增加。
  • 代码膨胀:每个模版实例化都会生成独立的代码,可能导致可执行文件体积增大。

2.函数模板

2.1 定义与使用

函数模板允许编写与类型无关的函数,通过模板参数在调用时指定具体类型。函数模板的语法以template关键字开头,紧随其后的是模板参数列表。

// 函数模版
template <typename T>
T getMax(T a, T b) {
    return (a > b) ? a : b;
}

int main() {
    cout << getMax<int>(3, 7) << endl;         // 输出:7
    cout << getMax<double>(3.5, 2.5) << endl;  // 输出:3.5
    cout << getMax<char>('g', 'e') << endl;    // 输出:g
    return 0;
}
2.2 模版参数推导

在大多数情况下编译期能够根据函数参数自动推导处模板类型,此时可以不用显式的指定类型。,因此上面对模版的使用还可以这样

int main() {
    cout << getMax(3, 7) << endl;          // 自动推导为 getMax<int>
    cout << getMax(3.5, 2.5) << endl;      // 自动推导为 getMax<double>
    cout << getMax('g', 'e') << endl;      // 自动推导为 getMax<char>
    return 0;
}

注意事项:

  • 如果模版参数无法从函数参数中推导出来,必须显式指定类型。
  • 模版参数推导在函数重载解析中起重要作用,可能影响函数的选择。
2.3 多参数模板

函数模板可以接收多个类型参数,以处理不同类型的参数组合。这为函数的通用性提供了更大的灵活性:

// 多参数函数模版
template <typename T1, typename T2>
auto add(T1 a, T2 b) -> decltype(a + b) {// 根据传入值a、b相加的结果推导类型
    return a + b;
}

int main() {
    cout << add(3, 4.5) << endl;         // 输出:7.5
    cout << add(2.3f, 4) << endl;        // 输出:6.3
    return 0;
}

拓展分析

decltype关键字:是C++11新增的一个关键字,和auto的功能一样,用来编译时期进行自动类型推导,确保返回类型与操作符的实际结果类型一致。它的引入很好的弥补了auto不适用或跟不无法使用的场景。

decltype基本语法:

int a = 10;
decltype(a) b = a; // b的类型与a相同,即int

3.类模板

3.1 定义与使用
#include <iostream>
#include <string>
using namespace std;

// 类模版
template <typename T>
class MyContainer {
private:
    T element;
public:
    MyContainer(T elem) : element(elem) {}
    void display() const {
        cout << "Element: " << element << endl;
    }
};

int main() {
    MyContainer<int> intObj(42);
    intObj.display();  // 输出:Element: 42

    MyContainer<string> strObj("Hello");
    strObj.display();  // 输出:Element: Hello

    return 0;
}
3.2 模板类的默认参数

类模板可以为模板参数指定默认类型,简化实例化时的类型指定。允许在需要时覆盖默认类型,保持代码的通用性。

// 类模版,默认类型为int
template <typename T = int>
class MyContainer {
private:
    T element;
public:
    MyContainer(T elem) : element(elem) {}
    void display() const {
        cout << "Element: " << element << endl;
    }
};

int main() {
    MyContainer<> defaultObj(100); // 默认类型为int
    defaultObj.display();           // 输出:Element: 100

    MyContainer<double> doubleObj(99.99);
    doubleObj.display();            // 输出:Element: 99.99

    return 0;
}

4.模板特化

模板特化允许为特定类型或类型组合提供专门的实现,以满足特殊需求。模板特化分为全特化偏特化

4.1 全特化

全特化为模板所有参数指定具体类型,提供专门的实现。这在处理特定类型时非常有用,例如针对指针类型或某些自定义类型提供不同的行为。

#include <iostream>
using namespace std;

// 原始类模版
template <typename T>
class MyContainer {
private:
    T element;
public:
    MyContainer(T elem) : element(elem) {}
    void display() const {
        cout << "Generic Element: " << element << endl;
    }
};

// 全特化,针对char*类型
template <>
class MyContainer<char*> {
private:
    char* element;
public:
    MyContainer(char* elem) : element(elem) {}
    void display() const {
        cout << "Specialized Element: " << element << endl;
    }
};

int main() {
    MyContainer<int> intObj(10);
    intObj.display();  // 输出:Generic Element: 10

    char msg[] = "Hello, World!";
    MyContainer<char*> charPtrObj(msg);
    charPtrObj.display();  // 输出:Specialized Element: Hello, World!

    return 0;
}
  • 限制: 全特化允许部分模板参数进行特化,适用于部分类型参数的特殊实现。
4.2 偏特化

偏特化允许部分模板参数进行特化,实现更加灵活和细粒度的模板行为。适用于部分类型的特殊实现。偏特化主要用于类模板,函数模板不支持偏特化。

#include <iostream>
#include <string>
using namespace std;

// 原始类模版
template <typename T1, typename T2>
class Pair {
private:
    T1 first;
    T2 second;
public:
    Pair(T1 a, T2 b) : first(a), second(b) {}
    void display() const {
        cout << "Pair: (" << first << ", " << second << ")" << endl;
    }
};

// 偏特化,当T2为char*时
template <typename T1>
class Pair<T1, char*> {
private:
    T1 first;
    char* second;
public:
    Pair(T1 a, char* b) : first(a), second(b) {}
    void display() const {
        cout << "Specialized Pair: (" << first << ", " << second << ")" << endl;
    }
};

int main() {
    Pair<int, double> p1(1, 3.14);
    p1.display();  // 输出:Pair: (1, 3.14)

    char msg[] = "C++ Templates";
    Pair<string, char*> p2("Topic", msg);
    p2.display();  // 输出:Specialized Pair: (Topic, C++ Templates)

    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2225604.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

AI创作者与人类创作者的协作模式

公主请阅 1. AI创作者的崛起1.1 AI创作者的工作原理1.2 AI创作者的优势 2. 人类创作者的独特价值2.1 创造性与情感2.2 伦理与价值观2.3 文化与背景 3. AI与人类的协作模式3.1 协同创作3.2 内容编辑3.3 数据驱动的创作3.4 跨媒体协作 4. AI与人类协作的挑战4.1 技术局限性4.2 版…

Linux学习笔记 | sudo命令的基本使用

sudo命令 sudo 命令是 Unix 和 Linux 系统中用于执行需要超级用户权限&#xff08;即 root 权限&#xff09;操作的工具。它允许系统管理员授予某些用户&#xff08;或用户组&#xff09;以 root 或其他指定用户的权限来运行特定命令&#xff0c;而无需知道 root 用户的密码。…

在Java中,需要每120分钟刷新一次的`assetoken`,并且你想使用Redis作为缓存来存储和管理这个令牌

学习总结 1、掌握 JAVA入门到进阶知识(持续写作中……&#xff09; 2、学会Oracle数据库入门到入土用法(创作中……&#xff09; 3、手把手教你开发炫酷的vbs脚本制作(完善中……&#xff09; 4、牛逼哄哄的 IDEA编程利器技巧(编写中……&#xff09; 5、面经吐血整理的 面试技…

Uni-App-02

条件编译 条件编译概念 不同的运行平台终归有些专有的特性&#xff0c;无法实现跨平台完全兼容&#xff0c;例如&#xff1a;微信小程序导航栏右上角的关闭图标。 uni-app提供了一种“条件编译”机制&#xff0c;可以针对特定的平台编译执行特定的代码&#xff0c;否则不执行。…

高翔【自动驾驶与机器人中的SLAM技术】学习笔记(十二)拓展图优化库g2o(一)框架

【转载】理解图优化&#xff0c;一步步带你看懂g2o框架 文章来源&#xff1a;理解图优化&#xff0c;一步步带你看懂g2o框架 小白&#xff1a;师兄师兄&#xff0c;最近我在看SLAM的优化算法&#xff0c;有种方法叫“图优化”&#xff0c;以前学习算法的时候还有一个优化方法…

Spring Web MVC 入门

1. 什么是 Spring Web MVC Spring Web MVC 是基于 Servlet API 构建的原始 Web 框架&#xff0c;从从⼀开始就包含在Spring框架中。它的 正式名称“SpringWebMVC”来⾃其源模块的名称(Spring-webmvc)&#xff0c;但它通常被称为"Spring MVC". 什么是Servlet呢? Ser…

OpenAI被爆12月发布其Orion AI模型!波兰“OFF”电台解雇所有记者,启用AI“主持人”|AI日报

文章推荐 Stability AI一口气推出3款图像生成模型系列&#xff01;升级版Claude 3.5 Sonnet能像人类一样操控电脑&#xff5c;AI日报 今日热点 据报道&#xff0c;OpenAI计划于12月发布其Orion AI模型 据The Verge昨日报道&#xff0c;OpenAI计划在今年12月之前发布其下一个…

ctfshow(171,172,173)--SQL注入--联合注入

Web171 进入靶场&#xff0c;是一个SQL查询界面&#xff1a; 审计&#xff1a; 查询语句如下&#xff1a; $sql "select username,password from user where username !flag and id ".$_GET[id]." limit 1;";语句功能从数据表user中查询username,pa…

MATLAB生态环境数据处理与分析

原文链接&#xff1a;MATLAB在生态环境数据处理与分析https://mp.weixin.qq.com/s?__bizMzUzNTczMDMxMg&mid2247624407&idx4&sn39c8119bba0797e6bf5cc35eea1c6767&chksmfa8da730cdfa2e266dac5221af101230d7ded29576a34856b31f736a89dbb2e3e481a5e94e8a&to…

日常笔记记录

1、Http 1.1 概念 HTTP 是 HyperText Transfer Protocol&#xff08;超文本传输协议&#xff09;的简写&#xff0c;它是 TCP/IP 协议集中的一个应用层协议&#xff0c;是客户端与服务端进行交互时必须遵循的规则。它用于定义 Web 浏览器与 Web 服务器之间交换数据的过程以及…

【Docker】在AlmaLinux 8.10系统中安装Docker-ce过程分享

随着2024年6月30日&#xff0c;官方停止了对CentOS 7的维护&#xff0c;属于CentOS 7的时代终于结束了。当然&#xff0c;对于CentOS 7的维护停止&#xff0c;大家也不用过度紧张&#xff0c;目前仍有部分Linux版本可以提供企业级的生产可用系统服务&#xff0c;比如&#xff1…

Python记录-字典

定义 Python 中的字典&#xff08;dictionary&#xff09;是一种内置的数据结构&#xff0c;用于存储键值对&#xff08;key-value pairs&#xff09;。字典中的每个键&#xff08;key&#xff09;都是唯一的&#xff0c;并且与一个值&#xff08;value&#xff09;相关联。键…

vue3学习(二)一款优秀的编辑器

开源项目&#xff1a; https://github.com/Leecason/element-tiptap doc需要改成document&#xff0c;改完之后依然有问题&#xff0c;应该是对vue3兼容不好&#xff0c; 所以在issue中有人回复使用 https://github.com/okijhhyu/element-tiptap-vue3 经过测试&#xff0c;确实…

shiro会话管理和加密

Shiro 会话管理和加密 会话管理 缓存 加密 会话管理 Shiro提供了完整的企业级会话管理功能&#xff0c;不依赖于底层容器&#xff08;如Tomcat&#xff09;&#xff0c;不管是J2SE还是J2EE环境都可以使用&#xff0c;提供了会话管理&#xff0c;会话事件监听&#xff0c;会话存…

【自动化测试之oracle数据库】MacOs如何安装oracle- client

操作系统为Mac OS&#xff0c;本地在pycharm上跑自动化脚本时&#xff0c;因为有操作oracle数据库的部分&#xff0c;所以需要安装oracle数据库的客户端&#xff0c;并install cx_oracle,本文主要介绍如何在macOS上完成安装&#xff0c;并在python自动化测试代码中配置&#xf…

vue3项目中引入阿里图标库

开篇 本篇的主题是在vue3项目中引入阿里图标库 步骤 注册阿里图标库账号(阿里图标)&#xff0c;并创建项目 将图标加入项目中 将需要的图标先加入购物车&#xff0c;随后加入到项目中 生成项目代码 在项目中生成项目代码&#xff0c;便于后续复制到vue项目中 ## 在vue3项目…

信息安全入门——网络安全威胁

目录 前言网络安全威胁概览悄无声息的数据泄露——被动攻击明目张胆的破坏行为——主动攻击网路世界的瘟疫——病毒总结 前言 在数字化时代&#xff0c;信息安全成为了我们每个人都不得不面对的重要议题。网络安全威胁无处不在&#xff0c;它们可能来自网络的暗角&#xff0c;…

MySQL 9从入门到性能优化-慢查询日志

【图书推荐】《MySQL 9从入门到性能优化&#xff08;视频教学版&#xff09;》-CSDN博客 《MySQL 9从入门到性能优化&#xff08;视频教学版&#xff09;&#xff08;数据库技术丛书&#xff09;》(王英英)【摘要 书评 试读】- 京东图书 (jd.com) MySQL9数据库技术_夏天又到了…

【51单片机】第一个小程序 —— 点亮LED灯

学习使用的开发板&#xff1a;STC89C52RC/LE52RC 编程软件&#xff1a;Keil5 烧录软件&#xff1a;stc-isp 开发板实图&#xff1a; 文章目录 单片机介绍LED灯介绍练习创建第一个项目点亮LED灯LED周期闪烁 单片机介绍 单片机&#xff0c;英文Micro Controller Unit&#xff0…

信息安全工程师(68)可信计算技术与应用

前言 可信计算技术是一种计算机安全体系结构&#xff0c;旨在提高计算机系统在面临各种攻击和威胁时的安全性和保密性。 一、可信计算技术的定义与原理 可信计算技术通过包括硬件加密、受限访问以及计算机系统本身的完整性验证等技术手段&#xff0c;确保计算机系统在各种攻击和…