南通市网站建设我的完,手机网站怎么做域名解析,河北邯郸信息港,校园网站建设初探在C11之前#xff0c;类模板和函数模板只能含有固定数量的模板参数。C11增强了模板功能#xff0c;允许模板定义中包含0到任意个模板参数#xff0c;这就是可变参数模板。可变参数模板的加入使得C11的功能变得更加强大#xff0c;而由此也带来了许多神奇的用法。 可变参数模…在C11之前类模板和函数模板只能含有固定数量的模板参数。C11增强了模板功能允许模板定义中包含0到任意个模板参数这就是可变参数模板。可变参数模板的加入使得C11的功能变得更加强大而由此也带来了许多神奇的用法。 可变参数模板
可变参数模板和普通模板的语义是一样的只是写法上稍有区别声明可变参数模板时需要在typename或class后面带上省略号...
templatetypename... Types
其中...可接纳的模板参数个数是0个及以上当然也包括0个。
若不希望产生模板参数个数为0的变长参数模板则可以采用以下的定义
templatetypename Head, typename... Tail
本质上...可接纳的模板参数个数仍然是0个及以上的任意数量但由于多了一个Head类型由此该模板可以接纳1个及其以上的模板参数。 函数模板的使用
在函数模板中可变参数模板最常见的使用场景是以递归的方法取出可用参数
void print() {}templatetypename T, typename... Types
void print(const T firstArg, const Types... args)
{std::cout firstArg sizeof...(args) std::endl;print(args...);
}通过设置...可以向print函数传递任意个数的参数并且各个参数的类型也是任意。也就是说可以允许模板参数接受任意多个不同类型的不同参数。这就是不定参数的模板格外需要关注的是...三次出现的位置。
如果如下调用print函数
print(2, hello, 1);
如此调用会递归将3个参数全部打印。细心的话会发现定义了一个空的print函数这是因为当使用可变参数的模板需要定义一个处理最后情况的函数如果不写会编译错误。这种递归的方式是不是觉得很惊艳
在不定参数的模板函数中还可以通过如下方式获得args的参数个数
sizeof...(args)
假设在上面代码的基础上再加上一个模板函数如下那么运行的结果是什么呢
#include iostreamvoid print() {}templatetypename T, typename... Types
void print(const T firstArg, const Types... args)
{std::cout firstArg sizeof...(args) std::endl;print(args...);
}template typename... Types
void print(const Types... args)
{std::cout print(...) std::endl;
}int main(int argc, char* argv[])
{print(2, hello, 1);return 0;
}现在有一个模板函数接纳一个参数加上可变参数还有一个模板函数直接接纳可变参数如果调用print(2, “hello”, 1)会发现这两个模板函数的参数格式都符合。是否会出现冲突、不冲突的话会执行哪一个呢
运行代码后的结果为
yngzmiaoyngzmiao-virtual-machine:~/test/$ ./main
2 2
hello 1
1 0
从结果上可以看出程序最终选择了一个参数加上不定参数的模板函数。也就是说当较泛化和较特化的模板函数同时存在的时候最终程序会执行较特化的那一个。
再比如一个例子std::max函数只可以返回两个数的较大者如果多个数就可以通过不定参数的模板来实现
#include iostreamtemplate typename T
T my_max(T value)
{return value;
}template typename T, typename... Types
T my_max(T value, Types... args)
{return std::max(value, my_max(args...));
}int main(int argc, char* argv[])
{std::cout my_max(1, 5, 8, 4, 6) std::endl;return 0;
}类模板的使用
除了函数模板的使用外类模板也可以使用不定参数的模板参数最典型的就是tuple类了。其大致代码如下
#include iostreamtemplatetypename... Values class tuple;
template class tuple {};templatetypename Head, typename... Tail
class tupleHead, Tail...: private tupleTail...
{typedef tupleTail... inherited;public:tuple() {}tuple(Head v, Tail... vtail) : m_head(v), inherited(vtail...) {}Head head() {return m_head;}inherited tail() {return *this;}protected:Head m_head;
};int main(int argc, char *argv[])
{tupleint, float, std::string t(1, 2.3, hello);std::cout t.head() t.tail().head() t.tail().tail().head() std::endl;return 0;
}根据代码可以知道tuple类继承除首之外的其他参数的子tuple类以此类推最终继承空参数的tuple类。继承关系可以表述为
tuple↑
tuplestd::stringstring hello↑
tuplefloat, std::stringfloat 2.3↑
tupleint, float, std::stringint 1接下来考虑在内存中的分布内存中先存储父类的变量成员再保存子类的变量成员也就是说对象t按照内存分布来说
┌─────────┐---- 对象指针
| hello |
|─────────|
| 2.3 |
|─────────|
| 1 |
└─────────┘
这时候就可以知道下一句代码的含义了
inherited tail() {return *this;}
tail()函数返回的是父类对象父类对象和子类对象的内存起始地址其实是一样的因此返回*this再强行转化为inherited类型。
当然上面采用的是递归继承的方式除此之外还可以采用递归复合的方式
templatetypename... Values class tup;
template class tup {};templatetypename Head, typename... Tail
class tupHead, Tail...
{typedef tupTail... composited;public:tup() {}tup(Head v, Tail... vtail) : m_head(v), m_tail(vtail...) {}Head head() {return m_head;}composited tail() {return m_tail;}protected:Head m_head;composited m_tail;
};两种方式在使用的过程中没有什么区别但C11中采用的是第一种方式(递归继承)。
在上面的例子中取出tuple中的元素是一个比较复杂的操作需要不断地取tail最终取head才能获得。标准库的std::tuple对此进行简化还提供了一些其他的函数来进行对tuple的访问。例如
#include iostream
#include tupleint main(int argc, char *argv[]) {std::tupleint, float, std::string t2(1, 2.3, hello);std::get0(t2) 4; // 修改tuple内的元素std::cout std::get0(t2) std::get1(t2) std::get2(t2) std::endl; // 获取tuple内的元素auto t3 std::make_tuple(2, 3.4, World); // make方法生成tuple对象std::cout std::tuple_sizedecltype(t3)::value std::endl; // 获取tuple对象元素的个数std::tuple_element1, decltype(t3)::type f 1.2; // 获取tuple对象某元素的类型return 0;
}相关阅读
C11相关知识点