변수 상수화

 

const는 대개 변수 선언부 앞에 붙여서 수정이 불가능 하게 제한할 목적으로 사용한다. 변수가 상수화되는것이다. 한가지 예를 들어보자.


 

위의 코드를 컴파일 하면 에러를 발생한다. const 키워드가 붙은 변수 pi를 변경하려했기 때문이다. 이처럼 const 변수는 초기화 된 후 변경이 불가능 하며, 초기화는 반드시 선언과 함께 해주어야한다.

 

 

─────────────────────────────────────────────

 

포인터와 const

 

포인터 변수의 경우는 const키워드가 올 수 있는 위치가 두가지가 있다. 한가지는 위와같이 선언부의 맨 앞에 위치하는 거이고, 다른 한가지는 변수명 앞에 위치하는것이다.

 


 

위의 코드를 보면 3가지 선언이 있다.

 

상수화된 int포인터는 값을 바꿀수없다.

int*를 상수화하면 가르키는 위치를 바꿀 수없다.

 

첫번째 ( 상수화한다. int* 가 가르키는 변수 a를 즉! 값은 변경할 수 없다. )

두번째 ( int* 를 상수화 한다. 즉! int* p2 가 지니는 주소값을 변경할 수 없다. )

세번째 (첫번째,두번째의 중첩 즉! 값도, 지니는 주소값도 변경할수 없게된다.)

//error 구문을 자세히 보면된다. 

 

 

포인터가 가리키는 변수의 상수화 후 포인터 변수 대입연산이 가능할까?

 

[코드]

 

결과는 불가능

 

[결과]


 

 

중요한건 왜 사용하냐 인데

 

변경되지 않아야 하는 변수가 있다고 하고, 나는 알고있지만, 다른 사람이 코드수정시 그변수를 조작한다고 본다면 이건 분명 큰 실수 이다. 변수를 조작한다고 해서 프로그램 에러가 나지 않는게 큰 문제다. 값이 변하면 프로그램은 어떻게 될지는 모르는 일이다.

 

예를 들어 원의 넓이 구하는 프로그램이 있다.

원의 넓이는 3.14라는 파이값은 변하지 않는다. 이값은 const 키워드를 통해 상수화 시킨다.

이값이 const키워드 없이 변경이 된다면 원의 넓이 구하는 프로그램은 에러가 안난다고

잘 실행되는것인가?

 

const 키워드는 많은 개발자들이 중요성을 인식 못하지만, 습관화 하는것이 좋다.

적절히 잘 사용해 보자.

 

아래코드는 무엇을 의미할까?

 

 

#ifdef __cplusplus

extern "C" {

#endif

 

현재 코드가 C++ 일 경우 이후 구문들은 extern "C" 선언과 함께 범위(scope)로 지정되는 것이겠죠.

C++은 컴파일시 함수 이름을 모두 다른 이름으로 바꿔주기 때문에 C에서 컴파일된 함수를 링크시킬때는 해당함수의 선언부 혹은 include부분에 extern "C"라는 키워드를 사용하여 해당 함수가 C로 컴파일된 함수라는것을 컴파일러에게 명시적으로 알려야한다.

 

즉! C로 컴파일된 함수를 C++에서 부를 수 없기에 이를 가능하게 하는 기법이다

 

 

//CFile.c

 

 

//CppFile.cpp

 

 

[ 컴파일 & 실행 ]


 

[ 재귀적 함수 ]

 

재귀함수란 “자기 자신을 재참조하는 함수”이기 때문에 본인이 본인을 호출하는 구조로 되어있다.

 

한번 보자

 

[코드]

 

 

[결과]


 

 

남들은 보니 팩토리얼을 예제로 많이 사용했네요

팩토리얼은 그냥 공식만

 

n!=n×(n-1)!의 성질에서 n이 1일 때 1=1×0! 이 되므로 0!=1로 약속한다. 

 

 int factorial( int num )
{
if ( num <= 1 )  
return 1; 

else

{        // 재귀 호출-> num이 5라면 5, 4, 3, 2, 1들어가서 곱해지면서 리턴 1* 2* 3* 4* 5

return num * factorial( num - 1 );

}

}

 

 

 재귀함수는 무한루프에 빠지지 않도록 주의를 기울여야한다.

간단 코드니 여기서 무한루프 빠져도 금방 눈에 보이지만, 프로젝트가 커지면 디버깅도 힘들고

스트레스로 인해 머리털이 빠지는 현상이 일어 날지 모른다.

 

생각하며 코딩하자

 

 

[ 가변 인자 함수 ]

 

printf() 함수를 보면 신기한것이 있습니다.

 

함수원형은 stdio.h 파일에 포함되어있습니다. 자 그럼 함수 원형을 보죠.

 _Check_return_opt_ _CRTIMP int __cdecl printf(_In_z_ _Printf_format_string_ const char * _Format, ...);

 

printf( const char * _Format, ...);

문자열을 받는건 알겠는데, ... 이건 멀까요?

 

"..."  

 

이것이 가변인자(Variable argument function)을 나타내는 것이다

 

자 그럼 우리도 함수 하나 만들어 봅시다.

 

[코드]

 

 

[결과]


 

간단하게 구현해봤다.

우선 몇개의 매개변수를 전달할지의 int cnt를 두고 가변인자를 둬서

 

printf( 3, a, b, c ); //4byte 씩 차례대로 할당이 되므로

 

포인터 연산하여 매개변수 갯수에 맞게 출력하게 끔 구현되었다. 

 

 

 

─────────────────────────────────────────────

이제 좀 함수들을 이용해서

va_list, va_start(), va_end(), vsprintf() 매크로 함수를 써서 printf 함수를 만들어 보자.

 

[코드]

 

 

간단하게 함수 설명

 

va_start() 함수를 통해 가변인자 포인터변수 ap를 _vFormat_, 다음에 오는 매개변수 위치에 이동시켜고, vsprintf(); 문자열을 조립하여 pszlog에 저장합니다. va_end(); ap를 해제합니다.

 

 

[결과]


 

가변인자의 첫번째 매개변수는 전달될 매개변수의 갯수와 타입를 정하는데 사용한다. 

ex. "%d. Hello World %s\n " int, char* 총 2개가 전달된다.

 

 

그다지 어려운 부분은 없다. MSDN 에 깊은 설명이 있으니 참고

 

가변인자는 왜 쓰고, 언제 쓸까?

"인자의 갯수가 정해지지않았을때, 인자의 자료형이 정해지지 않았을때"

...

[ 문자열 함수 ]

 

스트림에 대한 이해 

 

- 연속된 데이터의 열(Line)을 의미한다.

- 입력하는 데이터나 출력하는 데이터를 입출력 순서에 의해서 순차적으로 처리되는 데이터 열(Line)

- 데이터를 이동 시킬수 있는 다리 역할을 한다.

 

이름

스트림의 종류

입 출력 장치

stdin

표준 입력 스트림

키보드

stdout

표준 출력 스트림

모니터 

stderr

표준 에러 스트림

모니터

  

 

─────────────────────────────────────────────

문자 단위 입출력 함수 

 

1) 문자 출력함수

 

 - 하나의 문자를 출력할 때 일반적으로 사용하는 함수

int putchar( int c );

int fputc( int c, FiLE* stream); /* 스트림 지정 가능   

 * 모니터 뿐만아니라 파일에도 문자를 출력할 

 * 수 있는 함수다. 

 */ 

2) 문자 입력함수

 

 - 하나의 문자를 입력할 때 일반적으로 사용되는 함수

int getchar();

int fgetc( FiLE* stream );          /* 스트림 지정 가능

                                                    * 키보드 뿐만 아니라 파일로부터도 데이터를

                                          * 입력 받을 수 있다.

                                          */ 

 

 


 

* fgetc 나 getchar() 함수가 파일의 끝에 도달하는 경우에도 EOF(-1)가 반환 된다.

* getchar() 함수는 표준 입력 함수지만, Ctrl + Z 키입력시 파일의 끝이라고 정의

 

 

─────────────────────────────────────────────

문자열 단위 입출력 함수

1) 문자열 출력함수

int puts( const char* s );                          // 자동 줄바꿈처리

int fputs( const char* s, FILE* stream ); 

 

 

2) 문자열 입력 함수

char* gets(char* s);                                // 잠재적인 문제, 쓰지말자

char* fgets(char* s, int n, FiLE* stream ); 

 


 

"fgets함수는 입력 받을 수 있는 최대 문자열의 길이 n을 초과하는 문자열이 입력되는 경우에는 n-1개까지의 문자만 입력을 받고, 마지막에 NULL문자를 삽입 해준다."

 

 

─────────────────────────────────────────────

표준 입출력 버퍼 

 

 - 표준 입 출력 함수를 사용하는 경우에는 버퍼(여분의 임시메모리 공간)을 사용하게 된다

 - 바로바로 처리하는것보다, 어느정도 쌓아 두었다가 처리를 하는것이 효율적

 

 

버퍼를 지우는 작업을 하는 fflush 함수 

 

int fflush( FILE* stream);   

 

"fflush함수를 입력스트림에 사용할경우 입력 버퍼 안의 데이터들은 모두 버려지게됨"

"fflush함수를 출력스트림에 사용할경우 출력 버퍼 안의 데이터들은 모두 즉시 출력"

 

언제 쓸까?

예를 들어 5자리를 입력받아야 하는데, 계속입력해서 10자리가 입력되었다. 이걸 fgets함수에서 읽어드릴 사이즈를 5자리로 정해놓았다면, 버퍼에는 나머지 5자리가 남아있다. 이렇게 된다면, 다시 입력을 받을때 입력버퍼에 남아있는 5자리가 밀려서 들어간다. 이때 잔챙이(?) 남아있는 버퍼를 지우고 다시 입력을 받아야 하므로 fflush로 버퍼를 비우는게 맞다.

 

─────────────────────────────────────────────

문자열 조작 함수

 

 

 

 

 

 

 

 

 

 

 

 

 

 

[ 배열 ]

 

배열의 선언 

 

 int array[10];

 

 배열 요소 자료형 int

 배열 이름 array

 배열 길이 10 

 

 

배열은 선언과 동시에 초기화  

 int arr1[5] = {1, 2, 3, 4, 5};    // 1,2,3,4,5

 int arr2[ ] =  {1, 3, 5, 7, 9};   // 1,3,5,7,9

 int arr3[5] = {1, 2};              // 1,2,0,0,0

 int arr4[5] = {0, };        // 0,0,0,0,0 실무에서 많이들 배열 초기화할때 사용한다.

 

 char str1[5] = "Good";         // G o o d \0  모든 문자열 끝에는 NULL문자가 자동 삽입

 char str2[]  = "morning";     // m o r n i n g \0

 

위의 선언에서 중요한건 문자열이다

 

예를 들어

char str[10] = "Hello";

이렇게 코드를 입력하면, 자동으로 \0 널문자가 삽입되지만,

 

10개의 char 형 변수를 잡아논 상태에서 초기화가 이루어지지 않으면,

 

char str[10];

[0] = 'H';

[1] = 'e';

[2] = 'l';

[3] = 'l';

[4] = 'o';

 

나머지 [5] ~ [9] 까진 쓰레기 값이 들어간다.

결국 printf 함수는 문자열의 끝인 NULL문자가 없으므로 어디가 마지막 문자인지 알수가 없어진다.

그래서 쓰레기문자까지 출력하게 된다.

 

해결할려면?

#include <string.h>

 

int main()

{

      char str[10];

      memset( str, 0, sizeof(char) * 10 );   //초기화

      return 0;

}

 

문자열 배열을 memset() 함수를 통해 초기화를 하던가, 아니면 문자열입력후 \0문자를 직접 넣어주면 된다. 아무래도 초기화가 더 편하겠죠. 문자열을 다룰때는 좀 더 신경쓰자.

 

 

여기서 정적 배열의 길이선언은 무조건 상수로 한다. 

왜 상수로 해야할까? 

 

힙영역의 필요성과도 결합되는데, 예를 한번 들어보자 

 

 void func( void )

{

int i = 10;

int array[i]; // error

}

 

위의 코드를 그냥 봐서는 문제가 없을꺼같다. 하지만, 컴파일러 입장에서는 에러를 내뿝는다.

왜냐면, 컴파일러는 변수의 대입된 값을 런-타임(프로그램이 실행되는 동안)에 결정하자 하고 넘어가 버린다. 그래서 int array[i]; 문장을 만나면, 컴파일러는 i값이 먼지 모르니 에러가 나는것이다.

 

즉 변수 i의 값이 결정되는 시기는 '컴파일-타임'이 아니라 '런-타임' 이다.

 

조금만 생각해보면, 왜 힙영역이 필요한지 바로 나온다.

나는 프로그램 실행도중 즉 런-타임동안 메모리를 할당하고 싶은데, 이때 유용하게 사용되는 메모리공간이 바로 힙(Heap) 영역이다.

 

 

[ 포인터 ]

 

 

 "포인터란 메모리의 주소 값을 저장하기 위한 변수이다."  

 

이 말이 가장 맞는말 같다.

 

포인터의 선언 및 연산자

 


 

 

여기서 한가지더!  

포인터는 반드시 선언과 동시에 초기화 하는것이 좋다. 초기화를 하지 않으면 쓰레기값이 들어가 어디를 포인터변수가 가르킬지 모르니 항상 선언과 동시에 초기화 해놓는 습관을 길러야 한다.

int main(void)

{

int * pA;    //초기화 안됐으니, 대박 쓰레기값

*pA = 10;  //error

return 0;

 

 

 

─────────────────────────────────────────────

포인터에 다양한 타입이 있는 이유

 

포인터는 주소값을 저장하는 4바이트로 구성되어있다. 하지만 위의 코드처럼 다양한 포인터형이 있는건 이유가 있다. 포인터는 주소값을 가르키고 타입에 따라 몇바이트를 읽어 들어야 하는지를 알아낸다

 

즉!

포인터의 타입은 메모리를 참조하는 방법을 알려주는 역할을 한다. 

 

 

 

[ 포인터와 배열 ]

 

"배열 이름도 포인터, 단 그값을 바꿀 수 없는 상수라는 점이 일반적인 포인터와의 유일한 차이점이다." 


 

"배열의 이름은 배열의 첫번째 요소의 주소 값을 나타낸다."  

왜? 위의 코드에서 보면 배열 a의 이름값이 배열 &a[0] 주소값과 같기 때문에

  

포인터 연산 

 


 

포인터 연산은 바이트 단위이고, 바이트수는 포인터 자료형에 따라서 달라진다. 

 

여기서! 중요한 결론 

 

arr[i] == *(arr+i)  => 즉 arr[3] == *(arr+3) 과 같다.

풀어서 읽어보면, 배열 arr의 [3]요소는 arr을 가르키는 포인터변수에 즉 arr[0] 에 +3 포인터연산으로 이동된 주소에서 배열 arr의 [3]요소의 참조값과 같다. 값은 위의 코드에서 보면 4다.

 

 

─────────────────────────────────────────────

문자열 상수를 가르키는 포인터 

 

char *str1 = "abcd"; 

char * str2 = "ABCD";  // 문자열 상수 선언하고 바로 char* str2 가 가르키고 있다.

 

이게 가능한이유는?

"문자열 상수는 메모리 공간에 저장이 되면, 그 순간에 문자열 상수의 주소값이 반환된다."  

 

즉 ABCD의 문자열 첫번째 문자('A')의 주소를 str2가 가르키게 된다.

 

 

 

─────────────────────────────────────────────

배열 요소로 포인터를 지니는 포인터 배열 

 

포인터 배열이란?

"메모리 주소 값을 저장할 수 있는, 즉 포인터를 요소로 지니는 배열"  

 

[코드] 


 

[결과] 


 

하나만 읽어보자

 

printf("%d \n"', *(*(arr+1)));

배열 arr[0] 요소에서 +1 포인터연산 4byte이동되고 arr[1] (*)참조값이니 &b의 주소값이 되고, (&b)의 (*)참조값이니 20의 값을 출력하게 된다.

 

 

[ 함수 인자로 배열 전달하기 ]

 

입력한 두값을 바꿔주는 swap() 함수를 만들어 보면서 Call-By-Value와 Call-By-Reference를 충분히 이해해 보자

 

 

함수 호출방식

 

1. Call-By-Value ( 값의 복사 )

 

void swap( int a, int b )

{

    int temp = a;

    a = b;

    b = temp;

 

    printf("a : %d \n", a);

    printf("b : %d \n", b);

}

 

 

코드는 간단하다. 임시변수 temp를 통해 a를 저장하고 b를 a에 옮기고 a의 값을 임시로 저장했던 temp를 다시 b로 복사해주는것이다. 바로 printf() 호출도 하고있다.

 

이함수에서 매개변수로 넘어오는 int a, int b 값은 복사되어 넘어온다.

즉! 매개변수로 입력했던 값은 이함수안에서 a와 b를 수정한다해서 바뀌지 않는다는 말.

 

 

2. Call-By-Reference ( 참조의 의한 호출 )

 

 

void swap( int* a, int* b )  // void swap( int a[], int b[] )

{

   int temp = *a;

  *a = *b;

  *b = temp;

}

 

 

참조의 의한 호출

 

즉!

매개변수를 포인터로 받아, 매개변수로 전달된 변수도 swap함수에서 변경하면 바뀌게 된다는점이

가장큰 Call-By-Value 랑은 차이점이다.

 

"인자로 전달된 주소가 가리키는 변수의 조작을 함수 내에서 가능하게 하는것!"

[ 전역변수, 지역변수 ]

  

전역 변수 ( Global Vaiable )라는 것은 그이름이 의미하는 것처럼, 프로그램 어디에서나 접근이 가능한 변수를 말하는 것이다.

 

지역 변수 ( Local Variable )는 중괄호({ }) 내에 선언된 변수

즉! 함수 내에 선언된 변수와 {}내에 선언된 변수 

 

[코드]

 

위의 코드에서 보면 전역 변수와 같은 이름의 지역변수가 존재하는데 이럴땐 어떻게 될까?

 

[결과]


 

의외로 해깔릴수도 있겠지만, 결과를 보면, 확실히

"지역 내에서는 지역 변수가 전역 변수보다 우선시 된다." 는걸 알수 있다.

간단한거지만, 초보자에게는 실수하기 쉬운 내용이다.

프로젝트가 커지고, 전역변수가 난무(?)하는 프로그램을 만나다보면.. 알수있겠지만.. 사실상 저런코드는 아주 좋지 않다. 지역변수에 표기법도 그렇고, 나중에 알겠지만, 헝가리식표기법을 숙지하는게 좋다

 

머 다행이도 전역변수와 이름이 같다해도... 지역변수가 위니까 문법오류가 잘안나오는것 뿐 !!!

 

 

[ static, extern 키워드 ]

 

static 의 사전적의미는 '정적인'이란 뜻이다. static 키워드로 선언한 int형 변수는 자동으로 0으로 초기화 되며, 메모리 영역중 Data 영역에 저장되어 프로그램 시작부터 종료시까지 유지된다.

 

1) static 전역 변수

 

해당 소스파일내에서만 유효하게 쓰겠다는 의미이다.

따라서 다른 소스파일에서 해당 전역변수를 참고할 수 없다. 그러나 static이 아닌 extern 키워드로 전역변수를 선언할경우 다른 소스에서도 인식할 수 있다.

 

<main.c 소스>


<sub.c 소스>


 
static으로 전역변수를 선언하면, 다른 소스파일에서는 그 변수를 직접 바꿀 수도, 알 수도 없다.
하지만 static 전역변수를 선언한 쪽에서 이 변수를 바꿀 수 있는 함수를 제공한다면, 다른 소스파일에서 함수를 호출하는 방식으로 전역변수의 값을 바꿀 수 있다.
 
 
2) extern 전역 변수
<main.c 소스> 

 

<sub.c 소스> 


extern으로 변수를 선언한다면, 다른 소스파일에서도 이름을 같게 하여 설정하면 그 변수를 여러 소스에서 공유해서 쓴다는 것. 이후 main에 관련된 바이너리 파일을 만들 때, 컴파일하여 만들어진 각각의 오브젝트 main.o와 sub.o를 같이 링킹해준다. 그러면 main 소스쪽에서도 sub.c 함수를 통해 만들어진 전역변수 값의 변화를 알 수 있다.
─────────────────────────────────────────────
 

3) static 지역 변수

 

static 지역변수는 전역 변수의 특징을 일부 지니면서도, 선언된 지역 내에서만 접근이 가능하다.

 

 

[코드]

 

 

 

위 코드에서 static int sCount 변수는 main 함수에서 접근할 수 없다.

하지만 static 키워드로 함수내에 선언된 변수는 프로그램 시작시 한번 초기화 되므로 아래의 결과처럼 Count 가 증가 할 수 있는것이다.

 

[결과]


프로그램이 실행되는 동안에 계속해서 유지되어야 하는 변수가 있다면 대부분의 경우 전역 변수를 머리 속에 떠올린다. 그런데 그 변수에 접근하는 영역이 특정 지역에 종속된다면 우리는 static 변수의 선언을 생각 해야한다.

 

 [ goto 키워드 ]

 

프로그램의 실행 위치를  정해진 위치로 이동 시킬때 사용되는 키워드이다. 

 

[코드]

 

[결과]


 

─────────────────────────────────────────────

 

위에 프로그램 예시에서 중요한것은 goto문은 단순히 프로그램 실행위치를 바꿔주는것 뿐이라는 점 

사실상 goto문은 많이 사용하지 않는다. 프로그램 구조가 때로는 많이 복잡해질 수도 있기 때문이다.

 

나도 머 자주 사용하는건 아니지만, 쓸때는 확실히 가독성을 중점으로 코딩하는것이 좋다.

 

이렇게 말해도 goto문은 정이 안간다.

1000줄, 10000줄되는 코드에 goto문의 3~5개의 lable을 왔다갔다 하면서, 디버깅을 해본사람이라면 알수 있을꺼다.

 

 

위에 코딩처럼 어떻게 로직을 구성하느냐에 따라 달라지긴 하겠지만, 중갈호를 붙여 구분을 짖고, lable은 구분을 지어 해당 영역만 실행되는 switch문의 스타일로 사용해도 나쁘진 않다.

 

[결과]

 

goto문의 문법적요소는 프로그래머가 goto문을 사용해 어떻게 코딩을 하냐에 따라 너무 크게 영향을 준다. 그러므로 신중히 사용할 필요가 있겠다. 득이될지, 독이 될지는 프로그래머 몫

 


 

[ 연산자 ]

 

 

증감 연산자 ( ++ , -- )

 


 

위의 코드에서 왜 for문은 전위,후위를 썼는데도 출력 값은 같을까요?

간단합니다. 

 

int i;

for ( i =0 (1번) ; i < 5 (2번) ; i++ (4번) )

{

printf(); (3번)

}

for문 루프의 흐름은 이렇게 됩니다.

 

먼저 반복되어지는 코드로 들어가죠

왜 전위,후위를 사용해도 같은값이 나오는지 이제 딱 감이오죠?

 

보통 for문의 증감식에 증감 연산자를 사용하게 되는데 컴파일러에서 최적화를 하기 때문에 차이가 없을 수도 있지만 최적화를 하지 않을 경우에는 전위 증감 연산자를 사용하면 후위 증감 연산자 보다 성능상 이득이 있을 수 있습니다.

 

증감 연산자를 사용할 때 전위나 후위 모두 사용해도 될 경우에는 전위 증감 연산자를 사용하면 성능상 이득을 볼 수 있습니다

 

 

배열에서 전위,후위 증감연산자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
using namespace std;
 
int main(){
 
    int arr[5] = {1, 2, 3, 4, 5 };
    int pos = 0;
 
    cout<<arr[pos++]<<endl;        // 출력값 : 1
    cout<<arr[pos]<<endl;        // 출력값 : 2
    cout<<arr[++pos]<<endl;        // 출력값 : 3
    cout<<arr[pos]<<endl;        // 출력값 : 3
 
 
    system("pause");
    return 0;
}

 

 

─────────────────────────────────────────────

복합 대입연산자 = , += , -= , *= , /= , %= )

 

int a += 4; 

  

풀어쓰면

 

int a = a + 4; 의미 합니다.

 

결과값은 4

 

 

─────────────────────────────────────────────

sizeof 연산자 

 

변수나 자료형의 사이즈를 계산하는 연산자 입니다.

 
int num = 0;
 
printf( "%d \n" , sizeof(int) );
printf( "%d \n" , sizeof(num) );
 
int 형 변수는 4byte이므로
두개의 printf 결과 값은 4


+ Recent posts