C++11系列学习参考:
话不多说,我们直接进入正题!
一、可变参数模板
1、基本语法
//可变参数类模板
template <class ...Args>
//普通类型可变参数函数模板
void Func(Args... args) {}
//左值引用类型 可变参数函数模板
void Func(Args&... args) {}
//万能引用类型 可变参数函数模板
void Func(Args&&... args) {}
//Args为可变参数类型 ...表示为可变参数模板 args为参数包
template<class ...Args>
void Calculate(Args&&...args)
{
//计算参数包中参数的个数
cout << sizeof...(args) << endl;
}
void Test()
{
//参数包中没有参数
Calculate();
//有一个参数
Calculate(10);
//有两个参数
Calculate(3.14, string("helloworld"));
//有三个参数
Calculate(10, 3.14, string("helloworld"));
}
2、底层原理
实际使用时,当我们定义了一个可变参数的函数模板,在调用函数时,编译器会根据我们传递的参数的个数和类型,结合引用折叠的规则,去自动实例化对应的函数。
void Test()
{
//参数包中没有参数
Calculate();
//有一个参数
Calculate(10);
//有两个参数
Calculate(3.14, string("helloworld"));
//有三个参数
Calculate(10, 3.14, string("helloworld"));
}
//编译器会根据我们的函数调用,结合引用折叠的规则,实例化以下四个函数
void Calculate(){}
void Calculate(int&& arg){}
void Calculate(double&& arg1, string&& arg2){}
void Calculate(int&& arg1, double&& arg2, string&& arg3){}
本质上来说如果没有可变参数模板这个东西,我们必须要去实现多个这样的函数模板才可以支持这样的功能,有了可变模板参数,我们进一步被解放,让我们的泛型编程更加灵活。
//如果没有可变参数模板,下面这些函数模板需要我们自己去实现,才能支持上面的功能
void Calculate(){}
template <class T>
void Calculate(T1&& arg1){}
template <class T1, class T2>
void Calculate(T1&& arg1, T2&& arg2){}
template <class T1, class T2, class T3>
void Calculate(T1&& arg1, T2&& arg2, T3&& arg3){}
3、包扩展
//本质时编译时递归,终止条件需要放在上面
//递归的终止条件,参数包剩余0个参数时自动匹配,终止递归
void ShowList()
{
cout<< endl;
}
//实现包扩展的函数模板
template<class T,class ... Args>
void ShowList(T value,Args... args)
{
//打印参数包中第一个参数,剩下的n-1个递归ShowList获取
cout << value << " ";
ShowList(args ...);
}
void Test()
{
ShowList();
ShowList(1);
ShowList(1, 2.2);
ShowList(1, 2.2, 3.14);
ShowList(1, 2.2, 3.14,"helloworld");
}
解析参数包的结果:
template <class T>
const T & GetArg(const T & value)
{
cout << value << " ";
return value;
}
template < class ...Args>
void Arguments(Args... args)
{
}
template <class ...Args>
void Print(Args... args)
{
// 注意GetArg必须返回或者到的对象,这样才能组成参数包给Arguments
Arguments(GetArg(args)...);
cout << endl;
}
void Test()
{
Print();
Print(1.1);
Print(1.1, 2, 2);
Print(1.1, 2.2, 3.3);
Print(1.1, 2.2, 3.3, "helloworld");
}
4、emplace系列接口




namespace _zwy
{
//链表的节点
template<class T>
struct ListNode
{
ListNode<T>* _next;
ListNode<T>* _prev;
T _data;
ListNode(T&& data)
:_next(nullptr)
, _prev(nullptr)
, _data(move(data))
{}
template <class... Args>
ListNode(Args&&... args)
: _next(nullptr)
, _prev(nullptr)
, _data(std::forward<Args>(args)...)
{}
};
//链表的迭代器
template<class T, class Ref, class Ptr>
struct ListIterator
{
typedef ListNode<T> Node;
typedef ListIterator<T, Ref, Ptr> Self;
Node* _node;
ListIterator(Node* node)
:_node(node)
{}
// ++it;
Self& operator++()
{
_node = _node->_next;
return *this;
}Self& operator--()
{
_node = _node->_prev;
return *this;
}
Ref operator*()
{
return _node->_data;
}
bool operator!=(const Self& it)
{
return _node != it._node;
}
};
template<class T>
class list
{
typedef ListNode<T> Node;
public:
typedef ListIterator<T, T&, T*> iterator;
typedef ListIterator<T, const T&, const T*> const_iterator;
iterator begin()
{
return iterator(_head->_next);
}
iterator end()
{
return iterator(_head);
}
void empty_init()
{
_head = new Node();
_head->_next = _head;
_head->_prev = _head;
}
list()
{
empty_init();
}
void push_back(const T& x)
{
insert(end(), x);
}
void push_back(T&& x)
{
insert(end(), move(x));
}
iterator insert(iterator pos, const T& x)
{
Node* cur = pos._node;
Node* newnode = new Node(x);
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&& x)
{
Node* cur = pos._node;
Node* newnode = new Node(move(x));
Node* prev = cur->_prev;
// prev newnode cur
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
return iterator(newnode);
}
template <class... Args>
void emplace_back(Args&&... args)
{
insert(end(), std::forward<Args>(args)...);
}
template <class... Args>
iterator insert(iterator pos, Args&&... args)
{
Node* cur = pos._node;
Node* newnode = new Node(std::forward<Args>(args)...);
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;
};
}
结合我们模拟实现的string,我们研究emplace哪种场景下更高效。
class string
{
public:
typedef char* iterator;
typedef const char* const_iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
string(const char* str = "")
:_size(strlen(str))
, _capacity(_size)
{
cout << "string(char* str)-构造" << endl;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
// 拷贝构造
string(const string& s)
{
cout << "string(const string& s) -- 拷贝构造" << endl;
reserve(s._capacity);
for (auto ch : s)
{
push_back(ch);
}
}
void swap(string& ss)
{
::swap(_str, ss._str);
::swap(_size, ss._size);
::swap(_capacity, ss._capacity);
}
// 移动构造
string(string&& s)
{
cout << "string(string&& s) -- 移动构造" << endl;
// 转移掠夺你的资源
swap(s);
}
string& operator=(const string& s)
{
cout << "string& operator=(const string& s) -- 拷贝赋值" <<
endl;
if (this != &s)
{
_str[0] = '\0';
_size = 0;
reserve(s._capacity);
for (auto ch : s)
{
push_back(ch);
}
}
return *this;
}
// 移动赋值
string& operator=(string&& s)
{
cout << "string& operator=(string&& s) -- 移动赋值" << endl;
swap(s);
return *this;
}
~string()
{
//cout << "~string() -- 析构" << endl;
delete[] _str;
_str = nullptr;
}
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
if (_str)
{
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;
}
const char* c_str() const
{
return _str;
}
size_t size() const
{
return _size;
}
private:
char* _str = new char('\0');
size_t _size = 0;
size_t _capacity = 0;
};
}
下面是具体的比较
void Test()
{
_zwy::list<_zwy::string> lt;
// 传左值,跟push_back⼀样,⾛拷贝构造
_zwy::string s1("helloworld");
lt.emplace_back(s1);
cout<< endl;
// 传右值,跟push_back⼀样,走移动构造
lt.emplace_back(move(s1));
cout << endl;
// 直接把构造string参数包往下传,直接用string参数包构造string
// 这⾥达到的效果是push_back做不到的
lt.emplace_back("helloworld");
cout << endl;
_zwy::list<pair<_zwy::string, int>> lt1;
// 传左值跟push_back⼀样
// 构造pair + 拷贝构造pair到list的节点中data上
pair<_zwy::string, int> kv("苹果", 1);
lt1.emplace_back(kv);
cout << endl;
// 传右值跟push_back⼀样
//构造pair + 移动构造pair到list的节点中data上
lt1.emplace_back(move(kv));
cout << endl;
// 直接把构造pair参数包往下传,直接⽤pair参数包构造pair
// 这里达到的效果是push_back做不到的
lt1.emplace_back("苹果", 1);
cout << endl;
}
如果传递的是具体的对象,那么emplace_back和push_back效率一样,如果传递的是构造对象的参数包,emplace_back可以直接传递构造函数所需的参数,在容器内原地构造对象,无需先构造再拷贝或移动,而push_back做不到,emplace总体而言更高效,更安全。推荐以后使emplace系列替代insert和push系列进行容器的插入!
二、新的类功能
1、默认的移动构造和移动赋值函数
在C++11之前类中,一共有6个默认成员函数,分别是构造函数、析构函数、拷贝构造函数、拷贝赋值重载、取地址重载,const 取地址重载。最重要的是前四个默认成员函数。
C++11后,新增了两个默认成员函数,移动构造函数和移动赋值重载。不过新增的默认成员函数的生成需要一定的条件。
(1)、移动构造:
(2)、移动赋值重载:
如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意 ⼀个,那么编译器会自动生成⼀个默认移动赋值。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,否则就调用拷贝赋值。(默认移动赋值重载跟移动构造完全类似)
class Student
{
public:
//只提供构造函数
//不提供析构函数 、拷贝构造、拷贝赋值重载的任意一个
//编译器自动生成移动构造和移动赋值
Student(const char* name="", int age=0)
:_name(name)
,_age(age)
{}
private:
_zwy::string _name;
int _age;
};
void Test()
{
Student s1;
//调用编译器生成的拷贝构造
Student s2 = s1;
//调用编译器生成的移动构造
Student s3 = move(s1);
Student s4;
//调用编译器生成的移动赋值
s4 = move(s2);
}

2、声明时给缺省值
class Date
{
public:
//成员变量在声明位置给了缺省值,如果没有在初始化列表显示初始化
// 那么初始化列表会用这个缺省值初始化
//
//默认构造函数 三个成员变量都在初始化列表用缺省值初始化
Date()
{}
//构造函数 _year和_month用缺省值初始化
Date(int day)
:_day(day)
{}
private:
//声明类的成员变量给缺省值
int _year = 2024;
int _month = 11;
int _day = 19;
};
void Test()
{
//d1 缺省值 2024 11 19
Date d1;
//d2 2024 11 20
Date d2(20);
}
3、default和delete
(1)、default
class Student
{
public:
Student(const char* name="", int age=0)
:_name(name)
,_age(age)
{
cout << "构造函数:Student(const char* name="", int age=0)" << endl;
}
Student(const Student& s)
:_name(s._name)
,_age(s._age)
{
cout << "拷贝构造:Student(const Student & s)" << endl;
}
//提供了拷贝构造,编译器不会生成默认的移动构造,我们指定生成
Student(Student&& s)= default;
private:
_zwy::string _name;
int _age;
};
void Test()
{
Student s1("张三", 18);
//拷贝构造
Student s2(s1);
//使用编译器指定生成的移动构造
Student s3(move(s1));
}
可以看到移动构造s3后,s1被置空了,说明确实调用强制编译器生成的移动构造。
(2)、delete
class Student
{
public:
Student(const char* name="", int age=0)
:_name(name)
,_age(age)
{
cout << "构造函数:Student(const char* name="", int age=0)" << endl;
}
//将拷贝构造函数删除,编译器不会默认生成,调用拷贝构造就会报错
Student(const Student& s) = delete;
private:
_zwy::string _name;
int _age;
};
void Test()
{
Student s1("张三", 18);
//拷贝构造被删除,编译报错
Student s2(s1);
}
三、STL标准库的变化
1、新增容器:
2、容器的范围for遍历
void Test()
{
//范围for遍历修改vector
vector<int> v = { 1,2,3,4,5 };
for (auto& e : v)
{
e *= 10;
cout << e << " ";
}
cout << endl;
//范围for遍历string
string s = "helloworld";
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
}
四、包装器function
语法格式为:
std::function<返回值类型(参数类型列表)> 变量名
1、包装函数指针:
int add(int x, int y)
{
return x + y;
}
void Test()
{
//返回值类型int 参数类型为 int,int
function<int(int,int)> f1 = add;
//调用包装器对象
cout << f1(5, 10) << endl;
}
2、包装仿函数对象:
struct Greater
{
bool operator()(int x, int y)
{
return x > y;
}
};
void Test()
{
//返回值类型bool 参数类型为 int,int
function<bool(int, int)> f2 = Greater();
//调用包装器对象
cout << f2(10 ,5) << endl;
}
3、包装lambda表达式:
void Test()
{
auto sub=[](int x, int y)->int {return x - y; };
function<int(int, int)> f3 = sub;
cout << f3(10, 5) << endl;
}
4、包装类的成员函数:
成员函数要指定类域并且前面加&才能获取地址
普通成员函数还有⼀个隐含的this指针参数,所以绑定时传对象或者对象的指针过去都可以。
class Calculate
{
public:
//静态成员函数
static int add(int x, int y)
{
return x + y;
}
//普通成员函数
double div(double x, double y)
{
return x / y;
}
};
void Test()
{
//包装静态成员函数:
function<int(int, int)> f4 = &Calculate::add;
cout << f4(10,20) << endl;
//包装普通成员函数
//普通成员函数还有⼀个隐含的this指针参数,所以绑定时传对象或者对象的指针过去都可以
Calculate cal;
function<double(Calculate,double, double)> f5 = &Calculate::div;
//传对象
cout << f5(cal, 10.0, 5.0) << endl;
//传对象指针
function<double(Calculate*, double, double)> f6 = &Calculate::div;
cout << f6(&cal, 10.0, 5.0) << endl;
//传对象引用
function<double(Calculate&&, double, double)> f7 = &Calculate::div;
cout << f7(move(cal), 10.0, 5.0) << endl;
}
5、绑定bind
bind调整参数顺序(不常用)
//使用命名空间
using namespace placeholders;
int sub(int a, int b)
{
return a - b;
}
void Test()
{
//_1代表第一个参数,_2代表第二个参数
auto b1 = bind(sub, _1, _2);
cout << b1(10, 20) << endl;
}
如果交换位置呢?达到了调整参数顺序的目的
bind调整参数个数(常用)
在对某些对象调用时,有些参数是我们不期望改变的,我们可以直接使用bind将这些参数绑死,这样调用的时候就不用传递了。
例如:在包装调用成员函数时,我们每次调用都需要显示传递对象或者对象的指针,我们可以使用bind将这个参数绑定,就不需要每次显示传递了。
//使用命名空间
using namespace placeholders;
class Calculate
{
public:
//成员函数
double div(double x, double y)
{
return x / y;
}
};
void Test()
{
//_1代表第一个参数,_2代表第二个参数
//绑死成员函数,以及传递的对象,只需要传递成员函数所需的参数。
function<double(double, double)> f1 = bind(&Calculate::div, Calculate(), _1, _2);
cout << f1(10, 20) << endl;
}
又或者我们提供给用户的一些接口,需要将某些参数固定,其余的参数交给用户去填写,例如计算银行不同理财产品获利的接口。我们可以将利率和年限绑死,本金交给用户,不同的理财产品提供多种不同的存期给用户计算利息。
using namespace placeholders;
void Test()
{
// 计算复利的lambda表达式
auto func = [](double rate, double money, int year)->double
{
double ret = money;
for (int i = 0; i < year; i++)
{
ret += ret * rate;
}
return ret - money;
};
//将不同的利率绑死和存期绑死,留下本金给用户使用
//存期3年 年利率 1.5%
function<double(double)> func3_1_5 = bind(func, 0.015, _1, 3);
//存期5年 年利率 1.5%
function<double(double)> func5_1_5 = bind(func, 0.015, _1, 5);
//存期10年 年利率 2.5%
function<double(double)> func10_2_5 = bind(func, 0.025, _1, 10);
//存期15年 年利率 3.5%
function<double(double)> func20_3_5 = bind(func, 0.035, _1, 15);
//本金用户来填
int x;
//填写本金计算利息
cin >> x;
cout << func3_1_5(x) << endl;
cout << func5_1_5(x) << endl;
cout << func10_2_5(x) << endl;
cout << func20_3_5(x) << endl;
}
五、智能指针
前面的章节我们已经有过详细的讲解了,忘记的同学自行复习吧!
六、总结
到这里关于C++11的重点内容我们就全部讲解完了,后续如果还有需要补充的,我会第一时间更新博文为大家讲解,创作不易,还请多多支持,如果觉得对你有帮助,可以关注博主,后续会为提供更多优质内容!在成为C++高手的路上与你一路相伴! 上述如有差错,还请各位大佬指点斧正!