突破编程_C++_高级教程(模板编程的基础知识)
1 模板编程的基本概念
C++ 的模板编程是一种编程技术,它允许程序员编写处理不同类型数据的通用代码。通过使用模板,可以创建与特定数据类型无关的函数或类,这些函数或类在编译时可以根据需要生成特定数据类型的版本。这增加了代码的复用性、灵活性和类型安全性。
1.1 模板编程的本质
从本质上来说, C++ 的模板编程是一种编译时多态性( compile-time polymorphism )的机制。在 C++ 中,多态性通常指的是以统一的方式处理不同类型的数据或对象的能力。运行时多态性( runtime polymorphism )通常通过虚函数实现,它允许在运行时根据对象的实际类型来调用不同的函数。而编译时多态性则通过模板实现,它允许在编译时根据所使用的数据类型来生成不同的代码。
模板编程的本质可以归结为以下几点:
类型参数化
模板允许程序员定义接受类型作为参数的函数或类。这些类型参数在模板实例化时被具体的数据类型所替换,从而生成特定类型的代码。这种类型参数化使得代码能够以一种类型无关的方式编写,提高了代码的复用性和灵活性。
编译时生成
模板实例化发生在编译时,编译器根据提供的类型参数生成特定类型的函数或类的代码。这意味着在编译时就确定了函数或类的具体实现,而不是在运行时。这种编译时生成的方式使得模板编程具有很高的性能优势。
类型安全
由于模板实例化是在编译时进行的,编译器能够对类型进行严格的检查,确保类型安全。这意味着在编译时就能够发现类型错误,而不是等到运行时才出现错误。
泛型编程
模板编程是一种泛型编程的形式,它允许程序员编写与具体数据类型无关的通用代码。这种泛型编程的能力使得代码更加简洁、易读和可维护。
元编程能力
模板编程还具有元编程的能力,即利用模板在编译时进行编程。这包括模板特化、模板偏特化、递归模板等技术,它们允许程序员在编译时进行复杂的逻辑判断和代码生成。
综上所述, C++ 的模板编程的本质是一种编译时多态性的机制,它允许程序员以类型无关的方式编写通用代码,并在编译时生成特定类型的代码。这种机制提高了代码的复用性、灵活性和性能,同时保证了类型安全。通过模板编程,程序员能够编写出更加优雅、高效和可维护的代码。
1.2 模板编程的应用场景
C++ 模板编程支持使用通用代码处理多种数据类型。这种编程模式在多个应用场景中都非常有用,以下是 C++ 模板编程的一些典型应用场景:
数据类型与算法相分离的泛型编程
这是模板编程最常见的应用之一。通过将数据类型与算法分离,我们可以实现泛型编程,使代码能够处理多种数据类型而无需重复编写。例如,在STL(标准模板库)中,容器(如std::vector
、std::list
)和算法(如std::sort
、std::find
)都是通过模板实现的,这使得它们可以与任何数据类型一起使用,从而大大提高了代码的重用性。
类型适配( Traits )
类型适配是模板编程中的另一个重要概念。通过使用特性( Traits )模板,我们可以根据数据类型提供特定的类型信息或行为。这在需要根据数据类型执行不同操作时非常有用。例如,根据数据类型是否为指针或引用,可以提供不同的类型适配实现。
函数转发
函数转发是 C++11 引入的一种新特性,它允许我们编写能够转发其参数给另一个函数的模板函数。这在创建通用函数包装器或代理时非常有用。通过使用函数转发,我们可以编写一个模板函数,该函数能够将其参数以正确的方式转发给另一个函数,而无需知道目标函数的具体签名。
元编程
模板元编程是 C++ 模板编程的高级应用之一。它允许我们在编译时执行一系列的逻辑判断和计算。元编程通常用于常量计算、类型操作和策略模式等场景。例如,可以使用递归模板来实现编译时的阶乘计算,或者使用模板特化来实现不同类型的特定行为。
编译时容器与算法
通过模板元编程,我们还可以实现编译时的容器和算法。这意味着在编译阶段就可以生成针对特定数据类型的容器和算法实现,从而在运行时获得更高的性能。例如,我们可以使用模板元编程实现编译时的静态数组和排序算法。
策略模式
策略模式是一种设计模式,它允许程序在运行时根据需要选择不同的算法实现。通过模板编程,可以实现类似的功能,但在编译时进行选择。这有助于提高代码的灵活性和可扩展性。
跨平台编程
在跨平台编程中,由于不同平台可能使用不同的数据类型和大小,使用模板可以确保代码在不同平台上的兼容性和可移植性。通过编写与平台无关的模板代码,我们可以更容易地在多个平台上编译和运行程序。
综上所述, C++ 模板编程在多个应用场景中都发挥着重要作用,包括数据类型与算法相分离的泛型编程、类型适配、函数转发、元编程、编译时容器与算法、策略模式以及跨平台编程等。这些应用场景共同展示了模板编程在提高代码重用性、类型安全性、性能和灵活性方面的优势。
2 函数模板
函数模板是一种特殊的函数定义,它使用模板参数来指定函数可以处理的数据类型。模板参数在函数定义中以类型参数的形式出现,它们可以是任何有效的 C++ 数据类型,包括内置类型和用户定义的类型。
2.1 函数模板的定义和实例化
函数模板的定义包含了一个或多个类型参数,这些类型参数在函数定义中以 typename 或 class 关键字进行声明。这些类型参数在函数体内部被当作普通的数据类型来使用。
函数模板的定义
函数模板的一般定义形式如下:
template <typename T1, typename T2, ..., typename Tn>
return_type functionName(parameterList)
{ // 函数体
}
其中 T1, T2, …, Tn 是类型参数,代表可以是任何数据类型的占位符。return_type 是函数的返回类型, functionName 是函数的名称, parameterList 是函数的参数列表。
如下是一个简单的函数模板示例,该模板定义了一个加法函数:
template <typename T>
T add(const T& a, const T& b)
{ return a+b;
}
在上面代码中, T 是一个类型参数,它可以是任何数据类型。 add 函数接受两个类型为 T 的引用参数,并返回它们相加的值。
函数模板的实例化
当函数模板被调用时,编译器会根据提供的实际参数类型来推断类型参数的具体类型,并生成一个具体的函数实例,这个过程称为函数模板的实例化。
例如,如果使用 int 类型来调用上面的 add 函数模板:
int a = 1;
int b = 2;
int sum = add(a, b); // 这里会实例化一个 int 类型的 add 函数
编译器会生成一个专门处理 int 类型的 add 函数实例:
int add(const int& a, const int& b)
{ return a+b;
}
同样地,如果使用 double 类型来调用 add 函数模板,编译器会生成一个处理 double 类型的函数实例。
显式实例化
除了隐式地通过函数调用进行实例化外,我们还可以显式地要求编译器为特定的类型生成函数模板的实例。这通常在编译时性能优化或者某些特殊场景下是有用的。
显式实例化的语法如下:
template void functionName<T>(parameterList);
对于上面的 add 函数模板,如果想要显式地实例化一个处理 int 类型的版本,可以这样做:
template int add<int>(const int&, const int&);
显式实例化通常是在头文件中完成的,以确保在多个源文件中使用时链接器可以找到正确的实例。
2.2 函数模板的自动类型推导
函数模板的自动类型推导是一种编译器特性,它允许在调用函数模板时自动确定模板参数的类型。这种自动类型推导机制极大地简化了代码,并提高了代码的可读性和可维护性。
在调用一个函数模板时,编译器会根据传递给函数的实际参数来推导模板参数的类型。这个过程通常被称为模板参数的类型推导或类型推断。
2.2.1 自动类型推导的基本使用
如下为样例代码:
#include <iostream> template <typename T>
T add(const T& a, const T& b)
{return a + b;
}int main()
{int sum1 = add(1, 2);double sum2 = add(1.1, 2.1);return 0;
}
在上面代码中,add 是一个函数模板,它接受两个类型为 T 的参数,并返回它们的和。类型 T 是一个模板参数,代表可以是任何数据类型的占位符。
当调用这个函数模板时,编译器会自动推导类型 T 。在第一个调用 add(1, 2) 中,编译器能够推导出 T 的类型为 int,因为两个参数都是整数。类似地,在第二个调用 add(1.1, 2.1) 中,编译器推导出 T 的类型为 double ,因为两个参数都是浮点数。类型推导的规则通常是针对直观的数据类型,但是当涉及到引用、指针、模板中包含多个类型时,上面代码中的简单自动类型推导有可能会无法满足。此时就需要使用 C++11 的新特性来处理。
2.2.2 使用 decltype 与 auto
C++11 及其之后的版本引入了更强大的类型推导机制,即 decltype 和 auto 的联合使用,可以进一步简化代码并提高类型推导的灵活性。
如下为样例代码:
#include <iostream> template <class A, class B>
auto add(A a, B b) -> decltype(a + b)
{return a + b;
}int main()
{auto sum1 = add(1, 2);auto sum2 = add(1.1, 2.1);return 0;
}
在上面中,decltype(a + b) 允许编译器推导返回值的类型,而不仅仅是函数参数的类型。在这个场景下,sum1 与 sum2 的类型分别被推导为 int 与 double 。
C++14 支持的语法 decltype(auto) 更为简洁,可以将上面代码中的函数模板修改为:
template <class A, class B>
decltype(auto) add(A a, B b)
{return a + b;
}
使用 decltype 与 auto 尤其对模板函数中引用与 const 限定符的处理更为有效,如下为样例代码:
#include <iostream> template <typename T>
void func(T&& t)
{decltype(t) val = t; // a 的类型与 t 相同,包括引用和 const 限定符 // ...
}int main()
{int a = 1;const int& b = a;func(a); // t 的类型为 int& , val 的类型也为 int& func(b); // t 的类型为 const int& , val 的类型也为 const int& return 0;
}
在这个例子中,函数模板 func 接受一个右值引用参数 t ,并使用 decltype(t) 来声明一个局部变量 val ,其类型与 t 完全相同,包括引用和 const 限定符。这允许函数模板在处理不同类型的参数时保持更高的灵活性。
2.3 函数模板的显式类型指定
函数模板的显式类型指定是指在调用函数模板时明确指定模板参数的类型。这通常在希望消除类型推导的歧义或明确指定一个类型而不是让编译器自动推导时非常有用。
使用显式类型指定的语法是在函数名后面的尖括号 < > 中直接列出模板参数类型。这允许你在调用模板函数时提供具体的类型,即使这些类型可以从传递给函数的参数中推导出来。
如下为样例代码:
#include <iostream>
#include <typeinfo> template <typename T1, typename T2>
decltype(auto) add(T1 t1, T2 t2)
{return t1 + t2;
}int main()
{auto sum1 = add(1, 2);printf("type of sum1 is %s\n", typeid(sum1).name());auto sum2 = add<double, double>(1, 2);printf("type of sum2 is %s\n", typeid(sum2).name());return 0;
}
上面代码的输出为:
type of sum1 is int
type of sum2 is double
在这个例子中,add 是一个函数模板,它接受类型为 T1 、 T2 的参数。在 main 函数中,首先以正常方式调用 add 函数,让编译器从传递给函数的参数中推导出 T 的类型,最后的返回类型为 int 。然后,显式地指定了 T 的类型,最后的返回类型为 double 。
2.4 函数模板的特化
函数模板的特化(Function Template Specialization)是C++模板编程中的一个重要概念,它支持为特定的类型或一组类型提供定制的模板实现。特化版本的函数模板会覆盖通用模板的版本,当使用特化类型调用函数模板时,将使用特化版本的实现。
函数模板的特化通常指的是完全特化(类模板同时支持完全特化与部分特化),但是可以通过为特定的类型组合创建新的函数模板或重载现有的函数来实现部分特化的效果。
完全特化是指为函数模板提供一个完全限定的类型实现。这意味着为特定的类型提供了一个独立的实现,这个实现将仅适用于该类型。完全特化的语法与通用模板的语法类似,但在模板参数列表中使用具体的类型替代类型参数。
如下为样例代码:
#include <iostream> // 通用模板
template <typename T1, typename T2>
decltype(auto) add(T1 t1, T2 t2)
{printf("call general template\n");return t1 + t2;
}// 特化模板,仅适用于 int , int 类型
template <>
decltype(auto) add<int>(int t1, int t2)
{printf("call specialized template for int , int\n");return t1 + t2;
}int main()
{ // 调用特化模板auto sum1 = add(1, 2);// 调用通用模板auto sum2 = add(1.2, 2.3);return 0;
}
上面代码的输出为:
call specialized template for int , int
call general template
在这个例子中,当 add 函数以 int 类型调用时,将使用完全特化版本的实现。对于其他类型,如 double 或 std::string ,将使用通用模板的实现。
由于函数模板不支持部分特化,如果需要为一组类型提供特定的实现,通常需要通过重载函数来实现类似的效果。这些函数具有与通用模板相同的名称,但参数类型不同。
如下为样例代码:
#include <iostream> // 通用模板
template <typename T1, typename T2>
decltype(auto) add(T1 t1, T2 t2)
{printf("call general template\n");return t1 + t2;
}// 重载函数,仅适用于 int ,int 类型
decltype(auto) add(int t1, int t2)
{printf("call overloaded function for int , int\n");return t1 + t2;
}// 重载函数,仅适用于 double ,int 类型
decltype(auto) add(double t1, int t2)
{printf("call overloaded function for double , int\n");return t1 + t2;
}int main()
{ // 调用重载函数:int ,intauto sum1 = add(1, 2);// 调用重载函数:double ,intauto sum2 = add(1.2, 2);// 调用通用模板auto sum3 = add(1.2, 2.3);return 0;
}
上面代码的输出为:
call overloaded function for int , int
call overloaded function for double , int
call general template
3 类模板
类模板允许定义可以在实例化时进行指定数据类型的类。换句话说,类模板是一个参数化类型,它使用一个或多个参数来创建一系列类。
类模板的主要优势在于,它可以减少代码重复,提高编程效率。通过为一系列仅成员数据类型不同的类创建一个类模板,程序员只需提供一套程序代码,就可以生成多种具体的类,这些类可以看作是类模板的实例。
3.1 类模板的定义和实例化
类模板的定义类似于函数模板的定义。在类模板中,可以指定一个或多个类型参数,这些类型参数在实例化时将被实际的数据类型替代。
类模板的一般定义形式如下:
template <typename T> // T 是一个类型参数
class MyClass
{
public:MyClass(T val) : m_val(val) {} // 构造函数也使用类型参数 T void printVal() {std::cout << "value: " << m_val << std::endl;}private:T m_val; // 成员变量使用类型参数 T
};
在这个例子中,MyClass 是一个类模板,它有一个类型参数 T 。T 是一个占位符,代表一种未指定的数据类型。在类模板的定义中,可以像使用普通数据类型一样使用 T 。
要使用类模板,需要创建一个或多个该模板的实例。实例化类模板时,需要为模板参数提供具体的数据类型。这可以通过在类模板名称后的尖括号 < > 中指定类型来完成。针对上面定义的模板类,可以做如下实例化:
int main()
{// 实例化 MyClass 模板,T 被替换为 int MyClass<int> obj1(1);obj1.printVal(); // 输出: value: 1 // 实例化 MyClass 模板,T 被替换为 string MyClass<std::string> obj2("hello");obj1.printVal(); // 输出: value: hello return 0;
}
在上面代码中,为 MyClass 模板提供了两种数据类型:int 和 std::string。每次提供一个新的数据类型,编译器都会创建一个新的类类型。因此,MyClass<int> 和 MyClass<std::string> 是两种不同的类类型,它们有各自的对象实例和方法。
注意事项
(1)类模板的实例化是隐式的,也就是说,当创建一个对象时,编译器会自动处理模板的实例化。
(2)模板参数 T 在类模板的实例化时被实际类型替换,这种替换是在编译时完成的,因此不会增加运行时开销。
(3)可以为类模板定义多个类型参数,例如 template <typename T1, typename T2> ,这样就可以在类中使用两种不同类型的数据。
(4)类模板的实例化会产生新的类类型,这些类型之间是相互独立的,除了它们共享相同的模板定义外。
3.2 类模板的构造函数和析构函数
在类模板中,构造函数和析构函数的定义与处理常规类的方式类似。类模板的构造函数用于初始化模板类的对象,而析构函数用于在对象生命周期结束时释放资源。
构造函数
构造函数是特殊类型的成员函数,它在创建类的新对象时自动调用。类模板的构造函数在实例化时会用实际的类型参数替换模板参数,以便正确地进行初始化。
下面是一个类模板的例子,其中包含了构造函数:
template <typename T>
class MyClass {
public:// 构造函数 MyClass(T val) : m_val(val) {}// 其他成员函数... private:T m_val;
};
在这个例子中,MyClass 的构造函数接受一个类型为 T 的参数 val ,并用它来初始化私有成员变量 m_val 。
析构函数
析构函数是当对象的生命周期结束时自动调用的特殊成员函数。在类模板中,析构函数通常用于释放对象可能拥有的任何资源,如动态分配的内存。
下面是一个类模板的例子,其中包含了析构函数:
template <typename T>
class MyClass
{
public:// 构造函数 MyClass(T val) : m_val(new T(val)) { }// 析构函数 ~MyClass() {delete m_val;}// 其他成员函数...
private:T* m_val;
};
在这个例子中,MyClass 的析构函数使用 delete 释放了动态分配的内存。
实例化时的构造函数和析构函数调用
当类模板被实例化并创建对象时,相应的构造函数会被调用。同样,当对象离开其作用域或被显式删除时,析构函数会被调用。针对上面定义的模板类,可以做如下实例化的调用:
int main()
{// 实例化 MyClass 模板,T 被替换为 int MyClass<int> obj(1); // 调用 MyClass<int> 的构造函数 // ... obj 被使用 ... // 当 obj 离开作用域时,MyClass<int> 的析构函数会被调用 return 0;
}
在上面代码中,obj 是 MyClass 类型的一个对象。当 obj1 被创建时,MyClass 的构造函数会被调用,而当 obj 离开其作用域时,MyClass 的析构函数会被调用。
注意:类模板的构造函数和析构函数在处理资源管理和初始化/清理工作时与常规类相同,只是它们需要能够处理模板参数 T 所代表的不同数据类型。
3.3 类模板的成员变量和成员函数
在类模板中,成员变量和成员函数的概念与常规类中的相同。成员变量用于存储类的实例的状态,而成员函数则定义了可以对这些状态执行的操作。
成员变量
类模板的成员变量通常使用模板参数 T(或其他模板参数)作为它们的类型。这些变量在类的所有实例化中都是私有的、受保护的或公开的,具体取决于它们的访问修饰符。
下面是一个类模板的例子,其中包含了由不同访问修饰符修饰的成员变量:
template <typename T>
class MyClass
{// 构造函数和其他成员函数... // 成员变量
private:// 私有成员变量 T m_privateVar;protected:// 受保护成员变量 T m_protectedVar;public:// 公开成员变量 T m_publicVar;
};
在上面代码中,m_privateVar、m_protectedVar 和 m_publicVar 都是使用模板参数 T 类型的成员变量。它们的访问级别分别是私有、受保护和公开。
成员函数
类模板的成员函数定义了可以在类的实例上执行的操作。这些函数可以访问类的成员变量,并且可以使用模板参数 T(或其他模板参数)来执行类型无关的操作。
如下为样例代码:
template <typename T>
class MyClass
{
public:// 构造函数 MyClass(T val) : m_val(val) {}// 成员函数 void setVal(T val) { m_val = val; }T getVal() const { return m_val; }// 其他成员函数...
private:T m_val;
};
在上面代码中,setVal 和 getVal 都是成员函数。setVal 接受一个类型为 T 的参数,并设置 value 成员变量的值。getVal 则返回 value 的当前值,并且由于它被声明为 const,所以不能修改类的状态。
成员函数的重载
与常规类一样,也可以在类模板中重载成员函数。这意味着可以定义多个具有相同名称但参数不同的成员函数。
如下为样例代码:
template <typename T1, typename T2>
class MyClass
{
public:// 重载的成员函数 void setVal(T1 val) { m_val1 = val; }// 重载的成员函数 void setVal(T2 val) { m_val2 = val; }// 其他成员函数...
private:T1 m_val1;T2 m_val2;
};
在上面代码中,setVal 被重载了两次:一次接受 T1 类型的参数,另一次接受 T2 类型的参数。根据传递给函数的参数类型,编译器会选择适当的函数版本进行调用。
3.4 类模板的特化和偏特化
类模板的特化和偏特化是 C++ 模板编程中的两个重要概念,它们允许为特定的类型或一组类型提供定制的模板实现。
特化
特化是指为模板提供一个完整的替代实现,该实现仅适用于一个特定的类型。当需要要改变某个类型在模板中的行为时,则可以为这个类型创建一个特化版本。特化版本会覆盖模板的通用实现。
如下为样例代码:
template <typename T>
class MyClass
{ // 通用实现
}; // 特化版本,仅适用于int类型
template <>
class MyClass<int>
{ // int类型的特化实现
};
在上面代码中,MyClass 是一个模板类,它有一个通用实现。然后为 int 类型创建了一个特化版本,该版本将替代通用实现。
偏特化
偏特化允许为模板提供一个定制的实现,该实现适用于一组特定的类型。与特化不同,偏特化不需要指定所有的模板参数。
如下为样例代码:
template <typename T1, typename T2>
class MyClass
{ // 通用实现
}; // 偏特化版本,仅适用于T1为int,T2为任意类型的情况
template <typename T2>
class MyClass<int, T2>
{ // int, T2类型的偏特化实现
};
在上面代码中,为 MyClass 创建了一个偏特化版本,该版本仅当第一个模板参数 T1 是 int 类型时适用,而第二个模板参数 T2 可以是任意类型。
注意事项
(1)特化和偏特化必须在模板定义之后声明。
(2)特化和偏特化不能与原始模板在同一个头文件中定义。
(3)偏特化不能比原始模板更加通用。例如,如果原始模板接受两个类型参数,偏特化就不能只接受一个。
(4)偏特化在编译时的优先级高于特化,也高于原始模板。
使用特化和偏特化可以显著提高模板的灵活性和效率,但也需要谨慎使用,以避免产生复杂性和维护问题。
3.5 类模板与继承
模板类的继承与常规类的继承非常相似。你可以定义一个模板类作为基类,然后创建另一个模板类或非模板类来继承它。这种继承允许派生类继承基类的成员变量和成员函数,同时还可以添加或覆盖成员。
模板类继承非模板类
首先,一个模板类可以继承一个非模板类。这种情况下,模板类将继承非模板类的所有成员。
如下为样例代码:
#include <iostream> class MyClassWithNonTemplate
{
public:void withoutTemplateFunc(){printf("function without template\n");}
};template <typename T>
class MyClassWithTemplateDerived : public MyClassWithNonTemplate
{
public:void withTemplateFunc(){printf("function with template for type : %s\n", typeid(T).name());}
};int main() {MyClassWithTemplateDerived<int> obj;obj.withoutTemplateFunc(); // 调用继承自非模板基类的成员函数 obj.withTemplateFunc(); // 调用模板派生类的成员函数 return 0;
}
上面代码的输出为:
function without template
function with template for type : int
模板类继承模板类
更常见的是,一个模板类可以继承另一个模板类。在这种情况下,派生类模板可以添加、覆盖或使用基类模板的成员。
如下为样例代码:
#include <iostream> template <typename T>
class MyClassBase
{
public:void baseFunction(){printf("base type : %s\n", typeid(T).name());}
};template <typename T1, typename T2=int>
class MyClassDerived : public MyClassBase<T2>
{
public:void derivedFunction(){printf("derived type : %s, base type : %s\n", typeid(T1).name(), typeid(T2).name());}
};int main() {MyClassDerived<double> obj;obj.baseFunction(); // 调用模板基类的成员函数 obj.derivedFunction(); // 调用模板派生类的成员函数 return 0;
}
上面代码的输出为:
base type : int
derived type : double, base type : int
在这个例子中,MyClassDerived 继承自 MyClassBase,并且 MyClassBase 是一个模板类。MyClassDerived 可以选择性地提供模板参数 T2,如果没有提供,则默认为 int。
注意事项
(1)模板参数传递:当模板类继承另一个模板类时,可以选择传递或省略模板参数。在上面的例子中,MyClassDerived 选择了传递一个模板参数 T1,并且为 T2 提供了一个默认值 int。
(2)访问控制:继承的规则(公有继承、保护继承、私有继承)同样适用于模板类。公有继承允许派生类访问基类的公有和保护成员;保护继承允许派生类访问基类的公有和保护成员,但将这些成员视为保护成员;私有继承允许派生类访问基类的公有和保护成员,但将这些成员视为私有成员。
(3)特化和偏特化:当涉及到模板类的继承时,特化和偏特化的规则同样适用。可以为基类模板或派生类模板提供特化或偏特化版本。
(4)虚函数和纯虚函数:如果基类模板包含虚函数或纯虚函数,派生类可以选择覆盖这些函数或提供自己的实现。这对于创建模板类和派生类的抽象基类尤其有用。
相关文章:
突破编程_C++_高级教程(模板编程的基础知识)
1 模板编程的基本概念 C 的模板编程是一种编程技术,它允许程序员编写处理不同类型数据的通用代码。通过使用模板,可以创建与特定数据类型无关的函数或类,这些函数或类在编译时可以根据需要生成特定数据类型的版本。这增加了代码的复用性、灵…...
胆小勿入!AI创作恐怖电影宣传片《生化危机:重生》
胆小勿入!AI创作恐怖电影宣传片《生化危机:重生》 "The city is falling, and the dead walk among us." "In the shadow of the apocalypse, the fight for survival begins." "The streets are silent, but the nightmare …...
HTTP 超文本传送协议
1 超文本传送协议 HTTP HTTP 是面向事务的 (transaction-oriented) 应用层协议。 使用 TCP 连接进行可靠的传送。 定义了浏览器与万维网服务器通信的格式和规则。 是万维网上能够可靠地交换文件(包括文本、声音、图像等各种多媒体文件)的重要基础。 H…...
MySQL导入/导出数据
MySQL导入/导出数据 文章目录 MySQL导入/导出数据一、MySQL 导入数据1、mysql 命令导入2、source 命令导入3、使用 LOAD DATA 导入数据4、使用 mysqlimport 导入数据4.1、mysqlimport的常用选项介绍 二、MySQL 导出数据1、使用 SELECT ... INTO OUTFILE 语句导出数据2、mysqldu…...
Matplotlib初探:认识数据可视化与Matplotlib
Matplotlib初探:认识数据可视化与Matplotlib Fig.1 利用Matplotlib进行数据可视化( 可视化代码见文末) 🌵文章目录🌵 🌳引言🌳🌳一、数据可视化简介🌳🌳二、Matplotlib库简介&#x…...
LeetCode 0987.二叉树的垂序遍历:遍历时存节点信息,遍历完自定义排序
【LetMeFly】987.二叉树的垂序遍历:遍历时存节点信息,遍历完自定义排序 力扣题目链接:https://leetcode.cn/problems/vertical-order-traversal-of-a-binary-tree/ 给你二叉树的根结点 root ,请你设计算法计算二叉树的 垂序遍历…...
TCP 和 UDP的区别
文章目录 概述区别UDPTCPTCP与UDP的选择UDP和TCP编程区别 概述 TCP(Transmission Control Protocol,传输控制协议)和 UDP(User Datagram Protocol,用户数据报协议)是互联网中两种最常用的传输层协议 总的来…...
Python 将一维数组或矩阵变为三维
Python 将一维数组或矩阵变为三维 正文 正文 话不多说直接上代码: import numpy as npsampling_points 10001arr np.linspace(0, 2, sampling_points) arr_3D arr.reshape(1, 1, -1) print(arr_3D) """ result: [[[0.0000e00 2.0000e-04 4.0000…...
Python如何实现定时发送qq消息
因为生活中老是忘记各种事情,刚好又在学python,便突发奇想通过python实现提醒任务的功能(尽管TIM有定时功能),也可定时给好友、群、讨论组发送qq消息。其工作流程是:访问数据库提取最近计划——>根据数据…...
支付方式接入:支付宝、微信支付、微软支付
支付方式接入:支付宝、微信支付、微软支付 1、微信支付-接入指引 2、支付宝-接入指引 3、微软支付-接入指引 3.1、使用visual studio打包应用(发布到微软市场):Package a desktop app from source code using Visual Studio -…...
C++中的互斥量
互斥量是一个类,互斥量的使用必须引入头文件#include <mutex>。互斥量就如同一把锁,在同一时间,多个线程都可以调用lock成员函数尝试给这把锁头加锁,但是只有一个线程可以成功给这把锁加锁,其他没有加锁成功的线…...
盲盒小程序开发
现如今,盲盒已经成为了市场上不可忽视的新型消费模式,并且也逐渐遍布在全球各地中。盲盒的种类商品也逐渐丰富完善,不在局限于性价比高的盲盒玩具、手办等,也发展到了美妆、电子、食品等行业,具有较大的实用性和收藏价…...
安装 Windows 10
1.镜像安装 镜像安装:安装Windows 10 2.安装过程(直接以图的形式呈现) 选择专业版的 等待安装即可...
C++文件操作->文本文件(->写文件、读文件)、二进制文件(->写文件、读文件)
#include<iostream> using namespace std; #include <fstream>//头文件包含 //文本文件 写文件 void test01() { //1.包含头文件 fstream //2.创建流对象 ofstream ofs; //3.指定打开方式 ofs.open("test.txt", ios::out); //4.写…...
Mac相关问题
Mac 更新node版本 第一步,先查看本机node.js版本: node -v 第二步,清除node.js的cache: sudo npm cache clean -f 第三步,安装 n 工具,这个工具是专门用来管理node.js版本的,别怀疑这个工具…...
Python爬虫之Splash详解
爬虫专栏:http://t.csdnimg.cn/WfCSx Splash 的使用 Splash 是一个 JavaScript 渲染服务,是一个带有 HTTP API 的轻量级浏览器,同时它对接了 Python 中的 Twisted 和 QT 库。利用它,我们同样可以实现动态渲染页面的抓取。 1. 功…...
Deep深度系统下载安装Beyond compare4
Beyond Compare 4下载和安装 1、在线安装 Debian, Ubuntu安装命令: wget https://www.scootersoftware.com/bcompare-4.4.6.27483_amd64.deb sudo apt update sudo apt install ./bcompare-4.4.6.27483_amd64.deb Redhat Enterprise Linux, Fedora, CentOS安装命令…...
Qt 使用QScintilla 编辑lua 脚本
需求: 利用QScintilla 编辑lua 脚本 步骤: 1,下载 QScintilla Riverbank Computing | Download 2, 打开 src/qscintilla.pro 文件 编译出 dll库 3,工程中引入这个库 注意debug 模式 必须加载debug 版本编译的库࿰…...
2022长安杯复现
案件情况 某地警方接到受害人报案称其在某虚拟币交易网站遭遇诈骗,该网站号称使用“USTD 币”购买所谓的“HT 币”,受害人充 值后不但“HT 币”无法提现、交易,而且手机还被恶意软件锁定 勒索。警方根据受害人提供的虚拟币交易网站调取了对应…...
Netty Review - NioEventLoopGroup源码解析
文章目录 概述类继承关系源码分析小结 概述 EventLoopGroup bossGroup new NioEventLoopGroup(1); EventLoopGroup workerGroup new NioEventLoopGroup();这段代码是在使用Netty框架时常见的用法,用于创建两个不同的EventLoopGroup实例,一个用于处理连…...
团队配置管理规范浅见
在一段时间的工作过程中配置管理工作确实对我们的生产活动产生了巨大的工作量,现在就这个工作来进行梳理一下。 本文主要分为两部分: 1、借用软件系统分析师的配置管理部分内容来介绍配置管理的工作(原谅时间精力有限,原文基本已…...
「算法」二分查找1:理论细节
🎇个人主页:Ice_Sugar_7 🎇所属专栏:算法详解 🎇欢迎点赞收藏加关注哦! 二分查找算法简介 这个算法的特点就是:细节多,出错率高,很容易就写成死循环有模板,但…...
【网络安全】什么样的人适合学?该怎么学?
有很多想要转行网络安全或者选择网络安全专业的人在进行决定之前一定会有的问题: 什么样的人适合学习网络安全?我适不适合学习网络安全? 当然,产生这样的疑惑并不奇怪,毕竟网络安全这个专业在2017年才调整为国家一级…...
从零开始学习数据结构—【链表】—【探索环形链的设计之美】
环形链表 文章目录 环形链表1.结构图2.具体实现2.1.环形链表结构2.2.头部添加数据2.2.1.具体实现2.2.2.测试添加数据 2.3.尾部添加数据2.3.1.具体实现2.3.2.添加测试数据 2.4.删除头部数据2.4.1.具体实现2.4.2.测试删除数据 2.5.删除尾部数据2.5.1.具体实现2.5.2.测试删除数据 …...
AJAX——HTTP协议
1 HTTP协议-请求报文 HTTP协议:规定了浏览器发送及服务器返回内容的格式 请求报文:浏览器按照HTTP协议要求的格式,发送给服务器的内容 1.1 请求报文的格式 请求报文的组成部分有: 请求行:请求方法,URL…...
java面试微服务篇
目录 目录 SpringCloud Spring Cloud 的5大组件 服务注册 Eureka Nacos Eureka和Nacos的对比 负载均衡 负载均衡流程 Ribbon负载均衡策略 自定义负载均衡策略 熔断、降级 服务雪崩 服务降级 服务熔断 服务监控 为什么需要监控 服务监控的组件 skywalking 业务…...
JS进阶——垃圾回收机制以及算法
版权声明 本文章来源于B站上的某马课程,由本人整理,仅供学习交流使用。如涉及侵权问题,请立即与本人联系,本人将积极配合删除相关内容。感谢理解和支持,本人致力于维护原创作品的权益,共同营造一个尊重知识…...
【快速解决】python项目打包成exe文件——vscode软件
目录 操作步骤 1、打开VSCode并打开你的Python项目。 2、在VSCode终端中安装pyinstaller: 3、运行以下命令使用pyinstaller将Python项目打包成exe文件: 其中your_script.py是你的Python脚本的文件名。 4、打包完成后,在你的项目目录中会…...
数据结构——lesson3单链表介绍及实现
目录 1.什么是链表? 2.链表的分类 (1)无头单向非循环链表: (2)带头双向循环链表: 3.单链表的实现 (1)单链表的定义 (2)动态创建节点 &#…...
中科大计网学习记录笔记(八):FTP | EMail
前言: 学习视频:中科大郑烇、杨坚全套《计算机网络(自顶向下方法 第7版,James F.Kurose,Keith W.Ross)》课程 该视频是B站非常著名的计网学习视频,但相信很多朋友和我一样在听完前面的部分发现信…...
网站都是用html做的吗/万能软文范例800字
在我之前的文章 “Elasticsearch:Elasticsearch 中的数据强制匹配” 中,我详述了如何使用 coerce 属性来强制匹配一个不同的数据类型。在今天的文章中,我们来展示 Elasticsearch 是如何把一个浮点数保存到一个不同的数据类型中,比…...
联想桥做网站公司/网络营销培训机构
JDK1.6SQL2005tomcat6.0加载JDBC2.0驱动 出现这些错误: org.apache.jasper.JasperException: java.lang.UnsupportedOperationException: 此驱动程序不支持 Java Runtime Environment (JRE) 1.6 版。请使用支持 JDBC 4.0 的 sqljdbc4.jar 类库。 org.apache.jasper.…...
古色古香网站模板/网络营销推广专员
Log4j的配置 1、新建Java Project>>新建package>>新建java类; 2、下载log4j的jar包,导入到java项目; 3、新建log4j.properties,置于project根目录下; 4、在main()中,加载log4j:Prop…...
加工网平台/百度seo多少钱一个月
最近几年经常在网络上看到各种程序员鄙视链,其实真实情况是我们都很忙,每天 Bug 都调不玩,哪里有空鄙视这,鄙视那。所以很多的的鄙视链大家自己看看,笑一笑就过了,不要太过认真。1、穿衣服也是有讲究的2、不…...
郑州专业做网站的/seo搜索引擎优化方法
学生管理系统:学习了一点文件指针的操作和链表操作,以前总想搞下子,刚好碰到同学要做这个,自己瞎搞了一通。 实现功能:数据添加,查找,删除,插入,修改只是在查找加几句就没…...
门户网站建设项目招标/手机自己怎么建电影网站
1. 显示系统中全部Android平台: android list targets 2. 显示系统中全部AVD(模拟器): android list avd 3. 创建AVD(模拟器): android create avd --name 名称 --target 平台编号 4. 启动模拟器…...