7. 배열과 문자열 다루기

여러 개의 데이터를 한 번에 다루는 힘
지금까지는 변수 하나에 값 하나를 담아 다뤘습니다. 그런데 실제 프로그램은 대개 그렇게 단순하지 않습니다. 학생 30명의 점수, 일주일의 온도, 게임 캐릭터들의 이름, 사용자가 입력한 문장처럼 비슷한 종류의 데이터를 한꺼번에 보관하고 처리해야 하는 일이 아주 많습니다. 이때 등장하는 것이 배열입니다.
배열을 배우면 "값 하나"를 다루는 단계에서 "값 묶음"을 다루는 단계로 올라가게 됩니다. 그리고 C 언어에서 문자열도 결국 문자 배열이기 때문에, 배열을 이해하면 문자열도 훨씬 쉽게 보입니다.12
배열이란 무엇인가
배열은 같은 자료형의 값을 연속된 메모리 공간에 차례대로 저장하는 구조입니다.1 쉽게 말해, 같은 크기의 칸이 줄지어 있고, 각 칸에 값을 넣는 보관함이라고 생각하면 됩니다.
예를 들어 정수 5개를 저장하고 싶다면 이렇게 선언합니다.
int scores[5];이 코드는 scores라는 이름의 정수 배열을 만들고, 정수 5개를 저장할 공간을 준비합니다. 배열의 각 칸은 인덱스로 구분하는데, 인덱스는 0부터 시작합니다.1
scores[0] = 90;
scores[1] = 85;
scores[2] = 100;
scores[3] = 76;
scores[4] = 88;여기서 첫 번째 값은 scores[0], 마지막 값은 scores[4]입니다. scores[1]이 두 번째이고, scores[2]가 세 번째라는 점에 익숙해져야 합니다. 초보자가 가장 자주 헷갈리는 부분이 바로 이 "0부터 시작" 규칙입니다.
배열 선언과 초기화
배열은 선언만 할 수도 있고, 만들면서 바로 값을 넣을 수도 있습니다.
int numbers[5] = {10, 20, 30, 40, 50};초기값을 모두 적으면 크기를 생략할 수도 있습니다.
int numbers[] = {10, 20, 30, 40, 50};이 경우 컴파일러가 원소 개수를 보고 크기를 자동으로 정해 줍니다.1
일부만 초기화하는 것도 가능합니다.
int data[5] = {1, 2};이렇게 하면 앞의 두 칸만 1, 2로 채워지고 나머지는 0으로 초기화됩니다.3
배열을 선언만 하고 값을 넣지 않았다면, 지역 변수 배열의 값은 자동으로 믿을 만한 값이 되지 않습니다. 그러니 초보자일수록 배열은 만들자마자 초기화하는 습관을 들이는 것이 좋습니다.2
배열 요소를 읽고 바꾸기
배열의 각 원소는 일반 변수처럼 읽고 바꿀 수 있습니다.
#include <stdio.h>
int main() {
int scores[5] = {90, 85, 100, 76, 88};
printf("첫 번째 점수: %dn", scores[0]);
printf("세 번째 점수: %dn", scores[2]);
scores[3] = 80;
printf("수정된 네 번째 점수: %dn", scores[3]);
return 0;
}배열을 쓸 때 중요한 감각 하나가 있습니다. 배열 이름은 하나지만, 실제로는 여러 개의 값을 품고 있다는 점입니다. 그래서 배열은 반복문과 만나면 진가를 발휘합니다.
반복문과 배열은 찰떡궁합
배열의 원소를 하나씩 처리할 때는 for문을 가장 많이 사용합니다.
#include <stdio.h>
int main() {
int scores[] = {90, 85, 100, 76, 88};
int sum = 0;
int count = sizeof(scores) / sizeof(scores[0]);
for (int i = 0; i < count; i++) {
printf("scores[%d] = %dn", i, scores[i]);
sum += scores[i];
}
printf("총합: %dn", sum);
printf("평균: %.2fn", (double)sum / count);
return 0;
}여기서 눈여겨볼 부분은 다음 줄입니다.
int count = sizeof(scores) / sizeof(scores[0]);sizeof(scores)는 배열 전체의 바이트 수를 구하고, sizeof(scores[0])는 원소 하나의 바이트 수를 구합니다. 둘을 나누면 원소 개수를 알 수 있습니다.3 숫자 5를 코드 곳곳에 직접 쓰는 것보다 훨씬 안전합니다. 나중에 배열 크기가 바뀌어도 실수를 줄일 수 있기 때문입니다.
배열에서 가장 조심해야 할 것: 범위 초과
C 언어는 배열을 다룰 때 범위 검사(bounds checking)를 자동으로 해 주지 않습니다.2 쉽게 말해, 없는 칸을 읽거나 써도 친절하게 막아 주지 않을 수 있습니다.
예를 들어 크기가 5인 배열은 인덱스가 0부터 4까지만 유효합니다.
int scores[5] = {90, 85, 100, 76, 88};
printf("%dn", scores[5]); // 잘못된 접근이 코드는 겉보기에는 돌아갈 수도 있지만, 결과는 믿을 수 없습니다. 프로그램이 이상하게 동작하거나, 갑자기 종료되거나, 아주 조용히 다른 값을 망가뜨릴 수도 있습니다.2 C 언어에서 이런 현상을 정의되지 않은 동작이라고 부릅니다.
배열을 다룰 때는 늘 "지금 내가 접근하는 인덱스가 정말 유효한가?"를 확인해야 합니다. C는 자유를 많이 주는 대신, 실수의 책임도 프로그래머에게 넘깁니다.
함수에 배열 넘기기
배열은 함수와도 자주 함께 사용됩니다. 예를 들어 배열의 합을 구하는 함수를 만들 수 있습니다.
#include <stdio.h>
int get_sum(int arr[], int size) {
int sum = 0;
for (int i = 0; i < size; i++) {
sum += arr[i];
}
return sum;
}
int main() {
int numbers[] = {1, 2, 3, 4, 5};
int size = sizeof(numbers) / sizeof(numbers[0]);
printf("합: %dn", get_sum(numbers, size));
return 0;
}여기서 중요한 점은, 배열을 함수에 넘길 때는 보통 크기도 함께 넘겨야 한다는 것입니다.1 C는 함수 안에서 배열의 길이를 자동으로 기억해 주지 않기 때문입니다.
이 규칙은 앞으로 포인터를 배울 때 더 깊이 이해하게 됩니다. 지금은 "배열을 넘길 때는 크기도 같이 넘긴다"만 확실히 기억해 두면 충분합니다.
2차원 배열: 표처럼 저장하기
배열 안에 배열을 넣으면 다차원 배열이 됩니다. 가장 많이 쓰는 형태는 2차원 배열입니다. 행과 열이 있는 표처럼 생각하면 쉽습니다.1
int seats[2][3] = {
{1, 2, 3},
{4, 5, 6}
};이 배열은 2행 3열 구조입니다. 예를 들어 seats[0][1]은 첫 번째 행의 두 번째 값이고, seats[1][2]는 두 번째 행의 세 번째 값입니다.
2차원 배열은 중첩 반복문으로 처리합니다.
#include <stdio.h>
int main() {
int seats[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
printf("%d ", seats[i][j]);
}
printf("n");
}
return 0;
}표 형태의 데이터, 게임 보드, 좌석 배치, 성적표 같은 문제에서 아주 유용합니다.
문자열은 특별한 문자 배열이다
C 언어에는 다른 언어처럼 독립적인 string 자료형이 없습니다. 대신 문자열은 char 배열로 표현합니다.45
예를 들어 Hello라는 문자열은 메모리 안에서 사실상 이렇게 저장됩니다.
{'H', 'e', 'l', 'l', 'o', '0'}마지막의 0은 널 문자(null character)이고, 문자열의 끝을 나타냅니다.45 이 한 글자가 없으면 C는 문자열이 어디서 끝나는지 알 수 없습니다.
문자열을 만드는 가장 쉬운 방법은 큰따옴표를 사용하는 것입니다.
char greeting[] = "Hello";이 코드는 컴파일러가 자동으로 0까지 붙여 줍니다.4
직접 문자 배열로 적을 수도 있습니다.
char greeting[] = {'H', 'e', 'l', 'l', 'o', '0'};두 방식 모두 결과는 비슷하지만, 보통은 첫 번째 방식이 더 간단하고 읽기 쉽습니다.
문자열 배열의 크기와 널 문자
문자열에서 정말 자주 발생하는 실수가 배열 크기를 너무 작게 잡는 것입니다.
char name[6] = "David"; // 가능
char name2[5] = "David"; // 잘못됨David는 문자 5개이지만, 문자열로 저장하려면 끝의 0까지 필요해서 총 6칸이 있어야 합니다.5 문자열 배열을 만들 때는 항상 문자 개수 + 1을 떠올리세요.
문자열 출력과 문자 하나 접근하기
문자열 전체를 출력할 때는 %s를 사용합니다.
#include <stdio.h>
int main() {
char greeting[] = "Hello World";
printf("%sn", greeting);
return 0;
}문자 하나만 다룰 때는 배열처럼 인덱스로 접근할 수 있습니다.
printf("%cn", greeting[0]);이 경우 %s가 아니라 %c를 사용해야 합니다. %s는 문자열 전체, %c는 문자 하나를 뜻합니다.4
문자열도 배열이므로 문자 하나를 바꾸는 일은 가능합니다.
char word[] = "Hello";
word[0] = 'J';
printf("%sn", word); // Jello문자열 입력: scanf와 fgets의 차이
문자열 입력에서는 scanf와 fgets의 차이를 꼭 알아야 합니다.
char name[20];
scanf("%s", name);이 방식은 공백 전까지만 입력받습니다. 즉, Kim Minsu를 입력하면 Kim까지만 저장됩니다.5
공백이 포함된 한 줄 전체를 입력받고 싶다면 fgets가 더 적합합니다.
#include <stdio.h>
int main() {
char line[100];
printf("문장을 입력하세요: ");
fgets(line, sizeof(line), stdin);
printf("입력한 내용: %s", line);
return 0;
}fgets는 버퍼 크기를 함께 받기 때문에 문자열 입력에서 더 안전하게 사용할 수 있습니다.5 초보자라면 문자열 입력은 우선 fgets부터 익히는 편이 좋습니다.
string.h로 문자열 다루기
C는 문자열을 위한 여러 함수를 string.h 헤더 파일로 제공합니다.25 자주 쓰는 함수 몇 개만 먼저 익혀도 실전에서 꽤 도움이 됩니다.
strlen: 길이 구하기
#include <stdio.h>
#include <string.h>
int main() {
char text[] = "banana";
printf("길이: %zun", strlen(text));
return 0;
}strlen은 문자열 길이를 구합니다. 여기서 길이에는 0이 포함되지 않습니다.4
strcpy: 문자열 복사하기
#include <stdio.h>
#include <string.h>
int main() {
char src[] = "apple";
char dest[20];
strcpy(dest, src);
printf("%sn", dest);
return 0;
}단, 목적지 배열 dest가 충분히 커야 합니다. 작으면 문제가 생길 수 있습니다.
strcmp: 문자열 비교하기
#include <stdio.h>
#include <string.h>
int main() {
char a[] = "cat";
char b[] = "cat";
if (strcmp(a, b) == 0) {
printf("같은 문자열입니다.n");
}
return 0;
}문자열은 숫자처럼 ==로 내용 비교를 하는 것이 아니라, 보통 strcmp를 사용합니다.5
strcat: 문자열 이어 붙이기
#include <stdio.h>
#include <string.h>
int main() {
char text[20] = "Hello ";
char tail[] = "C";
strcat(text, tail);
printf("%sn", text);
return 0;
}이 함수 역시 결과를 담을 배열의 크기가 충분해야 합니다.
여러 문자열 저장하기
문자열 여러 개를 저장하고 싶을 때는 문자열 배열을 만들 수 있습니다. 가장 쉬운 방식은 2차원 char 배열입니다.6
#include <stdio.h>
int main() {
char fruits[3][10] = {"apple", "banana", "kiwi"};
for (int i = 0; i < 3; i++) {
printf("%sn", fruits[i]);
}
return 0;
}이 코드는 과일 이름 3개를 저장합니다. 각 행은 문자열 하나를 뜻하고, 열 크기 10은 각 문자열이 가질 수 있는 최대 문자 수를 뜻합니다. 물론 여기에도 0 자리가 포함됩니다.6
이 방식은 배우기 쉽지만, 짧은 문자열도 긴 문자열과 똑같은 칸 수를 예약하기 때문에 메모리가 조금 낭비될 수 있습니다.6 더 유연한 방법도 있지만, 그건 다음 장의 포인터를 배운 뒤 다루면 이해가 훨씬 잘 됩니다.
실전 예제: 학생 이름과 점수 출력하기
배열과 문자열을 함께 쓰면 이런 식의 프로그램을 만들 수 있습니다.
#include <stdio.h>
int main() {
char names[3][20] = {"Kim", "Lee", "Park"};
int scores[3] = {95, 88, 76};
int count = 3;
for (int i = 0; i < count; i++) {
printf("%s : %d점n", names[i], scores[i]);
}
return 0;
}여기서는 이름 목록과 점수 목록이 같은 인덱스를 기준으로 연결됩니다. 즉 names[0]의 점수는 scores[0]입니다. 실제 프로그램에서는 이런 방식이 매우 자주 등장합니다.
초보자가 자주 하는 실수
배열과 문자열은 편리하지만, 실수도 잘 일어납니다. 특히 아래 네 가지는 꼭 조심하세요.
배열 인덱스를 1부터 생각하는 실수
배열 범위를 넘어서 접근하는 실수
문자열 배열 크기에
0자리를 빼먹는 실수scanf("%s", ...)가 공백을 포함한 문장을 읽어 줄 거라고 착각하는 실수
이 네 가지만 잘 피해도 배열과 문자열 때문에 머리를 싸맬 일이 크게 줄어듭니다.
정리
배열은 같은 형식의 데이터를 모아 저장하는 가장 기본적인 도구입니다. 반복문과 함께 쓰면 여러 값을 빠르고 깔끔하게 처리할 수 있습니다. 2차원 배열을 이용하면 표 형태의 데이터도 다룰 수 있습니다.
문자열은 C에서 따로 존재하는 특별한 자료형이 아니라, 0으로 끝나는 char 배열입니다. 그래서 문자열을 잘 다루려면 결국 배열 감각이 필요합니다.45
이번 장에서 꼭 기억해야 할 핵심은 세 가지입니다.
배열의 인덱스는 0부터 시작한다.
C는 배열 범위를 자동으로 지켜 주지 않는다.
문자열은
0으로 끝나는 문자 배열이다.
이 세 가지가 머리에 들어오면, 다음 장에서 배우는 포인터도 훨씬 덜 낯설게 느껴질 것입니다. 사실 배열과 문자열은 포인터 장으로 가는 아주 훌륭한 다리이기도 하니까요.
참고
1C Programming Basics: Part 6 – Arrays and Strings
25. Arrays and Strings — The Book of C
이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.