티스토리 뷰



포인터 변수의 주소 연산



포인터가 자신이 가리킬 대상 메모리의 시작 주소만 기억하면 되기에 그에 따른 특성이 있다.

            

char *p1 = (char*) 100;

short *p2 = (short *) 100;

int *p3 = (int *) 100;

double *p4 = (double *) 100;

 

p1++;         //char형이기 때문에 p1에 저장된 주소 값이 101을 의미

p2++;        //short형이기 때문에 p2에 저장된 주소 값이 102을 의미

p3++;        //int 형이기 때문에 p3에 저장된 주소 값이 104을 의미

p4++;        //double 형이기 떄문에 p4에 저장된 주소 값이 108이 된다.

             


포인터와 대상의 크기

 

 

포인터의 데이터형과 가리키고자 하는 변수의 데이터형이랑 일치하는 경우


             int data = 0;

             int * p =&data;




만약 포인터의 데이터형이 가리키고자 하는 변수의 데이터형보다 작은 경우


      int data = 0;

             short *p = (short *)&data;





대상이 정해져 있지 않은 void * 형 포인터

 

void * 형 포인터는 사용할 메모리의 시작 주소만 알고 메모리 크기는 정해지지 않았을 때, 사용하는 포인터 형식이다.

이 포인터 형식을 어떠한 변수의 주소 값이든 담을 수 있다고 생각하면 된다. 하다못해 함수의 주소 값도 담을 수 있다.


int data = 0;

void * p = &data;          //data의 시작 주소를 가리키고 있다.

*p = 5;                          //오류 발생 정확한 크기를 알지 못해서

 

int data = 0;

void *p = &data;

*(int *) p = 5;               //형변환 문법을 사용하여 대상의 크기를

  4바이트로 지정하므로 data변수에 5를 저장


void를 사용하면 장점은 자기가 가리키는 대상의 크기를 알지 못하지만, 어떤 데이터 타입이든 받을 수 있다.

 

 

표준 입력 함수

 

표준 입력 버퍼


특정 키를 누를 때까지 사용자 입력을 임시로 저장하는 메모리 운영체제가 제공한다.

표준 입출력을 사용하는 시스템을 위해 별도의 메모리가 배정된다.

 

입력 버퍼에 있는 내용을 전부 사용하지 않는 경우

-> 다음에 호출하는 표준 입력 함수에 영향을 미칠 수 있음

입력 버퍼는 특정 키를 누를 때까지 사용자의 입력을 임시로 저장

-> 사용되지 않는 입력 정보는 입력 버퍼에 남아 있음

 

rewind 함수


입력 버퍼에 남아 있는 정보를 모두 지울 때 사용하는 함수이다. 원래는 fflush()라는 함수가 있었지만, 지금은 사용하지 못한다. 표준 입력 버퍼 초기화을 위해서 사용한다.

stdin 포인터 : 장치에서 입력한 값을 얻을 수 있음 ( rewind(stdin) )


 

getchar 함수


getchar 함수는 stdin으로 표현되는 표준 입력 스트림으로부터 하나의 문자를 입력 받아서 반환하는 함수이다. 

키보드에서 문자 하나를 입력 받는다.  문자 한 개를 입력 받더라도 enter 키를 눌러야 작업이 완료된다.

문자를 입력을 받는데, char 형식이 아닌 int 형식으로 데이터가 반환된다.

         

#include <stdio.h>

void main (){

int input_data;

input_data = getchar();

printf(“input : %c \n”, input_data);

}

 

getchar 함수를 사용시 주의할 점

       

getchar 함수를 두번 사용해서 연속으로 입력 받은 값들을 화면에 출력하는 코드가 있다.


#include <stdio.h>

void main (){

int input_data;

input_data = getchar();

printf(“first input : %c \n”, input_data);

input_data = getchar();

printf(“second input : %c \n”, input_data);

          }





위와 같은 결과가 나오는데, 이러한 결과가 나오는 이유는 getchar 함수는 엔터를 입력받으면 입력을 종료시킨다. 결과를 통해 유추하자면 한 문자를 받은 후, 엔터를 치면 first input : 1 은 제대로 출력되고, second input :  \n(엔터값) 이 들어가게 된다는 것을 알 수 있다.



문자 1개만 입력 받았을때, 입력 버퍼 상황이다. first input에 1이 들어가고, second input에 \n이 들어간다.

문자 4개를 입력시, 입력 버퍼 상황이다. first input에 1이 들어가고, second input에 2가 들어간다.

즉 getchar함수는 엔터를 치자마자 입력받기를 종료하고, 입력버퍼에는 첫 글자만 빠져나가고 개행문자(\n)만 남게되어 다음 getchar함수에 입력버퍼에 남아있는 개행문자를 입력받아 출력되는 것이다.


이를 해결하기 위해서는 입력 버퍼 초기화 함수인 rewind를 사용해주면된다. 


#include <stdio.h>

void main (){

int input_data;

input_data = getchar();

printf(“first input : %c \n”, input_data);

rewind(stdin);

input_data = getchar();

printf(“second input : %c \n”, input_data);

          }


아니면 getchar()를 한번더 사용한다. -> 엔터를 제거하는 용도로 사용한다.

     

#include <stdio.h>

void main (){

int input_data;

input_data = getchar();

printf(“first input : %c \n”, input_data);

getchar();

input_data = getchar();

printf(“second input : %c \n”, input_data);

getchar();

          }


여기서 getchar 함수의 반환값이 int형인 이유를 알아보자.

먼저 알아야 할 것이 있는데, EOF(End Of File)이라고 파일의 끝을 표현하기 위해 정해 놓은 상수이다. 이 상수값은 -1을 반환한다. EOF = -1이다. 반환되는 것은 1바이트 크기의 문자인데, 반환형은 int이다. 이유는 char형에 있다. char형을 어떤 컴파일러가 unsigned char로 처리하는 것이 존재하는데, 만약 이러한 컴파일러에 getchar를 사용하면 EOF의 값이 -1이 아닌 1로 표시되어 파일이 끝을 알리지 못하고 계속 읽어들이려는 상황이 발생된다. 이러한 불상사를 막기 위해 어떠한 상황에서도 -1을 인식 할 수 있는 int 형으로 반환형을 정의해 놓은것이다.


getc 함수


문자 하나를 입력 받기 위해 만들어진 함수 input_data = getc(stdin);

표준 입출력뿐만 아니라 파일 입출력에도 사용됨

파일에서 1바이트씩 정보를 읽어 사용자에게 전달해주는 역할

 


gets 함수

         

문자열을 입력 받는 표준 입력 함수이다. getchar형과 반대이다.           char input_string[10];

한번에 여러개의 문자를 입력 받을 수 있다.                                         gets(input_string);

엔터키를 입력할 때까지 입력한 모든 문자를 하나의 문자열로 간주하여 입력 받는다.

char 배열로 선언된 변수의 시작 주소를 넘겨주어야 한다.



#include <stdio.h>

void main() {

char input_string[10];

gets(input_string);

printf("input : %s \n", input_string);

}


여기서 문자열 입력은 test1234 다음에 NULL문자인 0이 포함된다. 

Enter 키까지 입력 버퍼에서 읽어와서 처리한다. getchar함수와 다르게 rewind 함수를 사용하여 입력 버퍼 초기화할 필요가 없다.

Enter 키는 입력 완료의 기준으로만 사용한다. 입력 완료의 기준으로 NULL 문자가 입력된다.

 

주소를 넘겨주는데 &를 사용하지 않는 이유

 

배열의 시작 주소를 적을 때 첫 번째 요소의 주소인 &input_string[0]이 정확한 표현이다.

-> input_string 이라 적으면 &input_string[0]과 같게 번역


프로그래밍할 때 키보드에서 &키와 [, ] 키의 위치가 불편한 위치에 존재한다.

->C언어에서는 생략할 수 있는 것은 최대한 생략

 

NULL 사용시 주의할 점


NULL은 값이 없음을 의미한다. #define NULL ((void *) 0)

0을 사용해도 컴파일러가 적절하게 판단하여 번역한다. null 메모리 주소가 없음으로 프로그래머들이 인식한다.

 

문자열을 정수로 변환해 사용하기


gets 함수를 사용하여 숫자 형태로 입력 받더라도 문자열로 인식한다. 

그래서 gets 함수로 입력받은 숫자 형태는 산술 연산이 불가하다.

문자열은 정수형 변수에 대입 불가

-> Gets 함수로 입력 받은 값으로 산술연산을 하려면 문자열 -> 정수로 변환


1단계 : 아스키코드 표를 이용해 문자열을 정수로 변환하기

아스키코드 표에서 숫자는 0 ~ 9 까지 순차적으로 나열

각 숫자 형식의 문제에서 문자 ‘0’의 아스키 코드 값을 빼면 정수 형태의 숫자 값

 





문자 형식의 숫자 1,2,3을 숫자로 만들기

1 100의 자릿수 *100, 2 10의 자릿수 * 10, 3 1의 자릿수 * 1해주면 된다.

 


문자 형식의 숫자 '1', '2', '3'을 숫자로 만들기


1은 100의 자릿수 이므로 X 100 , 2는 10의 자릿수 이므로 X 10, 3은 1의 자릿수이므로 X 1을 해주면된다.


#include <stdio.h>


int main (){

int pos_num = 100, num = 0 , i, tmp_num;

char num_string[4] = "123";


for(i =0; i <3; i++){

tmp_num = num_string[i]-'0';

num = num + tmp_num*pos_num;

pos_num = pos_num / 10;

}

printf(" %s -> %d \n", num_string, num);

}


길이 제한 없이 문자열 형식의 숫자를 정수로 변환하기


Sol 1)

#include <stdio.h>

#include <string.h>


int main (){

int pos_num = 1, num =0, i , count;

char num_string[4] = "123";

count = strlen(num_string);

for( i = 0; i< count-1; i++) pos_num = pos_num * 10;

for( i = 0; i < count ; i++){

num = num + (num_string[i] - '0') * pos_num;

pos_num = pos_num / 10;

}

printf("%s -> %d \n", num_string, num);

}


Sol 2)

#include <stdio.h>

int main () {

int num = 0, count = 0;

char num_string[4] = "123"


while(num_string[count] != 0 ) {

num = num * 10 + (num_string[count] - '0' );

count++;

}

printf("%s -> %d \n", num_string, num);

}

 

scanf 함수


다양한 키워드를 사용하여 문자, 문자열, 정수, 실수까지 모두 입력할 수 있도록 형식화된 입력을 제공한다.

형식화된 입력 : 입력 받을 데이터의 종류, 자릿수, 입력 형식 등 지정 가능

 

scanf 함수를 사용하는 방법


%서식 지정 키워드를 사용하여 변수 값을 일정한 형식으로 입력 받기 가능




scanf 함수에서 & 연산자를 사용하는 이유


한 번의 함수 호출로 여러 개의 값을 입력 받을 수 있도록 만들어졌기 때문에

-> 포인터 꼭 필요

여러 개의 값을 동시에 입력 받을 경우

-> 포인터를 통해 입력 값을 저장할 변수의 주소를 활용하는 scandf 함수 필요

&연선자를 사용하여 입력한 값을 저장할 변수의 주소를 넘겨주면

-> scanf 함수는 주소를 사용하여 입력 형식 키워드에 맞게 입력 값을 넣어준다. 

 

scanf 함수에서 입력된 값을 구분하는 방법

          

엔터키를 눌러야 입력이 끝남

-> 입력 값 구별은 엔터키와 공백 문자로도 가능

특별한 값 없이 엔터키나 공백 문자가 여러개 입력되면

->입력 무시하고 실제 정보를 기준으로 입력 처리


엔터키와 공백 문자로 입력 구분

-> 입력한 문자열에 공백이 들어가면 원하는 문자열을 다 받지 못하는 경우 발생

첫번째 공백 이후의 문자는 그대로 입력 버퍼에 남음

 

scanf함수는 입력 형식 키워드와 자료형이 일치해야 한다.


스캔 함수는 &연산자를 사용하여 변수의 주소를 전달받는 형식

-> 입력 형식 키워드와 변수의 자료형이 반드시 일치해야 함


#include <stdio.h>


int main (){

int num = 0;

while (1){

printf("input age : ");

scanf("%d\n", &num);


if(num > 0 && num <= 130) {

break;

}

else{

printf("Incorrect Age!!! \n");

}

}

printf("Your age : %d \n",num );

}


위의 코드는 정수를 입력하면 제대로 출력이 되지만 만약 문자를 입력시 예를 들어 a를 입력하면,





와 같은 결과가 나온다. 


#include <stdio.h>


int main (){

int num = 0;

while (1){

printf("input age : ");

if( scanf("%d\n", &num) == 0) {

rewind(stdin);

printf("[Enter] digit number!! \n" );

}

else{

if(num > 0 && num <= 130) {

break;

}

else{

printf("Incorrect Age!!! \n");

}

}

}

printf("Your age : %d \n",num );

}


위의 코드를 치면 무한 반복이 일어나는 사태를 막을 수 있다.


%d키워드를 사용하면 함수 내부적으로 int *형 포인터를 사용하여 입력 처리

-> 입력 형식 키워드 보다 크기가 작은 자료형의 변수를 사용하면 오류 발생

 

입력 키워드와 자료형이 다른 경우

 

          short data2;

          int *p = (int *)&data2;

          *p = 0x12345678;

 

 

배열과 포인터

 

          배열 표기법과 포인터 표기법의 관계



       


 

배열 표기법의 한계

 

int data[2] = {0x12345678, 0x12345678};


data [0] = 0x22; //data 배열 항목의 크기가 4바이트이기에 값 0x00000022가 대입된다. 



ptr이 임의의 배열을 가리키는 포인터이고 n 이 정수 일때, 

ptr [n] = *(ptr + n) 이라 사용한다


#include <stdio.h>


int main (){

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


printf("ar[2] = %d\n", ar[2]);

printf("ar[2] = %d\n", *(ar + 2));

printf("ar[2] = %d\n", 2[ar]);

}


ptr [n] = *(ptr+n) 이것은 일종의 약속이다. [] 연산자는 지정한 배열의 요소를 읽는 포인터 연산을 하도록 정의되어 있다. 포인터가 가리키는 배열의 n번째 요소를 읽으려면 배열의 시작 번지에서 n 만큼 더한 후 *을 이용해 값을 읽으면 된다.

*(data + 1) = 0x22;        =>     * (char *)(data + 1) = 0x22;

//일시적으로 int * 형을 char * 형으로 변환함



 

배열 변수의 이름은 배열의 시작 주소

 

          char data[4];

          char *p = &data[0];


          char *p = &data[0];

          char * p = & *(data + 0);

          char * p = & * data;       //주소를 얻는 &연산자와 주소를 지정하는 *연산자는 서로 반대 개념이기에 상쇄된다.

          char *p = data;

 

포인터로 배열의 주소를 저장하여 사용하기

 

배열은 해당 배열이 사용할 메모리 그룹의 시작 위치를 기준으로 색인 작업된 요소의 위치를 계산하여 사용한다.

배열의 색인 작업도 연산이기 때문에 같은 요소를 반복적으로 사용하는 경우 효율이 떨어진다.

 

#include <stdio.h>


int main (){

char data [5] = {1,2,3,4,5};

int i , sum =0 , select =2 ;

for ( i = 0; i<10 ; i++) sum = sum + data [ select];

printf("%d\n",sum);

sum =0;

for ( i = 0; i<10 ; i++) sum = sum + * (data + select) ;

printf("%d\n",sum);

}



         

배열의 특정 요소들이 지속적으로 많이 사용되는 경우에는 좀 더 효율적인 표현을 위해 배열의 특정 요소 주소를 

포인터 변수에 저장하여 사용한다.

 

#include <stdio.h>


int main (){

char data [5] = {1,2,3,4,5};

int i, sum = 0, select =2;

char * p = data + select;

for (i =0; i< 10 ; i++) sum = sum + *p;

printf("%d\n",sum);

}

 



배열 예제를 포인터 사용해서 바꾸기


#include <stdio.h>


int main (){


char data[5] = {1,2,3,4,5};

int result = 0, i;

for (i = 0; i <5; i++){

result = result + data[i];

}

printf("data 배열의 각 요소의 합은 %d입니다.\n",result);


/*

char data[5] = {1,2,3,4,5};

int result = 0, i;

char *p = data;//data 배열의 시작 위치를 포인터 변수 p에 저장함

for (i = 0; i <5; i++){

result = result + *p;

p++;

}

printf("data 배열의 각 요소의 합은 %d입니다.\n",result);

*/

}



배열을 기준으로 포인터와 합체하기


char * 형 변수 3개가 필요하다 => char *p1, *p2,*p3;


이렇게 사용하지 말고 배열을 이용하자


char *p[5]; // char *p1, *p2, *p3, *p4, *p5라고 선언한 것과 같다.


*가 앞쪽에 있으므로 p는 먼저 char형 포인터가 되고 다음으로 []에 의해 그런 포인터 변수 5개를 모아 배열을 

선언하게 된다.

포인터가 5개 선언된 것이기 때문에 p배열의 크기는 4 (포인트 크기)* 5 = 20바이트 이다.

개별 포인터를 사용하려면 p[0], p[1], p[2], p[3], p[4] 로 사용하면 된다.

각 포인터가 가리키는 대상에 값을 읽거나 쓰고 싶다면 *연산자를 추가하여 사용하면 된다.

이는 포인터 배열이라 하는데, 각각의 배열 요소인 포인터가 가리키는 대상은 원칙적으로 임의의 타입을 가질 수 있지만 주로 문자형을 가리키는 경우가 많다.

결구 포인터 배열은 배열이다.


#include <stdio.h>

int main (){

char *p[] = {"고양이", "개", "호랑이", "기린", "메뚜기", "양"};

int i;

for ( i= 0; i <6 ; i++) printf("%s \n", p[i]);

}


메모리에 구현된 모양을 그려보면 아래와 같다.



#include <stdio.h>


int main (){


char data1, data2, data3, data4, data5, i;

char *p[5] = {&data1, &data2, &data3, &data4, &data5};

for(i = 0; i< 5; i++) *p[i] = 0;

for(i = 0; i< 5; i++){

printf("P[%d]의 값은 %d입니다.\n",i,*p[i]);

}

}


char *p[5] = char *(p[5]);

포인터도 변수라서 배열을 이용하여 그룹으로 묶을 수 있다.


타입형 (*포인터명)[크기]


이 선언문에서 괄호는 생략할 수 없으며 반드시 필요하다. 이 형태를 배열 포인터라 하는데, 배열 포인터에서 

크기는 반드시 밝혀야 한다. 이 크기를 알아야 가리킬 배열의 전체 크기를 구할 수 있기 때문이다.

포인터는 자신이 가리킬 대상의 크기를 알아야 * 연산자로 값을 읽을 수 있고 증감 연산자로 앞뒤 요소로 

이동 가능하다.


#include <stdio.h>


int main (){

char data[3][5];

char (*p)[5];

p = data;

(*p)[1] = 3;

(*(p+1))[2] = 4;

(*(p +2))[4] = 5;





 

댓글