C++语法(24) C++11

C++语法(24) 哈希应用_哈里沃克的博客-CSDN博客https://blog.csdn.net/m0_63488627/article/details/130714567?spm=1001.2014.3001.5501

目录

1.{}初始化

1.应用

1.普通变量

2.数组和指针

3.自定义类型对象

4.stl中的应用

2.std::initializer_list

1.介绍

2.调用

3.解释

2.声明

1.auto

2.decltype

3.左值和右值

1.介绍

2.左值引用与右值引用比较

1.总结

2.作用

3.右值引用的更高阶理解

1.本质

2.万能引用

4.增加的默认成员函数

1.说明

2.强制生成默认函数的关键字default

3.禁止生成默认函数的关键字delete

1.C++98

2.C++11

5.lambda表达式

1.介绍格式

2.捕获元素格式说明

3.本质

6.线程库

7.可变参数模板

1.求参数包中有几个参数

2.递归式打印args

3.展开函数打印

4.应用

8.包装器---类型模板

类型模板

应用

9.bind---函数模板


1.{}初始化

1.应用

设计的思路大概就是为了使得所有的变量都能兼容花括号

1.普通变量

int x1 = 1;
int x2 = { 1 };
int x3{ 1 };

2.数组和指针

int array1[] = { 1, 2, 3, 4, 5 };
int array2[]{ 1, 2, 3, 4, 5 };
int* p3 = new int[10];
int* p4 = new int[10]{1, 2, 3, 4};

3.自定义类型对象

struct Point
{
	int _x;
	int _y;
};

Point* p5 = new Point[2]{{ 1, 1 }, { 2, 2 }};

4.stl中的应用

int main()
{
	vector v1 = { 1, 2, 3, 4 };
	vector v2{ 1, 2, 3, 4,6,7,7 };

	list lt = { 1, 2 };

	map dict = { { "字符串", "string" }, { "排序", "sort" } };

	return 0;
}

stl中的构造可以自定义数量

2.std::initializer_list

1.介绍

C++语法(24) C++11_第1张图片C++语法(24) C++11_第2张图片

2.调用

auto il = { 1, 2, 3, 4, 5 };
cout << typeid(il).name() << endl;

3.解释

之所以stl中的构造可以自定义数量,得益于initializer_list

C++语法(24) C++11_第3张图片

其实赋值=左边的花括号是initializer_list类型,其实生成list是list类型中的构造函数被调用,随后生成随意大小的list。其实就是将initializer_list花括号的数一个一个push到list中。

例如:自己写的vector中调整支持initializer_list的构造

vector(initializer_list il)
	: _start(nullptr)
	, _finish(nullptr)
	, _endofstorge(nullptr)
{
	auto it = il.begin();
	while (it != il.end())
	{
		push_back(*it);
		it++;
	}
}

void test2()
{
	vectora{1,2,3,4,5,6,7,8};
	for (auto e : a)
	{
		cout << e << " ";
	}
}

C++语法(24) C++11_第4张图片

2.声明

c++11提供了多种简化声明的方式,尤其是在使用模板时

1.auto

可自动推导变量的类型,以往的案例与模拟均有实现过,不过多讨论

int main()
{
	int i = 10;
	auto p = &i;
	auto pf = strcpy;
	cout << typeid(p).name() << endl;
	cout << typeid(pf).name() << endl;

	map dict = { {"sort", "排序"}, {"insert", "插入"} };
	auto it = dict.begin();
	return 0;
}

C++语法(24) C++11_第5张图片

2.decltype

关键字decltype将变量的类型声明为表达式指定的类型。推导类型,有些时候可以防止精度丢失。

int main()
{
	const int x = 1;
	double y = 2.2;
	decltype(x * y) ret; // ret的类型是double
	decltype(&x) p; // p的类型是int*
	cout << typeid(ret).name() << endl;
	cout << typeid(p).name() << endl;
	return 0;
}

C++语法(24) C++11_第6张图片

3.左值和右值

1.介绍

左值:左值是一个表示数据的表达式。变量,指针,引用这些可以获取它的地址+可以对它赋

左值引用:就是给左值取别名,&

int main()
{
    // 以下的p、b、c、*p都是左值
    int* p = new int(0);
    int b = 1;
    const int c = 2;
    // 以下几个是对上面左值的左值引用
    int*& rp = p;
    int& rb = b;
    const int& rc = c;
    int& pvalue = *p;
    return 0;
}

右值:左值是一个表示数据的表达式,不能取地址的值是右值。非左值就是右值,需明确的是左值的位置可以在右边,也就是左值可以赋值给别人,但是右值不能在左边

右值引用:就是给右值取别名,&&

int main()
{
	double x = 1.1, y = 2.2;
	// 以下几个都是常见的右值
	10;
	x + y;
	fmin(x, y);
	// 以下几个都是对右值的右值引用
	int&& rr1 = 10;
	double&& rr2 = x + y;
	double&& rr3 = fmin(x, y);
	// 这里编译会报错:error C2106: “=”: 左操作数必须为左值
	10 = 1;
	x + y = 1;
	fmin(x, y) = 1;
	return 0;
}

2.左值引用与右值引用比较

1.总结

左值引用总结:

1. 左值引用只能引用左值,不能引用右值。
2. 但是const左值引用既可引用左值,也可引用右值

int main()
{
	// 左值引用只能引用左值,不能引用右值。
	int a = 10;
	int& ra1 = a; // ra为a的别名
	//int& ra2 = 10; // 编译失败,因为10是右值
	// const左值引用既可引用左值,也可引用右值。
	const int& ra3 = 10;
	const int& ra4 = a;
	return 0;
}

右值引用总结:
1. 右值引用只能右值,不能引用左值。
2. 但是右值引用可以move以后的左值

int main()
{
	// 右值引用只能右值,不能引用左值。
	int&& r1 = 10;
	// error C2440: “初始化”: 无法从“int”转换为“int &&”
	// message : 无法将左值绑定到右值引用
	int a = 10;
	int&& r2 = a;
	// 右值引用可以引用move以后的左值
	int&& r3 = std::move(a);
	return 0;
}

2.作用

左值引用的作用:减少拷贝(函数传参/函数传返回值)

针对函数传参,彻底解决了减少拷贝,因为普通的左值引用和const左值引用引用左值和右值;但是函数传返回值减少拷贝并没有彻底解决。因为返回值可能是临时变量,那么左值引用就不能返回。

函数传参,彻底解决了减少拷贝

//左值引用彻底解决函数传参
template
void func1(const T& x)
{

}

template
const T& func2(const T& x)
{
	// ...

	return x;
}

//

函数传返回值,并没有彻底解决减少拷贝,以前都是传入输出型参数

//这样写是错误的,因为ret出作用域就失效了。左值引用不可以解决函数传返回值问题
template
T& func3(const T& x)
{
	T ret;
	// ...
	return ret;
}

//以前的解决
template
void func3(const T& x,T& ret)
{
	// ...
}

移动构造

那么右值引用就可以解决输出型参数的问题

借助移动构造将将亡值或者纯右值减少拷贝

string(string&& s)
{
	cout << "string(const string& s) -- 移动拷贝" << endl;

	swap(s);
}

string s1("111111");
string s2 = to_string(-1234);

特别的,其中的s1会变为空字符串,原因是移动构造直接将s1看为右值,将字符串内容全部swap,使得s1的内容变为s2的空内容

string s1("111111");
string s2(move(s1));

移动赋值

// 移动赋值
string& operator=(string&& s)
{
	cout << "string& operator=(string s) -- 移动赋值" << endl;
	swap(s);

	return *this;
}

右值引用和左值引用的减少拷贝原理不一样:

左值引用:起别名,直接起作用

右值引用:间接起作用,通过移动构造和移动赋值将右值(将亡值或者纯右值)进行资源转移

右值引用的价值

1.就是补齐这个最后一块短板,传值返回的拷贝问题

2.对于插入一些插入右值数据,也可以减少拷贝

此外,stl中也兼容了c++11中的移动构造和移动赋值

3.右值引用的更高阶理解

1.本质

右值本身是不被允许做上面一系列的操作的,但是其实设计的时候就是将右值存入到一个指定的空间进行存储,使得其右值引用拥有了左值的属性来完成上面的移动构造的操作。

那么我们能知道右值引用其实本质是左值,那么它就不能用来当右值使用。

2.万能引用

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;
}

我们会发现,该模板下,无论左值右值均能生成。下面是分析

1.但是,我们执行该段代码时会发现,所有的函数打印都是左值。这就是因为上面所说。在右值被右值引用后,这个右值引用其实是是左值,因为它可以改变了嘛(设计的时候就是将右值存入到一个指定的空间进行存储)。所以在PerfectForward函数中的t是左值,反应到Fun函数中打印的一律都是左值。

2.这里看不出什么大问题,但是如果T的类型表示的数据结构内存消耗很大,那么我们在mian函数调用右值传到PerfectForward确实起到了节省拷贝的功能,但是到了PerfectForward函数层,t已经不是右值了,那不就跟没有使用过右值一样吗,还是没起到节省拷贝嘛,如果函数的层数很大,那么就更没什么用了。为了解决这个问题,我们需要引入新的功能

3.完美转发
std::forward:就是会自动推导传入的数最开始是左值还是右值,然后调用其使他变为我们需要的左右值。

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;
}

执行时,会发现对应的左右值都会调用我们想要调用的函数。

4.增加的默认成员函数

1.说明

1.C++11中又多了两个默认函数,就是上面说过的移动构造函数和移动赋值函数

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

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

4.如果提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值

2.强制生成默认函数的关键字default

由于前面讲了,如果析构函数 、拷贝构造、拷贝赋值重载任意一个写了,那就不会有默认的移动构造函数和赋值重载函数。但是关键字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;
};

int main()
{
    Person s1;
    Person s2 = s1;
    Person s3 = std::move(s1);
    return 0;
}

只要向上面的操作一样,针对某个默认函数,我们只需要写出它的申明,在函数的后面加上"=default"就可以生成默认函数了

3.禁止生成默认函数的关键字delete

想要限制某些默认函数的生成,有哪些方法呢?


1.C++98

1.是在该函数设置成private,并且只需要写出其声明的形式即可,这样只要其他人想要调用就会报错。

2.不过,在class中,存在public的函数会调用到默认构造(比如在函数函数中使用了一个临时的对象)那么此时只能说我们只完成了一半的禁止

2.C++11

只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=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;
};

5.lambda表达式

1.介绍格式

lambda表达式书写格式:[捕捉元素] (参数)函数名 ->返回值类型{ 代码段}

[捕捉元素] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用,不可省略。
(传入参数):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略
函数名:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。
->返回值类型:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
{代码段}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量,不可省略。


2.捕获元素格式说明

[var]:表示值传递方式捕捉变量var
[=]:表示值传递方式捕获所有父作用域中的变量(包括this)
[&var]:表示引用传递捕捉变量var
[&]:表示引用传递捕捉所有父作用域中的变量(包括this)
[this]:表示值传递方式捕捉当前的this指针
 

3.本质

其实lambda也是一种对象函数,在底层调用的就是仿函数。其类型是一个随机生成的类型。

6.线程库

C++语法(24) C++11_第7张图片

void print1(int& x)
{
	for (; x < 100; ++x)
	{
		cout <<"thread1:" << x << endl;
	}
}

void print2(int& y)
{
	for (; y < 100; ++y)
	{
		cout <<"thread2:"<< y << endl;
	}
}

int main()
{
	int i = 0;
	thread t1(print1, ref(i));
	thread t2(print2, ref(i));

	t1.join();
	t2.join();

	cout << "xxxxxxxxxxxxxxxxxxxxxxxxxxx" << endl;

	return 0;
}

7.可变参数模板

1.Args是一个模板参数包,args是一个函数形参参数包
2.声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数


1.求参数包中有几个参数

cout << sizeof...(args) << endl;

template 
void ShowList(Args... args)
{
	// 参数包中有几个参数
	cout << sizeof...(args) << endl;
}

int main()
{
	ShowList(1);
	ShowList(1, 1.1);
	ShowList(1, 1.1, string("xxxxxx"));
	ShowList();


	return 0;
}

C++语法(24) C++11_第8张图片

2.递归式打印args

void ShowList()
{
	cout << endl;
}

// args参数包可以接收0-N个参数
template 
void ShowList(T val, Args... args)
{
	cout <

C++语法(24) C++11_第9张图片

 通过递归逐层剥离出数据进行打印

3.展开函数打印

template 
int PrintArg(T t)
{
	cout << t << " ";
	return 0;
}

//展开函数
template 
void ShowList(Args... args)
{
	int arr[] = { PrintArg(args)... };
	cout << endl;
}

int main()
{
	ShowList(1);
	ShowList(1, 1.1);
	ShowList(1, 1.1, string("xxxxxx"));
	return 0;
}

4.应用

stl的容器在C++11中都追加了可变参数模板即emplace_back

8.包装器---类型模板

包装器是一种可调用对象

类型模板

//类模板原型如下
template  function; // undefined
template 
class function;
模板参数说明:
//Ret: 被调用函数的返回类型
//Args…:被调用函数的形参

函数指针,仿函数,函数对象,lambda的使用会让人觉得凌乱,那么用包装器封装后

#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; //静态成员函数
	cout << func4(1, 2) << endl;
	std::function func5 = Plus::plusd; //类成员函数
	cout << func5(Plus(), 1.1, 2.2) << endl;
	return 0;
}

注意

1.成员函数的包装需要先找到类域,还需要在前面加上&修饰

2.类型在主观上看都基本一致,不过调用还是原来的函数,包装器是对各种类型调用的统一

3.此时的对象函数似乎没有达到统一,我们需要对函数进行再包装,在原来的基础上套一次lambda即可

//原本
std::function func5 = Plus::plusd; //类成员函数
cout << func5(Plus(), 1.1, 2.2) << endl;

//改版
Plus plus //生成一个类型
std::function func5 = [&plus](double x,double y)->double{return plus,plusd(x,y);}; //类成员函数
cout << func5(1.1, 2.2) << endl;

应用

力扣:150. 逆波兰表达式求值

class Solution {
public:
    int evalRPN(vector& tokens) {
        stack st;
        
        map> opFunMap=
        {
            {"+",[](int x,int y)->int{return x+y;}},
            {"-",[](int x,int y)->int{return x-y;}},
            {"*",[](int x,int y)->int{return x*y;}},
            {"/",[](int x,int y)->int{return x/y;}},
        };

        for(auto str:tokens)
        {
            if(opFunMap.count(str)==1)
            {
                int right = st.top();
                st.pop();
                int left = st.top();
                st.pop();
                st.push(opFunMap[str](left,right));
            }
            else
                st.push(stoi(str));
        }
        return st.top();
    }
};

如果不使用function,我们只能用条件判断语句一个一个的判断调用。如果遇到的方法有很多,那么这样的行为是不够高效的,固然我们使用function统一调用函数使得简化了代码,而且会很灵活,我们需要增加函数就增加,不会影响到其他的函数

9.bind---函数模板

模板:

// 原型如下:
template 
/* unspecified */ bind (Fn&& fn, Args&&... args);
// with return type (2)
template 
/* unspecified */ bind (Fn&& fn, Args&&... args);
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 的第一,二个参数指定
	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
	auto func2 = std::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;
}

多个参数,我们可以通过bind修改参数的顺序,如func3和func4的使用

你可能感兴趣的:(C++学习,c++,开发语言,算法)