德清建设银行网站,唐山自助建站,哈尔滨市人社app,定制网站和模板建站哪个更好简介 之前对模版的进行了初步了解和使用#xff0c;可查看博客#xff1a;C 初始模板_c模板初始化_chihiro1122的博客-CSDN博客 其实模版除了是一类算法#xff0c;或者自定义类型的 套用#xff0c;还有其他功能#xff0c;和其他的更高阶的使用方法。
之前在实现 各种 … 简介 之前对模版的进行了初步了解和使用可查看博客C 初始模板_c模板初始化_chihiro1122的博客-CSDN博客 其实模版除了是一类算法或者自定义类型的 套用还有其他功能和其他的更高阶的使用方法。
之前在实现 各种 C 当中的 STL 的容器的时候用就多次用到了类比如套用正向迭代器模版实现的 反向迭代器的适配器还有 queue 和 stack 容器适配器还有仿函数的实现都是使用了 模版来实现的
C - 优先级队列priority_queue的介绍和模拟实现 - 反向迭代器的适配器实现_chihiro1122的博客-CSDN博客
C - stack 和 queue 模拟实现 -认识 deque 容器 容器适配器_chihiro1122的博客-CSDN博客 模版进阶
typename 前缀修饰问题的解决
在简介当中都是类模版我们在这里回顾一下函数模版函数模版不想类模版一样需要 显示实例化直接传入参数就可以按照模版来自动进行替换
//templatetypename Container
templateclass Container
void Print(const Container v)
{typename Container::const_iterator it v.begin();while (it ! v.end()){cout *it ;it; // 因为只重载了 前置的}cout endl;
}int main()
{vectorint v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);for (auto ch : v){cout ch ;}cout endl;Print(v);return 0;
}
上述 Print函数不仅仅可以打印 vector 的多种参数类型其他容器也可以打印。
但是需要注意的是上述在取出迭代器的时候使用的方式有些奇怪如下所示
typename Container::const_iterator it v.begin();
如果前面不加 typename 前缀的话就会报错 我们在定义模版参数的时候参数之前的前缀可以是 class 可以是 typename如下所示
templatetypename Container
templateclass Container
但是不管是哪一种定义方式如果是 const 迭代器的话在迭代器类型之前必须用 typename前缀修饰。
原因很简单
上述如果不加 typename 的代码
Container::const_iterator it v.begin(); 程序运行首先进行编译实例化。
如果要寻找 const_iterator 编译器就会去 Container 当中去寻找但是这里寻找就会有问题如果我们不使用函数模版那么就不会出现问题因为这里的 Container 直接写成 vectorint 直接实例化了编译器直接在这个实例化的对象当中寻找就行了。
但是如果使用模版这里就有三种情况一种是在 Container 当中寻找 const_iterator 这个动态成员变量另一种是寻找 const_iterator 这个内部类对象那么到底这个 const_iterator 是一个成员变量还是一个对象还是一个类型编译器搞不明白。而此时的 Container 不知道是什么。
如果 Container::const_iterator 这个表示的是一个类型那么此处的语法是正确的如果表示的是一个 对象就不符合语法了。 所以在此处加一个 typename 表示此处的 Container::const_iterator 就是一个 类型。等实例化再去实例化的对象当中去寻找。 其实这里还有更好的方式来解决用一个 auto就可以自动推导类型 就不用再使用之前一大长串 的类型名了。这里我们就可以体会到了 auto 的强大。 因为 auto 一定是类型所以编译器就不会再去往 对象那一方面去想了。 但是不是所有使用 typename 的地方都可以用 auto有些地方还是需要用到 typename 的比如在 优先级对象当中就是用了 typename。如下图所示 问题有些编译器会 按需实例化比如当类当中的 某一个成员函数当中有编译错误如果这个函数没有调用那么编译器会略过这个错误但是 按需实例化也是看编译器的不同的编译器实例化程度不同。 非类型模版参数 模版当中不仅仅有需要类型模版参数的情况可能还需要传入一些数值比如下面这个例子定义一个静态栈
#define N 10templateclass T
class stack
{
public:private:T _a[N];size_t _size;
};int main()
{stackint st1; // 10 个stackint st2; // 100 个
}
此时我想定义两个静态栈但是因为是静态的宏 N 的大小不能改变 那么上述代码我们只能满足 一种情况这就和 C 当中的 宏 一样的。
所以C 当中的模版参数还可以传入值 templateclass T, size_t N
class stack
{
public:private:T _a[N];size_t _size;
};int main()
{stackint, 10 st1; // 10 个stackint, 100 st2; // 100 个
}
templateclass T, size_t N
上述模版直接用 类型来当做是模版参数的类型这个N 就是一个非类型的模版参数解决了上述静态栈的问题。 关于非类型模版参数需要注意的点 非类型的模板参数相等于是一个常量他不像函数参数一样可以进行修改非类型的模版参数是不能进行修改的。也就是说;非类型的模板参数必须在编译期就能确认结果。非类型模板参数的类型必须是整形。
模版的特化 函数模版的特化 模版可以实现无关类型的代码但是一些特殊类型的代码可能会出现问题
templateclass T
bool Less(T left, T right)
{return left right;
}
int main()
{cout Less(1, 2) endl; // 可以比较结果正确int d1 1;int d2 2;cout Less(d1, d2) endl; // 可以比较结果正确Date* p1 d1;Date* p2 d2;cout Less(p1, p2) endl; // 可以比较结果错误return 0;
} 最后一组传入的是指针类型那么 模版类型 T 就是指针指针的比较大小是比较地址的高低但是肯定有上述的情况下我们想要传入指针但是不想按照指针去比较按照传入指针 解引用的值来进行比较但是我们不可能直接修改模版函数如果改成解引用的话之前我们想要实现的功能就不能实现了。
所以上述的例子就要用到模版的特化。 上述的less 函数特化之后如下所示
// Less 函数的模版
templateclass T
bool Less(T left, T right)
{return left right;
}// Less 模版函数的特化
template
bool Lessint*(int* left, int* right)
{return *left *right;
} 如上下面一个 Lessint* 就是Less 模版函数的函数特化。
注意
模版函数的特化一定要有模版为前提然后再去对这个函数模版进行 模版特化。函数的特化不是函数的重载。 上述的特化还可以写成下述函数重载的样式和函数模版实例化出的函数进行 函数重载
bool Less(int* left, int* right)
{return *left *right;
} 上述的 模版特化 和 函数重载两种方式虽然都可以达到我们想要目的但是上述两种情况都只是实现了 int 类型的指针问题不能解决多种指针的问题。 所以聪明的你一下发现了那么我们在实现一个模版不就行了是这样的看如下代码
templateclass T
bool Less(T* left, T* right)
{return *left *right;
} 当传入的T 是一个指针的时候虽然 第一种形式的模版 和 上述这个模版都可以匹配但是上面这个模版更加的符合所以如果传入的是 某类型的指针的话就会调用上面这个模版。
而且如果你实现上述两个模版的同时在想上述一样实现了某一个类型的重载函数或者像第一次那样的 实现 模版函数的 特化那么这两个都是现成的编译器优先调用现成的函数。 类模板的特化 类模板的特化和函数模版的特化是类似的 函数模版的特化是相当于是重新写了一个函数类模板的特化也相当于是多写了一个类
// 类模板
templateclass T1, class T2
class Data
{
public:Data() { cout DataT1, T2 endl; }
private:T1 _d1;T2 _d2;
};// 类模板的全特化
template
class Dataint, char
{
public:Data() { cout Dataint, char endl; }
private:int _d1;char _d2;
};void TestVector()
{Dataint, int d1;Dataint, char d2;
} 输出 DataT1, T2 Dataint, char 当传入的模版参数是 int 和 char 的时候调用的就是下面定义的 特化的 模版类然后进行特殊处理在这个当中的特殊处理不影响 之前定义的类模板。 运用场景在使用仿函数的时候就可以使用当我们想对仿函数的 类模板 当中某一个类型的实例化进行特殊操作的时候就可以使用上述类模板的特化来对某一种类型进行特化。
像之前在 介绍 优先级队列当中对 less 这个仿函数的介绍当传入的是日期类指针Date*的结果就不对这时候就可以使用 特化给 less 类模板 特化出一个 特殊处理的类。 全特化和偏特化 向上述的
template
class Dataint, char和 template
bool Lessint*(int* left, int* right) 都属于是全特化。全特化就是把所以的模版参数都特化。
而偏特化也叫做半特化就是没有把全部的模版参数都特化。 如下代码所示
// 类模板的全特化
template
class Dataint, char
{
public:Data() { cout Dataint, char endl; }
private:int _d1;char _d2;
};// 类模板的偏特化特化部分参数
templateclass T1
class DataT1, char
{
public:Data() { cout DataT1, char endl; }
private:T1_d1;char _d2;
}; 向上述的偏特化是对某一些模版参数进行特化其实偏特化有两种方式
一种就是向上述一样的 特化部分参数。另一种就是对某一些模版参数类型的进一步限制。 // 类模板的偏特化对一些参数进行一些限制
templateclass T1, class T2
class DataT1*, T2*
{
public:Data() { cout DataT1, char endl; }
private:
}; 向上述不对某些参数进行特化只是判断传入的两个模版参数是不是指针是就调用这个 特化的类。 特化在库当中也是有运用的库当中对为了在某一模版当中找到其中调用的模版的模版参数即用了萃取而萃取本质上其实就是特化实现的只不过库当中的萃取实现很麻烦。
array 静态数组 array数组和 c语言 当中的 数组在功能上和 效率上没有任何区别就连不能用变量来初始化个数这个特性都是一样的。而且如果使用 array 的无参数的构造函数里面的元素也不会进行初始化这里和 C 当中的 数组也是一样的
array 相比于 C当中的数组唯一的好处就是array 可以检查越界而且检查非常的快他是用assert 断言来实现的。如果是 C语言的 数组只是读的访问越界是检查不出来的写可能会检查出来而 array 容器无论是 写还是读都会检查出来。利用的就是 operator[] 当中的 对 下标越界的检查。
除此之外array 容器 相比于数组是没有任何优点的。