Introduction to functions in c language (20,000 words in blood!!!!)

Article directory

function

One: What is a function?

Wikipedia's definition of a function : subroutine One or more statement blocks. It is responsible for completing a specific task and is relatively independent from other code. (2): Generally, there are input parameters and return values, which provide the encapsulation of the process and the hiding of details. These codes are usually integrated as software libraries.


Two: Classification of functions in C language

(1) Library functions: functions provided within the C language.
(2) Self-defined functions: functions written by yourself.

1: library function

(1): The meaning of the existence of library functions:

  1. We know that when we learn C language programming, we can't wait to know the result after writing a code, and want to print the result to our screen to see. At this time, we will frequently use a function:
  2. Print information to the screen in a certain format (printf).
  3. In the process of programming, we will frequently do some string copy work (strcpy).
  4. In programming, we also calculate, and always calculate the operation (pow) of n to the power of k. **
  5. In order to improve the efficiency of our work and prevent the appearance of bugs, we have introduced library functions

(2): Learning and use of library functions

Like the basic functions we described above, they are not business code. In the process of development, every programmer may use it. In order to support portability and improve program efficiency, a series of similar library functions are provided in the basic library of C language, which is convenient for programmers to develop software.

The use of library functions does not need to be memorized, we can find out how they are used.

A website and an app are recommended here

(1) www.cplusplus.com
(2) MSDN (Microsoft Developer Network)
(3) http://en.cppreference.com (English version)
(4) http://zh.cppreference.com (Chinese version)

passed In these ways, we can find their information, such as: function name, formal parameters, required header files and return values ​​and other necessary information.

The languages ​​of these tools are all in English. In the project of learning programming, we need to learn English.
Here are two examples of library functions.

strcpy

char * strcpy ( char * destination, const char * source );

insert image description here

The above English simply explains that the destination is the return value.
The deeper understanding is: After the
strcpy function will copy the data of the source source into the destination space, it will return the starting address of the destination space.

//strcpy的用法
#include<stdio.h>
#include<string.h>
int main()
{
    
    
	char arr1[20] = {
    
     "xxxxxxxxxxxxxxx" };
	char arr2[]   = {
    
     "hello bit" };
	strcpy(arr1, arr2);//arr1为目的地,arr2为源头,为了将arr2数组里面的内容拷贝到arr1里面
	printf("%s\n", arr1);
	return 0;
//arr1与t对齐的后面的x,也就是数组下标为9的x,被改成\0拷贝了过来
//实际上打印的结果为hello bit(\0不显示但存在)
}

insert image description here

memset (memory setting function)

void * memset ( void * ptr, int value, size_t num );

insert image description here
Supplement: This value refers to the number of bytes in num

//memset的用法
#include<stdio.h>
#include<string.h>
int main()
{
    
    
	char arr[] = {
    
     "hello bit" };
	memset(arr, 'x', 5);
	printf("%s\n", arr);
	return 0;
}
//memset中间一定是unsigned char类型,5则表示arr数组里面前五个东西被x所取代

Note:
But a secret that the library function must know is: to use the library function, the header file corresponding to #include must be included.
Here, we will learn the above library functions according to the documents, and the purpose is to master the use of library functions.

2: Custom function

(1): Composition of custom functions

User-defined functions are designed by programmers, and have function names, return types, formal parameters, etc., just like ordinary functions.

The basic structure is as follows:

ret_type fun_name(para1, * )
{
    
    
    statement;//语句项
}
//ret_type 返回类型
//fun_name 函数名
//para1    函数参数

(2): Example questions

Example 1: Write a function to find the maximum value of two integers
//写一个函数找出两个整数的最大值
#include<stdio.h>
int get_max(int x, int y)
{
    
    
    if (x > y)
    {
    
    
        return x;
    }
    else
    {
    
    
        return y;
    }
    //简便的代码,一行代替上面函数体里面的代码
    //return(x > y ? x : y);
}
//从之前笔记一:函数是什么里面的定义得知-
//上述函数就是一个语句块,它负责完成某项特定的任务—求两个数的最大值

int main()
{
    
    
    int a = 0;
    int b = 0;
    scanf("%d %d", &a, &b);
    int c = get_max(a, b);
    printf("%d\n", c);
    return 0;
}
//输入:10 20
//输出:20
Example 2: Write a function to swap the contents of two integer variables
//写一个函数交换两个整型变量的内容

//错误示范
#include<stdio.h>
void swap(int a, int b)
{
    
    
    int temp = 0;
    temp = a;
    a = b;
    b = temp;
}
int main()
{
    
    
    int a = 0;
    int b = 0;
    scanf("%d %d", &a, &b);
    printf("交换前:a=%d,b=%d\n", a, b);
    swap(a, b);
    printf("交换前:a=%d,b=%d\n", a, b);
    return 0;
}
//输入:10 20
//输出:
//交换前:a=10,b=20
//交换后:a=10,b=20
//没有达到相应的效果

Correct code:

//正确代码
#include<stdio.h>
void swap(int* pa, int* pb)
{
    
    
    int temp = 0;
    temp = *pa;
    *pa = *pb;
    *pb = temp;
}
int main()
{
    
    
    int a = 0;
    int b = 0;
    scanf("%d %d", &a, &b);
    printf("交换前:a=%d,b=%d\n", a, b);
    swap(&a, &b);
    printf("交换前:a=%d,b=%d\n", a, b);
    return 0;
}
//输入:10 20
//输出:
//交换前:a=10,b=20
//交换后:a=20,b=10
//

The reason for the error, we can go directly to the page of the transfer application and it will be explained clearly

Three: Parameters in the function

1: Actual parameter (actual parameter)

The actual parameters passed to the function are called actual parameters.
Arguments can be: constants, variables, expressions, functions, etc.
When a function is called, they must all have definite values ​​in order to pass those values ​​to the formal parameters.

For example, the get_max(a,b) and ab of the above code are the actual parameters, and they will be passed to the formal parameters xy in the function respectively.

2: Formal parameters (formal parameters)

Formal parameters are variables in parentheses after the function name.
Formal parameters are only instantiated (allocated memory units) when the function is called, so they are called formal parameters. So formal parameters are only valid within functions.

Remember the following sentence is very important! ! ! ! !
When the function is called, the actual parameter is passed to the formal parameter, and the formal parameter will be a temporary copy of the actual parameter, so the modification of the formal parameter will not affect the actual parameter. Remember the words that
the parameter will be a temporary copy of the actual parameter ! ! !

Four: function call

Functions function by being called. To be called is to be used in other code

1: call by value

The formal parameters and actual parameters of the function occupy different memory blocks respectively, and the modification of the formal parameters will not affect the actual parameters.

So, we can use call by value without changing the function arguments .

Let's take an example to
write a program to calculate the sum of two integers:

#include<stdio.h>
int add(int x, int y)
{
    
    
    return x + y;
}
int main()
{
    
    
    int a = 0;
    int b = 0;
    scanf("%d %d", &a, &b);
    int c = add(a, b);
    printf("%d\n", c);
    return 0;
}

In this program, a and b are actual parameters. We just use a and b to operate without changing the properties of a and b. At this time, we can use the call by value, and then return the value obtained by the operation.

2: call by address

Call by reference is a way to call a function by passing the memory address of a variable created outside the function to the function parameter .
This method of passing parameters allows the function to establish a real connection with the variables outside the function, that is, the function can directly operate the variables outside the function.

Let's use the code above for exchanging two numbers as an example:

#include<stdio.h>
void swap(int* pa, int* pb)
{
    
    
    int temp = 0;
    temp = *pa;
    *pa = *pb;
    *pb = temp;
}
int main()
{
    
    
    int a = 0;
    int b = 0;
    scanf("%d %d", &a, &b);
    printf("交换前:a=%d,b=%d\n", a, b);
    swap(&a, &b);
    printf("交换前:a=%d,b=%d\n", a, b);
    return 0;
}

In this program, we changed the values ​​of a and b, then we need to use call by reference, because the change of the formal parameters in the call by value will not affect the actual parameters.

Next, we will know why it is wrong.
Let's interpret the two codes of the Swap function in detail.

//错误代码
#include<stdio.h>
void swap(int a, int b)//返回类型为void表示不返回,
//此处的int a与int b表示形式参数和它们的类型
{
    
    
    int temp = 0;//定义一个临时变量
    temp = a;//把a的值赋给temp
    a = b;//把b的值赋给a
    b = temp;//把temp的值赋给b,完成交换操作
    //注意,因为形参只是实参的一份临时拷贝,
    //在整个函数中我们改变的只是实参,
    //出函数后形参被销毁无法改变实参
}
int main()
{
    
    
    int a = 0;//创建变量a
    int b = 0;//创建变量b
    scanf("%d %d", &a, &b);//输入数值
    printf("交换前:a=%d,b=%d\n", a, b);//展示
    swap(a, b);//交换函数,将a,b传进去
    printf("交换前:a=%d,b=%d\n", a, b);//实参依旧是a和b的原始值,没有达到我们的目的
    return 0;
}

//正确代码
#include<stdio.h>
void swap(int* pa, int* pb)//void表示不返回,此处的int* pa与int* pb表示形式参数和它们的类型
{
    
    
    int temp = 0;//定义临时变量
    temp = *pa;//用地址找到实参a并赋给temp
    *pa = *pb;
    //把用地址找到的实参b赋给用地址找到的实参a
    *pb = temp;//用地址找到实参b并赋给temp
    //跳出函数时,被销毁的形参只是两个指针变量,此时实参的交换已经完成
}
int main()
{
    
    
    int a = 0;
    int b = 0;
    scanf("%d %d", &a, &b);
    printf("交换前:a=%d,b=%d\n", a, b);
    swap(&a, &b);//传入地址
    printf("交换前:a=%d,b=%d\n", a, b);
    return 0;
}

3: Practice

(1): Write a function to determine whether a number is prime or not.

//写一个函数可以判断一个数是不是素数。
#include<stdio.h>
#include<math.h>
int is_primer(int n)
{
    
    
	int j = 0;
	for (j = 2; j <= sqrt(n); j++)
	{
    
    
		if (n % j == 0)
		{
    
    
			return 0;
			//如果经过return 0,
			//则下一步往函数int is_primer(int n)的大括号走,
			//然后进入主函数is_primer(i)那里进行判断i是否等于1	
		}
	}
	//j没有任何一个数能整除n,说明它一定是素数,则返回1,否则返回0
	return 1;
}
int main()
{
    
    
	//打印100~200之间的素数
	int i = 0;
	for (i = 100; i <= 200; i++)
	{
    
    
		//判断i是否为素数
		if (is_primer(i) == 1)//如果是素数返回的结果是1,则令函数==1,将素数打印出来
		{
    
    
			printf("%d ", i);
		}
		//如果这个函数返回值不等于1
		//则继续进入主函数的for循环,然后for循环再进入if中判断,
		//判断完后,将i传到形式参数里面去
	}
	return 0;
}

Note: If the function does not write a return value, the function will return a value by default. Some compilers return the result of the last instruction.

(2): Write a function to determine whether a year is a leap year.

//写一个函数判断一年是不是闰年。
int is_leap_year(int year)
{
    
    
	if (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0))
		return 1;
	else
		return 0;
}
//有一种更简单的方法,不需要用if...else
//int is_leap_year(int year)
//{
    
    
//	return ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0);
//}
int main()
{
    
    
	int y = 0;
	int count = 0;
	for (y = 1000; y <= 2000; y++)
	{
    
    
		//判断y是不是闰年
		if (is_leap_year(y) == 1)
		{
    
    
			count++;
			printf("%d ", y);
		}
	}
	printf("\ncount = %d\n", count);
	return 0;
}

(3): Write a function to implement a binary search of an integer ordered array.

//写一个函数,实现一个整形有序数组的二分查找
//错误代码
//#include<stdio.h>
//int binary_search(int* arr, int a)//地址传过去,得要指针接收,所以本质还是一个指针
//int binary_search(int arr[], int a)
//{
    
    
	//int left = 0;
	//int right = sizeof(arr)/sizeof(arr[0]) - 1;//right一直是0
	//上面的那一行代码是有逻辑错误的,
	//因为数组传参,传递的是数组首元素的地址
	//所以sizeof(arr)/sizeof(arr[0])=1,所以1-1=0
	//int mid = 0;
	//while (left <= right)
	//{
    
    
		//mid = left + (right - left) / 2;//找中间的元素,防止越界
		//if (arr[mid] > a)//中间元素大于查找值,就从右缩小一半的范围
		//{
    
    
			//right = mid - 1;//可以使用--mid,但不推荐
		//}
		//else if (arr[mid] < a)//中间元素小于查找值,就从左缩小一半的范围
		//{
    
    
			//left = mid + 1;//可以使用++mid,但不推荐
		//}
		//else
		//{
    
    
			//return mid;//找到了,返回下标
		//}
	//}
	//if (left > right) //正常情况下不会出现
	//{
    
    
		//return -1;//找不到,返回-1
	//}
//}
//int main()
//{
    
    
	//int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	//int k = 0;
	//scanf("%d", &k);
	//找到了返回下标,0~9
	//找不到返回 -1(这里不能返回0,因为数组的第一个数字下标是0,如果return 0很有可能会对结果造成影响)
	//传数组,一个元素4个字节,数组中10个元素40个字节,
	//把40个字节全都移过去,如果int arr[]为了接收也创建了一个40字节的空间
	//这样空间会有大量的浪费,所以c语言有了以下规定:
	//数组传参,传递的是数组首元素的地址
	//int ret = binary_search(arr, k);
	//if (-1 == ret)
		//printf("找不到\n");
	//else
		//printf("找到了,下标是:%d\n", ret);
	//return 0;
//}
//正确代码(在主函数里面定义一个sz变量)
#include<stdio.h>
//地址传过去, 得要指针接收, 所以本质还是一个指针
//int binary_search(int* arr, int a, int sz)//数组传参,传递的是数组首元素的地址
int binary_search(int arr[], int a, int sz)//形参为数组、需要查找的整数、数组的元素个数
{
    
    
	int left = 0;
	int right = sz - 1;
	int mid = 0;
	while (left <= right)
	{
    
    
		mid = left + (right - left) / 2;//找中间的元素,防止越界
		if (arr[mid] > a)//中间元素大于查找值,就从右缩小一半的范围
		{
    
    
			right = mid - 1;//可以使用--mid,但不推荐
		}
		else if (arr[mid] < a)//中间元素小于查找值,就从左缩小一半的范围
		{
    
    
			left = mid + 1;//可以使用++mid,但不推荐
		}
		else
		{
    
    
			return mid;//找到了,返回下标
		}
	}
	if (left > right) //正常情况下不会出现
	{
    
    
		return -1;//找不到,返回-1
	}
}
int main()
{
    
    
	int arr[10] = {
    
     1,2,3,4,5,6,7,8,9,10 };
	//只能在主函数里面定义sz变量,sizeof(arr)在主函数里使用不需要传递地址
	int sz = sizeof(arr) / sizeof(arr[0]);
	int k = 0;
	scanf("%d", &k);
	//找到了返回下标,0~9
	//找不到返回 -1(这里不能返回0,因为数组的第一个数字下标是0,如果return 0很有可能会对结果造成影响)
	//数组传参,传递的是数组首元素的地址
	int ret = binary_search(arr, k, sz);//再多加一个sz变量,传到形参上去
	if (-1 == ret)
		printf("找不到\n");
	else
		printf("找到了,下标是:%d\n", ret);
	return 0;
}

(4): Write a function that increases the value of num by 1 each time the function is called

#include<stdio.h>
void Add(int* p)//在主程序内定义一个变量储存调用的次数,因为需要改变变量的值,所以进行传址调用
{
    
    
    printf("hehe\n");
    (*p)++;//解引用找到变量再加1,注意这个括号不能忘
    //否则,*p++就表示每次这个指针先向后移动4个字节,然后解引用
}
int main()
{
    
    
	int num = 0;
	Add(&num);
	return 0;
}

Five: Nested calls and chained access of functions

1. Nested calls

Functions can call each other as needed.
Give two examples

#include<stdio.h>
int main()
{
    
    
    printf("Hello world\n");
    return 0;
}
//这是每一个初学者都会写的代码,我们先调用了main函数,
//然后在main函数的内部又调用了printf函数,这就是嵌套调用.(可以理解为复合函数)
#include <stdio.h>
void new_line()
{
    
    
    printf("hehe\n");
}
void three_line()
{
    
    
    int i = 0;
    for (i = 0; i < 3; i++)
    {
    
    
        new_line();
    }
}
int main()
{
    
    
    three_line();
    return 0;
}

Note: Functions can be called nested, but not nested definitions.

2. Chain access

Take the return value of one function as an argument to another function .

#include<stdio.h>
#include<string.h>
int main()
{
    
    
	int len = strlen("abcdef");
	printf("%d\n", len);
	//可以利用链式访问简化代码,如下:
	printf("%d\n", strlen("abcdef"));
	return 0;
}

The return value of the strlen function becomes the parameter of the printf function , which connects the two functions like a chain, that is, chain access.

(1): Classic example of chain access

What is the output of the following program?

#include<stdio.h>
int main()
{
    
    
    printf("%d", printf("%d", printf("%d", 43)));
    return 0;
}

Answer: 4321

Important knowledge points: The return value of the printf function is the number of characters it prints ,

First enter the outermost printf function

This layer of function requires the return value of the second layer function printf("%d", printf("%d", 43))

The second-level printf function needs the return value of the third-level function printf("%d", 43)

Execute the third-level function first, and after executing the third-level printf("%d", 43) function, the number of returned print characters is 2

So it becomes like this printf("%d", printf("%d", 2))

The second layer gets the return value 2, prints 2, and at this time the second layer function also returns the number of characters it typed 1

So it becomes printf("%d", 1) , and finally prints 1,

It also forms the output of 4321

Six: function declaration and definition

1: Declaration of the function

1. Tell the compiler what a function is called, what the parameters are, and what the return type is. But whether it exists or not is not determined by the function
declaration.

2. The declaration of a function generally appears before the use of the function. To satisfy the declaration before use.
3. Function declarations are generally placed in header files.

2: Definition of the function

1. The definition of a function refers to the specific implementation of the function, and the function implementation of the function is explained. It is equivalent to the steps we usually take to create a custom function.

2. Functions cannot be defined nested

3: The block writing of the program

When we write code, we may think: I write all the code in a source file, so it is not convenient to find it.

In fact, such a habit is detrimental to future program development.

Our society has its own division of labor. When we develop a program, we often only need to be responsible for part of a large project, such as one person to write the main program, one person to write functions, etc., and we Separating parts of a project makes it faster to find bugs and fix them accordingly.

Thus, when we write a function, we need a file allocation like this:

The declaration of the function, the definition of the type, the inclusion of the header file—the header file.hFunction
definition—the source file of the function implementation.cEach
function can be written in these two files, or several functions can be written in two in the file.

For example, when we write a project that is the sum of two numbers, many programmers divide the labor to complete it.
A created the header file add.h

#pragma once
//头文件中放:函数的声明,类型的定义,头文件的包含
int Add(int x,int y)

The small source file add.c created by B

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

inside test.c

#include "add.h"//引用头文件
int main()
{
    
    
	int a = 0;
	int b = 0;
	scanf("%d %d", &a. & b);
	int c = Add(a, b);
	printf("%d\n", c);
	return 0;
}

The advantage of writing code in modules is that we can hide some necessary code for implementing files. For
example, there is a programmer named Zhang San.
He wrote a game engine in his spare time. A company wants to buy his game engine and
Zhang San agreed to sell it. Give it to him, but I don't want to show him the source code, so what should he do?
He did it like this:
Zhang San wrote these two codes insert image description here
, assuming he works in the VS environment, click on the header file, add an existing item, and set the The above two code files were added.
This game engine happened to be a project that Li Si wanted to buy. insert image description here
Zhang San thought: I can let you use it, but I don’t want you to see how it is implemented,
so he clickedinsert image description here
insert image description here

Click Apply
. After use, the code in add.c will not run.

insert image description here
This add.lib file is the static library of the game engine written by Zhang San
. After Li Si bought it, he opened it and looked at the add.lib file. It was a bunch of garbled characters,
so Zhang San gave him an instruction manual
.
insert image description here
The code for test.c should end up looking like this

#include "add.h"//引用头文件
#pragma comment(lib, "add.lib")//加载引用静态库
int main()
{
    
    
	int a = 0;
	int b = 0;
	scanf("%d %d", &a. & b);
	int c = Add(a, b);
	printf("%d\n", c);
	return 0;
}

Seven: function recursion

1: What is recursion

The programming skill of the program calling itself is called recursion. ( Recursion is the program calling itself ) .
Recursion is widely used in programming languages ​​as an algorithm. A procedure or function has a method of directly or indirectly
calling itself in its definition or specification
. It usually transforms a large and complex problem into a smaller problem similar to the original problem to solve. The
recursive strategy
only A small number of programs can be used to describe the repeated calculations required for the problem-solving process, which greatly reduces the code amount of the program.

The main way of thinking about recursion is: make big things small

Here's a simple recursion

#include<stdio.h>
int main()
{
    
    
	printf("hehe\n");
	main();
	return 0;
}
//死递归hehe,到最后程序因为栈溢出而自动跳出(挂掉了)

When will stack overflow occur?
Each function call will apply for memory space in the stack area , and the above code recursively applies for infinite times, eventually leading to stack overflow.

2: Two necessary conditions for recursion

There is a constraint, when this constraint is met, the recursion does not continue.

It gets closer and closer to this limit after each recursive call.

(1): Exercise 1: (Drawing explanation)

Takes an integer value (unsigned) and prints its bits in order

For example: Input: 1234, Output: 1 2 3 4

#include<stdio.h>
void print(unsigned int n)
{
    
    
	if (n > 9)//如果n是两位数的话
	{
    
    
		print(n / 10);//满足递归的两个必要条件
	}
	printf("%d", n % 10);
	
}
int main()
{
    
    
	unsigned int num = 0;
	//无符号整型用%u
	scanf("%u", &num);//1234
	print(num);//按照顺序打印num的每一位
	return 0;
}

Now let's analyze the recursive process

//递归的思考方式:大事化小
//
//print(1234)
//print(123) 4   
//print(12) 3 4
//print(1) 2 3 4
//1 2 3 4
//
print(1234);//这个函数从上到下,先递进后回归
//1234大于9,进入if语句,第一层
print(1234)
{
    
    
    if (n > 9)//n=1234,满足条件,进入if
    {
    
    
        print(123);
    }
    printf("%d ", n % 10);//第一层,n%10=4
}
//print(123)展开,n=123满足条件,继续进入下一层,接着递归
print(123)
{
    
    
    if (n > 9)//n/10=123,满足条件,进入if
    {
    
    
        print(12);
    }
    printf("%d ", n % 10);//第二层,n%10=3
}
//print(12)展开,n/10=1此时不满足条件,不会继续进入下一层的if语句
print(12)
{
    
    
    if (n > 9)//n=12,不满足条件,不进入if
    {
    
    
        print(1);
    }
    printf("%d ", n % 10);//第三层,n%10=2
}
print(1)
{
    
    
    if (n > 9)//n=1,不满足条件,不进入if
    {
    
    
        print(0);
    }
    printf("%d ", n % 10);//第三层,n%10=1
}
//递归的“递”此时已经完成,我们将这个代码整理一下,查看它时如何“归”的
print(1234)
{
    
    
    {
    
    
        {
    
    
            {
    
    
                printf("%d ", n % 10);//第四层,n%10=1
            }
            printf("%d ", n % 10);//第三层,n%10=2
        }
        printf("%d ", n % 10);//第二层,n%10=3
    }
    printf("%d ", n % 10);//第一层,n%10=4
}
//代码从第四层开始向外执行,故可以实现数字的按位打印
//输出:1 2 3 4

insert image description here
Let's add some knowledge points of function stack frame (practice internal skills) (the blogger's article is written in detail and easy to understand, you can collect it)

Function stack frame and destruction

Here I will briefly talk about
the types of registers mentioned before, let's recall them now

eax: general-purpose register, saves temporary data, often used for return values
ebx: general purpose register, hold temporary data
ebp: stack bottom register
edp: top-of-stack register
eip: instruction register, which holds the address of the next instruction of the current instruction

ebp and edp are used to maintain function stack frames

insert image description here
How exactly is the function called? First, we press F10 in VS, and then click on disassembly. At this time, we can see the assembly code. We cancel "Show Symbol Names" because we want to observe the specific layout of memory addressesinsert image description here

insert image description here

insert image description here

insert image description here

insert image description here
Tips:
1. dword is four bytes
2. 4 bytes are initialized each time, a total of ecx times are initialized, and finally the content of eax, 0CCCCCCCCch is initialized
3. push: push the stack: put an element on the top
of the stack pop: pop the stack : remove an element from the top of the stack

insert image description here

insert image description here
From this we can find that, in fact, we do not define formal parameters at
all. We pass the parameters through the mov, push instructions (push the formal parameters of b and c to [ebp+8], [ebp+0Ch(12)]. )
Add(a,b) is passed to the right and then the left.
a' and b' (x and y) are actually temporary copies of the actual parameters of a and b

insert image description here

After return z, the function begins to destroy. Let's take a look at the disassembled code.

00C213F1 5F   		pop   edi
00C213F2 5E   		pop   esi
00C213F2 5B   		pop   ebx
//pop三次 相当于之前连续push三次的逆过程,连续在栈顶弹出值分别存放到edi,esi,ebx中
//pop完后,这些空间没必要存在了,应该被销毁
00C213F4 8B E5 		mov	  esp,ebp
//mov将ebp赋给了esp,这个时候esp与ebp在同一个位置上

00C213F6 5D			pop   ebp
//把ebp pop出来之后ebp回到main函数的栈底,esp回到main函数的栈顶	

insert image description here
As shown in the figure, esp returns to the top of the stack of the main function.

00C213F7 C3 		ret
//ret指令的执行,跳到栈顶弹出的值就是call指令的下一条指令的地址
//此时esp+4(pop了一下),栈顶的地址也弹出去了,esp指向了00C21450(call)的底部		

After calling the Add function, when returning to the main function, continue to execute

00C21450 83 C4 08		add			esp,8
//回到call指令的下一条地址,esp直接+8,把x,y形参的空间释放,esp这个时候指向edi的顶端
00C21453 89 45 E0		mov			dword ptr [ebp-20h],eax
//把eax中的值,存放到[ebp-20h(也就是c)]里面去
//函数的返回值是由eax寄存器带回来的

After reading, we can answer the following questions

  • How are local variables created?
    Answer: First allocate the stack frame space for the function, initialize a part of the space, and then allocate a little space in the stack frame space of the local variables.
  • Why is the value of a local variable a random value when it is not initialized?
    Answer: If it is not initialized, since the local variable comes from the stack frame of the main function, the value in it is put in randomly, and the random value can be overwritten after initialization.
  • How do functions pass parameters? What is the order in which the parameters are passed?
  • What is the relationship between formal parameters and actual parameters?
    Answer: A formal parameter is a temporary copy of an actual parameter.
  • What happens when the function is called?
    (3, 5) Answer: When you want to call a function, it has been pushed before it is called, and the two parameters (a, b) are pushed from right to left. When we enter the formal parameter function, in fact, in the stack frame of the Add function, the formal parameters of the function are retrieved through the offset of the pointer.
  • How does the function return after the end of the function call?
    A: Before the call, we store the address of the next instruction of the call instruction, and store the ebp of the previous function stack frame of the function called by ebp. When the function needs to return after the function call, pop up the ebp , you can find the ebp of the previous function call, when the pointer goes down (high address), you can go to the top of the esp, so as to be able to return to the stack frame space of the main function, after that, we remember the call instruction The address of the next instruction, when we return, we can jump to the address of the next instruction of the call instruction, so that the function can return after the function call, and the return value is brought back through the eax register.

(2): Exercise 2: (Drawing to explain)

To find the length of a string, three methods are described below:
Method 1:

//方法一
#include<stdio.h>
#include<string.h>
int main()
{
    
    
	char arr[] = {
    
     "abc" };
	int len = strlen(arr);
	printf("%d", arr);
	return 0;
}

Method 2: Using functions and loops

//方法二
#include<stdio.h>
int my_strlen(char* str)
{
    
    
	int count = 0;
	while (*str != '\0')
	{
    
    
		count++;
		str++;
	}
	return count;
}
int main()
{
    
    
	char arr[] = {
    
     "abc" };
	int len = my_strlen(arr);
	printf("%d", len);
	return 0;
}

insert image description here

Method 3: Recursion (easy, no need to create temporary variables)

//strlen("abc");
//1+strlen("bc");
//1+1+strlen("c");
//1+1+1+strlen("");
//1+1+1+0=3
#include<stdio.h>
int my_strlen(char* str)
{
    
    
	if (*str != '\0')
	{
    
    
//如果第一个字符不是"\0",则字符串至少包含一个字符
//可以剥出来个1,str+1是下一个字符所指向的地址
//空字符第一个是"\0"
		return 1 + my_strlen(str + 1);
//注意:这里str++并不能代替str+1的作用
//我们把str+1之后的地址传下去了,而留下来str还是原来的str.
//因为str++是先使用再++,
//那么根据原理传进去的str还是原来的str(原来是a的地址,传进去还是a的地址)
//所以按照原理:++str能代替str+1的作用,但并不推荐这样做,
//因为如果递归回来之后使用str的话,留下来的str不是原来的str了.
	}
	else
		return 0;
}
int main()
{
    
    
	char arr[] = {
    
     "abc" };
	int len = my_strlen(arr);
	printf("%d", len);
	return 0;
}

insert image description here

Method 3 seems to have very little code, but in fact a lot of calculations are repeated inside the program.

3: Recursion and iteration of functions

(1): What is iteration

Iteration is actually repetition, and a loop is a special kind of iteration.

(2): Advantages and disadvantages

In function recursion, we call functions layer by layer, which has the advantage of requiring less code and being concise . But there are two main disadvantages. On the one hand, a large number of repeated calculations slow down the running speed of the program ; on the other hand, each time the function is called, it needs to open up a corresponding space in the stack area, and when the recursion is too deep, it may A stack overflow occurred. (The space in the stack area has been used up, and the program cannot continue)

When we use iteration , the loop does not need to call a lot of functions, the repeated calculation will be much less, the running speed of this program will be much faster, but the code amount of this program will be much larger .

Find the nth Fibonacci number. (regardless of overflow)
//斐波那契数列
//1 1 2 3 5 8 13 21 34 55 ....
//方法一:递归法
//		 n<=2 1
//Fib1(n) 
//		 n>2 Fib1(n-1)+Fib1(n-2);//第三个数加第二个数
#include<stdio.h>
int Fib1(int n)
{
    
    
	//如果想知道某个斐波那契数究竟计算了多少次,可以设置一个全局变量count
	//if (n == k)//算第k个斐波那契数被计算了多少次
		//count++;
	if (n <= 2)
		return 1;
	else
		return Fib1(n - 1) + Fib1(n - 2);
}
//int count = 0;
int main()
{
    
    
	int n = 0;
	scanf("%d", &n);
	int ret1 = Fib1(n);//定义一个ret来接受上面函数的返回值
	printf("%d", ret1);
	//printf("count=%d\n", count);
	return 0;
}
find the factorial of n
//求n的阶乘
//方法一:递归法
//1*2*3=Fac(3)
//1*2*3*4=Fac(3)*4
// 
//		 n<=1  1			
//Fac(n)
//		 n>1 n*Fac(n-1);

#include<stdio.h>
int Fac1(int n)
{
    
    
	if (n <= 1)
		return 1;
	else
		return n * Fac1(n - 1);
}
int main()
{
    
    
	int n = 0;
	scanf("%d", &n);
	int ret1 = Fac1(n);//定义一个ret来接受上面函数的返回值
	printf("%d", ret1);
	return 0;
}

The above two codes seem to be very simple, but when they actually run, they find a big problem:

When using the Fib1 function, it is particularly time consuming if we want to calculate the 50th Fibonacci number.

Use the Fac1 function to find the factorial of 10000 (without considering the correctness of the result), the program will crash

why?
When using the Fib function, when n=50, the light n=46 is calculated (appeared) 3 times, which will make the program run very slowly.
When using the Fac1 function, when the factorial of 10000 is calculated, the information of stack overflow ( stack overflow ) will appear. The stack space allocated by the system to the program is limited, but if there is an infinite loop, or (dead recursion), This may cause the stack space to be opened all the time, and eventually the stack space will be exhausted. This phenomenon is called stack overflow.

So how do we correct this problem?
Change recursion to non-recursion
The corrected code is as follows

//斐波那契数列
//1 1 2 3 5 8 13 21 34 55 ....
//方法二:迭代  
#include<stdio.h>
int Fib2(int n)
{
    
    
	int a = 1;//
	int b = 1;//前两个数都是1
	int c = 1;//对斐波那契数列中前两个数之和的第三个数初始化
	//不能令c=0,如果输入的n小于2,那直接return 0了,很显然不行
	while (n > 2)//第三个斐波那契数的时候开始循环
	{
    
    
	//1 1 2 3 5 8 13 21 34 55 ....
	//
	//  
	//      a b c.....(斐波那契数前两个数之加赋给第三个数,以此类推...)
	//		  a b c....
	//			a b c....	  
		c = a + b; //斐波那契数前两个数之加赋给第三个数
		a = b;
		b = c;
		n--;
	}
	return c;
}
int main()
{
    
    
	int n = 0;
	scanf("%d", &n);
	int ret2 = Fac2(n);//定义一个ret来接受上面函数的返回值
	printf("%d", ret2);
	return 0;
}
//求n的阶乘
//方法二:迭代法
int Fac2(int n)
{
    
    
	int i = 0;
	int ret2 = 1;//阶乘初始化必然为1
	for (i = 1; i <= n; i++)
	{
    
    
		ret2 = ret2 * i;
	}
	return ret2;
}
int main()
{
    
    
	int n = 0;
	scanf("%d", &n);
	int ret2 = Fac2(n);//定义一个ret来接受上面函数的返回值
	printf("%d", ret2);
	return 0;
}

hint:

  1. Many problems are explained in recursive form simply because it is clearer than the non-recursive form.
  2. But iterative implementations of these problems tend to be more efficient than recursive implementations, although the code is slightly less readable.
  3. When a problem is too complex to implement iteratively, the simplicity of the recursive implementation can compensate for the runtime overhead it imposes.

4: The classic problem of function recursion

(1): Tower of Hanoi problem

one. Origin:
  The Tower of Hanoi (also known as the Tower of Hanoi) is an educational toy that originated from an ancient Indian legend. When Brahma created the world, he made three diamond pillars, on which 64 golden discs were stacked in order from bottom to top. Brahma ordered the Brahmin to rearrange the disc on another pillar in order of size, starting from the bottom. And it is stipulated that the disc cannot be enlarged on the small disc, and only one disc can be moved at a time between the three columns.

two. The abstraction is a mathematical problem:
  as shown in the figure below, there are three pillars A, B, and C from left to right, among which there are n disks stacked from small to large on pillar A. Now it is required to move the disk on pillar A to C When the column goes up, there is only one principle during the period: only one plate can be moved at a time, and the large plate cannot be on the small plate. Find the steps of moving and the number of times of moving.

insert image description here

(1)n == 1

1st Disk 1 A---->C sum = 1 time

(2) n == 2

1st set No. 1 A---->B
2nd set No. 2 A---->C
3rd set No. 1 B---->C sum = 3 times

(3)n == 3

1st set No. 1 A---->C
2nd set No. 2 A---->B
3rd set No. 1 C---->B
4th set No. 3A---- >C
5th set No. 1 B---->A
6th set No. 2 B---->C
7th set No. 1 set A---->C sum = 7 times

It is not difficult to find the law: the number of times 2 of a disc is reduced by 1 to the power of 1

The degree of 2 of the 2 discs is reduced by 1 to the power of 2.
The degree of 2 of the 3 discs is reduced by 1
. . . . .
Degree 2 of n disks minus 1 to the nth power

Therefore: the number of moves is: 2^n - 1

Analysis of Algorithms

(step 1) If it is a plate

Move the plate on the a column directly from a to c

otherwise

(Step 2) First move the n-1 plates on column a to b (Figure 1) with the help of c.

Certainly there is no c column that cannot be moved, and the known function parameter is hanoi(int n, char a, char b, char c).

Represents moving the plate on column a to column b with the help of column c. When calling the function here, n-1 on column a is moved.

The plate moves to the b-pillar with the help of the c-pillar. So here you need to change the position hanoi(n-1,a,c,b).

insert image description here

(Step 3) At this point, the movement is completed as shown in Figure 1, but the movement is not over yet. First, move the last plate (nth) plate on column a to c (picture 2)
insert image description here
(Step 4) Finally, move column b Move the n-1 plates on to c with the help of a (Figure 3)

insert image description here

// 汉诺塔
#include<stdio.h>
void hanoi(int n, char a, char b, char c)//这里代表将a柱子上的盘子借助b柱子移动到c柱子
{
    
    
	if (1 == n)//如果是一个盘子直接将a柱子上的盘子移动到c
	{
    
    
		printf("%c-->%c\n", a, c);
	}
	else
	{
    
    
		hanoi(n - 1, a, c, b);//将a柱子上n-1个盘子借助c柱子,移动到b柱子
		printf("%c-->%c\n", a, c);//再直接将a柱子上的最后一个盘子移动到c
		hanoi(n - 1, b, a, c);//然后将b柱子上的n-1个盘子借助a移动到c
	}
}
int main()
{
    
    
	int n = 0;
	printf("请输入需要移动的圆盘个数:");
	scanf("%d", &n);
	hanoi(n, 'A', 'B', 'C');//移动A上的所有圆盘到C上
	return 0;
}

(2): Frog jumping steps

(1) Problem description
A frog can jump up 1 or 2 steps at a time. Find the total number of jumps the frog can jump up a n steps.

Task: How many ways can a frog jump up a n steps?

Rules: Frogs can jump one or two steps at a time.

(2) Problem Analysis
When n = 1, there is one way of jumping;
when n = 2, the frog jumps one level and then another level; the frog jumps directly to two levels. There are 2 ways of jumping;
when n = 3, the frog jumps one level three times; the frog jumps one level and then two levels; the frog jumps two levels and then one level, there are 3 ways of jumping;
when n = 4, there are 5 There are 8 kinds of jumps ;
when n = 5, there are 8 kinds of jumps;  …

The law is similar to the judgment of the end of the Fibonacci sequence
insert image description here
(3) recursion

For example, a frog needs to jump up five steps.

In order to find the number of five steps, we need to find the values ​​of four steps and three steps.

In order to find the number of four steps, we need to find the values ​​of three steps and two steps, and one of the values ​​does not need recursion.

In order to find the number of three steps, we need to find the value of the second step and the first step, and the other value does not need recursion at this time.

At this point, our recursion can be interrupted at this time, so we get the judgment basis for the termination of the recursion: when the number of discs on column 1 is less than or equal to one, jump out of the recursion.

//青蛙跳台问题
#include<stdio.h>
//或者运用void函数,但之前要定义一个全局变量count
//int count = 0;//创建全局变量来统计个跳法个数
//void frog_jump(int n)
//{
    
    
//	if (n == 0)//当台阶数为0是跳法个数加1
//		count++;
//	else if (n < 0);
//	else
//	{
    
    
//		frog(n - 1);
//		frog(n - 2);
//	}
int frog_jump(int n)
{
    
    
	//int sum = 0;
	if (1 == n)//等于1时,sum=1
	{
    
    
		return 1; //sum += 1
	}
	else if (2 == n)//等于2时,sum=2
	{
    
    
		return 2;//sum += 2;
	}
	else//大于3时,开始递归
	{
    
    
		return frog_jump(n - 1) + frog_jump(n - 2);//sum = frog_jump(m - 1) + frog_jump(m - 2);
	}
	//return sum;
}
int main()
{
    
    
	int n = 0;
	printf("请输入台阶的个数:");
	scanf("%d", &n);
	int ret = frog_jump(n);
	printf("一共有%d种跳法\n", ret);
	return 0;
}

(3): Frog jumping steps (advanced version)

Question (1): Once upon a time, there was a frog who wanted to jump up the steps to reach the peak. If the frog could jump up to 1 step at a time, it could also jump up to 2 steps...(n-1) level, n level. Then how many jumping methods the frog has in total when it jumps up the nth stair. (The premise is that there will be a n-step jump method for n steps)

(1) Problem solving ideas:

If n is 1 step: f(1) = 1 (only one jump is possible)
If n is 2 steps: f(2) = f(2 - 1) + f(2 - 2) (there will be two A method of jumping steps, 1 step at a time or 2 steps at a time)
If n is 3 steps: f(3) = f(3 - 1) + f(3 - 2) + f(3 - 3) (there will be three The method of jumping steps, 1st, 2nd, 3rd at a time)
……
……
If n is (n - 1) steps:

f(n-1) = f((n-1)-1) + f((n-1)-2) + … + f((n-1)-(n-2)) + f((n-1)-(n-1))
f(n-1) = f(0) + f(1)+f(2)+f(3) + … + f((n-1)-1)
f(n-1) = f(0) + f(1) + f(2) + f(3) + … + f(n-2)

If n is n steps:

f(n) = f(n-1) + f(n-2) + f(n-3) + … + f(n-(n-1)) + f(n-n)
f(n) = f(0) + f(1) + f(2) + f(3) + … + f(n-2) + f(n-1)

Combining the cases of f(n-1) and f(n) you will find that f(n) = f(n-1) + f(n-1), so we get: f(n) = 2*f(n -1).

#include<stdio.h>
int frog_jump_step(int n)
{
    
    
	if (n <= 1)
	{
    
    
		return 1;
	}
	else
		return 2 * frog_jump_step(n - 1);
}
int main()
{
    
    
	int n = 0;
	printf("请输入青蛙应该跳的台阶个数:");
	scanf("%d", &n);
	int sum = frog_jump_step(n);
	printf("一共有种%d种跳法\n", sum);
	return 0;
}

Problem (2): A frog can jump up to 1 steps at a time, and it can also jump up to 2 steps... It can also jump up to n steps. How many ways can the frog jump up an m-level stair?

(1) Problem-solving ideas
The situation that is different from the above is that the last steps of the frog jumping are not n steps, so there may be a phenomenon of skipping.
Pre-order polynomial:
f(n) = f(n-1) + f(n-2) + f(n-3) + … + f(nm)
f(n-1) = f(n-2) + f(n-3) + … + f(nm) + f(nm-1)
simplifies to: f(n) = 2f(n-1) - f(nm-1)

#include<stdio.h>
int frog_jump_step(int n,int m)
{
    
    
	if (n > m)
	{
    
    
		return 2 * frog_jump_step(n - 1, m) - frog_jump_step(n - 1 - m, m);
	}
	else
	{
    
    
		if (n <= 1)
		{
    
    
			return 1;
		}
		else
			return 2 * frog_jump_step(n - 1);
	}	
}
int main()
{
    
    
	int n = 0;
	int m = 0;
	printf("请输入青蛙应该跳的台阶个数:");
	scanf("%d", &n);
	int sum = frog_jump_step(n, m);
	printf("一共有种%d种跳法\n", sum);
	return 0;
}

Guess you like

Origin blog.csdn.net/yanghuagai2311/article/details/125928613