C++11新特性列举

1.自动类型推断(auto)

C++11引入了一个新的关键字“auto”,用于自动推断变量的类型。这使得变量的类型可以根据其初始化值自动推断出来,不必显式地指定类型。auto关键字也可以提高代码的可维护性和可读性,减少类型错误和重复代码的问题。
例如:

#include <iostream>
#include <vector>

int main() {
  auto x = 1; // 自动推导为int类型
  auto y = 2.0; // 自动推导为double类型
  auto z = "hello"; // 自动推导为const char*类型

  std::vector<int> vec = {1, 2, 3, 4, 5};
  for (auto it = vec.begin(); it != vec.end(); ++it) { // 自动推导迭代器类型
    std::cout << *it << std::endl;
  }

  return 0;
}

在上面的示例中,定义了三个变量x、y和z,它们的类型分别被自动推导为int、double和const char*。auto关键字可以简化变量类型的声明过程,并提高代码的可读性。

接下来,使用auto关键字自动推导了迭代器类型,然后使用for循环遍历了一个vector容器。使用auto关键字可以避免手动定义迭代器类型,并减少代码的复杂度。

2.区间迭代(range-based for loop)

C++11中引入了一个新的语法结构:区间迭代(range-based for loop)。这种循环方式可以遍历一个区间(如数组、容器等)中的所有元素,而无需使用迭代器或指针。
例如:

int arr[] = {1, 2, 3};
for (int i : arr) {
  std::cout << i << std::endl;
}

3.智能指针(smart pointers)

C++11引入了三种智能指针:std::unique_ptr、std::shared_ptr和std::weak_ptr。这些指针可以自动管理指针的生命周期,避免出现内存泄漏或悬空指针的情况。
例如:

std::unique_ptr<int> ptr(new int(10)); // 创建一个unique_ptr
std::shared_ptr<int> ptr2 = std::make_shared<int>(20); // 创建一个shared_ptr

动态内存管理例子:

#include <iostream>
#include <memory>

class MyClass {
public:
  MyClass() { std::cout << "MyClass constructor" << std::endl; }
  ~MyClass() { std::cout << "MyClass destructor" << std::endl; }
};

int main() {
  std::unique_ptr<MyClass> ptr1(new MyClass);
  std::unique_ptr<MyClass> ptr2 = std::make_unique<MyClass>();

  std::shared_ptr<MyClass> ptr3 = std::make_shared<MyClass>();
  std::shared_ptr<MyClass> ptr4 = ptr3;

  return 0;
}

在上面的示例中,定义了一个MyClass类,它有一个默认构造函数和一个析构函数。在main函数中,首先创建一个unique_ptr对象ptr1,它使用new运算符分配了一个MyClass对象,并管理了该对象的内存。当unique_ptr对象ptr1超出作用域时,它会自动释放该对象的内存。

接下来,使用std::make_unique函数创建了一个unique_ptr对象ptr2,并分配了一个MyClass对象。std::make_unique函数是C++14中引入的,可以更方便地创建unique_ptr对象。

然后,创建了两个shared_ptr对象ptr3和ptr4,它们都管理同一个MyClass对象的内存。shared_ptr是一种多个指针共享同一个对象的智能指针,可以有效地避免重复释放内存的问题。

总结:在实际开发中,使用智能指针可以避免内存泄漏和重复释放内存等问题,提高代码的可靠性和安全性。同时,智能指针也可以使代码更加简洁和易于维护,减少手动内存管理的工作量。

4.Lambda表达式

C++11中引入了Lambda表达式,可以方便地定义匿名函数。Lambda表达式可以在函数中直接定义一个函数对象,避免了定义一个具名的函数或类。
例如:

auto func = [] (int x, int y) { return x + y; }; // 定义一个Lambda表达式
int result = func(1, 2); // 调用Lambda表达式

5.右值引用(rvalue reference)

C++11中引入了右值引用,用于实现移动语义和完美转发。右值引用可以绑定到临时对象,包括函数返回的临时对象,从而实现高效的对象传递和构造。
当使用传统的左值引用传递对象时,通常需要进行深度拷贝(deep copy)操作,这会造成不必要的开销,特别是在操作大型对象时。右值引用和移动语义,可以避免这些开销,提高代码的性能和效率。

  1. 右值引用是什么?

右值引用是一种新的引用类型,用于表示一个临时对象的引用。右值引用的语法是在类型名后加上两个连续的 ampersand(&&)。例如:

int&& x = 1;//在这个例子中,x 是一个指向临时整数对象的右值引用
  1. 右值引用是什么?

右值引用最重要的作用是支持移动语义(move semantics)和完美转发(perfect forwarding)。在使用右值引用时,需要注意正确使用 std::move 和 std::forward 函数。

移动语义是一种优化技术,它允许我们在不进行深拷贝的情况下将对象的资源所有权从一个对象转移到另一个对象。在使用传统的拷贝语义时,我们必须将对象的资源复制到新的对象中,这可能非常耗时。使用移动语义,我们可以将对象的资源指针直接转移到新的对象中,从而避免了复制操作,大大提高了程序的性能。

完美转发是一种技术,它允许我们将函数参数按原样转发给另一个函数,而不会丢失任何信息。在 C++11 之前,我们必须使用模板和函数重载等技术来实现完美转发,但是这些技术往往比较复杂。右值引用的出现使得完美转发变得更加容易实现。

  1. 右值引用的使用

右值引用通常用于两个场景:

(1)移动语义
在使用移动语义时,我们将对象的资源所有权从一个对象转移到另一个对象。例如,考虑下面的代码:

std::vector<int> v1 = {1, 2, 3, 4, 5};
std::vector<int> v2 = std::move(v1);

在这个例子中,v1 是一个 std::vector 对象,它包含了 5 个整数。在将 v1 移动到 v2 时,我们使用了 std::move 函数将 v1 转换为一个右值引用,然后将其传递给 v2 的构造函数。因为 v1 已经被移动到了 v2,所以 v1 现在是一个空的 std::vector 对象。

(2)完美转发
在使用完美转发时,我们将函数参数按原样转发给另一个函数,而不会丢失任何信息。例如,考虑下面的代码:

template<typename T>
void foo(T&& x) {
    bar(std::forward<T>(x));
}

在这个例子中,foo 函数接受一个类型为 T 的右值引用参数 x,然后将其转发给 bar 函数。为了实现完美转发,我们使用了 std::forward 函数,它会根据 x 的类型决定是将 x 转发为左值引用还是右值引用。这样,我们就可以将 foo 函数的参数按原样转发给 bar 函数,而不会丢失任何信息。

#include <iostream>
#include <vector>

class MyClass {
public:
  MyClass() { std::cout << "Default constructor" << std::endl; }
  MyClass(const MyClass& other) { std::cout << "Copy constructor" << std::endl; }
  MyClass(MyClass&& other) { std::cout << "Move constructor" << std::endl; }
  MyClass& operator=(const MyClass& other) { std::cout << "Copy assignment" << std::endl; return *this; }
  MyClass& operator=(MyClass&& other) { std::cout << "Move assignment" << std::endl; return *this; }
};

MyClass createMyClass() {
  return MyClass();
}

int main() {
  std::vector<MyClass> vec;

  MyClass obj1;
  vec.push_back(obj1); // 拷贝构造一个新对象

  MyClass obj2 = createMyClass(); // 移动构造一个新对象
  vec.push_back(std::move(obj2));

  return 0;
}

在上面的示例中,MyClass类定义了默认构造函数、拷贝构造函数、移动构造函数、拷贝赋值函数和移动赋值函数。在main函数中,首先创建一个MyClass对象obj1,然后将其插入到一个vector容器中,这会调用拷贝构造函数。

接下来,调用createMyClass函数创建一个MyClass对象,这会返回一个临时对象,然后使用std::move函数将其转换为右值引用,从而实现移动构造。这种方式避免了不必要的拷贝操作,提高了代码的性能和效率。

总结:在实际开发中,使用右值引用和移动语义可以大大提高代码的性能和效率,特别是在处理大型对象或容器时。

6.constexpr函数

C++11中引入了constexpr关键字,可以让函数或变量在编译期计算出值,从而提高代码的性能和效率。constexpr关键字可以用于常量表达式(constant expression)的计算和编译期优化。constexpr函数需要在编译时就能确定其参数和返回值,并且函数体必须是一个单一的返回语句或者一系列简单语句。可以用在以下几个方面:

1.常量表达式的计算:constexpr关键字可以用于函数和变量的声明,用于在编译期间计算出值,从而提高代码的性能和效率。
2.数组大小的定义:constexpr关键字可以用于定义数组的大小,数组大小必须是常量表达式,从而提高代码的可靠性和效率。
3.枚举类型的定义:constexpr关键字可以用于定义枚举类型的值,使其成为常量表达式,从而提高代码的可读性和可靠性。
4.类的成员函数和构造函数:constexpr关键字可以用于类的成员函数和构造函数的声明,用于在编译期计算出值,从而提高代码的性能和效率。
5.模板参数:constexpr关键字可以用于模板参数的声明,用于在编译期计算出值,从而提高代码的性能和效率。
例如:

#include <iostream>

constexpr int factorial(int n) {
  return n <= 1 ? 1 : n * factorial(n - 1);
}

int main() {
  constexpr int n = 5;
  constexpr int result = factorial(n);
  std::cout << result << std::endl;

  return 0;
}

在上面的示例中,定义了一个constexpr函数factorial,它计算了一个整数的阶乘。由于函数的参数和返回值都是常量表达式,因此可以在编译期计算出结果。

在main函数中,定义了一个constexpr变量n,并使用它调用了factorial函数。由于n是常量表达式,因此在编译期间就可以计算出结果,并将结果存储到result变量中。最后,使用std::cout输出了结果。

#include <iostream>

class MyClass {
public:
  constexpr MyClass(int x) : value(x) {}
  constexpr int getValue() const { return value; }

private:
  int value;
};

int main() {
  constexpr MyClass obj(5);
  constexpr int result = obj.getValue();
  std::cout << result << std::endl;

  return 0;
}

在上面的示例中,定义了一个名为MyClass的类,它有一个构造函数和一个成员函数getValue。构造函数使用constexpr关键字进行声明,可以在编译期计算出结果。成员函数getValue也使用constexpr关键字进行声明,可以在编译期间计算出结果。

在main函数中,使用constexpr关键字创建了一个MyClass对象obj,并使用它调用了成员函数getValue。由于getValue是常量表达式,因此可以在编译期间计算出结果,并将结果存储到result变量中。最后,使用std::cout输出了结果。

7.nullptr关键字

C++11中引入了nullptr关键字,用于表示空指针。nullptr可以替代旧的NULL宏定义,避免了由于NULL被定义为0而引发的一些错误。
例如:

int* ptr = nullptr; // 使用nullptr初始化指针

8.静态断言(static_assert)

C++11中引入了静态断言(static_assert),用于在编译时检查条件是否满足。如果条件不满足,则会导致编译错误。
例如:

static_assert(sizeof(int) == 4, "int must be 4 bytes"); // 检查int是否为4字节

9.别名声明(alias declaration)

C++11中引入了别名声明(alias declaration),可以用于定义类型别名、模板别名和函数指针别名等,以及定义命名空间别名等。以下是一些使用别名声明的例子:

1.定义类型别名
#include <iostream>
#include <vector>

using MyVector = std::vector<int>; // 定义类型别名MyVector

int main() {
  MyVector v = {1, 2, 3, 4, 5};
  for (auto x : v) {
    std::cout << x << " ";
  }
  std::cout << std::endl;

  return 0;
}

在上面的代码中,使用别名声明定义了一个名为MyVector的类型别名,它是std::vector类型的别名。在main函数中,使用MyVector类型创建了一个包含五个整数的向量,并输出向量的元素。

2.定义模板别名
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>

template<typename T>
using Predicate = std::function<bool(T)>; // 定义模板别名Predicate

int main() {
  std::vector<int> v = {1, 2, 3, 4, 5};
  Predicate<int> p = [](int x) { return x % 2 == 0; };

  auto it = std::find_if(v.begin(), v.end(), p);
  if (it != v.end()) {
    std::cout << "The first even number is " << *it << std::endl;
  } else {
    std::cout << "No even number found" << std::endl;
  }

  return 0;
}

在上面的代码中,使用别名声明定义了一个名为Predicate的模板别名,它是std::function<bool(T)>类型的别名。在main函数中,使用Predicate类型创建了一个lambda表达式,用于判断一个整数是否为偶数。使用std::find_if算法在向量中查找第一个偶数,并输出找到的结果。

3.定义函数指针别名
#include <iostream>

using PrintFunc = void (*)(int); // 定义函数指针别名PrintFunc

void print(int x) {
  std::cout << x << " ";
}

int main() {
  PrintFunc p = &print; // 使用函数指针别名创建函数指针
  for (int i = 1; i <= 5; i++) {
    p(i);
  }
  std::cout << std::endl;

  return 0;
}

在上面的代码中,使用别名声明定义了一个名为PrintFunc的函数指针别名,它是void (*)(int)类型的别名。在main函数中,使用PrintFunc类型创建了一个函数指针,指向名为print的函数。使用函数指针输出数字1到5。

4.定义命名空间别名
#include <iostream>

namespace foo {
  void bar() {
    std::cout << "Hello, world!" << std::endl;
  }
}

namespace myns = foo; // 定义命名空间别名myns

int main() {
  myns::bar(); // 使用命名空间别名调用foo::bar函数
  return 0;
}

在上面的代码中,使用别名声明定义了一个名为myns的命名空间别名,它是foo命名空间的别名。在main函数中,使用myns::bar()语句调用foo::bar函数,并输出Hello, world!。

10.变长参数模板(variadic templates)

C++11中引入了变长参数模板(variadic templates),用于处理可变数量的模板参数。这使得编写可以接受任意数量参数的函数或类成为可能。
例如:

template<typename... Args>
void print(Args... args) {
  std::cout << ... << args << std::endl; // 使用折叠表达式展开参数包
}

print(1, 2, 3, "hello"); // 输出: 123hello

11.静态多态(static polymorphism)

C++11中引入了模板元编程(template metaprogramming),可以在编译时实现静态多态。模板元编程可以通过模板实参推导和递归展开等技术,实现高效的代码生成和优化。
例如:

template<int N>
struct factorial {
  enum { value = N * factorial<N-1>::value };
};

template<>
struct factorial<0> {
  enum { value = 1 };
};

int x = factorial<5>::value; // 在编译时计算5的阶乘

12.线程支持库(thread support library)

C++11中引入了线程支持库(thread support library),包括std::thread、std::mutex、std::condition_variable等类,用于实现多线程编程,提高程序的并发性和性能。可以方便地创建和管理线程,实现并发编程。
例如:

#include <iostream>
#include <thread>

void hello() {
  std::cout << "Hello from thread " << std::this_thread::get_id() << std::endl;
}

int main() {
  std::thread t(hello); // 创建一个新线程
  t.join(); // 等待线程结束
  return 0;
}

在上面的代码中,使用std::thread类创建了一个新线程,并将函数printHello作为线程的执行函数。使用join()函数等待子线程执行完毕后,主线程才能继续执行。在控制台中执行该程序,可以看到输出“Hello, World!”。

以下是一个使用C++11线程库实现多线程计算向量和的例子:

#include <iostream>
#include <thread>
#include <vector>
#include <numeric>

void accumulate(std::vector<int>::iterator begin,
                std::vector<int>::iterator end,
                int& result) {
  result = std::accumulate(begin, end, 0);
}

int main() {
  std::vector<int> data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

  const size_t num_threads = 4;
  std::vector<std::thread> threads(num_threads);
  std::vector<int> results(num_threads);

  auto block_size = data.size() / num_threads;
  auto begin = data.begin();
  for (size_t i = 0; i < num_threads; ++i) {
    auto end = (i == num_threads - 1) ? data.end() : (begin + block_size);
    threads[i] = std::thread(accumulate, begin, end, std::ref(results[i]));
    begin = end;
  }

  for (auto& thread : threads) {
    thread.join();
  }

  int total = std::accumulate(results.begin(), results.end(), 0);
  std::cout << "The sum of the vector is " << total << std::endl;

  return 0;
}

在上面的代码中,使用四个线程并行计算了一个包含十个整数的向量的和,最后将四个线程的结果相加得到了向量的总和。在主线程中,使用std::vector和std::accumulate函数将向量分成了四个块,分别由四个子线程并行计算,每个子线程的结果存储在results向量中。使用std::ref函数将结果变量传递给线程函数,以便在线程函数中修改结果。

13.初始化列表(initializer list)

C++11中引入了初始化列表(initializer list)。当我们需要初始化一个数组、结构体或类的成员变量时,可以使用初始化列表(initializer list)来指定初始值。在C++11之前,通常使用构造函数或数组初始化语法来进行初始化,但是这些方法存在一些限制,比如不能初始化非静态的const成员变量。使用初始化列表可以避免这些限制,并提供更加灵活和简洁的初始化方式。

以下是一个使用初始化列表的例子,用于初始化一个包含三个元素的vector:

#include <iostream>
#include <vector>

int main() {
  std::vector<int> v = {1, 2, 3};

  for (auto i : v) {
    std::cout << i << " ";
  }
  std::cout << std::endl;

  return 0;
}

在上面的代码中,使用初始化列表初始化了一个vector对象v,其中包含了三个元素1、2和3。这里使用了花括号括起来的初始值列表来初始化vector对象,可以看作是一种简化的数组初始化方式。这种方式不仅可以用于vector对象,还可以用于数组、结构体和类的成员变量的初始化。

初始化列表还可以用于构造函数中初始化类的成员变量,例如:

#include <iostream>
#include <string>

class Person {
public:
  Person(std::string n, int a) : name(n), age(a) {}

  void print() const {
    std::cout << "Name: " << name << ", Age: " << age << std::endl;
  }

private:
  std::string name;
  int age;
};

int main() {
  Person p = {"Alice", 25};
  p.print();

  return 0;
}

在上面的代码中,定义了一个名为Person的类,使用初始化列表初始化了类的成员变量name和age。在main函数中,使用初始化列表创建了一个Person对象p,并调用了对象的print方法输出结果。这种方式可以避免在构造函数中进行赋值操作,使代码更加简洁和易读。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值