chapter 12
포인터와 배열: 함께 이해하기
포인터와 배열의 관계
배열의 이름은 포인터이다. 단, 그 값을 바꿀 수 없는 ‘상수 형태의 포인터’이다. %p는 주소 값의 출력에 사용되는 서식문자이다. 배열의 각 element의 크기는 그 배열의 자료형의 byte 크기이다. => int arr[3]; size of(arr[0])=4byte , size of(arr[1])=4byte , size of(arr[2])=4byte 또 모든 배열 element들은 메모리공간에 나란히 저장된다. => if arr[0] = 0x00000000 , arr[1] = 0x00000004, arr[2] = 0x00000008.
비교조건 | 포인터 변수 | 배열의 이름 |
---|---|---|
이름이 존재하는가? | 존재한다. | 존재한다. |
무엇을 나타내거나 저장하는가? | 메모리의 주소값 | 메모리의 주소값 |
주소값의 변경이 가능한가? | 가능하다. | 불가능하다.(상수의 개념 |
1차원 배열이름의 포인터 형은 배열의 이름이 가리키는 대상을 기준으로 결정된다.
int arr1[5]; // arr1이 가리키는 것은 arr1[0]이므로 arr1의 포인터형은 int* 이다.
## include <stdio.h>
int main(void){
int arr[3] = {15,25,35};
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]);
int num=3;
int* pnum = #
*(pnum+1) = 5;
printf(" %d \n", pnum[0]); // 포인터도 변수처럼 사용할 수 있는 예2
printf(" %d \n", pnum[1]);
return 0;
}
=> 배열의 이름이 상수 형태의 포인터인 것만 다를뿐,
포인터를 배열처럼 사용할 수 있고, 배열을 포인터처럼 사용할 수 있다.
포인터 연산
포인터를 대상으로 하는 증가 연산의 결과:
- int형 포인터를 대상으로 n 증가 = n x sizeof(int)의 크기만큼 증가
- double형 포인터를 대상으로 n 증가 = n x sizeof(double)의 크기만큼 증가
포인터를 대상으로 하는 감소 연산의 결과:
- int형 포인터를 대상으로 n 감소 = n x sizeof(int)의 크기만큼 감소
- double형 포인터를 대상으로 n 감소 = n x sizeof(double)의 크기만큼 감소 => type형 포인터를 대상으로
## include <stdio.h>
int main(void){
int arr[3] = {1,2,3};
printf("%d \n", *arr);
printf("%d \n", *(arr+1));
printf("%d \n", *(arr+2));
/*
int형 포인터를 대상으로 1를 증가시키면 주소값이 4가 증가하고,
double 형 포인터를 대상으로 1를 증가시키면 주소값이 8이 증가한다.
즉,
int형 포인터를 대상으로 n 증가 = n x sizeof(int) 크기만큼 증가
double형 포인터를 대상으로 n 증가 = n x sizeof(double) 크기만큼 증가
*/
return 0;
}
결론:
arr[i] == *(arr+i) // arr[i]는 *(arr+i)와 같다.
상수 형태의 문자열을 가리키는 포인터
char str1[] = "My String"; //배열로 문자열 표현 가능
char *str2 = "Your String"; //포인터 변수로 문자열 표현 가능
특징:
(1)
배열의 이름 str1의 값은 항상 문자 ‘M’이 가리키는 주소값이어야 하지만(바꿀 수 없다),
str2는 포인터 변수이기에 다른 주소값으로 재할당(assign) 할 수 있다.
(2)
str1은 배열이기에 배열의 element 들을 하나하나 바꿀 수 있어 문자열의 내용을 바꿀 수 있지만,
(애초에 문자들이 배열 element에 각각 저장된다)
str2가 가리키는 “Your String”은 ‘상수 형태의 문자열’라서 바꿀 수 없다.
대신 str2가 (1)에 말했듯이 다른 문자열 상수를 가리킴으로써 str2의 변수값을 변경할 수 있다.
## include <stdio.h>
int main(void){
char str[] ="My Home"; // 배열로 문자열 저장 가능.
char* str2 = "My Home"; // 포인터 변수로 문자열 저장 가능.
char* temp ="Your Home";
//str = temp; 배열의 이름은 상수형태의 포인터이기 때문에 assignment를 할 수 없다.=> 컴파일 오류가 난다.
str2 = temp; // 포인터 변수는 변수형태의 포인터이기 때문에 assignment를 할 수 있다.
printf("%s %s \n", str,str2);
str[0] = 'X'; //str은 배열이라서 애초에 문자열의 문자들을 배열의 element에 저장하였기 때문에 배열의 값을 바꿈으로써 문자열을 수정할 수 있다.
str2[0] = 'X'; //str2가 가리키는 것은 '상수 형태의 문자열'이라 결국 상수이므로 변경 될 수 없다.(상수 30을 40으로 바꿀수 없는 것처럼)
// 따라서 컴파일은 되지만 출력하면 런타임오류가 발생한다.(세그멘테이션 오류)
printf("%s \n", str);
printf("%s \n", str2); //이때 세그멘테이션 오류 발생.
return 0;
}
상수 형태의 문자열의 생성 과정:
(1) 문자열이 메모리 공간에 저장된다.
(2) 그 메모리의 주소값이 반환된다.
따라서 함수로 상수 형태의 문자열을 매개변수로 전달할때 문자열을 통째로 전달하는게 아니라,
문자열의 주소값을 전달하는 것이다.(명심)
printf("Show your story"); // 이는 printf("0x1234)";로 변한다. 즉 printf는 문자열 전체를 전달받는게 아니라 문자열의 주소값을 전달받는다.
simplefunc("Kim"); // 이 함수는 반환 타입 void인 경우, void simplefunc(*char ); 임을 유추할수 있다.: 문자열 전체(x) -> 문자열 주소값(o)
포인터 변수로 이뤄진 배열: 포인터 배열
포인터 배열
int* arr1[20]; // 길이가 20인 int형 포인터 배열 arr1
double* arr2[30]; //길이가 30인 double형 포인터 배열 arr2
=> 포인터 배열의 각 element들이 주소값을 저장하는 포인터 변수의 역할을 함을 쉽게 유추할 수 있다
즉 elements들의 값이 모두 주소값이란 뜻이다.
## include <stdio.h>
int main(void){
int num1=1,num2=2,num3=3;
int* pnum[3];
pnum[0] = &num1;
pnum[1] = &num2;
pnum[2] = &num3;
printf(" %p\n %p\n %p\n", pnum[0], pnum[1],pnum[2]);
return 0;
}
## include <stdio.h>
int main(void){
char* str[3]={"sorry,","my","friend..."};
printf("%s %s %s \n", str[0], str[1], str[2]);
return 0;
}
=> char* arr[3]과 같은 포인터 배열로는 각 element가 문자열도 저장할 수 있음을 쉽게 알 수 있다.