本篇介绍C++11标准对比之前C++标准的新特性,C++11为C++语言在2011年发布的版本,它改进幅度很大,影响至今。如加入auto 关键字、nullptr、移动语义(move semantics)、委托构造函数(delegating constructors)、默认构造函数(default constructor)、类型别名(type alias)、右值引用(rvalue reference)、Lambda 表达式(lambda expressions)、静态断言(static_assert)、新的数值类型(new numerical types)、智能指针(smart pointers)、并发库(concurrency library)等。本篇简单介绍一下这些常用的内容。
目录
一、auto关键字
1.1 自动推断变量类型
1.2 auto与模板函数结合使用
二、nullptr空指针
三、移动语义(move semantics)
3.1 移动语义介绍
3.2 移动语义的实现
四、委托构造函数(delegating constructors)
五、默认构造函数
六、类型别名(type alias)
6.1 typedef
6.2 using
七、右值引用(rvalue reference)
八、Lambda 表达式(lambda expressions)
九、静态断言(static_assert)
十、智能指针(smart pointers)
十一、并发库(concurrency library)
一、auto关键字
1.1 自动推断变量类型
auto可以自动推断变量的类型。它通过分析变量初始化表达式的类型来确定变量的类型。这样可以简化代码,减少类型的显式声明,提高代码的可读性。
eg:
auto x = 10; // x的类型为int
auto y = 3.14; // y的类型为double
auto还可简化模版代码:
template<typename T>
void func(T t) {
auto x = t + 1; // x的类型与t的类型相同
// ...
}
自动推断迭代器类型:
std::vector<int> v = {1, 2, 3, 4, 5};
for (auto it = v.begin(); it != v.end(); ++it) {
// ...
}
自动推断lambda函数返回类型:
auto func = [] (int a, int b) { return a + b; };
自动推断类型构造函数的返回类型:
struct A {
int x, y;
};
auto func() {
return A{1, 2};
}
在上面的代码中,函数func返回了一个类型构造函数A的结果,但是我们并不需要显式地声明返回类型,编译器会自动推断出返回类型为A。 这样做可以使代码更简洁易读,同时还可以避免因为类型改变导致的类型错误。
1.2 auto与模板函数结合使用
auto和模板函数结合使用可以实现自动推断函数返回类型,从而使代码更简洁易读。
template<typename T>
auto max(T a, T b) -> decltype(a > b ? a : b)
{
return a > b ? a : b;
}
template<typename T>
:声明模板函数,T表示模板参数类型。auto max(T a, T b)
:定义函数名为max,接收两个参数a和b,返回类型为auto。-> decltype(a > b ? a : b)
:使用decltype关键字指定函数的返回类型。
Tips:
decltype(a > b ? a : b)
是C++中的decltype
关键字,用于指定函数的返回类型。它的作用是根据表达式的类型推断出函数的返回类型。在上面的代码中,a > b ? a : b
是一个三元表达式,表示如果a大于b,则返回a,否则返回b。decltype
关键字可以推断出这个三元表达式的类型,并作为函数的返回类型。这样,我们可以使用
decltype
指定函数返回类型,避免手动编写长且复杂的类型名称,提高代码可读性。
return a > b ? a : b;
:返回两个数的最大值。
二、nullptr空指针
nullptr
是C++11引入的新特性,代表一个空指针常量。它可以明确地表示空指针,比起使用NULL或0更加明确和安全。
在C++11之前,使用NULL表示空指针,但是他是整数,存在类型转换问题,例如:
void foo(int);
void foo(char*);
foo(NULL);
代码在C++98无法编译通过,因为编译器无法确定调用的是foo(int)
还是foo(char*)
。但是,使用nullptr
就可以解决这个问题:
foo(nullptr);
编译器可以确定调用的是foo(char*)
,这样就可以避免类型转换问题。所以,使用nullptr
比使用NULL更加明确和安全,建议在C++11及以后的代码中使用nullptr
来表示空指针常量。
三、移动语义(move semantics)
3.1 移动语义介绍
移动语义是C++11新出的特性,可以实现快速资源转移,在C++中对象的赋值和传递一般需要复制对象内容:
Tips:
在C++中,对象之间的赋值和传递操作一般需要复制对象内容。这意味着,如果你将一个对象的值赋给另一个对象,或者将一个对象作为参数传递给函数,那么这两个对象的内容会被复制。
在C++中,所有的数据类型(包括内置类型和自定义类型)都是对象。因此,如果你赋值一个int类型的变量给另一个int类型的变量,或者将一个int类型的变量作为参数传递给函数,那么这两个int类型的变量的值会被复制。同样,如果你赋值一个自定义类型的对象给另一个自定义类型的对象,或者将一个自定义类型的对象作为参数传递给函数,那么这两个自定义类型的对象的值也会被复制。
这种复制行为可能导致代码慢,特别是在处理大对象时。因此,C++提供了一些特殊的方法,如引用和指针,来优化对象之间的赋值和传递操作。这些方法不需要复制对象内容,而是通过指向对象内存的指针或引用实现赋值和传递。
如果对象特别大会造成性能损失,移动语义提供了一种途径,在不复制对象内容的情况下,快速转移对象资源,从而解决性能问题。
移动语义的实现需要使用C++11的移动构造函数和移动赋值运算符,通过标记对象为可移动对象,告诉编译器在赋值或传递操作时,不要进行复制,而是直接转移资源。
eg:
vector<int> v1 = {1, 2, 3};
vector<int> v2 = move(v1);
在这段代码中,v1是源对象,v2是目标对象。通过使用移动语法(move),可以快速转移v1中的资源到v2中,不需要复制内容,从而提高性能。
完整代码示例:
#include <iostream>
#include <vector>
#include <utility>
using namespace std;
int main()
{
vector<int> v1 = {1, 2, 3};
cout << "v1 size: " << v1.size() << endl;
vector<int> v2 = move(v1);
cout << "v2 size: " << v2.size() << endl;
cout << "v1 size: " << v1.size() << endl;
return 0;
}
3.2 移动语义的实现
C++移动语义是通过重载"="操作符和移动构造函数实现的。
以下是移动构造函数的代码实现:
class MyClass
{
public:
MyClass(MyClass&& other)
{
// 移动资源的代码
}
};
Tips:
"MyClass&& other" 是一个右值引用,它指向一个右值(临时对象)。右值引用通常用于移动语义,它允许在不拷贝数据的情况下从一个对象转移到另一个对象。在上面的代码中,"MyClass&& other" 表示移动构造函数的参数,它是一个右值,可以通过移动语义在不复制数据的情况下从一个对象转移到另一个对象。
右值是表达式的一种结果,它代表一个不可变的、临时的、只用于赋值一次的值。右值通常是存储在程序栈上的数据,而不是存储在堆上或全局内存中的数据。右值可以是一个常量、字面值或者是一个表达式,但不能直接访问它们。右值通常通过移动语义从一个对象转移到另一个对象,从而避免了不必要的数据拷贝。
操作符重载:
class MyClass
{
public:
MyClass& operator=(MyClass&& other)
{
// 移动资源的代码
return *this;
}
};
四、委托构造函数(delegating constructors)
委托构造函数是指在 C++11 中,一个构造函数能够调用另一个同类的构造函数,以初始化对象。这样可以避免在多个构造函数间重复的代码,提高代码的可读性和可维护性。语法格式为:
构造函数名(参数列表):类名(同一类的其他构造函数的参数列表){ //其他代码 }。
代码示例:
class MyClass {
public:
MyClass() : MyClass(0, 0) {} // 委托构造函数,调用下面的构造函数
MyClass(int x, int y) : x_(x), y_(y) {}
int x_, y_;
};
下面是一个委托构造函数的妙用的示例:
class ComplexNumber {
public:
// 默认构造函数,使用委托构造函数初始化实部和虚部为 0
ComplexNumber() : ComplexNumber(0, 0) {}
// 委托构造函数,调用下面的构造函数
ComplexNumber(double real, double imag) : real_(real), imag_(imag) {}
// 委托构造函数,调用下面的构造函数,把另一个复数的值赋给新的复数对象
ComplexNumber(const ComplexNumber &other)
: ComplexNumber(other.real_, other.imag_) {}
double real_, imag_;
};
在这个示例中,我们可以看到三个构造函数,其中两个构造函数是委托构造函数。默认构造函数委托初始化实部和虚部为 0 的构造函数。另一个委托构造函数把另一个复数的实部和虚部赋给新的复数对象。因此,我们可以避免在多个构造函数间重复的代码。
五、默认构造函数
这个大家应该很熟悉了,很难想象之前没有默认构造函数,C++er的处境。
六、类型别名(type alias)
类型别名(type alias)是指为一个已经存在的类型创建一个新的名称。可以使用关键字 typedef
或 using
实现。
6.1 typedef
使用 typedef
的方式如下:
typedef int MyInt;
MyInt i = 5;
6.2 using
使用 using
的方式如下:
using MyInt = int;
MyInt i = 5;
七、右值引用(rvalue reference)
右值引用是 C++11 引入的一种引用类型,它是一个可以指向右值的引用。右值引用可以帮助我们实现移动语义。
语法:
int&& rvalueRef = 5;
也可以利用右值引用实现移动语义,例如:
std::string getName() { return std::string("Alex"); }
int main() {
std::string&& name = getName();
std::cout << name << std::endl;
return 0;
}
实际上,在调用 getName()
时,返回的是一个右值,而在给右值引用 name
赋值时,移动语义就可以得到实现。
八、Lambda 表达式(lambda expressions)
如下链接:
https://bobowen.blog.csdn.net/article/details/128696518?spm=1001.2014.3001.5502
九、静态断言(static_assert)
静态断言是C++11引入的一种编译期断言。它在编译期间评估一个常量表达式,并在表达式为false时生成编译错误。静态断言的语法是:
static_assert(expression, message);
其中expression是一个常量表达式,message是一个字符串常量,用于描述错误的原因。
代码示例:
#include <iostream>
int main()
{
static_assert(2 + 2 == 4, "2 + 2 should equal 4");
std::cout << "Compilation succeeded\n";
return 0;
}
这个代码编译时不会出错,但是如果将表达式改为2 + 2 != 4,就会生成编译错误,并显示错误消息"2 + 2 should equal 4"。
错误示例:
直接编译不通过,还是挺厉害的功能。
十、智能指针(smart pointers)
如下链接:
https://bobowen.blog.csdn.net/article/details/128714630?spm=1001.2014.3001.5502
十一、并发库(concurrency library)
C++11 标准引入了并发库(concurrency library),它提供了一组用于管理多线程的组件,包括线程、锁、同步原语、异步任务和其他工具。并发库的目的是简化多线程编程,使开发人员能够更容易地创建高效的多线程应用程序。它可以帮助开发人员避免常见的多线程错误,如死锁、竞争条件和数据不一致。
代码示例:
#include <iostream>
#include <thread>
void print_message(const char* message) {
std::cout << message << std::endl;
}
int main() {
std::thread t(print_message, "Hello from thread");
t.join();
return 0;
}