【从零开始学习C++ | 第二十二篇】C++新增特性(下)

目录

前言:

类型推导:

constexpr关键字:

初始化列表:

基于范围的for循环:

智能指针之unique ptr

Lambda表达式:

总结:


前言:

        本文我们将继续介绍   C++ 11 新增十大特性的剩余六个,如果没有看过介绍前四个特性的小伙伴的可以点进我C++的专栏就可以看到。

类型推导:

类型推导(type inference)是 C++11 引入的一个重要特性,表示编译器可以根据上下文推断变量或表达式的类型。也就是说,在某些情况下,可以省略类型声明,直接使用关键字 auto 来定义变量或表达式,而编译器会自动推导该变量或表达式的类型。

使用类型推导可以减少代码中的重复性,并且可以使代码更加简洁和易读。使用类型推导时应该注意以下几点:

1. auto 关键字只能用于定义局部变量,不能用于定义类成员变量或全局变量。

2. 使用 auto 定义变量时,默认情况下变量是 const 类型,如果要定义非 const 类型变量,可以使用关键字 const_cast 来进行强制类型转换。

3. 由于编译器需要通过上下文推断变量类型,因此 auto 变量的初始化表达式必须完全明确,不能存在二义性。如果初始化表达式中有重载函数,编译器会根据函数的返回值类型来进行类型推导。

4. 在定义 auto 变量时,可以使用 decltype 关键字来指定变量的类型。

以下是一些使用类型推导的示例:

auto i = 10; // 推导出 i 的类型是 int
auto p = new int[10]; // 推导出 p 的类型是 int*
auto s = "hello"; // 推导出 s 的类型是 const char*
auto fun = [](int x) { return x + 1; }; // 推导出 fun 的类型是 lambda 表达式类型

总之,在某些情况下,使用类型推导可以使代码更加简洁明了,并且在降低代码重复性的同时还能提高代码可读性。注意需要谨慎使用类型推导,特别是在代码复杂或可读性要求较高的场合。

constexpr关键字:

constexpr 是一种 C++11 新增的关键字,用于使编译器在编译时计算常量表达式,从而提高代码效率。

在函数或变量声明时,constexpr 关键字可以用于声明一个常量表达式,例如:

constexpr int MAX_LEVEL = 100; // 声明一个常量表达式

constexpr int factorial(int n) {  // 声明一个返回值为常量表达式的函数
    return (n <= 1) ? 1 : n * factorial(n - 1);
}

使用 constexpr  关键字声明时,编译器会在编译时对表达式进行求值,而不是在程序运行时对其进行计算。这样可以在编译时就确定常量值,从而在运行时提高代码效率。

在 C++11 中,使用  constexpr  关键字定义常量表达式时,必须满足以下条件:

1. 表达式必须是一个纯量表达式。

2. 表达式的结果必须在编译时可知。

3. 表达式的求值必须产生一个值而不是一个副作用。

4. 函数返回值为 constexpr 类型的函数必须是由成文定义而非 inline 定义的。

使用  constexpr  关键字定义常量表达式的好处包括:

1. 编译时求解:由于在编译时计算常量表达式,可以避免在程序运行时进行计算,从而提高程序的运行效率。

2. 类型安全:使用 `constexpr` 关键字定义的常量表达式具有固定的类型,可以帮助程序员避免类型转换错误和运行时错误。

3. 代码简洁:使用 `constexpr` 关键字定义常量表达式可以简化代码,从而使程序更加易读易懂。

综上所述,`constexpr` 是非常有用的 C++11 特性,可以帮助程序员编写更高效、更安全的代码,在实际编程中应该加以充分利用。

这是一个很实用的关键字,用好了可以直接让性能起飞。

但其实他还有一个很实用的应用:实现动态数组。

因为他是在编译阶段就完成计算了,因此可以将他作为数组的大小参数而不会报错。这样我们就实现了动态数组。

constexpr  int pow (int x,int y)
{
    int result=x;
    while(--y>0)
    {
        result *=x;
    }
    return result;

}
int main()
{
    int a[pow(2,4)]    
    //我们在这里不就实现了动态数组。

}

初始化列表:

C++11 引入了初始化列表(initializer list)的概念,可以用来初始化一些标准容器、数组和自定义类型等对象。

初始化列表使用花括号  {}  来表示,可以在花括号中列出需要初始化的元素。以下是一些初始化列表的示例:

std::vector<int> v {1, 2, 3};  // 初始化一个包含三个元素的 vector 对象

int numbers[] {1, 2, 3}; // 初始化一个包含三个元素的整数数组

struct Point {
    int x;
    int y;
};

Point p {3, 4};  // 初始化一个自定义类型对象

初始化列表的好处主要有以下几点:

1. 初始化列表可以通过列表方式快速创建并初始化一个标准容器、数组或自定义类型的对象,简化了代码。

2. 初始化列表可以派生两个对象的初始化,例如可以使用首项列表来初始化一个 std::pair 对象,这样可以非常方便地初始化复合对象。

3. 初始化列表可以保证元素的顺序准确无误,而且可以确保元素的数量正确,避免了遗漏或重复,从而提高了程序的安全性。

需要注意的是,初始化列表不仅可以用于创建新对象,还可以用于赋值操作。例如,可以使用初始化列表来替代传统的赋值操作:

std::vector<int> v;
v = {1, 2, 3}; // 使用初始化列表来替代传统的赋值操作

总之,初始化列表是一个非常有用的 C++11 特性,可以简化代码的编写,避免了繁琐的初始化操作,并且提供了一种简单、可靠的方式来初始化容器、数组和自定义类型等对象。在实际编程中应该充分利用该特性。

基于范围的for循环:

C++11 引入了基于范围的 for 循环(range-based for loop)的特性,其语法形式为:

for (element : range) {
    // 循环体内容
}

其中,range 是一个 range 类别对象,element 表示从 range 中遍历出来的每个元素值。在循环体中可以直接使用 element 变量完成相应的操作。

使用基于范围的 for 循环的好处主要有以下几点:

1. 简化了代码:基于范围的 for 循环可以直接遍历数组和标准容器,避免了循环计数器的管理和手动访问容器或数组的元素,从而简化了代码。

2. 可靠性更高:基于范围的 for 循环可以避免越界访问和逻辑错误,从而提高了代码的可读性和可靠性。

3. 支持自定义类型:可以自定义一个类,从而实现让该类具有 range 类别的特性,然后可以在基于范围的 for 循环中使用该类的实例。

以下是一些基于范围的 for 循环的示例:

std::vector<int> v {1, 2, 3, 4, 5};
for (int i : v) {
    std::cout << i << " ";
}

int a[] {1, 2, 3, 4, 5};
for (int i : a) {
    std::cout << i << " ";
}

std::string str = "Hello, world!";
for (char c : str) {
    std::cout << c << " ";
}

总之,基于范围的 for 循环是 C++11 的一项非常实用的特性,可以大大简化代码,提高代码的可读性和可靠性。在实际编程中应该充分掌握并应用该特性。

智能指针之unique ptr

需要头文件  #include <memory>

智能指针是  C++11  引入的一个非常有用的特性,可以管理动态内存分配,避免内存泄漏和空指针的问题。其中,unique_ptr 是一种独占所有权的智能指针,用于管理动态分配的对象,具有以下特点:

1. unique_ptr 持有对象的所有权,即只有一个 unique_ptr 可以管理一个对象。

2. unique_ptr 通过 RAII(Resource Acquisition Is Initialization)机制来管理动态分配的对象,即在 unique_ptr 对象生命周期结束时,会自动释放所管理的对象。

3. unique_ptr 不支持共享所有权,即不能通过复制或赋值的方式将 unique_ptr 对象的所有权转移给其他 unique_ptr 对象。

以下是一些 unique_ptr 的使用示例:

std::unique_ptr<int> p(new int(10)); // 创建一个 unique_ptr

if (p) { // 判断指针是否为空
    std::cout << *p << std::endl; // 输出指针所指向的对象值
}

*p = 20; // 通过指针修改所指向对象值

std::unique_ptr<int> p2(std::move(p)); // 转移指针所有权

if (p2) { // 判断指针是否为空
    std::cout << *p2 << std::endl; // 输出指针所指向的对象值
}

释放 p2 管理的对象

p2.reset();

使用自定义删除器来释放动态分配的对象

std::unique_ptr<int, void(*)(int*)> p3(new int(30), [](int* p) { delete p; });

总之,unique_ptr 是 C++11 引入的一种非常有用的独占所有权智能指针,可以有效解决动态内存分配管理问题,规避内存泄漏和空指针的风险,提高程序的稳定性和可靠性。在实际编程中,应该充分掌握并应用 unique_ptr。

其实 unique_ptr  简而言之就是可以自动释放内存,避免了内存泄漏的风险。

#include<memeory>
using namespace std;
struct data
{
    int a,b,c;
};
void (f)
{
    unique_ptr<data> data (new data)
    {
        data->a=10;
        data->b=20;
        data->c=30;
    }
}

我们在这里就不需要手动释放内存。此外,我们还提供了另外一种方法构建一个智能指针:

 auto data = make_unique<data>();

这种写法避免了因为 data 构建失败而 抛出异常之后 出现 野指针。

关于智能指针其实还有很多知识点,我们在这里只是给大家简单讲解了一种智能指针。在后面会专门为大家写一篇文章讲解。

Lambda表达式:

Lambda 表达式是 C++11 引入的一种函数对象,主要用于简化函数对象的定义和传递。

Lambda 表达式由三部分组成:

1. 捕获列表(capture list):用于捕获一些外部变量或对象,可以为空。

2. 形参列表(parameter list):用于定义函数对象的形参列表,可以为空。

3. 函数体(function body):用于定义函数对象的实现,可以为空。

Lambda 表达式的定义方式为:

[capture list](parameters) -> return-type { function body }

其中,capture list 和 return-type 都是可选的,而 parameters 和 function body 则是必须的。以下是一些 Lambda 表达式的示例:

// 定义一个 Lambda 表达式
auto func = [](int x, int y) -> int { return x + y; };

// 使用 Lambda 表达式
int result = func(1, 2);
std::cout << result << std::endl;

// 定义一个带捕获列表的 Lambda 表达式
int a = 1, b = 2;
auto func2 = [a, &b](){ std::cout << a << b << std::endl; };

// 使用带捕获列表的 Lambda 表达式
func2();

Lambda 表达式的好处主要有以下几点:

1. 简化函数对象的定义:Lambda 表达式可以直接在需要的地方定义函数对象,避免了传统定义函数对象的繁琐和冗余。

2. 方便函数对象的传递:Lambda 表达式可以作为函数对象进行传递,从而避免了手动定义函数对象并传递的过程。

3. 支持捕获外部变量或对象:通过捕获列表,Lambda 表达式可以直接捕获外部作用域的变量或对象,方便编程。

总之,Lambda 表达式是 C++11 中非常实用的一项特性,可以简化函数对象的定义和传递,极大地提高了代码编写效率和代码可读性。在实际编程中应该充分掌握并应用该特性。

总结:

        本文我们详细的介绍了剩余的六个新增特性,其中有不少都是很实用的特性,如果用的好了可以大大简化代码的重复以及精简结构,因此我们要熟练的掌握这十个特性。

如果我的内容对你有帮助,请点赞,评论,收藏创作不易,大家的支持就是我坚持下去的动力!

猜你喜欢

转载自blog.csdn.net/fckbb/article/details/131394853