코드는 https://github.com/blankspace-dev 참고 부탁드립니다.
1. 가변인자란? |
이번 포스팅은 가변인자에 대해서 정리하고자 합니다. 가변인자는 말 그대로 변수의 개수가 그때그때 변할 수 있는 인자라고 할 수 있습니다. 예를 들면, prinft 와 scanf 를 기본적으로 들 수 있겠습니다.
!!???? printf와 scanf (또는 cout, cin) 가 그렇게 거창한 이름을 가질만한 함수였어? 라고 생각하시는 분도 계실 수 있습니다. 하지만 생각해보면 print 와 scanf는 굉장히 사용자를 편하게 해주는 함수이라는 것이라는 사실에 동감하실 겁니다.
1 2 3 | printf( "%s %s %d %d %u %p ...", ~~, ~~ ,~~ ,~~ ,~~ ,~~...); scanf( "%s %d %s %c %s ...", ~~, ~~, ~~, ...); | cs |
이러한 printf와 scanf는 사용자의 임의대로 출력이나 입력하고자 하는 만큼 넣을 수 있습니다. 내부의 과정은 생각하지 않아도 된다는 말입니다.
그렇지만, 초보 개발자의 입장에선 이러한 동작 방법에 의문이 들 것입니다. 내가 지금까지 구현한 함수는 모두 인자의 개수가 정해진 것인데 어떻게 저런 방식이 가능하다는 것일까.. 라는 의문 말입니다.
따라서 C/C++에서 가변인자를 가지는 함수를 정의하는 법을 정리하고자 합니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | #include <iostream> using namespace std; void PrintNums(int args, ...) { cout << "args : " << args << endl; } int main(void) { PrintNums(1, 1); PrintNums(2, 1, 2); PrintNums(3, 1, 2, 3); PrintNums(4, 1, 2, 3, 4); return 0; } | cs |
>> args : 1 >> args : 2 >> args : 3 >> args : 4 |
위의 예제가 컴파일 에러가 나지 않는 것은 5번 라인의 ... 때문입니다. ...은 가변 인자를 가져오기 위한 용도라고 생각하면 됩니다. 따라서 12 ~ 15 라인의 내용을 보면 보내는 인자의 개수는 다르지만, 에러가 나지 않는 것입니다.
하지만, 결과는 위와 같이 첫 번째 인자만 가져올 수 밖에 없습니다. 이러한 인자의 개수가 다를 때는 다음과 같은 방법을 사용해야 합니다.
2. 가변 인자를 사용하는 방법. (1) |
먼저, 가변 인자를 사용하기 위해서는 다음의 함수 용도를 이해해야합니다.
- va_start(va_list, lastfix)
스택 상의 첫 가변인수의 위치를 구해서 ap에 대입합니다. ap는 void형 포인터 변수(va_list 형)이며, 함수가 호출되기 전에 선언되어 있어야 합니다.
두 번째 인수 lastfix는 고정 인수를 뜻합니다. 고정 인수가 필요한 이유는 가변인수의 시작번지를 찾기 위해서 중요한 역할을 합니다. 그 다음의 번지가 가변인수의 시작번지이기 때문입니다.
- va_arg(va_list, type)
가변인수를 읽는 함수라고 생각하면 되겠습니다. 첫 번째 인수 ap는 va_start가 사용한 ap와 동일한 변수이고, ap가 가리키는 번지의 데이터를 가변인수로 읽습니다.
두 번째 인수 type은 가변인수의 데이터형을 나타냅니다. 그러므로 데이터형에 따라서 데이터를 읽어 리턴한다고 생각하면 됩니다.
type의 인수로는 char, float은 사용될 수 없다고 합니다. 이것의 대체 방법은 밑에서 설명하겠습니다.
(window와 linux는 다르므로, 환경에 따라 허용될 수도 있습니다. 이것은 밑에서 설명하겠습니다.)
- va_end(va_lis)
가변 인수를 다 읽은 후, 함수가 정상적으로 리턴되도록 도와줍니다. va_end가 호출되면, ap가 변경되므로 va_satrt를 호출하지 않고는 가변인수를 다시 읽을 수 없도록 합니다.
예제를 바탕으로 설명하겠습니다.
VariableEx2.cpp
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 | #include <iostream> #include <cstdarg> // c 에서는 stdarg.h using namespace std; void PrintNums(int args, ...) { va_list ap; va_start(ap, args); for (int i = 0; i < args; i++) { int n = va_arg(ap, int); cout << "args " << args << " : " << n << endl; } va_end(ap); cout << endl; } int main(void) { PrintNums(1, 1); PrintNums(2, 1, 2); PrintNums(3, 1, 2, 3); PrintNums(4, 1, 2, 3, 4); return 0; } | cs |
결과:
>> args 1 : 1 >> args 2 : 1 >> args 2 : 2 >> args 3 : 1 >> args 3 : 2 >> args 3 : 3 >> args 4 : 1 >> args 4 : 2 >> args 4 : 3 >> args 4 : 4 |
결과를 보시면 다음과 21 ~ 24 라인에서 함수의 인자가 모두 출력되는 것을 볼 수 있습니다.
int n = va_arg(ap, int); 은 계속해서 ap가 int에 따라 번지를 옮기면서, 값을 리턴하여 n에 넣어준다고 생각하면 됩니다.
va_start, va_end는 가변인자를 읽을 때의 시작과 끝을 뜻하는 것입니다.
이러한 가변인자의 관련한 함수를 사용하기 위해서는 cstdarg (또는 stdarg.h) 헤더 파일을 이용하면 됩니다.
3. 가변 인자를 사용하는 방법. (2) - 서로 다른 데이터형을 출력할 때. |
위의 예제를 보시면 하나의 데이터형(int) 만 출력하는 것을 볼 수 있습니다.
따라서 이번에는 다양한 데이터형을 출력하는 방법을 소개하겠습니다.
VariableEx4.cpp
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 | #include <iostream> #include <cstdarg> using namespace std; void PrintNums(char* dataType, ...) { va_list ap; int count = 0; va_start(ap, dataType); while (dataType[count] != '\0') { switch (dataType[count]) { case 'i' : cout << "args : " << va_arg(ap, int) << " "; break; case 'c' : cout << "args : " << (char)va_arg(ap, int) << " "; break; case 'd' : cout << "args : " << va_arg(ap, double) << " "; break; case 's' : cout << "args : " << va_arg(ap, char*) << " "; break; default : break; } count++; } va_end(ap); cout << endl; } int main(void) { PrintNums("i", 1); PrintNums("ic", 1, 's'); PrintNums("sic", "Variable Ex", 2, 'h'); PrintNums("sdci", "Variable Ex", 2.128923, 'h', 4); return 0; } | cs |
PrintNums("sdci", "Variable Ex", 2.128923, 'h', 4); 을 보시면 첫 번째 인자에서 각 자료형의 첫 이름을 보내는 것을 확인할 수 있습니다.
이후, 13 ~ 28 라인에서 문자 하나씩 비교하여, 자료형에 따라서 출력하는 것을 확인할 수 있습니다.
이러한 자료형의 첫 이름은 숫자는 'd'이고, 문자열은 's' 이고... 이런건 아닙니다. 사용자가 정의한 대로 사용하면 됩니다.
그런데, 이상한 점을 발견할 수 있는데요. 예제의 19번 라인을 보시면 문자를 보내지만 va_arg(ap, int)로 출력하는 것을 확인할 수 있습니다.
저는 리눅스(우분투)에서 개발하기 때문에 그렇습니다. visual studio에서는 이렇게 하지 않아도 된다고 합니다. 이러한 환경 별 차이점은 다음의 표로 정리하였습니다.
환경 별 가변인자에서 사용하는 자료형 차이점. |
|||
Visual Studio |
char,bool |
short |
float |
GCC |
int |
int |
double |
혹시, Visual Studio에서 위의 예제를 확인하고자 하시는 분은 저의 git주소에서 VariableEx3.cpp 을 확인하시면 되겠습니다.
이상 포스팅을 마치겠습니다.
참고 자료 : 코딩도장
soen.kr
제 글이 도움이 되셨거나 공감이 되시는 부분이 있으셨다면, 밑에 있는 공감 버튼 한 번씩 꾸욱 눌러주시면 감사하겠습니다.
공감 버튼은 저에게 큰 도움이 됩니다. 감사합니다.
'프로그래밍 > C || CPP' 카테고리의 다른 글
[C/C++] 연산자 정리 (0) | 2017.05.28 |
---|---|
C/C++ cpp, h 파일 분할 방법 (0) | 2017.05.21 |
[C++ 정복하자] C에서 C++로 가기 위한 도약. [2] (0) | 2017.05.17 |
[C++ 정복하자] C에서 C++로 가기 위한 도약. [1] (0) | 2017.05.14 |
[C/C++] this 포인터 이해 및 활용 (0) | 2017.03.19 |
댓글