오늘은 배열(Array)에 대해 글을 쓰겠습니다.
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
|
#include <stdio.h>
// 치환
#define MAX_LEN 3
void main()
{
int arr[5] = { 1, 2, 3,/*, 6*/};
int arrInit[50] = { 0 };
// 정적배열(Static Array) - Compile-Time
// 동적배열(Dynamic Array) - Run-Time
// 배열의 요소(Elements)
// [0]: 배열의 인덱스(Index)
arr[0] = 10;
printf("arr[0]: %d\n\n", arr[0]);
// 반복문과 궁합 좋음
// i가 Index인 이유
// i가 0 부터 시작하는 이유
for (int i = 0; i < 5; ++i) {
printf("arr[%d]: %d\n", i, arr[i]);
}
printf("\n");
// 예외처리(Exception)
//int overLen = 100;
//arr[overLen] = 1000;
//printf("arr[100]: %d\n", arr[100]);
// define문을 활용한 배열의 크기 지정
// Temporary: temp, tmp
int tmpArr[MAX_LEN] = { 0 };
// 배열이 이름은 배열 첫 번째 요소의 주소와 같다.
printf("tmpArr: %p\n", tmpArr);
// 메모리 옵셋(Memory Offset)
for (int i = 0; i < MAX_LEN; ++i)
{
// 시작 주소에서 몇 번을 건너뛰는가
printf("tmpArr[%d]: %p\n", i, &tmpArr[i]);
}
printf("\n");
// 길이(Length), 크기(Size)
char sizeArr[5] = { 0 };
printf("sizeArr Size: %d byte\n",
(int)sizeof(sizeArr));
printf("sizeArr Length: %d\n",
sizeof(sizeArr) / sizeof(double));
// 확장성 및 유지보수 좋음
printf("sizeArr Length: %d\n",
sizeof(sizeArr) / sizeof(sizeArr[0]));
int lhs = 5;
int rhs = lhs;
// Source: sour, src
int copyArr[3] = { 1, 2, 3 };
// 정수형은 주소연산이 불가능
// Destination: dest, dst
//int destArr = copyArr;
//destArr[0] = 10;
// 배열 초기화 방식 잘못 됨
//int destArr[3] = copyArr;
//destArr[0] = 100;
//printf("destArr[0]: %d\n", destArr[0]);
// 배열의 값들을 복사하는 방법
int destArr[3] = { 0 };
for (int i = 0; i < 3; ++i)
destArr[i] = copyArr[i];
printf("\n");
// 포인터(Pointer): 주소연산 가능
int* pDest = copyArr;
printf("pDest[1]: %d\n", *pDest);
}
|
cs |
<define문>(전처리기: 매크로)
배열을 다루기 전 define문이 등장하였기 때문에 먼저 다루겠습니다. define문은 전처리기의 매크로 역할을 하며 사전적 의미는 '정의하다'입니다. 그 말 그대로 위의 코드 #define MAX_LEN 3 은 MAX_LEN을 3으로 정의한다 -> 단어를 대체한다와 같은 뜻으로 사용됩니다. 예시로 아래의 코드 둘은 같은 뜻의 함수로 해석됩니다.
int arr[Max_Len] = int arr[3]
이런 귀찮은걸 왜 하지? 싶지만, 매크로를 사용하면 상수 값 대신 의미 있는 이름을 사용할 수 있어 코드 가독성에서 좋은 효과를 주며 다음에 다룰 배열문에서 양수의 상수를 제외한 음수, 소수, 변수 등을 사용할 없기 때문입니다.
매크로는 디버깅(Debug) 시 매크로 이름인 MAX_LEN이 아닌 그 값 3으로 읽히게 됩니다. 매크로는 전처리 과정에서 코드에 직접 삽입되기 때문에 매크로의 이름은 보이지 않고 그 값만 확인할 수 있습니다.
<배열문>(Array)
배열문은 한 종류의 여러 변수가 필요한 경우와 단순 반복 작업을 간단하게 처리하고 싶을 때 사용합니다. 100개의 변수를 다뤄야하는 작업이 필요한데 그렇게 되면 변수 선언만 100개를 해야합니다. 이럴 때 배열문을 사용합니다.
void main()
{
int arr[5] = { 1, 2, 3,/*, 6*/}; //arr 이름의 크기가 5인 배열을 선언, 각각 1, 2, 3, 0, 0로 초기화
int arrInit[50] = { 0 }; //arrInit 이름의 크기가 50인 배열을 선언, 50개 모두 0으로 초기화
arr[0] = 10; //arr의 첫 번째 요소의 값을 10으로 수정 대입
printf("arr[0]: %d\n\n", arr[0]); //"arr[0]: 'arr[0]의 값'" 출력 후, \n\n 다다음 줄로 이동하라
for (int i = 0; i < 5; ++i) //전 글에서 다룬 반복문, i의 값 0, i의 값이 5 미만일 시 비교문, 1씩 증감문
{
printf("arr[%d]: %d\n", i, arr[i]); //"arr[arr[i]]: arr[i]값" 출력 후, 다음 줄로 이동하라
}
printf("\n");
}
배열문의 문법은 자료형 변수명[배열 크기] = {각 배열의 초기값};으로 구성됩니다. 우측 주석 설명에 이어 특징에 대해 알아보겠습니다.
- 배열문에서 [ ] 대괄호 안에 들어가는 숫자는 음수, 소수, 변수를 제외한 양수 정수값만을 취급하며 배열 크기를 나타냅니다. int arr[-4], int arr[1.5] 안됩니다.
- 변수를 배열 크기에 사용하고 싶다면 define문을 활용해 매크로를 이용하여 변수값과 상수값을 치환하면 가능합니다. 위의 코드대로면 int arr[MAX_LEN] 형태가 가능하다는 것입니다.
- { } 안의 값들은 그 배열 내 요소들에 할당되는 초기화값이며 크기를 넘어서는 수의 초기화는 하면 안됩니다. int arr[3] = { 1, 2, 3, 4, 5}; 안됩니다. 4와 5를 넣어 3의 크기를 침범했기 때문입니다.
- 배열은 1이 아닌 0부터 시작입니다. int arr[3]일 경우 arr[0], arr[1], arr[2]의 배열이 있는 셈입니다. 위의 코드의 반복문에서 사용하는 i는 index로 배열문의 요소값에 해당합니다. 우리가 프로그래밍 예제에서 자주 사용하는 i변수의 초기값을 0으로 자주 사용하는 이유가 이것입니다. 0부터 시작하니까.
- { } 중괄호 안에 아무것도 표시하지 않으면 자동 0 값으로 초기화됩니다. int arr[4] = { }; 또는 int arr[4]; 둘 다 가능하다는 뜻입니다.
- 배열문은 for문(반복)과 궁합이 좋아 자주 사용됩니다.
- 배열문에서 변수명은 첫번째 요소와 주소가 같습니다. 이건 밑에서 자세히 설명하겠습니다.
arr[0] = 10
arr[0] = 10
arr[1] = 2
arr[2] = 3
arr[3] = 0
arr[4] = 0 의 출력값이 나옵니다.
<define문을 활용한 배열문> (temporary: 임시)
#define MAX_LEN 3
void main()
{
//Temporary: temp, tmp 임시라는 뜻
int tmpArr[MAX_LEN] = { 0 }; //tmpArr이름의 배열로 매크로의 3 크기를 가지고 있음. 모두 0으로 초기화
printf("tmpArr: %p\n", tmpArr); //배열이 이름은 배열 첫 번째 요소의 주소와 같다.
for (int i = 0; i < MAX_LEN; ++i) //메모리 오프셋(Memory Offset)
{
printf("tmpArr[%d]: %p\n", i, &tmpArr[i]); //시작 주소에서 몇 번을 건너뛰는가
}
}
- Temporary는 임시라는 뜻으로 temp, tmp로 사용되며 임시 변수, 임시 값으로 사용되며 보통 두 변수의 값을 서로 바꿀 때 사용하는 변수입니다. 위 코드에서는 변수명 정도에 활용되었지만 temp, tmp는 포인터, Swap 함수를 사용할 때 자주 사용됩니다. 이에 대한 건 다음에 제대로 다루겠습니다. 이번 글에선 이런 이름의 코드도 있구나~ 정도로 알고 계시면 됩니다.
- 메모리 오프셋(Memory Offset)은 메모리 주소에서 특정 위치를 나타내는 개념이고 오프셋은 메모리에서 특정 데이터 요소까지의 상대적인 위치를 지정합니다. '상대적인' 중요합니다. 출력할 때는 %p를 사용합니다. 위의 코드는 일반적인 요소들의 주소를 출력합니다.
- tmpArr[MAX_LEN]에서 tmpArr은 배열의 첫 번째 요소와 주소가 같습니다. 임의로 tmpArr[0]의 주소값이라는 뜻입니다.
tmpArr: 첫 번째 요소의 주소
tmpArr[0]: 첫 번째 요소의 주소
tmpArr[1]: 두 번째 요소의 주소
tmpArr[2]: 세 번째 요소의 주소 의 출력값이 나옵니다.
<길이와 크기>(Length)(Size)
void main()
{
char sizeArr[5] = { 0 }; //char, 5크기를 가진 sizeArr 배열, 모두 0초기화
printf("sizeArr Size: %d byte\n", (int)sizeof(sizeArr)); //"sizeArr Size: SizeArr의 크기를 byte" 출력
printf("sizeArr Length: %d\n", sizeof(sizeArr) / sizeof(double)); //"sizeArr Length: sizeArr byte / double byte" 출력
printf("sizeArr Length: %d\n", sizeof(sizeArr) / sizeof(sizeArr[0])); //"sizeArr Length: sizeArr byte / sizeArr[0]크기 byte" 출력
}
- sizeof는 변수, 데이터 타입, 배열 등의 크기를 바이트 단위로 계산하여 반환하는 기능의 연산자이고 메모리 관리, 데이터 구조를 처리할 때 중요하게 다룹니다.
- (int)sizeof(sizeArr)은 char자료형인 sizeArr을 int로 자료형 변환 후 그 크기를 byte 단위로 표현하는 코드입니다. 즉, sizeArr은 [5]인 5의 크기를 가지고 있어 5byte입니다.
- sizeof(sizeArr) / sizeof(double)에서 5를 sizeof를 통해 double의 기본 바이트 값인 8 byte로 나누는 것입니다. 고로 5/8이 되어 %d로 출력하면 0의 값이 출력됩니다.
- sizeof(sizeArr) / sizeof(sizeArr[0])에서 5를 sizeof를 통해 sizeArr[0]의 타입인 char의 바이트, 1byte로 나누는 것입니다. 그래서 5/1이 되어 출력하면 5의 값이 출력됩니다.
- Length는 C언어의 표준 함수나 연산자가 아니지만, 길이나 크기를 설명하는 용어 중 하나로 사용합니다. 문자열은 strlen, 배열은 sizeof를 이용하여 측정 및 계산합니다.
sizeArr Size: 5
sizeArr Length: 0
sizeArr Length: 5 의 출력값이 나옵니다.
<배열 초기화> (destination, source용어)
배열 초기화할 때 사용하는 Destination과 Source에 대해 알아보겠습니다. 데이터를 복사하거나 이동할 때도 사용되지만, 이번엔 배열에서만 다루겠습니다. Destination? 90년대생 이상이신 분들과 특정 게임에서 특정 종족을 플레이해보신 분들은 꽤나 익숙한 단어일겁니다.
그렇습니다. 스타크래프트의 시즈탱크 유닛의 대사 중 하나입니다. "Destination?" 목적지는? 를 뜻합니다. Source는 원본 데이터를 뜻합니다. Destination은 배열을 다른 배열로 초기화할 때 사용하며 배열의 각 요소를 하나씩 복사하는 방식으로 초기화합니다. 스타크래프트로 예시를 들면 언덕 위에 3탱크 진영(Source)을 언덕 밑에 위치(Destination)에 언덕 위 3탱크를 가져와 똑같이 배치시키겠다는 뜻입니다.
int main()
{
int source[] = {1, 2, 3};
int destination[3]; // 목적지 배열
// 배열 복사
for (int i = 0; i < 3; i++)
{
destination[i] = source[i];
printf("destination[%d] = %d\n", i, destination[i]);
}
return 0;
}
위 코드를 예제로 사용하겠습니다. source 역할의 배열 source에 각각 1, 2, 3의 초기값을 보유한 3 크기의 배열이 존재합니다. 그리고 목적지(destination) 역할의 3 크기의 destination 배열이 있습니다. for문을 활용하여 destination[i]의 각각 요소가 source[i]가 같도록 만들었고 "destination[i] = 초기값"을 출력하라고 합니다.
destination[0] = 1
destination[1] = 2
destination[2] = 3 출력값이 나오게 됩니다.
이제 특징에 대해 나열하겠습니다. 위의 코드를 가져옵니다.
- 정수형은 주소연산이 불가합니다. 이건 int destArr = copyArr; 에서 destArr은 배열이 아니고 정수형 변수입니다. 만약 올바른 배열로 바꾸고싶다면 int destArr[3]으로 적어야합니다.
- C언어에서는 배열을 선언할 때 다른 배열로부터 직접 초기화할 수 없습니다. 초기화는 { } 중괄호를 통해 직접 나열하는 방식으로만 가능합니다. 따라서 int destArr[3] = copyArr;는 destArr 배열에 copyArr 배열을 초기화하려는 뜻이지만, 잘못된 배열 초기화로 사용해서는 안됩니다.
<포인터>(Pointer)
마지막은 포인터에 대해 다루겠습니다.
- p(oint)Dest가 변수명이고 포인터는 *가 특징입니다. copyArr은 정수형 배열이며 변수명인 copyArr은 배열의 첫 번째 요소의 주소를 가리킵니다. 이걸 pDest에 저장한다는 뜻입니다.
- 출력값은 pDest[1]: 1이 나옵니다. 그 이유는 printf 줄에서 *pDest의 주소는 copyArr의 첫 요소가 저장되었었습니다. 때문에 copyArr의 첫 요소, 1이 나오게 됩니다. 만약 *을 빼고 pDest[1]이라고 썼다면 두 번째 요소인 2가 출력값이 됩니다.
끝
'프로그래밍 C언어' 카테고리의 다른 글
함수 (Function) (0) | 2024.08.14 |
---|---|
포인터 (pointer) (0) | 2024.08.13 |
반복문(Loop) (1) | 2024.08.11 |
분기문(Branch)과 조건문(Condition) (0) | 2024.08.09 |
연산자(Operators)와 형변환(Type Casting) (0) | 2024.08.09 |