이번 포스팅에서 우리가 배워볼 것은 다음과 같다.
- 함수의 개념 & 형태
- 함수의 필요성
- 함수 유형
- 함수에서의 return 문장
- 함수의 매개변수
- 디폴트 인수(default argument)
- 함수 호출에서의 매개변수 전달 방법
- 함수 원형(prototype)
- 함수와 변수의 속성
- 변수의 속성
- 지역 변수(local variable)
- 정적 변수(static variable)
- 전역 변수(global variable)
- 중복함수
- 인라인 함수
- 함수 예제 풀기
- 최댓값 구하는 함수
- 정수의 제곱 값을 구하는 함수
- swap() 함수 만들기
- 정수, 실수, 문자를 모두 출력할 수 있는 print() 함수를 중복 함수로 정의하고 사용해보자.
- 디폴트 매개 변수 실습하기
1. 함수의 개념 & 형태
- 함수(function)란 특정한 작업을 수행하는 독립적인 부분을 의미한다. 다른 말로 특정 작업을 수행하여 그 결과를 반환하는 블랙박스와 같은 것이다.
- 함수를 호출하여 사용하는 것을 함수 호출(function call) 이라고 한다.
- 함수가 호출이 되면(즉, 프로그램 실행시 함수를 만나면) 실매개변수(인자)로부터 형식매개변수(매개변수)로 전달이 일어난다.
- 함수 안의 문장들이 순차적으로 실행이 될 것이고 문장의 실행이 끝나면(return 문을 만나든지 혹은 함수 몸체의 끝을 만나면) 호출한 위치로 되돌아 간다.
- 이러한 함수는 입력을 받아 특정한 작업을 실행하고 결과를 생성(반환)한다.
프로그램은 여러 개의 함수들로 이루어지고 함수 호출을 통하여 서로 서로 연결이 된다. 프로그램 실행할 때 제일 먼저 호출되는 함수는 main() 함수이다.
아래 코드와 같이 함수의 정의부에서는 반환형(return type), 함수 헤더(function header), 함수 몸체(function body)로 구성되어 있다.
int add(int x, int y) // 함수 정의 부 - 함수가 반환하는 값의 유형을 가장 먼저 적어준다.
{
int result;
result = x + y;
return result;
}
더하는 기능을 가진 함수가 있다면 int를 함수의 반환형, add는 함수 이름, int x, int y를 각각 형식매개변수1, 2(매개변수1, 2)라고 부른다. 함수의 scope는 { } 내이며 { } 를 함수의 몸체라고 하고, int add(int x, int y) 부분을 통틀어서 함수의 헤더라고 부른다.
즉, 위에서 말한 것들을 정리하자면
반환형 함수이름(형식 매개변수 리스트){
함수 몸체 // 코드 문장들
}
인 것이다. 함수의 이름을 써주고 함수 몸체에 함수가 필요로 하는 데이터를 나열한 다음 세미콜론을 붙이면 된다.
2. 함수의 필요성
#include <stdio.h>
int main(void)
{
int i;
for(i = 0; i < 10; i++)
printf("*");
printf(“\n");
for(i = 0; i < 10; i++)
printf("*");
printf(“\n");
for(i = 0; i < 10; i++)
printf("*");
printf(“\n");
return 0;
}
// 출력 결과
**********
**********
**********
다음 코드는 한 줄에 *을 10번 출력하여 총 30개의 *을 출력하는 코드이다.
보다시피 같은 코드를 3번 반복해서 사용하기 때문에 비효율적이다. 이러한 문제를 해결하기 위해 함수를 사용한다.
#include <stdio.h>
void print_star()
{
int i;
for(i = 0; i < 10; i++)
printf("*");
}
int main(void)
{
print_star();
printf("\n");
print_star();
printf("\n");
print_star();
printf("\n");
return 0;
}
이렇게 print_star() 함수에서 한 줄에 *을 10번 찍는 기능을 넣으면 main 함수에서 print_star() 로 불러주면 코드가 중복되는 것을 막을 수 있다.
또한 한 번 작성된 함수는 여러 번 재사용할 수 있고, 함수를 사용하면 전체 프로그램을 모듈로 나눌 수 있기에 개발 과정이 쉬워지고 보다 체계적이 되면서 유지보수도 쉬워진다.
3. 함수 유형
1) 라이브러리 함수
- 라이브러리 함수는 이미 언어에 정의되어 있고 컴파일 되어 있기에 함수의 원형(prototype)에 맞게 호출해서 사용하면 된다.
- 표준 라이브러리 헤더 파일에서 원형을 제공한다.
- 예) random 라이브러리, sqrt 라이브러리
2) 사용자 정의 함수
- 사용자가 직접 함수를 작성한다.
4. 함수에서의 return 문장
- return 문( 형식 : return 식;)
- 함수 결과로서 식의 값을 함수의 반환형에 맞게 반환하고 함수 수행을 끝낸다.(함수를 빠져 나간다.)
- 반환할 값이 없는 것( 형식 : return; )
- 함수의 반환형이 void며 그냥 함수 수행을 끝낸다.(함수를 빠져 나간다.)
- return 코드 없는 void 반환형
- return 코드 없는 void 함수에서는 함수의 수행 중 함수의 마지막을 만나면 함수를 빠져 나간다.(return 문장의 수행과 동일한 효과를 보인다.)
- 아래 코드에서 보면 print_hello() 함수는 결과값을 return 하지 않는 함수이며 인자(실매개변수)를 받지 않는 함수를 정의할 때에도 인자가 들어갈 자리에 void를 넣어주면 된다.(물론 이 때 void는 생략해도 된다.)
#include<stdio.h>
void print_hello(void)
{
cout <<“Hello World!\n”;
}
int main(void)
{
print_hello();
return 0;
}
5. 함수의 매개변수
함수가 자료를 전달 받기 위해 매개변수라는 녀석을 사용한다. 용어의 의미가 서로 헷갈릴 수 있으니 잘 알아두자!
- 실매개변수(인자, actual parameter, argument) : 함수 사용(호출)에 있는 인자
- 형식매개변수(매개변수, formal parameter, parameter) : 함수 정의에 있는 인자
#include <stdio.h>
int add(int x, int y) // int x, int y를 형식매개변수라고 한다.
{
int result;
result = x + y;
return result;
}
int main()
{
int value;
value = add(10, 30); // 10, 30을 실매개변수라고 한다.
printf("%d \n", value);
return 0;
}
1. 디폴트 인수(default argument)
인수를 전달하지 않아도 디폴트값을 대신 넣어주는 기능을 디폴트 인수라고 한다.
#include <iostream>
using namespace std;
void display(char c = '*', int n = 10) { // 디폴트 인수
for (int i = 0; i < n; i++) {
cout << c;
}
cout << endl;
}
int main() {
cout << "아무런 인수가 전달되지 않는 경우: \n";
display();
cout << "\n 첫 번째 인수만 전달되는 경우 : \n";
display('#');
cout << "\n모든 인수가 전달되는 경우 : \n";
display('#', 5);
return 0;
}
6. 함수 호출에서의 매개변수 전달 방법
매개변수 전달방법에는 대표적으로 2가지가 있다.(자세한 내용은 https://juni-tech.tistory.com/71 참고하자.)
- call by value(값에 의한 전달) : 실매개변수의 값을 형식매개변수로 복사하여 전달하는 기법
- 실매개변수의 값이 호출되는(called) 함수의 형식매개변수에 복사가 된다.
- 호출되는(called) 함수에서 형식매개변수의 값을 변경해도 호출하는 함수(caller) 쪽에서 실매개변수 값에 영향을 미치지가 않는다.(애초에 복사를 한 것이라 실체가 다르기 때문!)
- call by reference(참조에 의한 전달) : 실매개변수의 주소를 형식매개변수로 전달하는 기법
- 다시 말해, 원본 인수가 함수에 전달되는 방법이다.
- 즉, 실체가 똑같게 된다.
- 함수 안에서 매개변수는 변경하면 원본 인수가 변경된다.
C언어는 call by value 형식을 채택하고 있다. 아래의 예시 코드에서 설명을 하자면..
#include <stdio.h>
void swap(int x, int y) {
int temp;
temp = x;
x = y;
y = temp;
}
int main() {
int s = 5, t = 10;
cout <<"s=" << s <<" t=" << t << endl;
swap(s, t);
cout <<"s = " << s <<" t = " << t << endl;
return 0;
}
이 코드는 s와 t의 값을 바꿀려고 만든 코드인데, 실제로는 바뀌지가 않는다. 왜냐하면 함수 swap(5, 10)를 호출하면 실매개변수 s, t 값이 함수 swap의 형식매개변수 x, y로 각각 전달이 되는데 원본을 복사해서 전달하기 때문이다. 함수 swap에서 x와 y의 값을 바꾸지만, 수행이 끝난 후, 호출한 곳으로 돌아오면 s와 t의 값은 변하지 않는 것이다.
아래 코드는 call by reference에 의한 방식으로 사용했다.
#include <iostream>
using namespace std;
void modify (int& x, int& y) {
x = x * 2;
y = y * 2;
}
int main() {
int a = 2, b = 3;
modify(a, b);
return 0;
}
여기서 참조자(refernece)라는 말이 등장하게 되는데, 변수의 별명이며 '&' 기호를 사용한다.
7. 함수 원형(prototype)
컴파일은 위에서 아래로 진행이 되기 때문에 함수의 배치순서는 중요하다. 만약 함수 정의부가 함수 호출(함수 사용) 이후에 작성되어져 있다면, 함수를 호출할 때 해당 함수는 순서상 컴파일 되지 않았기 때문에 호출이 불가능하다. 이러한 문제를 해결하기 위해 이후에 등장하는 함수에 대한 정보를 미리 앞에서 컴파일러에게 제공해서 이후에 등장하는 함수의 호출문장이 컴파일 가능하게 도울 수 있는데 이렇게 재공되는 함수의 정보를 가리켜 '함수의 원형(prototype)'이라 한다. 아래 예시를 보면 사용법과 어떻게 쓰여지는지 한눈에 알아볼 수 있을 것이다.
#include <stdio.h>
int compute_sum(int n); // 함수 원형 - 함수의 이름, 매개변수, 반환형을 함수가 정의되기 전에 미리 한 번 써주는 것
int main(void)
{
int sum;
sum = compute_sum(100);
printf(“sum=%d \n”, sum);
}
int compute_sum(int n)
{
int i;
int result = 0;
for(i = 1; i <= n; i++)
result += i;
return result;
}
이 코드는 함수의 원형을 사용해야 하는 경우이다. 함수 정의가 함수 호출보다 먼저 오면 함수 원형을 선언해야 한다.
#include <stdio.h>
int compute_sum(int n)
{
int i;
int result = 0;
for(i = 1; i <= n; i++)
result += i;
return result;
}
int main(void)
{
int sum;
sum = compute_sum(100);
printf(“sum=%d \n”, sum);
}
다음 코드는 함수의 원형을 사용하지 않는 경우이다. 함수 정의가 함수 호출보다 먼저 오면 함수 원형을 정의하지 않아도 된다.
8. 함수와 변수의 속성
1. 변수의 속성
- 범위(scope) : 변수가 사용 가능한 범위, 가시성
- 생존 시간(life time) : 메모리에 존재하는 시간
2. 지역 변수(local variable)
- 지역 변수는 블록 안에 선언되어져 있는 변수라 한다. 즉 함수 내에 선언되어져 있는 변수 역시 지역 변수이다.
- 지역 변수는 선언된 이후로부터 함수(블록)내에서만 접근이 가능하다.
- 한 지역(함수, 블럭) 내에 동일한 이름의 변수 선언이 불가능하다.
- 다른 말로 말하자면, 2개의 지역 변수의 이름이 같아도 다른 블록에 있으면, 이들은 다른 변수로 취급이 되므로 사용 가능하다. = 다른 지역에 동일한 이름의 변수 선언이 가능하다.
- 해당 지역을 빠져나가면 지역 변수는 소멸이 된다. 그리고 호출될 때마다 새롭게 메모리 할당이 된다.
- 형식매개변수(매개변수)도 지역 변수의 일종이다.
- 즉, 형식매개변수도 선언된 함수 내에서만 접근이 가능하다.
- 선언된 함수가 반환을 하면(종료하면), 지역 변수와 마찬가지로 형식매개변수도 소멸된다.
- 아래 코드 예시로 알아보자.
int sub(void)
{
int x;
...
while(flag != 0)
{
int y;
...
...
...
}
...
...
}
이 코드에서 지역 변수 int x의 범위는 sub 함수 전체 범위이고, 지역 변수 y의 범위는 while문 전체 블록에 해당한다. 따라서 y는 while문 블록 바깥에서는 사용 불가능하다.
3. 정적 변수(static variable)
- 정적변수는 컴퓨터 프로그래밍에서 정적 변수는 정적으로 할당되는 변수이다.
- 프로그램 실행 전반에 걸쳐 변수의 수명이 유지된다.
- 즉, 함수 안에서 선언되는 정적 변수는 함수 수행이 종료되더라도 메모리 해제가 되지 않고 프로그램 종료시까지 메모리가 할당되어져 있는 것이다.
- 정적 변수가 선언된 함수가 다시 수행하게 된다면 정적 변수의 이전에 저장된 값을 그대로 유지하게 되는 것이다.
- 기억 장소가 콜 스택에서 할당 및 해제되는, 수명이 더 짧은 자동 변수와는 반대되는 개념이다. 즉, 기억 장소가 힙 메모리에 동적 할당되는 객체와 반의어이다.
- 사용법은 변수 선언 시에 자료형 앞에 static를 붙이면 된다.
- 아래 코드 예시로 알아보자.
-
#include <stdio.h> int f(void) { static int count = 0; count++; printf(“count = %d\n", count) return count; } int main(void) { f(); f(); return 0; }
처음 f() 함수를 실행하면 count 변수는 1증가되어서 'count = 1'로 출력이 될 것이다. 만약 static을 안 붙였다면 다시 f()를 실행할 때 'count = 1'로 출력이 되겠지만, static을 붙였기에 count 변수는 정적 변수가 되어서 기존에 있던 1값을 그대로 유지, 출력할 때 1이 더 증가되어서 'count = 2'로 출력하게 될 것이다.
4. 전역 변수(global variable)
- 전역 변수는 어떤 변수 영역 내에서도 접근할 수 있는 변수를 의미하는 전산학 용어이다.
- 지역 변수와 대비되는 개념이다.
- 프로그램 전체 영역 어디에서든 접근이 가능하고, 변경할 수 있기 때문에 지역성이 없다.
- 이것 때문에 될 수 있으면 피해야 하는 것으로 인식하는 경우가 많다.(잘못하다가 변수값이 의도치 않게 변경될 수도 있으니 조심스럽게 사용하자는 것!)
- 프로그램의 시작과 동시에 메모리 공간이 할당되어서 종료시까지 존재하게 된다.
- 전역 변수(global variable)는 함수 외부에 선언하면 되고, 별도의 값으로 초기화하지 않으면 초기값은 0으로 초기화된다.
- 아래 코드 예시로 알아보자.
#include <stdio.h>
void add(int val); // 함수 원형(prototype)
int num; // 전역 변수 : 별도의 값으로 초기화하지 않았느인 0으로 초기화된다.
int main(void)
{
printf("num: %d \n", num);
add(3);
printf("num: %d \n", num);
num++; // 전역 변수 num의 값이 1 증가된다.
printf("num: %d \n", num);
return 0;
}
void add(int val)
{
num -= val; // 전역 변수 num의 값이 val 만큼 증가하게 된다.
}
이처럼 add () 함수는 특정 변수인 num에 대해서 의존성이 생기게 된다.(전역 변수를 조심히 다뤄야 할 원인 중 하나)
9. 중복함수
동일한 이름의 함수를 여러 개 정의하는 것을 중복 함수(overloaded functions)라고 한다.
#include <iostream>
using namespace std;
// 정수값을 제곱하는 함수
int square(int i) {
cout << "square(int) 호출" << endl;
return i * i;
}
// 실수값을 제곱하는 함수
double square(double i) {
cout << "square(double) 호출" << endl;
return i * i;
}
int main() {
cout << square(10) << endl; // 인수가 정수이므로 정수 버전 호출
cout << square(2.0) << endl; // 인수가 실수이므로 실수 버전 호출
return 0;
}
10. 인라인 함수
인라인 함수는 코드 실행 중에 호출되는 함수가 아니라, 컴파일러에 의해 호출 지점에 함수의 코드가 직접 삽입되는 함수이다. 이를 통해 함수 호출에 따른 오버헤드를 줄이고 실행 시간을 최적화할 수 있다. 되게 잘 쓰이는 편이다.
#include <iostream>
// 인라인 함수 정의
inline int Add(int a, int b) {
return a + b;
}
int main() {
int x = 5, y = 10;
int result = Add(x, y); // Add 함수가 호출되지만 컴파일러는 이를 인라인으로 처리할 수 있음
std::cout << "Result: " << result << std::endl;
return 0;
}
위의 예시에서 Add() 함수는 inline 키워드로 정의되었다. 컴파일러는 이 함수 호출을 Add() 함수의 본문으로 대체하여 처리할 수 있다. 이로 인해 함수 호출의 오버헤드를 줄이고 프로그램의 실행 속도를 향상시킬 수 있다.
하지만, 인라인 함수의 코드가 길면 계속 호출할 때 코드가 점차 길어지고 정적 메모리 영역이 차지되므로 주의하자.
11. 함수 예제 풀기
이 문제를 풀기 전 여러분들도 제목을 보고 미리 코딩을 해보고나서 포스팅에 있는 코드와 비교해보자.
1. 최댓값 구하는 함수
#include <iostream>
using namespace std;
// 함수 정의
int max(int x, int y) {
if (x > y)
return x;
else
return y;
}
int main() {
int n;
n = max(2, 3); // 함수 호출
cout << "연산 결과 = " << n << endl;
return 0;
}
2. 정수의 제곱 값을 구하는 함수
정수의 제곱 값을 구하는 함수를 만들어보자.
#include <iostream>
using namespace std;
// 함수 정의
int square(int x) {
return x * x;
}
int main() {
int n;
n = square(3); // 함수 호출
cout << "연산 결과 = " << n << endl;
return 0;
}
3. swap() 함수 만들기
swap(a, b)와 같이 호출하면 변수 a와 변수 b의 값을 교환되도록 만들어보자. ( HINT : 참조자 & 사용)
#include <iostream>
using namespace std;
void swap(int& x, int& y) {
int temp;
temp = x;
x = y;
y = temp;
}
int main() {
int a = 100, b = 200;
cout << "swap 전) a=" << a << " b=" << b << endl;
swap(a, b);
cout << "swap 후) a=" << a << " b=" << b << endl;
return 0;
}
4. 정수, 실수, 문자를 모두 출력할 수 있는 print() 함수를 중복 함수로 정의하고 사용해보자.
#include <iostream>
using namespace std;
// 정수를 출력하는 함수
void print(int num) {
cout << "정수 출력 : " << num << endl;
}
// 실수를 출력하는 함수
void print(double num) {
cout << "실수 출력 : " << num << endl;
}
// 문자를 출력하는 함수
void print(char c) {
cout << "문자 출력 : " << c << endl;
}
int main() {
print(10); // 인수가 정수이므로 정수 버전 호출
print(2.0); // 인수가 실수이므로 실수 버전 호출
print('h'); // 인수가 문자이므로 문자 버전 호출
return 0;
}
4. 디폴트 매개 변수 실습하기
4개의 인자를 받는 sum함수를 만들어서 인자가 2개 넘어가는 경우, 3개 넘어가는 경우, 4개 넘어가는 경우를 출력해보자.
#include <iostream>
using namespace std;
int sum(int a = 0, int b = 0, int c = 0, int d = 0) {
return a + b + c + d;
}
int main() {
cout << "sum(10, 15)=" << sum(10, 15) << endl;
cout << "sum(10, 15, 25)=" << sum(10, 15, 25) << endl;
cout << "sum(10, 15, 25, 30)=" << sum(10, 15, 25, 30) << endl;
return 0;
}
'Develop > C, C++' 카테고리의 다른 글
[C/C++] 문자열(String) (0) | 2023.12.18 |
---|---|
[C, C++] 포인터(Pointer) (0) | 2023.12.18 |
[C/C++] 함수와 포인터를 사용해서 문자열을 뒤집어보자! (0) | 2023.10.12 |
[C++] 포인터를 사용해서 배열의 합계와 평균을 계산해보자! (0) | 2023.10.10 |
[C++/1편 - 실습편] C++ Setting & Variable, Basic Concept (0) | 2023.09.13 |