一、列表初始化
1、{}初始化:
#include <iostream>
using namespace std;
struct Point
{
Point(int x, int y)
:_x(x)
,_y(y)
{
cout << "Point(int x, int y)" << endl;
}
private:
int _x;
int _y;
};
//一切皆可用{}初始化且可以不加=
//(但是建议日常定义不要去掉=)
int main()
{
int x = 1;
int y = { 2 };
int z{ 3 };
//本质都是调用构造函数
Point a1(1, 1);
Point a2 = { 2,2 };//实质上是多参数隐式类型转换
Point a3{ 3,3 };
const Point& r = { 3,3 }; //类型转换产生临时对象具有常性
int arr1[] = { 0,1,2 };
int arr2[]{ 0,1,2 };
Point ptr1[]{ a1,a2 };
Point ptr2[]{ {0,0},{1,1} };
int* ptr3 = new int[]{ 1,2,3 };
Point* ptr3 = new Point[]{ {0,0},{1,1} };
}
2、initializer_list
int main()
{
//不同的规则
vector<int> arr = { 0,1,2,3 };//vector支持initializer_list的构造函数
Point a1{ 1,1 };//直接调用--隐式类型转换
initializer_list<int> in1 = { 1,2,3 };//常量区数组{ 0,1,2 }存在常量区,本质还是调用initializer_list的构造函数,底层是两个指针(_start和_finish)
cout << typeid(in1).name() << endl;
cout << sizeof(in1)<< endl;//两个指针
int a[] = { 1,2,3 };
//错误写法:和initializer_list冲突
//const int* ptr = { 1,2,3 };
}
//支持initializer_list构造函数
vector(initializer_list<T> It)
{
reserve(It.size());
for (auto e : It)
{
push_back(e);
}
}
二.、变量类型推导
template<class Func>
class B
{
private:
Func _f;
};
int main()
{
//定义变量
int i = 0;
auto p = &i;
auto pf = malloc;
//auto x无法推导x类型
//显示类型
cout << typeid(pf).name() << endl;
//推导类型
//typeid(pf).name() ptr//错误,推出类型是一个字符串,只能看不能用
decltype(pf) pf2;
B<decltype(pf)> bb;
}
三、新增加容器---静态数组array、forward_list以及unordered系列
四、新增接口
1、emplace系列(性能提升)
2、移动构造和移动赋值
五、默认成员函数控制
原来C++类中,有6个默认成员函数:
1. 构造函数
2. 析构函数
3. 拷贝构造函数
4. 拷贝赋值重载
5. 取地址重载
6. const 取地址重载
最后重要的是前4个,后两个用处不大。默认成员函数就是我们不写编译器会生成一个默认的。
C++11 新增了两个:移动构造函数和移动赋值运算符重载。
针对移动构造函数和移动赋值运算符重载有一些需要注意的点如下:
如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任
意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类
型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,
如果实现了就调用移动构造,没有实现就调用拷贝构造。
如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中
的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内
置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋
值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造
完全类似)
如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。
六、左值与右值
1、左值与右值
可以取地址的是左值,左值可以出现在赋值符号的左右。
右值不能取地址,只能出现在赋值符号的右边
右值不能取地址,只能出现在赋值符号的右边
int fmin(int a, int b)
{
return a < b ? a : b;
}
int main()
{
// 以下为左值
int* ptr = new int(0);
int b = 1;
const int c = 2;
"xxxxx";//字符串为右值,但此表达式返回首元素地址(为左值)
const char* p = "xxxxx";
p[2];
double x = 1.1, y = 2.2;
// 以下为右值
10;
x + y;
fmin(x, y);
return 0;
}
2、左值引用与右值引用
int main()
{
double x = 1.1, y = 2.2;
// 左值引用:给左值取别名
int a = 0;
int& r1 = a;
// 右值引用:给右值取别名
int&& r5 = 10;
double&& r6 = x + y;
// 左值引用能否给右值取别名?
// const左值引用可以
const int& r2 = 10;
const double& r3 = x + y;//x + y的返回值为临时变量具有常性
// 右值引用能否给左值取别名?
// 右值引用可以引用move以后的左值
int&& r7 = move(a);
return 0;
}
3、左值引用的使用场景和价值是什么?
使用场景:1、做参数 2、做返回值
价值:减少拷贝
内置类型的右值:纯右值
自定义类型的右值:将亡值
左值:深拷贝
右值:移动拷贝
//string.h
#pragma once
#include<assert.h>
#include<iostream>
using namespace std;
namespace star
{
class string
{
public:
string(const char* str = "")
:_size(strlen(str))
, _capacity(_size)
, _str(new char[_capacity+1])
{
cout << "string(const char* str)" << endl;
strcpy(_str, str);
}
//拷贝构造--深拷贝
string(const string& s)
:_str(nullptr)
{
cout << "string(const string& s)--深拷贝" << endl;
}
//拷贝构造--移动拷贝
string(string&& s)
:_str(nullptr)
{
cout << "string(string&& s)--移动拷贝" << endl;
swap(s);
}
//析构函数
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
size_t size() const
{
return _size;
}
void swap(string& s)
{
std::swap(_capacity, s._capacity);
std::swap(_size, s._size);
std::swap(_str, s._str);
}
//左值:深拷贝
/*string& operator=(string& tmp)
{
cout << "string& operator=(string& s)--深拷贝" << endl;
swap(tmp);
return *this;
}*/
//左值:深拷贝
string& operator=(const string& s)
{
cout << "string& operator=(string& s)--深拷贝" << endl;
string tmp(s);
swap(tmp);
return *this;
}
//右值:移动拷贝
string& operator=(string&& s)
{
cout << "string& operator=(string&& s--移动拷贝)" << endl;
swap(s);
return *this;
}
private:
size_t _size;
size_t _capacity;
char* _str;
public:
const static size_t npos;
};
const size_t string::npos = -1;
}
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
#include"string.h"
star::string Func()
{
star::string ret = "xxxxxxxxxxx";//"xxxxxxxxxxx"为右值,ret为左值
return ret;
}
int main()
{
//ret = 左值--深拷贝
//ret = 右值--移动拷贝
star::string ret1 = Func();
star::string ret2;
ret2 = Func();
return 0;
}
ret1编译优化前:
str(左值)先拷贝构造(深拷贝)后移动构造(浅拷贝),Func的返回值是右值。
编译优化后:
连续的构造/拷贝构造,合二为一,编译器把str识别成右值--将亡值。
ret2:编译器也进行了优化处理,把str识别成了右值--将亡值,先移动构造,再移动赋值
//错误写法
star::string& Func()
{
star::string ret = "xxxxxxxxxxx";//字符串为右值
return ret;
}
star::string&& Func()
{
star::string ret = "xxxxxxxxxxx";//字符串为右值
return ret;
}
错误写法如上,返回值是ret的别名,而ret正常销毁,不会将ret识别成将亡值(只有传值返回才会触发编译器的优化)(引用返回只要不是在栈区上均可)。
七、lambda表达式
1、可调用对象:
函数指针--能不用就不用 void(*ptr)(int x)
仿函数--类 重载operator() 对象可以像函数一样使用
lambda--匿名函数对象
2、表达式语法:
[capture-list]捕捉列表
函数指针--能不用就不用 void(*ptr)(int x)
仿函数--类 重载operator() 对象可以像函数一样使用
lambda--匿名函数对象
2、表达式语法:
[capture-list]捕捉列表
[var]:表示值传递方式捕捉变量var
[=]:表示值传递方式捕获所有父作用域中的变量(包括this)
[&var]:表示引用传递捕捉变量var
[&]:表示引用传递捕捉所有父作用域中的变量(包括this)
[this]:表示值传递方式捕捉当前的this指针
(parameters)参数列表(若没有参数传递,可以省略)
→returntype:返回值类型(没有返回值,可以省略)
{statement}:函数体
3、用法示例:
//内部可以调用全局函数,不能调用局部函数。
void func()
{
cout << "func()" << endl;
}
int main()
{
int a = 0, b = 2;
double rate = 2.5;
auto add1 = [](int x, int y)->int {return x + y; };
auto add2 = [](int x, int y) {return x + y; };
//捕捉变量
auto add3 = [rate](int x, int y) {return (x + y)* rate; };
cout << add1(a, b) << endl;
cout << add2(a, b) << endl;
cout << add3(a, b) << endl;
//捕捉函数
auto swap1 = [add1](int& x, int& y) {
int tmp = x;
x = y;
y = tmp;
cout << add1(x, y) << endl;
func();
};
swap1(a, b);
return 0;
}
int main()
{
int x = 0, y = 2;
//auto swap1 = [x, y]() mutable {
mutable让捕捉的x和y可以改变了,
但是他们依旧是外面x和y的拷贝
// int tmp = x;
// x = y;
// y = tmp;
//};
//swap1();
// 引用的方式捕捉
auto swap2 = [&x, &y](){
int tmp = x;
x = y;
y = tmp;
};
swap2();
int a = 0;
int b = 1;
int c = 2;
int d = 3;
const int e = 1;
cout << &e << endl;
// 引用的方式捕捉所有对象,除了a
// a用传值的方式捕捉
auto func = [&, a] {
//a++;
b++;
c++;
d++;
//e++;
cout << &e << endl;
};
func();
return 0;
}
int main()
{
auto f1 = [](int x, int y) {return x + y; };
auto f2 = [](int x, int y) {return x + y; };
//f1 = f2;不可以这么赋值,因为类型不同
cout << typeid(f1).name() << endl;
cout << typeid(f2).name() << endl;
return 0;
}
4、底层原理:与仿函数相同
八、包装器
1、可变参数包/可变模版参数
//模板可变参数
template <class ...Args>
void ShowList(Args... args)
{
cout << sizeof...(args) << endl;//打印参数包里值的个数
// 参数包不支持这么打印
/*for (size_t i = 0; i < sizeof...(args); i++)
{
cout << args[i] << endl;
}*/
}
//正确打印方式:重载编译递归
void _ShowList()
{
// 结束条件的函数
cout << endl;
}
template <class T, class ...Args>
void _ShowList(T val, Args... args)
{
cout << val << " ";
_ShowList(args...);
}
模版可变参数使传参更加灵活
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{
cout << "Date构造" << endl;
}
Date(const Date& d)
:_year(d._year)
, _month(d._month)
, _day(d._day)
{
cout << "Date拷贝构造" << endl;
}
private:
int _year;
int _month;
int _day;
};
template <class ...Args>
Date* Create(Args... args)
{
Date* ret = new Date(args...);
return ret;
}
int main()
{
Date* p1 = Create();
Date* p2 = Create(2023);
Date* p3 = Create(2023, 9);
Date* p4 = Create(2023, 9, 27);
Date d(2023, 1, 1);
Date* p5 = Create(d);
return 0;
}
2、emplace系列
1.)emplace_back支持可变参数,拿到构建pair对象的参数后自己去创建对象,除了用法上,和push_back没什么太大的区别
2.)对于拷贝构造和移动构造对象而言,emplace_back是直接构造了,push_back是先构造,再移动构造。
int main()
{
std::list< std::pair<int, string> > mylist;
mylist.emplace_back(10, "sort");
mylist.push_back(make_pair(30, "sort"));
std::list<Date> lt;
Date d(2023, 9, 27);
// 只能传日期类对象
lt.push_back(d);
// 传日期类对象
// 传日期类对象的参数包
// 参数包,一路往下传,直接去构造或者拷贝构造节点中日期类对象
lt.emplace_back(d);
lt.emplace_back(2023, 9, 27);
return 0;
}
3、包装器--解决可调用对象的类型问题
#include<iostream>
#include<functional>
#include<vector>
using namespace std;
double f(double i)
{
return i / 2;
}
struct Functor
{
double operator()(double d)
{
return d / 3;
}
};
int main()
{
// 包装器 -- 可调用对象的类型问题
function<double(double)> f1 = f;
function<double(double)> f2 = [](double d)->double { return d / 4; };
function<double(double)> f3 = Functor();
//vector<function<double(double)>> v = { f1, f2, f3 };
vector<function<double(double)>> v = { f, [](double d)->double { return d / 4; }, Functor() };
double n = 3.3;
for (auto f : v)
{
cout << f(n++) << endl;
}
return 0;
}
4、bind:底层和lambda类似,也是生成仿函数
int main()
{
function<double(int, int)> Plus1 = bind(Plus, placeholders::_1, placeholders::_2, 4.0);
function<double(int, int)> Plus2 = bind(Plus, placeholders::_1, placeholders::_2, 4.2);
function<double(int, int)> Plus3 = bind(Plus, placeholders::_1, placeholders::_2, 4.4);
cout << Plus1(5, 3) << endl;
cout << Plus2(5, 3) << endl;
cout << Plus3(5, 3) << endl;
function<double(int, int)> PPlus1 = bind(PPlus, placeholders::_1, 4.0, placeholders::_2);
function<double(int, int)> PPlus2 = bind(PPlus, placeholders::_1, 4.2, placeholders::_2);
cout << PPlus1(5, 3) << endl;
cout << PPlus2(5, 3) << endl;
function<double(int, int)> Sub1 = bind(&SubType::sub, placeholders::_1, placeholders::_2);//静态成员取地址要可加&可不加
SubType st;
function<double(int, int)> Sub2 = bind(&SubType::ssub, &st, placeholders::_1, placeholders::_2, 3);//非静态成员取地址要加&,且要再传一个参数(对象地址)
cout << Sub1(1, 2) << endl;
cout << Sub2(1, 2) << endl;
function<double(int, int)> Sub3 = bind(&SubType::ssub, SubType(), placeholders::_1, placeholders::_2, 3);//传对象也可以
cout << Sub3(1, 2) << endl;
cout << typeid(Sub3).name() << endl;
return 0;
}