C++ 11 常用标准浅析 (自用)

1.decultype

        语法:decultype (表达式) 变量名;

        一种变量定义方式, 将 这个变量的类型定义为表达式的返回值类型

#include <iostream>
using namespace std;

int main() {
	decltype (1 * 4) pp = 100;
	cout << pp << endl;
}

2.using 模板别名

#include<iostream>
#include<vector>
using namespace std;
template<typename T>
using arr = vector<T>;
int main(){
    arr<int> arr1;
    arr1.push_back(100);
    cout<<arr1[0]<<endl;
}

3.智能指针

 1.auto_ptr (C++98标准)

当前已经不建议使用, auto_ptr在C++17 标准中被废弃, 且auto_ptr可以完全被unique_ptr取代

 2.unique_ptr

特性:

1.基于排他所有权模式: 两个指针不能指向同一个资源

2.无法进行左值unique_ptr拷贝构造, 也无法进行左值赋值(operator=)操作, 但允许临时右值赋值构造和赋值

3.保存指向某个指针的对象, 当它本身离开作用域时会自动释放它指向的对象

4.在容器中保存指针是安全的

构造函数

unique_ptr<T, D = std::default_delete<T>> up(new T());

unique_ptr<T[], D = std::default_delete<T[]>> up(new T[N]); //可以使用初始化列表构造

T为管理的参数类型

D为删除器, 使用仿函数实现, 默认情况下删除方式是调用delete

//示例代码
#include<memory>
#include<iostream>
using namespace std;

int main() {
	unique_ptr<int[]> up(new int [5] {1, 2, 3, 4, 5});
	cout << up[0] << endl;
}

接口

ptr1 = std::make_unique<T>();

ptr1 = std::move(ptr2);

通过 std::move 转移 unique_ptr 的所有权。 //特性 2

operator->

operator*

_Ty* ptr.get()

_Ty* ptr.release()

ptr.reset(_Ty * ptr == NULL)

ptr.swap(other_ptr) //亦可使用 全局函数版本

ptr.get_deleter()

获取 unique_ptr 使用的删除器。

std::make_unique<T>(args...)

创建 unique_ptr 实例并构造对象

3.shared_ptr

特性:

 1.可以实现指针共享.

 2.在所有的指向同一个内存的shared_ptr类销毁后后释放所管理的原始指针空间 (在C++源码中可以看出是通过利用静态成员变量实现的)

接口

构造函数

std::shared_ptr();

explicit std::shared_ptr(T* ptr);

std::shared_ptr(T* ptr, D deleter);

std::shared_ptr(const std::shared_ptr& other);  //不释放 原来的智能指针

std::shared_ptr(std::shared_ptr&& other) noexcept; //移动构造 释放 原来的智能指针

赋值操作符

std::shared_ptr& operator=(const std::shared_ptr& other);

移动赋值操作符:

std::shared_ptr& operator=(std::shared_ptr&& other) noexcept;

使当前 shared_ptr 指向 other 的对象,并将 other 置为空。之前管理的对象会被自动释放(如果不再有其他 shared_ptr 指向它)。

成员函数

T* get() const noexcept;

T& operator*() const;

T* operator->() const;

void reset() noexcept;

void reset(T* ptr);

void reset(T* ptr, D deleter);

释放当前所管理的对象,并将 shared_ptr 重新指向 ptr,使用新的删除器 deleter。

void swap(std::shared_ptr& other) noexcept; (也可使用全局版本的swap)

交换当前 shared_ptr 和 other 的管理对象。

long use_count() const noexcept;

返回当前 shared_ptr 的引用计数。

bool unique() const noexcept;

判断当前 shared_ptr 是否唯一,即是否是唯一管理对象的 shared_ptr。

explicit operator bool() const noexcept;

检查 shared_ptr 是否管理非空。

make_shared全局函数:

  shared_ptr<类型> make_shared<类型>(构造类型对象所需要的参数列表);

  比起直接使用构造函数, 更推荐使用shared_ptr, 因为他的内存分配效率更高. (实际上, 当你调用构  造函数时 分配了两次内存, 调用make_shared时候只分配了一次内存)

注意:

1. shared_ptr<T> / shared_ptr<T[]> 的模板只允许传入两个参数类型, 自定义删除器时直接传入两个参数即可, 不需要传入参数类型, 注意与unique_ptr删除器的区分. 删除器的实现通过仿函数完成

2.shared_ptr<T[]> 支持数组是在C++17 后的标准才允许使用

3.尽量避免shared_ptr交叉使用/循环引用, 可能导致严重的内存泄漏. (有关交叉引用 / 循环引用的问题将在弱指针中的到解决)

4.weak_ptr

用途:

用于解决shared_ptr 循环引用问题

什么是循环引用?

//示例代码
#include<memory>
#include<iostream>
using namespace std;
class B;
class A {
public:
	A(){
		cout << "A construct" << endl;
	}
	~A() {
		cout << "A destruct" << endl;
	}
	shared_ptr<B> A_ptr;
};
class B {
public:
	B() {
		cout << "B construct" << endl;
	}
	~B() {
		cout << "B destruct" << endl;
	}
	shared_ptr<A> B_ptr;
};

int main() {
	shared_ptr<A> sA = make_shared<A>();
	shared_ptr<B> sB = make_shared<B>();
	sA->A_ptr = sB;
	sB->B_ptr = sA;
	return 0;
}

输出:
 

A construct
B construct

可见, sA 和 sB 所指向的空间并没有被释放, 这是因为sA 中有一个B 指针, 同样sB 中有一个A 指针, 因此, 总共有两个智能指针指向同一片区域, 但是随着主函数的结束, 只有一个智能指针被释放,  shared_ptr 类中的计数器认为该指针指向的区域并没有失效, 所以没有释放内存

特性

它只可以从一个share_ptr或另一个weak_ptr对象构造, 它的构造和析构不会引起引用计数的增加或减少, 同时weak_ptr 没有重载* 和 ->

但可以使用lock 获得一个可用的 shared_ptr 对象

接口:

构造函数

std::weak_ptr();

std::weak_ptr(const std::shared_ptr<T>& r);

std::weak_ptr(const std::weak_ptr<T>& r);

 赋值

std::weak_ptr& operator=(const std::shared_ptr<T>& r);

std::weak_ptr& operator=(const std::weak_ptr<T>& r);

成员函数

bool expired() const noexcept;

检查 shared_ptr 是否已经被销毁。如果对象已经被删除,则返回 true,否则返回 false。

std::shared_ptr<T> lock() const noexcept;

尝试获取一个 shared_ptr 对象。如果原始 shared_ptr 对象还存在,则返回一个 shared_ptr;如果对象已经被删除,则返回一个空的 shared_ptr。

std::size_t use_count() const noexcept;

返回 shared_ptr 对象的引用计数,但不保证对象是否仍然存在,因为 weak_ptr 不增加引用计数。

operator== 和 operator!=:

bool operator==(const std::weak_ptr<T>& r) const noexcept;

bool operator!=(const std::weak_ptr<T>& r) const noexcept;

bool operator<(const std::weak_ptr<T>& r) const;

bool operator>(const std::weak_ptr<T>& r) const;

比较两个 weak_ptr 对象的顺序(需要比较器支持)。

//shared_ptr / weak_ptr
#include<memory>
#include<iostream>
using namespace std;

class boy;

class girl {
public:
	girl() {
		cout << "girl construct" << endl;
	}
	~girl() {
		cout << "girl destruct" << endl;
	}
	
	void set_boy_friend(const shared_ptr<boy>& b) {
		this->b = b;
	}

	weak_ptr<boy> b;
};

class boy {
public:
	boy(){
		cout << "boy construct" << endl;
	}
	~boy() {
		cout << "boy destruct" << endl;
	}

	void set_girl_friend(const shared_ptr<girl>& g) {
		this->g = g;
	}

	weak_ptr<girl> g;
};

void use_trap(){
	shared_ptr<girl> sp_girl(new girl());
	shared_ptr<boy> sp_boy(new boy());
	//弱指针的使用
	weak_ptr<girl> wp_girl;
	weak_ptr<girl> wp_girl2(sp_girl);
	wp_girl = sp_girl;
	cout << "sp_girl. count:  " << sp_girl.use_count() << endl;
	cout << "wp_girl. count:  " << wp_girl.use_count() << endl;

	//在必要时可以转成共享指针:
	shared_ptr<girl> sp_girl1;
	sp_girl1 = wp_girl.lock();

	cout << "sp_girl. count:  " << sp_girl.use_count() << endl;
	cout << "wp_girl. count:  " << wp_girl.use_count() << endl;
	sp_girl1.reset();

	cout << "sp_girl. count:  " << sp_girl.use_count() << endl;
	cout << "wp_girl. count:  " << wp_girl.use_count() << endl;

	sp_girl->set_boy_friend(sp_boy);
	sp_boy->set_girl_friend(sp_girl);
}

int main() {
	use_trap();
}

4.异常处理声明的补充

C++11提出了noexcept关键字, 取代原先的throw()写法

5.枚举类

C++11 引入了枚举类(enum class),作为传统枚举(enum)的增强版本,提供了更强的类型安全和作用域控制。

1. 定义和语法

枚举类的定义使用 enum class 关键字,语法如下:

enum class Color { Red, Green, Blue };

2. 作用域控制

枚举类提供了更严格的作用域控制,枚举成员不会与其他枚举或全局变量发生冲突:

Color c = Color::Red; // 正确

// Color c = Red; // 错误,必须使用 Color::Red

3. 类型安全

枚举类提供了类型安全,即枚举类型不隐式转换为整数类型或其他枚举类型:

Color c = Color::Red; int i = static_cast<int>(c); // 需要显式转换

与传统枚举相比,枚举类避免了不安全的类型转换:

enum OldColor { Red, Green, Blue }; OldColor oc = Red; int j = oc; // 允许隐式转换

4. 底层类型

可以指定枚举类的底层类型(例如 intchar 等):

enum class Color : unsigned char

{

    Red,

    Green,

    Blue

};

这可以帮助节省存储空间或者与外部接口兼容。

5. 与传统枚举的兼容性

枚举类与传统枚举的操作方式不同,枚举类需要显式转换为底层类型以进行比较:

Color c1 = Color::Red;

Color c2 = Color::Blue; if (c1 == c2) {

// 不能直接比较

}

else { }

// 需要显式转换 if (static_cast<int>(c1) == static_cast<int>(c2)) { // 正确

6.移动语义与右值引用

右值引用的参数通常不应该定义为const, 右值引用的主要目的是实现移动语义,它允许资源从一个对象“移动”到另一个对象,而不进行深拷贝。为了实现这一点,右值引用的参数需要能够被修改,以便将资源的所有权转移给新的对象。

语法:

1.右值引用

 && 用于引用一个右值, 将他的返回值存在一个临时对象中

2.移动语义 与 移动构造函数

C++的移动语义(Move Semantics)是C++11引入的一种重要特性,它使得资源(如动态内存、文件句柄等)能够从一个对象转移到另一个对象,从而提高程序的性能,尤其是在涉及大量数据操作或临时对象的场景中。移动语义主要通过移动构造函数(Move Constructor)和移动赋值操作符(Move Assignment Operator)来实现。

1. 移动构造函数(Move Constructor)

移动构造函数允许从一个即将被销毁的对象(通常是临时对象)中“窃取”资源,而不是复制资源。这意味着可以避免不必要的复制和内存分配,从而提高效率。

语法

ClassName(ClassName&& other);

这里的 && 表示该参数是一个右值引用。右值引用是对临时对象的引用,只能绑定到即将销毁或不需要的对象。

2. 移动语义的优势

  • 性能提升:避免了不必要的复制和内存分配,尤其是对于包含大量数据的对象。
  • 资源安全:通过确保资源只有一个所有者(避免了双重删除的问题)。

3. 使用场景

  • 当函数返回大型对象时。
  • 在容器类中,如std::vector,在动态调整大小或进行元素重排时。

4. 注意事项

  • 移动构造的对象之后,源对象通常处于不确定的但有效的状态,因此设计API时需要明确这一点。
  • 不是所有对象都需要或可以实现移动语义。对于一些简单的或只包含栈分配数据的类,移动语义可能没有太大的效益.

7.lambda表达式 

定义 Lambda 表达式

Lambda 表达式的基本语法如下:

[捕获列表](参数列表) -> 返回类型 { 函数体; };

如果省略了返回类型,编译器会尝试自动推断非复杂的返回类型。对于包含多条语句的 lambda 或其返回类型不明显的情况,你需要显式指定返回类型。

调用 Lambda 并获取返回值

你可以在定义 lambda 的同时调用它,或者将其存储在一个变量中后调用。

立即调用

int result = [](int x) { return x * x; }(5);

在这个例子中,lambda 表达式定义了一个接受一个 int 参数并返回其平方的函数,立即以 5 作为参数调用,返回值 25 被存储在变量 result 中。

存储后调用

auto square = [](int x) -> int { return x * x; }; int result = square(5);

在这里,square 是一个 lambda 表达式,它被存储在变量 square 中。之后,它被调用并传递参数 5,返回值 25 被存储在变量 result 中。

捕获列表:
 

捕获列表的类型

  • [ ]: 不捕获任何外部变量。
  • [=]: 以值的方式捕获所有外部作用域中的变量(只读方式,除非变量是 mutable)。
  • [&]: 以引用方式捕获所有外部作用域中的变量。
  • [x, &y]: 捕获外部变量 x 以值的方式,捕获 y 以引用方式。
  • [=, &x, &y]: 以值的方式捕获所有外部变量,但 x 和 y 例外,它们以引用方式被捕获。
  • [&, x]: 以引用方式捕获所有外部变量,但 x 例外,它以值的方式被捕获。
  • [this]: 捕获当前类的 this 指针,使得 lambda 可以访问当前对象的所有成员(包括私有成员)。

详细说明

捕获值([=])

当使用 [=] 捕获时,所有在 lambda 外部作用域中的变量都将被复制到 lambda 中。这意味着在 lambda 内部,你无法修改捕获的变量的原始值。如果需要修改,可以将 lambda 声明为 mutable

int a = 10; auto lambda = [=]() mutable { a = 20; // 此处修改的是复制的局部副本,不影响原始变量 a }; lambda();

捕获引用([&])

使用 [&] 时,所有外部变量都通过引用捕获,lambda 函数体内的任何修改都会反映到原始变量上。

int b = 10;

auto lambda = [&]() {

b = 20; // 直接修改原始变量 b 的值

};

lambda();

混合捕获

混合捕获允许你指定某些变量以值捕获,某些以引用捕获。这在你需要对某些外部变量保持不变,而又需要修改其他一些变量的场景中非常有用。

int c = 10, d = 20;

auto lambda = [c, &d]() {

// c = 30; // 错误:c 是以值捕获的,不能修改(除非 lambda 是 mutable)

d = 30; // 正确:d 是以引用捕获的,可以修改

};

lambda();

捕获 this 指针

在类成员函数中使用 lambda 时,有时需要访问类的其他成员。这可以通过捕获 this 指针来实现。

class MyClass { public: int value = 10; void func() { auto lambda = [this]() { value = 20; // 通过 this 指针访问和修改成员变量 }; lambda(); } };

结论

Lambda 表达式的捕获列表提供了灵活的方式来控制如何和在什么范围内捕获外部变量。选择正确的捕获方式对于确保代码的正确性和效率至关重要。正确使用捕获列表可以帮助避免意外的副作用,特别是在多线程环境中操作共享数据时。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值