[C++] Inline functions, auto keyword, range-based for loop, pointer null value nullptr

Fan Zimu: Personal homepage

Personal columns: “C Language” “Data Structure” “Lanqiao Cup Test Questions” “LeetCode Question Notes” “Practical Training Projects” “C++”

Every day without dancing is a betrayal of life

Table of Contents

Preface

1. Inline functions

1.1 Concept

1.2 Features

2.auto keyword

2.1 Thinking about type aliases

2.2Introduction to auto

2.3Usage details of auto

2.4 Scenarios where auto cannot be used

3. Range-based for loop

3.1 Usage of range for

3.2 Conditions for using scope for

4. Pointer null value nullptr


Foreword

This article is the first lesson before entering the study of classes and objects. It is also the last introductory knowledge of C++ that I will share with you. You can save it for easy memory.

Welcome everyone to collect it so that you can quickly find ideas when doing questions in the future. Clever methods can get twice the result with half the effort.

================================================ ==========================

GITEE related code:fanfei_c’s warehouse

================================================ ==========================

1.Inline function

1.1 concept

Functions modified with inline are called inline functions. When compiling, the C++ compiler will expand the place where the inline function is called. There is no function call to establish a stack frame. Overhead, inline functions improve the efficiency of program operation.

Observe the assembly code of some common functions:

Those of us who have studied function stack frames must be very familiar with it. Here we call the address of add.

The purpose of the inline function is to omit this process and speed up the program.

If you add the inline keyword before the above function to change it to an inline function, the compiler will replace the function call with the function body during compilation.

Let’s see how to view this process

View via:

  • In release mode, check whether call Add exists in the assembly code generated by the compiler.
  • In debug mode, the compiler needs to be set, otherwise it will not be expanded (because in debug mode, the compiler will not optimize the code by default. The setting method of vs2013 is given below).

1.2 Feature

1. Inline is a method of exchanging space for time. If the compiler treats a function as an inline function, it will replace the function call with the function body during the compilation phase. Defect: it may cause the object file to Bigger, advantages: less calling overhead and improved program running efficiency.
2. Inline is just asuggestion for the compiler. Different compilers may have different inline implementation mechanisms. The general suggestion is: make the function smaller (that is, the function is not very long) , there is no accurate statement, it depends on the internal implementation of the compiler), functions that are not recursive and frequently called are inline modified, otherwise the compiler will ignore the inline feature.

The picture below shows the suggestions on inline in the fifth edition of “C++ Prime”:

3. Inline does not recommend the separation of declarations and definitions, as separation will cause link errors. Because inline is expanded, there is no function address and the link will not be found.

for example:

// F.h
#include <iostream>
using namespace std;
inline void f(int i);
//F.cpp
#include "F.h"
void f(int i)
{
cout << i << endl;
}
// main.cpp
#include "F.h"
int main()
{
f(10);
return 0;
}
// Link error: main.obj: error LNK2019: Unresolved external symbol "void __cdecl f(int)" (?
//f@@YAXH@Z), this symbol is referenced in function _main

[Interview questions]

What are the pros and cons of macros?
advantage:
1. Enhance code reusability.
2. Improve performance.
shortcoming:
1. It is inconvenient to debug macros. (Because of the replacement during the pre-compilation stage)
2. This results in poor code readability, poor maintainability, and easy misuse.
3. No type safety checks.

What are the technical alternatives to macros in C++?
1. Constant definition replace with const enum
2. Short function definition, use inline function instead

2.auto keyword

2.1 Type Alias Thinking

As the program becomes more and more complex, the types used in the program become more and more complex, often reflected in:

  • Type is difficult to spell
  • Unclear meaning leads to error-prone
#include <string>
#include <map>
int main()
{
std::map<std::string, std::string> m{ { "apple", "apple" }, { "orange", "orange" },
{"pear","pear"} };
std::map<std::string, std::string>::iterator it = m.begin();
while (it != m.end())
{
//....
}
return 0;
}

std::map::iterator is a type, but the type is too long and it is easy to make mistakes. Smart students may have thought that you can use typedef to give aliases to types, such as:

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

Using typedef to alias types can indeed simplify the code, but typedef will encounter new problems:

typedef char* pstring;
int main()
{
const pstring p1; // Was the compilation successful or failed?
const pstring* p2; // Was the compilation successful or failed?
return 0;
}

analyze:

  • const pstring p1;: This line of code defines a constant pointer named p1, pointing to char type data. This means that p1 is a constant pointer and the data pointed to cannot be modified. The compiler will issue a warning because p1 is a constant pointer, but there is no specification that the data it points to is a constant.
  • const pstring* p2;: This line of code defines a pointer named p2, which points to a constant pointer of type pstring. This means that p2 is a pointer to a constant pointer. The compiler will not issue a warning because p2 is a pointer, just a constant pointer to type pstring.

It should be noted that although the compiler may issue warnings or errors, the actual compilation results may vary depending on the compiler. This is because different compilers may handle the const modifier differently.

In summary, depending on how the code is defined and used, the compilation results may have warnings or errors, depending on the compiler implementation. It is recommended that when using typedefs, ensure consistency in definition and use, and follow the compiler’s warnings and error prompts.

That is, 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, so C++11 gives auto a new meaning.

2.2auto introduction

In the early days of C/C++, the meaning of auto was: variables modified with auto are local variables with automatic memory. Unfortunately, no one has ever used it. Can you think about why?
In C++11, the standards committee has given auto a new meaning: auto is no longer a storage type indicator, but serves as a new type indicator to instruct the compiler. Variables declared by auto must be compiled by the compiler. Derived from the period.

int TestAuto()
{
return 10;
}
int main()
{
int a = 10;
auto b = a;
auto c = 'a';
auto d = TestAuto();
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
cout << typeid(d).name() << endl;
//auto e; cannot pass compilation, variables must be initialized when using auto to define them
return 0;
}

Note: When using auto to define a variable, it must be initialized. During the compilation phase, the compiler needs to deduce the actual type of auto based on the initialization expression. Therefore, auto is not a “type” declaration, but a “placeholder” for type declaration. The compiler will replace auto with the actual type of the variable during compilation.

2.3auto usage details

1.auto is used in combination with pointers and references
When using auto to declare a pointer type, there is no difference between using auto and auto*, but when using auto to declare a reference type, you must add & amp;.

for example:

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. Define multiple variables on the same line
When multiple variables are declared 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 uses the deduced type to define other variables.
for example:

void TestAuto()
{
auto a = 1, b = 2;
auto c = 3, d = 4.0; // This line of code will fail to compile because the initialization expression types of c and d are different
}

2.4auto cannot be used in scenarios

1.auto cannot be used as a parameter of a function and returns a value.

//The code here fails to compile. auto cannot be used as a formal parameter type because the compiler cannot deduce the actual type of a.
void TestAuto(auto a)
{}

2. Auto cannot be used directly to declare arrays.

void TestAuto()
{
    int a[] = {1,2,3};
    auto b[] = {4, 5, 6};
}

3. In order to avoid confusion with auto in C++98, C++11 only retains the use of auto as a type indicator.
4. The most common advantage of auto in practice is to be used in conjunction with the new for loop provided by C++11, which will be discussed later, as well as lambda expressions, etc.

5. The real meaning of auto: When defining an object, the type is longer, so it is more convenient to use auto.

3. Range-based for loop

3.1 Usage of range for

In C++98 if you want to iterate over an array, you can do it 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.

So range-based for loops were introduced in C++11.

The brackets after the for loop are divided into two parts by the colon “:”: the first part is the variable in the range used for iteration, and the second part represents the range being iterated.

void TestFor()
{
int array[] = { 1, 2, 3, 4, 5 };
for (auto & amp; e : array)//If you want to modify the value in the array, add a reference & amp;, pointers cannot be used here!
e *= 2;
    // Take the data in the array and assign it to e in sequence, automatically + +, automatic judgment ends
for (auto e : array)//Actual types are available, but auto is recommended
cout << e << " ";
return 0;
}

Note: Similar to a normal loop, you can use continue to end this loop, or break to break out of the entire loop.

3.2 Conditions for use of range for

1. The range of for loop iteration must be determined
For an array, it is the range of the first element and the last element in the array; for a class, the begin and end methods should be provided, and begin and end are the range of the for loop iteration.
Note: There is a problem with the following code because the scope of for is uncertain

void TestFor(int array[])//Pass parameters to get the address of the first element of the array, not an array!
{
for (auto & amp; e : array)//so the range is uncertain
cout << e << endl;
}

2. The iterated object must implement + + and == operations. (As for the issue of iterators, I will talk about it later. I will mention it now. There is no way to explain it clearly. Now everyone can understand it.)

4. Pointer null value nullptr

In good C/C++ programming practice, it is best to give the variable a suitable initial value when declaring a variable, otherwise unpredictable errors may occur, such as uninitialized pointers. If a pointer does not have a legal pointer, we basically initialize it as follows:

void TestPtr()
{
int* p1 = NULL;
int* p2 = 0;
//…
}

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 a literal constant 0, or as a constant of an untyped pointer (void*).

No matter what definition is adopted, you will inevitably encounter some troubles when using null value pointers, such as:

void f(int)
{
cout << "f(int)" << endl;
}
void f(int*)
{
cout << "f(int*)" << endl;
}
int main()
{
    //Call the first function
f(0);
    //The first function is also called, but there is ambiguity at this time. Maybe the programmer thinks that NULL is a null pointer at this time.
f(NULL);//Maybe programmers can only use the following method to call the second function
    //Call the second function
f((int*)NULL);
return 0;
}

In C++98, the literal constant 0 can be either an integer number or an untyped pointer (void*) constant, but the compiler treats it as an integer constant by default. If you want to treat it as To use it in pointer mode, it must be cast to (void *)0.

Note:

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

Let’s start learning classes and objects right away

================================================ ==========================

If you are interested in this series of articles, please continue to follow the blogger’s updates. The blogger will continue to output high-quality content

Bloggers need everyone’s support. Your support is the inexhaustible motivation for my creation

~ Like, favorite + follow ~

================================================ ==========================