[C++] Initial C++ (2)

Table of contents

1 Inline function

1.1 Inline function concept

​Edit 1.2 Features of inline functions

2 auto keyword (C++11)

2.1 The concept of auto

2.2 Auto usage rules 

2.2.1 Combining auto with pointers and references

2.2.2 Define multiple variables on the same line

2.3 Scenarios where auto cannot be used

3 range-based for loop (C++11)

3.1 range for syntax

3.2 Conditions of use of scope for

4 pointer null value nullptr (C++11)


Past review: [C++] Initial C++ (1)


1 Inline function

1.1 Inline function concept

In C++, a function modified with inline is called an inline function. When compiling, the C++ compiler will expand it at the place where the inline function is called. There is no overhead for function calling to create a stack frame. Inline functions can improve the efficiency of program operation.

Inline functions are designed to make up for the shortcomings of C language macros. Macros in C language have three obvious disadvantages:

        1. It is inconvenient to debug macros. (Because it was replaced in the precompilation stage)
        2. The code has poor readability, poor maintainability, and is easy to misuse.
        3. There is no type safety check.

So C++ adopted a new method to improve this situation:

        1. For constant definition, use const enum instead;
        2. For short function definition, use inline function

Let's first take a look at how inline functions are implemented at the bottom:

The following is the situation without inline modification:

The following is the case of inline modification:

From the assembly code, it can be found that adding the inline keyword before the function will change it into an inline function. During compilation, the compiler will replace the function call with the function body, which can save the cost of calling for simple functions.

Here's how to check if an inline function has been replaced:

  1. In release mode, check whether call Add exists in the assembly code generated by the compiler
  2. In debug mode, the compiler needs to be set, otherwise it will not expand (because in debug mode, the compiler will not optimize the code by default, and some parameters need to be modified)

The following gives the method of modifying parameters in debug mode to view inline functions in the vs environment. The following two parameters need to be modified:

1.2 Characteristics of Inline Functions

Inline functions have three main characteristics:

  1. Inline is a way of exchanging space for time. If the compiler treats the function as an inline function, it will replace the function call with the function body during the compilation phase. Disadvantage: It may increase the size of the target file, resulting in code expansion; Advantage: Less call overhead, improve the efficiency of program operation;
  2. Inline is just a suggestion for the compiler. Different compilers may have different inline implementation mechanisms. The general
    suggestion : make the function smaller (that is, the function is not very long, there is no exact statement, it depends on the internal implementation of the compiler), Functions that are not recursive and frequently called should be inlined, otherwise the compiler will ignore the inline feature. In other words, an inline function is just a request to the compiler, which the compiler can choose to ignore;
  3. Inline does not recommend separation of declaration and definition, which will lead to link errors. Because the inline is expanded, there is no function address to enter the symbol table, and the link link will not find the address of the function and report an error.

2 auto keyword (C++11)

2.1 The concept of auto

The meaning of auto in early C/C+ is: variables modified with auto are local variables with automatic storage, that is, the early auto keyword can only be used to modify local variables, but since the default value of all non-global variables is stipulated After it is assumed to be auto, almost no one uses auto.

In C++11, the auto keyword is used to automatically deduce the actual type of an expression or variable, and is a type indicator .

As the program becomes more and more complex, many very complex types are often used in the program, mainly in two aspects:

  1. type is hard to spell;
  2. Ambiguity leads to error-prone.

Such as the following procedure:

#include <string>
#include <map>
int main()
{
    std::map<std::string, std::string> m{ { "apple", "苹果" }, { "orange"
    ,"橙子" }, {"pear","梨"} };
    std::map<std::string, std::string>::iterator it = m.begin();
    while (it != m.end())
    {
    //....
    }
    return 0;
}

This is the first case we mentioned. Among them, std::map<std::string, std::string>::iterator is a type, which seems to be very difficult to spell and very inconvenient.

In the C language, it can be renamed by using typedef, but using typedef will also cause new problems.

typedef char* pstring;
int main()
{
    const pstring p1; // 编译成功还是失败?
    const pstring* p2; // 编译成功还是失败?
    return 0;
}

In the above program, we rename char* to a new type pstring.

For p1, its type is pstring, and it is modified with const. Direct compilation will report an error

 This is because p1 is directly modified by const, which is a constant variable, and the creation of constant variables must be initialized.

For p2, the compiler will not report an error, because p2 is a pointer pointing to an object of type pstring. const directly modifies pstring instead of p2.

At the same time, when programming, it is often necessary to assign the value of an expression to a variable, which requires that the type of the expression be clearly known when declaring the variable. However, sometimes it is not so easy to do this.

In order to solve the above problems, in C++11, the standard committee gave auto a brand-new meaning: auto is no longer a storage type indicator, but as a new type indicator to indicate the compiler, auto declaration Variables must be inferred by the compiler at compile time.

In layman's terms, the new auto is more "intelligent" and can automatically analyze the type according to the specific situation. Therefore, special attention should be paid when using auto:

It must be initialized, and the compiler needs to deduce the actual type of auto based on the initialization expression during the compilation phase. Therefore, auto is not a "type" declaration, but a "placeholder" when the type is declared. The compiler will replace auto with the actual type of the variable during compilation.

2.2 Auto usage rules 

2.2.1 Combining auto with pointers and references

When using auto to declare a pointer type, there is no difference between using auto and auto*, and the compiler will handle it automatically. But when using auto to declare the reference type, you must add &.

int main()
{
    int x = 10;
    auto a = &x;
    auto* b = &x;
    auto& c = x;
    cout << typeid(a).name() << endl;
    cout << typeid(b).name() << endl;
    cout << typeid(c).name() << endl;
    *a = 20;
    *b = 30;
    c = 40;
    return 0;
}

2.2.2 Define multiple variables on the same line

When declaring multiple variables on the same line, these variables must be of the same type , otherwise the compiler will report an error, because the compiler actually only deduces the first type, and then defines other variables with the deduced type.

void TestAuto()
{
    auto a = 1, b = 2;//该行代码编译成功
    auto c = 3, d = 4.0; // 该行代码会编译失败,因为c和d的初始化表达式类型不同
}

2.3 Scenarios where auto cannot be used

1. Cannot be used as a function parameter

// 此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导
void TestAuto(auto a)
{}

2. Cannot be used directly to declare arrays

3. In order to avoid confusion with auto in C++98, C++11 only retains the usage of auto as a type indicator.

3 range-based for loop (C++11)

3.1 range for syntax

In C++98, iterating over an array can be done as follows:

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
	//依次取数组中的数据赋值给e
	//自动判断结束
	//自动迭代
    for(auto& e : array)
        e *= 2;
    for(auto e : array)
        cout << e << " ";
    return 0;
}

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.

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

4 pointer null value nullptr (C++11)

In C++98, we always use NULL to describe the null pointer , but this method is problematic. NULL is actually a macro. In the traditional C header file (stddef.h), you can see the following code:

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

As you can see, NULL may be defined as the literal constant 0, or as a constant of an untyped pointer (void*). No matter what
kind of definition is adopted, some troubles will inevitably be encountered when using pointers of null values, such as the following program:

void f(int)
{
    cout<<"f(int)"<<endl;
}
void f(int*)
{
    cout<<"f(int*)"<<endl;
}
int main()
{
    f(0);
    f(NULL);
    f((int*)NULL);
    return 0;
}

operation result:

The original intention of the program is to call the pointer version of the f(int*) function through f(NULL), but because NULL is defined as 0, the compiler replaces NULL with an integer constant 0, so NULL is not called as a pointer. If you want to use it as a pointer, you must cast it. This is a pitfall of NULL, which is a bug in early C++.

So in C++11, nullptr is used to fill this pit. The value of nullptr is also 0, but its type is void*. In today's C++ programming, nullptr is preferred instead of NULL.

Notice:

1. When using nullptr to represent the null value of the pointer, there is no need to include the header file, because nullptr is introduced as a new keyword in C++11
.
2. In C++11, sizeof(nullptr) and sizeof((void*)0) occupy the same number of bytes.
3. In order to improve the robustness of the code, it is recommended to use nullptr when representing the null value of the pointer.

Guess you like

Origin blog.csdn.net/fbzhl/article/details/131971440