C언어) 함수 사용

반응형

C언어) 함수 사용

선언한 함수를 사용하고 싶다면 함수의 이름과 필요한 인수를 적어주면 되고, 이를 호출이라고 한다.

함수는 프로그램의 어느 곳에서나(심지어 다른 프로그램에서도) 호출할 수 있다.

 

함수명(매개변수1, 매개변수2, ...);

 

예를 들어, 두 숫자를 더한 결괏값을 반환하는 함수를 만든다면, 더해야 할 두 숫자를 매개변수를 통해 전해주고 return을 사용해 두 숫자의 합을 반환해주면 된다.

 

 

그리고 이를 다음 예제와 같이 코드를 작성하여 구현할 수 있다.

#include <stdio.h>

int plus(int x, int y) //x와 y가 함수 plus의 매개변수다.
{
	return x+y; //x와 y의 합을 반환한다.
}

int main()
{
	//2, 5가 인수이고, 함수에서 7이 반환되어 출력된다.
	printf("2 + 5 = % d", plus(2, 5));
	// (출력) 2+5 = 7
	return 0;
}

위 코드에서 메인 함수 위에 plus라는 두 매개변수의 합을 반환하는 함수를 정의하였다.

메인 함수 안에서 이 함수 plus에 두 매개변수로 각각 25를 전달하자 두 수의 합인 7이 반환되어 출력됐다.

 

그런데 코드를 보면 함수를 따로 선언하지 않고 정의만 하고도 함수를 문제없이 사용하고 있는데,

이는 함수의 정의가 함수의 선언을 포함하기 때문이다.

렇다면 정의만으로도 함수를 사용할 수 있으니 선언은 불필요해 보일 수 있다.

 

다음과 같은 상황을 살펴보자.

#include <stdio.h>

int main()
{
	printf("2 + 5 = % d", plus(2, 5)); 
	// 에러(경고) 발생! 함수를 정의한 위치보다 앞에서 함수 사용
	return 0;
}

int plus(int x, int y)
{
	return x + y; //x와 y의 합을 반환한다.
}

 

함수는 항상 메인 함수와 동등한 위치에 선언해야 하고, 함수가 선언된 위치보다 앞에서 이를 사용하면 안된다. 따라서 위 코드처럼 함수를 사용하는 코드보다 뒤에서 함수를 정의한다면 컴파일 에러가 발생한다.

 

이를 다음과 같이 수정해보자.

#include <stdio.h>

int plus(int x, int y); // 함수 선언

int main()
{
	//2, 5가 인수이고, 함수에서 7이 반환되어 출력된다.
	printf("2 + 5 = % d", plus(2, 5));
	// (출력) 2+5 = 7
	return 0;
}

int plus(int x, int y) // 함수를 사용지점보다 뒤에서 정의하고 있다.
{
	return x + y; //x와 y의 합을 반환한다.
}

이 코드에서는 함수 plus를 먼저 선언하고, 이를 사용하는 코드보다 뒤에서 함수를 정의하고 있다.

이는 컴퓨터에게 선언을 통해 함수의 존재를 미리 알려줬기 때문에 컴파일 에러가 발생하지 않는다.

 

여러 개의 사용자 정의 함수를 사용할 때 함수를 정의하는 순서와 사용하는 순서가 헷갈릴 수 있기 때문에,

이렇게 선언을 미리 해놓으면 컴파일 에러를 피할 수 있다.

 

Visual Studio를 포함한 몇몇 최신 컴파일러는 함수를 미리 선언하지 않고 앞의 코드와 같이 함수가 정의되기 이전에 함수를 사용하더라도 경고만 발생하고 결국 컴파일이 된다.
하지만 원칙에 따라 에러가 발생하는 컴파일러를 사용하는 경우가 분명히 있을 것이기 때문에 꼭 함수는 사용 시점 앞에서 선언하도록 하자.

 

추가로 함수를 사용할 때 지켜야 할 규칙들을 알아보자.

 

1. 함수를 선언할 때는 전역적으로 선언되어야 한다.

 

2. 함수를 선언할 때 지정한 매개변수의 개수만큼 매개변수를 괄호 안에 적어야 하고, 자료형도 일치해야 한다.

 

3. 함수에서는 최대 하나의 값만 반환할 수 있다.

 

전역 vs 지역
전역이란 main() 함수를 포함하여 모든 함수들의 밖을 의미하고,
지역이란 main()함수를 포함하여 어떤 함수의 내부를 의미한다.

 

지금까지 배운 명령어, 연산자들 모두 함수에 해당한다. 이 함수들은 stdio.h 와 같은 헤더에 정의돼있다.

 

반환값이 없는 함수를 정의할 때는 반환자료형을 적는 위치에 다음 예제와 같이 void를 적으면 된다.

#include<stdio.h>

void printSign(int n) // 반환값이 없으므로 void를 작성한다.
{
	if (n > 0)
	{
		printf("양수입니다.\n");
	}

	else if (n < 0)
	{
		printf("음수입니다.\n");
	}

	else
	{
		printf("0 입니다.\n");
	}
}

int main()
{
	printSign(2); // (출력) 양수입니다
	printSign(-1); // (출력) 음수입니다
	printSign(0); // (출력) 0 입니다

	return 0;
}

 

함수 호출 방법

 

함수를 호출하는 방법에는 값에 의한 호출참조에 의한 호출이 있다.

값에 의한 호출은 사용자가 입력한 인수의 값을 매개변수에 복사하여 함수에서 사용한다.

, 함수가 인수에 직접적인 영향을 주지 않는다.

 

반면에 참조에 의한 호출은 인수의 주소가 함수에 전달되고, 이는 함수는 인수에 직접적인 영향을 줄 수 있다.

주소에 대한 개념은 포인터파트에서 자세하게 다루니, 일단 다음 예제를 통해 두 호출 방식의 차이점 정도만 알아보자.

#include<stdio.h>

void swapNumByValue(int a, int b)
{
	int temp;
	temp = a;
	a = b;
	b = temp;
} 

// 참조에 의한 호출을 위해선 선언하는 매개변수의 이름 앞에 *을 적어준다.
void swapNumByReference(int *a, int *b) 
{
	int temp;
	// 매개변수를 사용할 때도, 변수의 값을 사용하려면 *을 적어주어야 한다.
	temp = *a; 
	*a = *b;
	*b = temp;
}

int main()
{
	int x1 = 10;
	int y1 = 20;

	int x2 = 10;
	int y2 = 20;
	
	printf(“함수 사용 이전 x1 = %d, y1 = %d\n”, x1, y1);
	// (출력) 함수 사용 이전 x1 = 10, y1 = 20
	printf(“함수 사용 이전 x2 = %d, y2 = %d\n”, x2, y2);
	// (출력) 함수 사용 이전 x2 = 10, y2 = 20
	swapNumByValue(x1, y1); 
	
	//변수의 주소값을 인수로 하는 참조에 의한 호출을 위해서 	//변수 앞에 주소 연산자(&)를 적어준다.
	swapNumByReference(&x2, &y2); 

	printf(“함수 사용 이후 x1 = %d, y1 = %d\n”, x1, y1);
	// (출력) 함수 사용 이후 x1 = 10, y1 = 20
	printf(“함수 사용 이후 x2 = %d, y2 = %d\n”, x2, y2);
	// (출력) 함수 사용 이후 x2 = 20, y2 = 10

	return 0;
}

 

출력 결과를 보면 값에 의한 호출을 사용한 함수는 x1y1의 값을 바꾸지 않았다.

하지만, 참조에 의한 호출을 사용한 함수는 x2y2의 값을 바꿨다.

 

두 호출의 차이를 간단히 비교하여 정리해보자.

값에 의한 호출 참조에 의한 호출
값의 복사본이 함수에 전달한다. 값의 주소를 함수에 전달한다.
함수 내부에서의 변경사항은 해당 함수 안으로 제한된다. 실제 인수의 값은 바뀌지 않는다. 함수 내부에서의 변경사항이 해당 함수 외부에서도 유효하다. 실제 인수의 값이 바뀔 수 있다.
매개변수와 인수는 다른 메모리에 위치한다. 매개변수와 인수가 동일한 메모리에 위치한다.

 

전역(global) 변수와 지역(local) 변수

 

앞에서 함수는 전역적으로 선언해주어야 한다고 배웠다.

다시 말해, ‘전역적함수 외부에서, ‘지역적함수 내부에서를 의미한다.

 

변수 또한 전역적으로 선언하여 프로그램의 어디서든 사용할 수 있다.

 

반대로 지역적으로 선언한 변수는 그 변수를 선언한 함수 내에서만 사용할 수 있다.

또한 다른 함수의 지역 변수와는 이름이 같아도 된다.

#include<stdio.h>

int global = 10; //함수 외부에서 정의한 전역변수 global

int plusLocal(int num)
{
	int local = 10; //함수 내부에서 정의한 지역변수 local
	return num + 10;
}

int main()
{
	int a = plusLocal(global); //전역변수 global 사용
	printf(“%d”, a);
	printf(“%d”,local); //에러발생! 다른 함수의 지역변수를 사용하려 하고 있다
	return 0;
}

이 코드를 실행하려 하면 메인 함수에서 plusLocal 함수의 지역변수를 사용하려 해서 에러가 발생한다.

 

또 지역변수는 정적 지역 변수와 자동 지역 변수로 나뉜다.

정적 지연 변수는 함수를 호출한 뒤, 다시 함수를 호출해도 변수의 값이 유지가 된다.

하지만 자동 지역 변수는 함수를 호출할 때마다 변수가 새롭게 만들어지고, 함수 실행이 끝나면 변수가 없어진다.

일반적인 지역 변수들은 모두 자동 지역 변수다.

 

다음 예제를 통해 이 차이를 알 수 있다.

#include<stdio.h>

void plusNum()
{
	int num = 0; // 자동 지역 변수
	printf(“num = %d\n”, num);
	num++;
}

void plusStaticNum()
{
	static int num = 0; // 정적 지연 변수
	printf(“static num = %d\n”, num);
	num++;
}

int main()
{
	plusNum(); // (출력) num = 0
	plusNum(); // (출력) num = 0
	plusNum(); // (출력) num = 0
	printf(“\n”);
	
	plusStaticNum(); // (출력) static num = 0
	plusStaticNum(); // (출력) static num = 1
	plusStaticNum(); // (출력) static num = 2

	return 0;
}

자동 지역 변수는 함수내에 num 을 1 증가시키는 명령문이 있지만 함수의 실행이 종료되고 다시 실행되면 num은 다시 0이 된다.

하지만 정적 지역 변수는 1씩 증가시키는 num의 값이 유지되고 있다.

 

마지막으로 다음은 함수를 사용해서 얻을 수 있는 장점들을 정리한 것이다.

1. 동일한 코드를 반복해서 쓰는 것을 피할 수 있다.

2. 대형 프로그램에서 기능들을 함수를 통해 나누어 놓으면 이후에 각각의 기능들을 쉽게 추적할 수 있다.

3. 다른 프로그램에서도 코드를 쉽게 재사용할 수 있다.

4. 특정 함수 내에서 선언된 지역변수는 다른 함수에서는 조작할 수 없기 때문에, 변수에 저장된 데이터를 보호할 수 있다.

 
반응형

'C language' 카테고리의 다른 글

C언어) 배열  (0) 2023.06.27
C언어) 재귀함수  (0) 2023.06.24
C언어) 함수에 대하여 & 함수 정의 방법  (0) 2023.06.23
C언어) 포인터의 이해  (0) 2023.06.21
C언어) 논리 연산  (0) 2023.06.07