언어마다 다른 것 · 36
구조체
관련된 값을 한 덩어리로 묶고, 그 덩어리가 복사되는지 공유되는지 알아 둬요.
이 단원은 C# · C++ · C에서 배워요.
Python·JavaScript·TypeScript·Java는 클래스와 객체로 자료를 묶고(클래스 단원), 크레이콘은 사전()으로 묶어요.
셋 다 대입하면 같은 객체를 함께 쓰는 참조라서, 복사되는 구조체와는 달라요.
점의 x·y, 학생의 이름·점수처럼 늘 같이 다니는 값은 구조체(struct)로 묶어요.
아래 예제는 점을 복사한 뒤 복사본만 바꿔 보고, 원본을 움직여서 복사와 원본의 차이를 보여 줘요.
using System; struct Point // 값 타입: class로 바꾸면 q = p 가 같은 객체를 가리켜요{ public int X, Y; public void Move(int dx, int dy) { X += dx; Y += dy; } public int Len2() => X * X + Y * Y; public override string ToString() => $"({X}, {Y})";} class Program{ static void Main() { string[] t = Console.ReadLine().Split(); var p = new Point { X = int.Parse(t[0]), Y = int.Parse(t[1]) }; Console.WriteLine($"p = {p}"); Point q = p; // 데이터가 복사돼요 q.X = 10; Console.WriteLine($"q = {q}"); Console.WriteLine($"p = {p}"); p.Move(1, 1); Console.WriteLine($"p = {p}"); Console.WriteLine($"len2 = {p.Len2()}"); }}#include <iostream>#include <string> struct Point { int x = 0, y = 0; void move(int dx, int dy) { x += dx; y += dy; } // 멤버 함수 int len2() const { return x * x + y * y; } // 바꾸지 않으면 const}; void print(const std::string& name, const Point& p) { std::cout << name << " = (" << p.x << ", " << p.y << ")\n";} int main() { Point p; std::cin >> p.x >> p.y; print("p", p); Point q = p; // 멤버가 통째로 복사돼요 q.x = 10; print("q", q); print("p", p); p.move(1, 1); print("p", p); std::cout << "len2 = " << p.len2() << '\n';}#include <stdio.h> typedef struct { int x, y;} Point; void print_point(const char *name, Point p) { printf("%s = (%d, %d)\n", name, p.x, p.y);} void move(Point *p, int dx, int dy) { /* 원본을 바꾸려면 포인터로 */ p->x += dx; p->y += dy;} int len2(Point p) { /* 읽기만 하니 값으로 받아도 돼요 */ return p.x * p.x + p.y * p.y;} int main(void) { Point p; scanf("%d %d", &p.x, &p.y); print_point("p", p); Point q = p; /* 멤버가 통째로 복사돼요 */ q.x = 10; print_point("q", q); print_point("p", p); move(&p, 1, 1); print_point("p", p); printf("len2 = %d\n", len2(p)); return 0;}3 4
p = (3, 4) q = (10, 4) p = (3, 4) p = (4, 5) len2 = 41
핵심
- 묶음 하나가 새 타입이 돼요.
변수 하나로 넘기고, 돌려주고, 배열에 담을 수 있어요. - 안에 든 값은 멤버(필드)라고 하고,
p.x처럼 점으로 꺼내요. - 구조체를 대입하거나 함수에 넘기면 멤버가 통째로 복사돼요.
그래서q = p다음에q.x = 10을 해도p는(3, 4)그대로예요. - 원본을 바꾸려면 포인터나 참조로 넘겨요.
큰 구조체는 복사가 비싸서, 읽기만 할 때도const포인터·참조로 넘기곤 해요.
언어별로 쓰는 법
| C# | struct는 값 타입이라 Point q = p;가 데이터를 복사하고, class는 참조 타입이라 같은 객체를 가리켜요(예제를 class Point로 바꾸면 q.X = 10 뒤에 p도 (10, 4)) — int·double·DateTime도 구조체예요.좌표·색·날짜처럼 작은 값은 struct(바뀌지 않게 하려면 readonly struct나 record struct), 그 밖에는 class.원본을 넘길 땐 ref |
|---|---|
| C++ | typedef 없이 바로 Point.안에 멤버 함수를 둘 수 있고( void move(int dx, int dy)), 바꾸지 않는 함수엔 int len2() const — struct와 class는 기본 공개 범위(struct는 public, class는 private)만 달라요.원본은 Point&나 const Point&로 넘겨요 |
| C | struct Point { int x, y; };는 쓸 때마다 struct Point p;라고 적어야 해서 보통 typedef struct { int x, y; } Point; — 원본을 바꿀 땐 move(&p, 1, 1), 포인터로 멤버에 닿을 땐 (*p).x 대신 p->x, 읽기만 할 땐 const Point * |
자주 하는 실수
목록 안 구조체의 멤버를 바꿨어요C#
var list = new List<Point> { new Point() };
list[0].X = 5;→ 컴파일 오류 CS1612 — 인덱서가 복사본을 돌려줘요. 배열의 arr[0].X = 5는 되지만, foreach로 꺼낸 구조체의 멤버도 못 바꿔요(CS1654)
복사본을 꺼내 바꾼 뒤 다시 넣어요: var t = list[0]; t.X = 5; list[0] = t;
구조체를 ==로 비교했어요
if (p == q) { … }→ C는 컴파일 오류예요. C++는 operator==가 없으면, C# struct는 ==를 정의하지 않으면(CS0019) 컴파일 오류예요
C는 멤버를 하나씩 비교해요(memcmp는 멤버 사이의 빈 패딩 바이트 때문에 틀릴 수 있어요).
C++20은 bool operator==(const Point&) const = default;, C#은 p.Equals(q)(멤버 값 비교)나 ==까지 만들어 주는 record struct.
값으로 받아서 복사본만 움직였어요C · C++
void move(Point p, int dx, int dy) {
p.x += dx;
p.y += dy;
}→ 부른 쪽 p는 그대로예요. 오류도 경고도 없어요
C는 Point *, C++는 Point&로 받아요.
구조체를 넘기거나 대입할 때마다 복사인지 같은 것인지 따져 보세요.
C·C++·C#
struct는 복사라서 원본을 바꾸려면 포인터(Point *)·참조(Point&)·C# ref를 써야 하고, C# class는 같은 객체를 함께 써요.