배열과 포인터

라니·2023년 3월 22일
0

C language

목록 보기
2/3
post-thumbnail

배열(Array)

  • 배열의 구조 : 자료형 배열이름 [크기][크기]…;

    • 자료형 : 배열의 요소가 어떤 값들의 집합인가를 지정함, int, char, double과 같이 기본 자료형 사용
    • 배열이름 : 배열도 변수이므로 이름 필요 (변수이름과 같은 규칙을 갖음)
    • 크기 : 요소의 개수가 몇 개인가를 []괄호 안에 정수 상수로 지정한다. 크기 지정이 하나만 있으면 1차원, 두 개 이상이면 다차원 배열이라고 한다.
      • 반드시 한 개 이상 입력해야한다.
      • 크기를 변수로 지정하면 안된다.
    • ex) int array1[20]; (정수형 20개짜리 배열), double array2[20][30]; (실수형 20개씩 30개인 배열, 즉 행이 20, 열이 30)

  • 배열 요소 접근 방법 : 인덱스

    • 임의의 위치에 존재하는 배열 요소에 접근하기 위한 요소의 위치 표시
    • [0]부터 [크기-1]까지 존재한다.

  • 배열의 초기화

    • 선언과 동시에 초기화
    • ex) int array [5] = {1,2,3,4,5}; → [ 1 2 3 4 5 ]
      int array [5] = {0,1}; → [ 0 1 0 0 0 ]
      int array [5] = {0}; → [ 0 0 0 0 0 ]
      int array [] = {1,2,3,4,5,6,7}; → [ 1 2 3 4 5 6 7 ]
  • 배열과 메모리

    • 배열이 선언되면 (배열의 자료형 * 크기) 만큼의 공간이 메모리에 할당
    • ex) int array [10]; → 4byte * 10 = 40byte

  • 다차원 배열

    • 배열이 원소를 배열로 갖는 것이 다차원 배열. 즉, 배열의 배열. 3차원 이상의 배열도 가능

    • ex) int arr2 [3][4]; →

    • 2차원 배열의 메모리상의 구조

      • 메모리는 2차원적인 구조가 아니므로 그림처럼 1차원 구조로 할당됨 ex) int arr2 [3][4];
    • 2차원 배열 요소의 접근 방법

        
    • 2차원 배열 초기화

      • 행 단위로 모든 요소들을 초기화
      • 행 단위로 일부 요소들만 초기화
      • 1차원 배열 형태로 초기화
      • 배열 크기를 가르쳐 주지 않고 초기화
        • 2차원 배열도 1차원배열과 마찬가지로 선언과 동시에 초기화할 경우 그 크기를 명시 하지 않아도 된다.
        • 배열의 크기 선언의 위치에서 앞의 크기는 알려주지 않아도 되지만, 뒤의 크기는 꼭 알려주어야 한다.



포인터(Pointer)

  • 포인터는 메모리 공간의 주소를 값으로 갖는 변수

    • 포인터는 변수지만 자료 값이 아닌, 자료가 있는 메모리 영역의 주소 값을 갖는 변수
    • 포인터는 C언어를 다른 언어들과 차별화 시키는 특징임. 직접 메모리에 접근하여 데이터 조작이 가능.
  • 포인터 선언

    • 가리키고자 하는 기본 자료형에 * 연산자를 붙여서 선언, ( *연산자를 주위로 띄어쓰기는 상관이 없다.)
    • ex) int *a;            //int형 변수의 주소 값을 지닐 수 있는 int형 포인터
            char *b;         //char형 변수의 주소 값을 지닐 수 있는 char형 포인터
            double *c;    //double형 변수의 주소 값을 지닐 수 있는 double형 포인터
    • 메모리의 주소값은 항상 4byte(32bit)이기 때문에 포인터 변수는 모두 4byte
    • 포인터는 가급적이면 선언과 동시에 초기화 시켜주는 것이 좋음
  • &연산자

    • 변수(메모리공간)의 주소를 알려주는 연산자
    • 변수의 이름 앞에 &연산자를 붙이면 해당 변수의 주소값이 반환됨
      #include <stdio.h>
      int main(void)
      {
      	int a = 2005;
       	printf(“변수 a의 메모리 주소는 %d입니다.\n”, &a);
       	printf(“변수 a의 메모리 주소는 %X입니다.\n”, &a);
       	return 0;
       }
       
       /*
       <결과>
       변수 a의 메모리 주소는 1245024입니다. 변수 a의 메모리 주소는 12FF60입니다.
       */
  • *연산자

    • 간접 참조 연산자 *
      : 포인터가 가리키는 주소에 있는 값을 읽거나 변경하도록 하는 연산자
      • 포인터 변수 앞에 *연산자를 붙이면 포인터가 가리키는 메모리 공간에 존재하는 값을 참조하겠다는 뜻
        void main(void) {
        		int * ptr;
            	int a = 21;
            	ptr = &a; //pointer 변수 ptr에 &a(a의 메모리 주소)를 저장 
            	printf(%d\n”, ptr);
            	printf(%d\n”,*ptr); //*pa는 변수 a자체를 의미
            }
            /*
            <결과>
            12345678
            21
            */
  • 포인터 변수의 자료 유형이 다양하게 존재하는 이유?

    • 포인터 변수는 가리키는 자료유형에 상관없이 모두 4byte로 크기가 똑같다. (메모리의 주소가 4byte이기 때문)

    • 포인터는 자료의 첫 번째 byte(첫번째 메모리공간)을 가리킨다. → 자료유형에 따라 메모리를 참조할때 몇 byte를 읽어하는지를 알아야함.

  • 포인터 형 변환

    • 포인터 변수는 자동 형 변환이 불가능
    • 형 변환을 하고 싶은 경우에는 강제 형 변환을 수행해야함
    #include <stdio.h>
    #include <stdlib.h>     // malloc, free 함수가 선언된 헤더 파일
    int main()
    {
    	int *numPtr = malloc(sizeof(int)); 	// 4바이트만큼 메모리 할당
        char *cPtr;
        *numPtr = 0x12345678;
        cPtr = (char *)numPtr;  		//int포인터 numPtr를 char 포인터로 변환. 메모리 주소만 저장됨
        printf("0x%x\n", *cPtr);   		//0x78: 낮은 자릿수 1바이트를 가져오므로 0x78
        free(numPtr);  					//동적 메모리 해제
        return 0;
     }
     /*
     <결과>
     0x78
     */

  • 포인터 변수의 초기화

    • 포인터 변수는 초기화가 매우 중요, 변수를 지정하고 변수의 포인터를 지정해야한다. 내 맘대로 포인터를 지정하면 안됨
    • 메모리 영역에는 시스템이 현재 사용하는 중요한 부분이 많음. 임의로 메모리 주소를 조작하는 것은 시스템에 치명적인 문제를 야기시킬 수 있음
    • 포인터 변수를 초기화 하지 않고 사용하는 경우
      → 포인터는 쓰레기 값을 가져 임의의 주소를 가리키게 됨
      이 상태에서 포인터를 이용하여 메모리의 내용을 변경한다면 문제 발생
      포인터가 아무것도 가리키고 있지 않을 경우 NULL(0)으로 설정해라.

배열과 포인터

  • 포인터와 배열의 관계
    • 배열 이름도 포인터! (배열의 이름은 포인터 상수)
    • 배열 이름에는 첫 번째 배열 원소의 메모리의 주소 값이 저장
      #include <stdio.h>
      int main (void)
      {
      	int a[5] = {1, 2, 3, 4, 5};
        	printf ("%d, %d\n", a[0], a[1]);
        	printf ("%d, %d\n", &a[0], &a[1]);
          printf ("배열 이름 : %d\n", a);      // 배열의 이름을 출력하면 포인터와 같이 메모리 주소가 출력
          return 0;
      }
      /*
      <결과>
      1, 2
      1245008, 1245012
      배열 이름 : 1245008   //배열의 시작주소
      */
    • 배열 이름은 수정이 불가능한 포인터임!! (상수 포인터)
      #include <stdio.h>
      int main (void)
      {
      	int a[5] = {1, 2, 3, 4, 5};
      	int b = 10;
          a = &b;           //a는 상수 포인터이므로 에러!!!!
          return 0;
      }
    • 배열 이름도 포인터이므로 그에 따른 포인터 타입이 존재함
      → 배열의 요소의 타입이 그대로 포인터 타입
    • 포인터를 배열처럼 사용하기
      • 배열이름이 포인터이므로 당연히 포인터를 배열 이름처럼 사용도 가능함
      #include <stdio.h>
      int main (void)
      {
      	int arr[3] = {1, 3, 5};
        	int *ptr;
          ptr = arr;
        	printf ("ptr[0] : %d\n", ptr[0]);
          printf ("ptr[1] : %d\n", ptr[1]);
          printf ("ptr[2] : %d\n", ptr[2]);
          return 0;
      }
      /*
      <결과>
      ptr[0] : 1
      ptr[1] : 3
      ptr[2] : 5
      */

포인터 연산

: 포인터 값을 증가 혹은 감소시키는 연산
  → 포인터가 참조하는 부분을 연산하는 것이 아니라 포인터 안에 저장된 주소값을 증가시키거나 감소시키는 것을 의미

  • 포인터 연산에 따른 실질적인 값의 변화는 타입에 따라 다르다.
    ( 포인터는 가리키는 데이터 타입의 크기 (byte)만큼 곱해져서 증가하거나 감소 )
    ex) double * 의 경우 double의 크기인 8byte만큼 증가, 감소
	#include <stdio.h>
      int main (void)
      {
      	char *ptr1=0;    //포인터를 0으로 초기화시키면 아무것도 가리키지 않는다는 의미
        int *ptr2=0;
        double *ptr3=0;
        printf ("증가시키기 전 : %d %d %d\n", ptr1, ptr2, ptr3);
        printf ("1증가시킨 후 : %d %d %d\n", ++ptr1, ++ptr2, ++ptr3);
        printf ("추가로 3증가 : %d %d %d\n", ptr1+3, ptr2+3, ptr3+3);
        return 0;
      }
      /*
      <결과>
      증가시키기 전 : 0, 0, 0
      1증가시킨 후 : 1, 4, 8
      추가로 3증가 : 4, 16, 32
      */
  • 포인터 연산을 이용한 배열의 사용(호출)
	#include <stdio.h>
      int main (void)
      {
      	int a[5] = {1, 2, 3, 4, 5};
        int *pArr=arr;
        
        printf ("%d\n", *pArr);
        printf ("%d\n", *(++pArr));
        printf ("%d\n", *(++pArr));  
        printf ("%d\n", *(pArr+1));  //포인터 연산을 이용해 배열을 다룰때
        printf ("%d\n", *(pArr+2));  //범위를 넘어가는 연산을 하지 않도록 주의
        return 0;
      }
      /*
      <결과>
      1
      2
      3
      4
      5
      */
	#include <stdio.h>
      int main (void)
      {
      	int a[53] = {1, 2, 3};
        int *pArr=arr;
        
        printf ("%d %d\n", arr[0], arr[1]);
        printf ("%d %d\n", *arr, *(arr+1));
        printf ("%d %d\n", pArr[0], *(pArr+1));
        return 0;
      }
      /*
      <결과>
      1, 2
      1, 2
      1, 2
      */



다차원 배열과 포인터

  • 2차원 배열 이름

    • 2차원 배열에서도 배열의 이름은 역시 포인터
    • 아래의 예시에서 a[0], a[1], a[2]도 역시 포인터이다.
    		#include <stdio.h>
    		int main() 
    		{
    			int a[3][2] = {1,2,3,4,5,6};
    			printf("a[0] : %d\n", a[0]);
    			printf("a[1] : %d\n", a[1]);
    			printf("a[2] : %d\n", a[2]);
    			printf("a : %d\n", a);
    		}
    		/* <결과>
    		a[0] : 1245004
    		a[1] : 1245012
    		a[2] : 1245020
    		a : 1245004
    		*/
  • 2차원 배열 이름과 연산

    • 아래 예시의 2차원 배열의 이름인 포인터 a와 배열의 원소인 포인터 a[0], a[1], a[2]의 차이를 비교
    • a[0], a[1], a[2]와 a, a+1, a+2, a[0]+1, a[1]+1 비교
    		#include <stdio.h>
    		int main() 
    		{ 
    			int a[3][2] = {1,2,3,4,5,6};
    			printf("a[0] : %d\n", a[0]);
    			printf("a[1] : %d\n", a[1]);
    			printf("a[2] : %d\n", a[2]);
    			printf("a[0] + 1: %d\n", a[0]+1);
    			printf("a[1] + 1: %d\n", a[1]+1);
    			printf("a : %d\n", a);
    			printf("a+1 : %d\n", a + 1);
    			printf("a+2 : %d\n", a + 2);
    			return 0;
    		}
    
    		/* <결과>
    		a[0] : 2030040
    		a[1] : 2030048
    		a[2] : 2030056
    		a[0] + 1 : 203044
    		a[1] + 1 : 203052
    		a : 2030040
    		a+1 : 2030048
    		a+2 : 2030056
    		*/
  • 2차원 배열의 포인터 타입 비교

    • 2차원 배열의 차이에 따른 포인터의 주소 값 증가 및 감소 비교
    		#include <stdio.h>
    		int main()
    		{
    			int arr1[4][2]; //arr1은 2개씩 4개이기 때문에 8씩 증가
    			int arr2[2][4]; //arr2은 4개씩 2개이기 때문에 16씩 증
    			/*arr1은 8씩 증가하지만 arr2는 16씩 증가한다. 따라서 arr1과 arr2는
    				같은 2차원 배열이지만 포인터 타입이 다르다.*/
    			printf("arr1 : %d\n", arr1);
    			printf("arr1+1 : %d\n", arr1+1);
    			printf("arr1+2 : %d\n", arr1+2);
    			printf("arr2 : %d\n", arr2);
    			printf("arr2+1 : %d\n", arr2+1);
    			printf("arr2+2 : %d\n", arr2+2);
    			return 0;
    		}
    
    		/* <결과>
    		arr1 : 1244996
    		arr1+1 : 1245004
    		arr1+2 : 1245012
    		arr2 : 1244956
    		arr2+1 : 1244972
    		arr2+2 : 1244988
    		*/
  • 2차원 배열과 포인터

    • 2차원 배열 원소인 배열의 연산
      • a[i][j] 중 a[k]가 연산으로 자신의 배열의 범위를 넘을 경우 a[k+1] 배열의 영역을 가리킨다.
      • 아래 예시. a[2] = a[0] +4 = a[1] + 2
    		#include <stdio.h>
    		int main() 
    		{
    			int a[3][2] = {1,2,3,4,5,6};
    			printf("*a[0] : %d\n", *a[0]);
    			printf("*(a[0]+1) : %d\n", *(a[0]+1));
    			printf("*(a[1]+1) : %d\n", *(a[1]+1));
    			printf("*(a[2]+1) : %d\n", *(a[2]+1));
    			printf("*(a[0]+3) : %d\n", *(a[0]+3));
    			printf("*(a[0]+4) : %d\n", *(a[0]+4));
    			return 0;
    		}
      
    		/*<결과>
    		*a[0] : 1
    		*(a[0]+1) : 2
    		*(a[1]+1) : 4
    		*(a[2]+1) : 6
    		*(a[0]+3) : 4
    		*(a[0]+4) : 5
    		*/



포인터의 포인터 (더블 포인터)

  • 포인터의 포인터
    • *연산자를 두 개 선언하는 포인터로 더블 포인터라고도 부른다.
    • 더블 포인터는 포인터를 가리킨다.
    • 포인터가 기본 자료형 변수의 주소 값을 저장하는 것처럼 더블 포인터는 포인터의 주소 값을 저장한다.
    		#include <stdio.h>
    		int main() 
    		{ 
    			int val = 4;
    			int *ptrl1 = &val; //싱글포인터 
    			int **ptrl2 = &ptrl1; //더블포인터 
    			return 0;
    		}
  • 더블 포인터와 메모리
    • 더블 포인터의 메모리 구조
      - 더블 포인터는 포인터의 첫 바이트 주 값을 가리킨다.
      - 포인터의 주소값도 최대 4byte이기 때문에 더블 포인터 자료형의 크기도 4byte

  • 배열 포인터와 포인터 배열
    • 배열 포인터란 배열을 가리키는 포인터이다.
    • 포인터 배열은 포인터를 모아놓은 배열.
profile
강아지를 좋아합니다🐶

0개의 댓글