본문 바로가기

학습공간/C, 프로그래밍기초

[7주차] 포인터와 배열 함께 이해하기, 포인터와 함수에 대한 이해 (Call by Value & Call by Reference)

반응형

13장

포인터와 배열의 관계

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

위 예제를 보면, a라는 배열(size = 5)에 0,1,2,3,4를 순서대로 담고 있다.

자 그럼 0번지 요소와 1번지 요소의 값을 보면 0, 1 이 나올 것이다.

그리고 & 연산자를 이용하여 각 각의 주소를 보니 1245036, 1245040 으로 나타나있다.

자료 형이 int 이므로 주소가 4byte 차이나는 것을 볼 수 있다. 그렇다면 세 번째 출력 문을 보니 배열의 이름 a를 출력하였다. 이를 통해 배열의 이름은 가장 첫 번째 0번지 주소와 같다는 것을 볼 수 있을 것이다. 다시 말하면 배열의 이름은 0번지의 주소. 다시 말해, a 라는 포인터 변수에 배열의 첫 번째 주소를 가리키고 있는 것과 같다. 그림으로 보면..

메모리 공간에서 &a[0] 는 a 와 같고, 그 후의 &a[1] 는 a+1 과 같다.

다시 말해 &a[i] == a+i 이고, a[i] == *(a+i) 로도 표현 할 수 있다.

또, 배열이 상수 포인터라 불리는 이유는     int a[5] = {0, 1, 2, 3, 4};

이 와 같은 경우에서 볼 수 있다.                int b = 10;       a = &b;

 

포인터 연산

포인터 연산을 하기 전에 포인터를 배열의 이름처럼 사용할 수도 있다는 경우를 보자.

포인터 변수 ptr를 이용해서 arr배열을 가리키게(배열의 첫 번째 주소를 담게) 하면 ptr도 배열과 같이 사용할 수 있다.

 

포인터 연산이란 포인터 값을 증가 혹은 감소시키는 연산을 말하는 것이다. (곱 X, 나눔 X)

위 예제를 보면 처음 포인터를 선언 후 0을 대입하였는데 0번지를 가리킨다는 것이 아니라 아무것도 가리키지 않는다(NULL)는 의미 이다. 이것을 널 포인터라고 한다. 또한 포인터 연산에 따른 실질적인 값의 변화는 포인터 타입(Pointer Type)에 따라 다르다는 것을 알 수 있다.

 

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

문자열 표현 방식의 이해

- 문자열을 표현하는 방법은 크게 두 가지로 문자열 변수로 표현, 문자열 상수로 표현이다.

오른쪽 그림으로 설명 하자면 위는

문자열 변수를 배열을 통해 선언을 한 경우고,

아래는 문자열 상수를 포인터를 통해 선언한

경우 이다. 문자열 변수는 내용을 저장하고

있는 것이고, 문자열 상수는 저장되어있는

내용을 단순히 가리키고만 있는 것이다.

똑같이 문자열을 담고 있는 것처럼 보이지만

내용을 변경할 수 있는지 없는지 차이가 있다.

 

문자열 상수에 대한 조금 더 깊은 이해

위에서 문자열 상수를 좀 더 깊이 바라보면 이런 생각을 할 것이다.

“왜 포인터는 주소를 담아야 하는데, 문자열을 담을 수가 있지?”

이는 문자열 상수가 메모리 공간에 할당되면 주소를 반환하는 특징이 있기 때문이다.

따라서 char *str = "ABCDEFG"; 라 하면 str 포인터에는 ABCDEFG 라는 값의 주소가 자동으로 들어가게 된다. 그러니까 단순하게 str 포인터는 ABCDEFG 라는 문자열을 가리키게 되는 것이다.

 

문제 1] 크기가 5인 int형 배열 arr을 선언하고 1,2,3,4,5로 초기화한 다음, 포인터 p를 선언해서 배열 arr의 첫 번째 요소를 가리키게 한다. 그 다음 포인터 p를 조작(포인터 연산을 의미함)해서 배열 요소의 값을 2씩 증가시킨 후, 전체 배열 요소를 출력하는 프로그램을 작성하자.

 

문제 2] 1번 문제와 같이 초기화를 하고 포인터 p를 선언하여 가리키게 하고 p를 이용해서 내용을 역순 시킨 뒤 출력하는 프로그램을 작성하자.

 

 

14장

포인터와 함수에 대한 이해

- 배열을 함수의 인자로 전달하는 방식

위의 예로 배열을 인자로 전달하기 위해서는 평소 우리가 사용하던 방식대로 값을 인자로 넘겨주는 방식이 아닌 주소를 인자로 넘겨주고 있다. 이에 대한 설명은 잠시 후에 다시 언급하기로 하고 다음 예제를 보자.

이 두 프로그램의 차이는 인자에 int *pArr를 쓰느냐 int pArr[]를 쓰느냐 인데 둘 다 같은 표현이다. 다만 int pArr[] 과 같은 선언은 함수에서 인자로써만 선언이 가능하다는 것이다.

 

 

 

다음은 이 와 같은 형식으로 만든 최대 값 구하기 프로그램이다.

여기에서 sizeof의 기능은 메모리의 byte 값을 반환한다고 알고 있다. 따라서 arr의 size는 4x10, int의 size는 4이므로 40/4 = 10 이 n에 들어가게 된다. 그리고 pArrarr을 받게 되어 자유롭게 참조 할 수 있다. (pArrarr과 같아지게 된다.)

 

- call by value 와 call by reference

call by value란 값에 의한 호출이고, call by reference란 주소에 의한 호출이다.

다시 말하면 함수의 인자로 값을 받아 호출을 할 것인지, 주소를 받아 호출을 할 것인지에 대한 차이이다. 먼저 call by value를 이용하여 두 수를 바꾸는 함수를 구현하여 보자.

이 프로그램의 의도는 val1의 값과 val2의 값을 서로 바꾸어 출력 결과물이 20 10이 되길 원했을 것이다. 하지만 swap 함수 내부에서는 잘 바뀌었는데 호출 후 메인함수에서는 변화가 없다. 이것이 어떻게 된 일인가? 함수를 인자로 넘겨줄 때 a변수에는 10을, b변수에는 20을 전달해준 것이다. 그 후에는 val1, val2a, b는 서로 독립적으로 작용한다. 따라서

함수를 호출을 하여도 바뀐 것은 ab이지 val1val2에는 아무런 지장을 주지 않았다는 것이다. 그렇다면 call by reference를 이용하여 함수를 구현해 보자.

위와 같은 프로그램이지만 인자를 주소로 받았을 때의 경우이다. a 와 b에 이번에는 각각의 주소를 넘겨주었다. 그리고 함수 호출하게 되면 그들이 가리키는 값들을 직접 참조해서 바꾸어 주게 된다. 따라서 각각의 주소가 가리키는 값들을 swap을 통해서 바꾸어 주므로 val1val2의 값이 바뀌어 위와 같은 결과가 나오게 되는 것이다.

 

- 포인터와 const 키워드

const 키워드는 변수를 상수로 고정시키기 위하여 선언 시 앞에 붙여 준다.

이처럼 const를 이용하여 PI라는 변수를 3.14 라는 상수로 고정

시켜버리게 되면 이 후에 PI값이 변경될 때 컴파일러가 에러를

찾아낼 수 있게 되는 것이다.

포인터 변수 역시 const를 붙여 선언하게 되면 변경이 불가능.

이처럼 꼭 고정시켜야 하는 변수가 필요할 땐  const를 이용하여

프로그램을 보다 안정적이게 만들 수 있다.

 

 

 

 

 

 

프로그래밍기초 연습문제 풀이

문제 1]

정수형 변수 a 선언 후 a = 20; 값을 입력한 뒤 포인터 변수 c 가 변수 a 를 가리키게 한다.

정수형 변수 b 선언 후 b = 포인터 변수 c 가 가리키는 값을 대입한다면 결과는 어떻게 나오는가?

#include <stdio.h> 
main()
{
 int a, b;
 int *c;
 
 a = 20;
 c = &a;
 b = *c;
 
 printf("a = %d, b = %d, c = %d\n", a, b, c);
}

 

문제 2]

sizeof 함수를 사용해서 정수형, 실수형, 문자형의 byte 및 해당 자료형의 포인터 변수의 크기도 구해보자.

#include <stdio.h>  
main()
{
 int *a;
 float *b;
 char *c;
 printf("정수형 크기 %d byte\n", sizeof(int) );
 printf("실수형 크기 %d byte\n", sizeof(float) );
 printf("문자형 크기 %d byte\n", sizeof(char) );
 printf("a의 크기 %d byte\n", sizeof(a) );
 printf("b의 크기 %d byte\n", sizeof(b) );
 printf("c의 크기 %d byte\n", sizeof(c) );
}

 

문제 3]

문자형 변수 a 선언 후 a = 'A'; 값을 입력한 뒤 포인터 변수 p 가 변수 a 를 가리키게 한다.

변수 a 값과 주소, 포인터 변수 p 값과 주소를 출력해보자.

#include <stdio.h> 
main()
{
 char a;
 char *p;
 a = 'A';
 p = &a;
 printf("변수 a에 기억된 문자 : %c\n",a);
 printf("변수 a의 주소값 : %x\n",&a);
 printf("p가 가리키는 주소의 문자 : %c\n", *p);
 printf("p에 저장된 주소값 : %d\n", p);

 

문제 4]

일차원 정수 배열(5)에 {10, 20, 30, 40, 50} 값이 저장되어 있다. 포인터 변수 p 가 배열을 기리키게 하고, 포인터 변수를 활용하여 배열의 모든 요소를 출력해보자.

#include <stdio.h> 
main()
{
 int a[5] = {10, 20, 30, 40, 50};
 int i, *p;
 p = a;
 for (i=0; i<5; i++)
 {
  printf("a[%d] = %d\t",i, a[i]);
  printf("a[%d] = %d\n",i, *(p+i));
 }
}

 

문제 5]

일차원 정수 배열(5)를 선언하고 포인터 변수 p 가 해당 배열을 가리키게 한다. 포인터 변수를 활용하여 값을 입력받고 배열의 모든 요소의 합계를 출력해보자.

#include <stdio.h>
main()
{
 int a[5];
 int i, *p, sum=0;
 p = a;
 printf("숫자입력 : ");
 
 for(i=0;i<5;i++)
 {
  scanf("%d ",(p+i));
  sum += *(p+i);
 }
 printf("합계 : %d\n",sum);

 

문제 6]

일차원 정수 배열(10)에 {2, 5, 7, 5, 8, 6, 4, 7, 9, 3} 값이 저장되어 있다. 포인터 변수 p 가 배열을 가리키게 하고, 포인터 변수를 활용하여 배열의 홀수번째 요소의 합계를 출력해보자.

#include <stdio.h>  
main()
{
 int a[10] = {2, 5, 7, 5, 8, 6, 4, 7, 9, 3};
 int i, *p;
 int sum = 0;
 p = a;
 for(i=0; i<10; i++)
 {
  if(i%2 == 0) 
   sum += *(p+i);  
 }
 printf("홀수번째 합 = %d\n",sum);

반응형