[Introduction to C++ 4] Learn C++ inline functions | auto keyword | range-based for loop (C++11) | pointer null value nullptr (C++11)


Insert image description here

1. Inline functions

In C language, we have learned about macro definitions. In fact, during the learning process, we thought that macro definitions are very good, but we will explain the shortcomings of macros below.


1.1 Preface (Introduction to inline functions)

①Write a macro definition of the Add function

#define _CRT_SECURE_NO_WARNINGS 1

#define ADD(int x, int y) return x + y;
#define ADD(x, y) return x + y;
#define ADD(x, y) x + y;
#define ADD(x, y) (x + y)
#define ADD(x, y) (x) + (y)
#define ADD(x, y) ((x) + (y));
#define ADD(x, y) ((x) + (y))//正确写法
int main()
{
    
    
	int ret = ADD(2, 3)*5;//加外面()理由
	//为什么要里面加(),总结下面图片可以看出,
	//如果是表达式涉及优先级,就会出现问题
	return 0;
}

Summary:
1. Macros are a kind of replacement
2. ‘’ cannot be added after the macro definition; ”
Insert image description here


②Disadvantages of macros

1.容易出错,语法细节多
2.不能调试(宏在预处理阶段就被处理(替换)了),如下图
Insert image description here
3.没有类型安全的检查

③C++’s attitude towards macros

Replace macros with enum const inline

1.enum const 替代宏常量
2. inline 替代宏函数

#define ADD(x, y) ((x) + (y))

inline int Add(int x, int y)
{
    
    
	int c = x + y;
	return c;
}

int main()
{
    
    
	int ret1 = Add(1, 2);
	// 内联函数优点:不用建立栈帧,提高效率
	
	int ret2 = ADD(1, 2);

	return 0;
}


1.2 Inline functions

①Concept

以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率。


内联函数几乎克服了上面说的宏的缺点

注意:
Inline functions will not be expanded by default, we need to set it (vs2022)
Insert image description here


②Inline function characteristics

  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 a function body during the compilation phase.
    缺陷: may cause The target file becomes larger
    优势:, which reduces the calling overhead and improves the program running efficiency.
  2. Inline is just a suggestion 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), not Functions that are 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":
    内联说明只是向编译器发出的一个请求,编译器可以选择忽略这个请求。

一般来说,内联机制用于优化规模较小、流程直接、频繁调用的函数。很多编译器都不支持内联递归函数,而且一个75行的函数也不大可能在调用点内联地展开

  1. inline is not recommended声明和定义分离, separation will cause link errors. Because inline is expanded, there is no function address and the link will not be found


2.auto keyword (C++11)

① Thinking about type aliases

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

  1. Type is difficult to spell
  2. Unclear meaning leads to error-prone
#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;
}

std::map<std::string, std::string>::iterator
is a type, but this type is too long and it is easy to make mistakes. Some people may have thought that you can use typedef to alias the type, such as:

#include <string>
#include <map>
typedef std::map<std::string, std::string> Map;
int main()
{
    
    
	Map m{
    
     {
    
     "apple", "苹果" },{
    
     "orange", "橙子" }, {
    
    "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;    // 编译成功还是失败?
 const pstring* p2;   // 编译成功还是失败?
 return 0;
}

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.因此C++11给auto赋予了新的含义


②Introduction to auto

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; 无法通过编译,使用auto定义变量时必须对其进行初始化
	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.


③ Rules for using auto

  1. Auto is used in combination with pointers and references. When using auto to declare pointer types, there is no difference between using auto and auto*, but when using auto to declare reference types, 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;
}
  1. Defining 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 the first type is deduced, and the other variables are defined using the deduced type.
void TestAuto()
{
    
    
    auto a = 1, b = 2; 
    auto c = 3, d = 4.0;  // 该行代码会编译失败,因为c和d的初始化表达式类型不同
}

④ Scenarios that cannot be derived by auto

  1. auto cannot be used as a function parameter
// 此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导
void TestAuto(auto a)
{
    
    }
  1. auto cannot be used directly to declare arrays
void TestAuto()
{
    
    
    int a[] = {
    
    1,2,3};
    auto b[] = {
    
    456};
}
  1. To avoid confusion with auto in C++98, C++11 only retains the use of auto as a type designator.
  2. The most common advantageous use of auto in practice is to be used in conjunction with the new for loop provided by C++11, which will be discussed later, and
    lambda expressions, etc.

3. Range-based for loop (C++11)

① The syntax of range for

In C++98, if you want to traverse 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. 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 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& e : array)
     e *= 2;
for(auto e : array)
     cout << e << " ";
return 0;
}

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


② Conditions for using range for

  1. The range of for loop iteration must be certain

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.
注意:以下代码就有问题,因为for的范围不确定

void TestFor(int array[])
{
    
    
    for(auto& e : array)
        cout<< e <<endl;
}
  1. The iterated object must implement the ++ 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 (C++11)

①Pointer null value in C++98

In good C/C++ programming habits, when declaring a variable, it is best to give the variable an appropriate initial value, 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-valued pointers, such as:

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;
}

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, it goes against the original intention of the program.

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.

注意:

1. 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.
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 subsequently representing a pointer null value.

Insert image description here
Insert image description here

Guess you like

Origin blog.csdn.net/qq_58286439/article/details/134252772