Home C EXAMPLES: STRUCT
Post
Cancel

C EXAMPLES: STRUCT

필자의 대학 코스 Advanced Programming에서 사용된 예시를 발췌, 정리했습니다. FINAL 시험에서 좋은 성과를 바라며…

POINT

Point라는 구조체는 x좌표와 y좌표로 정의될 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
#include <cmath> // for sqrt

struct point
{
    double x;
    double y;
};

double d(point const * p1, point const * p2) // 1
{
    double x_diff = p1->x - p2->x; // 1
    double y_diff = p1->y - p2->y; // 1
    return sqrt(x_diff*x_diff + y_diff*y_diff);
}

int main()
{
    using namespace std;
    point p; // 2
    cout << "x: " << flush; // 3
    cin >> p.x; // 2
    cout << "y: " << flush; // 3
    cin >> p.y; // 2
    point origin = {0, 0};
    cout << "Distance: " << d(&p, &origin) << endl; // 1
    return 0;
}

몇 가지 의미있는 내용이 있습니다.

  1. 함수 인자로 구조체의 주솟값을 받았다면, 구조체의 변수에 접근하기 위해서는 p1->x 형식으로, 즉 [구조체 포인터]->[구조체의 변수] 형식으로 접근해야 합니다.
  2. 그저 구조체를 받았다면, 구조체의 변수에 접근하기 위해 p.x 형식으로, 즉 [구조체].[구조체의 변수] 형식으로 접근해야 합니다.
  3. 제시문을 출력하고 사용자의 입력을 기다릴 때에는 << flush를 해주는 것이 관습입니다.

하지만 위 코드는 밑에처럼 쓸 수도 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
#include <cmath> // for sqrt

struct point
{
    double x;
    double y;
};

double d(point const & p1, point const & p2) // 1
{
    double x_diff = p1.x - p2.x; // 1
    double y_diff = p1.y - p2.y; // 1
    return sqrt(x_diff*x_diff + y_diff*y_diff);
}

int main()
{
    using namespace std;
    point p;
    cout << "x: " << flush;
    cin >> p.x;
    cout << "y: " << flush;
    cin >> p.y;
    point origin = {0, 0};
    cout << "Distance: " << d(p, origin) << endl; // 1
    return 0;
}

이번에는 함수의 인자를 구조체 자체로 주고 있습니다. 이 상황만 보면 누군가가 이렇게 비판하겠죠. “구조체를 인자로 전달하는 것은 구조체 전체를 복사하여 다시 연산하기 때문에 비효율적이야!” 하지만 위로 올라가서 함수의 인자 포맷을 보게 된다면, &가 붙어 있는 것을 알 수 있습니다. 이는 *reference**를 활용한 값의 참조이며, 이 함수는 레퍼런스로 받은 인자들에 붙은 .은 모두 ->로 바꾸어서 처리하게 됩니다. 즉 1에서 바로 인자들에게 접근할 수 있는 것이지요. 잘 알아두시길 바랍니다.

후자의 방법이 메모리의 관점에서는 더 효율적입니다.

Pointer vs Reference

레퍼런스는 포인터와 중요한 차이점을 가집니다.

  1. 꼭 초기화를 시켜주지 않아도 되는 포인터와 달리, 레퍼런스는 반드시 처음에 누구의 별명이 될 것인지 지정해야 합니다.
  2. 가리키는 값이 바뀔 수 있는 포인터와 달리, 레퍼런스가 한 번 별명이 되면 절대로 다른 이의 별명이 될 수 없다.
  3. 항상 메모리상 공간을 차지하는 포인터와 달리, 레퍼런스는 메모리 상에 존재하지 않을 수도 있다.

조금 사소한 것들로는…

  1. 배열과 사용할 수 없다.
  2. 포인터 연산을 할 수 없다.
  3. null-reference는 불가능, null-pointer는 가능
  4. reference는 항상 무언가를 함의하도록 초기화되고, 그 값이 바뀌지 않는다.

여기 예시가 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>

int main()
{
    using namespace std;
    int x[] = {3, 4, 5};
    int &a = x[1];
    a++;
    cout << x[1] << endl;
    int &b = a;
    cout << b << endl;
    return 0;
}

출력 결과

1
2
5
5

왜 이런 결과가 나왔을까요? int &a = x[1]; 이 줄에서, ax[1]의 별명이 되었습니다. 즉 밑에 있는 a++x[1]++과 동치인 것이죠. 따라서 5가 출력됩니다.

또한, int &b = a;에서 ba의 별명이 되었으므로 b 역시 x[1]의 별명이 됩니다. 따라서 5가 출력됩니다.

ENUM

ENUM은 기존 정수형 변수를 조건을 벗어난 값으로 지정되지 않게 하고, 가독성을 높히기 위해 사용됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>

using namespace std;

enum day_of_week { SUN, MON, TUE, WED, THU, FRI, SAT };

string day_of_week_name(day_of_week d)
{
    switch (d)
    {
        case SUN: return "SUN";
        case MON: return "MON";
        case TUE: return "TUE";
        case WED: return "WED";
        case THU: return "THU";
        case FRI: return "FRI";
        case SAT: return "SAT";
    }
}

즉, enumswitch문과 각별한 사이이다.

enum은 내재적으로 int형 변수로 타입이 전환되곤 한다. 또한 global range에서 정의되기 때문에, 우리는 다른 SUN이란 변수를 만들 수 없다. 이를 막기 위해서, 혼동을 줄이기 위해서 enum class로 정의할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>

using namespace std;

enum class day_of_week { SUN, MON, TUE, WED, THU, FRI, SAT };

string day_of_week_name(day_of_week d)
{
    switch (d)
    {
        case day_of_week::SUN: return "SUN";
        case day_of_week::MON: return "MON";
        case day_of_week::TUE: return "TUE";
        case day_of_week::WED: return "WED";
        case day_of_week::THU: return "THU";
        case day_of_week::FRI: return "FRI";
        case day_of_week::SAT: return "SAT";
    }
}

이제 헷갈리지 않는다!

ENUM으로 날짜 계산해주는 프로그램 만들기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <iostream>

enum class month {jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec};

int days_of_month(month m)
{
    case month::jan:
    case month::mar:
    case month::may:
    case month::jul:
    case month::aug:
    case month::oct:
    case month::dec:
        return 31;
    case month::apr:
    case month::jun:
    case month::sep:
    case month::nov:
        return 30;
    case month::feb:
        return 28
}

struct day_of_month
{
    month m;
    int day;
}

int days_between (day_of_month const & d1, day_of_month const &d2)
{
    int total_days;
    for (month i = d1.m; i < d2.m; i = (month)((int)i + 1))
        total_days += days_of_month(i);
    return total_days + d2.day - d1.day;
}

int main()
{
    using namespace std;
    day_of_month a = {month::april, 30}, {month::may, 3};
    cout << days_between(a, b) << endl;
}
This post is licensed under CC BY 4.0 by the author.
Trending Tags