훈, IT 공부/C,C++,MFC

[독하게C]함수에 대한 기본이론과 사용 주의점!! (feat.전역변수)

IT훈이 2018. 1. 18.
반응형


C언어에서 말하는 함수


C언어에 있어서 함수는 프로그램을 이루는 핵심중의 핵심입니다.

연산식이 모여 -> 구문이되고 절차식의 구문이 모여 -> 함수가 됩니다. 
-> 또 함수가 함수를 호출하여 연계하고 그렇게 또 다른 흐름을 만듦으로 C언어 기반의 프로그램이 완성됩니다.

- 함수는 필요의 따라서 언제든지 재사용이 가능합니다. ( 불연속적 반복을 통하여 코드 재사용 가능 ) 
- 적재적소에 함수를 잘 사용한다면 좋은 코드를 작성 할 수 있습니다.

사용자 정의 함수와 선언 및 정의

우리가 작성하는 함수들은 모두 사용자 정의 함수입니다.

# 함수의 선언 및 정의에 대하여 살펴봅시다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<stdio.h>
 
int MyFunc(int nA, int nB)
{
    printf("nA : %d \n", nA);
    printf("nB : %d \n", nB);
 
    return nA + nB;
}
 
int main(void)
{
    int numA = 10;
    int numB = 20;
    int nSum = 0;
 
    nSum = MyFunc(numA, numB);
    return 0;
}
cs


# 프로그램을 실행하면 제일먼저 main() 함수로 진입을 합니다.

main() 함수는 항상 우선순위 1순위...


# 3번 ~ 9번 행 은 사용자 정의함수, MyFunc()의 선언 및 정의가 됩니다.

선언 : MyFunc() 함수에 대하여 골격을 뜻합니다. ( 건축에서 뼈대만 세운 것이죠 )

정의 : MyFunc() 함수 안에 있는 구문들을 뜻합니다.


# 잠시 여기서 호출자와 피호출자를 잠시 보고 갑시다.

호출자 : 자신 함수 구문 내에서 다른 함수를 호출 하였다면 ( 17번 행과같이 ) 호출자( Caller)라고 부릅니다.

피호출자 : 호출자(Caller)가 호출한 대상을 뜻합니다. 호출 당한놈! 피호출자( Callee )


본문으로 다시 넘어가면 


# 3번 행을 살펴보면 아래와 같습니다.

int MyFunc(int nA, int nB) == 반환형식 함수명(매개변수1,매개변수2,...)


반환형식 : 함수 구문에서 return문을 만나서 호출자에게 어떤 값을 돌려줄지 자료형식을 작성합니다.

함수명 : 사용자가 정의하고자 하는 이름을 작성해줍니다.

매개변수 : 호출자가 전달하는 메모리의 값을 어떤 형식으로 받을 것인지 작성합니다.


# 디버그 모드를 통하여 한번 살펴보죠!!

그림에서 보면 17번 행에서 매개변수로 (numA,numB)를 피호출자에게 전달합니다.


피 호출자는 받은 값을 자신의 매개변수에 Copy&Overwrite하게 됩니다. 


# 여기서 하나 알고 가야 할 것이 있습니다.

피호출자의 매개변수는 메모리에 저장될 때, 순서가 뒤에서부터 들어가게 된다는 것입니다.

nB가 메모리에 먼저 들어가게 되고 그 다음 

nA가 메모리에 들어 간다는 것입니다.


자 디버그 모드로 한번 들어가 보겠습니다.

메모리를 보시게 되면 nB가 메모리에 먼저 적제 되었다는 것이 확인이 됩니다.


확인이 되었으면 계속 진행해보죠


3번 행에서 매개변수를 받고 5번6번 행에서 각 변수의 값을 출력합니다.

8번행에서 return nA+nB; 구문이 나타납니다.


이 부분이 앞서 말씀 드렸던 반환에 대한 문법입니다.


# return문을 작성하는 법은 아래와 같습니다. 

return 변수값 ;  

return 연산식;

return 문자열 혹은 상수값들; 

return 함수();

경우에 따라서 사용하는 방법이 다를 수 있습니다.

만약 반환 형식이 void 라면 return문을 작성하지 않으셔도 됩니다~!!



8번 행에서 return문은 반환형식이 int 이므로 "return nA+nB;"를 하게 되면 호출자에게 "nA+nB" 의 합을 int형식으로 돌려줍니다.

# 아래의 그림에서 메모리를 살펴보시죠

"MyFunc이(가) 반환되었습니다" 라고 되어있고 값은 "30"이 들어있죠?? 


17번 행에서 한번더 F10을 눌러주게되면 반환된 값은 nSum 변수에 담기게 됩니다.


메모리를 보시면 16진수"0x1e" == 10진수"30" 이므로 리턴된 값 "30"이 들어있음을 확인 할 수 있습니다.




함수 설계할때 지켜주십쇼 하는것!! something??

C언어는 여러 항을 구문으로 기술하고, 여러 구문을 함수단위로 구성한다고 앞서 말씀 드렸습니다.
그리고 함수들은 호출/피호출자 관계로 묶어(binding) 코드를 확장합니다.

#이는 마치 밥을 먹는 것과 같습니다.
밥을 떠서 한숟가락씩 입에 넣어야하는데, 자신의 입보다 몇배나 크게 집어 넣으려 하거나,
고기장조림 반찬이 있는데, 과자 사탕 초콜릿 같은 것들이 들어가 있으면 안되지 않습니까?? ( 취향존중을 하지만 ;; )
요리사가 음식을 이 딴식으로 내어주면 어떤 사람이 먹으려 하겠습니까??


# 우리가 작성하는 코드도 이와 같습니다.

C언어 프로그래머가 코드를 잘 작성하여야 메모리가 소화도 잘 하고 
소화를 잘하면 속도도 빠를 것 아닙니까. ( 유지보수도 중요하구요 )
그렇다면 C언어 프로그래머가 고민해야 할 것은 "무엇을 함수화 해야 하는가" 입니다.


# 이러한 고민은 설계에서 부터 중요하다고 생각이 됩니다.

1. 사용자 인터페이스 ( User Interface 겉으로 드러나는 외형)와 내부기능은 반드시 분리할 것 
2. 하나의 단위기능으로 규정할 수 있는 대상은 함수로 만들 것.

단위기능의 예로는 평균 계산하기, 최댓값 찾기, 정렬 등과 같은 것들이 있습니다. 


함수 설계할때 지켜주십쇼 하는것!! #1 UI와 기능분리

프로그램의 사용자 인터페이스"인간과 기계가 상호작용할수 있도록 도와주는 것!!" 으로 정의됩니다.

코드를 예로 한번 들어보겠습니다.

# 1~100의 약수를 구하는 코드입니다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include<stdio.h>
 
int divFunc(int nData){
    int nCnt = 0;    //약수 갯수 
 
    if (nData > 10 || nData < 0)    //오류체크
    {
        printf("ERROR!! 0~10 사이의 숫자를 입력하세요\n");
        return 0;
    }
 
    for (int i = 1; i <= nData; i++)
    {
        if (nData % i == 0++nCnt; // 약수일때 nCnt값 증가  
    }
 
    return nCnt;    //약수의 개수 리턴
}
 
int main(void)
{
    int nInput = 0;
 
    printf("약수 개수를 구할 1~10의 숫자 입력:");
    scanf("%d"&nInput);
    printf("%d의 약수 개수:%d\n",nInput,divFunc(nInput));
 
    return 0;
}
 
cs
약수개수를 구할 1~10의 숫자 입력:10
10의 약수 개수:4
계속하려면 아무 키나 누르십시오. . .

코드에 보시면 문제가 별로 없어 보이시죠??

그러나, 앞서 "내부기능과 유저인터페이스는 분리하여야 한다" 언급을 하였습니다.

유저인터페이스는 현재 main()함수에서 담당하고 있습니다. 
그러나 8번 행을 보면 기능함수 divFunc()에서 printf()을 호출함으로써 유저인터페이스를 구현하고 있음을 볼 수 있습니다. 

main()에서 반환값을 가지고 와서 판단하는 방법도 있는데 
기능 함수 내부에서 사용자 인터페이스의 해야할 일을 처리 한다는 것을 생각을 한번 해봐야할 문제입니다.

# 그럼 한번 UI를 제대로 분리하여 코드를 수정해볼까요??
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include<stdio.h>
 
int divFunc(int nData){
    int nCnt = 0;    //약수 갯수 
 
    if (nData > 10 || nData < 0)    return 0;    //오류체크
 
    for (int i = 1; i <= nData; i++)
    {
        if (nData % i == 0)     ++nCnt; //약수라면 nCnt 개수증가
    }
    return nCnt;    //약수의 개수 리턴
}
 
int main(void)
{
    int nInput = 0;
 
    printf("약수의 개수를 구할 1~10의 숫자 입력:");
    scanf("%d"&nInput);
    
    int nResult = divFunc(nInput);
    
    if (nResult == 0)
    {
        puts("ERROR 1 ~ 10 의 숫자만 입력해주세요");
        return 0;
    }
 
    printf("%d의 약수 개수:%d\n",nInput,nResult);
 
    return 0;
}
 
cs


어떤가요 ?? 앞서 본 코드와 달리 기능과 UI가 명확이 분리되었죠??


이런 방법으로 코드를 작성하는것이 중요합니다.


예재하나만 더 들어보겠습니다.



# 학점 계산하는코드입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
 
//학점 입력하는 인터페이스
int GetResult(void)
{
    int nInput = 0;
    printf("학점을 입력하세요:");
    scanf("%d"&nInput);
 
    return nInput;
}
 
//학점 계산하는 인터페이스
char GetGrade(int nScore)
{
    if (nScore > 90return 'A';
    else if (nScore > 80return 'B';
    else if (nScore > 70return 'C';
    else if (nScore > 60return 'D';
 
    return 'F';
}
 
int main(void){
    
    int nResult = 0;
    nResult = GetResult();
    printf("%d점은 학점 %c 입니다.\n", nResult, GetGrade(nResult));
    return 0;
}
cs

이번 코드에서는 UI 와 기능을 잘 분리하였습니다.

그러나 몇 구문만 더 추가하여 완성도 높은 코드로 만들어 봅시다.

#추가작성
- 입력값 유효성 체크 (1 ~ 100 숫자 입력 )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
 
//학점 입력하는 인터페이스
int GetResult(void)
{
    int nInput = 0;
    printf("학점을 입력하세요:");
    scanf("%d"&nInput);
 
    if (nInput > 100 || nInput < 0return 0;
 
    return nInput;
}
 
//학점 계산하는 인터페이스
char GetGrade(int nScore)
{
    if (nScore > 90return 'A';
    else if (nScore > 80return 'B';
    else if (nScore > 70return 'C';
    else if (nScore > 60return 'D';
 
    return 'F';
}
 
int main(void){
    
    int nResult = 0;
    nResult = GetResult();
    if (nResult == 0)
    {
        puts("ERROR!!! 1~100의 숫자만 입력하세요");
        return 0;
    }
    printf("%d점은 학점 %c 입니다.\n", nResult, GetGrade(nResult));
    return 0;
}
cs
어떤가요?? 코드는 이런식으로 확실하게 분리하여서 작성하여야 합니다.

함수 설계할때 지켜주십쇼 하는것!! #2 재사용 가능한 단위기능 구현

불연속 적으로 사용되거나 계속해서 반복하여 사용 될 일이 있는 코드는 함수화 하시는 것이 좋습니다. 
유지보수하기 유용하기 때문이죠.

그러나 반복이나 재사용이 아니더라도 함수로 만드는 것을 추천합니다. 
여러 기능을 각각 함수로 만들면 관리하기가 수월해지기 때문이죠.

"같은 일을 수행하는 코드가 반복(여러곳에 존재) 되지 않게 하여라"

전역변수!!! 사용하기

전역변수는 선언을 하게 되면 어떤 함수의 위치에서도 활용 가능합니다.

즉, 프로그램이 작동하는 동안에는 계속해서 메모리속에 적재 되어 있다는 뜻이죠


# 자.. 전역변수 사용할 때 조심할 점이 있습니다.

전역변수를 여러 함수에서 남발하고나서 
전역 변수의 식별자명을 변경해야할 때 혹은 전역변수의 배열의 크기를 변경할때
코드에 있는 모든 전역변수의 식별자명을 변경해줘야합니다...

그냥 단순히 변수일때가 아닌 배열일 경우를 생각해보세요
배열의 크기가 변경된다면 프로그래머는 코드의 전체의 배열을 손대야합니다.

벌써 걱정스럽지 않나요??

# 전역변수를 선언한 코드를 한번 보시죠 ( 배열 x )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include<stdio.h>
int g_money = 0;
 
//입금
void KeepMoney(int nPrice){
    g_money += nPrice;
}
 
//출금
void OutMoney(int nPrice){
    g_money -= nPrice;
}
 
//메뉴선택
int GetMenu(){
 
    int nInput = 0;
    int nMoney = 0;
    printf("[1]입금 [2]출금 \n>>");
    scanf("%d"&nInput);
    
    if (nInput > 2 || nInput < 0return 0;
 
    return nInput;
}
 
//금액선택 자주사용하게될 구문은 함수화!!
int GetMoney(){
    int IN_Money = 0;
 
    printf("금액을 입력해주세요 :");
    scanf("%d"&IN_Money);
    
    return IN_Money;
}
 
int main(void)
{
    g_money = 2000;    //기본 돈 지정
    int Menu = 0;
 
    while ((Menu=GetMenu()) != 0){
        switch (Menu){
        case 1:
            KeepMoney(GetMoney());
            printf("현재 잔고: %d\n\n", g_money);
            break;
        case 2:
            OutMoney(GetMoney());
            printf("현재 잔고: %d\n\n", g_money);
            break;
        default:
            printf("ERROR!!");
        }
    }
    return 0;
}
cs

(이 예재는 조금 약할 수도 있습니다.)


2번 행을 보면 전역변수 g_money 를 선언 및 정의하였습니다.

코드를 보시면 함수 부분부분에서 전역변수 g_money 를 사용합니다.


# g_money를 자료형과 식별자명을 변경한다고 생각한다면.. 

수정해야할 것들이 많이 보이겠죠..?? 



그렇다면 어떻게 코드들을 작성해야할까요?




# 좋은설계는 간단합니다. 

프로그램을 적절한 단위요소로 나누고 

각 요소가 다른 요소의 변화에 영향을 받지 않도록 

의존성을 최대한 낮추는 것입니다.


그렇게 해서 테스트도 좋고 유지보수 하기도 좋게 만들어 생산성을 향상시키면 됩니다.

  




반응형

댓글