[열혈C프로그래밍] chapter 12. 포인터와 배열: 함께 이해하기

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 = &num;
  *(pnum+1) = 5;
  printf(" %d \n", pnum[0]); // 포인터도 변수처럼 사용할 수 있는 예2
  printf(" %d \n", pnum[1]);
  

  return 0;
}

=> 배열의 이름이 상수 형태의 포인터인 것만 다를뿐,
포인터를 배열처럼 사용할 수 있고, 배열을 포인터처럼 사용할 수 있다.

포인터 연산

포인터를 대상으로 하는 증가 연산의 결과:

포인터를 대상으로 하는 감소 연산의 결과:

# 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가 문자열도 저장할 수 있음을 쉽게 알 수 있다.