Introduction to C++ (2)—Function overloading and references

Table of contents

1. Function overloading

1. Different parameter types

2. The number of parameters is different

3. The order of parameters is different. 

4. How to distinguish function overloading in links

2. Quote

1. Rules

2. Characteristics

3. Usage scenarios

Make parameters

do return value

4. Often cited

5. Comparison of efficiency of passing by value and passing by reference 

6. The difference between references and pointers

Introduction Part 3: Inline functions, auto, range for, nullptr


Continued from the previous sectionIntroduction to C++ (1)—namespace, default parameters

1. Function overloading

Function overloading: is a special case of function, C++ allows in the same scopeDeclare several functions of the same name with similar functions. These functions of the same name The formal parameter list (parameter number or type or type order) is different, which is often used to deal with the problem of different data types when implementing similar functions.

 1. Different parameter types

There are two add functions in the code below, and their parameter types are different. When we call the add function, the add function will automatically determine which add function to call based on the type of the parameters passed in.

int add(int a, int b)
{
	cout << a + b << endl;
	return 0;
}

double add(double a, double b)
{
	cout << a + b << endl;
	return 0;
}
int main()
{
	add(2, 3);
	add(2.2, 3.3);
	return 0;
}

When we pass in an integer type, we call the add function that calculates the integer, and when we pass in a floating point type, we call the add function that calculates the floating point type.

 2.The number of parameters is different

Functions are automatically matched based on the number of parameters.​ 

void f(int a,int b)
{
	cout << a+b << endl;
}
void f(int a)
{
	cout << a << endl;
}
int main()
{
	f(6+6);
	f(6);
	return 0;
}

 

 3.The parameter order is different 

Functions are automatically matched based on the order of different types.

void f(int a, char b)
{
	cout << a << b << endl;
}
void f(char b, int a)
{
	cout << b << a <<endl;
}
int main()
{
	f('a', 6);
	f(6, 'a');
	return 0;
}

 

 Note: Function overloading does not slow down because matching corresponding functions is done at compile time.

4. How to distinguish function overloading in links


int Add(int left, int right)
{
	cout << "int Add(int left, int right)" << endl;
	return left + right;
}

double Add(double left, double right)
{
	cout << "double Add(double left, double right)" << endl;
	return left + right;
}

int main()
{
	Add(1, 2);     
	Add(1.1, 2.2); 
	return 0;
}

Go to disassembly in VS debugging, and you can see that the Add function is called through call Add. So how does the linker distinguish which one to link?

Answer: The compiler will modify the function name. Different compilers have different modification rules for function names. Since the rules of VS are too complicated, we check the assembly instructions of the C++ function in the g++ compiler under Linux.

Under Linux, after compilation with g++ is completed, the modification of the function name changes. The compiler adds the function parameter type information to the modified name. We can also find that its modification rule is _Z + function name length + function name + The first letter of the parameter type.

  • From here, we understand that C language cannot support overloading because functions with the same name cannot be distinguished. C++ is distinguished by function modification rules. As long as the parameters are different, the modified names are different, and overloading is supported.
  • .If the function names and parameters of two functions are the same, different return values ​​do not constitute overloading, because the compiler cannot distinguish them when calling.

2. Quote

Reference is not a new definition of a variable, but is an alias for an existing variable. The compiler will not allocate memory space for the reference variable, it shares the same memory space as the variable it refers to.

1. Rules

Type & reference variable name (object name) = reference entity; 

int main()
{
	int i = 0;
	int& k = i;//引用

	int j = i;

	cout << &i << endl;
	cout << &k << endl;
	cout << &j << endl;

	return 0;
}

It can be found from the addresses of the three that references are just aliases for variables.​ 

What if we modify the reference?​ 

int main()
{
	int i = 0;
	int& k = i;//引用
	int j = i;

	k++;
	j++;
	cout << i << " " << k << " " << j << endl;

	return 0;
}

According to the output result, if the reference value is modified, the corresponding reference variable will also be modified.

 

 2. Characteristics

  1. References must be initialized when defined
    int& a; // 该条语句编译时会出错
  2. A variable can have multiple references
    int& a1 = a;//给a取别名
    int& a2 = a;//给a取别名
    int& a3 = a2;//给a2(a的别名)取别名也是可以的
  3. Once a reference refers to one entity, it cannot refer to other entities.
    int a = 5;
    int b = 10;
    int& ref = a; // 将ref引用绑定到a上
    ref = b; // 此时a的值为10,而不是5,ref仍然绑定到a上
    int& ref = b; // 错误:ref已经绑定到a上,不能再绑定到其他实体上

 Let’s look at the following code:

int main()
{
	int i = 0;
	int& k = i;//引用
	int j = i;

	k++;
	j++;

	int& m = i;
	int& n = k;
	++n;

	return 0;
}
  • Define variable i to be initialized to 0, define k as a reference to i, and variable j is assigned the value of i.
  • Increment the values ​​of k and j respectively.
  • Define two references to variables i and k respectively, and increment n.

 It can be seen that modifying the referenced value also modifies the value of the referenced variable.

3. Usage scenarios

Make parameters

Usually when writing the function Swap to exchange two variables, the value of the variable is usually modified with the help of a pointer.​ 

void Swap(int* i, int* j)
{
	int tmp = *i;
	*i = *j;
	*j = tmp;
}
int main()
{
	int i = 0, j = 1;
	Swap(&i, &j);
	printf("i=%d,j=%d", i, j);
	return 0;
}

When we master the reference, we can use the reference to pass parameters. The function accepts two integer reference variables x and y as parameters.

void _Swap(int& x, int& y)
{
	int tmp = x;
	x = y;
	y = tmp;
}
int main()
{
	int i = 0, j = 1;
	_Swap(i, j);
	printf("i=%d,j=%d\n", i, j);
	return 0;
}

In the singly linked list we studied before, in order to modify the structure member *next in the pointer to the node structure, we need to use a secondary pointer to receive the address of the pointer in order to modify the content pointed to by the pointer.

However, this time we learned about references. When passing parameters using references, we can only pass the parameter pointer without the address.

typedef struct Node
{
	struct Node* next;
	int val;
}Node, * PNode;

void PushBack(Node*& phead, int x)
{
	Node* newNode = (Node*)malloc(sizeof(Node));
	if (phead == nullptr)
	{
		phead = newNode;
	}
}

int main()
{
	Node* plist = NULL;
	PushBack(plist, 1);
	PushBack(plist, 2);
	PushBack(plist, 3);

	return 0;
}

We can see that in the PushBack function, a method of combining pointers and references is used. Specifically, in the PushBack function, the parameter phead is a reference pointing to a pointer, which means that we can modify the phead outside the function. Pointer variable plist.

Let’s look at this value-passing method again.


void PushBack(PNode& phead, int x)

 The parameter types of these two functions are different, namely PNode& and Node*&. The difference lies in the way of passing parameters.

  • PNode& in void PushBack(PNode& phead, int x) is a reference type pointing to a pointer, which represents a reference to a pointer to the Node structure.
  • The Node*& in void PushBack(Node*& phead, int x) is a pointer type, which represents a pointer to a pointer to the Node structure.

do return value

Let’s take a look firstConventional functionThe process of return value assignment.

int Count()
{
	int n = 0;
	n++;
	return n;
}
int main()
{
	int ret = Count();
	return 0;
}

In fact, the value of the function return value n is passed to ret with the help of a temporary variable. The temporary variable is usually acted by a register (usually 4/8 bytes). If it is larger, it will be added to the stack of the main function before calling the function. A space is opened in the frame in advance to serve as a temporary variable. When the Count function is called, the return value is copied to the temporary variable of the main function before the call is completed and destroyed, and then the temporary variable of the main function is copied to ret.

The process in the function stack frame is as follows:

 

If n is in the static area, you also need to usetemporary variables to copy the return value to ret.

int Count()
{
	static int n = 0;
	n++;
	return n;
}
int main()
{
	int ret = Count();
	return 0;
}

 The abovementioned global variable n returns by value. Temporary variable is somewhat redundant. Use Passing reference and returning can be solved very well.

Next is a function that uses a reference as the return value type. The return value is used to generate an alias for the return value n for the reference type, which is equivalent to passing the value of n, reducing the process of copying temporary variables, saving time and improving efficiency.

int& Count()
{
	static int n = 0;
	n++;
	return n;
}
int main()
{
	int ret = Count();
	return 0;
}

 As long as going out of the count scope does not affect the life cycle of the variable, you can use return by reference.

Benefits of using reference returns:

  • Reduce copies
  • The caller can modify the returned object

Use reference return conditions:

  • When the function returns, it goes out of the function scope. If the returned object is still there (it has not been returned to the system, such as: static scope, global variables, located in the upper stack frame, malloc, etc.), you can use reference return, If it has been returned to the system, it must be returned by value.

The following code uses reference-related syntax to access and modify array elements. This method can improve the efficiency of the program and make the code more concise and readable.​ 

#define N 10
typedef struct Array
{
	int a[N];
	int size;
}AY;

int& PosAt(AY& ay, int i)
{
	assert(i < N);
	return ay.a[i];
}

int main()
{
	AY ay;
	for (int i = 0; i < N; ++i)
		PosAt(ay, i) = i*10;

	for (int i = 0; i < N; ++i)
		cout << PosAt(ay, i) << " ";

	cout << endl;
	return 0;
}

If returned by reference, the result is undefined.​ 

 Next look at this code:

int& Add(int a, int b)
{
	int c = a + b;
	return c;
}

int main()
{
	int& ret = Add(1, 2);
	Add(3, 4);
	cout << "Add(1, 2) is :" << ret << endl;
	cout << "Add(1, 2) is :" << ret << endl;

	return 0;
}
  • In this code, the Add function returns a reference to the local variable c. However, this is an undefined behavior, because when the function returns, the life cycle of the local variable c ends, and the memory space it occupies may be used by other variables or functions, which means that the returned reference may point to an invalid memory address.
  • In the main function, we assign the return value of the Add function to the ret variable. Here, ret is not a reference to the reference of c, but a new variable whose value is obtained by copying the reference returned by the Add function. However, since the reference returned by the Add function may point to an invalid memory address, the value of ret may be undefined.
  • Then we called the Add function again without storing the return value in any variable. This call may change the value of the memory space occupied by c, thus affecting the value of the ret variable.
  • Finally, we use the cout statement to output the value of the ret variable twice. Because the value of ret may be undefined, these output statements may produce unpredictable results.
  • Overall, this code is problematic because it returns a reference to a local variable, which is undefined behavior. To avoid this problem, we should avoid returning references to local variables, or use dynamically allocated memory, static variables, or global variables to store return values.

4. Often cited

In the process of quoting:

  1. Permissions can be moved horizontally
  2. Permissions can be narrowed
  3. Permissions cannot be enlarged
int Count()
{
	int n = 0;
	n++;
	return n;
}

int main()
{
	int a = 1;
	int& b = a;

	// 指针和引用,赋值/初始化 权限可以缩小,但是不能放大

	// 权限放大
	/*const int c = 2;
	int& d = c;

	const int* p1 = NULL;
	int* p2 = p1;*/

	// 权限保持
	const int c = 2;
	const int& d = c;

	const int* p1 = NULL;
	const int* p2 = p1;

	// 权限缩小
	int x = 1;
	const int& y = x;

	int* p3 = NULL;
	const int* p4 = p3;

    // 权限可以缩小不能放大只适用于指针和引用
    const int m = 1;
	int n = m;//正确的

    //函数传值返回,借助临时变量传值,临时变量不能修改
	const int& ret = Count();//const保持权限一致

    //类型转换都会产生临时变量
	int i = 10;
    cout << (double)i << endl;//显式类型转换
	double dd = i;//隐式类型转换
	const double& rd = i;

	return 0;
} 

5. Comparison of efficiency of passing by value and passing by reference 

#include <time.h>
struct A { int a[10000]; };
void TestFunc1(A a) {}
void TestFunc2(A& a) {}
void TestRefAndValue()
{
	A a;
	// 以值作为函数参数
	size_t begin1 = clock();
	for (size_t i = 0; i < 10000; ++i)
		TestFunc1(a);
	size_t end1 = clock();
	// 以引用作为函数参数
	size_t begin2 = clock();
	for (size_t i = 0; i < 10000; ++i)
		TestFunc2(a);
	size_t end2 = clock();
	// 分别计算两个函数运行结束后的时间
	cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
	cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}
int main()
{
	TestRefAndValue();

	return 0;
}

The speed of passing a reference is less than 1 millisecond, so the displayed time consumption is 0. It can be seen that the efficiency of passing a reference is very high.

In C++, the main reasons why passing by reference is more efficient than passing by value are as follows:

  1. Avoid copy operations: When we pass parameters by value, we actually create a new object in memory, and then copy the value of the original object to the new object . This copy operation can be very resource intensive, especially when the object is large. When passing parameters by reference, we only pass the address of the object without copying it, so it is more efficient.

  2. Save memory space: Because pass-by-reference only passes the address of the object instead of copying the entire object, it uses less memory space.

  3. The original object can be modified: When we need to modify the original object in a function, the parameters must be passed by reference or pointer. If a parameter is passed by value, the function will operate on a copy of the original object and the original object will not be modified.

6. The difference between references and pointers

  1. Concept: A reference is an alias of an existing variable, and they share the same memory space. A pointer is the address of a variable.

  2. Initialization: References must be initialized when defined, and once an entity is referenced, it cannot reference other entities. In contrast, a pointer can point to any entity of the same type at any time, and initialization is not required.

  3. NULL value: There is no NULL reference, but there can be a NULL pointer.

  4. sizeof operator: Use the sizeof operator on a reference, and the result is the size of the reference type; use the sizeof operator on a pointer, and the result is always the number of bytes occupied by the address space. (For example, 4 bytes on a 32-bit platform).

  5. Auto-increment operation: Reference increment means that the referenced entity is increased by 1, while pointer increment means that the pointer is offset backward by the size of the type.

  6. Level: There are multi-level pointers, but no multi-level references.

  7. Accessing entities: Accessing the entity pointed to by the pointer requires explicit dereference, but when accessing the entity pointed to by the reference, the compiler will automatically handle it.

  8. Safety: Compared to pointers, references are relatively safer to use.

  9. In the underlying implementation, the reference actually has space because it is implemented through pointers. But in grammatical concepts, references are treated as aliases and have no independent space.

Introduction Part 3: Inline functions, auto, range for, nullptr

Introduction to C++ (3)—inline functions, auto, range for, nullptr

Guess you like

Origin blog.csdn.net/m0_73800602/article/details/134407893