C++模板的笔记2
关于可变参函数模板借鉴了一部分笔记,感谢大佬
类模板中的嵌套
类模板可以嵌套其他类模板,就像普通类可以嵌套其他普通类一样。嵌套的类模板可以访问外部类模板的成员,包括私有成员。
示例:
#include <iostream>
using namespace std;
template <typename T>
class Outer {
public:
template <typename U>
class Inner
{
public:
void print()
{
std::cout << Outer::value << std::endl;
}
private:
T value;
};
private :
static int value;
};
template <class T>
int Outer<T>::value = 10;
int main()
{
Outer<int>::Inner<double> inner;
inner.print();
return 0;
}
类模板中的友元
类模板可以声明友元函数或类模板,就像普通类可以声明友元函数或类一样。友元函数或类模板可以访问类模板的私有成员。
注意非本类的友元函数必须定义在本类中,在类外编译器会找不到。
示例:
#include <iostream>
using namespace std;
template <typename T>
class MyClass {
public:
// 友元函数
friend void print(const MyClass& obj)
{
// 访问类模板的私有成员
std::cout << obj.data << std::endl;
}
MyClass(T d):data(d){};
template <typename U>
friend class MyFriend;
private:
T data;
};
// 友元类模板
template <typename T>
class MyFriend {
public:
void print(const MyClass<T>& obj) {
// 访问类模板的私有成员
std::cout << obj.data << std::endl;
}
};
int main()
{
MyClass<int> mc(10);
MyFriend<int> mf;
print(mc); //10
mf.print(mc); //10
return 0;
}
函数模板作为模板友元
函数模板可以作为类模板的友元,就像普通函数可以作为普通类的友元一样。
示例:
#include <iostream>
using namespace std;
template <typename T>
class MyClass {
public:
// 将函数模板友元声明放在类模板的内部
template <typename U>
friend void p(const MyClass<T>& obj);
MyClass(T d):data(d)
{}
void print(){
std::cout<<data<<std::endl;
}
private:
T data;
};
template <typename U>
void p()
{
MyClass<int> obj(10);
obj.print();
}
int main() {
// 调用函数模板友元
p<int>(); //10
return 0;
}
- 类模板可以嵌套其他类模板,嵌套的类模板可以访问外部类模板的成员,包括私有成员。
- 类模板可以声明友元函数或类模板,友元函数或类模板可以访问类模板的私有成员。
- 函数模板可以作为类模板的友元。
可变参函数模板
可变参函数模板是C++11标准中新增的功能。在C++11之前,C++中没有可变参函数模板,只能使用宏或其他变通方法来实现类似的功能。
template <class... T>
void f(T... args);
上面的可变模版参数的定义当中,省略号的作用有两个:
1.声明一个参数包T... args,这个参数包中可以包含0到任意个模板参数;
2.在模板定义的右边,可以将参数包展开成一个一个独立的参数。
可变参函数模板允许函数接受数量可变的参数。这可以通过使用 ... 语法来实现。
template <class... T>
void f(T... args)
{
cout << sizeof...(args) << endl;
}
f();
f(1);
f(2,10);
/*
输出
0
1
2
*/
如果我们需要将参数包中的每个参数打印出来的话就需要通过一些方法了。展开可变模版参数函数的方法一般有两种:一种是通过递归函数来展开参数包,另外一种是通过逗号表达式来展开参数包。下面来看看如何用这两种方法来展开参数包。
递归函数来展开参数包
//可变参函数模板
void print();
template <class T,class ...Args>
void print(T head,Args... rest)
{
cout << "parameter "<<head<< endl;
print(rest...);
}
// 递归终止函数
void print()
{
cout<<"empty"<<endl;
}
int main() {
print(1,2,3,4);
return 0;
}
/*
parameter 1
parameter 2
parameter 3
parameter 4
empty
*/
递归调用的过程是这样的:
print(1,2,3,4);
print(2,3,4);
print(3,4);
print(4);
print();
上面的终止递归函数可以写这样
template <class T>
void print(T t)
{
cout<<t<<endl;
}
/*
输出结果
parameter 1
parameter 2
parameter 3
4
*/
递归调用的过程是这样的:
print(1,2,3,4);
print(2,3,4);
print(3,4);
print(4);
我们可以通过递归来实现参数求和
template <class T>
T sum(T);
template <class T,class ...Args>
T sum(T head,Args... rest)
{
return head +sum<T>(rest...);
}
// 递归终止函数
template <class T>
T sum(T t)
{
return t;
}
通过逗号表达式来展开参数包。
递归函数展开参数包是一种标准做法,也比较好理解,但也有一个缺点,就是必须要一个重载的递归终止函数,即必须要有一个同名的终止函数来终止递归,这样可能会感觉稍有不便。我们采取用逗号表达式来简化代码。
逗号表达式:逗号运算符只返回整个逗号表达式中最右边的操作数的值。
int a=1,b=2,c=6;
int d=0;
d = (a = b, c);
cout<<d<<endl; //返回6
表达式 d = (a = b, c); 的计算过程如下:
- 首先,逗号运算符从左到右计算其操作数。
- 第一个操作数 a = b 将 b 的值(即2)赋给 a。
- 然后,逗号运算符继续计算第二个操作数 c,其值为6。
- 最后,逗号表达式的结果是整个逗号表达式中最右边的操作数,即 c 的值6。
- 这个值(6)被赋给 d。
- 因此,d 的最终值是6,cout << d << endl; 会输出6。
运用逗号表达式把之前的print例子修改这样,终止函数不用再同名
template <class T>
void printarg(T t)
{
cout << t << endl;
}
template <class ...Args>
void expand(Args... args)
{
int arr[] = {(printarg(args), 0)...};
}
expand(1,2,3,4);
/*
1
2
3
4
*/
我们可以把上面的例子再进一步改进一下,将函数作为参数,就可以支持lambda表达式了,从而可以少写一个递归终止函数了,具体代码如下:
template<class F,class... Args>
void expand(const F&f,Args&&...args)
{
initializer_list<int>{ (f(std::forward<Args>(args) ),0)... } ;
}
int main()
{
expand([](auto i){cout<<i<<endl;},1,2.0,1.8,20,0,111,"test");
return 0;
}
/*
输出
1
2
1.8
20
0
111
test
*/
讲解一下上面的代码,利用了C++11引入的右值引用、完美转发、初始化列表和C++14 lambda函数等特性.
这段代码定义了一个名为 expand 的可变参函数模板,并在 main 函数中使用了一个 lambda 表达式来调用它。我们一步一步来分析这段代码。
-
- 可变参函数模板 expand
template<class F,class... Args>
void expand(const F& f,Args&&...args)
{
std::initializer_list<int>{ (f(std::forward<Args>(args) ),0)... } ;
}
- 模板参数
F: 一个函数对象或可调用实体的类型。
Args...: 一个可变参数模板,表示可以接收任意数量和类型的参数。
- 函数参数
const F& f: 一个对函数对象或可调用实体的常量引用。
Args&&...args: 使用右值引用的可变参数列表,这允许我们完美转发参数。
- std::initializer_list
std::initializer_list 是 C++11 标准引入的一个模板类,它提供了一种方便的方式来初始化和访问一组同类型的对象。这个列表是由花括号 {} 包围的初始化器生成的,可以包含任意数量的元素,这些元素在列表中是常量引用。
主要特点
非常量对象:
尽管 std::initializer_list 的元素是常量引用,但你可以修改这些元素所引用的底层对象的值(如果这些对象是可修改的)。然而,你不能修改 std::initializer_list 本身的大小或内容。
不拥有其元素: std::initializer_list 并不拥有其包含的元素。
这意味着当 std::initializer_list 的生命周期结束时,其引用的底层对象不会被自动销毁。
构造和析构:
std::initializer_list 的构造函数接受一个指针和长度,或者另一个 std::initializer_list,或者一个花括号包围的初始化列表。它没有显式的析构函数,因为析构是自动处理的。
存储未指定:
std::initializer_list 的存储是未指定的,这意味着它可以在自动、临时或静态只读内存中存储。
不是容器:
尽管 std::initializer_list 提供了一种访问元素的方式,但它不是一个容器类。因此,你不应该用它来传递期望长期存储的值。
初始化器列表构造函数:
当一个类的构造函数接受一个 std::initializer_list 作为参数时,这个构造函数被称为“初始化器列表构造函数”。
下面是一个简单的例子,展示了如何使用 std::initializer_list:
#include <iostream>
#include <initializer_list>
class MyClass {
public:
MyClass(std::initializer_list<int> list) {
for (const auto& element : list) {
std::cout << element << " ";
}
std::cout << std::endl;
}
};
int main() {
MyClass obj{1, 2, 3, 4, 5}; // 使用初始化器列表构造了一个 MyClass 对象
return 0;
}
解析这段代码 std::initializer_list
- 1,先使用了initializer_list 来调用一组参数,然后在这里面使用了逗号表达式,例如 f(1), 0,f(2.0), 0,f(1.8), 0 等,每个逗号表达式的结果(即 f 的返回值)都是 int 类型,所以std::initializer_list
里面是int类型。 - 2,在函数里面调用了f函数指针类型,在函数指针里面使用了完美转发来转发右值args
- 3,由于逗号表达式f(args),0... 因此最后都只会返回0
折叠表达式
分为左折叠和右折叠表达式
左折叠的例子:
template <typename... Args>
constexpr auto sum(Args... args) {
return (args + ...); // 左折叠表达式,从左往右边 计算所有args的和
}
右折叠的例子:
template <typename... Args>
constexpr auto sum(Args... args) {
return (... + args); // 左折叠表达式,从左往右边 计算所有args的和
}
可变模版参数类
可变参数模板类是一个带可变模板参数的模板类,比如C++11中的元祖std::tuple就是一个可变模板类,它的定义如下:
template< class... Types >
class tuple;
这个可变参数模板类可以携带任意类型任意个数的模板参数:
std::tuple<int> tp1 = std::make_tuple(1);
std::tuple<int, double> tp2 = std::make_tuple(1, 2.5);
std::tuple<int, double, string> tp3 = std::make_tuple(1, 2.5, “”);
可变参数模板的模板参数个数可以为0个,所以下面的定义也是也是合法的:
std::tuple<> tp;
可变参数模板类的参数包展开的方式和可变参数模板函数的展开方式不同,可变参数模板类的参数包展开需要通过模板特化和继承方式去展开,展开方式比可变参数模板函数要复杂。下面我们来看一下展开可变模版参数类中的参数包的方法。
标签:std,函数,list,笔记,参数,C++,print,模板 From: https://www.cnblogs.com/AndreaDO/p/18019389