🎇C++学习历程:入门
- 博客主页:一起去看日落吗
- 持续分享博主的C++学习历程
博主的能力有限,出现错误希望大家不吝赐教- 分享给大家一句我很喜欢的话: 也许你现在做的事情,暂时看不到成果,但不要忘记,树🌿成长之前也要扎根,也要在漫长的时光🌞中沉淀养分。静下来想一想,哪有这么多的天赋异禀,那些让你羡慕的优秀的人也都曾默默地翻山越岭🐾。
♠️ ♥️ ♣️ ♦️
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
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;
}

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
void PerfectForward(T&& t) {Fun(std::forward(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;}

template
struct ListNode
{ListNode* _next = nullptr;ListNode* _prev = nullptr;T _data;
};
template
class List
{typedef ListNode Node;
public:List(){_head = new Node;_head->_next = _head;_head->_prev = _head;}void PushBack(T&& x){//Insert(_head, x);Insert(_head, std::forward(x));}void PushFront(T&& x){//Insert(_head->_next, x);Insert(_head->_next, std::forward(x));}void Insert(Node* pos, T&& x){Node* prev = pos->_prev;Node* newnode = new Node;newnode->_data = std::forward(x); // 关键位置// prev newnode posprev->_next = newnode;newnode->_prev = prev;newnode->_next = pos;pos->_prev = newnode;}void Insert(Node* pos, const T& x){Node* prev = pos->_prev;Node* newnode = new Node;newnode->_data = x; // 关键位置// prev newnode posprev->_next = newnode;newnode->_prev = prev;newnode->_next = pos;pos->_prev = newnode;}
private:Node* _head;
};
int main()
{List lt;lt.PushBack("1111");lt.PushFront("2222");return 0;
}
默认成员函数
原来C++类中,有6个默认成员函数:
最后重要的是前4个,后两个用处不大。默认成员函数就是我们不写编译器会生成一个默认的。
C++11 新增了两个:移动构造函数和移动赋值运算符重载。
针对移动构造函数和移动赋值运算符重载有一些需要注意的点如下:





C++11允许在类定义时给成员变量初始缺省值,默认生成构造函数会使用这些缺省值初始化.
C++11可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以使用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:lc::string _name;int _age;
};
int main()
{Person s1;Person s2 = s1;Person s3 = std::move(s1);return 0;
}
如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且只声明补丁已,这样只要其他人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数
class Person
{
public:Person(const char* name = "", int age = 0):_name(name), _age(age){}Person(const Person& p) = delete;
private:lc::string _name;int _age;
};
int main()
{Person s1;//C2280 “Person::Person(const Person&)”: //Person s2 = s1; //Person s3 = std::move(s1);return 0;
}
C++11的新特性可变参数模板能够创建可以接受可变参数的函数模板和类模板,相比C++98/03,类模版和函数模版中只能含固定数量的模版参数,可变模版参数无疑是一个巨大的改进。
template
void ShowList(Args... args)
{}// Args是一个模板参数包,args是一个函数形参参数包;
//声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
上面的参数args前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为“参数包”,它里面包含了0到N(N>=0)个模版参数。我们无法直接获取参数包 args中的每个参数的,只能通过展开参数包的方式来获取参数包中的每个参数 这是使用可变模版参数的一个主要特点。由于语法不支持使用args[i]这样方式获取可变参数

// 递归终止函数
template
void ShowList(const T& t) {cout << t << endl;
}
// 展开函数
template
void ShowList(T value, Args... args) {cout << value << " ";ShowList(args...);
}
int main()
{ShowList(1);ShowList(1, 'A');ShowList(1, 'A', std::string("sort"));return 0;
}

这种展开参数包的方式,不需要通过递归终止函数,是直接在expand函数体中展开的, printarg不是一个递归终止函数,只是一个处理参数包中每一个参数的函数。这种就地展开参数包的方式实现的关键是逗号表达式。逗号表达式会按顺序执行逗号前面的表达式
template
void PrintArg(T t) {cout << t << " ";
}
//展开函数
template
void ShowList(Args... args) {int arr[] = { (PrintArg(args), 0)... };cout << endl;
}
int main()
{ShowList(1);ShowList(1, 'A');ShowList(1, 'A', std::string("sort"));return 0;
}
template
void emplace_back (Args&&... args);
int main()
{std::list< std::pair > mylist;mylist.emplace_back(10, 'a');mylist.emplace_back(20, 'b');//mylist.emplace_back({ 2, 'v' }); emplace_back不支持这种写法(模板参数,而push_back的参数是固定的可以推导)std::pair kv(100, 'x');mylist.emplace_back(kv); // pair 左值mylist.emplace_back(make_pair(30, 'c')); // pair 右值mylist.push_back(kv);mylist.push_back(make_pair(30, 'c'));mylist.push_back({ 50, 'e' });mylist.emplace_back(10, 'a');/*std::pair kv1(100, 'x');构造std::pair kv2 = { 100, 'x' }; 构造+拷贝std::pair kv3{ 100, 'x' };构造+拷贝std::pair { 100, 'x' };匿名对象 */for (auto e : mylist)cout << e.first << ":" << e.second << endl;return 0;
}
int main()
{std::list< std::pair > mylist;std::pair kv(1, "11111");mylist.push_back(kv);mylist.emplace_back(kv);cout << endl;mylist.push_back(make_pair(2, "sort"));mylist.push_back({ 40, "sort" });cout << endl;mylist.emplace_back(make_pair(2, "sort"));mylist.emplace_back(10, "sort");return 0;
}

注意:emplace_back在底层是实现是先构造(new)再调用定位new;push_back先构造(new)再拷贝构造(或者移动拷贝)
可调用类型-类型定义的对象可以像函数一样去调用
在C++98中,如果想要对一个数据集合中的元素进行排序,可以使用std::sort方法
#include
#include
int main()
{int array[] = { 4,1,8,5,3,7,0,9,2,6 };// 默认按照小于比较,排出来结果是升序std::sort(array, array + sizeof(array) / sizeof(array[0]));// 如果需要降序,需要改变元素的比较规则std::sort(array, array + sizeof(array) / sizeof(array[0]), greater());return 0;
}
如果待排序元素为自定义类型,需要用户定义排序时的比较规则
#include
#include
struct Goods
{string _name;double _price;
};
struct Compare
{bool operator()(const Goods& gl, const Goods& gr){return gl._price <= gr._price;}
};
int main()
{Goods gds[] = { { "苹果", 2.1 }, { "香蕉", 3 }, { "橙子", 2.2 }, {"菠萝",1.5} };sort(gds, gds + sizeof(gds) / sizeof(gds[0]), Compare());return 0;
}
struct Goods
{string _name;double _price;
};
int main()
{Goods gds[] = { { "苹果", 2.1 }, { "相交", 3 }, { "橙子", 2.2 }, {"菠萝",1.5} };sort(gds, gds + sizeof(gds) / sizeof(gds[0]), [](const Goods& l, constGoods& r)->bool{return l._price < r._price;});return 0;
}
上述代码就是使用C++11中的lambda表达式来解决,可以看出lamb表达式实际是一个匿名函数。
lambda表达式书写格式:[capture-list] (parameters) mutable -> return-type { statement}
lambda表达式各部分说明:
注意: 在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为空。因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情。
void(*PF)();
int main()
{// 最简单的lambda表达式, 该lambda表达式没有任何意义[] {};// 省略参数列表和返回值类型,返回值类型由编译器推导为intint a = 3, b = 4;// 省略了返回值类型,无返回值类型auto fun1 = [&a,&b](int c) {b = a + c; };fun1(10);cout << a << " " << b << endl;//捕捉列表可以是lambda表达式auto fun = [fun1] {cout << "hello" << endl;};fun();// 各部分都很完善的lambda函数auto fun2 = [=, &b](int c)->int {return b += a + c; };cout << fun2(10) << endl;// 复制捕捉xint x = 10;auto add_x = [x](int a) mutable { x *= 2; return a + x; };cout << add_x(10) << endl;// 编译失败--->提示找不到operator=() //auto fun3 = [&a,&b](int c) {b = a + c;};//fun1 = fun3; //允许使用一个lambda表达式拷贝构造一个新的副本auto fun3(fun);fun();//可以将lambda表达式赋值给相同类型的函数指针auto f2 = [] {};PF = f2;PF();return 0;
}
lambda表达式实际上可以理解为无名函数,该函数无法直接调用,如果想要直接调用,可借助auto将其赋值给一个变量
捕获列表说明
捕捉列表描述了上下文中那些数据可以被lambda使用,以及使用的方式传值还是传引用。
注意:
函数对象,又称为仿函数,即可以想函数一样使用的对象,就是在类中重载了operator()运算符的类对象
class Rate
{
public:Rate(double rate) : _rate(rate){}double operator()(double money, int year){return money * _rate * year;}
private:double _rate;
};
int main()
{// 函数对象double rate = 0.49;Rate r1(rate);r1(10000, 2);// lamberauto r2 = [=](double monty, int year)->double {return monty * rate * year;};r2(10000, 2);return 0;
}
从使用方式上来看,函数对象与lambda表达式完全一样。
函数对象将rate作为其成员变量,在定义对象时给出初始值即可,lambda表达式通过捕获列表可以直接将该变量捕获到。

实际在底层编译器对于lambda表达式的处理方式,完全就是按照函数对象的方式处理的,即:如果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator()。
function包装器 也叫作适配器。C++中的function本质是一个类模板,也是一个包装器。
之前学过的函数指针、仿函数、lamber表达式这些都是可调用的类型!但是类型丰富,无法做到类型统一,可能会导致模板的效率低下!这是就需要包装器了
template T useF(F f, T x) {static int count = 0;cout << "count:" << ++count << endl;cout << "count:" << &count << endl;return f(x);
}
double f(double i) {return i / 2;
}
struct Functor
{double operator()(double d){return d / 3;}
};
int main()
{// 函数名cout << useF(f, 11.11) << endl; //count:1 count:0025C140 5.555// 函数对象cout << useF(Functor(), 11.11) << endl; //count:1 count: 0025C144 3.70333// lamber表达式cout << useF([](double d)->double { return d / 4; }, 11.11) << endl; //count : 1 count: 0025C148 2.7775return 0;
}
通过上面的程序验证,会发现useF函数模板实例化了三份。
std::function在头文件
// 类模板原型如下
template function; // undefined
template
class function;//模板参数说明:
//Ret: 被调用函数的返回类型
//Args…:被调用函数的形参
template T useF(F f, T x) {static int count = 0;cout << "count:" << ++count << endl;cout << "count:" << &count << endl;return (f(x));
}
double f(double i) {return i / 2;
}
struct Functor
{double operator()(double d){return d / 3;}
};int main()
{// 函数名function fn1 = f;//function fn1 ( f); 支持cout << useF(fn1, 11.11) << endl; // 函数对象function fn2=Functor();//function fn2(Functor()); //不支持,因为function、Functor()不是同一个类型(类型匹配匹配不上)//function fn2=&Functor::operator(); //支持//function fn2 = bind(&Functor::operator(), Functor(), placeholders::_1); //支持cout << useF(fn2, 11.11) << endl; // lamber表达式function fn3([](double d)->double { return d / 4; });cout << useF(fn3, 11.11) << endl; return 0;
}
#include
int f(int a, int b) {return a + b;
}
struct Functor
{
public:int operator() (int a, int b){return a + b;}
};
class Plus
{
public:static int plusi(int a, int b){return a + b;}double plusd(double a, double b){return a + b;}
};
int main()
{// 函数名(函数指针)std::function func1 = f;cout << func1(1, 2) << endl;// 函数对象std::function func2 = Functor();cout << func2(1, 2) << endl;// lamber表达式std::function func3 = [](const int a, const int b){return a + b; };cout << func3(1, 2) << endl;// 类的成员函数// 包装类的静态成员函数std::function func4 = Plus::plusi;//std::function func4 = &Plus::plusi; 都可以cout << func4(1, 2) << endl;// 包装类的非静态成员函数std::function func5 = &Plus::plusd;cout << func5(Plus(), 1.1, 2.2) << endl;return 0;
}
包装器可以是使用在map、unordered_map……这类容器中便于调用函数(eg:map
std::bind函数定义在头文件中,是一个函数模板,它就像一个函数包装器(适配器),接受一个可调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表。一般而言,我们用它可以把一个原本接收N个参数的函数fn,通过绑定一些参数,返回一个接收M个(M可以大于N,但这么做没什么意义)参数的新函数。同时,使用std::bind函数还可以实现参数顺序调整等操作。
//头文件
// 原型如下:
template
/* unspecified */ bind (Fn&& fn, Args&&... args);
// with return type (2)
template
/* unspecified */ bind (Fn&& fn, Args&&... args);
可以将bind函数看作是一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表。 调用bind的一般形式:auto newCallable =bind(callable,arg_list);`
其中,newCallable本身是一个可调用对象,arg_list是一个逗号分隔的参数列表,对应给定的callable的参数。当我们调用newCallable时,newCallable会调用callable,并传给它arg_list中的参数。
arg_list中的参数可能包含形如_n的名字,其中n是一个整数,这些参数是“占位符”,表示newCallable的参数,它们占据了传递给newCallable的参数的“位置”。数值n表示生成的可调用对象中参数的位置:_1为newCallable的第一个参数,_2为第二个参数,以此类推。
int Plus(int a, int b) {return a + b;
}
class Sub
{
public:int sub(int a, int b){return a - b;}
};
int main()
{//表示绑定函数plus 参数分别由调用 func1 的第一,二个参数指定// 使用bind进行优化std::function func1 = std::bind(Plus, placeholders::_1,placeholders::_2);//auto func1 = std::bind(Plus, placeholders::_1, placeholders::_2);//func2的类型为 function 与func1类型一样//表示绑定函数 plus 的第一,二为: 1, 2// 需要绑定的参数,直接绑定值,不需要绑定的参数给 placeholders::_1 、 placeholders::_2.... 进行占位function func2 = bind(Plus, 1,2);cout << func1(1, 2) << endl;cout << func2() << endl;Sub s;// 绑定成员函数std::function func3 = std::bind(&Sub::sub, s,placeholders::_1, placeholders::_2);// 参数调换顺序std::function func4 = std::bind(&Sub::sub, s,placeholders::_2, placeholders::_1);cout << func3(1, 2) << endl;cout << func4(1, 2) << endl;return 0;
}