c++11的一些新特性

c++11

  • 1. {}初始化
  • 2. 范围for循环
  • 3. final与override
  • 4. 右值引用
    • 4.1 左值引用和右值引用
    • 4.2 左值引用与右值引用比较
  • 5. lambda表达式
  • 6. 声明
    • 6.1 auto
    • 6.2 decltype
    • 6.3 nullptr
  • 7. 可变参数模版

1. {}初始化

在C++中,使用花括号初始化的方式被称为列表初始化。列表初始化可以用于数组、结构体、类等类型的初始化。在C++11之前,列表初始化仅能用于数组和POD类型的初始化。C++11新标准将列表初始化应用于所有对象的初始化。以下是一些使用列表初始化的例子:

struct Point 
{
    int _x;
    int _y;
}; 

class foo 
{
public:
    foo(int i, double d) :_i(i), _d(d) {} // 构造函数
private:
    int _i;
    double _d;
};
int main()
{
    int arr[]{ 1, 2, 3, 4, 5 }; // 数组
    Point p{ 1,2 }; // 结构体
    foo f{ 1, 3.14 }; // 类
    return 0;
}

以上代码中,arr 是一个整型数组,使用列表初始化方式进行了初始化;p 是一个结构体,使用列表初始化方式对其成员变量进行了初始化;f 是一个类对象,使用列表初始化方式对其成员变量进行了初始化。

2. 范围for循环

在C++中,范围for循环是一种用于遍历数组、容器、初始化列表等类型的语法结构。它的语法格式如下:

for (declaration : expression)
{
    // 循环体
}

其中,declaration 表示遍历声明,在遍历过程中,当前被遍历到的元素会被存储到声明的变量中。expression 是要遍历的对象,它可以是表达式、容器、数组、初始化列表等。

以下是一个使用范围for循环的例子:

#include 
#include 

int main()
{
    std::vector<int> v = {1, 2, 3, 4, 5};
    for (auto value : v)
    {
        std::cout << value << " ";
    }
    std::cout << std::endl;
    return 0;
}

以上代码中,v 是一个整型向量,使用范围for循环方式进行了遍历。
需要注意的是,在使用范围for循环遍历容器时,循环会自动以容器为范围展开,并且循环中也屏蔽掉了迭代器的遍历细节,直接抽取容器中的元素进行运算,使用这种方式进行循环遍历会让编码和维护变得更加简便。

其中还有一个点需要注意,上述value相当于一个形参,也就是说value改变,不影响数组v的改变,那么怎么在遍历的时候又能修改数组v?其实可以使用引用访问元素,如下:

#include 
#include 

int main()
{
    std::vector<int> v = { 1, 2, 3, 4, 5 };
    cout << "没有使用引用访问:";
    for (auto value : v)
    {
        value *= 2;
    }
    for (auto value : v)
    {
        std::cout << value << " ";
    }
    std::cout << std::endl;


    cout << "使用引用访问:";
    for (auto &value : v)
    {
        value *= 2;
    }
    for (auto value : v)
    {
        std::cout << value << " ";
    }
    std::cout << std::endl;

    return 0;
}

c++11的一些新特性_第1张图片
在C++中,使用范围for循环时,如果使用引用访问元素,可以避免对元素进行拷贝,从而提高程序的效率。当使用值访问元素时,会对元素进行一次拷贝,而使用引用访问元素时,则不会进行拷贝。因此,在不改变元素的情况下,使用引用访问元素可以减少一次拷贝,提高程序的效率。

3. final与override

在C++11中,override 和 final 是两个新的关键字,用于增强代码的安全性和可读性。

override 用于在派生类中重写基类的虚函数时,显式地告诉编译器此函数是重写基类的虚函数。如果重写时函数名、参数列表和返回类型都和基类的虚函数一致,但是没有加上 override 关键字,那么编译器无法判断是否是故意的重写,容易导致程序出错。加上 override 关键字后,编译器会在编译时检查是否真的重写了基类的虚函数,如果没有则会报错,从而避免了这种错误。

final 用于修饰类、函数或者变量,表示它们是终态的,不能被派生类、重写或者修改。使用 final 关键字可以防止子类再覆写父类的虚函数。如果一个虚函数被声明为 final,则派生类不能再重写它。

4. 右值引用

4.1 左值引用和右值引用

什么是左值?什么是左值引用?
左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址对它赋值,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边。定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址。左值引用就是给左值的引用,给左值取别名。

int main()
{
    // 以下的p、b、c、*p都是左值
    int* p = new int(0);
    int b = 1;
    const int c = 2;
    // 以下几个是对上面左值的左值引用
    int*& rp = p;
    int& rb = b;
    const int& rc = c;
    int& pvalue = *p;
    return 0;
}

什么是右值?什么是右值引用?
右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(这个不能是左值引用返回)等等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址。右值引用就是对右值的引用,给右值取别名。

int main()
{
    double x = 1.1, y = 2.2;
    // 以下几个都是常见的右值
    10;
    x + y;
    fmin(x, y);
    // 以下几个都是对右值的右值引用
    int&& rr1 = 10;
    double&& rr2 = x + y;
    double&& rr3 = fmin(x, y);
    // 这里编译会报错:error C2106: “=”: 左操作数必须为左值
    //10 = 1;
    //x + y = 1;
    //fmin(x, y) = 1;
    return 0;
}

需要注意的是右值是不能取地址的,但是给右值取别名后,会导致右值被存储到特定位置,且可以取到该位置的地址,也就是说例如:不能取字面量10的地址,但是rr1引用后,可以对rr1取地址,也可以修改rr1。如果不想rr1被修改,可以用const int&& rr1 去引用。

4.2 左值引用与右值引用比较

  1. 左值引用是用符号 & 声明的,它只能绑定到左值,即可以取地址、有名字、非临时的对象。左值引用可以用来修改或读取所绑定的对象的值。
  2. 右值引用是用符号 && 声明的,它只能绑定到右值,即不能取地址、没有名字、临时的对象。右值引用可以用来延长所绑定的对象的生命周期,或者实现移动语义,即将对象的资源从一个所有者转移到另一个所有者,而不需要进行拷贝。
  3. 一个例外是,const左值引用可以绑定到右值,这样可以实现对右值的只读访问,而不改变其生命周期。
  4. 另一个例外是,在函数重载时,如果有一个参数既可以接受左值引用,又可以接受右值引用,那么编译器会优先选择左值引用。这是为了避免对左值进行不必要的移动操作。

左值引用和右值引用的优缺点如下:

  1. 左值引用的优点是可以对所引用的对象进行修改或读取,而不需要拷贝或移动。左值引用的缺点是不能绑定到右值,如果需要绑定到右值,必须使用常量左值引用,但这样就不能修改所引用的对象了。
  2. 右值引用的优点是可以实现移动语义,减少拷贝或赋值操作的开销,提高程序的效率。右值引用的缺点是不能修改所引用的对象,而且会改变所引用对象的状态,使其失去资源的所有权。

5. lambda表达式

在C++98中,如果想要对一个数据集合中的元素进行排序,可以使用std::sort方法,但是如果排序的是自定义类型元素,每次为了实现一个algorithm算法,都要重新去写一个类,如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名,这些都给编程者带来了极大的不便。因此,在C++11语法中出现了Lambda表达式。

在C++11中,Lambda表达式是一种用于定义匿名函数的语法结构。Lambda表达式可以用于任何需要函数对象的地方,例如函数参数、返回值、STL算法等。Lambda表达式的语法格式如下:

[capture-list] (parameters) mutable -> return-type { statement };

lambda表达式各部分说明:

  1. [capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。
  2. (parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略。
  3. mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。
  4. ->returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
  5. {statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。

注意:
在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为空。因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情。

int main()
{
    // 最简单的lambda表达式, 该lambda表达式没有任何意义
    [] {};
    // 省略参数列表和返回值类型,返回值类型由编译器推导为int
    int a = 3, b = 4;
    [=] {return a + 3; };
    // 省略了返回值类型,无返回值类型
    auto fun1 = [&](int c) {b = a + c; };
    fun1(10);
    cout << a << " " << b << endl;
    // 各部分都很完善的lambda函数
    auto fun2 = [=, &b](int c)->int {return b += a + c; };
    cout << fun2(10) << endl;
    // 复制捕捉x
    int x = 10;
    auto add_x = [x](int a) mutable { x *= 2; return a + x; };
    cout << add_x(10) << endl;
    
    return 0;
}

int main()
{
    int x = 10, y = 20;
    auto func1 = [](int x = 1,int y = 2) //当捕捉列表和参数列表都有x,y,优先用参数列表中的值。
    {
        cout << x + y << endl; // 3
    };
    func1();

    return 0;
}

int main()
{
	int x = 0, y = 1;
	int m = 0, n = 1;

	auto swap1 = [](int& rx, int& ry)
	{
		int tmp = rx;
		rx = ry;
		ry = tmp;
	};

	swap1(x, y);
	cout << x << " "<< y << endl;

	// 引用捕捉
	auto swap2 = [&x, &y]()
	{
		int tmp = x;
		x = y;
		y = tmp;
	};

	swap2();
	cout << x << " " << y << endl;

	// 混合捕捉
	auto func1 = [&x, y]()
	{
		//...
	};

	// 全部引用捕捉
	auto func2 = [&]()
	{
		//...
	};

	// 全部传值捕捉
	auto func3 = [=]()
	{
		//...
	};

	// 全部引用捕捉,x传值捕捉
	auto func4 = [&, x]()
	{
		//...
	};

	return 0;
}

lambda表达式的优点是可以使代码更加简洁紧凑,并且可以避免定义不必要的函数对象。Lambda表达式的缺点是可能会降低代码的可读性和可维护性。

6. 声明

6.1 auto

auto是C++11引入的一个关键字,用于在声明变量时自动推导变量的类型。auto的使用可以让编译器在编译期间自动推算出变量的类型,这样就可以更加方便的编写代码了。auto还可以用于定义函数返回值类型,但此时auto仍然使用的是模板实参推断的机制,因此返回类型为auto的函数如果返回一个初始化列表,则会出错。

6.2 decltype

在C++11中,decltype 是一种用于推导表达式类型的关键字。decltype 可以用于推导变量、函数返回值、表达式等的类型。decltype 的语法格式如下:

decltype(expression)

其中,expression 是要推导类型的表达式。

以下是一个使用 decltype 的例子:

#include 

int main()
{
    int i = 42;
    decltype(i) j = i + 1;
    std::cout << "i = " << i << ", j = " << j << std::endl;
    return 0;
}

以上代码中,使用了 decltype 推导了变量 j 的类型。

decltype 的优点是可以在编译期间推导出表达式的类型,从而提高程序的效率。decltype 的缺点是可能会降低代码的可读性和可维护性。

6.3 nullptr

C++11中,nullptr是一个用于表示空指针的关键字,它可以替代C++03中的0或NULL。nullptr的类型是std::nullptr_t,它可以隐式转换为任意类型的指针或成员指针,但不能转换为整数类型或布尔类型。nullptr的优点是可以避免一些类型推导的歧义,例如在函数重载或模板参数推导时。nullptr的缺点是可能会与一些旧代码不兼容,例如使用NULL作为整数常量的代码。

在C++中NULL被定义成字面量0,这样就可能回带来一些问题,因为0既能指针常量,又能表示整形常量。所以出于清晰和安全的角度考虑,C++11中新增了nullptr,用于表示空指针。

#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif

7. 可变参数模版

在C++11中,可变参数模板是一种用于定义可变数量参数的模板。它允许模板函数或类接受任意数量的参数,包括类型、非类型和模板参数。可变参数模板的语法格式如下:

template<typename... Args>
void foo(Args... args)
{
    // 函数体
}

其中,Args 是一个模板参数包,可以接受任意数量的模板参数。在函数体中,可以使用 args… 来展开参数包,以便对每个参数进行操作。

可变参数模版中有一点需要注意,在使用sizeof()求可变参数的个数时,应该这样书写:sizeof…(args),//错误格式sizeof(args…);如下所示:

template <class ...Args>
void ShowList(Args... args)
{
    cout << sizeof...(args) << endl; //求可变参数的个数
}
int main()
{
    ShowList(1);
    ShowList(1, 2.2);
    ShowList(1, 2.2, "hello");

    return 0;
}

c++11的一些新特性_第2张图片

如何解析可变参数包?这里使用递归来解决。以下是一个使用可变参数模板的例子:

void ShowList() //函数重载,当参数个数为0时,没有该函数,就会找不到匹配的函数
{
    cout << endl;
}
template <class T, class ...Args>
void ShowList(const T& val, Args... args) //每次从参数包中解析一个
{
    cout << val << " ";
    ShowList(args...);
}
int main()
{
    ShowList(1);
    ShowList(1, 2.2);
    ShowList(1, 2.2, "three");

    return 0;
}

c++11的一些新特性_第3张图片

可变参数模板的优点是可以使代码更加灵活和通用,可以接受任意数量和类型的参数。可变参数模板的缺点是可能会降低代码的可读性和可维护性。

你可能感兴趣的:(c及c++笔记,c++,开发语言)