Primera introducción al lenguaje C en la formación en lenguaje C (5)

1. ¿Qué es una función?

En matemáticas vemos a menudo el concepto de funciones. ¿Pero conoces las funciones en lenguaje C?
Definición de función en Wikipedia: subrutina

En informática, una subrutina (inglés: subrutina, procedimiento, función, rutina, método,
subprograma, unidad invocable) es una parte del código de un programa grande, que consta de uno o más bloques de instrucciones
. Es responsable de completar una tarea específica y es relativamente independiente de otros códigos.
Generalmente, habrá parámetros de entrada y valores de retorno, lo que proporcionará encapsulación y ocultación de los detalles del proceso. Estos códigos suelen estar integrados en
bibliotecas de software.

2. Clasificación de funciones en lenguaje C.

  1. Funciones de biblioteca
  2. Función personalizada

2.1 Funciones de la biblioteca

¿Por qué hay funciones de biblioteca?

  1. Sabemos que cuando aprendemos programación en lenguaje C, siempre queremos saber el resultado después de escribir un código e imprimir el resultado en nuestra pantalla. En este momento, usaremos con frecuencia una función: imprimir información en la pantalla en un formato determinado (printf).
  2. En el proceso de programación, frecuentemente haremos algún trabajo de copia de cadenas (strcpy).
  3. Al programar también calculamos, y siempre calculamos operaciones como n elevado a la k-ésima potencia (pow).

Al igual que las funciones básicas que describimos anteriormente, no son código comercial. Cada programador puede usarlo durante nuestro proceso de desarrollo. Para admitir la portabilidad y mejorar la eficiencia del programa, la biblioteca básica del lenguaje C proporciona una serie de funciones de biblioteca similares para facilitar a los programadores el desarrollo de software. Entonces, ¿cómo aprender las funciones de la biblioteca? Aquí echamos un vistazo breve: funciones del lenguaje C
Insertar descripción de la imagen aquí

Un breve resumen, las funciones de biblioteca comúnmente utilizadas en lenguaje C son:

  • función E/S
  • Funciones de manipulación de cadenas
  • Funciones de manipulación de personajes.
  • Función de operación de memoria
  • funciones de hora/fecha
  • Funciones matemáticas
  • Otras funciones de la biblioteca

Nos referimos a la documentación para conocer varias funciones de la biblioteca:
strcpy

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

Insertar descripción de la imagen aquí

conjunto de memorias

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

Insertar descripción de la imagen aquí

Nota:
Pero un secreto que debes conocer sobre las funciones de la biblioteca es: cuando uses funciones de la biblioteca, debes incluir el archivo de encabezado correspondiente a #include.
Aquí nos referimos a la documentación para aprender las funciones de la biblioteca anteriores, con el propósito de dominar cómo usar las funciones de la biblioteca.

2.1.1 ¿Cómo aprender a utilizar las funciones de la biblioteca?

¿Necesitas memorizarlos todos? No
es necesario aprender a utilizar herramientas de consulta:

MSDN (Microsoft Developer Network)
www.cplusplus.com
http://en.cppreference.com (versión en inglés)
http://zh.cppreference.com (versión en chino)

El inglés es muy importante. Como mínimo, hay que entender la literatura.

2.2 Funciones personalizadas

Si las funciones de la biblioteca pueden hacerlo todo, ¿qué deben hacer los programadores?
Aún más importantes son las funciones personalizadas.
Las funciones personalizadas tienen el mismo nombre de función, tipo de valor de retorno y parámetros de función que las funciones de biblioteca.
Pero la diferencia es que los diseñamos nosotros mismos. Esto les da a los programadores mucho espacio para jugar.
Composición de funciones:

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

Pongamos un ejemplo:

Escribe una función para encontrar el mayor de dos números enteros.

#include <stdio.h>
//get_max函数的设计
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);
	return 0;
}

3. Parámetros de función

3.1 Parámetros reales (parámetros reales)

Los parámetros que realmente se pasan a la función se denominan parámetros reales.
Los parámetros reales pueden ser: constantes, variables, expresiones, funciones, etc.
No importa qué tipo de cantidad sean los parámetros reales, deben tener valores definidos cuando se llama a la función, para que estos valores puedan transferirse a los parámetros formales.

3.2 Parámetros formales (parámetros formales)

Los parámetros formales se refieren a las variables entre paréntesis después del nombre de la función. Debido a que los parámetros formales solo se instancian (se asignan unidades de memoria) cuando se llama a la función, se denominan parámetros formales. Los parámetros formales se destruyen automáticamente cuando se completa la llamada a la función. Por tanto, los parámetros formales sólo son válidos dentro de funciones.

Los parámetros xey en la función get_max anterior son parámetros formales.
Los números num1 y num2 pasados ​​a get_maxd en la función principal son parámetros reales.

Simplemente podemos pensar que: después de crear una instancia del parámetro formal, en realidad es equivalente a una copia temporal del parámetro real. Cuando se llama a la función get_max, los parámetros formales xey se destruyen automáticamente.

4. Llamada de función

4.1 Llamada por valor

Los parámetros formales y los parámetros reales de una función ocupan diferentes bloques de memoria respectivamente, y las modificaciones a los parámetros formales no afectarán los parámetros reales.

4.2 Llamada por dirección

La llamada por dirección es una forma de llamar a una función pasando la dirección de memoria de la variable creada fuera de la función al parámetro de la función.
Este método de pasar parámetros puede establecer una conexión real entre la función y las variables fuera de la función, es decir, las variables fuera de la función se pueden manipular directamente
dentro de la función.

4.3 Ejercicio

  1. Escribe una función para comprobar si un número es primo o no.
  2. Escribe una función para determinar si un año es bisiesto.
  3. Escriba una función para implementar una búsqueda binaria de una matriz ordenada por números enteros.

Respuesta de referencia

//写一个函数可以判断一个数是不是素数
//是素数返回1
//不是素数返回0

#include <math.h>

//判断素数
int is_prime(int n)
{
    
    
	int j = 0;
	for (j = 2; j <= sqrt(n); j++)
	{
    
    
		if (n % j == 0)
		{
    
    
			return 0;
		}
	}
	return 1;
}

int main()
{
    
    
	int i = 0;
	int count = 0;
	for (i = 100; i <= 200; i++)
	{
    
    
		//判断i是否为素数
		if (is_prime(i) == 1)
		{
    
    
			printf("%d ", i);
			count++;
		}
	}

	printf("\ncount = %d\n", count);

	return 0;
}

//写一个函数判断一年是不是闰年
//是闰年返回1
//非闰年返回0
int is_leap_year(int y)
{
    
    
	if (((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0))
		return 1;
	else
		return 0;
}

//2. 实现函数
int is_leap_year(int y)
{
    
    
	return  ((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0);
}

int main()
{
    
    
	int y = 0;
	int count = 0;
	for (y = 1000; y <= 2000; y++)
	{
    
    
		if (is_leap_year(y) == 1)
		{
    
    
			printf("%d ", y);
			count++;
		}
	}
	printf("\ncount = %d\n", count);

	return 0;
}

//写一个函数,实现一个整形有序数组的二分查找。
int binary_search(int arr[], int k, int sz)
{
    
    
	int left = 0;
	int right = sz - 1;

	while (left <= right)
	{
    
    
		//int mid = (left + right) / 2;
		int mid = left + (right - left) / 2;
		if (arr[mid] < k)
		{
    
    
			left = mid + 1;
		}
		else if (arr[mid] > k)
		{
    
    
			right = mid - 1;
		}
		else
		{
    
    
			return mid;
		}
	}
	return -1;
}

int main()
{
    
    
	int arr[] = {
    
     1,2,3,4,5,6,7,8,9,10 };
	int k = 7;
	int sz = sizeof(arr) / sizeof(arr[0]);
	//二分查找
	//找到了:返回下标
	//找不到:返回-1
	int ret = binary_search(arr, k, sz);
	if (ret == -1)
		printf("找不到\n");
	else
		printf("找到了,下标是:%d\n", ret);

	return 0;
}

5. Llamadas anidadas y acceso encadenado a funciones.

Las funciones y funciones se pueden combinar según las necesidades reales, es decir, se llaman entre sí.

5.1 llamadas anidadas

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

Las funciones se pueden llamar de forma anidada, pero las definiciones no se pueden anidar.

5.2 Acceso a la cadena

Utilice el valor de retorno de una función como argumento para otra función.

#include <stdio.h>
int main()
{
    
    
	printf("%d", printf("%d", printf("%d", 43)));
	//结果是啥?
	//注:printf函数的返回值是打印在屏幕上字符的个数
	return 0;
}

6. Declaración y definición de funciones.

6.1 Declaración de función

  1. Dígale al compilador cómo se llama la función, cuáles son los parámetros y cuál es el tipo de retorno. Pero si existe o no, la declaración de función no puede decidir.
  2. La declaración de una función generalmente aparece antes del uso de la función. Primero debe declararse y luego usarse.
  3. Las declaraciones de funciones generalmente se colocan en archivos de encabezado.

6.2 Definición de función

La definición de función se refiere a la realización específica de la función y explica la realización funcional de la función.
El contenido de test.h
coloca la declaración de la función.

#ifndef __TEST_H__
#define __TEST_H__
//函数的声明
int Add(int x, int y);
#endif //__TEST_H__

El contenido de test.c
coloca la implementación de la función.

#include "test.h"
//函数Add的实现
int Add(int x, int y)
{
    
    
 return x+y;
}

7. recursividad

7.1 ¿Qué es la recursividad?

La técnica de programación en la que un programa se llama a sí mismo se llama recursividad.
La recursividad como algoritmo se utiliza ampliamente en los lenguajes de programación. Un proceso o función tiene un método para llamarse a sí mismo directa o indirectamente en su definición o descripción. Generalmente transforma un problema grande y complejo en un problema más pequeño similar al problema original a resolver. La estrategia recursiva solo se necesita una pequeña cantidad de programas. para describir los múltiples cálculos repetidos necesarios en el proceso de resolución de problemas, lo que reduce en gran medida la cantidad de código del programa.
La forma principal de pensar sobre la recursividad es hacer que las cosas grandes sean más pequeñas.

7.2 Dos condiciones necesarias para la recursividad

  • Hay restricciones y, cuando se cumple esta restricción, la recursividad no continuará.
  • Este límite se acerca cada vez más después de cada llamada recursiva.

7.2.1 Ejercicio 1

Toma un valor entero (sin signo) e imprime sus bits en orden.
Por ejemplo:
entrada: 1234, salida 1 2 3 4

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

7.2.2 Ejercicio 2

Escribe una función que no permita la creación de variables temporales para encontrar la longitud de la cadena.

Código de referencia:

#include <stdio.h>
int Strlen(const char* str)
{
    
    
	if (*str == '\0')
		return 0;
	else
		return 1 + Strlen(str + 1);
}
int main()
{
    
    
	char* p = "abcdef";
	int len = Strlen(p);
	printf("%d\n", len);
	return 0;
}

7.3 Recursión e iteración

7.3.1 Ejercicio 3

Encuentra el factorial de n. (no se considera desbordamiento)

Código de referencia:

//递归
int Fac(int n)
{
    
    
	if (n <= 1)
		return 1;
	else
		return n * Fac(n - 1);
}
//n! = 1*2*3*4...*n
//循环(迭代)
int Fac(int n)
{
    
    
	int r = 1;
	int i = 0;
	for (i = 1; i <= n; i++)
	{
    
    
		r = r * i;
	}
	return r;
}

int main()
{
    
    
	int n = 0;
	scanf("%d", &n);
	int ret = Fac(n);
	printf("%d\n", ret);
	return 0;
}

7.3.2 Ejercicio 4

Encuentra el enésimo número de Fibonacci. (no se considera desbordamiento)

Código de referencia:

//求第n个斐波那契数
//1 1 2 3 5 8 13 21 34 55 ...
//前2个的数的和是第三个数

//递归写法
int Fib(int n)
{
    
    
	if (n <= 2)
		return 1;
	else
		return Fib(n - 1) + Fib(n - 2);
}



int main()
{
    
    
	int n = 0;
	scanf("%d", &n);
	int ret = Fib(n);
	printf("%d\n", ret);
	//printf("count = %d\n", count);
	return 0;
}

Pero descubrimos que algo andaba mal;

  • Cuando se utiliza la función Fib (método recursivo), lleva mucho tiempo si queremos calcular el número 50 de Fibonacci.
  • Si utiliza la función factorial para encontrar el factorial de 10.000 (independientemente de la exactitud del resultado), el programa fallará.

¿por qué?

  • Descubrimos que muchos cálculos en realidad se repiten durante el proceso de llamada de la función fib.
    Si modificamos un poco el código:
int count = 0;//全局变量
int Fib(int n)
{
    
    
	if (n == 3)
		count++;
	if (n <= 2)
		return 1;
	else
		return Fib(n - 1) + Fib(n - 2);
}

Finalmente, generamos el recuento, que es un valor muy grande.
Entonces, ¿cómo mejoramos?

  • Al depurar la función factorial, si sus parámetros son relativamente grandes, se informará un mensaje de error como desbordamiento de pila.
    El espacio de pila asignado al programa por el sistema es limitado, pero si ocurre un bucle infinito o (recursión muerta), esto puede hacer que el espacio de pila se asigne continuamente y, eventualmente, el espacio de pila se agote. Este fenómeno se llama pila. desbordamiento. .

Entonces, cómo resolver los problemas anteriores:

  1. Reescribe la recursividad en no recursiva.
  2. Utilice objetos estáticos en lugar de objetos locales no estáticos. En el diseño de funciones recursivas, se pueden usar objetos estáticos en lugar de
    objetos locales no estáticos (es decir, objetos de pila), lo que no solo reduce la sobrecarga de generar y liberar objetos no estáticos durante cada llamada y retorno recursivo, sino que los objetos estáticos también pueden ahorrar el tiempo intermedio. estado de las llamadas recursivas. , y cada capa de llamada puede acceder a él.

Por ejemplo, el siguiente código se implementa de forma no recursiva:

//迭代写法
int Fib(int n)
{
    
    
	int a = 1;
	int b = 1;
	int c = 1;
	//5 4 3
	while (n >= 3)
	{
    
    
		c = a + b;
		a = b;
		b = c;

		n--;
	}
	return c;
}

int main()
{
    
    
	int n = 0;
	scanf("%d", &n);
	int ret = Fib(n);
	printf("%d\n", ret);
	//printf("count = %d\n", count);
	return 0;
}

pista:

  1. Muchos problemas se explican de forma recursiva simplemente porque es más claro que una forma no recursiva.
  2. Pero las implementaciones iterativas de estos problemas suelen ser más eficientes que las implementaciones recursivas, aunque el código es un poco menos legible.
  3. Cuando un problema es demasiado complejo para resolverlo de forma iterativa, la simplicidad de la implementación recursiva puede compensar la sobrecarga del tiempo de ejecución.

Si todavía está interesado en la recursividad, aquí hay varios temas clásicos de la recursividad de funciones (investigación independiente):

  1. El problema de las torres de Hanoi
  2. problema de rana saltando escaleras

Supongo que te gusta

Origin blog.csdn.net/weixin_44752340/article/details/130538684
Recomendado
Clasificación