Chapter 9 함수
1. 함수
- 어떤 작업을 단 한 번만 수행할지라도, 함수를 사용하면 프로그램이 모듈화되므로 읽기 쉽고, 변경하거나 에러를 수정하기가 쉬워진다.
- 잘못된 함수 형식
void dibs(int x,y,z) => void dibs(int x, int y, int z) 로 작성해야한다.
- 어떤 데이터형을 가지는지 컴파일러에게 알려주기위해 함수 프로토타입 선언 (main()위에서 선언)
- main()안에서 함수호출
- 함수가 무엇을 실행하는지 함수정의 (main() 밖에서 선언)
** 함수 예제1(9.1)
/* 애스터리스크 (*) 40개를 한 라인에 표시하는 함수 */
#include <stdio.h>
#define NAME "GIGATHINK, INC."
#define ADDRESS "101 Megabuck Plaza"
#define PLACE "Megapolis, CA 94904"
#define WIDTH 40
void starbar(void); /* 함수 프로토타입 */
int main(void)
{
starbar();
printf("%s\n", NAME);
printf("%s\n", ADDRESS);
printf("%s\n", PLACE);
starbar(); /* 함수의 사용 */
return 0;
}
void starbar(void) /* 함수의 정의 */
{
int count;
for (count = 1; count <= WIDTH; count++)
putchar('*');
putchar('\n');
}
- starbar()라는 함수를 세군데에 사용하고 있다.
- void starbar(void) : 함수 프로토 타입으로 어떤 유형의 함수인지 컴파일러에게 알려준다.
- void 형 함수 : 함수가 리턴값을 가지지 않는다.
- 프로그램이 starbar()라는 함수를 사용하는데, 그 함수가 리턴값이나 전달인자가 없고 컴파일러는 어딘가에 있을 이함수의 정의를 반드시 찾아야한다고 공표한다.
- 매우 오래된 일부 컴파일러들은 void형을 인식하지 못한다는 점에 유의하라
- 리턴값이 없는 함수들에 대해 int형을 사용해야 한다.
- starbar()안에 들어있는 변수 count는 지역(local)변수이다.
- 지역변수 : starbar()안에서만 알려지는 변수이다. 다른 함수나 main()에서도 부를 수 없다.
++ Void형 함수 예제 추가
- 반환값과 매개변수가 모두 없는 함수
#include <stdio.h>
void print_line(void); // 함수 선언
int main(void)
{
print_line(); // 함수 호출
printf("학번 이름 전공 학점\n");
print_line(); // 함수 호출
return 0;
}
void print_line(void)
{
int i;
for (i = 0; i < 50; i++) // 50번 반복하여 '-' 출력
{
printf("-");
}
printf("\n");
}
#include <stdio.h>
void print_line(void); // 함수 선언
int main(void)
{
print_line(); // 함수 호출
printf("학번 이름 전공 학점\n");
print_line(); // 함수 호출
return 0;
}
void print_line(void)
{
int i;
for (i = 0; i < 50; i++) // 50번 반복하여 '-' 출력
{
printf("-");
}
printf("\n");
}
- 함수정의에서 return문이 없기 때문에 호출할 때 값을 주지 않으면 호출은 수식의 일부가 아닌 독립된 문자으로 쓰이게 된다.
** 함수 예제2(9.2)
/* lethead2.c */
#include <stdio.h>
#include <string.h> /* strlen() 사용하기 위해 헤더 선언 */
#define NAME "GIGATHINK, INC."
#define ADDRESS "101 Megabuck Plaza"
#define PLACE "Megapolis, CA 94904"
#define WIDTH 40
#define SPACE ' '
void show_n_char(char ch, int num);
int main(void)
{
int spaces;
show_n_char('*', WIDTH); /* 기호 상수를 전달인자로 사용한다.*/
putchar('\n');
show_n_char(SPACE, 12); /* 기호 상수를 전달인자로 사용한다. */
printf("%s\n", NAME);
spaces = (WIDTH - strlen(ADDRESS)) / 2;
/* 몇개의 스페이스가 필요한지 */
/* 프로그램이 계산하게 한다. */
show_n_char(SPACE, spaces);/* 변수를 전달인지로 사용한다. */
printf("%s\n", ADDRESS);
show_n_char(SPACE, (WIDTH - strlen(PLACE)) / 2);
/* 표현식을 전달인자로 사용한다. */
printf("%s\n", PLACE);
show_n_char('*', WIDTH);
putchar('\n');
return 0;
}
/* show_n_char() 함수정의 */
void show_n_char(char ch, int num)
{
int count;
for (count = 1; count <= num; count++)
putchar(ch);
}
- show_n_char(char ch , int num) : ch, num이라는 두 개의 전달인자를 사용한다.
- char ch, int num 이라는 것을 컴파일러에게 알린다.
- ch, num은 형식전달인자 또는 형식매개변수 라고 한다.(지역변수)
** 함수 예제3(9.3)
/* 둘 중에 작은 것을 구한다. */
#include <stdio.h>
int imin(int, int);
int main(void)
{
int evil1, evil2;
printf("두 정수를 입력하세요 (끝내려면 q):\n");
while (scanf("%d %d", &evil1, &evil2) == 2)
{
printf("(%d , %d)에서 작은것은 %d\n",
evil1, evil2, imin(evil1,evil2));
printf("두 정수를 입력하세요 (끝내려면 q):\n");
}
printf("종료.\n");
return 0;
}
int imin(int n,int m)
{
int min;
if (n < m)
min = n;
else
min = m;
return min;
}
** 함수 예제4(9.4)
/* 부정확하게 함수를 사용한다.*/
#include <stdio.h>
int imax(); /* 옛날버전의 함수선언 */
int main(void)
{
printf("The maximum of %d and %d is %d.\n",
3, 5, imax(3));
printf("The maximum of %d and %d is %d.\n",
3, 5, imax(3.0, 5.0));
return 0;
}
int imax(n, m)
int n, m;
{
return (n > m ? n : m); // 조건문 n > m이 참이면 n 리턴 거짓이면 m리턴
}
- 이 함수는 프로토타입을 컴파일러에게 알려주지 않았기 때문에 발생한 것이다.
** 함수 예제5(9.5)
/* 함수 프로토타입을 사용하여 먼저 선언해주는 코드 */
#include <stdio.h>
int imax(int, int); /* 프로토타입 컨파일러에게 미리 알려줌 */
int main(void)
{
printf("(%d , %d)에서 큰값은 %d \n",
3, 5, imax(3)); // imax(3) : 호출에서 형식매개변수의 개수가 적다고 에러메시지 출력되는이유
printf("(%d ,%d)에서 큰값은 %d\n",
3, 5, imax(3.0, 5.0));
return 0;
}
int imax(int n, int m)
{
return (n > m ? n : m);
}
- imax(3) : imax() 함수호출 시 선언한 매개변수의 개수가 맞지 않아 에러 메세지 출력
/* 함수 프로토타입을 사용하여 먼저 선언해주는 코드 */
#include <stdio.h>
int imax(int, int); /* 프로토타입 컨파일러에게 미리 알려줌 */
int main(void)
{
printf("(%d , %d)에서 큰값은 %d \n",
3, 5, imax(3,5)); // imax(3)에서 imax(3,5)로 수정
printf("(%d ,%d)에서 큰값은 %d\n",
3, 5, imax(3.0, 5.0));
return 0;
}
int imax(int n, int m)
{
return (n > m ? n : m);
}
*** 전달인자가 가변적일때
- printf() 와 scanf() 같은 몇몇 함수들은 전달인자의 개수가 가변적이다.
예를들면, printf()에서 첫번째 전달인자는 문자열이다. 그러나 나머지 전달인자들의 데이터형과 개수는 고정되어 있지 않다.
- C에서는 int printf(const char *, ...);
첫번째 전달인자가 문자열 이라는것과 지정되지않은 전달인자가 더 있을 수도 있다는 것을 말해 준다.
#include <stdara.h> 헤더파일을 통해 , 매개변수의 개수가 가변적인 함수를 정의하는 표준방법을 제공한다.
2. 재귀 (개념만 알고 가자)
- 재귀 : 함수가 자기 자신을 호출하는 것을 허용한다.
- 재귀를 끝내는 조건 검사를 프로그램에 포함시켜야 한다. 그렇지 않으면 무한정 자기 자신을 호출한다.
#include <stdio.h>
void P(void)
{
printf("재귀함수 입니다."); // 1. 함수 실행
P(); // 2. 자기 자신 호출
}
#include <stdio.h>
void P(int c)
{
if (c == 0) //재귀함수의 탈출조건
return;
printf("재귀함수입니다.%d \n",c);
P(c-1);
}
int main(void)
{
P(10);
return 0;
}
3. 꼬리 재귀 (개념만 알고 가자)
- 꼬리 재귀 : 함수의 끝 return문 바로 앞에 있는 것
- 꼬리 재귀는 루프처럼 동작하기때문에 간단하다.
- 계승(factorial)을 계산하기 위해, 루프 사용 시 용이하다.
// 계승을 계산하기 위해 루프와 재귀를 사용한다.
#include <stdio.h>
long fact(int n);
long rfact(int n);
int main(void)
{
int num;
printf("이 프로그램은 계승을 구합니다.\n");
printf("0-12범위의 한 값을 입력하세요.(끝내려면 q):\n");
while (scanf("%d", &num) == 1)
{
if (num < 0)
printf("음수는 계승이 정의되지 않습니다.\n");
else if (num > 12)
printf("13미만을 입력하세요.\n");
else
{
printf("루프: %d 계승 = %ld\n",
num, fact(num));
printf("재귀: %d 계승 = %ld\n",
num, rfact(num));
}
printf("0-12범위의 한 값을 입력하세요.(끝내려면 q):\n");
}
printf("끝.\n");
return 0;
}
long fact(int n) // 루프사용하는 fact() 함수 정의
{
long ans;
for (ans = 1; n > 1; n--)
ans *= n;
return ans;
}
long rfact(int n) // 재귀사용하는 rfact() 함수 정의
{
long ans;
if (n > 0)
ans= n * rfact(n-1);
else
ans = 1;
return ans;
}
5. 두개의 파일로 된 프로그램 컴파일 ( 개념만 알고가자)
- 두 개의 파일에 함수들이 들어있고 두파일을 컴파일하여 a.out이라는 실행파일을 만든다.
파일 명 : file1.c / file2.c
Linux 에서 두개의 파일을 컴파일 할때
gcc file1.c file2.c - > 실행 시키면 두 개의 file1.o와 file2.o의 오브젝트 파일이 만들어진다.
나중에 file1.c를 수정하고 file2.c를 수정하지 않았을 경우,
gcc file1.c file2.o 명령어를 실행하자 / file1.c 소스만 컴파일하고 file2.o 오브젝트 파일만 결합시킨다.
6. 주소 알아내기 : &연산자
- 포인터 : 주소(address)를 저장하는데 사용되는 변수이다. ( 밑에 8번을 자세히 보자~~)
- 단항연산자(&) : 어떤 변수가 저장되어 있는 주소를 알아낸다.
ex.)
pooh = 24;
(저장되어 있는 주소가 0B76)일때 , printf("%d %p\n",pooh,&pooh); 의 결과 값은 "24 0B76" 출력한다.
* %p : 주소를 출력하기 위한 지정자
** 주소알아내기 예제1(9.12) / 서로 다른 함수에서 사용하는 변수들이 어디에 저장되는지 알아보자
/* 변수들이 어디에 저장되는지 확인한다. */
#include <stdio.h>
void mikado(int); /* 함수선언 */
int main(void)
{
int pooh = 2, bah = 5; /* main()의 지역변수 */
printf("main()에 있는 pooh = %d and &pooh = %p\n",
pooh, &pooh);
printf("main()에 있는 bah = %d and &bah = %p\n",
bah, &bah);
mikado(pooh);
return 0;
}
void mikado(int bah) /* 함수정의 */
{
int pooh = 10; /* mikado()의 지역변수 */
printf("mikado()에 있는 pooh = %d and &pooh = %p\n",
pooh, &pooh);
printf("mikado()에 있는 bah = %d and &bah = %p\n",
bah, &bah);
}
- %p : 16진수로 주소 출력한다.
- 여기서 main()과 mikado() 함수에서 호출한 pooh, bah의 주소값을 보면 서로 다른다는것을 알 수 있다.
이걸로 main()과 mikado()함수에서 사용되는 변수는 지역변수이다. (자신들만의 고유한 값을 지닌다.)
### 필수로 알고가자 !!!!!!!!!!!!!!!!
☑️ 지역변수(local variable, 자동변수)는 중괄호 내부, 함수의 매개변수(Parameter)에서 사용되는 변수를 의미한다.
지역변수의 지역은 함수의 내부, 중괄호 내부를 의미한다.
따라서 함수 안에서만 접근 가능하며, 함수를 벗어나면 사라진다 (= 변수의 메모리 공간이 소멸된다).
지역변수는 초기화하지 않으면 컴파일 에러가 나거나 쓰레기값이 저장된다.
☑️ 전역 변수(global variable)는 지역변수와 반대로 중괄호 외부에 선언되는 변수다.
전역이라는 이름 그대로 어느 지역에서든 참조해서 사용할 수 있다.
전처리기 밑에 선언하며, 반드시 상수로 초기화 해야한다.
초깃값을 지정하지 않으면 디폴트값 0으로 자동 초기화 된다.
전역변수는 모든 곳에서 접근이 가능하고, 프로그램 종료 전엔 메모리가 소멸되지 않는 장점이 있지만
잘못 사용하거나 남용할 경우 관리가 어려워질 수 있다. (유지 보수, 재사용 등)
이를 부분적으로 보완한것이 정적변수다.
☑️ 정적변수(static variable)는 프로그램이 종료되기 전까지 메모리가 소멸되지 않는 변수다.
함수를 벗어나도 변수가 사라지지 않고 유지된다.
초기화할 때 반드시 상수로 초기화 해야한다.
초깃값을 지정하지 않으면 디폴트값 0으로 자동 초기화 된다.
여기까지는 전역변수와 동일하지만, 차이점은 초기화가 딱 한번만 진행된다는 것이다.
정적변수는 프로그램이 시작될 때 생성 및 초기화 되고 프로그램이 끝날 때 사라진다.
또한 함수의 매개변수로 사용할 수 없다는 특징이 있다.
정적변수는 사용범위에 따라 정적 지역변수와 정적 전역변수로 나뉜다.
정적 지역변수(static local variable)는 중괄호 내부에서만 사용할 수 있고, 한번 초기화 되면 이후에 함수 호출 시 값의 초기화를 무시한다.
정적 전역변수(static global variable)는 자신이 선언된 소스 파일에서만 사용할 수 있고, 외부에서는 가져다 쓸 수 없다.
전역변수에 static을 붙이면 변수의 범위를 파일 범위로 제한하는 효과를 내기 때문이다.
참고로 전역변수와 정적변수는 값을 초기화했으면 Data 영역에 생성되고, 초기화하지 않았으면 BSS 영역에 생성되고 0이 들어간다.
7. 호출 함수에 있는 변수 바꾸기 ( 정확히 이해하고 넘어가자!!)
- 두 변수의 맞교환하는 작업 시 temp 라는 임시로 보관해주는 변수가 필요하다
temp = x;
x = y;
y = temp;
** 변수의 맞교환 예제1 (9.13) / ** 변수의 맞교환 예제2 (9.14)
- 교환이 되지 않는 경우1
/* 맞교환 함수 1*/
#include <stdio.h>
void interchange(int u, int v); /* 함수 선언*/
int main(void)
{
int x = 5, y = 10;
printf("교환전 x = %d , y = %d.\n", x , y);
interchange(x, y);
printf("교환후 x = %d , y = %d.\n", x, y);
return 0;
}
void interchange(int u, int v) /* 힘수 정의 */
{
int temp;
temp = u;
u = v;
v = temp;
}
- 교환이 되지 않는 경우2
/* 맞교환 함수 2 */
#include <stdio.h>
void interchange(int u, int v);
int main(void)
{
int x = 5, y = 10;
printf("교환전 x = %d , y = %d.\n", x , y);
interchange(x, y);
printf("교환후 x = %d , y = %d.\n", x, y);
return 0;
}
void interchange(int u, int v)
{
int temp;
printf("교환전 u = %d and v = %d.\n", u , v);
temp = u;
u = v;
v = temp;
printf("교환후 u = %d and v = %d.\n", u, v);
}
- interchange()함수안에서는 맞교환이 이루어졌지만 main()에서 x,y에는 영향을 주지 않았다.
/* 맞교환 함수 2 */
#include <stdio.h>
int interchange(int u, int v); // void를 int로 수정
int main()
{
int x = 5;
int y = 10;
printf("교환전 x = %d , y = %d.\n", x , y);
x = interchange(x, y); // x 값에 u값만 대입이 된다. return이 하나만 받기 때문에
printf("교환후 x = %d , y = %d.\n", x, y);
return 0;
}
int interchange(int u, int v)
{
int temp;
printf("교환전 u = %d and v = %d.\n", u , v);
temp = u;
u = v;
v = temp;
printf("교환후 u = %d and v = %d.\n", u, v);
return (u); // return 추가
// return (v); // return을 따로 밑에 적어도 u값만 리턴해준다.
}
- y의 값이 변화 안 생기는 이유는 return은 호출함수에 하나의 값만 리턴할 수 있다. (x 값만 새로운값을 갖게된다.)
* 두개의 값을 리턴 받고 싶다면, 포인터를 사용할 것
8. 포인터 맛보기
- 포인터(Pointer)
- 기본적으로 주소를 값으로 가지는 변수이다.
- 16진수로 주소값을 나타낸다.
- ptr = &pooh; -> pooh의 주소를 ptr에 대입한다. ( ptr은 변수 / &pooh는 상수)
8-1. 간접연산자 : *
- 간접연산자(*) 과 단항 연산자 (곱하기 *)를 혼동하지 말자!!!
var = *ptr; -> ptr이 가리키고 있는 주소의 값을 변수 var에 대입한다.
### 주소 연산자(&) / 간접 연산자(*)
주소 연산자 (&) : 변수 이름 앞에 사용했을때, 그 변수의 주소를 제공한다.
ex.) &nurse는 변수 nurse의 주소다.
간접 연산자(*) : 포인터 이름이나 주소 앞에 사용했을 때, 그것이 가리키는 주소에 저장되어 있는 값을 제공한다.
*연산자로 주소로부터 값을 얻을 수 있다.
ex.)
nurse = 22;
ptr = &nurse;
var = *ptr;
이것은 val에 22를 대입하는 것과 효과가 같다.
8-2. 포인터 선언
- 서로 다른 데이터형의 변수들은 메모리를 차지하는 크기가 다르고, 일부 포인터 연산이 그와 같은 메모리 크기르 요구하기 때문이다.
ex.)
int *pi ; // pi는 int형 정수 변수를 가리키는 포인터이다.
char *pc ; // pc는 char형 문자 변수를 가리키는 포인터이다.
float *pf, *pg ; // pf,pg는 float형 변수를 가리키는 포인터이다.
- 내부에서 부호없는 정수로 표현된다. 그러나 포인터를 정수형이라고 생각하면 안된다.
** 함수간의 포인터 사용하기 예제 (9.15)
#include <stdio.h>
void interchange(int *u, int *v);
int main()
{
int x = 5;
int y = 10;
printf("교환전 x = %d , y = %d.\n", x , y);
interchange(&x, &y); // 함수에 주소를 전달한다.
printf("교환후 x = %d , y = %d.\n", x, y);
return 0;
}
void interchange(int *u, int *v)
{
int temp;
temp = *u; // temp는 u가 가리키는 값을 얻는다.
*u = *v;
*v = temp;
return (u); // return 추가
}
- 함수호출은 x와 y의 값들 전달하는 대신에, x와 y의 주소들을 전달한다.
- void interchange(int *u, int *v) 의 프로토타입과 정의에 있는 형식매개변수 u와v가, 주소를 갖게된다.
x, y는 정수이고, u와 v는 정수를 가리키는 포인터가 되기 때문에, (int *u, int *v)로 선언한다.
- 리턴값을 가지지 않아도 주소와 포인터로 변수의 값을 맞교환 할 수있다.
'LAB > C' 카테고리의 다른 글
[24.08.12] c언어의 기초10 (1) | 2024.08.12 |
---|---|
[24.08.11] c언어의 기초 9 (1) | 2024.08.11 |
[24.08.09] c언어의 기초7 (0) | 2024.08.09 |
[24.08.08] c언어의 기초6 (1) | 2024.08.08 |
[24.08.08] c언어의 기초5(2) (0) | 2024.08.08 |