이 게시글은 원본 링크
https://lsoovmee-rhino.tistory.com/27?category=847612
에서 공부를 하며 주석을 달고 오류가 있는 부분을 수정한 게시글임을 미리 알립니다.
C 프로그래밍 기초에서 배열과 포인터에 대해 공부하면서 배열에 대한 이해는 충분히 됐다고 생각합니다.
C 언어에서 배열과 포인터는 거의 동일한 역할을 합니다.
단지, 배열은 상수 포인터는 변수라는 차이점을 가지고 있을 뿐입니다.
///이는 int array = (int)malloc(sizeof(n)); 과 같이 동적할당을 통해 받은 메모리 주소를 배열로 활용할 때와
int array[];로 배열을 선언하여 활용할 때의 가장 큰 차이점이기도 하다.
상수는 등호의 왼쪽에 설 수 없습니다.
위의 예제를 보시면 이해가 가시죠?
3 = 2; 라고 선언할 수 없으니까요... (a는 &a[0]과 같고 &a[0]은 주소 값입니다.)
배열은 간단히 정의만 하고 넘어가겠습니다.
배열은 동일한 타입의 데이터들을 묶는 구조입니다.
메모리의 연속된 위치에 차례대로 데이터를 저장합니다.
주소 당 1byte의 메모리가 할당됩니다.
즉, int a[3]는 각 원소 당 4byte의 메모리가 할당되고, 총 12byte의 메모리가 할당됩니다.
주소 0x00 ~ 0x03 // 0x04 ~ 0x07 // 0x08 ~ 0x0B
메모리 4byte // 4byte // 4byte
배열과 포인터는 다음과 같은 특징을 갖고 있습니다.
구조체는 하나 이상의 자료형을 기반으로 '사용자 정의 자료형'을 만들 수 있는 문법 요소입니다.
배열과 다른 점은 다양한 자료형을 포함하고 있다는 것입니다.
선언하는 방법이 다양한데 3가지로 나눠서 보겠습니다.
1)
struct A {
int a;
char b;
double c;
};
이렇게 구조체와 구조체 이름을 선언할 수 있습니다.
사용하기 위해선
('struct 구조체 이름 구조체 변수')
// A란 struct 이름이 있고, 거기에 구조체 변수를 정해준다. (B or C)
struct A B;
struct A C;
///구조체 변수란? 위에서 struct A라는 새로운 사용자 정의 자료형을 선언하였고 그 자료형의 변수 B,C를 구조체 변수라 한다.
B.a = 3;
B.b = 'c';
B.c = 0.2;
C.a = 1;
C.b = 'e';
C.c = 0.7;
2)
struct A {
int a;
char b;
double c;
} B;
B.a = 3;
B.b = 'c';
B.c = 0.2;
이렇게 구조체 이름 선언과 동시에 변수를 정해줄 수도 있습니다.
이것도 다시 struct A C; ('struct 구조체 이름 구조체 변수')와 같은 구문을 추가 함으로써 아래와 같은 코드를 작성할 수 있습니다.
C.a = 1;
C.b = 'e';
C.c = 0.7;
3)
typedef struct A {
int a;
char b;
double c;
} sA;
typedef @ &는 @를 앞으로 &라고 부를거야 라고 선언해주는 역할을 합니다.
///위에서는 struct A 를 SA로 부를거야 라고 하는 것
예를들어, typedef unsigned short int UINT16과 같이 unsigned short int를 typedef를 통해 앞으로 UINT16으로 쓸 수 있게 만듭니다.
위와 같이 구조체 이름을 선언한 경우 다음과 같이 구조체의 변수를 선언할 수 있습니다.
('struct 구조체 이름 구조체 변수' == '구조체 별명 구조체 변수') => 두가지 방법 모두 가능
sA B;
sA C;
B.a = 3;
B.b = 'c';
B.c = 0.2;
C.a = 1;
C.b = 'e';
C.c = 0.7;
가끔 이럴 때도 있습니다.
typedef struct {
int a;
char b;
double c;
} sA;
사용은 방금 전과 같습니다.
마지막 선언 방식은 익명 구조체라고 합니다.
/// 이 구조체의 이름은 없고 대신 선언은 struct (구조체 이름) 대신 sA로 '정의'한다.(type 'definition'라고 허는 것.
원래 '대신' 정의는 아닌데 여기서는 struct (구조체 이름) 을 구조체 이름이 없어서 못 하니......
물론 이름이 없기 때문에
'struct 구조체 이름 구조체 변수'와 같은 구문을 사용할 수 없습니다.
2-1. 구조체 포인터
다른 변수들과 마찬가지로 구조체도 포인터를 활용할 수 있습니다.
struct A {
int a;
char b[3];
};
위와 같이 선언되었을 때 구조체 이름을 다음과 같이 선언합니다.
struct A AA;
struct A *pA = &AA;
///복습! 메모라의 주소값을 저장하는 주소를 포인터라 한다.
정수인 변수가 저장된 메모리 주소를 가리키는 포인터를
int p와 같이 선언하듯이
구조체도
(사용자 정의 자료형) pA와 같이 선언하여야 한다.
구조체 멤버는 다음과 같이 접근할 수 있습니다.
AA.a = 10;
AA.b[0] = "123";
//b[0] = '1'; b[1] = '2'; 이런 식으로 해주거나 strcpy를 써야한다.
AA.b = "123";도 안 된다.
배열의 초기화는 선언과 동시에 해줘야 한다.
(pA).a = 10; // AA.a = 10과 동일합니다. // 괄호가 중요합니다!!! 의 우선순위는 엄청 낮아요.
pA -> a = 10; // (*pA).a == AA.a = 10과 동일합니다.
///구조체 포인터 -> a = 10; 즉 그 구조체의 메모리 주소에 가서 그 구조체의 a변수의 값을 10으로 설정!
구조체 포인터를 알면 화살표를 통해 구조체 변수의 값을 바꿀 수 있다.
일반화!
구조체 포인터 -> 구조체 멤버 변수 = ~;
를 하면 해당 구조체의 멤버 변수의 값을 바꿀 수 있다.
(call by reference)
2-2. 구조체 배열
마찬가지로 구조체로 배열을 만들 수 있습니다.
///얘도 자료형이니까
struct A AA{3] = {{1, "123"}, {2, "234"}, {3, "345"}};
///int AA[3]과 큰 차이가 없다!
위와 같이 선언을 하면 AA[0], AA[1], AA[2] 3개의 구조체 변수가 생성된 것과 같습니다.
///int array[3]도 array[0],array[1],array[2]라는 변수 세개가 만들어 진 것과 같으니까!
초기화는 바로 괄호를 통해 해주었습니다.
단일 구조체 변수도 괄호로 초기화 할 수 있습니다.
(struct A BB = {4, "456"};)
2-3 구조체 포인터와 배열
구조체 배열을 구조체 포인터와 void 포인터를 통해 가리키는 것을 해보겠습니다.
일단 위와 동일한 struct A가 선언됐다고 가정하고 진행하겠습니다.
struct A AA{3] = {{1, "123"}, {2, "234"}, {3, "345"}};
struct A* p1;
(A는 int, char [3]로 구성되어 있으므로 (4+13)3 = 21의 메모리가 할당된다.)
(는 뻥입니다...하하하 8*3 = 24의 메모리가 할당됩니다.)
(글 말미에 설명드리겠습니다.)
void* p2 = AA; // p2에 AA 배열의 첫번째 요소 주소를 넣습니다.
이렇게 선언하고 난 다음에 다음과 같이 멤버 변수에 접근할 수 있습니다.
p1[0].a = 2; //== AA[0].a = 2;
p1[0].b[2] = '2'; //== AA[0].b[2] = '2';
///이유 p1은 AA의 첫칸 주소와 같으므로! 거기다 struct A* 이므로 배열의 한칸의 크기가 sizeof(struct A)임도 컴파일러에서 알 수 있다.
또는, 위와 같은 기능을 하기 위해
p1[0].a는 (*(p1+0)).a와 (p1+0) -> a로 바꿔 사용할 수 있습니다.
///각각 참조와 구조체 포인터
(원래 p.a를 (p).a로 쓸 수 있는데 (p).를 쓰기 귀찮아서 간접참조 연산자인 p->로 대체했다고 생각하시면 편합니다.)
(즉, p[].a == (*p).a == p->a가 모두 동일한 역할을 합니다.)
void 포인터 p2는 다음과 같이 멤버 변수에 접근할 수 있습니다.
(((struct A)p2+1)).a == ((struct A*)p2+1)->a
///p2[1]의 구조체 멤버 변수 a
(((struct A)p2+1)).b[1] == ((struct A*)p2+1)->b[1]
///p2[1]의 구조체 멤버 배열 b의 [1]
///형변환을 해서 이게 struct A의 포인터임을 알려줘야 컴파일러에서
배열의 1칸이 sizeof(struct A)임을 알지
(중요 !) array + 1 은 array[1]의 주소
p1과 거의 동일한데 강제 형변환 (struct A*)을 p2 앞에 붙여주는 것만 다릅니다.
아래 코드를 통해 결과를 확인하실 수 있습니다.
struct A{
int a;
char b[3];
};
struct A AA[3] = {{1, "123"}, {2, "234"}, {3, "345"}};
struct A* p1 = AA;
void* p2 = AA;
int main(int argc, const char argv[]) {
///const char argv[]의 의미는 다음과 같다.
const는 상수 char* char포인터 argv는 배열
즉, char포인터 배열 argv를 만들고 이를 바꾸지 않겠다는 의미
그런데.. 왜.. 있는거지?
printf("%d\n", p1[1].a); //2
printf("%d\n", p1[1].b[1]); //3
printf("%d\n", (p1+1)->a); //2
printf("%d\n", (*(p1+1)).a); //2
printf("%s\n", (p1+1)->b); // 234
printf("%s\n", (*(p1+1)).b); // 234
printf("%c\n", (p1+1)->b[1]); // 3
printf("%c\n", (*(p1+1)).b[1]); // 3
/// p1 + 1을 참조하면 구조체가 나오고 그 구조체의 b[1]을 프린트
printf("%c\n", *((p1+1)->b+1)); // 3
// printf("%c\n", ((p1+1).(b+1)); // 안 됨 ㅜㅜ
///안 되는 이유 : 참조를 해야하는데 (p1 + 1).(b + 1))은 메모리 주소가 아니기 떄문
그럼 ((p1 + 1).(b + 1))은 ?
안됨 p1 + 1은 AA[1]의 메모리 주소이지 AA[1]은 아니다.
(((p1 + 1)).b+1)
b + 1 을 쓰려면 이렇게 써야한다. p1 + 1로 구조체를 참조하고 거기 있는 b배열의 주소를 불러와서 거기서 + 1
*(p1 + 1).(b + 1)은 안 된다. 왜냐면 b+1 은 변수 이름이 아니기 떄문
printf("%d\n", ((struct A*)p2)[1].a); // 2
printf("%c\n", ((struct A*)p2)[1].b[1]); //3
printf("%d\n", ((struct A*)p2+1)->a); //2
/// 화살표는 보면서 계속 익혀두자.
struct A포인터 p2 + 1에서 변수 a를 바로 찾아가는 것.
printf("%d\n", (*((struct A*)p2+1)).a); //2
printf("%s\n", ((struct A*)p2+1)->b); // 234
printf("%s\n", (*((struct A*)p2+1)).b); // 234
printf("%c\n", ((struct A*)p2+1)->b[1]); // 3
printf("%c\n", (*((struct A*)p2+1)).b[1]); // 3
printf("%c\n", *(((struct A*)p2+1)->b+1)); // 3
return 0;
}
구조체의 속성 중 하나가 스스로를 가리키는 구조체 입니다.
struct A{
char data;
struct A* A;
};
위와 같이 선언해 줄 수 있습니다.
연결 리스트의 구현에 많이 사용됩니다.
지금은 이름만 기억해주세요!!
큐, 스택 이후에 연결 리스트 게시물에서 주구장창 할 거에요...
**추가
구조체의 메모리 사이즈에 관하여!
구조체의 각 멤버 중에 가장 큰 멤버의 크기에 영향을 받는다!
struct A{
char a;
int b;
};
///8바이트
struct A{
char a;
char b;
int c;
};
여유가 생긴 a뒤 3byte 중에 b가 들어갔습니다.
///8바이트
struct A{
char a;
int c;
char b;
};
순서에도 영향을 받습니다. 크기가 작은 자료형을 앞에 몰아넣는 것이 유리할 것 같습니다.
///12바이트(a의 여유공간에 c가 4바이트이므로 못 들어감)
struct A{
char a;
double b;
};
2번 규칙이 적용되어 double 형의 크기를 따라갑니다.
///16바이트
struct A{
char a;
int c;
double b;
};
///16바이트
이 때 char 뒤에 여유공간을 두고 int가 들어감
따라서 char int char double로 선언하면 구조체의 사이즈는 24바이트가 됨
메모리 차지하는 원리
2의 n승만큼 칸을 나누어서 판단
ex.
가장 큰 자료형이 double일 때
char 다음 short면 바로 옆에 안 들어가고 한칸 띄워서
char 다음 char면 바로 다음 칸에
char 다음 int면 세칸 띄우고 들어감
참조 : https://blog.naver.com/sharonichoya/220495444611
다음 게시물에서는 큐와 스택을 들어가기 전 순서 리스트와 배열과 구조체를 통한 다항식의 표현에 대해 알아보겠습니다.