C 프로그래밍 기초에서 배열과 포인터에 대해 공부하면서 배열에 대한 이해는 충분히 됐다고 생각합니다.
C 언어에서 배열과 포인터는 거의 동일한 역할을 합니다.
단지, 배열은 상수 포인터는 변수라는 차이점을 가지고 있을 뿐입니다.
상수는 등호의 왼쪽에 설 수 없습니다.
위의 예제를 보시면 이해가 가시죠?
3 = 2; 라고 선언할 수 없으니까요... (a는 &a[0]과 같고 &a[0]은 주소 값입니다.)
배열은 간단히 정의만 하고 넘어가겠습니다.
1. 배열
배열은 동일한 타입의 데이터들을 묶는 구조입니다.
메모리의 연속된 위치에 차례대로 데이터를 저장합니다.
주소 당 1byte의 메모리가 할당됩니다.
즉, int a[3]는 각 원소 당 4byte의 메모리가 할당되고, 총 12byte의 메모리가 할당됩니다.
주소 0x00 ~ 0x03 // 0x04 ~ 0x07 // 0x08 ~ 0x0B
메모리 4byte // 4byte // 4byte
배열과 포인터는 다음과 같은 특징을 갖고 있습니다.
2. 구조체
구조체는 하나 이상의 자료형을 기반으로 '사용자 정의 자료형'을 만들 수 있는 문법 요소입니다.
배열과 다른 점은 다양한 자료형을 포함하고 있다는 것입니다.
선언하는 방법이 다양한데 3가지로 나눠서 보겠습니다.
1)
struct A {
int a;
char b;
double c;
};
이렇게 구조체와 구조체 이름을 선언할 수 있습니다.
사용하기 위해선
('struct 구조체 이름 구조체 변수')
// A란 struct 이름이 있고, 거기에 구조체 변수를 정해준다. (B or C)
struct A B;
struct A 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 @ &는 @를 앞으로 &라고 부를거야 라고 선언해주는 역할을 합니다.
예를들어, 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 구조체 이름 구조체 변수'와 같은 구문을 사용할 수 없습니다.
2-1. 구조체 포인터
다른 변수들과 마찬가지로 구조체도 포인터를 활용할 수 있습니다.
struct A {
int a;
char b[3];
};
위와 같이 선언되었을 때 구조체 이름을 다음과 같이 선언합니다.
struct A AA;
struct AA *pA = &AA;
구조체 멤버는 다음과 같이 접근할 수 있습니다.
AA.a = 10;
AA.b[0] = "123";
(*pA).a = 10; // AA.a = 10과 동일합니다. // 괄호가 중요합니다!!! *의 우선순위는 엄청 낮아요.
pA -> a = 10; // (*pA).a == AA.a = 10과 동일합니다.
2-2. 구조체 배열
마찬가지로 구조체로 배열을 만들 수 있습니다.
struct A AA{3] = {{1, "123"}, {2, "234"}, {3, "345"}};
위와 같이 선언을 하면 AA[0], AA[1], AA[2] 3개의 구조체 이름이 생성된 것과 같습니다.
초기화는 바로 괄호를 통해 해주었습니다.
단일 구조체 변수도 괄호로 초기화 할 수 있습니다.
(struct A BB = {4, "456"};)
2-3 구조체 포인터와 배열
구조체 배열을 구조체 포인터와 void 포인터를 통해 가리키는 것을 해보겠습니다.
일단 위와 동일한 struct A가 선언됐다고 가정하고 진행하겠습니다.
struct A AA{3] = {{1, "123"}, {2, "234"}, {3, "345"}};
struct A *p1;
void *p2 = malloc(sizeof(struct A) * 3); // 빈 포인터에 구조체 A 3개 만큼의 메모리 할당
(A는 int, char [3]로 구성되어 있으므로 (4+1*3)*3 = 21의 메모리가 할당된다.)
(는 뻥입니다...하하하 8*3 = 24의 메모리가 할당됩니다.)
(글 말미에 설명드리겠습니다.)
p2 = AA; // p2에 AA 배열의 첫번째 요소 주소를 넣습니다.
이렇게 선언하고 난 다음에 다음과 같이 멤버 변수에 접근할 수 있습니다.
p1[0].a = 2; //== AA[0].a = 2;
p1[0].b[2] = '2'; //== AA[0].b[2] = '2';
또는, 위와 같은 기능을 하기 위해
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
(*((struct A*)p2+1)).b[1] == ((struct A*)p2+1)->b[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;
int main(int argc, const 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
printf("%c\n", *((p1+1)->b+1)); // 3
// printf("%c\n", *(*(p1+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
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;
}
3. 자기 참조 구조체
구조에의 속성 중 하나가 스스로를 가리키는 구조체 입니다.
struct A{
char data;
struct A *A;
};
위와 같이 선언해 줄 수 있습니다.
연결 리스트의 구현에 많이 사용됩니다.
지금은 이름만 기억해주세요!!
큐, 스택 이후에 연결 리스트 게시물에서 주구장창 할 거에요...
**추가
구조체의 메모리 사이즈에 관하여!
1. 각각의 멤버를 저장하기 위해서 기본 4 byte 단위로 메모리가 할당된다.
-> char 1개를 읽어올 때 1byte만 달랑 읽는게 아니라 1word (4byte) 단위로 읽어온다.
=> 이런 방식이 속도가 더 빠르다.
2. 구조체의 각 멤버 중에 가장 큰 멤버의 크기에 영향을 받는다.
struct A{
char a;
int b;
};
struct A{
char a;
char b;
int c;
};
struct A{
char a;
int c;
char b;
};
struct A{
char a;
double b;
};
struct A{
char a;
int c;
double b;
};
참조 : https://blog.naver.com/sharonichoya/220495444611
다음 게시물에서는 큐와 스택을 들어가기 전 순서 리스트와 배열과 구조체를 통한 다항식의 표현에 대해 알아보겠습니다.
'Programming > C' 카테고리의 다른 글
[자료구조 C 언어] C 프로그래밍 자료구조 - 3 (추가) : 희소 행렬 (1) | 2020.02.27 |
---|---|
[자료구조 C 언어] C 프로그래밍 자료구조 - 3 : 순서 리스트, 다항식의 표현 (0) | 2020.02.27 |
[자료구조 C 언어] C 프로그래밍 자료구조 - 1 : 자료구조와 알고리즘의 개념, 알고리즘 복잡도 (시간 복잡도) (0) | 2020.02.26 |
[자료구조 C 언어] C 프로그래밍 기초 - 7 : 이중 포인터와 2차원 배열의 관계 (4) | 2020.02.25 |
[자료구조 C 언어] C 프로그래밍 기초 - 6 : 2차원 배열, 포인터 배열, 배열 포인터 (2) | 2020.02.24 |