C++ 模板是一种通用编程工具,它允许程序员编写可重用的代码,以在不同的数据类型上进行操作。
(简单来说,模板就是建立通用的模具,大大提高复用性)
模板允许程序员编写通用的代码,以适应多种数据类型和需求。通过使用模板参数,可以在函数模板或类模板中处理不同类型的数据,而无需为每种类型编写单独的实现。这极大地提高了代码的可重用性和灵活性。
C++编译器可以在编译时对模板进行类型检查,确保代码在运行时没有类型错误。这使得在使用模板时可以避免一些常见的运行时错误,如类型不匹配、类型转换错误等。静态类型检查还可以提供更好的编程安全性和代码质量。
C++模板使用延迟实例化的机制,即只有在实际使用模板时才会进行实例化。这意味着模板不会引入额外的开销,直到真正需要使用模板的功能时才会生成相应的代码。这种延迟实例化的机制使得模板可以有效地处理各种数据类型而不会产生额外的性能损失。
C++模板还提供了一种元编程的能力,即在编译时生成代码。利用模板的特性,程序员可以在编译期间进行一些复杂的计算和逻辑操作,并将其结果作为编译时常量或类型的一部分。这种元编程能力为编写高度灵活和性能优化的代码提供了很大的空间
泛型编程是C++中另一种重要的编程思想,它主要利用了模板技术。泛型编程旨在实现通用的、与具体数据类型无关的算法和数据结构。通过使用模板,我们可以编写可以适用于不同数据类型的通用代码,从而提高代码的可重用性和灵活性。
通过使用模板参数,我们可以将类型相关的细节推迟到编译时处理,并通过实例化模板生成针对特定类型的代码。这种方式使得我们能够以一种抽象的方式编写代码,从而实现对不同类型的操作。
通过编写通用的数据结构和算法,我们可以在不需要重复编写代码的情况下针对不同的数据类型进行操作。这不仅减少了代码的冗余,还提高了开发效率。
如容器类(例如vector、list等)、算法库(例如排序、搜索等)和函数对象(Functors)等。STL(Standard Template Library)就是C++标准库中基于泛型编程思想的一个重要组成部分。
泛型编程是利用模板技术实现的一种编程思想,它旨在实现通用的算法和数据结构。通过将类型相关的细节推迟到编译期处理,泛型编程提供了高效的代码复用和灵活性。
函数模板是C++中一种特殊类型的模板,它允许我们编写通用的函数定义,以处理不同类型的数据。函数模板使用了参数化的方式,使得函数可以接受不同类型的参数,从而实现代码的通用性和灵活性。
函数模板通过在函数声明和定义中使用通用的类型参数(也称为模板参数)来实现。这些模板参数可以用作函数的
参数类型、返回类型或局部变量类型
等。当我们调用函数模板时,编译器会根据实际传入的参数类型实例化模板,并生成对应的函数定义。
template <typename T>
返回类型 函数名(参数列表) {
// 函数体
}
其中,template
关键字用于声明一个模板,并且
指定了模板参数列表,T 是我们自己定义的类型参数。可以使用关键字 class
替代 typename
。
自动类型推导更为简洁,而显式指定类型可以提供更明确的类型控制
编译器会根据函数参数的类型推断出要使用的模板类型。在这种情况下,不需要显式地指定模板类型,编译器会自动选择合适的函数定义。
template <typename T>
T add(T a, T b) {
return a + b;
}
int main() {
int result1 = add(3, 5); // 自动类型推导为 add(3, 5),结果为 8
double result2 = add(4.2, 1.5); // 自动类型推导为 add(4.2, 1.5),结果为 5.7
return 0;
}
通过在函数调用时显式地指定模板类型,来实例化特定的函数定义。
template <typename T>
T add(T a, T b) {
return a + b;
}
int main() {
int result1 = add<int>(3, 5); // 显式指定类型为 int,结果为 8
double result2 = add<double>(4.2, 1.5); // 显式指定类型为 double,结果为 5.7
return 0;
}
在使用自动类型推导实例化函数模板时,编译器会根据传入的参数类型来推断模板参数的数据类型 T。如果无法推导出一致的数据类型 T,则会导致编译错误。
template <typename T>
T maximum(T a, T b) {
return a > b ? a : b;
}
使用自动类型推导实例化该函数模板时,参数的类型必须一致,在数据类型不同的情况下会导致编译错误
int main() {
int result1 = maximum(3, 5); // 自动类型推导为 maximum(3, 5),结果为 5
double result2 = maximum(4.2, 1.5); // 自动类型推导为 maximum(4.2, 1.5),结果为 4.2
char result3 = maximum('a', 'b'); // 错误!无法推导出一致的数据类型T
return 0;
}
模板参数列表是指在函数模板定义中用于声明和定义模板参数的部分。函数模板的模板参数列表中只能包含模板参数的名称和模板参数的约束(如果有的话),而不可以指定默认参数值。这是因为在使用函数模板时,编译器需要根据参数的类型进行模板实例化,并根据实际提供的参数来确定模板参数的具体值。然而,对于函数模板的函数参数列表(即在模板定义中的函数参数部分),可以有默认参数。默认参数允许在调用函数模板时省略某些参数,而使用预先定义的默认值。
#include
template <typename T>
void print(T value, int width = 10) {
std::cout.width(width);
std::cout << value << std::endl;
}
int main() {
print("Hello"); // 使用默认的宽度(10)进行输出
print(3.14159, 5); // 使用自定义的宽度(5)进行输出
return 0;
}
函数模板的重载规则与普通函数的重载规则类似。
template <typename T>
T maximum(T a, T b) {
return a > b ? a : b;
}
template <typename T>
T maximum(T a, T b, T c) {
return maximum(maximum(a, b), c); // 调用了上面定义的函数模板
}
int maximum(int a, int b) {
return a > b ? a : b;
}
int main() {
int result1 = maximum<int>(3, 5); // 实例化第一个函数模板,结果为 5
double result2 = maximum<double>(4.2, 1.5); // 实例化第一个函数模板,结果为 4.2
int result3 = maximum<int>(3, 5, 8); // 实例化第二个函数模板,结果为 8
int result4 = maximum(3, 5); // 调用了普通函数 maximum(int, int),结果为 5
return 0;
}
匹配度/空模板参数列表 > 普通函数 > 函数模板
通过使用空模板参数列表,我们可以明确地指示编译器实例化函数模板,以确保调用的是函数模板而不是普通函数。
myPrint<>(a, b);
建立一个通用类,类中的成员 数据类型可以不具体制定,用一个虚拟的类型来代表。
template <typename T>
class ClassName {
// 类成员声明和定义
};
其中,template 表示定义一个类模板,并使用 T 作为类型参数。T 可以被替换为任意类型名称,用于表示类中的成员类型、函数参数类型或返回类型等。
在类模板中,可以像定义普通类一样,声明和定义成员函数、数据成员和类型。在这些成员的声明和定义中,可以使用 T 来表示类型参数。
一共有三种传入方式
void printPerson1(Person<string, int>& p) {
// 函数实现
}
template <class T1, class T2>
void printPerson2(Person<T1, T2>& p) {
// 函数实现
}
template<typename T>
class MyClass {
// 类模板定义
};
template<typename T>
void myFunction(MyClass<T> obj) {
// 函数操作 MyClass 类型的对象
}
int main() {
MyClass<int> myObj;
myFunction(myObj); // 将 MyClass 作为函数参数传递
return 0;
}
类模板的继承和普通类的继承基本相同,只不过需要指明基类是一个类模板。
// 定义一个类模板
template<typename T>
class BaseClass {
public:
T data;
BaseClass(T value) : data(value) {}
void printData() {
cout << "Base Data: " << data << endl;
}
};
// 派生类从类模板 BaseClass 继承
template<typename T>
class DerivedClass : public BaseClass<T> {
public:
DerivedClass(T value) : BaseClass<T>(value) {}
void printDerivedData() {
cout << "Derived Data: " << (this->data * 2) << endl;
}
};
int main() {
// 实例化一个基类对象
BaseClass<int> baseObj(10);
baseObj.printData(); // 输出: Base Data: 10
// 实例化一个派生类对象
DerivedClass<int> derivedObj(5);
derivedObj.printData(); // 输出: Base Data: 5
derivedObj.printDerivedData(); // 输出: Derived Data: 10
return 0;
}
类模板中成员函数类外实现时,需要加上模板参数列表
// 定义一个类模板
template<typename T>
class MyClass {
public:
T data;
MyClass(T value) : data(value) {}
void printData(); // 在类外声明成员函数
};
// 在类外实现成员函数,需要在函数名称前加上模板参数列表
template<typename T>
void MyClass<T>::printData() {
cout << "Data: " << data << endl;
}
int main() {
// 实例化一个类模板对象
MyClass<int> obj(10);
// 调用成员函数
obj.printData(); // 输出: Data: 10
return 0;
}
class MyClass {
public:
// 定义全局函数,并将其声明为友元函数
friend void myGlobalFunction(MyClass& obj) {
// 在函数内部可以直接访问类的私有成员和保护成员
obj.privateVar = 10;
}
private:
int privateVar;
public:
void setPrivateVar(int value) {
privateVar = value;
}
};
class MyClass;
// 声明全局函数的原型
void myGlobalFunction(MyClass& obj);
class MyClass {
public:
// 其他类成员和方法的定义
private:
int privateVar;
public:
void setPrivateVar(int value) {
privateVar = value;
}
// 友元函数的声明
friend void myGlobalFunction(MyClass& obj);
};
// 在类外实现全局函数
void myGlobalFunction(MyClass& obj) {
// 在全局函数内部可以访问类的私有成员和保护成员
obj.privateVar = 10;
}
类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到
将文件后缀名更改为.hpp 是一种常见的约定,用于表示该文件是一个 C++ 头文件。虽然这只是一种约定,而不是强制要求,但它有助于标识文件类型和提高代码的可读性。在C++开发中,通常使用不同的文件后缀名来区分源代码文件和头文件。常见的约定是将源代码文件命名为以.cpp或.c为后缀的文件,并将头文件命名为以.hpp或.h为后缀的文件。
person.hpp
#pragma once
#include
using namespace std;
#include
template<class T1, class T2>
class Person {
public:
Person(T1 name, T2 age);
void showPerson();
public:
T1 m_Name;
T2 m_Age;
};
//构造函数 类外实现
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age) {
this->m_Name = name;
this->m_Age = age;
}
//成员函数 类外实现
template<class T1, class T2>
void Person<T1, T2>::showPerson() {
cout << "姓名: " << this->m_Name << " 年龄:" << this->m_Age << endl;
}
person.cpp
#include
using namespace std;
//#include "person.h"
#include "person.cpp" //解决方式1,包含cpp源文件
//解决方式2,将声明和实现写到一起,文件后缀名改为.hpp
#include "person.hpp"
void test01()
{
Person<string, int> p("Tom", 10);
p.showPerson();
}
int main() {
test01();
system("pause");
return 0;
}
#pragma once
是一个预处理指令,用于确保头文件只被编译一次。在C++中,当头文件被包含到多个源代码文件中时,可能会导致多重包含(multiple inclusion)的问题。多重包含指的是同一个头文件在同一个编译单位中多次被包含,这可能导致符号重定义和编译错误。为了解决多重包含的问题,可以使用 #pragma once
指令。当编译器遇到 #pragma once
时,它会将该指令所在的头文件标记为只能被编译一次。这意味着在同一个编译单位中,当多次包含同一个头文件时,只有第一次包含会起作用,后续的包含将被忽略。
PS:又是7000多字大论文QAQ