<C++> C++11

Getting Started with C++11

C++11 is an important standard of the C++ language, released in 2011. It introduces many new features and improvements aimed at improving development efficiency and code quality.

Compared with C++98/03, C++11 has brought a considerable number of changes, including about 140 new features, and about 600 bug fixes in the C++03 standard, which makes C ++11 is more like a new language conceived from C++98/03. In comparison, C++11 can be better used for system development and library development, the syntax is more generalized and simplified, more stable and safer, not only more powerful, but also can improve the development efficiency of programmers.

1. Uniform list initialization

1.1 {} initialization

In C++98, the standard allows the use of curly braces {} for uniform list initialization of array or structure elements.

struct Point {
    
    
    int _x;
    int _y;
};

int main() {
    
    
    int array1[] = {
    
    1, 2, 3, 4, 5};
    int array2[5] = {
    
    0};
    Point p = {
    
    1, 2};
    Point p{
    
     1,2 };
    return 0;
}

C++11 expands the scope of use of the list enclosed in curly braces (initialization list), so that it can be used for all built-in types and user-defined types. When using the initialization list, you can add an equal sign (=), or Do not add . Here are some examples:

struct Point {
    
    
    int _x;
    int _y;
};


int main() {
    
    
    int num1{
    
    10};            // 初始化整型变量
    double pi{
    
    3.14159};      // 初始化浮点型变量
    char ch{
    
    'A'};            // 初始化字符型变量
    bool flag{
    
    true};         // 初始化布尔型变量
    std::string name{
    
    "John"};// 初始化字符串对象

    int array1[]{
    
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    int array2[5]{
    
    0};
    Point p{
    
    1, 2};


    std::vector<int> numbers{
    
    1, 2, 3, 4};// 初始化容器对象
    std::list<int> lt{
    
    1, 2, 3, 4};
    std::pair<int, double> p{
    
    42, 3.14};// 初始化pair对象

    //C++11中列表初始化也可以适用于new表达式中
    int *pa = new int[4]{
    
    0};
    return 0;
}

When creating an object, you can also use the list initialization method to call the constructor initialization

class Date {
    
    
public:
    Date(int year, int month, int day)
        : _year(year), _month(month), _day(day) {
    
    
        cout << "Date(int year, int month, int day)" << endl;
    }

private:
    int _year;
    int _month;
    int _day;
};

int main() {
    
    
    Date d1(2022, 1, 1);// old style()
    // C++11支持的列表初始化,这里会调用构造函数初始化
    Date d2{
    
    2022, 1, 2};//new style{}
    Date d3 = {
    
    2022, 1, 3};
    return 0;
}

When list initialization is initialized, if type truncation occurs, a warning or error will be reported

#include <iostream>

using namespace std;

int main() {
    
    


    short c = 65535;
    short d{
    
    65535};
    return 0;
}
//narrowing conversion of '65535' from 'int' to 'short int' inside { } [-Wnarrowing]

1.2 std::initializer_list

std::initializer_listis a class template introduced in the C++11 standard library to simplify the handling of initialization lists. It allows passing a set of values ​​in the form of an initializer list in a function or constructor and accessing these values ​​in a uniform way on the receiving end.

std::initializer_listProvides an easy way to access elements in the initialization list by overloading the constructor and the iterator interface. It can be used to define function parameters, constructor parameters, and other container classes that accept initializer lists.

What is the type of std::initializer_list

void test() {
    
    
    auto i1 = {
    
     10, 20, 30, 40, 50, 60, 70 };
    auto i2 = {
    
     10, 20, 30 };
    cout << typeid(i1).name() << endl;  //class std::initializer_list<int>
    cout << typeid(i2).name() << endl;  //class std::initializer_list<int>
}

std::initializer_list features and usage

  1. Constructor and member functions: std::initializer_list is a template class that accepts elements of any type and provides constructors and member functions to handle initialization lists. These functions include:

    • initializer_list(): The default constructor, which creates an empty initializer list.

    • initializer_list(size_type count, const T& value): Constructor that creates an initialization list containing the specified number of elements, each of which is a given value.

    • const T* begin() const: Returns a pointer to the first element in the initialization list.

    • const T* end() const: Returns a pointer to the next position of the last element in the initialization list.

    • size_type size() const: Returns the number of elements in the initialization list.

  2. Syntax usage: The syntax for using std::initializer_listis similar to using curly braces {}to initialize a list. For example:

    std::initializer_list<int> myList = {
          
          1, 2, 3, 4, 5};
    
  3. Function parameters: can be std::initializer_listused as parameters of a function to pass a set of values ​​when the function is called. For example:

    void myFunction(std::initializer_list<int> values) {
          
          
        // 使用初始化列表中的值
        for (const auto& value : values) {
          
          
            // 处理每个值
        }
    }
    
    // 调用函数
    myFunction({
          
          1, 2, 3, 4, 5});
    
  4. Constructor Parameters: The constructor of a class can accept std::initializer_listparameters for initialization using an initialization list when the object is created. For example:

    class MyClass {
          
          
    public:
        MyClass(std::initializer_list<int> values) {
          
          
            // 使用初始化列表中的值
            for (const auto& value : values) {
          
          
                // 处理每个值
            }
        }
    };
    
    // 创建对象时使用初始化列表
    MyClass obj = {
          
          1, 2, 3, 4, 5};
    

std::initializer_list usage scenarios

std::initializer_listGenerally, it is used as a parameter of the constructor. C++11 adds a std::initializer_listconstructor as a parameter to many containers in STL, so that it is more convenient to initialize the container object. It can also be used as a parameter of operator=, so that it can be assigned with curly braces

void test() {
    
    
    auto i1 = {
    
     10, 20, 30, 40, 50, 60, 70 };
    auto i2 = {
    
     10, 20, 30 };

    initializer_list<int>::iterator it1 = i1.begin();
    initializer_list<int>::iterator it2 = i2.begin();
    cout << it1 << endl;   //00000053083EF1F8
    cout << it2 << endl;   //00000053083EF268
    
    initializer_list<int> i3 = {
    
     10,20,30,40,50,60,70,80 };
    initializer_list<int>::iterator it3 = i3.begin();
    cout << it3 << endl;   //00000053083EF308
}
void test2() {
    
    
	Date d1(2023,5,20);
	Date d2(2023,5,21);
	
	//initializer_list<Date>  从C++11开始常量数组会被识别成initializer_list
	vector<Date> v1 = {
    
     d1,d2 };   
	vector<Date> v2 = {
    
     Date(2023,5,20),Date(2023,5,21) };
	vector<Date> v3 = {
    
     {
    
    2023,5,20},{
    
    2023,5,21} };

	map<string, string> dict = {
    
     {
    
    "sort","排序"},{
    
    "string","字符串"},{
    
    "Date","日期"}};
	pair<string, string> kv1 = {
    
     "Date","日期" };
	pair<string, string> kv2{
    
     "Date","日期" };
}

std::initializer_listThe introduction of makes working with initializer lists in C++ more convenient and consistent. Its use in function parameters and constructor parameters provides a concise and flexible way of manipulating a set of values.

Let the simulated vector also support {} initialization and assignment

template<class T>
class vector {
    
    
public:
	typedef T* iterator;
	vector(initializer_list<T> l)
	{
    
    
		_start = new T[l.size()];
		_finish = _start + l.size();
		_endofstorage = _start + l.size();
		iterator vit = _start;
        //内嵌类型需要加上typename
		typename initializer_list<T>::iterator lit = l.begin();
		while (lit != l.end())
		{
    
    
			*vit++ = *lit++;
		}
		//for (auto e : l)
		//   *vit++ = e;
	}

	vector<T>& operator=(initializer_list<T> l) {
    
    
		vector<T> tmp(l);
		std::swap(_start, tmp._start);
		std::swap(_finish, tmp._finish);
		std::swap(_endofstorage, tmp._endofstorage);
		return *this;
	}
private:
	iterator _start;
	iterator _finish;
	iterator _endofstorage;
};

2. Declaration

2.1 auto

Before C++11 (same as C language), autoit was a storage class specifier used to indicate that the variable has automatic storage duration. When declaring a variable inside a function, if no storage-class specifier is explicitly specified, it defaults to autothe storage class.

autoStorage class means that variables are automatically created when they enter their scope and destroyed when they leave their scope. autoThis is the default behavior of variables in C, so it's uncommon to use the keyword in C.

But the local variable defined in the local field is the automatic storage type by default, so auto is of little value. In C++11, the original usage of auto is discarded, and it is used to realize automatic type inference. This requires explicit initialization, which lets the compiler set the type of the defining object to the type of the initialization value.

When you use the auto keyword, the compiler analyzes the initialization expression on the right side of the assignment operator (=) and determines the appropriate type for the variable. Then, the type is deduced and assigned to the variable.

Here is an example illustrating the use of auto:

void test(){
    
    
    auto x = 42;  // x 被推导为 int 类型
	auto y = 3.14;  // y 被推导为 double 类型
	auto z = "Hello, World!";  // z 被推导为 const char* 类型

	// 你也可以在更复杂的类型中使用 auto,比如迭代器:
	std::vector<int> numbers = {
    
    1, 2, 3, 4, 5};
	auto it = numbers.begin();  // it 被推导为 std::vector<int>::iterator 类型

	int i = 10;
	auto p = &i;
	auto pf = strcpy;

	cout << typeid(p).name() << endl;  //int * __ptr64
	cout << typeid(pf).name() << endl;  //char * __ptr64 (__cdecl*)(char * __ptr64,char const * __ptr64)
}

2.2 decltype

decltypeIt is a type deduction mechanism introduced by C++11 . It is used to deduce the type of a variable or expression from an expression. The type of an expression is evaluated at compile time and used as part of the return type of a variable or function.

decltypeThe syntax for is as follows:

decltype(expression)

Among them, expressionis an expression, which can be a variable name, function call, type conversion, etc.

decltypeworks like this:

  • If expressionis an identifier (variable name), decltype(expression)the type of the identifier will be deduced.
  • If expressionis a function call, decltype(expression)the return type of the function call will be deduced.
  • If expressionis an expression, decltype(expression)the type of the expression will be deduced.

Here are some examples to illustrate decltypethe usage of :

void test(){
    
    
    int x = 5;
	decltype(x) y = 10;  // 推导出 y 的类型为 int

	int foo();
	decltype(foo()) result = foo();  // 推导出 result 的类型为 foo() 函数的返回类型

	int a = 1;
	int b = 2;
	decltype(a + b) c = a + b;  // 推导出 c 的类型为 int,表达式 a + b 的类型为 int
}

decltypeA common use of is in template programming, where it can be used to deduce types from function return values ​​or expressions and use them as template parameters or variable types for other types.

It should be noted that decltypethe expression is not executed, it is only used to deduce the type of the expression. Also, decltypededuced types may preserve reference and cv-qualifiers (const and volatile).

Some usage scenarios of decltype

template<class T1, class T2>
void F(T1 t1, T2 t2) {
    
    
    decltype(t1 * t2) ret;
    cout << typeid(ret).name() << endl;   //int
}


int main() {
    
    
    const int x = 1;
    double y = 2.2;
    decltype(x * y) ret;// ret的类型是double
    decltype(&x) p;     // p的类型是int*
    cout << typeid(ret).name() << endl;   //double
    cout << typeid(p).name() << endl;     //int const * __ptr64
    F(1, 'a');
    
    // vector存储的类型跟x*y表达式返回值类型一致
	// decltype推导表达式类型,用这个类型实例化模板参数或者定义对象
	vector<decltype(x* y)> v;

    return 0;
}

2.3 nullptr

nullptris a null pointer constant used to represent a null pointer. It is a new feature introduced by the C++11 standard, which aims to solve some ambiguity and ambiguity problems of NULL in C++.

In C++, pointers are usually used to point to objects or functions in memory. Whereas, a null pointer means that the pointer does not point to any valid object or function. In previous versions of C++, NULLmacros were usually used to represent null pointers, which were usually defined as 0 or (void*)0. However, such a definition may cause ambiguity in some cases, since 0 may be used to represent the zero value of integer types.

To solve this problem, C++11 introduced nullptrthe keyword, which is an explicit null pointer constant. Use nullptrto unambiguously indicate that a pointer is null without confusion with the integer 0. This avoids some programming errors and ambiguities.

The syntax used nullptris very simple, just assign it directly to the pointer variable, for example:

int* ptr = nullptr;  // 整型指针指向空

nullptrCan also be used for comparison operations with other pointers. When compared against other pointers, nullptrthe result of will be true (i.e. the pointer is null). For example:

int* ptr = nullptr;
if (ptr == nullptr) {
    
    
    // 指针为空
}

nullptrIt is a null pointer constant introduced by C++11, which is used to clearly represent the null pointer without causing ambiguity. It provides better type safety and code clarity, and is the recommended representation of null pointers in modern C++ programming.

3. Range for loop

3.1 Syntax of range for

In C++98, if you want to traverse an array, you can do it in the following way:

void TestFor() {
    
    
    int array[] = {
    
    1, 2, 3, 4, 5};
    for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
        array[i] *= 2;
    for (int *p = array; p < array + sizeof(array) / sizeof(array[0]); ++p)
        cout << *p << endl;
}

For a ranged collection , it is redundant and sometimes error-prone for the programmer to specify the range of the loop. Therefore, range-based for loops were introduced in C++11. The parentheses after the for loop are divided into two parts by the colon ":": the first part is the variable used for iteration in the range, and the second part represents the range to be iterated.

void TestFor() {
    
    
    int array[] = {
    
    1, 2, 3, 4, 5};
    for (auto &e: array)
        e *= 2;
    for (auto e: array)
        cout << e << " ";
}

Note: Similar to ordinary loops, you can use continue to end this loop, or use break to jump out of the entire loop.

3.2 Conditions of use of scope for

1. The range of for loop iterations must be definite

**For an array, it is the range of the first element and the last element in the array; **For a class, methods of begin and end should be provided, and begin and end are the range of for loop iterations.

Note: The following code is problematic because the scope of for is uncertain

void TestFor(int array[]) {
    
    
    for (auto &e: array)
        cout << e << endl;
}

2. The iterated object must implement ++ and == operations .

4. Changes in STL

C++11 introduces some important improvements and new features, as well as some changes and enhancements to the STL (Standard Template Library). The following are the main changes in STL after C++11:

  1. Move Semantics: C++11 introduces Rvalue references, Move constructors, and Move assignment operators. These features enable STL containers and algorithms to manage resources more efficiently and provide better support for movable types such as std::unique_ptr and std::shared_ptr.
  2. New container types: C++11 introduces two new container types:
    • std::array: is a fixed-size array container that provides a safer and more convenient interface than C-style arrays.
    • std::unordered_XXX: introduces hash containers such as std::unordered_map and std::unordered_set to provide fast hash-based lookup and insertion operations.
  3. New algorithms: C++11 introduces some new algorithms to enrich the functions of STL:
    • std::move, std::move_backward: used to move elements.
    • std::copy_if, std::move_if: Copy or move elements based on conditions.
    • std::find_if_not: Finds the first element that does not satisfy the given condition.
    • std::is_sorted, std::is_partitioned: Used to check whether a sequence is sorted or partitioned.
    • std::all_of, std::any_of, std::none_of: Used to check whether elements in a sequence satisfy a certain condition.
  4. Lambda expressions: C++11 introduces lambda expressions, which allow anonymous functions to be written in a more compact and convenient way in STL algorithms. Using lambda expressions, you can more easily write custom predicates (predicates) and other function objects.
  5. Concurrent programming support: C++11 introduces concurrent programming primitives such as std::thread and std::mutex, making STL easier to use in a multi-threaded environment. In addition, atomic operation types such as std::atomic and std::atomic_flag are introduced to implement lock-free algorithms.
  6. New standard library components: C++11 introduces some new standard library components, such as:
    • std::tuple: A tuple used to represent a fixed number of heterogeneous values.
    • std::chrono: Provides time and date types and operations.
    • std::regex: A library that supports regular expressions.

These changes and enhancements make the post-C++11 STL more powerful, more efficient, and provide more tools and options to handle the demands of modern C++ programming.

Guess you like

Origin blog.csdn.net/ikun66666/article/details/131314726