网站运营的内容,生哥seo博客,南京专业网站制作多少钱,如何优化网站 提高排名一、什么是泛型编程
泛型编程 是一种编程范式#xff0c;它通过编写可以处理多种数据类型的代码来实现代码的灵活复用。泛型编程主要通过模板来实现。
比如我们日常使用的容器类型vector就应用了模板来实现其通用性#xff0c;我们在使用时可以通过传入型别创建对应的动态数…一、什么是泛型编程
泛型编程 是一种编程范式它通过编写可以处理多种数据类型的代码来实现代码的灵活复用。泛型编程主要通过模板来实现。
比如我们日常使用的容器类型vector就应用了模板来实现其通用性我们在使用时可以通过传入型别创建对应的动态数组如传入int定义vectorint 整形数组也可以传入char创建 vectorchar 字符数组等。 二、模板简介
1、简介
泛型编程主要使用模板来实现。模板就是允许你编写与类型无关的代码即对所有传入的数据类型编写通用的实现代码。
比如我想返回传入数据的占内存大小。对这个问题的解决可以不依赖数据类型我们都可以通过相同的操作返回结果。
template typename T
size_t getSize(const T data) {return sizeof(data);
}
2、模板分类
模板主要分为函数模板和类模板。
函数模板是使用泛型参数的函数。例如如下函数
templatetypename T
T add_func(T a, T b){return a b;
}
类模板即使用泛型参数的类。例如如下类成员参数或成员函数中可以动态指定参数类型
templatetypename T
class MClass{
public:MClass(){}MClass(T t):__data(t){}T getData(){return __data;}private:T __data;
}; 3、模板实例化
模板的声明知识给出一个函数或类提供一个语法框架其实并没有完成成为一个函数或类。当你定义一个模板时编译器并不立即生成代码。
当你在代码中使用模板时编译器会根据传入的类型生成对应的代码。这被称为模板实例化。每当使用新的类型实例化一个模板时编译器会生成一份新的代码副本。 在编译阶段编译器在模板实例化时会进行类型检查和其他错误检查确保传入的类型符合模板的要求。如果有错误编译器会报告这些错误。 在运行阶段模板已经被实例化并编译成代码则运行时行为与普通函数相同。模板本身的特性不会影响运行时的性能生成的代码是静态的。 如上述的函数模板我们就可以通过传入参数类型int、char、double等构建出add_funcint、add_funcchar、add_funcdouble等不同的函数实例。
模板实例化有2种方式
显式实例化直接在代码中明确指定传入型别如下方式的调用就是显式实例化
add_funcdouble(5.09, 10.26);
隐式实例化让编译器通过传入的参数进行型别推导完成的实例化。例如下面的调用编译器会根据传入的数据推导为add_funcint的函数
add_func(2, 8); 关于隐式实例化一定要注意要能让编译器推导出型别。如下代码只有一个模板参数的情况下却传入了两种类型的值。类似这种情况编译器无法完成型别推导就会报错。这时可以 1将数据类型强转为相同类型 2显式实例化模板 即可编译通过。 int main(){add_func(5.09, 28); //ERROR:无法推导出T的型别add_func(5.09, static_castdouble(28)); //OK 将28强转为double后只存在一种数据类型了编译器可以推导出Tdoubleadd_funcint(5.09, 28); //OK 显式得指定T为int不用编译器推导在调用时5.09会被强转为int型使用会丢失精度
} 三、函数模板
以函数形式定义的模板它可以定义一族函数。函数模板可以指定一个或者多个类型参数这些类型参数在具体调用时被具体数据类型取代。
函数模板的定义
函数模板的定义如下代码
注意每个函数模板都要在其之前使用template声明不可复用。
// 指定一个参数类型的函数模板
templatetypename T
void func(T t){}// 指定多个参数类型的函数模板
templatetypename T1, typename T2
T2 func(T1 t){ return T2();}//指定可变参数函数模板
templatetypename ...Args
void func(Args ...args){}
模板定义的语法不用多说但是模板参数有很多知识点需要掌握。
函数模板参数
从上面看我们知道模板参数可以是一个可以是多个也可以是可变参数。其中可变参数需要详细介绍所以博主又写了一篇文章大家可以参考
可变参数函数、可变参数模板和折叠表达式_可变参数模板函数-CSDN博客
下面介绍下关于模板参数的其他知识点
默认模板参数
模板的参数列表也可以设置默认传入类型。和函数参数默认值一样默认类型必须在右侧。如下代码所示
template typename T1, typename T2 int
void printData(T1 a, T2 b 0)
{cout a b endl;
}int main(){printDatastring(print data:, 97); //默认是int类型会打印97printDatastring, char(print data:, 97); //指定是char类型会打印areturn 0;
}
输出 非类型模板参数 除了类型模板参数在模板中还可以使用非类型模板参数。如下代码所示参数N是一个size_t常量参数而不是个类型参数。
并且非类型参数也可以有默认值。
templatetypename T, size_t N 10
T cal_func(T a, T b){return (a b) * N;
}int main(){cout cal_func(2,3) endl;cout cal_funcint, 20(2,3) endl;
}
输出 并不是所有参数类型都可以作为模板的非类型参数。非类型模板参数仅支持整形、枚举类型、字符常量几种。具体我放在类模板单元介绍因为它们主要使用在类模板中函数模板虽然在语法上也支持但是用的不常见。 函数模板的重载
函数模板也可以像普通函数一样被重载也可以重载普通函数编译器可以通过推导来决定调用哪个函数
// 1、普通函数
void func(int a){cout func 1 called!!! value a endl;
}
// 2、具有一个模板参数的函数模板
templatetypename T
void func(T a){cout func 2 called!!! value a endl;
}
// 3、具有2个模板参数的函数模板
templatetypename T
void func(T a, T b){cout func 3 called!!! value a endl;
}
// 4、具有可变参数的函数模板
templatetypename...Args
void func(Args...args){cout func 4 called!!! endl;
}int main(){func(1);funcint(2);func(3, 4);func(5.6);func(7, 8.9);
}
输出 如上述代码函数func有3个重载版本其中第一个是普通函数。
从调用结果看
1当直接传入一个int型参数时调用的是普通函数func 1
2当显式调用一个模板函数时即funcint时无论参数是什么都会调用函数模板;
3当传入一个非int型参数时会调用具有一个模板参数的func 2
4当传入两个相同类型参数时会隐式调用具有两个相同类型参数的模板函数func 3
5当传入两个不同类型参数时编译器调用了可变参数的模板函数 func 4.
从上面结果可以推断出编译器会优先调用更明确的函数而不是选择推导如果没有更明确的选择必须要使用模板编译器会优先选择推导更少的模板参数。即如果同时存在普通函数和模板函数可以调用时编译器会优先调用普通函数如果同时存在多个模板函数可以选择编译器会选择调用具有更少“推导工作量”的模板函数。 函数模板的特化 模板的特化是指为特定类型或特定参数数量提供自定义实现。这允许开发者为某些类型提供更高效或更适合的实现而不必改变模板的整体设计。 有两种主要类型的特化 全特化为特定类型提供完全不同的实现。偏特化为某些特定类型组合提供不同的实现但保留模板的一部分灵活性。 但是函数模板只允许全特化。
如下我们实现一个判断是否相等的函数模板但是因为浮点数不能使用“”判断是否相等所以进行了全特化
templatetypename T
bool Equal(T a, T b){return a b;
}
// 全特化
template
bool Equaldouble(double a, double b){return abs(a-b) 0.0001;
}int main(){cout Equal(1, 2) endl;cout Equal(1.1, 1.1000001) endl;
}
输出 关于特化的剩余知识在类模板介绍。 四、类模板
类模板允许创建类的模板定义类中的成员变量和成员函数都可以使用传入的数据类型确定。
类模板定义
类模板的定义形式和函数模板没有太大区别。只不过类模板不仅可以将成员函数的形参、返回值泛化也可以将成员变量进行泛化。我把上面的类模板定义代码拷贝下来大家可以再复习下
templatetypename T
class MClass{
public:MClass(){}MClass(T t):__data(t){}T getData(){return __data;}private:T __data;
};
在类模板的定义中还有些函数模板涉及不到的注意事项
1如果在类模板中涉及到对类对象本身的使用需要完整写出类模板名称类似MClassT而不是只简单些MClass
2如果需要再类声明之外进行初始化例如类静态成员变量的成员以及进行定义的成员例如类成员函数都需要完整地声明出模板参数
如下代码是相对比较复杂的类模板定义简单实现了一个数组的功能
template typename T, unsigned N
class Array {
private:T data[N]; // 使用 N 定义数组大小
public:ArrayT, N operator (const ArrayT, N other){}void setData(int i, T dat);T getData(int i){return data[i];}
};template typename T, unsigned N
void ArrayT, N::setData(int i, T dat){if (i 0 i N){data[i] dat;}
}int main(){Arrayint, 10 arr;arr.setData(5, 999);cout arr.getData(5) endl;
}
输出 类模板参数
类模板的参数和函数模板类似可以是一个可以是多个也可以是可变参数也可以传入默认参数类型这里就不再赘述。
下面重点介绍下在函数模板中没有说完的非类型模板参数的使用。
非类型模板参数
非类型模板参数支持整形、枚举类型、字符常量下面逐个介绍。
1、整形变量
类型如size_t、int、unsigned int等
用途
经常用于定义数组大小、循环次数、容量等可以为函数或类提供编译时确认的常量
例如
template typename T, std::size_t N
class Array {
public:T data[N]; // 使用 N 定义数组大小
};int main(){Arrayint, 10 arr;cout sizeof(arr.data)/sizeof(int) endl;
}
输出 2、枚举类型
类型enum
用途
提供一种方式来选择特定的行为或配置可以用作模板参数来控制模板的行为
enum class Color { Red, Green, Blue };template Color C
class ColorBox {
public:void display() {if constexpr (C Color::Red) {std::cout Red Box\n;} else if constexpr (C Color::Green) {std::cout Green Box\n;} else {std::cout Blue Box\n;}}
};int main(){ColorBoxColor::Green cbox;cbox.display();
}
输出 3、字符常量
类型char
用途
可以用于定义固定的字符串或字符常量常用于模板元编程中例如处理字符串或字符集
使用举例
template char C
class CharPrinter {
public:void print() {std::cout C std::endl;}
};int main(){CharPrinterA cp;cp.print();
}
输出 非类型模板参数的作用
灵活性非类型模板参数允许你编写更灵活和高效的代码。性能由于在编译时已确定值能提升性能。错误检查使用非类型参数时可以在编译阶段捕获错误减少运行时错误。 类模板特化
类模板可以全特化也可以偏特化也叫部分特化。特化后的具体实现可以和泛式的实现不一样
类模板全特化
类模板全特化的格式和函数模板全特化基本相同
// 类模板
templatetypename T, typename U
class MClass{}// 全特化版本
template
class MClassint, string{};
上面的全特化版本用于特殊处理类型参数是int, string的问题。
类模板偏特化
如下类模板声明
// 类模板
templatetypename T, typename U
class MClass{};// 部分特化-特化第二个类型参数为string
templatetypename T
class MClassT, string{};// 部分特化-特化第一个类型参数为int
templatetypename T
class MClassint, T{};
下面我写出完整地类模板、全特化和偏特化的几个类声明。及它们的使用。
示例代码
// 类模板
templatetypename T, typename U
class MClass{
public:MClass(){cout MClass T U called endl;}
};
// 全特化版本
template
class MClassint, string{
public:MClass(){cout MClass int str called endl;}
};
// 部分特化-特化第二个类型参数为string
templatetypename T
class MClassT, string{
public:MClass(){cout MClass T str called endl;}
};
// 部分特化-特化第一个类型参数为int
templatetypename T
class MClassint, T{
public:MClass(){cout MClass int T called endl;}
};int main(){MClassint, string c1;MClassint, double c2;MClassdouble, string c3;MClasschar, int c4;return 0;
}输出 从上面的代码中我们可以看到有些调用可以匹配多个类模板。比如如下这句调用它可以使用MClassint, string的全特化版本也可以使用MClassint, T或MClassT, string的偏特化版本更可以使用MClassT, U的无特化一般版本。但为什么只调用了MClassint, string的全特化版本呢
MClassint, string c1;
这是因为编译器在查找类模板时会优先匹配全特化版本其次是偏特化版本最后才是一般模板。
所以上面的输出结果那般。