[초급 C언어] 서사시 작품 전체 네트워크에서 가장 완벽한 기능 설명(완전 건어물 + 추천 모음집)

친절한 팁

안녕하세요 여러분 씨빌트스입니다 제 블로그에 이해하기 어려운 문장 이나 글로 표현하기 어려운 포인트가 있으면 사진 으로 올려드립니다 . 그래서 사진이 있는 내 블로그는 매우 중요합니다 ! ! !

당신이 나에 관심이 있다면첫 번째 블로그를 참조하십시오 !

이 장의 요점

이 장에서는 주로 함수 및 재귀의 기본 사용을 마스터합니다.

  1. 기능은 무엇입니까
  2. 라이브러리 기능
  3. 맞춤 기능
  4. 함수 매개변수
  5. 함수 호출
  6. 함수의 중첩 호출 및 연결된 액세스
  7. 함수 선언 및 정의
  8. 함수 재귀

텍스트의 시작


1. 함수란?


우리는 종종 수학에서 함수의 개념을 봅니다. 하지만 C 언어의 기능을 이해합니까?
Wikipedia 의 기능 정의 :子程序

  1. 컴퓨터 과학에서 서브루틴(영어: Subroutine, procedure, function, routine, method,
    subprogram, callable unit)은 하나 이상의 명령문 블록으로 구성된 대형 프로그램 코드의 특정 부분입니다
    . 특정 작업을 완료하는 역할을 하며 다른 코드와 상대적으로 독립적입니다.
  2. 일반적으로 입력 매개변수와 반환 값이 있어 프로세스를 캡슐화하고 세부 정보를 숨길 수 있습니다. 이러한 코드는 일반적으로 소프트웨어 라이브러리로 통합됩니다
    .

2. C언어의 함수 분류


1. 라이브러리 기능
2. 사용자 정의 기능

2.1 라이브러리 기능

라이브러리 기능이 있는 이유는 무엇입니까?

  1. 우리는 C 언어 프로그래밍을 배울 때 코드가 작성된 후 결과를 알기 위해 기다릴 수 없고 결과를 화면에 인쇄하여 볼 수 있기를 원한다는 것을 알고 있습니다. 이때 우리는 특정 형식으로 정보를 화면에 출력하는 기능(printf)을 자주 사용하게 됩니다.
  2. 프로그래밍 과정에서 문자열 복사 작업(strcpy)을 자주 수행합니다.
  3. 프로그래밍할 때 n의 k승(pow)과 같은 연산을 계산하고 항상 계산합니다.

위에서 설명한 기본 기능과 마찬가지로 비즈니스 코드가 아닙니다. 모든 프로그래머가 개발 과정에서 사용할 수 있으며 이식성을 지원하고 프로그램 효율성을 향상시키기 위해 C 언어의 기본 라이브러리는 프로그래머의 소프트웨어 개발을 용이하게 하는 일련의 유사한 라이브러리 기능을 제공합니다.

간단히 요약하면 C 언어에서 일반적으로 사용되는 라이브러리 함수는 다음과 같습니다.

  • IO 함수( 입력/출력 입력 및 출력 함수 : printf scanf gethcar putchar ...)
  • 문자열 조작 함수(strlen strcmp strcpy strcat ...)
  • 문자 조작 기능(tolower toupper ...)
  • 메모리 조작 기능(memcpy memset memmove memcmp ...)
  • 시간/날짜 기능(시간 ...)
  • 수학 함수(sqrt asb fabs pow ...)
  • 기타 라이브러리 기능

2.1.1 라이브러리 함수 사용법 익히기

그렇다면 라이브러리 기능을 배우는 방법은 무엇입니까?
库函数不需要全部记住,需要学会查询工具的使用。

우선, 모든 사람에게 몇 가지 검색어 도구를 추천합니다(영어는 매우 중요합니다. 최소한 문헌을 이해해야 합니다).

2.2 커스텀 함수

라이브러리 함수가 모든 것을 할 수 있다면 프로그래머는 무엇을 해야 할까요?
더 중요한 것은 사용자 지정 기능입니다.

라이브러리 함수와 마찬가지로 사용자 지정 함수에는 함수 이름, 반환 값 유형 및 함수 매개 변수가 있습니다.
그러나 차이점은 이것들이 모두 우리 자신에 의해 설계되었다는 것입니다 . 이것은 프로그래머에게 많은 공간을 제공합니다.

기능 구성:

ret_type fun_name(para1, *)
{
    
    
	statement;//语句项
}

ret_type 返回类型
fun_name 函数名
para1 函数参数

예를 들어 보겠습니다.

최대 두 수를 찾는 함수를 작성하십시오.

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

두 정수 변수의 내용을 바꾸는 함수를 작성하십시오.

극복하기 어려움 : 예를 들어 빈 병에 간장을 먼저 붓고, 간장병에 식초를 붓고, 마지막으로 식초병에 간장을 붓는다.
지도에서 직접

#include <stdio.h>

void Swap1(int x, int y)
{
    
    
	int tmp = 0;
	tmp = x;
	x = y;
	y = tmp;
}

void Swap2(int* px, int* py)
{
    
    
	int tmp = 0;
	tmp = *px;
	*px = *py;
	*py = tmp;
}

int main()
{
    
    
	int num1 = 1;
	int num2 = 2;
	Swap1(num1, num2);//传值调用,在下面讲解
	printf("Swap1:num1 = %d num2 = %d\n", num1, num2);
	Swap2(&num1, &num2);//传址调用,下面讲解
	printf("Swap2:num1 = %d num2 = %d\n", num1, num2);
	
	return 0;
}

위의 Swap1 Swap2 함수x매개변수는 y모두 형식 매개변수 입니다 . 실제 매개변수는 기본 함수Swap1에 전달되고 Swap2 함수에 전달 됩니다 .pxpynum1num2&num1&num2


3. 함수 매개변수


3.1 실제 매개변수(실제 매개변수)

함수에 전달되는 실제 매개변수는 입니다 实参.

常量인수는 , 变量, 表达式, 가 될 수 있습니다 函数等.

실제 매개변수가 어떤 종류의 양이든 관계없이 함수가 호출될 때 명확한 값을 가져야 이 값이 형식 매개변수로 전달될 수 있습니다
.

3.2 형식 매개변수(formal parameters)

형식 매개변수는 함수가 호출될 때만 인스턴스화(할당된 메모리 단위) 되기 때문에 함수 이름 뒤에 괄호 안의 변수를 참조 하므로 호출됩니다 .
形式参数

형식 매개변수는 함수 호출이 완료되면 자동으로 소멸됩니다.

따라서 형식 매개변수는 함수 내에서만 유효합니다.

3.3 파라미터 결론

메모리 창을 열고 이전 코드의 실제 매개변수형식 매개변수를 분석합니다
여기에 이미지 설명 삽입
.여기서 Swap1 함수가 호출될 때 x와 y가 자체 공간을 가지며 실제 매개변수와 정확히 동일한 내용을 갖는 것을 볼 수 있습니다.

따라서 우리는 간단히 다음과 같이 생각할 수 있습니다 形参实例化之后其实相当于实参的一份临时拷贝.


4. 함수 호출


4.1 값에 의한 호출

함수의 공식 매개변수와 실제 매개변수는 각각 다른 메모리 对形参的修改不会影响实参블록을 차지합니다 .

4.2 참조에 의한 호출

  • 주소에 의한 호출은 함수 외부에서 생성된 변수의 메모리 주소를 함수 매개변수에 전달하여 함수를 호출하는 방식 입니다 .
  • 매개 변수를 전달하는 이 방법은 함수와 함수 외부 변수 사이의 실제 연결을 설정할 수 있습니다 . 즉, 함수 외부 변수를 함수 내부에서 직접 조작할 수 있습니다
    .

(보충설명) 함수의 매개변수 전달 에 대하여 : 제가 구조체를 작성할 때 (구조체 매개변수 전달) 클릭하여 函数传参的时候,参数是需要压栈的확장할 수 있습니다.

4.3 연습

1. 범위의 소수를 판단하는 함수를 작성하십시오.

#include <stdio.h>
#include <math.h>

//判断n是否为素数
int is_prime(int n)
{
    
    
	//试除法,有两种方法
	//2~n-1
	//2~sqrt(n)
	int j = 0;
	for (j = 2; j <= sqrt(n); j++)
	{
    
    
		if (n % j == 0)
			return 0;
	}
	return 1;
}

int main()
{
    
    
	//100-200之间的素数
	int i = 0;
	int cnt = 0;
	for (i = 100; i <= 200; i++)
	{
    
    
		//判断i是否为素数
		if (is_prime(i) == 1)
		{
    
    
			printf("%d ", i);
			cnt++;
		}
	}
	printf("\ncount = %d\n", cnt);

	return 0;
}

2. 판정 범위 내에서 윤년 판정하는 함수 작성

#include <stdio.h>

//int is_leap_year(int y)
//{
    
    
//	if (y % 4 == 0)
//	{
    
    
//		if (y % 100 != 0)
//		{
    
    
//			return 1;
//		}
//	}
//	if (y % 400 == 0)
//		return 1;
//
//	return 0;
//}

// 优化后
//int is_leap_year(int y)
//{
    
    
//	if (((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0))
//		return 1;
//	else
//		return 0;
//}

//再优化后
int is_leap_year(int y)
{
    
    
	return (((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0));
}

int main()
{
    
    
	int y = 0;
	for (y = 1000; y <= 2000; y++)
	{
    
    
		//判断y是否为闰年
		//函数
		//是闰年,返回1
		//不是闰年,返回0
		if (is_leap_year(y) == 1)
		{
    
    
			printf("%d ", y);
		}
	}

	return 0;
}

3. 정수 정렬 배열의 이진 검색을 구현하는 함수를 작성하십시오.

#include <stdio.h>

int main()
{
    
    
	int arr[] = {
    
     1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int reft = 0;
	int right = sz - 1;
	int mid = 0;
	int k = 0;//要查找的元素
	int flag = 0;//找不到
	printf("请输入您要查找的数字:");
	scanf("%d", &k);
	while (reft <= right)//即使是 reft==right 也有一个元素需要查找
	{
    
    
		mid = (reft + right) / 2;//每次二分查找都要求出中间元素的下标
		if ((arr[mid] < k))
		{
    
    
			reft = mid + 1;
		}
		else if (arr[mid] > k)
		{
    
    
			right = mid - 1;
		}
		else
		{
    
    
			flag = 1;//找到了
			break;
		}
	}
	if (1 == flag)
	{
    
    
		printf("找到了,下标是%d\n", mid);
	}
	else
	{
    
    
		printf("没找到\n");
	}
	return 0;
}

4. 함수가 호출될 때마다 num 값을 1씩 증가시키는 함수를 작성하세요.

#include <stdio.h>

void Use(int* p)
{
    
    
	*p = *p + 1;
}

int main()
{
    
    
	int num = 0;
	Use(&num);
	printf("%d\n", num);
	Use(&num);
	printf("%d\n", num);
	Use(&num);
	printf("%d\n", num);

	return 0;
}

5. 기능의 중첩 호출 및 체인 액세스


5.1 중첩 호출

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

함수는 중첩하여 호출할 수 있지만 정의는 중첩할 수 없습니다.

5.2 체인 액세스

한 함수의 반환 값을 다른 함수의 인수로 사용(2가지 예)

//例子1:
#include <stdio.h>
#include <string.h>

int main()
{
    
    
	int len = strlen("abc");

	printf("%d\n", len);
	printf("%d\n", strlen("abc"));//链式访问

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

//解析:所以打印的是4321

6. 함수 선언 및 정의


6.1 함수 선언

  1. 컴파일러에게 함수가 무엇인지 , 매개변수가 무엇인지 , 반환 유형이 무엇인지 알려주십시오 . 그러나 그것이 존재하는지 여부는 함수 선언이 결정할 수 없습니다.
  2. 함수 선언은 일반적으로 함수를 사용하기 전에 나타납니다. 要满足先声明后使用.
  3. 함수 선언은 일반적으로 헤더 파일에 배치됩니다.

xxx.h 내용 : 배치 함수 선언

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

//方法2:完全等价的且简单
#pragam once//这段代码的意思是 防止头文件重复,多次的应用
//后面进行函数的声明
extern int Add(int x, int y);

6.2 기능 정의

기능의 정의는 기능의 특정 구현을 말하며 기능의 기능 구현을 설명합니다.

xxx.c의 내용 : 배치 기능 구현

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

6.3 서브모듈 설계 아이디어

이러한 형태의 하위 파일 작성은 대규모 프로젝트에서 다음과 같은 많은 이점을 제공합니다.

  1. 협동
  2. 캡슐화 및 숨기기

코드 조각이 매우 복잡하고 우리가 직접 작성하고 싶지 않고 다른 사람의 코드를 구입할 수 있지만 소스 코드는 판매하지 않는다고 가정해 보겠습니다 .
그러나 사람들은 정적 라이브러리(.lib 파일)무언가를 컴파일하여 판매 할 것입니다 .

6.3.1 .lib 파일을 컴파일하는 방법(그림을 직접 참조)

여기에 이미지 설명 삽입
.lib 파일의 내용은 바이너리 정보 이며 .lib 파일과 헤더 파일은 귀하에게 판매되므로 귀하는 자신의 코드를 보호할 수 있을 뿐만 아니라 귀하에게 기능을 판매하여 달성할 수 있습니다. 포장 및 숨기기!

6.3.2 정적 라이브러리 추가 방법

여기에 이미지 설명 삽입
그런 다음 실행하십시오!


7. 함수 재귀 및 반복


7.1 재귀란 무엇인가?

프로그램이 자신을 호출하는 프로그래밍 기술을 递归( recursion).

재귀는 프로그래밍 언어에서 알고리즘 으로 널리 사용됩니다 .

프로세스 또는 기능은 정의 또는 설명 직접 또는 간접 방법있습니다 . 일반적 으로 크고 복잡한 문제를 레이어별로 원래 문제와 유사한 소규모 문제로 변환하여 해결합니다 . 재귀 전략은 소량의 이 프로그램은 문제 해결 프로세스에 필요한 여러 번의 반복 계산을 설명할 수 있으므로 프로그램 의 코드 양이 크게 줄어듭니다 .调用自身

재귀에 대해 생각하는 주요 방법은 다음과 같습니다把大事化小 .

7.2 재귀를 위한 두 가지 필수 조건

  • 제약 조건이 있으며 이 제약 조건이 충족되면 재귀가 계속되지 않습니다.
  • 이 제한은 각 재귀 호출 후에 점점 가까워지고 있습니다.

7.2.1 연습 1: (설명을 위한 그림 그리기)

정수 값(부호 없음)을 취하고 해당 비트를 순서대로 인쇄합니다.
예:
입력: 1234, 출력 1 2 3 4.

참조 코드:

#include <stdio.h>

void print(unsigned int n)
{
    
    
	if (n > 9)//这个就是限制条件
	{
    
    
		print(n / 10);
	}
	printf("%d ", n % 10);
}

int main()
{
    
    
	unsigned int num = 1234;
	print(num);

	return 0;
}

도면 설명:

1. 극복해야 할 어려움:
여기에 이미지 설명 삽입
2. 분석 아이디어:
여기에 이미지 설명 삽입

3. 보충 설명:
코드에 추가하지 않으면 어떻게 됩니까 if (n > 9)//这个限制条件?
여기에 이미지 설명 삽입
이때 실행 시 멈춘 후 디버깅 시 오류가 보고됩니다.
재귀가 무한히 계속되면 오류라고 합니다 栈溢出. 함수 스택 프레임의 생성과 소멸에
여기에 이미지 설명 삽입
대해서는 [Advanced C Language]로 설명하는 글을 작성할 예정이니 기대해주세요! 시간이 되면 링크를 직접 게시하세요! !

7.2.2 실습 2: (그리기 및 설명)

문자열의 길이를 찾기 위해 임시 변수 생성을 허용하지 않는 함수를 작성하십시오.

참조 코드:

#include <stdio.h>

int my_strlen(char* s)
{
    
    
	if (*s != '\0')
	{
    
    
		return 1 + my_strlen(s + 1);
	}
	else
	{
    
    
		return 0;
	}
}

int main()

{
    
    
	char arr[20] = "abcd";//数组名arr是数组首元素的地址 — char*
	int len = my_strlen(arr);
	printf("%d\n", len);

	return 0;
}

도면 설명:

1. 어려움 극복:
여기에 이미지 설명 삽입
2. 분석적 사고:
여기에 이미지 설명 삽입
3. 보충 설명:

제안: 디자인 타임의 기능 能不创建全局变量就不创建全局变量.

7.2.3 재귀의 몇 가지 고전적인 문제

이 부분에 대해서는 따로 블로그를 작성해서 방법을 알려드리도록 하겠습니다!
그래서 너무 많은 소개는 하지 않고 링크는 추후에 직접 올릴 예정이니 기대해주세요!

  1. 하노이 탑 문제
  2. 개구리 점프 계단 문제

7.3 반복

먼저 함수 재귀를 사용하여 다음 질문을 해봅시다!

7.3.1 연습 3:

n의 계승을 찾으십시오. (오버플로는 고려하지 않음)

키 코드 참조:

int factorial(int n)
{
    
    
	if (n <= 1)
		return 1;
	else
		return n * factorial(n - 1);
}

7.3.2 연습 4:

n번째 피보나치 수를 구합니다. (오버플로는 고려하지 않음)

키 코드 참조:

int fib(int n)
{
    
    
	if (n <= 2)
		return 1;
	else
		return fib(n - 1) + fib(n - 2);
}

하지만 문제를 발견했습니다 .

  1. fib 함수를 사용할 때 50번째 피보나치 수를 계산하려는 경우 특히 시간이 많이 걸립니다.
  2. 계승 함수를 사용하여 10000의 계승을 찾으면(결과의 정확성에 관계없이) 프로그램이 충돌합니다.

왜?
우리는 fib 함수를 호출하는 동안 실제로 많은 계산이 반복된다는 것을 발견했습니다.

코드를 약간 수정하면:

int count = 0;//全局变量
int fib(int n)
{
    
    
	if (n == 3)//第三个斐波那契数
		count++;
	if (n <= 2)
		return 1;
	else
		return fib(n - 1) + fib(n - 2);
}

마지막으로 매우 큰 값인 카운트를 출력합니다.

문제는 다음과 같습니다.

  • factorial 함수를 디버깅할 때 매개변수가 상대적으로 크면 이러한 정보와 같은 오류가 보고됩니다 stack overflow(栈溢出).
  • 시스템에서 프로그램에 할당하는 스택 공간은 제한적이지만 무한 루프 또는 (dead recursion)이 발생하면 항상 스택 공간이 생성될 수 있으며 결국 스택 공간이 고갈됩니다. 현상을 스택 오버플로우라고 합니다.

따라서 위의 문제를 해결하는 방법은 다음과 같습니다.

  1. 재귀적으로 비재귀적으로 재작성합니다.
  2. 비정적 로컬 객체 대신 정적 객체를 사용하십시오. 재귀 함수 설계에서 비정적 로컬 객체(즉, 스택 객체) 대신 정적 객체를 사용할 수 있습니다. 이렇게 하면 각 재귀 호출 및 반환에 대해 비정적 객체를 생성하고 해제하는 오버헤드를 줄일 수 있을 뿐만 아니라 중간 상태를 저장합니다. 재귀 호출의 각 호출 계층에서 액세스할 수 있습니다.

예를 들어 다음 코드는 비재귀적 방식으로 이를 구현합니다.

//求n的阶乘
int factorial(int n)
{
    
    
	int result = 1;
	while (n > 1)
	{
    
    
		result *= n;
		n -= 1;
	}
	return result;
}
//求第n个斐波那契数
int Fib(int n)
{
    
    
	int a = 1;
	int b = 1;
	int c = 1;

	while (n > 2)
	{
    
    
		c = a + b;
		a = b;
		b = c;
		n--;
	}

	return c;
}

7.3.1 요약

  1. 재귀적 방법 과 비재귀적 방법을 모두 사용할 수 있고 문제가 명확하지 않은 경우 에는 재귀적 방법을 사용한다 .
  2. 재귀적으로 작성된 문제를 푸는 것이 매우 간단 하고 명백한 문제가 없을 때 재귀 를 사용하십시오.
  3. 재귀적으로 문제를 푸는 것은 작성하기 쉽지만 명백한 문제가 있는 경우 재귀를 사용할 수 없습니다 .

마지막으로 다음 사항을 상기시켜 드리겠습니다.

  1. 비재귀적 형식보다 명확하기 때문에 많은 문제가 재귀적 형식으로 설명됩니다.
  2. 그러나 이러한 문제를 반복적으로 구현하는 것이 종종 재귀적 구현보다 더 효율적이지만 코드의 가독성은 약간 떨어집니다.
  3. 반복적으로 구현하기에 문제가 너무 복잡한 경우 재귀 구현의 단순성으로 인해 발생하는 런타임 오버헤드를 보상할 수 있습니다.

텍스트의 끝

Supongo que te gusta

Origin blog.csdn.net/Cbiltps/article/details/119814668
Recomendado
Clasificación