1 함수 만들기
이제 프로그래밍을 하기 위한 기본적인 단계로 간단한 함수를 만드는 작업에 대해서 익히도록 합시다. 프로그래밍을 할 때에는 크게 구도를 잡고 이를 쪼개고 다시 쪼개는 방법에 의한 top-down방식과 작은 부분에 대한 구현을 한 뒤에 이들을 조합을 해 나가는 방법에 의해 bottom – up 방식이 있습니다.
일반적으로 프로그램의 코드를 라이브러리를 사용하는 코드와 개발자 정의 코드로 구분을 한다면 개발자가 정의하는 코드에 대한 구현을 함에 있어서는 top-down방식을 사용을 하는 것이 추세이며 라이브러리를 사용하는 코드에 한해 부분적인 bottom-up방식을 사용하고 있습니다. 즉, 우리가 만드는 부분은 top-down방식이라고 생각을 하더라도 큰 무리는 없습니다.(물론, 프로그램을 설계하는 방식은 너무나 다양하기 때문에 이것이 정답이라고 할 수는 없습니다.)
프로그래밍을 하는 과정으로 들어가기 전에 아래의 여러개의 작은 함수를 구현하는 연습을 하고 넘어가도록 합시다. (대부분의 함수는 이미 라이브러리에서 제공하고 있는 함수인데 직접 만들어 보자는 것입니다.)
· 1. 두 수를 더하는 함수
· 2. n!
· 3. 문자열 길이를 구하는 함수
· 4. 문자열을 입력받는 함수
· 5. 문자열을 복사하는 함수
· 6. 문자열 중에 n개의 문자만 복사하는 함수
· 7. 문자열을 비교하는 함수
· 8. 문자열 중에 n개의 문자만 비교하는 함수
· 9. 문자열을 숫자로 변환하는 함수
· 10. 숫자를 입력받는 함수
· 11. 특정 범위내의 숫자를 입력받는 함수
· 12. n개의 정수 중에 가장 큰 값이 있는 메모리 주소를 반환하는 함수
· 13. 두개의 정수 값을 바꾸는 함수
· 14. n개의 정수를 sorting하는 함수(select sort)
· 15. 특정 문자열에서 sub string이 첫번째 있는 곳의 메모리 주소를 변환하는 함수
기본적으로 위의 15여개의 함수를 만드는 과정을 통해 여러분은 함수의 매개변수에 대한 이해와 기본적인 배열과 포인터에 대한 개념 정리 및 프로그래밍을 배울 준비를 해 나가기로 합시다.
설계를 통하지 않은 프로그래밍을 할 경우에는 작성해 보고 자주 사용하면 함수로 꺼내고 필요하면 매개변수를 생각하게 되지만 설계를 통해 프로그래밍을 할 경우에는 어떠한 기능이 필요한지 생각하고 매개변수를 결정하고 실제 기능을 구현하고 테스팅하는 과정을 거치게 됩니다. 여기에서 함수를 만드는 과정은 다음과 같습니다.
· 1. 함수명 정하기 - 함수의 기능에 대한 명확한 이해 필요(왜 만드는가? 어떨 때 필요한가?)
· 2. 매개변수 정하기 - 함수의 기능을 수행에 필요한 값과 호출한 함수가 필요한 값에 대한 깊은 이해 필요(call by value에 의해 매개변수가 전달됨에 언제나 유의- 사용자가 어떻게 사용하면 편할까?)
· 3. 함수 로직 구현 - 함수의 기능을 구현한다.
· 4. 테스팅 - 해당 함수를 호출하여 정확한 동작을 하는 지 확인
1.2 함수명 정하기
함수명을 정할 때에는 이해하기 쉽고 명확하게 해 주어야 필요할 때 정확하게 찾아 사용할 것입니다. 많은 프로그래머들은 자기만의 명명법을 갖고 있는데 그 중 자주 사용하는 방법에 대해 얘기해 봅시다. 먼저, 라이브러리에 있는 함수는 소문자로만 함수명을 구성되어 있다는 것을 알 수 있습니다. 사용자가 정의하는 함수명은 보통 대 소문자를 적절하게 혼합하여 사용하는 경우가 많습니다. Window 응용을 개발하는 개발자들은 함수명의 처음을 대문자로 시작하는 것이 일반적이고 그 외에는 소문자로 시작하는 것 같습니다. 즉, 문자열을 비교하는 함수를 예로 든다면 라이브러리에서는 strcmp, Window 응용에서는 StrCmp, 그 외에서는 strCmp등과 같이 사용한다는 것이죠. 어떠한 방법을 사용하든지 여러분은 자신의 취향에 맞는 naming rule을 익히고 그 방법을 사용하시길 바랍니다. 물론, 남들이 이해하기 힘든 naming rule을 고집한다면 아티스트로 성공할 지는 모르겠지만 엔지니어로 성공하지 못할 것입니다.
여러분이 위의 15개의 기능에 적합한 함수명을 정하였다면 아래에 본인이 명명한 함수명과 비교하여 보십시요.(여러분이 보기에 매끄럽지 않은 함수명도 있을 것입니다. 본인이 부족한 부분은 뛰어 넘으시길 바랍니다.- 청출어람)
· 1. fnAdd
· 2. fnFactorial
· 3. fnStrLen
· 4. fnInsertStr
· 5. fnStrCpy
· 6. fnStrNCpy
· 7. fnStrCmp
· 8. fnStrNCmp
· 9. fnAtoi
· 10. fnInsertNum
· 11. fnInsertSNum
· 12. fnGetMaxNumPos
· 13. fnSwap
· 14. fnSortNI
· 15. fnStrStr
1.3 함수의 매개변수 정하기
함수의 매개변수에는 앞에서 설명하였던 것처럼 여러개의 입력 매개변수 리스트와 한개의 리턴 타입이 있습니다. 여기서 입력 매개변수 리스트는 해당 함수의 기능을 수행함에 필요로 하는 인수들 중에 사용(호출)하는 함수에서 초기값을 전달하는 인수를 얘기를 합니다. 즉, 재료가 되는 것이죠. 그리고, 리턴 타입은 해당 함수가 수행한 결과를 호출한 함수에게 전달할 값에 대한 타입입니다.
먼저 1번에서 3번까지 매개변수를 결정하는 방법을 참조하시고 나머지 함수들은 각자가 고민한 후에 비교해 보시기 바랍니다. 고민하지 않고 보면 너무나 당연한 내용으로 보이실 수 있습니다. 중요한 건 너무나 당연하게 생각될 수 있는 것을 우리가 스스로 결정할 수 있는가 하는 것입니다.
· 1. fnAdd
두 수를 더하는 함수로 해당 기능을 수행하기 위해서는 두개의 수를 필요로 할 것이며 수행 결과는 입력된두수의 합한 값일 것입니다. 즉, 다음과 같이 매개변수를 결정할 수 있겠지요.
int fnAdd(int a, int b);
이와 같이 표현하는 구문을 함수 선언문이라고 합니다. 이는 만들어진 함수와 사용하는 사용자간에 약속이라고 할 수 있을 것입니다. 그리고, 컴파일러는 이를 통해 사용하는 문맥이 문법에 맞는지 타입 체킹을 해 줍니다.
· 2. fnFactorial
n!은 n*(n-1)*(n-2)*...*1, n>=1인 정수라는 것은 여러분도 잘 알고 있을 것입니다. 결국 우리가 만들 함수는 사용자가 n을 집어 넣으면 n!를 계산하여 돌려주도록 해야 되겠죠. 다음과 같이 매개변수를 결정할 수 있을 것입니다.
unsigned fnFactorial(unsigned n);
물론 개발자에 따라 unsigned fnFactorial(unsigned char);와 같이 다른 형태의 매개변수로 결정하였다 하더라도 잘못된 것은 아니겠지요.
· 3. fnStrLen
문자열의 길이를 구하는 함수로 사용자는 문자열을 넣을 것이며 해당 함수는 길이를 카운팅하여 반환해 줄 것입니다. 그럼 입력 매개변수는 문자열을 받을 수 있는 타입인 char *이고 반환하는 타입은 int정도면 될 것입니다. 좀 더 신뢰성 있는 함수로 만든다면 입력받은 문자열의 내용을 바꾸지 않는다는 의미로 입력 매개변수를 const char *로 명시하면 될 것입니다.
int fnStrLen(const char *str);
물론 리턴 타입을 unsigned로 한다고 해서 잘못된 것은 아니겠지요.
· 4. fnInsertStr
문자열을 입력받는 함수라고 했는데 왜 이런 함수를 만들자고 했는지 이상하게 생각하시는 분들이 있을 것입니다. 이미 C언어에 gets나 scanf와 같은 라이브러리 함수가 있는데 왜 만들자고 하는 것인지 궁금할 것입니다. 그런데, gets함수나 scanf를 사용을 했을 때 개발자가 정한 배열의 크기보다 end-user가 많이 입력을 하면 어떻게 될까요? 결국 해당 라이브러리로는 경우에 따라서 한계가 있다는 것입니다. 그리고, scanf의 경우에 포맷에 따라 변환을 하고 있고 그 기준으로 공백, 탭, 엔터를 하고 있어서 공백을 포함한 문자열을 입력 받고자 한다면 새로운 함수를 작성해야 할 것입니다. 즉, 일반화된 라이브러리를 모든 특수한 경우에 사용하기에는 무리가 있다는 것입니다. 우리는 해당 라이브러리를 적절히 이용하여 우리 프로그램에서 사용에 용이하도록 특수화 시킬 필요가 있습니다.
위의 문제를 해결하기 위해서는 결국 해당 함수에 end-user가 입력한 문자들을 저장할 버퍼공간뿐만이 아니라 해당 버퍼의 사이즈를 입력 매개변수로 넘겨주어야 할 것입니다. 참고로 연쇄작업을 할 수 있게 하기 위해 반환값을 char *로 하면 사용자는 좀 더 해당 함수를 편하게 사용할 수 있을 것입니다.
char *fnInsertStr(char *buf, unsigned size);
다음은 연쇄작업에 대한 예입니다.
char name[10+1];
printf(“이름을 입력하세요\n”);
printf(“입력된 이름:[%s]\n”fnInsertStr(name,10));
만약 리턴 타입이 void로 정의하였다면 다음과 같이 사용할 것입니다. 즉, 이 함수는 반드시 리턴 타입이 char *일 필요는 없지만 함수를 사용자에게 편의성을 주기 위해서 리턴 타입을 char *로 한 것입니다.
char name[10+1];
printf(“이름을 입력하세요\n”);
fnInsertStr(name,10);
printf(“입력된 이름:[%s]\n”,name);
· 5. fnStrCpy
문자열을 복사하기 위해서는 함수 사용자가 원본 문자열과 복사할 버퍼를 제공해야 할 것입니다. 그리고, 해당 함수 호출로 인해 원본 문자열이 바뀌면 알 될 것입니다. 앞에 함수와 마찬가지로 연쇄작업에 대한 편의성을 주기 위해 char *를 반환을 해 주면 좋을 것입니다.
char *fnStrCpy(char *dest, const char *src);
· 6. fnStrNCpy
앞의 함수의 입력매개변수에 추가로 몇개의 문자를 복사할 것인지에 해당하는 정수값이 필요하겠지요.
char *fnStrNCpy(char *dest, const char *src, unsigned n);
· 7. fnStrCmp
문자열 비교한다는 것은 무엇을 의미할까요? 일반적으로 우리는 문자열을 비교할 때 사전식 비교를 하고있습니다. 사전식 비교는 앞에서부터 비교하는데 다른값이 나오면 뒤는 비교를 하지 않는 것을 얘기를 합니다. 물론, 이러한 내용은 실제 함수를 구현하면서 구체적으로 고민하시기 바랍니다. 여기에서 이러한 기본적인 사항에 대한 언급을 하는 것은 해당 함수에서 비교한 결과를 호출한 함수에게 어떻게 전달할 것인가 하는 것입니다. printf(“앞쪽이 크네요\n”); 와 같이 출력문을 사용하면 되지 않냐고 생각할 수도 있을 것입니다. 하지만, 그러한 구문을 출력은 함수 사용자가 정할 부분이지 이 함수에서 그러한 문맥을 출력하는 순간 쓰레기 함수가 되어버립니다. 즉, 그러한 출력문을 원하지 않는 사용자는 사용하지 않게 된다는 말입니다. 저는 두개의 문자열이 같으면 차이가 없다는 의미로 0을 다를 경우에 앞에 문자열이 크면 양수를 뒤에 문자열이 크면 음수를 반환하도록 하겠습니다. 물론, 이러한 부분은 해당 함수 선언문에 주석으로 설명을 해 주어야 사용자가 쉽게 사용할 수 있을 것입니다.
int fnStrCmp(const char *str1, const char *str2);
· 8. fnStrNCmp
int fnStrNCmp(const char *str1,const char *str2, unsigned n);
· 9. fnAtoi
먼저 이 함수의 매개변수를 결정하기 위해서 이 함수가 하는 일이 무엇인지에 대한 요구분석이 필요할 것입니다. 이 함수는 “123”과 같은 문자열이 있으면 이를 정수 123으로 변환하고자 할 때 사용할 것입니다. 다만, “abc12”나 “123ab”와 같은 문자열은 어떻게 바꿀 것인지가 고민사항이 되겠지요. C언어의 특징대로 한다면 할 수 있는데까지 처리하면 될 것입니다. 즉, “abc12”와 같이 처음부터 변환을 할 수 없다면 0으로 “123ab”처럼 앞에 세문자까지 변환할 수 있다면 해당 부분까지(즉, 123으로) 변환해 주면 될 것입니다. 물론, 해당 문자열에 숫자문자외에 문자가 있다면 0을 반환하는 함수로 만든다고 해도 잘못되지는 않을 것입니다. 암튼 이 함수는 입력 매개변수로 문자열이 필요할 것이고 반환타입은 int가 되겠지요.
int fnAtoi(const char *str);
· 10. fnInsertNum
앞에 문자열을 입력받는 함수처럼 또, 왜 정수를 입력받는 함수를 왜 만드는지 이상하게 생각하시는 분들이또 계시겠죠. scanf(“%d”,&num);과 같이 라이브러리를 사용한다고 할 때 fflush(stdin);을 사용하는 습관이 안 되신 분들은 해당 호출로 인해 end-user가 숫자문자 이외에 키를 누른다면 해당 함수에서는 문제가 없을 수 있겠지만 뒤쪽에서 scanf나 getchar등의 기본 입력함수를 사용할 때 문제가 될 것입니다. 정확히 이해가 가지 않는 분들은 기본 입력에 대한 부분을 다시 확인하시고 보시기 바랍니다.
만드는 이유야 어떻든 간에 해당 함수는 입력 매개변수는 필요하지 않을 것이며 end-user가 입력한 수를 반환하면 되겠지요.
int fnInsertNum(void);
· 11. fnInsertSNum
이 함수는 특정 범위의 수를 입력받겠다는 것입니다. 예를 들어 학생 1번부터 50번까지 있는 학급의 학생들 데이터를 관리하고자 한다면 해당 프로그램에서는 학생 번호를 입력받는 기능이 필요할 것입니다. 이러한 경우에 이 함수를 사용할 수도 있겠지요.
int fnInsertSNum(int min, int max);
· 12. fnGetMaxNumPos
이 함수는 n개의 정수 중에 가장 큰 수가 저장된 위치를 구해 달라는 것입니다. 입력 매개변수로는 n개의정수를 넘겨주어야 할 텐데 C언어에서 이럴 때 배열을 사용을 하고 있잖아요. 즉, 입력 매개변수로 n개의 정수가 있는 배열의 주소와 배열의 크기를 넣어주면 되겠지요. 그리고, 저장된 위치이므로 int형 포인터를 반환하면 될 것입니다.
int *fnGetMaxNumPos(int *base,unsigned asize);
· 13. fnSwap
이 함수는 함수의 매개변수 전달방식을 설명하면서 나왔다는 것을 인지하시는 분들이 계실겁니다. 맞습니다. 여기서는 설명을 하지 않겠습니다.
void fnSwap(int *,int *);
· 14. fnSortNI
이 함수는 n개의 정수를 정렬하는 함수로 입력 매개변수로 n개의 정수가 저장된 배열의 주소와 배열의 크기를 넘겨주어야 겠지요.
void fnSortNI(int *base,unsigned size);
· 15. fnStrStr
이 함수는 특정 문자열에서 sub string이 있는 첫번째 문자열이 있는 메모리 주소를 찾는 함수입니다. 예를 들어 "I am a boy and you are a boy."와 같은 문자열에서 "boy"라는 문자열이 처음으로 발견되는 위치를 찾아 달라고 할 때 사용할 함수입니다. 이를 위해서는 사용자는 두개의 문자열을 넘겨주어야 하고 수행한 결과는 찾은 위치를 반환해야 할 것입니다.
char *fnStrStr(const char *bstr,const char *sstr);
1.4 함수 로직 구현
드디어 함수 내부의 기능을 구현하는 단계로 왔습니다. 여기도 마찬가지로 여러분이 직접 구현을 해 보고 나서 비교만 하시기 바랍니다. 먼저 보시는 것은 전혀 도움도 안되고 여러분의 지식이 결코 될 수 없습니다. 먼저 보실 분은 지금 당장 책을 가까운 휴지통에 던져 버리시길 바랍니다.
· 1. 두 수를 더하는 함수
입력 받은 두 수를 더한 후에 이 값을 반환하는 로직입니다. 함수의 결과를 반환하기 위해서 return을 사용하면 됩니다.
int fnAdd(int a, int b) { return a+b; } |
· 2. n!
특정 함수의 로직을 작성하기 전에 먼저 해당 함수의 기능을 논리적으로 따져보고 아래처럼 의사코드(pseudo-code)로 정리를 하십시요. 그리고, 각 부분을 C문법에 맞게 구현하고 테스트를 하는 단계로써 함수를 작성하시면 됩니다. 처음에는 귀찮겠지만 결국 이러한 습관이 전체 개발 비용도 줄이고 유지 보수 비용등을 줄이는 데 기여를 하게 됩니다.
/*fnFactorial*/
입력 매개변수: n
초기갑: 1
반복(조건: n이 1보다 크거나 같다면)
현재 결과=이전결과 * n
n 감소
결과 반환
unsigned fnFactorial(unsigned n) { unsigned result=1; //초기값:1 while(n >=1) //n이 1보다 크거나 같다면 { result = result * n; //현재 결과 = 이전결과 *n; n --; //n감소 } return result; //결과 반환 } |
f(n) = n! (n>=1인 정수)이라고 할 때 f(n)=n*f(n-1) (n>=2인 정수, f(1)=1)과 같은 명제가 참이라는 것은 알고 있을 것입니다. 위의 명제처럼 자신을 이용하여 자신을 표현하는 것을 재귀라고 합니다. 이를 C언어의 함수로 만들면 다음과 같은 재귀함수가 작성이 됩니다. 재귀함수를 작성할 경우에는 반드시 재귀의 종료조건이 있도록 작성하여야 하면 재귀함수의 호출이 이루어졌을 때 이전보다 재귀의 종료조건에 근접하도록 해야 할 것입니다. 만약, 이를 위배한다면 해당 구문은 스택 메모리가 꽉찰 여지가 있게 되겠지요.
unsigned fnFactorial(unsigned n) { unsigned result=1; if(n <= 0) //명제에 위배 { return 0; } if(n ==1) //재귀의 끝 { return result; } result = base * fnFactorial(n – 1); //재귀 호출 return result; } |
· 3. 문자열 길이 구하는 함수
여기에서는 먼저 입력된 값이 해당 문자열 중에 첫 문자가 저장된 메모리 주소이며 C언어에서 문자열은 ‘\0’가 오기전까지의 연속된 ascii코드의 나열을 의미한다는 것을 알고 있어야 할 것입니다.
/*fnStrLen*/
입력 매개변수: str
초기갑: 0
반복(조건: str이 가리키는 곳에 있는 문자가 ‘\0’문자가 아니라면)
카운트 중가
str 증가
결과 반환
int fnStrLen(const char *str) { int count=0; //초기값-0 while(*str!='\0') //str이 가리키는 곳에 있는 문자가 ‘\0’문자가 아니라면 { count++; //카운트 증가 str++; //str 증가 } return count; } |
참고로 위의 while(*str!=’\0’) 은 whil(*str)과 동일한 표현입니다. ‘\0’은 0이라는 것을 안다면 두개의 표현이 같다라는 것을 알 수 있겠지요. 즉, ‘\0’은 거짓인 문자이고 나머지는 참인 문자라고 해석해도 C언어에서는 무리가 없습니다.
· 4. 문자열을 입력받는 함수
문자열을 입력받는 함수는 end – user로부터 입력을 받는 것이기 때문에 주의해야 할 사항이 많습니다. 프로그램에서 관리해야 할 것은 문자열이며 enu-user와 O/S사이에 데이터를 주고 받는 약속은 stream이고 buffered I/O를 통해 처리가 이루어집니다. 또한, 문자열을 관리하기 위해서
/*fnInsertStr */
입력 매개변수: buf(문자열 입력받을 메모리 주소) , size(입력받을 문자 최대 개수)
반복: 입력한 문자가 ‘\n’가 아니고 입력받은 문자 개수가 size보다 작다라면
입력받은 맨 끝에 ‘\0’문자 대입
stdin버퍼를 flush시킴
char *fnInsertStr(char *buf, unsigned size) { char *bufpos = buf; while((*buf=getchar())!='\n')&&(size)) /* stdin으로 부터 한문자를 얻어오고 그 문자가 '\n'가 아니고 현재 처리한 문자의 개수가 size보다 작을 동안 loop을 수행하라. */ { size++; } *buf='\0'; /* 문자열의 끝임을 처리 */ fflush(stdin); /* stdin에 남아있는 문자들을 flush시킴 */ return bufpos;; } |
· 5. 문자열을 복사하는 함수
C언어에서 문자열이라는 것은 ascii코드의 연속된 나열로써 그 끝은 ‘\0’(널문자)로 명시함으로써 이후의 문자는 무의미하다라는 것을 표시하고 있습니다. 즉, 문자열을 복사한다는 것은 ‘\0’를 만나기 전까지의 문자들을 복사를 하는 것이 됩니다.
/*fnStrCpy */
입력 매개변수: dest(문자열을 복사할 메모리 주소) , src(원본 문자열이 있는 시작 주소)
반복: src가 가리키는 문자가 ‘\0’이 아니라면
dest가 가리키는 곳에 src가 가리키는 문자를 대입
dest가 가리키는 곳에 ‘\0’문자 대입
char *fnStrCpy(char *dest, const char *src) { char *dst = dest; for(;*src;dest++,src++) //src가 가리키는 문자가 참이라면 { *dest = *src; //src가 가리키는 문자를 dest가 가리키는 곳에 대입 }; *dest = ‘\0’; //dest가 가리키는 곳에 ‘\0’문자 대입 return dst; } |
동일한 함수를 배열스러운 표현으로 바꾼다면 다음과 같이 표현 할 수 있을 것입니다. 앞으로 뒤에 나오는 함수들은 본인 각자가 배열스러운 표현과 포인터스러운 표현으로 각각 만들어 보시길 바랍니다.
char *fnStrCpy(char *dest, char *src) { int count; for(count=0; src[count];count++) { dest[count] = src[count]; }; dest[count] = src[count]; return dest; } |
· 6. 문자열 중에 n개의 문자만 복사하는 함수
이 함수는 앞의 함수에서 복사할 개수를 호출하는 함수에서 입력매개변수로 넘겨준 범위까지만 복사를 해야 하기 때문에 loop의 조건부에 해당 조건이 추가될 것입니다.
char *fnStrNCpy(char *dest, char *src, unsigned int len) { char *dst = dest; for(;(*src)&&(len);dest++,src++,len--) //src가 가리키는 문자가 참이라면 { *dest = *src; //src가 가리키는 문자를 dest가 가리키는 곳에 대입 }; *dest = ‘\0’; //dest가 가리키는 곳에 ‘\0’문자 대입 return dst; } |
· 7. 문자열을 비교하는 함수
이번 함수의 로직을 구현하기 위해서는 먼저 문자열을 비교할 기준에 대해서 먼저 결정해야 할 것입니다. 해당 기준을 정하는 것은 개발자 자신이긴 하지만 해당 기준이 일반적인 범주를 벗어난다면 해당 함수를 사용하는 사용자는 불편해서 사용을 안하게 되겠지요.
일상 생활에서 문자열을 비교하는 기준은 사전식 비교가 가장 일반적입니다. 사전식 비교라 하면 앞에서부터 비교해 나가되 다른 부분이 있으면 더 이상 비교를 하지 않는 것을 얘기합니다. 사전에 단어들이 나열되는 순서를 생각해 본다면 사전식 비교에 대해서 쉽게 이해할 수 있을 것입니다.
/*fnStrCmp */
입력 매개변수: str1(첫번째 문자열의 시작 주소) , str2(두번째 문자열의 시작 주소)
반복: str1이 가리키는 문자와 str2가 가리키는 문자가 같다라면
str1과 str2는 다음 문자를 가리키게 함
str1이 가리키는 문자와 str2가 가리키는 문자의 차를 반환
int fnStrCmp(const char *str1, const char *str2) { for(;(*str1) &&(*str1 == *str2);str1++,str2++); return *str1 - *str2; } |
· 8. 문자열 중에 n개의 문자만 비교하는 함수
이번 함수도 앞 함수의 존건에 개수에 해당되는 조건을 추가하면 될 것입니다.
int fnStrNCmp(const char *str1,const char *str2, unsigned n) { for(;(*str1) &&(*str1 == *str2)&&(n);str1++,str2++,n--); return *str1 - *str2; } |
· 9. 문자열을 숫자로 변환하는 함수
여기에서 문자열을 숫자로 변환한다는 것은 숫자 문자가 연속적으로 오면 해당 부분을 정수로 바꾸자는 것입니다. 즉, “123”이라는 문자열을 123이라는 정수로 바꾸자는 것이지요. 그렇다면 “abc12”나 “123ab”와 같은 형태의 문자열은 어떻게 변환해 주어야 할까요? 그것은 개발자가 정하고 이를 명시하는 것이 중요할 텐데 C언어의 특징을 따른다면 변환할 수 있는 곳까지 변환하다가 불가능하면 거기까지만 변환하는 것이 C언어의 특징입니다. 즉, “abc12”는 처음부터 변환을 못하기 때문에 0으로 “123ab”는 123까지 변환하도록 한다는 것이지요.
/*fnAtoi */
입력 매개변수: str(문자열의 시작 주소)
초기값: result = 0
반복: str이 가리키는 문자가 숫자 문자라면
result = result * 10 + str이 가리키는 문자에 해당하는 숫자
result반환
int fnAtoi(const char *str) { int result=0; while( (*str>='0')&&(*str<='9')) //str이 가리키는 문자가 숫자 문자라면 { result = result * 10 + (*str - '0'); //결과 조정 p_in++; } return result; //결과 반환 } |
· 10. 숫자를 입력받는 함수
우리는 이미 문자열을 입력받는 함수와 문자열을 정수로 변환하는 함수를 작성하였습니다. 즉, 이번 함수는 기존에 만든 함수를 조합하여 만드는 함수라는 것이지요.
extern char *fnInsertStr(char *, unsigned int ); extern int fnAtoi(const char *); int fnInsertNum() { char c_num[512]; fnInsertStr(c_num,512); return fnAtoi(c_num); } |
기본 입출력 함수를 이용하여 만든다면 다음과 같이 만들 수도 있을 것입니다. 단순히 scanf(“%d”,&num);과 같이 사용하는 것은 stdin버퍼에 처리되지 않은 stream이 존재하여 다음 입력구문에 예상 못한 영향을 줄 수 있기 때문에 반드시 fflush를 사용하여야 할 것입니다. 이에 다음과 하는 기능은 별볼일 없는 것 같지만 함수로 만들어서 자주하는 실수를 줄일 수 있는 것이지요.
int fnInsertNum() { int re=0; scanf(“%d”,&re); fflush(stdin); return re; } |
· 11. 특정 범위내의 숫자를 입력받는 함수
이 함수도 결국 위의 함수를 이용하여 만드는 함수입니다. 별다른 설명은 필요 없을 것 같네요.
extern int fnInsertNum(); int fnInsertSNum(int min, int max) { int result; result = fnInsertNum(); if((result >= min) &&(result <=max)) { return result; } return 0; } |
· 12. n개의 정수 중에 가장 큰 값이 있는 메모리 주소를 반환하는 함수
이 함수에서는
n개의 정수 중 최대값의 위치를 찾기 위해서 여러가지 알고리즘이 있겠지만 쉽게 생각할 수 있는 로직은 다음과 같을 것입니다. 첫번째 원소를 일단 최대값으로 생각을 합니다. 그리고 배열의 각 요소와 순차적으로 비교하다 더 큰 값을 만나면 최대값을 교체를 한다면 끝에 갔을 때 기억하는 값이 최대값이 되겠지요.
/*fnGetMaxNumPos */
입력 매개변수: base(배열의 시작 주소),asize(배열의 원소 개수)
초기값: max_pos = base
반복: asize까지
비교: max_pos가 가리키는 값과 base가 가리키는 값의 크기 비교하요 base가 가리키는 값이 크면
max_pos = base
max_pos반환
int *fnGetMaxNumPos(int *base,unsigned asize) { int *max_pos = base; //첫번째 원소가 최대값이라 가정 base ++; asize-- ; for( ;asize;base++,asize--) //전체 배열의 요소까지 { if(*max_pos < *base) //현재 최대값과 배열의 요소의 값 비교 { max_pos = base; //최대값 위치 변경 } } return max_pos; //최대값 위치 반환 } |
· 13. 두개의 정수 값을 바꾸는 함수
이 함수는 앞서 함수의 매개변수 전달 방식을 설명한 예제 함수입니다. 설명을 생략하더라도 상관이 없으리라 생각됩니다.
void fnSwapI(int *first,int *second) { int temp; temp = *first; *first = *second; *second = temp; } |
· 14. n개의 정수를 sorting하는 함수(select sort)
이 함수는 n개의 정수배열을 sort함수는 함수로 여기에서는 select sort를 구현하기로 하겠습니다. 물론, 알고리즘적으로 보면 더 좋은 sort알고리즘이 있지만 여기에서는 함수가 이슈이지 알고리즘이 이슈가 아닌 것을 이해해 주시기 바랍니다. 더 좋은 sort알고리즘에 관심 있는 분들은 알고리즘에 관련된 책이나 문서를 참고하시기 바랍니다.
select sort는 최대값(혹은 최소값)의 위치를 찾아 맨 처음의 요소와 바꾸고 다음 최대값(혹은 최소값)의 위치를 찾아 두번째 요소와 바꾸는 형태의 로직을 반복함으로써 정렬하는 알고리즘을 얘기합니다. 즉, 앞에서 만든 fnGetMaxNumPos, fnSwapI함수를 반복하여 호출함으로써 완성 시킬 수 있겠지요.
/*fnSortNI */
입력 매개변수: base(배열의 시작 주소),asize(배열의 원소 개수)
초기값: max_pos = base
반복: asize까지
비교: max_pos가 가리키는 값과 base가 가리키는 값의 크기 비교하요 base가 가리키는 값이 크면
max_pos = base
max_pos반환
extern int *fnGetMaxNumPos(int *,unsigned int ); extern void fnSwapI(int *,int *); void fnSortNI(int *base,unsigned size) { for( ; size>1; size--,base++) { fnSwapI(base,fnGetMaxNumPos(base,size)); } } |
· 15. 특정 문자열에서 sub string이 첫번째 있는 곳의 메모리 주소를 변환하는 함수
이 함수는 긴 문맥 속에 sub문자열이 있는 위치를 찾는 함수입니다. 해당 기능을 구현하기 위해서는 앞에서부터 sub문자열과 긴 문자열의 부분 문자열을 비교하면서 같은 부분이 있는지를 체크해 나가야 할 것입니다.(물론, 효율적인 알고리즘은 따로 있습니다.) 이를 위해서는 sub문자열의 길이를 구하여 해당 길이 만큼 문자열 비교를 반복해 나가면 되겠지요. 이들 처럼 우리는 작은 함수를 조합하여 좀 더 큰 기능을 하는 함수를 작성할 수 있는 능력을 키워나가야 할 것입니다.
/*fnStrStr */
입력 매개변수: bstr(긴 문맥의 시작 주소),sstr(sub string의 시작 주소)
초기값: sub_len = sub string의 문자열 길이
반복: bstr이 가르키는 것이 ‘\0’아니면서 bstr과 sstr의 sub_len길이의 문자열이 같지 않다라면
bstr을 다음 위치로
찾았으면
찾은 위치 반환
못 찾았음을 반환
extern unsigned int fnStrLen(char *); extern int fnStrNCmp(char *,char *, unsigned int ); char *fnStrStr(const char *bstr,const char *sstr) { int sub_len; sub_len = fnStrLen(sub_str); while((*src_str!='\0') &&(fnStrNCmp(src_str,sub_str,sub_len)!=0)) { src_str++; } if(*src_str) { return src_str; //찾은 위치 반환 } return NULL; //못 찾았음을 반환 } |
· 참고: 단축키를 쉽게 사용할 수 있게 하는 함수
C에서
아래와 같은 stub코드를 작성해 봄으로써 우리는 키보드의 각 키를 눌렀을 때 해당 함수가 반환하는 값을확인할 수 있을 것입니다.
int main() { while(1) { printf(“[%x]”,getch()); } return 0; } |
위의 코드를 통해 확인해 보면 F1에서 F10까지는 첫 값이 0을 반환하고 두번째 값이 0x3b에서 0x44에 해당하는 값을 반환하는 것을 알 수가 있습니다. 또한, 방향 키의 경우는 첫번째 값이 0xe0이며 두번째 값이 0x48, 0x50, 0x4b, 0x4d라는 것을 알 수가 있지요. 즉, 기능 키와 방향 키는 두번의 함수 호출을 통해서 무슨 키를 눌렀는지 확인 할 수 있다는 것입니다. 이들 키외에 ASCII코드에 있는 키는 ESC키를 제외한 나머지 키는 동일하다는 것을 확인할 수 있을 것입니다. 여기서 기능키와 방향키는 두번째 값을 8비트 쉬프트함으로써 ASCII코드와 중복되지 않게 다음과 같이 상수를 정의해 보았습니다.
#define F1 0x3b00 #define F2 0x3c00 #define F3 0x3d00 #define F4 0x3e00 #define F5 0x3f00 #define F6 0x4000 #define F7 0x4100 #define F8 0x4200 #define F9 0x4300 #define F10 0x4400 #define UP 0x4800 #define DOWN 0x5000 #define LEFT 0x4b00 #define RIGHT 0x4d00 #define ESC 0x11b ...생략... |
그리고, 이제 getch함수를 이용하여 기능키를 입력받아 확인할 수 있는 함수를 작성해 보았습니다. 참고하여 여러분의 것으로 승화하여 사용하시기 바랍니다.
#include <conio.h> //visual studio에서 getch()함수 사용을 위해 int fnGetKey() { int key; key = getch(); if((key == 0)||(key == 0xe0)) //기능키와 방향키를 눌렀을 경우에 { key = getch(); key = key << 8; // 0x3b ==> 0x3b00으로 변환 return key; } if(key == 0x1b) //ESC 키일 경우 { return 0x11b; } return key; } |
이제 프로그래밍을 함에 있어 로직 작성을 하는 기본적인 함수 작성에 대한 부분을 살펴보았습니다. 이 후 동적 메모리 할당과 파일 입출력에 관련된 라이브러리 사용법을 설명을 하고 나서 프로그래밍 과정으로 넘어갈 것입니다. 이번 장을 충분히 본인의 것으로 만들었다면 앞으로의 과정의 내용을 본인의 것으로 만드는 데 상대적으로 적은 비용이 들 것입니다. 앞으로의 과정은 프로그래밍에 직결되는 부분이니 반드시 실습을 병행해 나가시기 바랍니다.
'컴퓨터 > 언어,프로그래밍' 카테고리의 다른 글
[C언어] 배열과 포인터 (0) | 2009.03.23 |
---|---|
[C언어] 포인터 - void *와 함수 포인터 (기본) (0) | 2009.03.23 |
[C언어] 함수 - 매개변수 전달하기 (0) | 2009.03.23 |
[C언어] 함수의 흐름 (0) | 2009.03.23 |
[C언어] 함수 - 정의 (0) | 2009.03.23 |