C++98传统的{}
// C++98中⼀般数组和结构体可以⽤{}进⾏初始化。
struct Point
{
int _x;
int _y;
};
int main()
{
int array1[] = { 1, 2, 3, 4, 5 };
int array2[5] = { 0 };
Point p = { 1, 2 };
return 0;
}
#include<iostream>
#include<vector>
using namespace std;
struct Point
{
int _x;
int _y;
};
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{
cout << "Date(int year, int month, int day)" << endl;
}
Date(const Date& d)
:_year(d._year)
, _month(d._month)
, _day(d._day)
{
cout << "Date(const Date& d)" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
// C++98⽀持的
int a1[] = { 1, 2, 3, 4, 5 };
int a2[5] = { 0 };
Point p = { 1, 2 };
// C++11⽀持的
// 内置类型⽀持
int x1 = { 2 };
// ⾃定义类型⽀持
// 这⾥本质是⽤{ 2025, 1, 1}构造⼀个Date临时对象
// 临时对象再去拷⻉构造d1,编译器优化后合⼆为⼀变成{ 2025, 1, 1}直接构造初始化
Date d1 = { 2025, 1, 1};
//C++98⽀持单参数时类型转换,也可以不⽤{}
Date d3 = { 2025};//c++11
Date d4 = 2025;//c++98
//可以省略掉=
Point p1 { 1, 2 };
int x2 { 2 };
Date d6 { 2024, 7, 25 };
const Date& d7 { 2024, 7, 25 };
//只有初始化列表才支持省略=
Date d8 2025//会报错
}
vector<int> v1={1,2,3,4};
initializer_list<int>l1={10,20,30};//本质是底层在栈上开一个数组,
//这里在语义上表示构造+拷贝构造+优化,,,但编译器会优化成直接构造
//本质也可以理解为隐式类型转换
vector<int> v1={1,2,3,4};
vector<int> v2{1,2,3,4};
//这里在语义上表示直接进行构造
vector<int> v3({1,2,3,4});//调用initializer_list进行构造
//上述两种方式在语义上表示的意思不同,但最后的结果是相同的
double x = 1.1, y = 2.2;
const int& rx1 = 10;
const double& rx2 = x + y;
int* p = new int(0);
int b = 1;
string s("111111");
int&& rr1 = move(b);
int*&& rr2 = move(p);
string&& rr3 = move(s);
string&& rr4 = (string&&)s;//move本质是进行强转
int& tt1 = rr1;//用左值引用来引用右值引用表达式
左值引用与右值引用在底层其实就是指针
右值引用可用于为临时对象延长生命周期,const的左值引用也能延长临时对象生存期,但这些对象无法被修改。
如果想用引用来延长被调用的函数内部局部变量的生命周期,这是不被允许的。第一点:引用不会改变变量的存储位置。第二点:局部变量是创建在函数栈帧中的,当函数调用结束栈帧销毁,局部变量也会随之销毁。
string s1 = "test";
//string&& r1 = s1;//右值引用无法引用左值
const string& r2 = s1 + s1;
//r2 += s1;//const左值可以引用右值,但无法进行修改
string&& r3 = s1 + s1;
r3 += s1;
cout << r3 << endl;
void func(int& x)
{
cout << "左值引用" << x <<endl;
}
void func(const int& x)
{
cout << "const左值引用" << x << endl;
}
void func(int&& x)
{
cout << "右值引用" << x<<endl;
}
int main()
{
int i = 1;
const int ci = 2;
func(i);
func(ci);
func(3);
func(move(i));
int&& x = 1;
func(x);
func(move(x));
return 0;
}
左值引用与右值引用最终目的是减少拷贝、提高效率。 左值引用还可以修改参数或者返回值,方便使用
在部分函数场景,只能传值返回,不能传引用返回。比如:当前函数的局部对象,出了当前函数的作用域就销毁
移动构造是进行指针交换,其本质是“掠夺资源”。被掠夺的右值的指针则指向”空“
所以一个左值不能轻易的去move,因为这会导致左值的资源被掠夺
右值对象构造,只有拷贝构造,没有移动构造的场景
vs2019debug环境下编译器对拷贝进行了优化。左边为优化前的场景,右边为优化后的场景。看到编译器直接将两次拷贝构造合二为一了。
右值对象构造,有拷贝构造,也有移动构造的场景
vs2019debug环境下编译器对拷贝进行了优化。当移动构造与拷贝构造同时存在时,编译器会选择代价小的移动构造。优化前,需要进行两次移动构造,优化后只需进行一次移动构造
如果是在2019的release或者2022的环境下,则只进行一次构造。在2019的release或者2022的环境下str直接变成了ret的引用。
如果想看未优化的场景,在Linux下通过:g++ test.cpp -fno-elide-constructors关闭构造优化来观察。
右值对象赋值,只有拷贝构造和拷贝赋值,没有移动构造和移动赋值的场景
右值对象赋值,既有拷贝构造和拷贝赋值,也有移动构造和移动赋值的场景
str本质是对临时对象的引用,修改str,临时对象也会被修改。
左值调用拷贝构造,因为左值数据不能轻易改动,可能会影响到后面的程序。 右值调用移动构造,因为右值生命周期极短,比起拷贝构造,用移动构造付出的代价更小,并且效率更高
右值引用和移动语义在传参中的提效
右值:
泛左值:
typedef int& lref;
typedef int&& rref;
int n = 0;
//只有当是右值右值时,才是右值引用。有左则是左
lref& r1 = n;
lref&& r2 = n;
rref& r3 = n;
rref&& r4 = 1;
------------------------------------------------------------
template<class T>
void f1(T& x)
{
}
template<class T>
void f2(T&& x)
{ }
int main()
{
typedef int& lref;
typedef int&& rref;
int n = 0;
lref& r1 = n;
lref&& r2 = n;
rref& r3 = n;
rref&& r4 = 1;
f1<int>(n);//这里没有引用折叠
//f1<int>(0);//是左值类型,所以不能引用右值
f1<int&>(n);
//f1<int&>(0);//调用的是f1,因为引用折叠,所以f1只可能是左值
f1<int&&>(n);
//f1<int&&>(0);////调用的是f1,因为引用折叠,所以f1只可能是左值
f1<const int&>(n);
f1<const int&>(0);//const 左值可以引用右值
f1<const int&&>(n);
f1<const int&&>(0);//因为引用折叠,所以推出是左值类型,const左值可以引用右值
//f2<int>(n);//这里没有引用折叠,所以推出类型是右值,右值不能引用左值
f2<int>(0);
f2<int&>(n);
//f2<int&>(0);存在引用折叠,所以有左则是左,所以推出是左值类型,不能引用右值
//f2<int&&>(n);//存在引用折叠,全右则右,所以推出是右值类型,右值引用不能引用左值
f2<int&&>(0);
return 0;
}
template<class T>
void func2(T&& z)
{
cout << z << endl;
}
template<class T>
void func1(T&& t)
{
//func2(t);因为右值引用表达式属性是左值,如果没调用完美转发,则传给func2的参数的属性是左值
func2(forward<T>(t));//调用完美转发,则会维护参数的属性,传递给func2的参数的属性则保持为右值
cout << t << endl;
}
int main()
{
int x = 1;
func1(1);
return 0;
}
在Function函数内部推出了参数的类型,但是由于右值引用表达式是左值属性,所以在调用Func函数时,调用的是左值引用的函数。
完美转发维护了Function函数内部传给Func函数参数的属性,所以右值能调用到右值引用的函数
//前面是模板参数包,后面是函数参数包
template <class ...Args> void Func(Args... args) {}
template <class ...Args> void Func(Args&... args) {}
template <class ...Args> void Func(Args&&... args) {}
计算参数包中参数的个数:
template<class ...Args>
//前提:有模板语法支持
//这里是一个万能引用+可变参数模板+引用折叠。编译器会根据传入的参数实例化出对应的函数
void Print(Args&&...args)
{
cout << sizeof...(args) << endl;
}
int main()
{
int x = 1;
string s1("1");
double y = 1.1;
Print();
Print(1);
Print(1,s1,y);
return 0;
}
------------------------------
在使用可变参数模板时,从语法层面我们可以理解为:
函数模板:一个函数模板,可以实例化出多个不同类型的函数 可变参数模板:一个可变参数模板函数,可以实例化出多个参数个数不同的函数模板
包扩展:解析一个包就叫做包扩展
void ShowList()//当包中的参数个数为0时,调用这个函数
{
cout << endl;
}
template<class T,class ...Args>
void ShowList(T x,Args...args)//将参数包的第一个参数匹配给x,再将参数个数为n-1的参数包匹配给args
{
/*
这种写法是错误的,因为包展开匹配解析是在编译时进行的,而if判断是在程序运行时进行的
if(sizeof...(args)==0)
return;
*/
cout << x << " ";
ShowList(args...);
}
template<class ...Args>
void Print(Args&&...args)
{
//cout << sizeof...(args) << endl;
ShowList(args...);
}
int main()
{
Print(1,string("22222"),2.2);
return 0;
}
包扩展详细过程第一种方法:
编译时递归
将包扩展的过程展开写就是:
void ShowList()
{
cout << endl;
}
void ShowList( double z)
{
cout << z << " ";
ShowList();
}
void ShowList(string y, double z)
{
cout << y<< " ";
ShowList( z);
}
void ShowList(int x,string y,double z)
{
cout << x << " ";
ShowList(y,z);
}
template<class ...Args>
void Print(Args&&...args)
{
//cout << sizeof...(args) << endl;
ShowList(args...);
}
int main()
{
Print(1,string("22222"),2.2);
return 0;
}
模板是写给编译器的,模板实例化的过程交给编译器来完成,方便程序员
包扩展详细过程第二种方法:
template<class T>
const T& GetArs(const T& x)
{
cout << x << " ";
return x;
}
template<class ...Args>
void Arguments(Args...args)
{ }
template<class ...Args>
void Print(Args&&...args)
{
Arguments(GetArs(args)...);、
/*
void Print(int x,string y,double z)
{
Arguments(GetArs(x),GetArs(y),GetArs(z));
}
*/
}
int main()
{
Print(1,string("22222"),2.2);
return 0;
}
-----------------------------------
/*
不能写成如下这样,因为这不符合c++语法规则。
参数包展开(args...)必须发生在允许的上下文环境中如:函数调用参数、初始化列表或模板参数列表等地方。
单独的GetArs(args)...;会被解析为试图展开多个表达式语句,这在语法上是不合法的。
*/
template<class ...Args>
void Print(Args&&...args)
{
GetArs(args)...;
}
emplace_back:
push_back:
两者区别:
当是以隐式类型转换的方式传入值时:
emplace_back是直接构造
push_back是调用构造在调用移动构造。
当插入的参数是多参数时,push_back()须使用make_pair,而emplace_back则不需要使用make_pair
emplace系列兼容push系列和insert的功能,部分场景下emplace可以直接构造,push和insert则是调用构造+移动构造/拷贝构造。 所以emplace系列接口综合而言更强大
lambda表达式的格式:
[capture-list] (parameters)-> return type {function boby }
/*
捕捉列表不能省略
参数列表与返回值可以省略
函数体不能省略
*/
int main()
{
auto add1 = [](int x, int y)->int {return x + y; };
cout << add1(1,2) << endl;
auto func1 = []
{
cout << "hello world" << endl;
return 0;
};
func1();
return 0;
}
lambda 表达式中默认只能用lambda 函数体和参数中的变量,如果想用外层作用域中的变量就需要进行捕捉。
捕捉方法:
同一个变量不能捕捉两次
值捕捉的变量不能修改,因为值捕捉的变量相当于默认加了const
引用捕捉允许对变量进行修改
namespace liu
{
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);
}
void swap(string& s)
{
::swap(_str, s._str);
::swap(_size, s._size);
::swap(_capacity, s._capacity);
}
//构造
string(const string& s)
{
cout << "string(const string& s) -- 拷⻉构造" << endl;
reserve(s._capacity);
for (auto ch : s)
{
push_back(ch);
}
}
// 移动构造
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;
};
};
class Person
{
public:
Person(const char*name="张三", int age=1)
:_name(name)
,_age(age)
{ }
~Person()
{
}
private:
liu::string _name;
int _age;
};
int main()
{
//如果Person类中没有实现析构函数、拷贝构造、拷贝赋值重载,那么编译器会自己生成一个移动构造,由于Person类中包含了string类,string类中实现了移动构造,所以直接调用string类中的移动构造
Person s1;
Person s2=s1;
Person s3=(move(s2));
//因为Person类中实现了析构函数,所以编译器不会自己生成移动赋值,哪怕string类中实现了移动赋值,也不会调用。
Person s4;
s4 = move(s2);
return 0;
}
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;
};
int main()
{
Person s1;
Person s2 = s1;
Person s3 = std::move(s1);
return 0;
}
std::function 是一个类模板,也是一个包装器。std::function 的实例对象可以包装存储其他的可以调用对象:
存储的可调用对象称为:function的目标,若没有目标,则称为空,调⽤空 std::function 的目标导致抛出std::bad_function_call异常
int f(int a,int b)
{
return a + b;
}
struct Functor
{
public:
int operator()(int a, int b)
{
return a + b;
}
};
int main()
{
//包装函数
function<int(int, int)> f1 = f;
//包装仿函数
function<int(int, int)> f2 = Functor();
//包装lamdba
function<int(int, int)> f3 = [](int a, int b) {return a + b; };
cout << f1(1,1) << endl;
cout << f2(1,1) << endl;
cout << f3(1,1) << endl;
//包装静态成员函数,需要指定类域
function<int(int, int)>f4 = Plus::plusi;
cout << f4(1,1) << endl;
//包装非静态成员函数还需传入this指针
function<int(Plus*,double, double) > f5 = &Plus::plusd;
Plus pl;
cout << f5(&pl, 1, 1) << endl;
//包装非静态成员函数也可以像如下这样,因为this本质是不允许显示传递的。
//传入Plus*其实是用指针调用成员函数,传入Plus是用对象调用成员函数
//调用成员函数其实是使用".*"操作符进行调用
function<double(Plus, double, double) > f6 = &Plus::plusd;
cout << f6(pl, 1.1, 1.1) << endl;
//包装非静态成员函数也可以像如下这样。也可也使用左值引用,只不过使用左值引用就不能传入Plus()匿名对象了
function<double(Plus&&, double, double)> f7 = &Plus::plusd;
cout << f7(move(pl), 1.1, 1.1) << endl;
cout << f7(Plus(), 1.1, 1.1) << endl;
return 0;
}