C++必知必会 C++11实用特性

本文介绍了C++11引入的一些关键特性,如nullptr替代NULL、const和constexpr的用法区别、auto和decltype的类型推导、lambda表达式和函数绑定,以及右值引用和移动语义在提升效率中的作用,最后提到了智能指针在内存安全管理中的重要性。
摘要由CSDN通过智能技术生成


前言

C++11开始添加了很多好用的新特性,个人认为想要真正掌握这些特性还是需要多读代码,多应用这些特性,本文只记录了一些个人用过的,并结合自己的使用体验讲了一下使用场景


nullptr和NULL

C++11中NULL表示的是一个int类型的0,用nullptr来表示空指针以避免野指针,悬空指针等问题,nullptr不指向任何地址,对他输出的结果就是0.nullptr允许隐式转换为其它类型的指针,编译器确保了这一操作的正确执行,比如在下面这段代码中,nullptr被隐式转换为其他类型指针,并正确的执行了重载

void func(int* ptr){
    if(ptr == nullptr){
        cout<<"int指针为空"<<endl;
    }else{
        cout<<"int指针不为空"<<endl;
    }
}

void func(double* ptr){
    if(ptr == nullptr){
        cout<<"double指针为空"<<endl;
    }else{
        cout<<"double指针不为空"<<endl;
    }
}

int main()
{
    int* ptr = nullptr;//nullptr隐式转换为int*
    double* ptr1 = nullptr;//nullptr隐式转换为double*
    func(ptr);
    func(ptr1);
    system("pause");
    return 0;
}
输出:
int指针为空
double指针为空

const和constexpr

1.const
const在C++11之前就有,用处在于变量只读以及修饰常量,但要注意两者是不一样的

  • 变量只读:比如函数中我们不希望传入的参数被修改void func (const int num)
  • 修饰常量:有时提前定义一个常量,并用于描述数组大小const int a = 20; int arr[a];

2.constexpr
首先讲明constexpr和const的区别:在修饰常量方面,二者都可用,但是constexpr达不到修饰变量只读的作用,所以在参数传递的时候只能用const来修饰其只读的效果

1.使用意义:constexpr所声明的表达式或函数,必须在编译期间完成运算,这就意味着其作用主要是用于优化程序,个人认为,如果不是厉害的cpper,用好const就够了。
2.使用场景:

声明变量:
constexpr int max_size = 100; // 编译时常量
const int max_size = 100; // 编译时const
const int max_size = N;//运行时const
声明函数(前提是所有参数也都是编译时常量):
constexpr int add(int x, int y) {
    return x + y;
}

constexpr int val = add(5, 3); // 编译时求值

auto和decltype

1.auto用于编译期间类型推导,主要用于stl的遍历,不能用于形参的推导
2.decltype用于表达式类型的推导,其也是在编译阶段实现的,使用 decltype推导出的是表达式类型的引用,decltype通常与尾返回类型推导结合使用,来获取函数的返回类型,而decltype前置时变量会未定义,如:decltype(x+y) add(T x, U y)是不对的,正确的应该是这样

template<typename T, typename U>  
auto add2(T x, U y) -> decltype(x+y){  
return x + y;
}

在C++11之前,函数的返回值类型必须显示的定义在函数前面,在C++11及以后的标准中可以使用尾返回类型推导来让编译器推导函数的返回类型

lambda表达式

1.使用方法
lambda表达式定义了一个匿名函数,并且可以捕获一定范围内的变量。其最大的好处就是使用时再定义,其定义格式如下:

[capture](params) opt -> ret {body;};
[] - 不捕捉任何变量 
[&] - 捕获外部作用域中所有变量, 并作为引用在函数体内使用 (按引用捕获) 
[=] - 捕获外部作用域中所有变量, 并作为副本在函数体内使用 (按值捕获) 拷贝的副本在匿名函数体内部是只读的

- opt:函数选项,没有可以不写 

一般来说,我们使用lambda表达式只需要关注捕获列表,函数参数,函数体即可,举个例子,sort函数默认进行从小到大的排序,如果我们希望修改排序规则,那么就要在sort的第三个参数位置传入一个函数来修改规则:

vector<int> nums;
sort(nums.begin(), nums.end(), [&] (int a,int b){
	return a>b;
};)

如果需要执行lambda表达式,需要在表达式后面加上()来调用它,也可以通过()来传递参数如:

auto ret = [](int x){
	return 2;
}(10);

2.对按值捕获的一个说明
通常情况下,我们使用按值捕获拿到的数据是只读的,不能对其进行写操作,但是在类和结构体中定义的lambda函数,可以隐式或显示的使用this指针来进行成员的修改

struct A{
    int val_;
    int val1_;
    A(int val):val_(val){}

    int getCaptureValue(){
        return [=](){
            val_ = 2;//隐式调用this指针
            this->val1_ = 10;//显示调用this指针
            return val_;
        }();
    }

    int getReferenceValue(){
        return [&](){
            val_ = 3;
            return val_;
        }();
    }
};

int main()
{
    A a(1);
    //测试按值捕获
    auto ret = [=](){
        //a.val_ = 0;编译器会报错,它不允许对只读变量进行写操作
        return a.val_;
    }();
    cout<<ret<<endl;
    cout<<"按值捕获:"<<a.getCaptureValue()<<endl;
    cout<<a.val_<<endl;

    //测试按引用捕获
    cout<<"按引用捕获"<<a.getReferenceValue()<<endl;
    cout<<a.val_<<endl;
    system("pause");
    return 0;
}

输出:
1
按值捕获:2
2
按引用捕获3
3

function和bind

1.function的目地在于封装各种各样的可调用实体,形成一个新的可调用的function对象,另外function对象是对可调用对象的一种类型安全的包装,相比于类型不安全的函数指针更适合作为回调函数。
个人在使用中,主要是用function包装一个仅在本函数中使用的函数,通过function+lambda表达式来实现,举个例子:在进行图论的算法题中经常会用到dfs算法,我们可以直接在main函数中定义dfs函数,并实现调用

int main()
{
	//这里包装的dfs函数可以在
	function<void(int,int)> dfs = [&](int x, int y){
		//函数逻辑
	};
	...
	dfs(3,5);
}

可调用对象主要有以下几种:

  • 重载了函数调用运算符()的类的对象,即为函数对象(仿函数)
  • 函数
  • 函数指针
  • lambda表达式

2.bind
它主要用于将可调用对象(如函数、成员函数、函数对象)与其参数绑定,创建一个新的可调用对象。例如:

void print(int a, int b) {
    std::cout << a << ", " << b << std::endl;
}

class Foo {
public:
    void display(int x) {
        std::cout << x << std::endl;
    }
};

int main() {
    // 绑定全局函数的参数
    auto boundFunc = std::bind(print, 42, std::placeholders::_1);
    boundFunc(24); // 输出: 42, 24

    Foo foo;
    // 绑定成员函数及其对象
    auto boundMemberFunc = std::bind(&Foo::display, &foo, std::placeholders::_1);
    boundMemberFunc(15); // 输出: 15
}

std::placeholders::_1、std::placeholders::_2…这些表示的是参数占位符,通常表示未进行绑定的那些参数

右值引用

1.右值和左值:左值可以取地址,右值不能取地址。
2.右值引用,用&&表示,它主要用于支持移动语义(Move Semantics)和完美转发(Perfect Forwarding),右值引用允许我们安全地引用临时对象
3.右值引用通常用于延长临时对象的生命周期,使得可以直接使用它,而不是把它拷贝给我们自己定义对象,它提高了程序的效率,比如说有时我们会通过一个临时对象来构造我们的对象,如果这个临时对象本身就很大,它创建出来已经耗费了一部分系统资源,然后还要再拷贝给我们自己的对象,拷贝完再销毁这个临时对象,整个过程的开销是很大的。
4.通常用于移动构造函数和移动赋值操作符重载函数

Example(Example&& other){}
Example& operator=(Example&& other){
	return *this;
}

移动语义move

在C++11添加了右值引用,并且不能使用左值初始化右值引用,如果想要使用左值初始化一个右值引用需要借助std::move()函数,使用std::move方法可以将左值转换为右值。使用这个函数并不能移动任何东西,而和移动构造函数一样都具有移动语义,将对象的状态或者所有权从一个对象转移到另一个对象,只是转移,没有内存拷贝

智能指针

智能指针对于C++的内存安全来说是一个重大的更新,值得我们单独讲一下,看我的这篇文章智能指针详解

  • 63
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值