배열
여러 데이터를 묶어서 하나의 단위로 처리하는 데이터 타입을 "구조적 데이터 타입" 이라 한다. 구조적 데이터 타입은 배열, 구조체, 공용체 등으로 구분할 수 있는데,
본 포스팅에서는 우선 배열을 먼저 알아본다.
배열을 왜 배워야 하는지 알아보기 위해 우선 다음의 프로그램을 살펴보자.
< 5개의 성적을 입력받아 평균을 구하는 프로그램 >
우선 위의 프로그램에는 몇 가지 문제가 있다.
1. 입력 받아야 하는 성적의 개수의 이상으로 변수가 필요하다. (입력 성적이 100개면 100개 이상의 변수가 필요)
2. 숫자를 제외하고 동일한 부분이 계속 반복된다. (효율성 떨어짐)
즉, 위와 같은 경우에 배열을 사용하면 편한데, 배열을 선언하는 형식은 다음과 같다.
크기가 5 인 int 데이터 타입 변수로 이루어진 str 배열을 다음과 같이 선언한다.
이 때 각 변수의 이름을 arr[0], arr[1], arr[2], ...., arr[4] 라고 하며, 각각의 배열 변수를 요소라고 한다.
일반적으로 반복문을 이용할 때는 다음과 같이 첨자를 이용하여 표현한다.
int arr[5] ;
for (int i=0 ; i<5 ; i++) {
printf("%d 번째 성적 입력 : ", i+1);
scanf("%d", &arr[i]);
여기서 i와 같이 위치를 나타내는 것을 '첨자'라고 한다. 가장 조심해야 하는 것은 C 언어는 배열 첨자가 '1' 이 아닌 '0' 부터 시작한다는 것이다.
즉, arr[10]으로 선언하면 배열의 실제적은 변수는 arr[0] ~ arr[9] 가 된다.
이제 위의 프로그램을 배열을 이용하여 만들어보자.
변수의 개수가 성적의 개수에 따라 증가하는 부분은 해결 했으나 아직도 비슷한 소스가 반복되는 문제를 해결하지 못했다.
위 코드의 문제점을 해결하기 위해 반복문을 사용하여 다음과 같이 코딩할 수 있다.
05 행 : 크키가 5인 배열 sc를 선언 //sc[0] ~ sc[4]
08 행 : 첨자변수 'i' 사용, 0 부터 4 까지 증가하면서 처리.
배열의 초기화
배열 역시 일반적인 변수와 마찬가지로 선언과 동시에 초기화 할 수 있다. 배열의 초기화는 다음과 같이 중괄호를 이용한다.
int arr[5] = {1, 2, 3, 4, 5};
크기가 5인 arr 배열을 선언하고 동시에 각각의 변수 값을 초기화 한다.
int arr[] = {1, 2, 3};
선언 시 크기를 생략하면, 뒤에 오는 초기 값의 개수가 배열의 크기가 된다. (여기서는 3)
int arr[5] = {1, 2, 3};
배열의 크기가 초기값의 개수보다 크면, 남는 배열의 크기(여기서는 2)는 자동으로 '0'으로 채워진다.
다차원 배열
앞서 살펴본 배열에서는 1개의 첨자변수를 이용하였는데 이러한 배열을 '1차원 배열' 이라고 한다. 즉, 첨자를 여러개 사용한 것을 '다차원 배열'이라고 한다.
2 차원 배열
arr[1][2]
3 차원 배열
arr[1][2][3]
C 언어에서는 사실상 2 차원배열까지 사용하는 것이 대부분이다. 2 차원 배열의 선언 형식은 다음과 같다
다음 예를 통해 설명하면, 크키가 2 행 3 열의 int 데이터 타입 배열 arr이 생성되는데, 각 요소의 이름은 아래의 그림과 같다.
물론, 1 차원 배열과 마찬가지로 첨자가 0으로 시작된다는 것을 주의해야 한다.
2 차원 배열을 이용한 간단한 프로그램은 다음과 같다.
< 2차원 배열을 이용해서 각 학생의 과목에 대한 평균을 구하는 프로그램 >
05 행 : int score[4][3] => 각 과목 점수를 저장할 int 데이터 타입의 크기가 4행 3열인 배열 score를 생성한다.
int sum[4] = {0} => 각 학생의 총점을 저장할 배열을 생성하고 0 으로 초기화 한다.
23~25 행 : 각 학생에 대한 성적의 합을 구하는 부분이다. 첫 번째 학생의 총점은 sum[0]에 저장하고, 두 번째 학생의 총점은 sum[1]에 저장한다.
늘 말하지만 프로그램은 효율성이 중요하기 때문에 위의 프로그램을 보다 효율적이게 아래와 같이 코딩할 수 있다.
07~12 행 : i 번째(1~4) 학생의 j 과목(1~3) 성적을 입력받고, 각각 학생에 대한 총점을 sum 배열에 저장한다.
2 차원 배열의 초기화
2 차원 배열 역시 선언과 동시에 초기화 할 수 있다. 2 차원 배열을 초기화 하는 형식은 다음과 같은데, 행의 크기는 생략이 가능하지만 열의 크기는 생략할 수 없다.
생략된 행의 크기는 초기값의 개수에 따라 자동적으로 구해진다.
C 언어를 배우면서 1차적으로 배열에서 포기하고, 구조체에서 포기하고, 마지막으로 포인터에서 포기하는 사람들이 꽤나 많다.
따라서 다소 복잡해 보이는 위의 초기화 형식을 예제를 통해 살펴보자.
아래의 사진은 크기가 2 행 3 열인 배열 arr를 선언과 동시에 1,2,3,4,5,6 으로 초기화하는 예인데, 4가지 모두 같은 의미를 지닌다.
특히, 3번째/4번쨰의 경우에는 각 행에 대한 초기값을 중괄호로 다시 묶어 표현한 것이다.
1 번째 : 2행 3열 크기를 가진 배열을 선언하고 초기화 한다.
2 번째 : x행 3열 크기를 가진 배열을 선언하는데, 뒤에 초기값의 개수가 6개이고 3 열로 초기화 했기 때문에 행은 자동적으로 2 행이 된다.
3 번째 : 2행 3열 크기를 가진 배열을 선언하고, 초기화 부분이 중요한데 중괄호 안의 중괄호 개수가 행의 크기를 나타내고, (2)
중괄호 안의 중괄호 중 1개의 중괄호 안에 위치한 초기값이 열의 크기가 된다. (3)
4 번째 : x행 3열 크기를 가진 배열을 선언하고, 중괄호 안의 중괄호 개수인 2가 행의 크기가 되고, 열의 크기는 3이 된다.
몇 가지 초기화 예를 더 보도록 하자.
위와 같이 배열의 크기보다 개수가 작으면 초기화할 값이 없는 요소들은 1차원 배열과 마찬가지로 0으로 초기화 된다.
첫 번째와 세 번째 행의 초기값 개수가 행의 요소 수보다 작은데, 이런 경우 해당 행의 초기화 값이 없는 부분은 0으로 초기화 된다.
2 차원 배열을 이용한 프로그램의 실제 소스를 보기 전에, 어떤 프로글매인지 그림을 통해 알아보자.
해당 프로그램은 5행 5열 배열을 선언하고 다음과 같이 초기화 한다.
1 |
2 |
3 |
4 |
0 |
5 |
6 |
7 |
8 |
0 |
9 |
10 |
11 |
12 |
0 |
13 |
14 |
15 |
16 |
0 |
0 |
0 |
0 |
0 |
0 |
그리고 각 행에서 왼쪽 4개 요소(0~4)의 합을 구해 각 행의 마지막 요소(5)에 저장하고, 각 열에서 위쪽 4개 요소의 합을 구해 각 열의 마지막 요소에 저장한다.
그리고 전체 합을 구해 5행 5열에 저장한다.
1 |
2 |
3 |
4 |
10 |
5 |
6 |
7 |
8 |
26 |
9 |
10 |
11 |
12 |
42 |
13 |
14 |
15 |
16 |
58 |
28 |
32 |
36 |
40 |
136 |
05 ~ 10 행 : 배열을 선언하고 초기화 한다.
14 행 : i는 0이 되고, 식 'i<4'가 참이므로 15 행에서 21행을 실행한다.
15 행 : j는 0이 되고, 식 'j<4'가 참이므로 16 행에서 18행을 실행한다.
16 행 : num[0][4]에 num[0][0]의 값 '1'을 더해 num[0][4]는 1이 된다. (1행 합계)
17 행 : num[4][0]에 num[0][0]의 값 '1'을 더해 num[4][0]은 1이 된다. (1열 합계)
18 행 : num[4][4]에 num[0][0]의 값 '1'을 더해 num[4][4]는 1이 된다. (전체 합계)
===============================================================================
15 행 : j는 1이 되고, 식 'j<4'가 참이므로 16 ~ 18 행을 실행한다.
16 행 : num[0][4]에 num[0][1]의 값 '2'를 더해 num[0][4]는 3이 된다. (1행 합계)
17 행 : num[4][1]에 num[0][1]의 값 '2'를 더해 num[4][1]는 2가 된다. (2열 합계)
18 행 : num[4][4]에 num[0][1]의 값 '2'를 더해 num[4][4]는 3이 된다. (전체 합계)
===============================================================================
15 행 : j는 2가 되고, 식 'j<4'가 참이므로 16 ~ 18 행을 실행한다.
16 행 : num[0][4]에 num[0][2]의 값 '3'을 더해 num[0][4]는 6이 된다. (1행 합계)
17 행 : num[4][2]에 num[0][2]의 값 '3'을 더해 num[4][2]는 3이 된다. (3열 합계)
18 행 : num[4][4]에 num[0][2]의 값 '3'을 더해 num[4][4]는 6이 된다. (전체 합계)
================================================================================
이와 같은 동작을 반복하면서 행 첨자('i')에서 열 첨자('j')가 바뀌면서 현재 행 합계 (num[i][4])에 누적하고,
동시에 해당 열 합계(num[4][j])와 전체 합계 (num[4][4])에 누적한다.
22 행 : 3 자리로 출력하며 j가 4, 즉 하나의 행이 끝나면 줄을 바꾸고 그렇지 않으면 공백을 추가하여 간격을 띄운다.
배열 & 문자열
char 데이터 타입의 배열을 사용한다면 문자열을 처리할 수 있다고 요전 포스팅에서 언급한 적이 있다. 예를 들어 char 타입의 크기가 5인 str 배열을 선언하고 해당 배열에
"Book" 이라는 문자열을 저장하면 다음과 같다.
여기서 가장 마지막 배열인 str[4] 의 '\0' 은 문자열의 끝을 의미하기 때문에 반드시 넣어줘야 한다. 이를 프로그램으로 코딩하면 다음과 같아진다.
11 행 : 문자열의 마지막을 의미하는 널문자 '\0'을 저장한다. 만약 이를 저장하지 않고 실행하면 문자열의 끝을 인식하지 못하기 때문에 쓰레기 값이 출력된다.
char 데이터 타입 배열에 문자여를 대입하기 위한 방법은 위의 방법 말고도 다음과 같은 방법이 있다.
1 ~ 2 번째 : 배열 선언 후 문자열을 전체적으로 대입하면 자동적으로 널문자가 삽입된다.
3 ~ 4 번째 : 배열 선언 후 한글자씩 일일이 대입하면 직접 널문자를 삽입해야 한다.
그리고 선언문이 아닌 실행문에서 char 데이터 타입 배열 str에 문자열 "Book"을 대입하기를 원한다면, 아마 다음과 같이 하면 될꺼라 생가하지만 오류가 발생한다.
오류 발생 이유는 배열 이름인 str은 변수 이름을 의미하는 것이 아니라 첫 번째 배열 오쇼의 주소를 의미하는 상수이기 때문이다.
주소의 개념에 대해서는 이 후 포스팅 할 포인터 부분에서 자세히 공부 할 수 있다.
어찌되었든 위와 같은 방법은 되지 않기 때문에, 선언 후 나중에 대입을 하려면 "strcpy" 라는 함수를 이용하면 된다.
위에서 사용한 strcpy 함수를 "문자열 처리 함수" 라고 하는데 이 함수에는 몇 가지 종류가 더 있다.
함수 |
사용 형식 |
설명 |
strcpy |
strcpy (s,t) |
문자열 t를 s에 복사 |
strcat |
strcat (s,t) |
문자열 t를 s에 연결 |
strlen |
strlen (s) |
문자열 s의 길이를 반환 |
strcmp |
strcmp (s,t) |
문자열 s와 t를 비교해서 s가 크면 양의 정수, t가 크면 음의 정수, 같으면 0 을 반환 |
* 현존하는 대부분의 프로그램들은 사실 위의 함수들을 사용하지 않는다. 그 이유를 간략히 설명하자면 위의 함수들은 BOF (버퍼 오버플로우)라는 공격에
취약한 함수인데, BOF 공격은 변수에 할당된 공간(버퍼)의 크기를 초과하는 데이터를 입력하여 다른 데이터를 얻는 공격기법이다.
따라서 대부분 "strncpy, strncat 등"과 같이 중간에 n이 들어간 함수의 사용을 권장하는데 차이점은,
위 표안에 있는 함수들은 문자열을 처리 할때 별도의 크기 제약을 두지 않아 마음대로 입력할 수 있지만 ,
n이 달린 함수들은 인자값이 3개(문자열1, 문자열2, 사이즈) 입력되어 사이즈를 제한 함으로써 BOF 공격을 어렵게 한다.
그러나, 본 포스팅은 C언어를 배우기 위함이지 시큐어 코딩이 아니기 때문에 기본 함수를 사용한다.
< strcat 이용, 문자열 연결 >
06 행 : 배열은 선언하고 "Hello"를 대입한다.
08 행 : strcat 함수를 이용하여 위에서 선언한 str 변수(Hello)에 " World!"를 연결한다.
< strlen 이용, 문자열의 순서 뒤집기 >
str에 "hello"가 저장되어 있다고 가정한다.
12 행 : i=0, j=4 가 되며, 식 'i<j'가 참이기 때문에 13 ~ 15 행을 실행한다.
13 ~ 15 행 : str[i]와 str[j]의 값을 교환하는 문장인데, str[0]의 'h'와 str[4]의 'o'가 교환된다. // hello ==> oellh
12 행 : i++, j-- 에 의하여 i=1, j=3 이 되며, 식 'i<j'가 참이기 때문에 13 ~ 15 행을 실행한다.
13 ~ 15 행 : str[1]의 'e'와 str[3]의 'l' 이 교한된다. // hello ==> oellh ==> olleh
12 행 : i=2, j=2 가 되며, 식 'i<j'가 거짓이기 때문에 종료된다.
< strcmp 이용, 종료 여부 판단 >
12 행 : if 식 '!strmp(str, "quit")는 str과 "quit"가 같은지를 확인하는데
우선 str과 "quit"를 비교하여 같으면 0 이 된다.
앞에 !(NOT) 연산자에 의하여 1 이 된다.
1 은 논리적으로 True 값이기 때문에 if 문의 조건을 만족하여 프로그램이 종료된다.
문자열 입력
scanf 함수를 통해 문자열을 입력받을 수 있는데, 이전과 다르점은 str 앞에 '&' 기호가 붙지 않는점이다. 바로 위에서 설명했듯이 str은 변수 이름이 아니라 주소이기
때문인데, 이 역시 상세한 내용은 포인터 부분에서 다룬다. 우선은 char 데이터 타입의 배열에 문자열을 입력 받을 때는 '&' 기호가 붙지 않는 다는 것만 알아두자.
다른 프로그램 보기 전에 앞서 설명한 널문자 '\0'를 다시 생각해보자. 널문자란 문자열의 끝을 의미하는 문자라고 했는데 그렇다면 다음과 같이 문자열 중간에
널문자가 들어가 있으면 어떻게 될까?
05 행 : An 다음에 널문자가 입력되고 그 담에 on이 입력된다.
실행 결과 'An'만 출력된다. 중간에 문자열의 끝을 의미하는 널문자가 있기 때문에 뒷부분 'on'은 출력되지 않는다.
'Developing > C 언어' 카테고리의 다른 글
[C언어 강의 - 14] 기억클래스, 변수 (2) | 2014.05.10 |
---|---|
[C언어 강의 - 13] 함수 (0) | 2014.05.10 |
[C언어 강의 - 11] 제어문 (0) | 2014.05.08 |
[C언어 강의 - 10] 반복문 (1) | 2014.05.08 |
[C언어 강의 - 09] 조건문 (0) | 2014.05.08 |