Chap.13 포인터와 배열! 함께 이해하기

xyzw·2022년 9월 11일
0

C

목록 보기
2/6

13-1; 포인터와 배열의 관계

배열 이름은 포인터

배열의 이름은 값을 바꿀 수 없는 상수 형태의 포인터이다.

#include<stdio.h>

int main(void)
{
	int arr[3] = {0,1,2};
    printf("배열의 이름: %p \n", arr);  //%p: 주소 값의 출력에 사용된다.
    printf("첫 번째 요소: %p \n", &arr[0]);
    printf("두 번째 요소: %p \n", &arr[1]);
    printf("세 번째 요소: %p \n", &arr[2]);
    
    //arr = &arr[i];   //이 문장은 컴파일 에러를 일으킨다.
    
    return 0;
}

실행결과
배열의 이름: 0012FF50
첫 번째 요소: 0012FF50
두 번째 요소: 0012FF54
세 번째 요소: 0012FF58

배열의 이름과 포인터 변수 모두 이름이 존재하며, 특정 메모리 공간의 주소 값을 지닌다. 다만 포인터 변수는 변수지만, 배열의 이름은 가리키는 대상의 변경이 불가능한 상수라는 점에서 차이를 보인다. 즉 배열의 이름은 상수 형태의 포인터이다. 그래서 배열의 이름을 가리켜 포인터 상수라고 부르기도 한다.

1차원 배열 이름의 포인터형과 배열 이름 * 연산

1차원 배열이름의 포인터형

1차원 배열이름의 포인터 형은 배열의 이름이 가리키는 대상을 기준으로 결정하면 된다.

int arr1[5];

배열 이름 arr1이 가리키는 것은 배열의 첫번째 요소인데, 그것은 int형 변수이므로 arr1은 int형 포인터이다.

double arr2[7];

배열 이름 arr2가 가리키는 것은 첫번째 배열요소인 double형 변수이므로 arr2는 double형 포인터가 된다.

1차원 배열 이름의 * 연산

#include <stdio.h>

int main(void)
{
	int arr1[3] = {1, 2, 3};
    double arr2[3] = {1.1, 2.2, 3.3};
    
    printf("%d %g \n", *arr1, *arr2);
    *arr1 += 100;
    *arr2 += 120.5;
    printf("%d %g \n", arr1[0], arr2[0]);
    return 0;
}

실행결과
1 1.1
101 121.6

포인터를 배열의 이름처럼 사용할 수 있다.

배열의 이름과 포인터 변수는 변수냐 상수냐의 특성적 차이가 있을 뿐, 둘 다 포인터이기 때문에 포인터 변수로 할 수 있는 연산은 배열의 이름으로도 할 수 있고, 그 반대도 가능하다.

#include <stdio.h>

int main(void)
{
	int arr[3] = {15, 25, 35};
    int* ptr = &arr[0];  //int* ptr = arr;과 동일한 문장
    
    printf("%d %d \n", ptr[0], arr[0]);
    printf("%d %d \n", ptr[1], arr[1]);
    printf("%d %d \n", ptr[2], arr[2]);
    printf("%d %d \n", *ptr, *arr);
    return 0;
}

실행결과
15 15
25 25
35 35
15 15

13-2; 포인터 연산

포인터를 대상으로 하는 증감 연산

int형 포인터를 대상으로 1을 증가시키면 4가 증가하고, double형 포인터를 대상으로 1을 증가시키면 8이 증가한다.

#include <stdio.h>

int main(void)
{
	int* ptr1 = 0x0010;
    double* ptr2 = 0x0010;
    
    printf("%p %p \n", ptr1+1, ptr1+2);
    printf("%p %p \n", ptr2+1, ptr2+2);
    
    printf("%p %p \n", ptr1, ptr2);
    ptr1++;
    ptr2++;
    printf("%p %p \n", ptr1, ptr2);
    return 0;
}

실행결과
00000014 00000018
00000018 00000020
00000010 00000010
00000014 00000018

즉, 포인터를 대상으로 하는 증감연산의 결과는 다음과 같다.

n×sizeof(type) 의 크기만큼 주소 값이 증감

arr[i] == *(arr+i)

#include <stdio.h>

int main(void)
{
	int arr[4] = { 11, 22, 33 };
	int* ptr = arr;
	printf("%d %d %d \n", *ptr, *(ptr + 1), *(ptr + 2));

	printf("%d ", *ptr); ptr++;
	printf("%d ", *ptr); ptr++;
	printf("%d ", *ptr); ptr--;
	printf("%d ", *ptr); ptr--;
	printf("%d ", *ptr); printf("\n");
	return 0;
}

실행결과
11 22 33
11 22 33 22 11

포인터 변수 ptr은 int형 포인터이므로 값을 1 증가시킬 때 실제로 4가 증가한다. 따라서 배열 arr이 할당된 위치의 주소 값을 0x001000이라 가정할 때, (ptr+1)과 (ptr+2)의 연산 결과로 반환되는 주소 값은 각각 0x001004, 0x001008이다. 결국 [*ptr], [*(ptr+1)], [*(ptr+2)]의 참조결과 출력 시 arr[0], arr[1], arr[2]에 저장된 요소가 출력된다.

문제

1.

Q. 길이가 5인 int형 배열 arr을 선언하고 이를 1, 2, 3, 4, 5로 초기화한 다음, 이 배열의 첫 번째 요소를 가리키는 포인터 변수 ptr을 선언한다. 그 다음 포인터 변수 pt에 저장된 값을 증가시키는 형태의 연산을 기반으로 배열요소에 접근하면서 모든 배열요소의 값을 2씩 증가시키고, 정상적으로 증가가 이뤄졌는지 확인하는 예제를 작성해보자.

A.

#include <stdio.h>
int main(void) 
{
	int arr[5] = { 1, 2, 3, 4, 5 };
	int* ptr = &arr[0];

	printf("%d ", (*ptr) + 2);
	printf("%d ", (*(++ptr)) + 2);
	printf("%d ", (*(++ptr)) + 2);
	printf("%d ", (*(++ptr)) + 2);
	printf("%d ", (*(++ptr)) + 2);
    printf("\n");

	return 0;
}

2.

Q. 문제 1 에서는 포인터 변수 ptr에 저장된 값을 변경시켜가면서 배열요소에 접근하라고 하였다. 그런데 이번에는 포인터 변수 ptr에 저장된 값을 변경시키지 않고, ptr을 대상으로 덧셈연산을 하여, 그 결과로 반환되는 주소 값을 통해서 모든 배열요소에 접근하여 값을 2씩 증가시키는 예제를 작성해보자.

A.

#include <stdio.h>
int main(void)
{
	int arr[5] = {1, 2, 3, 4, 5};
	int* ptr = &arr[0];

	printf("%d ", (*ptr) + 2);
	printf("%d ", (*(ptr + 1)) + 2);
	printf("%d ", (*(ptr + 2)) + 2);
	printf("%d ", (*(ptr + 3)) + 2);
	printf("%d ", (*(ptr + 4)) + 2);
	printf("\n");

	return 0;
}

3.

Q. 길이가 5인 int형 배열 arr을 선언하고 이를 1, 2, 3, 4, 5로 초기화한 다음, 이 배열의 마지막 요소를 가리키는 포인터 변수 ptr을 선언한다. 그 다음 포인터 변수 ptr에 저장된 값을 감소시키는 형태의 연산을 기반으로 모든 배열요소에 접근하여, 배열에 저장된 모든 정수를 더하여 그 결과를 출력하는 프로그램을 작성해보자.

A.

#include <stdio.h>

int main(void) {
	int arr[5] = { 1, 2, 3, 4, 5 };
	int sum = 0;
	int* ptr = &arr[4];
	
	for (int i = 0; i < 5; i++) {
		sum += (*(ptr--));
	}

	printf("%d \n", sum);
	
	return 0;
}

4.

Q. 길이가 6인 int형 배열 arr을 선언하고 이를 1, 2, 3, 4, 5, 6으로 초기화한 다음, 배열에 저장된 값의 순서가 6, 5, 4, 3, 2, 1이 되도록 변경하는 예제를 작성해보자. 단, 배열의 앞과 뒤를 가리키는 포인터 변수 두 개를 선언해서 이를 활용하여 저장된 값의 순서를 뒤바꿔야 한다.

A.

#include <stdio.h>
int main(void)
{
	int arr[6] = { 1,2,3,4,5,6 };
	int* ptr1 = &arr[0];
	int* ptr2 = &arr[5];
	int temp = 0;

	for (int i = 0; i < 3; i++)
	{
		temp = *ptr1;
		*ptr1 = *ptr2;
		*ptr2 = temp;

		ptr1 += 1;
		ptr2 -= 1;
	}

	for(int i=0; i<6; i++)
		printf("%d ", arr[i]);

	return 0;
}

13-3; 상수 형태의 문자열을 가리키는 포인터

두 가지 형태의 문자열 표현

변수 형태의 문자열

char str1[] = "My String";

상수 형태의 문자열 - 포인터 기반

char* str2 = "Your String";

메모리 공간에 문자열 "Your String"이 저장되고, 문자열의 첫 번째 문자 Y의 주소값이 반환되어 포인터 변수 str2에 저장된다.

차이점

  1. 가리키는 대상을 변경 가능한가?
    str1: 불가능
    str2: 가능
  2. 가리키는 문자열의 내용을 변경 가능한가?
    str1: 가능
    str2: 불가능

상수 형태의 문자열과 함수

상수 형태의 문자열 선언 시 처리 과정

char* str = "Const String";
  1. 문자열이 메모리 공간에 저장된다.
  2. 그 메모리의 주소 값이 반환된다. (주소 값이 0x1234라고 가정)
  3. 포인터 변수 str에 0x1234가 저장된다.

문자열과 printf 함수

printf("Show your string");

""로 묶여서 표현되는 문자열은 그 형태에 상관없이 메모리 공간에 저장된 후 그 주소 값이 반환된다. 따라서 문자열이 0x1234번지에 저장되었다고 가정하면, 다음과 같은 형태가 된다.

printf(0x1234);

즉, printf 함수는 문자열의 주소 값을 전달받는 함수임을 알 수 있다.

문자열과 함수의 매개변수

WhoAreYou("Hong");

위와 같이 함수를 호출했을 때, 실제로 전달되는 값은 문자 H의 주소 값이므로 WhoAreYou 함수의 매개변수 선언은 다음과 같아야 한다.

void WhoAreYou(char* str){...}

13-4; 포인터 변수로 이루어진 배열: 포인터 배열

포인터 배열의 이해

주소 값을 저장할 수 있도록 포인터 변수를 대상으로 선언된 배열

#include <stdio.h>

int main(void)
{
	int num1 = 10, num2 = 20, num3 = 30;
    int* arr[3] = {&num1, &num2, &num3};
    
    printf("%d \n", *arr[0]);
    printf("%d \n", *arr[1]);
    printf("%d \n", *arr[2]);
    return 0;
}

문자열을 저장하는 포인터 배열

문자열의 주소 값을 저장할 수 있는 배열로, 사실상 char형 포인터 배열

#include <stdio.h>

int main(void)
{
	char* strArr[3] = {"Simple", "String", "Array"};
    printf("%s \n", strArr[0]);
    printf("%s \n", strArr[1]);
    printf("%s \n", strArr[2]);
    return 0;
}

0개의 댓글