C++/class

class_lab10.cpp 포인터의 개념

year.number 2022. 5. 25. 17:47


포인터의 주 사용 목적

1. 용량이 큰 데이터를 전달하는 경우 -> 비용이 큼
데이터 전달이 아니라 데이터 주소만 전달하기 때문에 시간, 메모리 등 비용적인 측면에서 효율적

2. call by value
함수의 매개변수로 어떤 값을 전달할 때 함수 내에서 매개변수를 수정하는 경우 변경되지 않음 (main함수 내에서는 변경O)
ex) main 함수 내에서의 x,y -> 함수에서 x++, y++을 해도 돌아왔을 때 변화X
그동안은 전역변수를 선언해서 사용하는 편법(?)을 이용했지만 포인터로 해결!


#include <iostream>
#include <iomanip>
using namespace std;

/*
 1. 포인터 변수란?
 주소를 가지고 있는 변수
 
 변수, 베열은 RAM(주기억장치)에 저장된다.
 int i의 메모리를 어디에 할당할지는(주소) 운영체제가 결정한다.
 
 [직접 참조] int i = 10;
 [간접 참조] int i가 4번지일 때, 4번지 = 10;
 
 4번지라는 주소를 알기 위해 필요한 것: 주소 연산자(&), 간접 참조 연산자(*)
 
 */




//함수 원형
void printArray(int x[], int size){

    
    for (int i = 0; i<5; i++)
        cout << setw(5) << x[i];
    cout << endl;
    
}

void intArray_i(int x[], int size){
    //배열을 {0,1,2,3,4}로 초기화
    for (int i = 0; i<5; i++){
        x[i] = i;
        cout << setw(5) << x[i];
    }
}


void intArray_p(int *x, int size){
    //배열을 {0,1,2,3,4}로 초기화
    for (int i = 0; i<5; i++){
        *(x+i) = i;
        cout << setw(5) << *(x+i);
    }
}


//함수 원형
//함수 오버로딩: 원래 함수 이름이 같아도 매개변수만 다르면 OK였지만 포인터는 XXXX!!
//생긴 건 다르게 생겼지만 int x[]도 주소고 int *x[]도 주소라서 컴파일러가 구분X

void printArray2(int *x, int size){
    for (int i = 0; i<5; i++)
        cout << setw(5) << *(x+i);
    cout << endl;
    
}



int main(){

    int i = 10;     //운영체제가 주소를 정해줌 (ex. 4바이트, 4번지)
    char c = 69;    //1바이트, 8번지
    double f = 12.3; //8바이트, 12번지
    
    //주소는 고정된 값이 아니기 때문에 항상 알아봐야 한다. -> 주소 연산자(&) 필요
    //&i = 4;
    //*p = 10;

    //주소 출력
    cout << "i의 주소: " << &i << endl;    //1000
    cout << "f의 주소: " << &f << endl;    //2000
    
    //16진법으로 출력
    cout << "i의 주소: " << (long long)&i << endl;    //1000
    cout << "f의 주소: " << (long long)&f << endl;    //2000
    
    //i의 저장값
    cout << i << endl;      //10, 직접 참조
    cout << *(&i) << endl;  //10, 간접 참조, i의 주소를 먼저 찾고(&i)->거기에는 뭐가 있을까?*(&i)
    
    
    // ====================================================================
    
    
    //i의 주소는 매번 바뀜 -> 저장했다가 나중에 간접 참조를 하고 싶을 때
    //변수에 주소를 저장: 주소가 12자리이기 때문에 int는 탈락, long long 자료형의 변수 사용
    cout << "\n포인터의 선언" << endl;
    
    /*
    [에러] 주소를 일반 변수에 저장할 수 없음
     
    long long juso;
    juso = &i;      //변수에 i의 주소 저장
    */
    
    // 주소를 저장할 수 있는 변수 필요 -> 주소형 변수(포인터 변수)
    
    int *pi;    //정수형 주소를 저장할 수 있는 포인터 변수 pi 선언,  pi에는 정수형 주소만 저장할 수 있다
    
    // * 연산자: 곱셈, 간접참조, 포인터 선언 3가지로 선언
    
    pi = &i;    //포인터 변수에 i의 주소 저장,
    cout << "i의 저장값: " << i << endl;    //10
    cout << "pi의 참조값 *pi: " << *pi << endl; //10(i값)
    cout << "i의 주소의 참조값 *(&i): " << *(&i) << endl;
    cout << "pi의 저장값: " << pi << endl;  //i의 주소
    
    
    // ====================================================================
    
    
    //변수 i에 1 더하기
    
    i++;
    cout << "\ni + 1의 저장값: " << i << endl;
    
    i = i + 1;
    cout << "i + 1의 저장값: " << i << endl;
    
    i = *pi + 1;
    cout << "i + 1의 저장값: " << i << endl;
    
    *pi = *pi + 1;
    cout << "i + 1의 저장값: " << i << endl;
    
    (*pi)++;
    cout << "i + 1의 저장값: " << i << endl;
    
    
    // ====================================================================
    
    /*
     [주의사항]
     
     1. 포인터 타입과 저장하는 주소의 타입이 일치해야 한다.
     ex)
     char c;
     pi = &c; // pi는 정수형 포인터 변수라서 할당할 수 없다.
     
     2. 포인터(초기화) 누락 -> 문법 상 문제는 없지만 쓰레기값 출력
     int *p;
     *p = 100;
     
     3. NULL 주소 참조
     int *p  = NULL;    //비어있는 주소로 초기화
     *p = 100;
     
     */
    
    
    // ====================================================================
    
    //포인터의 크기
    cout << "\n정수형 변수 i의 크기: " << sizeof(i) << endl;  //4
    cout << "정수형 변수 c의 크기: " << sizeof(c) << endl;  //1
    cout << "정수형 변수 f의 크기: " << sizeof(f) << endl;  //8
    
    double d;
    char *pc = &c;
    double *pd = &d;
    
    cout << "\n문자형 포인터 pc의 크기 " << sizeof(*pc) << endl;  //
    cout << "정수형 포인터 pi의 크기: " << sizeof(*pi) << endl;  //
    cout << "double형 포인터 pd의 크기: " << sizeof(*pd) << endl;  //
    
    
    // ====================================================================
    
    //포인터의 연산
    cout << "\n포인터의 연산" << endl;
    
    //포인터++의 의미?
    //=>다른 데이터의 주소! **번지수를 1 증가 시키라는 얘기가 아님!
    
    // pi = 100,
    // pi + 1 = 104,
    // pi - 1 = 96
    
    float *pf = (float *)100;
    pc = (char *)100;
    pi = (int *)100;
    pd = (double *)100;
    
    cout << "pc의 저장값: " << (long long)pc << endl;    //문자형 100번지
    cout << "pi의 저장값: " << (long long)pi << endl;    //정수형 100번지
    cout << "pf의 저장값: " << (long long)pf << endl;    //플롯형 100번지
    cout << "pd의 저장값: " << (long long)pd << endl;    //더블형 100번지
    
    
    cout << "포인터 연산 => 포인터에 ++(증감 연산자)를 했을 때" << endl;
    pc++;
    pi++;
    pf++;
    pd++;
    
    cout << "\npc의 저장값: " << (long long)pc << endl;   //문자형 101번지
    cout << "pi의 저장값: " << (long long)pi << endl;    //정수형 104번지
    cout << "pf의 저장값: " << (long long)pf << endl;    //플롯형 104번지
    cout << "pd의 저장값: " << (long long)pd << endl;    //더블형 108번지
    
    
    // ====================================================================
    
    
    // @@이해 필요
    // 112 - 108 = 8이 아니라 112과 104 사이에는 정수가 두 개 있으므로 결과는 2
    int *pi2 = (int *)112;
    cout << "pi2 - pi = " <<pi2 - pi << endl;       //112 - 104 => 8???
    
    
    // ====================================================================
    
    
    // 포인터와 증감 연산자
    
    int j;
    
    i = 3;
    pi = &i;
    
    cout << "\npi의 저장값: " << (long long)pi << endl;
    j = *pi++;
    
    cout << "\nj = *pi++ 실행 후 " << endl;
    cout << "i의 저장값: " << i << endl;    //3
    cout << "j의 저장값: " << j << endl;    //3
    cout << "pi의 저장값: " << (long long)pi << endl;   // +4번지만큼 증가
    
    // ====================================================================
    
    i = 3;
    pi = &i;    //초기 상태로 복구
    
    j = (*pi)++;
    
    cout << "\nj = (*pi)++ 실행 후 " << endl;
    cout << "i의 저장값: " << i << endl;    //4
    cout << "j의 저장값: " << j << endl;    //3
    cout << "pi의 저장값: " << (long long)pi << endl;
    
    
    
    // ====================================================================
    
    i = 3;
    pi = &i;    //초기 상태로 복구
    
    j = *++pi;
    
    cout << "\nj = *++pi; 실행 후 " << endl;
    cout << "i의 저장값: " << i << endl;    //3
    cout << "j의 저장값: " << j << endl;    //쓰레기값
    cout << "pi의 저장값: " << (long long)pi << endl;   // +4번지만큼 증가
    
    
    // ====================================================================
    
    i = 3;
    pi = &i;    //초기 상태로 복구
    
    j = ++*pi;
    
    cout << "\nj = ++*pi; 실행 후 " << endl;
    cout << "i의 저장값: " << i << endl;    //4(참조값 증가)
    cout << "j의 저장값: " << j << endl;    //4
    cout << "pi의 저장값: " << (long long)pi << endl;
    
    
    // ====================================================================
    
    // 배열과 포인터 [오늘의 핵심]
    /*
     함수에 배열 전달
     함수 호출: 함수명(배열명[], ...)
     함수를 호출할 때 배열의 크기를 쓰지 않는 이유 -> 배열명이 배열의 시작 주소이기 때문이다.
     
     !!! 배열명 = 배열의 시작 주소
     
     배열명은 포인터다.
     포인터는 주소를 바꿀 수 있지만,
     배열명은 배열의 시작 주소를 항상 알려줘야 하기 때문에 const pointer여서 수정 불가
     
     */
    
    int point[5] = {1,2,3,4,5};
    
    cout <<"\n배열의 시작주소는 제일 앞 원소의 주소(&point[01]) = " << (long long)&point[0] << endl;
    
    cout << "배열의 시작주소는 배열명(point) = " << (long long)point << endl; //소오름...
    
    //*(point+i) = point[i]
    // 주소로 표현  = index로 표현
    
    //예시
    //point = (int *)100; -> 배열명이 const pointer여서 오류
    
    cout << "\n*(point + 0) = " <<*(point + 0) << endl;     //1;
    cout << "point[0] = " << point[0] << endl;             //1;
    
    cout << "*(point + 1) = " <<*(point + 1) << endl;     //2;
    cout << "point[1] = " << point[1] << endl;             //2;
    
    
    //배열 전체 출력
    cout << "\n배열 전체 출력(index+loop): " << endl;
    for (int i = 0; i<5; i++)
        cout << setw(5) << point[i];
    cout << endl;
    
    
    cout << "\n배열 전체 출력(포인터): " << endl;
    for (int i = 0; i<5; i++)
        cout << setw(5) << *(point+i);
    cout << endl;
    
    
    // ====================================================================
    
    
    // 포인터와 함수
    // 배열을 함수에 포인터로 전달하는 방법
    
    //배열 전체 출력
    cout << "\n배열 전체 출력(index+function): " << endl;
    //printArray(point[5], 5);          //X
    //printArray(point[], 5);           //X
    printArray(point, 5);
    
    
    cout << "\n배열 전체 출력(포인터+function): " << endl;
    //printArray(&point[], 5);        //OK
    printArray2(point, 5);
    
    
    
    
    //배열을 {0,1,2,3,4}로 초기화(배열 이용)
    for (int i = 0; i<5; i++){
        point[i] = i;
    }
    
    cout << "\n배열의 초기화: " << endl;
    printArray(point, 5);
    
    //배열을 {0,1,2,3,4}로 초기화(포인터 이용)
    for (int i = 0; i<5; i++){
        *(point + i) = i;
    }
    printArray(point, 5);
    
    intArray_i(point, 5);       //배열
    cout << endl;
    intArray_p(point, 5);       //주소
    cout << endl;
    
    // 시간 되면 2차 배열과 포인터
    
    // 과제 설명
    //1. lab8의 함수 원형을 다 포인터로 바꾸기
    //int x[] -> int *x
    
    //merge 함수는 할 필요X, 함수 원형, 함수 선언만 바꾸기
    //main 함수도 손 댈 일이 없음(배열명이 배열의 주소였기 떄문에 호출에는 수정사항X)
    
    return 0;
}