当前位置: 首页 > article >正文

【C++复习】C++11经典语法

文章目录

  • {}列表初始化
      • 1. 初始化内置类型变量
      • 2. 初始化数组
      • 3. 初始化标准容器
      • 4. 初始化自定义类型
      • 5. 构造函数初始化列表
      • 6. 初始化列表(initializer_list)
      • 7. 返回值初始化
      • 8. 静态成员变量和全局变量的就地初始化
      • 9. 防止类型收窄
      • 总结
  • decltype
  • 右值引用
  • 完美转发--由右值引用引发的问题
  • delete default
  • 逗号表达式
      • 特性
      • 示例
      • 注意事项
  • 可变参数模板
    • 展开参数包
  • emplace_back
  • 仿函数
  • function包装器/适配器/类模板
  • bind函数模板/包装器/适配器

{}列表初始化

1. 初始化内置类型变量

  • 直接初始化int a{10};。明确表达初始化过程,避免与赋值混淆。
  • 空列表初始化:如果{}内为空,则内置类型变量会被初始化为该类型的零值,如int b{};会将b初始化为0

2. 初始化数组

  • C++98风格:在C++98中,数组初始化就需要使用{},如int arr1[] = {1, 2, 3};
  • C++11风格:C++11引入了更简洁的数组初始化方式,允许省略=,如int arr2[]{1, 2, 3};

3. 初始化标准容器

  • STL容器:如vectormap等,可以使用{}进行初始化,

    vector<int> v{1, 2, 3};
    map<int, float> m{ {1, 1.0f}, {2, 2.0f} };
    

4. 初始化自定义类型

  • 无模板类:对于自定义的无模板类,可以在构造函数中使用{}初始化成员变量,也可以在对象创建时直接使用{}进行初始化,如struct A { A(int a = 0, int b = 0) : _a(a), _b(b) {} }; A a{1, 2};
  • 模板类:模板类同样支持使用{}进行初始化,如template<class T> struct B { B(T c = 0, T d = 0) : _c(c), _d(d) {} }; B<int> b{3, 4};

5. 构造函数初始化列表

  • 在类的构造函数中,可以使用{}来初始化成员变量,这有助于确保成员变量在构造函数体执行之前就被正确初始化。

6. 初始化列表(initializer_list)

  • C++11引入了initializer_list,允许构造函数接收一个初始化列表作为参数,从而可以更方便地初始化容器或自定义类型。
  • 示例:class C { C(initializer_list<int> lt) : _arr(lt) {} private: vector<int> _arr; }; C c = {1, 2, 3, 4};

7. 返回值初始化

  • 在函数返回时,也可以使用{}来初始化返回值,这有助于确保返回值的正确性和类型安全。

8. 静态成员变量和全局变量的就地初始化

  • 在C++11中,除了构造函数初始化列表外,还允许使用={}对静态成员变量和全局变量进行就地初始化。

9. 防止类型收窄

  • 列表初始化可以防止类型收窄,即当尝试将一种类型的值初始化为另一种类型(且这种转换可能会导致精度损失或范围超出)时,编译器会报错。

总结

int a{10}; //明确表达初始化过程,避免与赋值混淆
int b{}; //空列表初始化 内置类型变量会被初始化为该类型的零值

int arr1[] = {1, 2, 3};
int arr2[]{1, 2, 3};

int* pa = new int[4]{ 1,2,3,4 };

vector<int> v{1, 2, 3};
map<int, float> m{ {1, 1.0f}, {2, 2.0f} };

struct A 
{ 
    A(int a = 0, int b = 0) : _a(a), _b(b) 
    {

    } 
}; 
A a{1, 2};

template<class T> 
struct B 
{ 
    B(T c = 0, T d = 0) : _c(c), _d(d) 
    {
        
    } 
}; 
B<int> b{3, 4};

class C 
{ 
    C(initializer_list<int> lt) : _arr(lt) 
    {
        
    } 
private: 
    vector<int> _arr; 
}; 
C c = {1, 2, 3, 4};

std::string createString() 
{  
    return {"Hello, World!"}; // 使用 {} 初始化返回值  
}

static int staticVar = 42; // 使用 = 初始化  
static std::string staticStringVar{"Static String"}; // 使用 {} 初始化  
  
int globalVar = 100; // 使用 = 初始化  
std::string globalStringVar{"Global String"}; // 使用 {} 初始化  

char c1 = 256; // 可能不会报错,但值会被截断  
// char c2{256}; // 这会编译错误,因为256超出了char的表示范围  
  
// 尝试将浮点数转换为整数,同样使用 {} 可以防止类型收窄  
float f = 3.14;  
int i1 = f; // 隐式转换,f的值被截断为整数  
// int i2{f}; // 这会编译错误,因为浮点数到整数的转换可能不安全  
int i3 = static_cast<int>(f); // 显式转换,明确意图 // 安全的转换  

int main()
{
    auto i = { 10,20,30 };
    cout << typeid(i).name() << endl; // class std::initializer list<int>

    return 0;
}


int main()
{
    vector<int> v1 = { 1,2,3,4,5 }; //  initializer_list构造函数
    list<int> l1 = { 10, 20, 30 }; //  initializer_list构造函数
    Date d1 = { 2024, 1, 9 }; // 参数匹配Date构造==》构造+拷贝=直接构造;
    						  // 参数不匹配,{ 2024, 1, 9, 0}被识别成 initializer_list类型,参数不匹配报错
    
	//vector& operator= (initializer_list<value_type> il);
    v1 = {10, 20, 30}; // 这个时候调的是赋值重载,而不是 initializer_list 的构造
    return 0;
}

decltype

将变量的类型声明为表达式指定的类型。decltype 可以推导对象的类型,这个类型是可以用来模板实参,或者再定义对象。

template<class T1, class T2>
void F(T1 t1, T2 t2)
{
    decltype(t1 * t2) ret;
    cout << typeid(ret).name() << endl;
}

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;

    vector<decltype(ret)> v;   // 使用 ret 的类型去实例化 vector

    F(1, 'a');
    return 0;
}

右值引用

右值引用的出现

左值引用解决了:传引用传参 传引用返回;

左值引用无法解决:局部变量传引用返回。那么传值返回就要经历【构造+拷贝构造】

移动构造

移动构造本质是将参数右值的资源窃取过来,占为已有,就不用做深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己。

https://blog.csdn.net/LHRan_ran_/article/details/133823688?fromshare=blogdetail&sharetype=blogdetail&sharerId=133823688&sharerefer=PC&sharesource=LHRan_ran_&sharefrom=from_link

// 移动构造: 传过来的s是右值 比如 返回值或 ("hello" + "world")
//ape::string ret2 = (s1 + '!'); 右值拷贝 ret2.string( (s1 + '!') );
string(string&& s) noexcept
	:_str(nullptr)
{
	cout << "  string(string&& s)    -- 右值移动拷贝" << endl;
	swap(s); //直接换过来

	s._str = nullptr;
}

ape::string int_to_string(int value)
{
    //return str; 构造一个右值tmp + 调用移动构造 使得s获得返回值

	return move(str);// 这里只是为了调用我们的移动构造输出调试信息 
				 	 // 实际应用中 str是一个出了函数就销毁的变量 这是错误代码 仅为演示!

	// ape::string s = int_to_string(1234);
}

在这里插入图片描述

c++库里移动构造的行为

在这里插入图片描述

总结

左值引用通过引用直接操作 省去中间额外的拷贝
右值引用通过识别是左值还是右值 如果是右值 直接移动拷贝

为什么要有右值引用

为了提高效率 即 右值直接移动拷贝而非深拷贝 因为拷贝时拷贝对象获取到右值的资源后 右值就不再具有价值

因为想提升效率所以搞了右值 但是移动拷贝转移资源时需要”左值“属性 于是这样被设计:s作为”右值引用“ 但是s的属性是左值!

在这里插入图片描述

完美转发–由右值引用引发的问题

Fun(forward(t));将t的属性置为原有属性 当原有属性为左值 你还是左值 是右值 你还是右值 当需要把原有属性是右值但是在该函数需要当成左值来用时 不加forward<T>(t) 即可

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<typename T>
//这里是一个右值引用 当这个函数被调用 t是参数的右值引用 但是此时 t的属性变为了左值
void PerfectForward(T&& t)//模板中的 && 不代表右值引用,而是万能引用,其既能接收左值又能接收右值
{
	//Fun(forward<T>(t));
	Fun(t);
}

int main()
{
	PerfectForward(10);           // 右值

	int a;
	PerfectForward(a);            // 左值
	PerfectForward(move(a));      // 右值

	const int b = 8;
	PerfectForward(b);		      // const 左值
	PerfectForward(move(b));      // const 右值

	return 0;
}

应用场景:如果不使用完美转发就只能手动把forward改成move()

在这里插入图片描述

在这里插入图片描述

右值引用的移动语义出来以后,对深拷贝的类的影响比较大,自定义类的深拷贝传值返回影响也较大,因为移动构造和移动赋值出来以后减少了它们的深拷贝;一些容器的插入接口也新增了右值版本,也减少了深拷贝。但是右值引用对于浅拷贝的类是没有意义的,因为它们没有资源可以转移

默认移动构造/赋值

在这里插入图片描述

delete default

Person(Person&& p) = delete; 		// 不让生成实现
Person(const Person& p) = default;  // 强制编译器生成

C++11可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以使用 default 关键字显示指定移动构造生成。

逗号表达式

逗号表达式的求值顺序是从左到右。整个逗号表达式的值是最后一个表达式的值。

特性

  1. 求值顺序:逗号表达式中的表达式按照从左到右的顺序被求值。
  2. 结果:逗号表达式的结果是最后一个表达式的值,而不是一个列表或者集合。
  3. 副作用:逗号表达式常用于利用其副作用,比如对变量进行多次赋值或调用具有副作用的函数。

示例

#include <iostream>  

int main() 
{  
    int a = 0, b = 0;  

    // 逗号表达式  
    (a = 5, b = a + 1, a + b); 
    // a 被赋值为 5,b 被赋值为 6,表达式的结果是 a + b = 11,但赋值给变量时仅使用最后一个表达式的值  

    // 注意:逗号表达式通常用于语句中,而不是直接赋值给变量  
    // 下面的赋值仅会接收逗号表达式的最后一个表达式的值  
    int result = (a = 2, b = a + 1, a + b); // result 被赋值为 3(即 a + b 的结果)  

    std::cout << "a = " << a << ", b = " << b << ", result = " << result << std::endl;  

    return 0;  
}

注意事项

逗号操作符与逗号分隔符(在函数参数列表、初始化列表等中使用的)在语法上是不同的。逗号操作符用于创建逗号表达式,而逗号分隔符用于分隔列表中的元素。

可变参数模板

template <class ...Args>
void ShowList(Args... args)
{
    cout << sizeof...(args) << endl;
}
int main()
{
    ShowList(1, 2, 3, 4, 5);
    ShowList(1, "abcde", 3.33);

    return 0;
}

展开参数包

编译时的递归推演

// 递归终止函数
void _ShowList()
{
    cout << endl;
}

// 展开函数
template <class T, class ...Args>
void _ShowList(const T& val, Args... args)
{
    cout << val << " ";
    _ShowList(args...);
}

template <class ...Args>
void ShowList(Args... args)
{
    _ShowList(args...);
}

int main()
{
    ShowList(1, 2, 3, 4, 5);
    ShowList(1, "abcde", 3.33);

    return 0;
}

逗号表达式

template <class T>
void PrintArg(T t)
{
    cout << t << " ";
}

template <class ...Args>
void ShowList(Args... args)
{
    // int arr[] = { PrintArg(args)... };
     (PrintArg(args), ...);
    cout << endl; 
}

int main()
{
    ShowList(1, 2, 3, 4, 5);
    ShowList(1, "abcde", 3.33);

    return 0;
}

emplace_back

在这里插入图片描述

int main()
{
    list<string> lt;
    lt.push_back("abc");	// const char* 构造string + string移动构造node
    lt.emplace_back("abc"); //  const char*直接移动构造node(完美转发)

    // 同样的 多参数也是如此
    list<pair<string,int>> lt;
    lt.push_back(make_pair("abc",1));
    lt.emplace_back("abc",1);
    return 0;
}

修改list模拟实现类

template <class... Args>
list_node(Args&&... args)
: _data(args...)
, _next(nullptr)
, _prev(nullptr)
{
    
}

template <class... Args>
void emplace_back(Args&&... args)
{
    Node* newnode = new Node(args...);
    // 链接节点
}

在这里插入图片描述

仿函数

在这里插入图片描述

function的构造函数接收Functor()的临时对象将其封装为一个可调用对象

class TD
{
public:
	void operator()()
	{
		cout << "Thread3" << endl;
	}
};

int main()
{
	function<void()> f = TD();
	f();

	TD td;
	f = td;
	f();

	// 线程函数为仿函数
	thread t1((TD())); // ( TD() ) 必须这样写 原因不明 不重要
	thread t2(td);

	t1.join();
	t2.join();
	cout << "Main thread!" << endl;

	return 0;
}

function包装器/适配器/类模板

https://blog.csdn.net/LHRan_ran_/article/details/133930224?fromshare=blogdetail&sharetype=blogdetail&sharerId=133930224&sharerefer=PC&sharesource=LHRan_ran_&sharefrom=from_link

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

通过给这些可调用对象加上一层包装器,使得函数模板只实例化一份

function的构造函数接收Functor()的临时对象将其封装为一个可调用对象

在这里插入图片描述

包装器的应用

class Solution
{
	typedef long long Long;
public:
	int evalRPN(vector<string>& tokens)
	{
		stack<Long> st;
		map< string, function<Long(Long, Long)> > m =
		{
			{"+", [](Long a, Long b) { return a + b; }},
			{"-", [](Long a, Long b) { return a - b; }},
			{"*", [](Long a, Long b) { return a * b; }},
			{"/", [](Long a, Long b) { return a / b; }}
		};
		for (auto& e : tokens)
		{
			if (m.count(e))
			{
				Long right = st.top();
				st.pop();
				Long left = st.top();
				st.pop();

				st.push(m[e](left, right));
			}
			else
				st.push(stoll(e));
		}
		return st.top();
	}
};

bind函数模板/包装器/适配器

  1. 接收一个可调用对象(callable object),生成一个新的可调用对象“适应”原对象的参数列表

调整参数顺序

int Sub(int a, int b)
{
    return a - b;
}

int main()
{
    function<int(int, int)> f1 = Sub;
    cout << f1(10, 5) << endl;

    // 调整参数顺序
    function<int(int, int)> f2 = bind(Sub, placeholders::_2, placeholders::_1);
    cout << f2(10, 5) << endl;// 10传给b 5传给a

    return 0;
}

调整参数个数

int main()
{
    function<int(int, int)> f1 = Sub;
    cout << f1(10, 5) << endl;

    // 调整参数个数,有些参数可以使用 bind 时固定
    function<int(int)> f3 = bind(Sub, 20, placeholders::_1);
    cout << f3(5) << endl;//20给a 5给b

    return 0;
}

调整参数个数的应用

class Plus
{
public:
	static int plusi(int a, int b)
	{
		return a + b;
	}

	double plusd(double a, double b)
	{
		return a + b;
	}
};
int main()
{
	// 静态成员函数名会自动退化为指向该函数的指针 
	// 写成&Plus::plusi也对 没必要
	function<int(int, int)> f1 = Plus::plusi;
	cout << f1(1, 2) << endl;

	// 普通成员函数 必须加&
	function<double(Plus*, double, double)> f2 = &Plus::plusd;
	Plus ps;
	cout << f2(&ps, 1.1, 2.2) << endl;

	// 这里传 Plus 编译器会进行特殊处理,实际上是 Plus*
	function<double(Plus, double, double)> f3 = &Plus::plusd;
	cout << f3(Plus(), 1.11, 2.22) << endl;

	// 通过提前bind简化调用的编写并显得统一
	function<double(double, double)> f4 = bind(&Plus::plusd, Plus(), placeholders::_1, placeholders::_2);
	cout << f4(1.11, 2.22) << endl;

	return 0;
}

应用

在这里插入图片描述


http://www.kler.cn/news/330823.html

相关文章:

  • 将模板引擎用于 Express
  • 65 注意力分数_by《李沐:动手学深度学习v2》pytorch版
  • 前端开发技术框架选型
  • 每日一题|1928. 规定时间内到达终点的最小花费|动态规划、最小路径
  • 强弱依赖(含示例)
  • ANTLR4 与 flex/bision、lex/yacc 的比较
  • Electron 进程通信
  • Spring MVC系统学习(二)——Spring MVC的核心类和注解
  • 五子棋双人对战项目(5)——对战模块
  • Ubuntu编译fftw3
  • 端口隔离配置的实验
  • ElasticSearch学习笔记(三)Ubuntu 2204 server elasticsearch集群配置
  • JavaCV 实现视频链接截取封面工具
  • 掌控物体运动艺术:图扑 Easing 函数实践应用
  • 【Linux 从基础到进阶】Cassandra数据库安装与调优
  • SpringBoot与微服务:网上租赁系统的现代化构建
  • CSS——文字闪烁效果
  • 机器学习框架
  • 虚拟机三种网络模式详解
  • Android常用C++特性之std::sort