본문 바로가기
C++/뇌를 자극하는 C++

[C++] 챕터 12 - 배열과 구조체와 포인터 : 복합적인 방법으로 정보를 다루는 법

by Minkyu Lee 2023. 6. 14.

// 12-1 : 포인터에 1을 더하는 것의 의미

#include <iostream>
using namespace std;

int main()
{
    int array[10]; // 배열 정의
    int* p = &array[5]; // array[5]를 가리킨다.
    cout << p << "\n";
    cout << &array[5] << "\n";

    p++; // array[6]을 가리킨다.
    cout << p << "\n";
    cout << &array[6] << "\n";

    return 0;
}

/*
--- 결과 ---
000000A4C296F95C
000000A4C296F95C

000000A4C296F960
000000A4C296F960

---
포인터와 배열의 관계 파악 중요하다.
포인터는 커다란 크기의 정보와 함께 사용이 많다.

위에서 p++는 1이 더해지는 것이 아니다. 4가 더해진다.

포인터 타입은 산술 덧셈과 의미가 다르다.
다음 원소를 가리켜라 의미이다.

따라서 int타입이니까 4가 증가된 것이다.
char타입이라면 1이 증가된다.
즉, 타입의 크기만큼 증가된다.

뺼셈도 동일하다.
*/

 

// 12-2 : 포인터의 덧셈과 뺄셈

#include <iostream>
using namespace std;

int main()
{
    int iArray[10];
    int* pi = &iArray[3];
   
    cout << pi << "\n";
    cout << pi + 1 << "\n";
    cout << pi + 2 << "\n";
    cout << pi - 1 << "\n";

    short sArray[10];
    short* ps = &sArray[3];

    cout << ps << "\n";
    cout << ps + 1 << "\n";
    cout << ps + 2 << "\n";
    cout << ps - 1 << "\n";

    return 0;
}

/*
--- 결과 ---
00000053B80FF984
00000053B80FF988
00000053B80FF98C
00000053B80FF980

00000053B80FF9DE
00000053B80FF9E0
00000053B80FF9E2
00000053B80FF9DC

---
pi는 4바이트 단위
ps는 2바이트 단위 계산이다.

*/

 

// 12-3 : 포인터간의 뺄셈

#include <iostream>
using namespace std;

int main()
{
    short sArrays[10];
    short* ps1 = &sArrays[3];
    short* ps2 = &sArrays[7];

    cout << ps1 << "\n";
    cout << ps2 << "\n";
    cout << ps2 - ps1 << "\n";

    return 0;
}

/*
--- 결과 ---
000000BA5D31FBDE
000000BA5D31FBE6
4

---
포인터 간의 뺼셈은 원소 사이에 몇 개의 원소가 있는지 반환한다.
단순히 그 주소 간의 차 반환이 아니다.
*/

 

// 12-4, 5 : 주석 참고

// 12-4 : 간단한 배열의 탐색
int main()
{
    int nArray[10];
   
    for (int i = 0; i < 10; ++i)
    {
        nArray[i] = i;
    }

    return 0;
}
/*
배열의 이름은 첫번째 원소의 주소다.
아래의 코드를 보자.
float f[5];
if(f == &f[0])

f는 배열의 이름이다.
&f[0]은 첫번째 원소의 주소이다.
따라서 위 if문은 항상 성공한다.
*/

// 12-5 포인터의 덧셈을 사용한 배열의 탐색
int main()
{
    int nArray[10];
    int* p = &nArray[0]; // 첫번째 원소를 가리키는 포인터.

    for (int i = 0; i < 10; ++i)
    {
        *(p + i) = i; // i개만큼 뒤에 있는 원소를 가리킨다.
    }

    return 0;
}
/*
*(p+i)는 nArray[i]와 같은 의미이다.
 
배열의 이름을 포인터처럼 사용할 수 있다.

차이점도 있다.
배열의 이름은 항상 첫번째 원소의 주소만 가리키는 상수다.
포인터는 다른 원소 가리키게 변경할 수 있다.
*/

 

// 12-6 : 배열을 가리키는 포인터

#include <iostream>
using namespace std;

int main()
{
    long lArray[20];
    long(*p)[20] = &lArray;
    (*p)[3] = 300;

    cout << lArray[3] << "\n";

    return 0;
}

/*
--- 결과 ---
300

---
배열을 가리키는 포인터
개념은 간단하다.
하지만 문법은 간단치 않다.

long (*p)[20]에서 괄호를 빼면 []가 먼저 해석된다.
따라서 long* 타입의 원소 20개를 갖는 배열 정의가 된다. 원치 않는 결과다.
그래서 괄호를 사용해야한다.

올바르게 정의하면 다음과 같이 해석된다.
p는 포인터다.
p는 long[20]에 대한 포인터다.
= p는 long 타입의 배열에 대한 포인터다.
*/

 

// 12-7 : 포인터의 배열

#include <iostream>
using namespace std;

int main()
{
    // '포인터의 배열'을 정의한다.
    double a, b, c;
    double* pArray[3];

    // 각 원소가 변수를 가리킨다.
    pArray[0] = &a; // a의 주소를 저장한다.
    pArray[1] = &b;
    pArray[2] = &c;

    return 0;
}

/*
--- 결과 ---

---
원소의 타입이 포인터라는 것을 제외하고 일반 배열과 다를 것이 없다.

*/

 

// 12-8 : 배열을 포함하는 구조체

#include <iostream>
using namespace std;

struct StudentInfo
{
    char name[20];
    int stdNumber;
    float grade[2];
};

int main()
{
    StudentInfo si = { "Kim Chol-Su", 200121233, {3.3f, 3.5f} };

    cout << si.name << "\n";
    cout << si.stdNumber << "\n";
    cout << si.grade[0] << " " << si.grade[1] << "\n";

    return 0;
}

/*
--- 결과 ---
Kim Chol-Su
200121233
3.3 3.5

---
배열을 포함하는 구조체.
특별한 것은 없다.

*/

 

// 12-9 : 구조체의 배열

#include <iostream>
using namespace std;

struct StudentInfo
{
    char name[20];
    int stdNumber;
    float grade[2];
};

int main()
{
    // 구조체 초기화
    StudentInfo stdInfos[5] = {
        {"1 Chol - Su", 100121233, {1.3f, 1.5f}},
        {"2 Chol - Su", 200121233, {2.3f, 2.5f}},
        {"3 Chol - Su", 300121233, {3.3f, 3.5f}},
        {"4 Chol - Su", 400121233, {4.3f, 4.5f}},
        {"5 Chol - Su", 500121233, {5.3f, 5.5f}},
    };

    // 출력
    for (int i = 0; i < 5; ++i)
    {
        cout << stdInfos[i].name << "\n";
        cout << stdInfos[i].stdNumber << "\n";
        cout << stdInfos[i].grade[0] << " " << stdInfos[i].grade[1] << "\n\n";
    }

    return 0;
}

/*
--- 결과 ---
1 Chol - Su
100121233
1.3 1.5

2 Chol - Su
200121233
2.3 2.5

3 Chol - Su
300121233
3.3 3.5

4 Chol - Su
400121233
4.3 4.5

5 Chol - Su
500121233
5.3 5.5

---
구조체의 배열.
원소의 타입이 구조체라는 점을 제외하고는 다를 것이 없다.

*/

 

// 12-10 : 구조체를 가리키는 포인터

#include <iostream>
using namespace std;

struct Rectangle
{
    int x, y;
    int width, height;
};

int main()
{
    Rectangle rc = { 100, 100, 50, 50 };
    Rectangle* p = &rc;

    // 구조체 멤버에 접근
    (*p).x = 200;
    p->y = 250;

    // 출력
    cout << rc.x << rc.y;
    cout << rc.width << rc.height << "\n";

    return 0;
}

/*
--- 결과 --- (임의로 엔터침)
200
250
50
50

---
구조체를 가리키는 포인터
(*p).x처럼 괄호로 감싸주어야 연산자 우선순위 문제가 없다.
p->x도 동일한 기능이다. 이게 더 대중적인 방식이다.

*/

 

// 12-11 : 포인터를 포함하는 구조체

#include <iostream>
using namespace std;

struct IHaveAPointer
{
    int x, y;
    long* pl;
};

int main()
{
    // 참조될 변수
    long l = 300;

    IHaveAPointer ihap;
    ihap.pl = &l; // 참조될 변수를 가리킨다.

    return 0;
}

/*
--- 결과 ---


---
포인터를 포함하는 구조체.
특별할 것은 없다.

*/

 

// 12-12 : 서로를 가리키는 구조체 변수들

#include <iostream>
using namespace std;

struct Dizzy
{
    int id;
    Dizzy* p; // 자신을 포함한 Dizzy 타입을 가리킬 수 있다.
};

int main()
{
    Dizzy a, b, c;

    a.id = 1;
    a.p = &b; // a는 b를 가리킨다.

    b.id = 2;
    b.p = &c; // b는 c를 가리킨다.

    c.id = 3;
    c.p = &a; // c는 a를 가리킨다.

    cout << "a.id = " << a.id << "\n";
    cout << "b.id = " << a.p->id << "\n";
    cout << "c.id = " << a.p->p->id << "\n";
    cout << "a.id = " << a.p->p->p->id << "\n";

    return 0;
}

/*
--- 결과 ---
a.id = 1
b.id = 2
c.id = 3
a.id = 1

---
서로를 가리키는 구조체 변수.
구조체 안에 포함된 포인터 변수가 구조체 자신을 가리킨다.

코드를 쉽게 이해하려면 그림을 그려본다.
자료구조를 우선 파악해야한다.

순환하는 모습으로 서로를 가리키고 있다.

이것이 링크드 리스트이다.
대표적인 자료구조다.
동적으로 구성원을 넣고 뺄 수 있는 장점이 있다.
배열 대신 많이 사용한다.
*/

댓글