IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++11新特性 -> 正文阅读

[C++知识库]C++11新特性

long long & unsigned long long

  • long long 超长整型是 C++ 11 标准新添加的
  • 对于有符号 long long 整形,后缀用 “LL” 或者 “ll” 标识。例如:long long a = 10ll;
  • 对于无符号 long long 整形,后缀用 “ULL”、“ull”、“Ull” 或者 “uLL” 标识。例如:unsigned long long b = 10ull;
  • <climits>头文件中的LLONG_MIN、LLONG_MAX 和 ULLONG_MIN 分别代表了当前平台下最小的longlong值、最大的longlong值和最大的unsigned longlong值

初始化列表

  • C++11中加入{}对变量进行统一的初始化
  • 例子:
class test
{
public:
	test(int x){}
}
struct test1
{
	int x;
	int y;
}
test1 func()
{
	return { 1, 2 };
}

int main()
{
	test x(10);
	test y = { 20 };
	test z{ 30 };
	int arr[] = { 1, 2, 3, 4, 5 };
	int* p = new int[6]{ 0, 1, 2, 3, 4, 5 };

}

nullptr类型

  • NULL宏定义来自于C语言,用于给予指针变量初始值,但在C++中,NULL带来了二义性,因为C++允许函数重载,例如:
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif

void func(int a){}
void func(int* a){}

这时候我们调用func(NULL),编译器会认为我们要调用的是int类型的func,如果我们要调的是指针类型的怎么办,这个时候加入的nullptr作用就来了

  • nullptr可以明确的区分指针类型,虽然它也有二义性,但它类型不会错,只需指定一种指针类型即可,例如:
void func(int a){}
void func(int* a){}
void func(double* a){}

func(nullptr)//此代码会引起二义性
func((int*)nullptr)//这样就是对的

auto智能推导类型

  • auto 关键字会在编译期间自动推导出变量的类型,使得某些较长的类型无需再手动定义,编写代码时会相当的方便
  • 定义:auto a = value; a为变量名,value为变量初始值
  • 简意:auto关键字仅为占位符,在编译期间会被替换为真正的类型,如同define一样
  • 简单例子:
    auto a = 10;
    auto b = 10.5;
    auto c = &b;
    auto d = "www.csdn.com";
  • 解释:
    a的类型被推导为int类型,10为整型
    b的类型被推导为double类型,10.5为小数,auto默认为double,使用类型为float,需改为10.5f
    c的类型被推导为double类型,&b为变量b的地址
    d的类型被推导为const char
    类型,"www.csdn.com"为字符串
  • 注意:
    auto关键字在类型推导时不可产生二义性,例如:auto a = 10, b = 10.5;
    在推导引用类型时需加入&符号,例如:auto& a = b;
    auto在定义时必须初始化,不能作为形参在函数中使用,不能作为模板参数使用
    不能用于定义数组,但可接收数组变量退化为指针,例如:char arr[] = "www.csdn.com"; auto arr1 = arr;,此时的arr1就会被推导为char*指针

decltype关键字

  • decltype是 C++11 新增的一个关键字,它和 auto 的功能一样,都用来在编译时期进行自动类型推导,那为什么有了auto还要引入decltype关键字呢,因为auto并不能适用于所有场景,所以引入了decltype
  • 定义:decltype(type) a = value; a为变量名,value为变量初始值,type可以是变量,也可以是表达式
  • 简意:type就是一个表达式,必须产出一个结果并且不能为void类型
  • 差异:和auto的差异是auto必须在定义时初始化,而decltype不需要,因为auto是靠右值推导类型,而decltype是靠type
  • 定义:
    int a = 1;
    decltype(a) b;//此时的b是int类型,在这里的b没有初始化
    decltype(10.5) c = 20.8;//此时的c是double类型
    decltype(c + 100) d = 30.55;//此时的d是double类型
  • 注意:
    decltype定义引用类型时需加双层括号,例如:decltype( (d) ) e = d;此时加了双层括号,推导的类型肯定是引用类型,而单层括号时需要推导的变量为引用类型,定义的变量才会是引用

for循环

  • C++11中加入了基于范围的for循环
  • 定义:
for(type : list)
{
}
  • type是定义的变量,可以使用auto类型推导定义,list就是范围的列表或者容器,例如:
int arr[] = { 1, 2, 3, 4, 5 };
vector<type> v;
for(auto& i : v)
{
}
  • 除此之外,新语法格式的 for 循环还支持遍历用{ }大括号初始化的列表,例如:
for(int& i : { 1, 2, 3, 4, 5 })
{
	std::cout << i;
}

using定义别名

  • using的加入是为了替代typedef,因为typedef在某些情况会有局限性,而using拥有typedef特性的同时还扩展了一些它做不到的事,例如:
//using
template <typename t>
using str_map = std::map<string, t>;
str_map<int> a;
//typedef
template <typename t>
struct strmap
{
	std::map<string, t> str_map;
}
strmap<int>::str_map b;

缺省模版参数

  • 在C++11中,模板可以指定某个类型为默认类型,例如:
template <typename R = int, typename val>
R func(val a)
{
	return a;
}
func(10); //R = int val = int
func<char>(10);//R = char val = int
func<double, int>(10); // R = double val = int
  • 在上述代码中,出现了一个类型都没有指定的调用,是因为编译器自动推导了第二个参数的类型,这意味着,当默认模板参数和编译器自行推导出模板参数类型的能力一起结合使用时,代码的书写将变得异常灵活。

override与final

  • override的作用是检查某函数是否重写了基类的函数,如果没有编译报错
  • final的作用是表示该虚函数不能再继续继承下去

右值引用&std::move

  • 什么是左值什么是右值?
    int a = 10; a就是左值,10就是右值
    有名称的、可以获取到存储地址的表达式即为左值;反之则是右值。
    而我们再创建个变量 int& b = a;这里的b就是左值引用
  • 而右值引用在C++11中也进行了实现:&&
    int&& a = 10;这就是右值引用,可以通过a修改10在内存里的数字
  • std::move 唯一的功能就是将一个左值强制转化为右值引用,通过右值引用使用该值,实现移动语义。
  • 例子:
class string
{
......
public:
		string(string&& b) : _str(b._str)
		{
				b._str == nullptr;
		}
......
}

string s1("test");
string s2(std::move(s1));
string s3(s2);
  • s2调用移动构造函数创建,s3调用拷贝构造函数创建

lambda表达式

  • 定义:[捕获列表](参数)->返回值{ 函数体 };
  • 简意:lambda表达式实际上是一个匿名类函数,在编译时会将表达式转换为匿名类函数
  • 例子:
void print(int b) == [](int b) {}
int add(int a, int b) == [](int a, int b){ return a + b; }//返回值可以省略,由编译器推导
  • 捕获列表:
int a = 10;
//等号相当于值传递,函数内部所有操作不影响外部变量,捕获所有lambda表达式使用到的所有变量
auto fn = [=]{ a = 20; }
//&相当于引用传递,函数内部所有操作会改变外部变量的值,捕获所有lambda表达式使用到的所有变量
auto fn = [&]{ a = 20; }
//可以单个捕获变量,引用或者值拷贝,也可以&,&a这样的混合使用
auto fn = [&a]{ a = 20; }
  • 使用lambda函数绑定类成员函数
class test
{
public:
	test(){}
	int add(int& a, int& b) { return a + b; }
}
test t;
auto fn_add = std::bind(&test::add, &t, std::placeholders::_1, std::placeholders::_2);
auto ret = fn_add(10, 20);
  • 详解:
int a = 10;
//lambda
auto fn = [&a](int b){ a = a * b; return a; }
//展开后
class lam
{
public:
       lam(int& a) : _a(a)
       {
       }
public:
       auto operator()(const int a)
       {
               _a = _a * a;
               return _a;
       }
private:
       int& _a;
};
lam l(a);
l(10) == fn(10)//这两个是相等的
  • lambda表达式的出现也解决了很多地方的函数指针调用问题,例如:
int arr[] = { 2, 5, 3 ,4 ,9 ,0 ,7 ,8, 1, 6 };
//不用lambda
bool compare(int& a, int& b){ return a > b; }
std::sort(arr, arr + 10, compare);
//用lambda
std::sort(arr, arr + 10, [](int& a, int& b){ return a > b; });
  • 注意:
    由于这是个匿名类,在使用lambda表达式时不可在函数体中申请内存,否则会造成意想不到的错误。

thread

  • C++11中最重要的特性就是对线程进行支持了,使得C++在并行编程时不需要依赖第三方库,而且在原子操作中还引入了原子类的概念。要使用标准库中的线程,必须包含< thread >头文件,该头文件声明了 std::thread 线程类
  • 简单定义:
//函数式
int add(int a, int b){ return a + b; }
std::thread td(add, 10, 20);
td.join();
//lambda表达式
std::thread td([&](int a, int b){return a + b;}, 10, 20);
td.join();
  • 线程结束后我们应该怎么去回收线程资源呢?thread线程库给了我们2种选择
  1. join:当新的线程终止时,join()会清理相关的资源,然后返回,调用线程再继续向下执行。也就是阻塞式线程
  2. detach:会从调用线程中分离出新的线程,之后不能再与新线程交互。非阻塞式线程
  • 原子类型:atomic_xx,类似于锁,多线程访问时不会同时访问一个数据,仅适用于基础类型和基础类型的扩展类型
  • 还有其他标准库多线程锁(condition_variable),详情请看C++25设计模式之线程池模式

智能指针

auto_ptr

  • 在c++11之后,它被弃用了,原因就是因为当把一个auto_ptr对象赋值给另一个变量时, 原有的对象的指针就为空了. 我们很容易实现它:
template <typename T>
class AutoPtr
{
private:
        T* _ptr;
public:
        AutoPtr(T* p = nullptr) : _ptr(p)
        {
        }
        ~AutoPtr()
        {
                delete _ptr;
                _ptr = nullptr;
        }
        //一旦发生拷贝,立即将其它对象的指针置空
        //防止多个对象引用同一个指针地址造成程序崩溃
        AutoPtr(AutoPtr<T>& other) : _ptr(other._ptr)
        {
                other._ptr = nullptr;
        }

        AutoPtr<T>& operator=(AutoPtr<T>& other)
        {
                if (this != &other)
                {
                        if(_ptr)
                        {
                                delete _ptr;
                        }
                        _ptr = other._ptr;
                        other._ptr = nullptr;
                }
                return *this;
        }
        T& operator*() { return *_ptr; }
        T* operator->() { return _ptr; }
        void reset(T* p) { if(_ptr){ delete _ptr; } _ptr = p; }
};

unique_ptr

  • unique_ptr与auto_ptr基本相似,有一点不一样: unique_ptr很聪明,它不允许拷贝构造也不允许赋值操作,只允许移动构造和移动赋值操作. 所以实现它也很容易:
template <typename T>
class UniquePtr
{
private:
        T* _ptr;
public:
        UniquePtr(T* p = nullptr) : _ptr(p)
        {
        }
        ~UniquePtr()
        {
                delete _ptr;
                _ptr = nullptr;
        }
        //使用C++11新特性的关键字,表示不需要这两个拷贝,如编写了拷贝由编译器报错
        UniquePtr(UniquePtr<T>& other) = delete;
        UniquePtr<T>& operator=(UniquePtr<T>& other) = delete;
        
        T& operator*() { return *_ptr; }
        T* operator->() { return _ptr; }
        void reset(T* p) { if(_ptr){ delete _ptr; } _ptr = p; }
};

shared_ptr

  • 它允许多个shared_ptr对象共享一块内存, 与前两者有所不同。本质是就是维护一个内存块的引用计数,引用计数什么时候变为0了,什么时候释放内存。简单实现:
template <typename T>
class SharedPtr
{
private:
        size_t* _count;
        T* _ptr;
public:
        SharedPtr(T* p = nullptr) : _ptr(p)
        {
                if(_ptr)
                {
                        _count = new size_t{1};
                }
                else 
                {
                        _count = new size_t{0};
                }
        }
        ~SharedPtr()
        {
                (*_count)--;
                releasePtr();
        }
        SharedPtr(const SharedPtr& other)
        {
                if (this != &other)
                {
                        *(_count)--;
                        releasePtr();
                        _ptr = other._ptr;
                        _count = other._count;
                        (*_count)++;
                }
        }
        SharedPtr<T>& operator=(const SharedPtr& other)
        {
                if(_ptr != other._ptr)
                {
                        if (_ptr)
                        {
                                (*_count)--;
                        }
                        releasePtr();
                        _ptr = other._ptr;
                        _count = other._count;
                        (*_count)++;
                }
                return *this;
        }
        T& operator*() { return *_ptr; }
        T* operator->() { return _ptr; }
        auto reset(T* p)
        {
                if (_ptr != p)
                {
                        (*_count)--;
                        releasePtr();
                }
        }

        size_t use_count()
        {
                return *_count;
        }

        T* get()
        {
                return _ptr;
        }
private:
        void releasePtr()
        {
                if (*_count == 0)
                {
                        delete _count;
                        delete _ptr;
                }
                _count = nullptr;
                _ptr = nullptr;
        }
};
  • 使用shared_ptr时需注意避免循环引用,例如:
#include <memory>//所有的智能指针都在这个头文件里
using namespace std;

struct ListNode
{
		int _data;
		shared_ptr<ListNode> _prev;
		shared_ptr<ListNode> _next;

		~ListNode() { cout << "~ListNode()" << endl; }
};

int main()
{
		shared_ptr<ListNode> node1(new ListNode);
		shared_ptr<ListNode> node2(new ListNode);
		cout << node1.use_count() << endl;	// 1
		cout << node2.use_count() << endl;	// 1

		node1->_next = node2;
		node2->_prev = node1;

		cout << node1.use_count() << endl;	// 2
		cout << node2.use_count() << endl;	// 2

		return 0;
}
  • 循环引用分析:
  1. node1和node2两个智能指针对象指向两个节点,引用计数变成1,我们不需要手动delete。

  2. node1的_next指向node2,node2的_prev指向node1,引用计数变成2。

  3. node1和node2析构,引用计数减到1,但是_next还指向下一个节点。但是_prev还指向上一个节点。

  4. 也就是说_next析构了,node2就释放了。

  5. 也就是说_prev析构了,node1就释放了。

  6. 但是_next属于node的成员,node1释放了,_next才会析构,而node1由_prev管理,_prev属于node2成员,所以这就叫循环引用,谁也不会释放。

那么有没有解决方案呢? 答案是有的

  • weak_ptr指针是为了shared_ptr存在的,它的作用就是不增加引用计数,还是上面的例子:
#include <memory>//所有的智能指针都在这个头文件里
using namespace std;

struct ListNode
{
		int _data;
		weak_ptr<ListNode> _prev;
		weak_ptr<ListNode> _next;

		~ListNode() { cout << "~ListNode()" << endl; }
};

int main()
{
		shared_ptr<ListNode> node1(new ListNode);
		shared_ptr<ListNode> node2(new ListNode);
		cout << node1.use_count() << endl;	// 1
		cout << node2.use_count() << endl;	// 1

		node1->_next = node2;
		node2->_prev = node1;

		cout << node1.use_count() << endl;	// 2
		cout << node2.use_count() << endl;	// 2

		return 0;
}
  • 解决了引用计数问题,那么我是malloc的内存空间,shared_ptr怎么释放呢? 答案是删除器概念
#include <memory>
using namespace std;

// 仿函数的删除器 
template<class T> 
struct FreeFunc 
{
    	void operator()(T* ptr)
    	{
        		cout << "free:" << ptr << endl;
        		free(ptr);
    	}
};
template<class T>
struct DeleteArrayFunc 
{
    	void operator()(T* ptr)
    	{
        		cout << "delete[]" << ptr << endl;
        		delete[] ptr;
    	}
};

int main() 
{
    	FreeFunc<int> freeFunc;
    	shared_ptr<int> sp1((int*)malloc(4), freeFunc);
    
    	DeleteArrayFunc<int> deleteArrayFunc;
    	shared_ptr<int> sp2(new int[4], deleteArrayFunc);

    	return 0;
}

总结:智能指针的存在就是为了更方便、更安全的使用指针

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-05-08 07:51:17  更:2022-05-08 07:51:38 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/21 3:30:43-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码