8 분 소요

1. 포인터

1.1 포인터 기본 개념

포인터 변수는 주소값을 저장하는 변수인다.

1.1.1 포인터 변수 예시

p는 n의 주소를 저장하는 변수이다.


 	int n=10;
	int* p =&n; //p는 n의 주소를 저장하는 변수이다.
	
	*p =25; //n에 25가 저장됨.
	
	cout<< n <<' '<< *p; // 둘다 25가 출력된다.

1.1.2 포인터를 사용한 원소의 탐색

기존의 일반적인 원소탐색 방법

	int nArray[10];

    for(int i=0; i<10; ++i)
        nArray[i] = i; //*(nArray+i)=i
        //nArray[i]==*(nArray+i) 와 같기 때문이다.



포인터를 사용한 원소의 탐색

int nArray[10];
int*p = &nArray[0]; //int*p = nArray; 해도 같은 의미!

for(int i = 0; i < 10; i++){
    *(p+i) = i;
  }
  • 포인터 이용한 다양한 표현
int arr[6] = {1,2,3,4,5,6};
int *chr_ptr; // 1) int에 대한 포인터 변수 chr_ptr을 선언하는 문장 작성
int chr_ptr=arr; // chr_ptr이 arr 배열이 저장되어 있는 메모리 주소값을 갖도록 초기화.


cout << chr_ptr << endl; // 2) 배열의 시작주소 : 0x61fef4


chr_ptr++; // chr_ptr의 값을 하나 증가
cout << chr_ptr << endl; // 3) 배열의 시작주소+4 : 0x61fef8


cout << *chr_ptr << endl; // 4) 2
cout << arr << endl; // 5)  ~~1~~  0x61fef4
cout << arr+4 << endl; // 6)  5번쨰 원소의 주소 0x61fef4 + 16이므로, 0x61ff04 
cout << &arr[3] << endl; // 7) 옆 문장이 실행되었을 때의 결과는? 4번째 원소의 주소 : 0x61ff00
cout << arr[4] <<endl; // 5


// arr[3]의 값을 chr_ptr을 이용하여 프린트
cout <<*(chr_ptr+2)<< endl; //아까 chr_ptr하나 증겨시켰으므로 +2만해도 된다.
  • 배열과 포인터는 밀접한 연관이 있다.
    int p[] = {6, 5, 4};
   

    cout << p[0] << " " << p[1] << " " << p[2] << endl;
    // 6 5 4
    
    cout << *p << " " << *(p + 1) << " " << *(p + 2) << endl; // 주소를 증가시킨다음 그 내용
    // 6 5 4
    
    cout << p << " " << p + 1 << " " << p + 2 << endl; // 그다음 주소로 이동하고 싶을때는 +1을 해주면 된다.
    //0x61fed8 0x61fedc 0x61fee0
    
    cout <<&p[0] << " " << &p[1] << " " << &p[2] << endl; // 그다음 주소로 이동하고 싶을때는 +1을 해주면 된다.
    //0x61fed8 0x61fedc 0x61fee0

=> int 정수형은 4바이트이다. 따라서 주소가 4씩 늘어난다.

1.2 16진수 개념

  1. 16진수로 표현된 값

    :

    • 0x61fef416진수로 표현된 숫자이다.

    • 여기서 0x는 숫자가 16진수라는 의미를 나타낸다.

  2. 예를들어 16을 더한다.

    :

    • 이 값에 16을 더한다는 것은 10진수로 16을 더하는 것과 동일한 연산이다.
    • 컴퓨터에서는 모든 숫자를 2진수(바이너리)로 처리하므로, 어떤 진수든 더하기 연산 자체는 동일하다. 하지만 숫자를 표현하는 방식만 다를 뿐이다.
  3. 계산 방법

    :

    • 0x61fef410진수로 변환하면 6422260이다.

    • 여기에 16을 더하면 6422276이 된다.

    • 다시 16진수로 변환하면 0x61ff04가 된다.

1.2.1 10진수에서 16진수로 변환:

  • 예를 들어, 10진수 6422260을 16진수로 변환하면 0x61fef4가 된다.
  • 계산 방식은 6422260 ÷ 16을 반복하여 나머지를 구하는 방식으로 진행된다.

<10진수 6422260을 16진수로 변환하기 >

#1. 16으로 나누기

10진수에서 16진수로 변환하는 방법은, 숫자를 계속 16으로 나누면서 나머지를 기록하고, 몫이 0이 될 때까지 이를 반복하는 것이다. 나머지들은 16진수 자릿수에 해당하며, 가장 먼저 나온 나머지가 16진수에서 가장 낮은 자리에 오고, 마지막 몫은 가장 높은 자리에 해당한다.

10진수 6422260을 예로 들어보겠다.

  • 1단계: 6422260 ÷ 16 = 401391, 나머지 4

    (여기서 4는 16진수의 가장 낮은 자리에 해당한다. 즉, 16진수로 변환된 수의 마지막 자리가 된다.)

  • 2단계: 401391 ÷ 16 = 25086, 나머지 15

    (나머지가 15이므로 16진수에서는 F로 표시된다. 따라서 F가 16진수에서 두 번째 자리에 온다.)

  • 3단계: 25086 ÷ 16 = 1567, 나머지 14

    (나머지 14는 16진수에서 E에 해당하므로, E가 세 번째 자리에 온다.)

  • 4단계: 1567 ÷ 16 = 97, 나머지 15

    (나머지 15는 16진수로 F이므로 네 번째 자리에 F가 온다.)

  • 5단계: 97 ÷ 16 = 6, 나머지 1

    (나머지 1이 다섯 번째 자리에 해당한다.)

  • 6단계: 6 ÷ 16 = 0, 나머지 6

    (마지막 몫이 0이므로 변환이 끝나고, 나머지 6이 가장 높은 자리에 해당한다.)

#2. 나머지들을 역순으로 나열

나눈 결과에서 나온 나머지들을 역순으로 나열하면, 16진수로 변환된 숫자가 나온다.

  • 나머지들: 6, 1, F, E, F, 4

따라서 10진수 6422260을 16진수로 변환하면 0x61FEF4가 된다.

1.2.2 16진수에서 10진수로 변환:

  • 16진수 0x61fef4를 10진수로 변환하려면 각 자릿수에 해당하는 값을 계산한다.
  • 0x61fef4는 6 * 16^5 + 1 * 16^4 + 15 * 16^3 + 14 * 16^2 + 15 * 16^1 + 4 * 16^0로 계산된다.

2. 실습

2.1 포인터 변수

int main(void)
{
    int K[3] = {7, 8, 9};

    cout << K << " " << &K[0] << " " << &K[1] << " " << &K[2] << endl;
    // 0x61ff04 0x61ff04 0x61ff08 0x61ff0c
    // 배열의 이름은 배열의 시작주소를 의미함. 따라서 K는 K[0]의 주소와 같다.
    // K[1]은 K[0]의 주소에서 4만큼 떨어진곳에 있다. K[2]는 K[0]의 주소에서 8만큼 떨어진곳에 있다.

    /* C++에서는 문자열로 취급되는 배열은 주소를 통해 출력할 수 있으며, 
    배열의 첫 번째 요소에서 시작해 **널 문자(\\0)**를 만날 때까지 문자열을 출력한다.*/
    char C[4] = {'7', '8', '9','\\0'};
    cout << C << " " << &C[0] << " " << &C[1] << " " << &C[2] << endl;
    // 789 789 89 9

	//&C[1]은 배열의 두 번째 요소 '8'의 주소를 가리킨다. 
	//이때 주소는 '8'의 위치부터 출력이 시작되며, 배열의 끝인 널 문자 '\\0'를 만날 때까지 출력된다.

    int M[3] = {6, 5, 4};
    int *p = M; // 주소를 저장하는 변수라는 뜻***

    cout << *p << endl; // 변수의 내용이라는 뜻***
    // 6
    cout << p << " " << p + 1 << " " << p + 2 << endl; // 그다음 주소로 이동하고 싶을때는 +1을 해주면 된다.
    // 0x61fee0 0x61fee4 0x61fee8

    // 다음 둘은 같은 의미이다.
    cout << p[0] << " " << p[1] << " " << p[2] << endl;
    // 6 5 4

    cout << *p << " " << *(p + 1) << " " << *(p + 2) << endl; // 주소를 증가시킨다음 그 내용
    // 6 5 4

    p = &K[2];                                                // p에 K[2]의 주소를 저장
    cout << *p << " " << *(p - 1) << " " << *(p - 2) << endl; // 내용을 출력
    // 9 8 7
    cout << p[0] << " " << p[-1] << " " << p[-2] << endl;
    // 9 8 7

    return 0;

2.2 실습2-1

int main(void)
{
    int K[3] = {7, 8, 9}; // K[0], K[1], K[2]
    int M[4] = {6, 5, 4, 0}; // M[0], M[1], M[2], M[3]
    int N[2] = {1, 2}; // N[0], N[1]

  
    int *q[3]; // q[0], q[1], q[2]<---- 포인터 배열
    

    q[0] = K; // q[0]에 K의 주소를 저장
    q[1] = M; // q[1]에 M의 주소를 저장
    q[2] = N; // q[2]에 N의 주소를 저장

    cout << q[0] <<endl;

    //q[0] : K의 주소
    cout<< *(q[0]) << " " << *(q[0] + 1) << " " << *(q[0] + 2) << endl; // 7 8 9
    cout <<q[0][0] << " " << q[0][1] << " " << q[0][2] << endl; // 7 8 9
    
    //q[1] : M의 주소
    cout<< *(q[1]) << " " << *(q[1] + 1) << " " << *(q[1] + 2) <<" "<<*(q[1]+3) <<endl; // 6 5 4 0
    cout <<q[1][0] << " " << q[1][1] << " " << q[1][2] <<" "<<q[1][3] <<endl; // 6 5 4 0
    
    //q[2] : N의 주소
    cout<< *(q[2]) << " " << *(q[2] + 1) <<endl; // 1 2
    cout <<q[2][0] << " " << q[2][1]  <<endl; // 1 2

    cout<< *(*(q+2)+0) << " " << *(*(q+2) + 1) <<endl; // 1 2
    // q[2] : *(q+2)와 같다. 
    // q[2]의 주소에 0을 더하면 N[0]의 주소가 되고, 1을 더하면 N[1]의 주소가 된다.
    
    return 0;
}
  • (\*(q+2) + 0):
  • (q+2)는 배열 N의 시작 주소(N[0]의 주소)이며, 여기에 0을 더하면 배열 N의 첫 번째 요소를 가리킨다.
  • 따라서 (*(q+2) + 0)는 N[0]의 값인 1을 출력한다.
  • (\*(q+2) + 1):
  • (q + 2)는 여전히 배열 N의 시작 주소이다.
  • 여기에 1을 더하면 배열 N의 두 번째 요소 N[1]의 주소가 된다.
  • 이를 역참조하면 N[1]의 값인 2가 출력된다.

2.3 배열 호출 전달의 두가지 방법**

2.3.1 기존의 배열의 함수호출,전달

(이전에 실습1-3에서 했던 코드)

  • 함수 호출 시 배열의 이름은 달라도 상관없다. 중요한 것은 배열의 첫 번째 요소의 주소가 함수로 전달된다는 사실이다. 따라서 배열의 이름은 호출할 때와 함수 내에서 다르게 사용해도 문제되지 않는다.
  • 배열이 아닌 기본 자료형을 함수에 전달하면 값이 복사되어 전달된다. 하지만!
  • 배열을 함수에 전달할 때 배열의 값이 복사되지 않는다**. → 배열의 **주소가 전달되기 때문에, 함수에서 배열의 요소를 변경하면 원본 배열도 함께 변경된다.
// 함수가 호출될때 값을 복사해서 가져온다. 따라서 이름이 달라도 상관없다.
double biggest(double b[], int n) // 배열의 이름과 배열의 크기를 받아야함
// 위의 코드는 이함수내에서 선언하는것과 같은 효과를 가진다.
{
    double a_max = b[0];

    for (int i = 1; i < n; i++)
    {
        if (a_max < b[i])
        {
            a_max = b[i];
        }
    }
    return a_max;
}

int ex0909_6(void)
{

    /*배열에 입력받기*/
    double a[5]; // a[0] ~ a[4]

    cout << a; // 배열의 이름은 배열의 시작주소를 의미함. 0x61fed0

    cout << "type 5 floating point5 numbers>>";
    for (int n = 0; n < 5; n++)
    {
        cin >> a[n];
    }
    for (int n = 0; n < 5; n++)
    {
        cout << a[n] << " ";
    }

    /*큰수 찾기*/
    int N = 5;
    double a_max = biggest(a, N); // 호출할때는 타입이 필요없고, 배열의 이름만 적어주면 됨***

    cout << "max number is " << a_max << endl;
}

2.3.2 포인터를 이용한 배열 호출 전달

배열을 이용해서 다시 짜 보았다.

// double biggest(double *b, int n) //변수의 주소와 변수의 크기를 받아야함
double biggest1(double* b, int n) 
{
    double a_max = *(b+0); //b[0]

    for (int i = 1; i < n; i++)
    {
        if (a_max < *(b+i))
        {
            a_max =*(b+i);
        }
    }
    return a_max;
}

int main(void)
{

    /*배열에 입력받기*/
    double a[5]; // a[0] ~ a[4]

    cout << a; // 배열의 이름은 배열의 시작주소를 의미함. 0x61fed0

    cout << "type 5 floating point5 numbers>>";
    for (int n = 0; n < 5; n++)
    {
        cin >> a[n];
    }
    for (int n = 0; n < 5; n++)
    {
        cout << a[n] << " ";
    }

    /*큰수 찾기*/
    int N = 5;
    double a_max = biggest1(a, N); // 호출할때는 타입이 필요없고, 배열의 이름만 적어주면 됨***

    cout << "max number is " << a_max << endl;
    
    return 0;
}

2.4 실습 2-2

메인함수의 출력 a와 b가 30,40이 되도록 수정해봐라

void pfunc_1_(int a, int b)
{
    cout << a<<" "<<b <<endl;
    a=30; b=40;
}

int main(void)
{
    int a =10, b=20;
    pfunc_1_(a,b);
    cout <<a<<" "<<b<<endl; 
    return 0;
}

위의 코드를 실행하면, 호출한 함수에서 두값을 변경하였음에도, 다음과 같이 출력된다.

image-20241007025938943

이를 통해, 호출된 함수의 변수와, 호출한 함수의 변수는 다른 변수임을 알수 있다.

a 10
b 20
   
함수가 호출되는 순간, 변수가 다른 주소에 복사된다.  
a 10→ 30
b 20→ 40
즉, 호출된 함수에서 변수의 값을 바꾸어도
원래 함수에서는 바뀐 값이 적용되지 않는다.

이를 해결 ⇒ 포인터를 이용한 함수호출

//c는 주소이기 때문에 c에다가 넣으면 안된다.
void pfunc_1_(int* c, int* d)
{
    cout <<*c<<" "<<*d <<endl;
    *c=30; *d=40; //c는 주소이기 때문에 c에다가 넣으면 안된다. 
    //*C에 넣어야한다.
}

int main(void)
{
    int a =10, b=20;
   
    pfunc_1_(&a,&b);

    cout << a<<" "<<b <<endl;
    return 0;
}

image-20241007030106186

태그:

카테고리:

업데이트:

댓글남기기