포인터와 배열의 관계
사실 배열의 이름은 포인터이다. 단 그 값을 바꿀 수 없는 상수 형태의 포인터 이다.
ex)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #include<stdio.h> int main() { int arr[3]={0,1,2}; printf("배열의 이름 : %p \n",arr); printf("첫 번째 요소 : %p \n",&arr[0]); printf("두 번째 요소 : %p \n",&arr[1]); printf("세 번째 요소 : %p \n",&arr[2]); return 0; } |
출력결과
배열의 이름 : 0x7ffeefbff5fc
첫 번째 요소 : 0x7ffeefbff5fc
두 번째 요소 : 0x7ffeefbff600
세 번째 요소 : 0x7ffeefbff604
int형 배열요소간 주소 값의 차는 4바이트 이다.
모든 배열요소가 메모리 공간에 나란히 할당된다 는 것을 위에 예제를 통해 확인할 수 있다.
또 한, 배열의 첫 번째 바이트의 주소 값과, 배열의 이름의 주소 값이 같음을 알 수 있다.
'배열의 이름은 배열의 시작 주소 값을 의미하며, 그 형태는 값의 저장이 불가능한 상수이다.'
포인터변수와 배열의 이름의 비교 !
주소 값의 변경만 차이점을 보고 있다. 이 점에서 배열의 이름은 '상수 형태의 포인터'라고 말할 수 있다. 그래서 배열의 이름을 '포인터 상수'라고 부르기도 한다.
1차원 배열이름의 포인터 형과 배열이름을 대상으로 하는 *연산
int arr[5]; // arr1은 int형 포인터 상수
배열 arr이 가리키는 것의 배열의 첫 요소이다. 배열의 첫 요소가 int형 변수이니 int *(int형 포인터)라는 결론
double arr2[7] ; // arr2 는 double형 포인터 상수
배열 arr2이 가리키는 것의 배열의 첫 요소이다. 배열의 첫 요소가 doble형 변수이니 doble *(double형 포인터)라는 결론
1차원 배열이름의 포인터 형은 배열의 이름이 가리키는 대상을 기준으로 결정하면 된다.
ex)*연산
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #include<stdio.h> int main() { int arr1[3]={10,20,30}; 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,*arr2); return 0; } |
출력결과
10 1.1
110 121.6
포인터를 배열의 이름처럼 사용할 수 있다.
배열의 이름과 포인터 변수는 변수나 상수냐의 특성적 차이가 있을 뿐, 둘 다 포인터이기 때문에 포인터 변수로 할 수 있는 연산은 배열의 이름으로도 할 수 있고, 배열의 이름으로 할 수 있는 연산은 포인터 변수로도 할 수 있다.
ex)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #include<stdio.h> int main() { int arr[3]={15,25,35}; int *ptr = arr; // int *ptr=&arr[0] 과 동일한 문장 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
6행 : 주석에서 보이듯이 배열이름을 이용해서 변수 ptr을 초기화해도 그 결과는 같다.
8~11행 : 포인터 변수와 배열의 이름을 대상으로 수행가능한 연산의 형태에는 차이가 없다.
포인터연산
포인터 변수를 대상으로 하는 연산은 증가 및 감소만 가능하다. ( + , - , ++ , -- , += , -= )
ex) sizeof(type)만큼 증가 및 감소가 된다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | #include<stdio.h> int main() { int *ptr1 = 0x0010; double *ptr2 = 0x0010; printf("%p %p \n", ptr1+1, ptr1+2); // 4증가하고 8증가한다. printf("%p %p \n", ptr1+1, ptr1+2); // 8증가하고 16증가한다. printf("%p %p \n", ptr1, ptr2); ptr1++; // 4증가한다. ptr2++; // 8증가한다. printf("%p %p \n", ptr1, ptr2); return 0; } |
출력결과
00000014 00000018 // sizeof(int)만큼 4증가, 8증가
00000018 00000020 // sizeof(double)만큼 8증가, 16증가
00000010 00000010 // 8행과 9행에서 진행된 +연산은 피연산자의 값을 변경시키는 연산이 아니므로 값 변경 X
00000014 00000018 // 12,13행 : ++ 이는 포인터 변수에 저장된 값 자체를 변경시키는 연산
int형 포인터를 대상으로 1을 증가시키면 4가 증가하고 double형 포인터를 대상으로 1을 증가시키면 8이 증가한다.
int형 포인터를 대상으로 n증가 n * sizeof(int)의 크기만큼 증가
double형 포인터를 대상으로 n증가 n * sizeof(double)의 크기만큼 증가
ex)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #include<stdio.h> int main() { int arr[3] = {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
6행 : 포인터 변수 ptr이 배열arr을 가리키고 있다.
ptr은 int형 포인터이므로 값을 1 증가시키는 연산을 할 때마다 실제로는 4가 증가한다.
*(++ptr); // ptr에 저장된 값 자체를 변경
*(ptr+1); // ptr에 저장된 값은 변경되지 않음
arr[i] == *(arr+i) // 중요한 결론
arr[0] arr[1] arr[2]
*(arr+0) *(arr+1) *(arr+2)
위의 두 문장은 같다 !
ex)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #include<stdio.h> int main() { int arr[3] = {11,22,33}; int *ptr=arr; printf("%d %d \n",ptr[0],*arr); printf("%d %d \n",ptr[1],*(arr+1)); printf("%d %d \n",ptr[2],*(arr+2)); return 0; } |
11 11
22 22
33 33
같다!
상수 형태의 문자열을 가리키는 포인터
두 가지 형태의 문자열 표현
char str1[ ] = "My String";
이는 배열을 기반으로 하는 '변수 형태의 문자열' 선언이다. 변수라 하는 이유는 문자열의 일부를 변경할 수 있기 때문이다.
char *str2 = "Your String";
이는 메모리 공간에 문자열 "Your String"가 저장되고, 문자열의 첫 번째 문자인 Y의 주소 값이 반환된다. 그리고 그 반환 값이 포인터 변수 str2에 저장된다. 그래서 str2를 char형 포인터로 선언한 것이다. char형 문자 Y의 주소 값이 저장되기 때문에
str1은 그 자체로 문자열 전체를 저장하는 배열, str2는 메모리상에 자동으로 저장된 문자열 'Your String"의 첫 번째 문자를 단순히 가리키고만 있는 포인터 변수이다. 하지만 str1도 실제로 문자 M의 주소 값이기 때문에 주소값을 담고 있다는 측면에서는 동일하다. 무엇이 차이가 있을까 ? 차이점은 배열이름 str1은 계속해서 문자 M이 저장된 위치를 가리키는 상태이어야 하지만 포인터 변수 str2는 다른 위치를 가리킬 수 있다.
char *str = "Your team";
str = "Our team"; // str이 가리키는 대상을 Our team으로 변경
하지만 배열 이름인 str1은 상수형태의 포인터이기 때문에 위와 같이 가리키는 대상을 변경할 수 없다.
또 하나 차이점
char str1[ ] = "My String";
위 문장은 애초에 문자열이 배열에 저장된다. 그리고 배열을 대상으로 값의 변경이 가능하기 때문에 이를 '변수형태의 문자열' 이라고 한다.
char *str = "Your String"; 이는 상수형태의 문자열
ex)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | #include<stdio.h> int main() { char str1[] = "My String"; // 변수형태의 문자열 char *str2 = "Your String"; // 상수형태의 문자열 printf("%s %s \n",str1, str2); str2 = "Our String"; printf("%s %s \n",str1, str2); str1[0]='X'; // 문자열 변경 성공 str2[0]='X'; // 문자열 변경 실패 printf("%s %s \n",str1, str2); return 0; } |
포인터 변수로 이루어진 포인터 배열
포인터 변수로 이뤄진, 그래서 주소 값의 저장이 가능한 배열을 가리켜 포인터 배열 이라고 한다.
int *arr1[20]; // 길이가 20인 int형 포인터 배열 arr1
double *arr2[30]; // 길이가 30인 double형 포인터 배열 arr2
ex)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #include<stdio.h> int main() { int num1 = 10, num2 = 20, num3 = 30; int *ptr[3]= {&num1,&num2,&num3}; printf("%d\n",*ptr[0]); printf("%d\n",*ptr[1]); printf("%d\n",*ptr[2]); return 0; } |
10
20
30
arr[0] -> 10 (num1)
arr[1] -> 20 (num2)
arr[2] -> 30 (num3)
문자열도 저장할 수 있다.
char *str[3]; // 길이가 3인 char형 포인터 배열
ex)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #include<stdio.h> int main() { char * str[3]={"Simple","Cprogramming","Hello"}; printf("%s\n",str[0]); printf("%s\n",str[1]); printf("%s\n",str[2]); return 0; } |
출력결과
Simple
Cprogramming
Hello
5행 : 큰 따옴표로 묶여서 표현되는 문자열은 그 형태에 상관없이 메모리 공간에 저장된 후 그 주소값이 반환된다.
즉 위 문장이 실행되면 초기화 리스트에 선언된 문자열들은 메모리 공간에 저장되고, 그 위치에 저장된 문자열의 주소 값이 반환된다. 따라서 문자열이 저장된 이후에는 다음의 형태가 된다.
char *str[3] = {0x1004, 0x1048, 0x2018}; 반환된 주소 값은 임의로 결정
반환된 주소 값은 문자열의 첫 번째 문자의 주소 값이니, char형 포인터 배열에 저장이 가능한 것이다.
'programming > C' 카테고리의 다른 글
C // 다차원배열 (0) | 2017.12.22 |
---|---|
C // 포인터와 함수 (0) | 2017.12.21 |
C // 포인터 (0) | 2017.12.19 |
C // 1차원배열 (0) | 2017.12.18 |
C // 재귀함수 (0) | 2017.12.16 |