세 사람의 성적을 입력받아 평균을 내는 프로그램을 작성한다고 합시다.
int score1, score2, score3;
과 같이 선언하여 차례대로 입력받을 수 있죠.
그런데 만약 임의의 n
명의 성적을 입력받아 평균을 구한다면 어떻게 해야 할까요?
입력받는 것 자체는 for (int i = 0; i < n; i++)
와 같은 반복문을 사용하면 쉽게 구현할 수 있지만, 변수의 경우 일일이 열거해주어야 합니다.
하지만 그 수가 많아질수록 생성해야 하는 변수도 많아지며, n
을 사용자에게 입력받아 사용할 경우, 변수를 몇 개까지 만들어 놓아야 할지 알 수 없습니다.
이런 경우에 사용할 수 있는 것이 배열입니다.
배열은 동일한 자료형의 값을 일정 개수만큼 일렬로 나열해놓은 것입니다.
각각의 값은 배열의 원소라고 불리며, 몇 번째 원소인지지 나타내는 숫자를 인덱스라고 합니다.
인덱스는 0부터 시작하며, arr
라는 이름의 배열의 0번째 원소는 arr[0]
로 사용하죠.
배열의 원소의 개수는 배열의 크기라고도 하며, 배열이 가질 수 있는 가장 큰 인덱스는 배열의 크기보다 1 작습니다.
변수는 다음과 같이 선언할 수 있습니다.
자료형 배열식별자[원소개수];
가령 정수 5개를 저장하는 scores
라는 이름의 배열을 선언한다면,
int scores[5];
라고 할 수 있죠.
그리고 배열은 다음과 같이 원소를 중괄호로 묶어서 초기화합니다.
int scores[5] = {78, 96, 36, 42, 89};
단, 이와 같이 여러 개의 값을 한 번에 묶어서 대입하는 것은 선언과 함께 초기화할 때만 가능하며, 이후에는 각각의 원소에 개별적으로 값을 대입해야 합니다.
배열은 선언할 때 반드시 개수가 정해져 있어야 하지만 선언과 함께 초기화할 땐 대입되는 값을 통해 원소의 개수를 알 수 있으므로 다음과 같이 원소의 개수를 생략할 수 있습니다.
int scores[] = {78, 96, 36, 42, 89};
그리고 배열의 크기보다 많은 원소를 전달하여 초기화할 경우 오류가 발생하지만, 배열의 크기보다 적은 원소를 전달하여 초기화할 경우, 앞에서부터 채워지고 나머지는 0으로 채워집니다.
int error[5] = {1, 2, 3, 4, 5, 6}; // 오류 발생
int autofill[5] = {1, 2, 3}; // {1, 2, 3, 0, 0} 으로 취급
배열을 for
문과 함께 사용하기 좋습니다.
예를 들어, 5명의 성적을 입력받는다고 할 때 다음과 같이 작성할 수 있죠.
int scores[5];
for (int i = 0; i < 5; i++) {
printf("학생 %d 성적: ");
scanf("%d", &scores[i]);
}
배열 중 문자의 배열은 특별히 문자열이라고 부릅니다.
문자열은 0개 이상의 연속된 문자로 이루어진 텍스트로, 어떤 프로그래밍 언어에서는 이를 나타내는 자료형이 따로 존재하기도 하지만 C언어에서는 char
자료형의 배열로 나타냅니다.
문자열은 일반적인 배열과 크게 다르지 않으면서도 문자열만의 특징이 몇 가지 있습니다.
잊지 말아야 할 가장 중요한 특징은, 문자열의 마지막 원소는 항상 null
이어야 한다는 것입니다.
null
은 "아무것도 없음"을 의미하는 문자로, 문자로는 \0
로 표기되며 내부적으로는 숫자 0
으로 표현됩니다.
그리고 문자열은 다음과 같은 배열 표현 방식뿐만 아니라
char str[] = {'H', 'e', 'l', 'l', 'o', '\0'};
큰따옴표로 묶은 문자열 표현 방식으로도 나타낼 수 있습니다.
char str[] = "Hello";
큰따옴표로 묶었을 땐 마지막 null
을 생략하고, 내부적으로는 큰따옴표가 끝날 때 null
을 추가하여 저장합니다.
그동안 "Hello World"
같이 출력했던 모든 텍스트들이 문자열이었던 것입니다!
문자열은 입출력 시 %s
라는 형식지정자를 사용합니다.
그리고 입력 시 개별 원소를 각각 따로 입력 받아야 하는 배열과 달리 한 번에 문자열로 입력 받을 수 있죠.
단, 입력되는 문자열의 길이가 문자열 크기를 넘어서지 않도록 주의합시다.
숫자 자료형에 범위를 넘어선 값을 넣으면 오버플로우가 발생하듯, 입력되는 문자열이 그것을 저장할 수 있는 문자열 크기를 넘어서면 문제가 됩니다.
문자열을 비롯한 값들은 내부적으로 "스택 stack"이라고 불리는 저장 공간에 저장된 채 사용되는데 이 스택에서 오버플로우가 발생했다고 하여 이러한 상황을 "스택 오버플로우"라고 부릅니다.
입력한 문자열이 그대로 저장되기는 하지만 범위를 초과하여 저장되므로 다른 값을 덮어씌워 나중에 잘못된 값을 사용하게 될 수 있죠.
어떤 값을 입력받을 때 변수 앞에 &
를 붙여 사용했습니다.
&
는 이후 포인터에 대해 배울 때 다시 이야기하겠지만 어떤 값의 주소를 추출하는 연산자입니다.
이걸 붙임으로써 "이 변수의 저장공간에 값을 입력해라" 하는 게 되었던 거죠.
그런데 배열과 문자열의 경우 그것의 이름이 그것의 주소를 나타냅니다.
따라서 문자열 char str[]
의 주소를 추출하기 위해서는 &
를 붙일 필요 없이 str
자체로 주소가 됩니다.
즉, 문자열을 입력받고자 할 땐 다음과 같이 작성합니다.
char str[10];
scanf("%s", str);
scanf
를 통한 문자열 입력은 기본적으로 띄어쓰기, 개행 등의 공백을 만날 때까지만 입력받는데, 만약 띄어쓰기를 포함하고 개행만을 입력 종료로 하고 싶다면 다음과 같이 작성할 수 있습니다.
char str[10];
scanf("%[^\n]s", str);
scanf
외에도 gets
와 같은 다른 함수를 사용하는 방법도 있긴 하지만 그것과 관려된 내용은 구글에게 물어보도록 합시다.
표준입출력을 위해 #include <stdio.h>
를 코드 맨 윗줄에 적어놓았던 것처럼, 문자열을 효과적으로 사용하기 위해 #include <string.h>
을 사용할 수 있습니다.
string.h
에는 문자열을 복사하는 함수, 두 문자열을 합치는 함수, 문자열을 비교하는 함수, 문자열의 길이를 재는 함수, 문자열 내부에 특정 문자열이 포함되어 있는지 확인하는 함수 등 다양한 문자열 관련 함수가 정의되어 있습니다.
구체적인 함수의 이름 및 사용법은 구글에게 물어보도록 합시다.
앞서 언급된 배열과 문자열은 1차원 배열입니다.
그저 선형적으로 한 줄로 나열된 자료들이죠.
그런데 때로는 행과 열이 있는 표 형태의 자료나, 큐브 형태로 쌓아올린 데이터가 필요할 수도 있습니다.
그럴 때 사용할 수 있는 것이 다차원 배열입니다.
사실, 다차원 배열이라고 해서 특별할 건 없습니다.
단지 1차원 배열에서와 같이 배열을 선언하되, []
가 그만큼 더 붙을 뿐이죠.
자료형 이차원배열[열개수][행개수];
자료형 삼차원배열[높이][세로][가로];
다차원 배열을 초기화할 땐 모든 원소를 차례로 나열해서 값을 전달할 수도 있지만 중괄호 안에 중괄호를 넣어 값을 전달하면 가독성을 높일 수 있습니다.
예를 들어, 2차원 배열을 초기화 하는 경우 다음과 같은 두 가지 방식을 사용할 수 있습니다.
// 4명의 학생의 국영수 성적을 저장한다거나?
int scores[4][3] = {
{54, 62, 78},
{97, 35, 42},
{76, 84, 92},
{82, 93, 64}
};
// 어디까지가 어느 열인지 알아보기 어렵죠?ㅋ
int scores[4][3] = {54, 62, 78, 97, 35, 42, 76, 84, 92, 82, 93, 64};
그리고 원소에 접근할 땐 차원만큼의 인덱스를 명시해주어야 하며, 중첩 반복문을 통해 모든 원소에 순차적으로 접근할 수 있습니다.
예를 들어, 네 명의 세 과목 성적을 입력받는다고 할 때 다음과 같이 작성할 수 있죠.
int scores[4][3];
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 3; j++) {
printf("학생 %d의 과목 %d 성적: ", i, j);
scanf("%d", &scores[i][j]);
}
}
어떤 극장의 좌석이 한 줄에 10개씩 4줄이 있다고 합시다.
이 극장의 좌석을 예매하는 시스템을 작성합니다.
각 좌석은 정수형으로 표현되며, 단순히 예매가 되어 있을 경우 1
, 그렇지 않을 경우 0
의 값을 갖습니다.
반복문과 조건문, 그리고 2차원 배열을 이용하여 1. 좌석 출력
, 2. 예매하기
, 3. 취소하기
, 4. 종료하기
기능을 구현합니다.
좌석 출력 기능은 좌석의 예매 여부를 시각적으로 보여줍니다.
예매된 좌석은 ■
, 그렇지 않은 좌석은 □
으로 출력한다거나? 방식은 자유입니다!
예매하기 기능은 좌석의 인덱스를 입력받아 예매되지 않은 좌석일 경우 예매 상태로 바꾸고, 예매된 좌석일 경우 예매할 수 없다는 내용을 출력합니다.
취소하기 기능은 좌석의 인덱스를 입력받아 예매된 좌석일 경우 예매되지 않은 상태로 바꾸고, 예매되지 않은 좌석일 경우 취소할 수 없다는 내용을 출력합니다.
종료하기 기능은 반복문을 벗어나 시스템을 종료합니다.
예시:
--- 메뉴 ---
1. 좌석 출력
2. 예매하기
3. 취소하기
4. 종료하기
-----------
메뉴 선택: 1
□□□□□□□□□□
□□□□□□□□□□
□□□□□□□□□□
□□□□□□□□□□
--- 메뉴 ---
1. 좌석 출력
2. 예매하기
3. 취소하기
4. 종료하기
-----------
메뉴 선택: 2
좌석 입력: 2 4
예매가 완료되었습니다.
--- 메뉴 ---
1. 좌석 출력
2. 예매하기
3. 취소하기
4. 종료하기
-----------
메뉴 선택: 1
□□□□□□□□□□
□□□□□□□□□□
□□□□■□□□□□
□□□□□□□□□□
--- 메뉴 ---
1. 좌석 출력
2. 예매하기
3. 취소하기
4. 종료하기
-----------
메뉴 선택: 4
시스템을 종료합니다.
문자열을 입력 받아서 그 문자열을 역순으로 출력하세요.
예를 들어, "abbd"
를 입력 받았으면 "dbba"
를 출력하세요.
이 때, 문자열의 크기는 100으로 설정하며, 이보다 긴 문자열은 입력되지 않는다고 가정합니다.
예시:
입력: abbd
출력: dbba
첫번째 과제는 이번 시간에 다룬 것뿐만 아니라 이전에 배운 조건문과 반복문에 대한 복습도 포함시켰습니다 깔깔
즐거운 코딩 하시기 바랍니다 : )