IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++11特性(上) -> 正文阅读

[C++知识库]C++11特性(上)

写在前面

今天我们谈谈C++的一些语法,这些语法是C++11标准下新增的.有的人感觉学C++很难,那么C++11标准出来之后你会发现学习的成本又增加了.C++11增添了很多特性,有有用的,有"无用"的,有的人甚至调侃C++11标准出来后,C++越来越感觉像一门新语言.这个博客我们简单的谈一谈C++11比较常用的一些特性.

初始化列表

首先我们要说明这个和类和对象构造函数那里的初始化列表没有半毛钱的关系.在C++98中,标准允许使用花括号{}对数组或者结构体元素进行统一的列表初始值设定.对数组初始化我们是常用的.

struct Person
{
	char name[20];
	int age;
	char sex[10];
};
int main()
{
	int arr[10] = { 1,2,3,4,5,6 };
	struct Person per = { "张三",18,"男" };

	return 0;
}

image-20221005083240050

C++11标准出来后,我们可以用{}干更多的事情,例如使用{}对内置类型和自定义类型进行初始化,这个就涉及到初始化列表.下面三个变量的初始化的值是一样的.

int main()
{
	int x = 10;
	int y = { 10 };
	int z{ 10 };
	return 0;
}

image-20221005083638821

甚至我们在C++98那里初始化方式里面的等号也可以省略.

struct Person
{
	char name[20];
	int age;
	char sex[10];
};
int main()
{
	int arr[10]{ 1,2,3,4,5,6 };
	struct Person per{ "张三",18,"男" };

	return 0;
}

有的人可能会感到疑惑,我们这么些好像麻烦了,这里我们之前的方法不是挺好的吗?现在倒是多此一举了.实际上语法上支持这样做是为了后面的方法.

在我们谈动态内存管理的时候,我们谈过对于只new一个对象可以初始化,如果new多个对象这里我们就没有办法了,只能看对象的对应类的默认构造函数了.C++11的初始化列表解决了这个问题.

int main()
{
	int* p1 = new int(1);
	delete p1;
	return 0;
}

image-20221005084225866

我们先来看看如何new多个对象并初始化的,这个也支持不完全初始化和完全初始化.

int main()
{
	int* p1 = new int[3]{ 0 };
	int* p2 = new int[3]{ 1,2,3 };
	delete[] p1;
	delete[] p2;
	return 0;
}

image-20221005085043756

这里我们需要看一下什么是不完全初始化,和我们之前不够的补零是不是一样?

struct A
{
	A(int a = 1)
		:_a(a)
	{}
	int _a;
};
int main()
{
	A* p = new A[3]{ 0 };
	delete p;
	return 0;
}

image-20221005085646850

这里我们分析一下,对于不完全初始化,剩下的调用自己的默认构造函数.这里和我们C++98的语法是相匹配的.我们知道在C++中内置类型也被封装成类了,那么int的默认构造函数的的初始化值是0,这个和我们之前的补零是一样的.

现在我们看一看初始化列表对于自定义类型的使用方法,它们和内置类型是一样的.

class Date
{
public:
	Date(int year, int month, int day)
		:_year(year)
		, _month(month)
		, _day(day)
	{
		cout << "Date(int year, int month, int day)" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1{ 2000, 1, 2 };
	Date d3 = { 2000, 1, 3 };
	Date* p1 = new Date[3]{ { 2022, 1, 2 },{ 2022, 1, 2 },{ 2022, 1, 2 } };
	delete p1;
	return 0;
}

这里我们先解释一下单参数的构造函数支持隐式类型转换.这里的本质是 { 2000, 1, 2 }先构造一个临时对象,后面再进行拷贝构造.这里编译器给优化了.我们这试一下不允许单参数的隐式类型转换.

class Date
{
public:
	explicit Date(int year, int month, int day)
		:_year(year)
		, _month(month)
		, _day(day)
	{
		cout << "Date(int year, int month, int day)" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1 = { 2000, 1, 2 };
	return 0;
}

image-20221005091753759

initializer_list

我们需要解释一下我为何C++11可以支持上面的用法.实际上我们把{}封装成成一个类了,这个类就是initializer_list.

image-20221005092407319

我们需要好好分析一下,这个究竟是什么.

int main()
{
	auto il = { 10, 20, 30 };
	cout << typeid(il).name() << endl;
	return 0;
}

image-20221005092553605

这我们可以这么理解,我们把{}看作一个initializer_list类对象的数组,里面存储的一些对象,这些对象里面存储这我们需要的数据,编译器可以根据这些对象来推导出对应的数据类型.

int main()
{
	auto il = { 10, 20, 30 };
	auto it = il.begin();
	while (it != il.end())
	{
		cout << *it << endl;
		++it;
	}
	return 0;
}

image-20221005092957903

我们已经知道了这个原理了,这我们需要看一看STL里面C++11出现后新增的构造函数.这里以vector为例子.

image-20221005093605594

我们先来用一下,后面简单的说一下模拟实现就可以了.

int main()
{
	vector<int> v = { 1,2,3,4,5,6 };
	
	return 0;
}

image-20221005093802582

这里我们需要简单的解释下.

image-20221005100110253

我们这里模拟实现一下vector支持initializer_list的构造函数,这里倒是挺简单的,反正它支持迭代器,这里直接使用范围for

namespace bit
{
	template<class T>
	class vector {
	public:
		typedef T* iterator;
		vector(initializer_list<T> l)
		{
			for (auto& data : l)
				push_back(data);
		}
		vector<T>& operator=(initializer_list<T> l) {
			vector<T> tmp(l);
			std::swap(_start, tmp._start);
			std::swap(_finish, tmp._finish);
			std::swap(_endofstorage, tmp._endofstorage);
			return *this;
		}
		void push_back(const T& data)
		{

		}
	private:
		iterator _start;
		iterator _finish;
		iterator _endofstorage;
	};
}

auto

这个是新增的关键字,主要是让编译器自动推导变量的类型,前面我们已经谈过了,这里就不浪费大家的时间了.

int main()
{
	map<string, string> m;
	m.insert(make_pair("left", "左边"));
	map<string, string>::iterator it1 = m.begin();
	auto it2 = m.begin(); // 简单点
	return 0;
}

范围for

这个也不谈了,支持迭代器就支持范围for.范围for的底层还是调用了迭代器.

int main()
{
	vector<int> v = { 1,2,3,4,5,6 };
	for (int val : v)
	{
		cout << val << " ";
	}
	return 0;
}

image-20221005101623490

final override 和 array

final与override这是C++11增加的两个关键字.作用我们都谈过了.其中array也是槽点满满.

decltype

这个关键字可以通过一些表达式来自动推导变量的类型,而且还可以在变量命名的时候使用.

int main()
{
	decltype(1) a = 10;
	decltype(1.0*a) b = 1.00;
	return 0;
}

image-20221005185338166


右值引用

这个是C++11更新的很好的特性.我们需要重点谈谈.这个特性提高了程序的效率,但是在一定程度上增加了学习的成本.

右值 VS 左值

在C语言的时候我就想和大家分享,但是那时候知识储备还不够,自己的思路还没有整理出来,就放弃了.这里我也是从最简单的结论开始谈起.

首先我们要分析一下什么是左值什么是右值?

左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址+可以对它赋值,左值可以出现赋值符号的左边定义时const修饰符后的左值,不能给他赋值,但是可以取地址.

右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(这个不能是左值引用返回)等等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址.

这里我们需要完善一下自己的结论.可以取地址的叫做左值,不能取地址叫右值.这个是我们最终的结论.

右值引用

之前我们用的所有都是左值引用,这里就不谈了.所谓的右值引用就是给右值取别名,方法和左值一样,要求也和左值差不多.

int main()
{
	10;
	int&& a = 10; // 右值引用
	return 0;
}

这里问题就来了,我们好象之前也对10进行过左值引用吧.这里我们先得到几个结论.

  • 左值引用只能引用左值
  • 右值引用只能引用右值
  • const修饰的左值引用可以引用右值

我们还可以得到一个现象,我们不能对右值取地址,但是可以对右值引用取地址.

int main()
{
	10;
	int&& a = 10;
	cout << &a << endl;
	return 0;
}

image-20221005121508739

右值引用的意义

这里我们就需要好好的分析一下了,前面我们说的是右值和右值引用的基本特点.这里我们需要谈一谈右值引用究竟有什么好处.

我们知道,右值引用是不能引用左值的,但是下面的方法可以使左值属性变成右值.这里我们就可以看到move只是改变了左值的属性,不会做其他的事情.

int main()
{
	int x = 111;
	int&& y = move(x);
	return 0;
}

在这里插入图片描述

左值引用的缺点

我们开始好奇,为何会出现右值引用,这是由于左值引用有照顾不到的地方.我们知道左值引用的出现在引用传参的时候避免了深拷贝,提高了效率.但是我们知道有一个场景我们使用左值引用需要慎重考虑.我们返回引用的时候需要仔细思虑一下,不要返回函数内部的局部变量,这个出了函数是要被销毁的.那么我们只能值返回,这就需要拷贝构造,甚至需要深拷贝.这效率可就低了去了.

看一下杨辉三角的代码,你返回试试尤其当数据大的时候,这个效率更低.

class Solution {
public:
vector<vector<int>> generate(int numRows) {
        vector<vector<int>> vv(numRows);
		for (int i = 0; i<numRows; i++)
		{
			vv[i].resize(i + 1, 0);
		}


		// 遍历  vv
		for (int i = 0; i<numRows; i++)
		{
			for (int j = 0; j<vv[i].size(); j++)
			{
				if (j == 0 || j == vv[i].size() - 1)
				{
					vv[i][j] = 1;
				}
				else
				{
					vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];
				}
			}
		}
		return vv;
    }
};

这个时候就出现了右值引用了.我们先来看一下效果.

int main()
{
	string s1("hello");
	string s2 = move(s1);
	return 0;
}

在这里插入图片描述

移动构造

移动构造就是我们使用右值引用重新写一个构造函数.当我们传入左值的时候就使用左值的构造函数,右值的就匹配右值的.这就有点意思了,我们这里用自己模拟实现的string来和大家验证.


namespace bit
{
    class string
    {
    public:

        string(const char* str = "")
            :_size(strlen(str))
            , _capacity(_size)
        {
            _str = new char[_capacity + 1];
            strcpy(_str, str);
        }
        // 拷贝构造
        string(const string& s)
            :_str(nullptr)
        {
            cout << "string(const string& s) -- 深拷贝" << endl;
            string tmp(s._str);
            swap(tmp);
        }
        // 移动构造
        string(string && s)
            :_str(nullptr)
            , _size(0)
            , _capacity(0)
        {
            cout << "string(string&& s) -- 移动语义" << endl;
            swap(s);
        }
        ~string()
        {
            delete[] _str;
            _str = nullptr;
        }

        void reserve(size_t n)
        {
            if (n > _capacity)
            {
                char* tmp = new char[n + 1];
                strcpy(tmp, _str);
                delete[] _str;
                _str = tmp;
                _capacity = n;
            }
        }
        void push_back(char ch)
        {
            if (_size >= _capacity)
            {
                size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
                reserve(newcapacity);
            }
            _str[_size] = ch;
            ++_size;
            _str[_size] = '\0';
        }
        string& operator+=(char ch)
        {
            push_back(ch);
            return *this;
        }
        private:
        void swap(string& s)
        {
            ::swap(_str, s._str);
            ::swap(_size, s._size);
            ::swap(_capacity, s._capacity);
        }
        private:
        char* _str;
        size_t _size;
        size_t _capacity; // 不包含最后做标识的\0
    };

    //这个实现是不对的
    bit::string to_string(int val)
    {
        bit::string str;
        while (val > 0)
        {
            int x = val % 10;
            val /= 10;
            str += ('0' + x);
        }
        return str;
    }
}

这里我们使用那个不正确的to_string来验证.我们先把右值引用的构造函数给屏蔽掉.

int main()
{
	bit::string str = bit::to_string(123);
	return 0;
}

image-20221005141316460

这里我需要解释一下.上面代码的原理实际上是下面图显示的,这里是需要两次拷贝构造的,而且是深拷贝.编译器一看,这两次连续了,就把他们优化成一次了,也就是我们上面得到一次打印的结果.

image-20221005141900947

这里我们就要看看把移动构造放开,这里只调用了移动构造,这是有原因的.

int main()
{
	bit::string str = bit::to_string(123);
	return 0;
}

image-20221005164650612

我先说一下原因,我们首先构造一个拷贝构造出一个临时变量,要知道里临时变量具有常性,这里我们可以认为是右值.这里使用移动拷贝构造.

image-20221005165055628

原理是是上面谈的,这里编译器会发生优化,直接进行移动构造,这里出现了问题,str可是地地道道的左值,移动拷贝里面的参数是右值引用,接受不了左值.但是实际上我们需要这样分析,右值又可以分为两种.

  • 纯右值 例如10,20,‘a’…
  • 将亡值 函数返回值,临时对象…

这里我们就会看看到to_string中str是一个左值.这里编译器发现你这个变量出来了就会被销毁,资源就浪费了,编译器就会把他move成一个右值,这个右值是一个将亡值.它的作用就是为了资源转移.

int main()
{
	bit::string s1 = "hello";
	bit::string s2(s1);
	bit::string s3 = "word";
	bit::string s4(move(s3));
	return 0;
}

在这里插入图片描述

image-20221005170702556

移动赋值

移动赋值这就不用说了,作用和之前一样.

// 移动赋值
string& operator=(string && s)
{
    cout << "string& operator=(string&& s) -- 移动语义" << endl;
    swap(s);
    return *this;
}

这里测试一下就可以了.

int main()
{
	bit::string s1("hello");
	bit::string s2;
	s2 = move(s1);
	return 0;
}

image-20221005171440361

总结

现在我们需要总结一下.C++98的时候我们谈到了类面存在六个默认的函数,今天要再加上两个了,就是上面的移动构造和移动赋值.不过这两个默认的函数编译器自动生成还有一定的要求.

如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造.

完美转发

完美转发是底层的原理,这里我们需要了解一下,有的选择题可能会考.

万能引用

我们先来看一下下面的例子.这个结果会让你大跌眼见(不知道成语).

void Fun(int& x) 
{ 
	cout << "左值引用" << endl;
}
void Fun(const int& x) 
{
	cout << "const 左值引用" << endl; 
}
void Fun(int&& x) 
{ 
	cout << "右值引用" << endl; 
}
void Fun(const int&& x)
{
	cout << "const 右值引用" << endl; 
}
template<typename T>
void PerfectForward(T&& t)
{
	Fun(t);
}
int main()
{
	PerfectForward(10); // 右值
	int a;
	PerfectForward(a); // 左值
	PerfectForward(std::move(a)); // 右值
	const int b = 8;
	PerfectForward(b); // const 左值
	PerfectForward(std::move(b)); // const 右值
	return 0;
}

image-20221005172446853

这个就涉及到万能引用了,模板里面的引用无论是左值还是右值同一退化成了左值,这就是万能指针.

template<typename T>
void PerfectForward(T&& t)
{
	Fun(t);
}

完美转发

我们希望能够在传递过程中保持它的左值或者右值的属性, 就需要用我们下面学习的完美转发.我们这里开始想对于万能引用,传入左值还好说,就是右值有点难搞,我们不能对传入的值都move掉,这样有都变成了右值了.

template<typename T>
void PerfectForward(T&& t)
{
	Fun(move(t));
}

image-20221005173640451

完美转发很好的解决了这个场景,我们可以使用完美转发是的参数恢复它的右值还是左值属性.

void PerfectForward(T&& t)
{
	Fun(std::forward<T>(t));
}

image-20221005174035967

应用场景

C++11之后STL里面的增加右值的插入,这些是对于那些资源进行转移的.

image-20221005175509665

完美转发是主要在底层使用的,我们里模拟实现的list为例.我们这里给右值的push_back和右值的insert

namespace bit
{
	template<class T>
	struct list_node
	{
		list_node<T>* _next;
		list_node<T>* _prev;
		T _data;

		list_node(const T& val = T())
			:_next(nullptr)
			, _prev(nullptr)
			, _data(val)
		{}
	};

	
	template<class T, class Ref, class Ptr>
	struct __list_iterator
	{
		typedef list_node<T> Node;
		typedef __list_iterator<T, Ref, Ptr> self;

		typedef Ptr pointer;
		typedef Ref reference;

		Node* _node;

		__list_iterator(Node* node)
			:_node(node)
		{}
		Ref operator*()
		{
			return _node->_data;
		}

		Ptr operator->()
		{
			//return &(operator*());
			return &_node->_data;
		}

		self& operator++()
		{
			_node = _node->_next;
			return *this;
		}

		self operator++(int)
		{
			self tmp(*this);
			_node = _node->_next;
			return tmp;
		}

		self& operator--()
		{
			_node = _node->_prev;
			return *this;
		}

		self operator--(int)
		{
			self tmp(*this);
			_node = _node->_prev;
			return tmp;
		}


		bool operator!=(const self& it)
		{
			return _node != it._node;
		}

		bool operator==(const self& it)
		{
			return _node == it._node;
		}

	};

	template<class T>
	class list
	{
		typedef list_node<T> Node;
	public:

		typedef __list_iterator<T, T&, T*> iterator;
		typedef __list_iterator<T, const T&, const T*> const_iterator;

		const_iterator begin() const
		{
			return const_iterator(_head->_next);
		}

		const_iterator end() const
		{
			return const_iterator(_head);
		}

		iterator begin()
		{
			return iterator(_head->_next);
		}

		iterator end()
		{
			return iterator(_head);
		}

		list()
		{
			_head = new Node();
			_head->_next = _head;
			_head->_prev = _head;
		}

		void empty_init()
		{
			_head = new Node();
			_head->_next = _head;
			_head->_prev = _head;
		}


		void swap(list<T>& lt)
		{
			std::swap(_head, lt._head);
		}



		void push_back(const T& x)
		{
			insert(end(), x);
		}

		void push_back(T&& xx)
		{
			//insert(end(), std::forward<T>(xx));
			insert(end(), xx);
		}


	
		// 插入在pos位置之前
		iterator insert(iterator pos, const T& x)
		{
			Node* newNode = new Node(x);
			Node* cur = pos._node;
			Node* prev = cur->_prev;

			// prev  newnode  cur
			prev->_next = newNode;
			newNode->_prev = prev;
			newNode->_next = cur;
			cur->_prev = newNode;

			return iterator(newNode);
		}

		iterator insert(iterator pos, T&& xx)
		{
			//Node* newNode = new Node(std::forward<T>(xx));
			Node* newNode = new Node(xx);
			Node* cur = pos._node;
			Node* prev = cur->_prev;

			// prev  newnode  cur
			prev->_next = newNode;
			newNode->_prev = prev;
			newNode->_next = cur;
			cur->_prev = newNode;

			return iterator(newNode);
		}

	private:
		Node* _head;
	};
}

这里我们测试一下,你会发现结果和我们预料的不一样.

int main()
{
	bit::list<bit::string> lt;
	bit::string s1("1111");
	// 这里调用的是拷贝构造
	lt.push_back(s1);

	// 下面调用都是移动构造
	lt.push_back("2222");
	lt.push_back(std::move(s1));

	return 0;
}

image-20221005184504151

这里面的原因就是万能引用,参数无论是左值还是右值,这里会退化成左值.这里push_back和insert都要使用完美转发.

void push_back(const T& x)
{
    insert(end(), x);
}

void push_back(T&& xx)
{
    insert(end(), std::forward<T>(xx));
}

// 插入在pos位置之前
iterator insert(iterator pos, const T& x)
{
    Node* newNode = new Node(x);
    Node* cur = pos._node;
    Node* prev = cur->_prev;

    // prev  newnode  cur
    prev->_next = newNode;
    newNode->_prev = prev;
    newNode->_next = cur;
    cur->_prev = newNode;

    return iterator(newNode);
}

iterator insert(iterator pos, T&& xx)
{
    Node* newNode = new Node(std::forward<T>(xx));
    Node* cur = pos._node;
    Node* prev = cur->_prev;

    // prev  newnode  cur
    prev->_next = newNode;
    newNode->_prev = prev;
    newNode->_next = cur;
    cur->_prev = newNode;

    return iterator(newNode);
}

我们这里需要再次测试下这个是不是正确的.

image-20221005184504151

这是有原因的,我们在new 节点间的是时候也是传入了引用,节点的构造函数也是万能引用.

template<class T>
struct list_node
{
    list_node<T>* _next;
    list_node<T>* _prev;
    T _data;

    list_node(const T& val = T())
        :_next(nullptr)
            , _prev(nullptr)
            , _data(val)
        {}

    list_node(T&& val)
        :_next(nullptr)
            , _prev(nullptr)
            , _data(std::forward<T>(val))
        {}
};

image-20221005185004015

default&delete

这是两个关键字里面的内容都是挺简单的

  • 强制生成默认函数的关键字default
  • 止生成默认函数的关键字delete
// default
class Person
{
    public:
    Person(const char* name = "", int age = 0)
        :_name(name)
            , _age(age)
        {}
    Person(const Person& p)
        :_name(p._name)
            ,_age(p._age)
        {}
    Person(Person&& p) = default;
    private:
    bit::string _name;
    int _age;
};
// delete
class Person
{
    public:
    Person(const char* name = "", int age = 0)
        :_name(name)
            , _age(age)
        {}
    Person(const Person& p) = delete;
    private:
    bit::string _name;
    int _age;
};

可变参数模板

这个看起来非常的恶心,看起来非常不C++.这个起源是C语言的printf.对于这个知识点我们只要求理解,有可能选择题会涉及到.image-20221005190312310

相对于printf.可变参数模板更加的恶心,编译甚至不知道参数的类型.

// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
template <class ...Args>
void ShowList(Args... args)
{}

参数个数

这里我们要谈两个方面,计算模板参数的大小和打印模板参数对应的数据.

template <class ...Args>
void ShowList(Args... args)
{
	cout << sizeof...(args) << endl;
}
int main()
{

	ShowList(1, 2, 3, 4, 5);
	return 0;
}

image-20221005202612367

打印数据还是有点困难的,这里我们需要先看一下做法,后面和大家分析.

template <class T>
void ShowList(const T& val)
{
	cout << val <<"->"<<typeid(val).name()<<" end"<< endl;
}

template <class T, class ...Args>
void ShowList(const T& val, Args... args)
{
	cout << sizeof...(args) << endl;
	cout << val <<" -> "<<typeid(val).name() << endl;
	ShowList(args...);
}

int main()
{
	ShowList(1, 2, 3, 4, 5);
	return 0;
}

image-20221005202925323

这里我们用两个参数简单的分析一下.这里剩最后一个参数的时候,编译器看到有一个现成,直接用了.

image-20221005203250548

有的人可能还用下面的方法.这里是因为参数包允许是零个.

void ShowList()
{
}
template <class T, class ...Args>
void ShowList(const T& val, Args... args)
{
	cout << sizeof...(args) << endl;
	cout << val <<" -> "<< typeid(val).name() << endl;
	ShowList(args...);
}

int main()
{
	ShowList(1, 2);
	return 0;
}

image-20221005203530609

这里就有点问题了,一般情况下,我们只有模板参数这一个参数,前面是没有另外的参数的.

template <class ...Args>
void ShowList(Args... args)
{
}

这里我们可以给出另外一种方法.这里我们不做解释.

template <class T>
int PrintArg(const T& t)
{
	cout << t << " ";
	return 0;
}
template <class ...Args>
void ShowList(Args... args)
{
	int arr[] = { PrintArg(args)... };
	cout << endl;
}

int main()
{
	ShowList(1, 'x', 1.1, string("hello world"));
	cout << endl;

	ShowList(1, 2, 3, 4, 5);
	return 0;
}

image-20221005204047491

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-10-08 20:22:12  更:2022-10-08 20:25:04 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 2:27:03-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码