C language learning sharing (fifth) ------ function


1 Introduction

Let's follow the content branch and loop of the previous chapter . This chapter will be included in the column C language learning and sharing . Those who are interested in learning more C language knowledge can jump to the above content. This article will introduce the relevant functions in detail Content


2. What is a function

We often see the concept of function in mathematics. But do you understand functions in C language?
Definition of function from Wikipedia: Subroutine

  1. In computer science, a subroutine (English: Subroutine, procedure, function, routine, method,
    subprogram, callable unit) is a certain part of the code in a large program, consisting of one or more statement blocks
    . It is responsible for completing a specific task and is relatively independent from other codes.
  2. Generally, there will be input parameters and a return value, providing the encapsulation of the process and the hiding of details. These codes are often integrated as software libraries.

Classification of functions in C language:

  1. Library Functions
  2. custom function

3. Library functions

Like the scanf we usually use, the printf function is a library function, which is a function that comes with the C language. So why do library functions appear?


3.1 Why there are library functions

  1. We know that when we are learning C language programming, we can't wait to know the result after a code is written, and want to print the result to our screen to see. At this time, we will frequently use a function: print information to the screen in a certain format (printf).

  1. In the process of programming, we will frequently do some string copy work (strcpy).

  1. While programming, we will also encounter calculations, and we will always calculate operations (pow) such as n to the kth power.

Like the basic functions we described above, they are not business codes. Every programmer may use it during the development process. In order to support portability and improve program efficiency, the basic library of C language provides a series of similar library functions to facilitate software development by programmers.


3.2 How to learn library functions

Now that many function library functions of the C language have been implemented for us, how do we learn the library functions?
Here is a website I recommend to you:cplusplus is similar to a C language dictionary. We can search for all library functions related to C language on this website. If some students can’t find the search bar, then you may have entered the latest official website. We only need to click on the upper right corner The legacy version can switch to the old version to start searching

insert image description here


When we enter this interface, we will see the header files we are familiar with and some header files we have not seen before:
insert image description here


For example, if we click on the stdio.h header file we are familiar with, we can see all the library functions contained in this header file,
including the scanf function and printf function we are familiar with

insert image description here

Of course, if you want to search and learn a specific function, you can also search directly on it.


3.3 Reference Documents to Learn Library Functions

Next, we refer to the documentation above cplusplus to learn two library functions:

insert image description here



3.31 strcpy function

We first search strcpy in the search bar:

insert image description here

From the information given in the document, we can see that the return type of the strcpy function is char*, and it accepts two variables of type char*. We know that strcpy means string copy, and one of its two formal parameters is called source: Source, one is called destination:destination. We can guess that it may copy the string pointed to by source to the string pointed to by destination , but it doesn’t matter if you don’t know, it will be explained below:


insert image description here

First of all, here it gives the intention of this function: copy string is to copy the string. The first paragraph below is saying : copy the C string pointed to by source to the string pointed to by destination, including the ending null character ( and stop at that point). .Seeing this, we already know what this function is doing, and there is a reminder in the next line: In order to avoid stack overflow, the size of the array pointed to by destination should be long enough to contain the same C as source string (including the terminating null character), and should not overlap source in memory. Let's look down and explain the formal parameters and return values ​​and use cases of library functions, which is very convenient:


insert image description here


According to the case it gives, we can type the code and feel it by ourselves

#include <stdio.h>
#include <string.h>
int main ()
{
    
    
  char str1[]="Sample string";
  char str2[40];
  char str3[40];
  strcpy (str2,str1);
  strcpy (str3,"copy successful");
  printf ("str1: %s\nstr2: %s\nstr3: %s\n",str1,str2,str3);
  return 0;
}


3.32 memset function

The same steps as we learn strcpy, first search for the memset function:

insert image description here

The function of this function is to fill the memory block, and it passed three parameters in. Now the information we get is not enough to understand the function of this function, let's look down:


insert image description here

Let's take a look at the explanation of the parameters: ptr is a pointer to the memory block to be filled, and value refers to the value we fill in the memory block, which is of type int. Finally, num refers to the byte to be set to the value number. It should be noted here that the unit of num is byte instead of one or two. After analyzing here, we probably understand that this function needs to fill the space pointed to by ptr with value, fill num bytes, and then look at its return value


insert image description here
When we fill the memory, we return this memory


Finally, let's take a look at its use cases, and feel its implementation after typing the code and running it:

#include <stdio.h>
#include <string.h>

int main ()
{
    
    
  char str[] = "almost every programmer should know memset!";
  memset (str,'-',6);
  puts (str);
  return 0;
}

3.33 Header files that should be included when using library functions

Finally, if we want to see which header file should be included when our library function is used, then we look at the left column of the page:

insert image description here


Finally, we don't need to memorize all the library functions. We usually consult the documentation once we meet one in the learning process of C language, and we are familiar with one. Although we don't need to memorize all the library functions, we need to master How to learn library functions.



4. Custom functions

If library functions can do everything, what do programmers need to do?
All the more important are custom functions. Like library functions, custom functions have function names, return value types, and function parameters. But the difference is that these are all designed by ourselves. This gives programmers a lot of room to play

Custom functions are the same as library functions, including return type, function name, and parameters.

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

Let's say we write a function that returns the maximum value of two variables:

#include<stdio.h>
int get_max(int x, int y)
{
    
    
 return (x>y)?(x):(y);
}
int main()
{
    
    
 int num1 = 10;
 int num2 = 20;
 int max = get_max(num1, num2);
 printf("max = %d\n", max);

Let's write another function to swap the values ​​of two variables:

void Swap1(int x, int y)
{
    
    
	int tmp = 0;
	tmp = x;
	x = y;
	y = tmp;
}
int main()
{
    
    
	int num1 = 1;
	int num2 = 2;
	Swap1(num1, num2);
	printf("Swap1::num1 = %d num2 = %d\n", num1, num2);
	return 0;
}

We will find a problem, this code can run, but the values ​​of num1 and num2 are not exchanged, at this time we are often puzzled, let us start debugging ! We press F11 on the VS compiler Enter debugging, then open the monitoring window according to the video below, enter num1, num2 and x, y, and their addresses to view the situation

vs compiler watch window


When we bring up the monitoring window, we then press F11 to go down and go to the position where num1 and num2 are defined. The values ​​and addresses of num1 and num2 in the monitoring window will be displayed. When we enter the function, x and y The values ​​and their addresses will also be displayed. Here we find that when we pass the values ​​of num1 and num2, the values ​​of x and y are indeed the same as num1 and num2:

insert image description here


But we will find that the addresses of x, y and num1 and num2 are different!, Their addresses on my computer are:

insert image description here

They are all the same before, but at the end num1 and num2 are 34 and 54, but x and y are 10 and 18. So we come to a conclusion: when the main function calls our custom function, our formal parameters ( Here x and y) are a temporary copy of the actual parameters (here num1 and num2), they have two completely different addresses, when we change the formal parameters, the actual parameters will not be affected. So this place It can explain why the function we wrote cannot exchange the values ​​of two variables.


We modify our code, and when passing parameters, we can solve this problem by passing the address in the past: (we will analyze why this is possible later)

//正确的版本
void Swap2(int* px, int* py)
{
    
    
int tmp = 0;
tmp = *px;
*px = *py;
*py = tmp;
}
int main()
{
    
    
int num2 = 2;
int num1 = 1;
Swap2 (&num1, &num2);
printf("Swap2::num1 = %d num2 = %d\n", num1, num2);
return 0;
}


5. Function parameters

  • Actual parameters: The actual parameters passed to the function are called actual parameters.
    Actual parameters can be: constants, variables, expressions, functions, etc.
    No matter what type of quantity the actual parameters are, they must have definite values ​​when the function is called, so that these values ​​can be transferred to the formal parameters.

  • Formal parameter: The formal parameter refers to the variable in the parentheses after the function name, because the formal parameter is only instantiated (allocated memory unit) when the function is called, so it is called the formal parameter. Formal parameters are automatically destroyed when the function call completes. Therefore formal parameters are only valid within the function.

The parameters x, y, px, py in the Swap1 and Swap2 functions above are all formal parameters.


The num1, num2 passed to swap1 in the main function and &num1, &num2 passed to the Swap2 function are the actual parameters



5.1 Detailed explanation of the problem of exchanging two numbers

In the above topic:

insert image description here

When we pass the pointer into the function now, px and py store the addresses of num1 and num2, we can use the dereference operation on px and py to modify the values ​​​​of num1 and num2, and we can find that the addresses of px and py are Two values ​​that have nothing to do with num1 and num2, which means that our px and py here are also a temporary copy, except that they store addresses, and we can indirectly change the values ​​​​of num1 and num2 through addresses.



6. Function call

6.1 Call by address

Call by address is a way to call a function by passing the memory address of the variable created outside the function to the function parameter. This way of passing parameters can establish a real connection between the function and the variables outside the function, that is, the inside of the function can directly manipulate the variables outside the function. *
int a[]={
    
    1,2,3,4,5,6};
int len=sizeof(a)/sizeof(a[0]);//求数组a的元素个数

However, when we combine this place with the function, it seems that we cannot write like this:

int len_of(int* p)
{
    
    
  int len=sizeof(p)/sizeof(p[0]);
  return len;
}
int main()
{
    
    
  int a[]={
    
    1.,2,3,4,5,6];
  int count=len_of(a);
  return 0;
}

We say that the array name is the address of the first element. After passing the address of the first element of the array a in this place, p only accepts the address of the first element of the array. The actual parameter is not an array but a pointer. In this place, sizeof is used to find the length We will encounter problems. Below we can see that the length is not the "6" we expected, because my machine is 64-bit, and the size of the pointer variable is 8 bytes. Our sizeof ( p ) is equal to 8, and then our sizeof (p[0]) means that the length of the integer is 4, so the value we get is 2. Novices are prone to make mistakes in this place, so pay attention

insert image description here



6.2 Call by value

When calling by value, the formal parameters and actual parameters of the function occupy different memory blocks, and the modification of the formal parameters will not affect the actual parameters. Refer to the function we wrote above: Swap the values ​​of two variables



7. Nested call and chained access of functions

Functions and functions can be combined according to actual needs, that is, they can call each other

7.1 Nested calls

The nested call of our function is like producing a car. I produce some parts, and you also produce some parts. Finally, after the production, we are combined to form a car. Let's take a look at the example :

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

In this place, our main function calls the three_line function, and then calls the new_line function in the three_line function. This is the nested call of the function. The function of the three_line function in our place is to realize three loops, and the function of the new_line function is to print "hehe" , combining the two to form a new function

Note: Functions can be called nestedly, but definitions cannot be nested.



7.2 Chain access

Chained access is to use the return value of one function as the parameter of another function.

Let me give a relatively simple example to illustrate:Find the length of the string abc

#include<stdio.h>
#include<string.h>
int main()
{
    
    
	int len = 0;
	len = strlen("abc");
	printf("%d", len);
	return 0;
}

If we use chained access to achieve this function:

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

The second code is to use the return value of the strlen function as the parameter of the printf function. Another example:

#include<stdio.h>
int main()
{
    
    
	printf("%d", printf("%d", printf("%d", 43)));//会打印什么出来?
}

The logic of this place should be relatively clear. Our first-level printf function prints the return value of the second-level printf function, then the second-level printf function prints the return value of the third-level printf function, and the third-level printf function prints 43. We may have never met the return value of the printf function. At this time, let's go to cplusplus to learn the return value of this function:

insert image description here

Here it says: If successful, returns the total number of characters written. Then we know that the return value of the third layer printf function should be 2, because 43 is two characters, and so onOur code will print: 4321 out.



8. Function declaration and definition

8.1 Definition of functions

We said before that the main function can be placed anywhere in our program. If we now write code like this to add two numbers:

#include<stdio.h>
int main()
{
    
    
	int a = 10;
	int b = 20;
	int sum = Add(a, b);
	printf("%d",sum);
	return 0;
}

int Add(int x, int y)//函数的定义
{
    
    
	return x + y;
}

When we compile, the program will report an error ":Add is undefined"

insert image description here

This is because the code is scanned from front to back when scanning the code. When we scan the code to the line calling the function, we find that we have never encountered the Add function before. Although we define the function in this place, others do not know it, so we To add a statement in front



8.2 Declaration of functions

  1. Tell the compiler what a function is called, what its parameters are, and what its return type is. But whether it exists or not, the function
    declaration cannot decide.
  2. The declaration of a function generally appears before the use of the function. It must be declared before use.
  3. Function declarations are generally placed in header files.

After modifying the code:

#include<stdio.h>

int Add(int x, int y);//在最前面加上声明
int main()
{
    
    
	int a = 10;
	int b = 20;
	int sum = Add(a, b);
	printf("%d", sum);
	return 0;
}

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

This way of writing is more verbose, so we usually put the custom function above the main function, so that we don't have to declare the function.

In fact, the real usage of the function declaration is that when we call the functions in other files in the test.c file, we need to declare the function, which we will explain in detail later when we implement the three chess and minesweeping in C language.

9. Function recursion

9.1 What is recursion

The programming technique in which a program calls itself is called recursion .
Recursion is widely used as an algorithm in programming languages.
A process or function has a method of calling itselfdirectly or indirectly in its definition or description . It usually converts a large and complex problem layer by layer into a smaller-scale
problem similar to the original problem to solve. The recursive strategy only A small number of programs can be used to describe the multiple repeated calculations required in the problem-solving process, which greatly reduces
the amount of code in the program.


9.2 Two necessary conditions for using recursion

  • There are constraints, and when this constraint is met, the recursion will not continue.
  • This limit is getting closer and closer after each recursive call.

9.3 Recursive exercise (drawing explanation)

9.31 print 1 2 3 4

Let's look at a piece of code first:

#include <stdio.h>//打印1 2 3 4
void my_print(int n)
{
    
    
 if(n>9)
 {
    
    
   my_print(n/10);
 }
 printf("%d ", n%10);
}
int main()
{
    
    
 int num = 1234;
 print(num);
 return 0;
}

This code is to print our 1234 as 1 2 3 4; let's see if it satisfies the two conditions of our recursion:

insert image description here


Now let's analyze this recursion by drawing a picture:

insert image description here

When we entered the function for the first time, our n>9 ​​was established, so we entered the second-level function again. Note that our first-level function is still in the if statement at this time. When we second Enter the function, n>9 is also established, and then enter the if loop to enter the third layer of recursion. At this time, our first layer function and second layer function are in the if statement. And so on until n>9 is not established function, we do not enter the if loop at this time, but directly print out 1. When our innermost layer of recursion does not meet the conditions, we go back layer by layer and print new content continuously.



9.32 Find the length of a string

We use a recursive method to find the length of the string:

#include <stdio.h>
int my_Strlen(const char*str)
{
    
    
 if(*str == '\0')//递归结束条件
 {
    
    
   return 0;
 }
 else
 {
    
    
   return 1+my_Strlen(str+1);//str+1,不断往后读取字符串,不断靠近递归结束条件
 }
}
int main()
{
    
    
 char *p = "abcd";
 int len = my_Strlen(p);
 printf("%d\n", len);
 return 0;
}

Let's use the drawing method to analyze how this recursion works:
insert image description here
Both of these recursions have a characteristic, that is, when we recurse to the last time, we have to push back to get the final value



9.4 Common Mistakes with Recursion

Let's take a look at the following code:

int fib(int n)//用递归求斐波那契数列
{
    
    
 if (n <= 2)//递归结束条件         
 return 1;
    else
    return fib(n - 1) + fib(n - 2);//n不断减少,越来越接近结束递归的条件
}

And another code:

int factorial(int n)//求n的阶乘
{
    
    
 if(n <= 1)
 return 1;
 else
 return n * factorial(n-1);
}

The code is very simple, and our derivation form is similar to the previous steps, so the recursive derivation of these two questions is left to you to implement by yourself


But we found a problem;

  • When using the fib function, it is particularly time-consuming if we want to calculate the 50th Fibonacci number.
  • Using recursion to find the factorial of 10000 (regardless of the correctness of the result), the program will crash.

_This
place has to mention our recursive common mistakes: stack overflow (stack overflow)

Stack overflow:

  • When debugging the factorial function, if your parameters are relatively large, an error will be reported:
    information such as stack overflow (stack overflow).
  • The stack space allocated by the system to the program is limited, but if there is an infinite loop, or (dead recursion), this may lead to the creation of stack space all the time, and eventually the stack space will be exhausted. This phenomenon is called stack overflow .

We can see that the error message is: stack overflow

insert image description here


So how should we solve this problem when we encounter this kind of recursive error?
Below I will give some methods for reference only:

  1. Rewrite recursively to non-recursively .
  2. Use static objects instead of nonstatic local objects. In the design of recursive functions, static objects can be used instead of
    nonstatic local objects (that is, stack objects), which not only reduces the overhead of generating and releasing nonstatic objects for each recursive call and return
    , but also saves the intermediate state of recursive calls , and is
    accessible by individual calls to .

We can optimize the code for factorial and Fibonacci numbers:

//求n的阶乘
int factorial(int n)
{
    
    
        int result = 1;
        while (n > 1)
       {
    
    
             result *= n ;
             n -= 1;
       }
        return result;
}
//求第n个斐波那契数
int fib(int n)
{
    
    
     int result;
     int pre_result;
     int next_older_result;
     result = pre_result = 1;
     while (n > 2)
     {
    
    
           n -= 1
           next_older_result = pre_result;
           pre_result = result;
           result = pre_result + next_older_result;
     }
     return result;
}


10. Summary

The detailed knowledge about the function has been explained, here are still a few points to pay attention to :

  1. There is no need to memorize library functions, you only need to know how to learn library functions
  1. When writing a custom function, choose call by value or call by reference according to the function you want to achieve
  1. Many problems are explained in a recursive form simply because it is clearer than a non-recursive form.
    But iterative implementations of these problems are often more efficient than recursive implementations, although the code is slightly less readable.
  1. When a problem is too complex to implement iteratively, the simplicity of the recursive implementation can compensate for the runtime overhead it brings.

Finally, if there is anything wrong or needs to be added, please discuss it in the comment area!

Guess you like

Origin blog.csdn.net/m0_61982936/article/details/130336988