배열과 문자열
이 장에서 배우는 것
비품 이름이 세 개일 때는 이름마다 변수를 하나씩 만들 수 있다. 그러나 비품이 늘어날 때마다 변수를 추가하고 출력문을 고치는 방식은 관리하기 어렵다. 같은 종류의 값을 묶으면 반복문으로 전체 목록을 살펴보거나 특정 위치의 값을 바꾸기 쉬워진다. 이때 사용하는 것이 배열(array)이다.
비품 이름과 관리 번호는 문자열로 다룬다. 사람이 입력하거나 파일에서 가져온 문자열에는 앞뒤 공백이나 일정하지 않은 표기가 섞일 수 있다. 이 장에서는 배열에 비품 이름을 담고, 문자열을 정리한 뒤, 대여 현황을 한 번에 출력하는 프로그램을 만든다. 앞 장에서 배운 프로시저와 조건문, 반복문을 바탕으로 설명한다.
- 배열의 위치 번호와 길이를 구분하고, 반복문으로 모든 요소를 읽는다.
- ReDim Preserve로 기존 값을 보존하면서 배열의 크기를 바꾼다.
- 다차원 배열에 비품별 보유 수량과 대여 수량을 함께 저장한다.
- String 메서드로 공백을 정리하고 문자열을 찾거나 나눈다.
- 문자열 보간과 StringBuilder로 여러 줄의 보고서를 만든다.
문제 상황
작은 사무실에서 노트북, 프로젝터, 회의용 스피커를 빌려준다. 담당자는 비품 이름을 적은 목록과 비품별 수량을 함께 확인해야 한다. 예를 들어 노트북은 두 대를 보유하고 한 대를 대여 중이며, 프로젝터는 한 대를 보유하고 현재 대여 중인 것은 없다.
목록을 받아 보니 노트북 이름 앞뒤에 공백이 있고, 회의용 스피커 이름 가운데에는 공백이 두 칸 들어 있다. 여기에 태블릿을 새로 들여왔으므로 목록의 저장 공간도 늘려야 한다. 출력할 때는 비품마다 보유 수량, 대여 수량, 남은 수량을 같은 형식으로 보여 주고 싶다.
이번 프로그램은 입력을 기다리지 않는다. 정해진 비품과 수량을 코드에 넣어 실행할 때마다 같은 보고서를 만든다. 먼저 이름 배열을 정리하고 태블릿을 추가한다. 그다음 수량 배열을 읽어 남은 수량을 계산하고, 마지막에 여러 줄의 문자열을 출력한다. 이름 배열의 같은 위치와 수량 배열의 같은 행은 동일한 비품을 나타내도록 약속한다.
배열의 위치와 크기
괄호 안의 숫자는 마지막 위치다
배열은 같은 데이터 형식의 값을 여러 개 저장한다. 배열에 들어 있는 개별 값을 요소라고 하며, 요소를 가리키는 위치 번호를 인덱스(index)라고 한다. Visual Basic에서 이 장의 배열은 위치 번호가 0부터 시작한다. 요소가 세 개라면 위치 번호는 0, 1, 2다.
Dim equipment(2) As String
equipment(0) = "노트북"
equipment(1) = "프로젝터"
equipment(2) = "회의용 스피커"
equipment(2)의 2는 요소 개수가 아니라 사용할 수 있는 마지막 위치다. 따라서 위 선언은 문자열 세 개를 저장할 공간을 만든다. 값을 지정하지 않은 String 배열 요소의 초기값은 Nothing이다. Nothing은 아직 문자열 값이 들어 있지 않다는 뜻이며, 길이가 0인 문자열인 ""와 구분된다. 완성 코드에서는 처음부터 값을 함께 지정한다.
Dim equipment As String() = {"노트북", "프로젝터", "회의용 스피커"}
String()은 문자열 배열이라는 형식 표시다. 중괄호에 값을 세 개 적었으므로 배열의 길이도 세 개로 정해진다. 배열 변수의 이름 뒤에 괄호를 두어 선언하는 방식과 형식 이름 뒤에 괄호를 두는 방식 모두 사용할 수 있다. 여기서는 배열 형식이 눈에 잘 들어오도록 String()을 사용한다.
| 표현 | 뜻 | 세 요소 배열의 값 |
|---|---|---|
| equipment.Length | 전체 요소 개수 | 3 |
| equipment(0) | 첫 번째 요소 | 노트북 |
| equipment(2) | 세 번째 요소 | 회의용 스피커 |
| equipment.Length - 1 | 마지막 위치 번호 | 2 |
모든 요소를 순서대로 처리할 때는 For 반복문을 사용한다. 위치 번호가 필요하다면 시작값을 0으로 하고 끝값을 Length - 1로 한다. 값만 읽으면 되는 작업에는 For Each도 사용할 수 있다. 이번에는 정리한 이름을 원래 위치에 다시 저장해야 하므로 위치 번호를 사용하는 For가 적합하다.
For i As Integer = 0 To equipment.Length - 1
Console.WriteLine(equipment(i))
Next
배열을 만들고 나면 그 배열 자체의 길이는 고정된다. 요소에 다른 값을 대입하는 것과 배열의 길이를 바꾸는 것은 별개의 작업이다. equipment(0)에 다른 이름을 넣어도 저장 공간의 개수는 달라지지 않는다.
ReDim Preserve로 공간 늘리기
기존 비품 세 개에 태블릿을 추가하려면 네 번째 공간이 필요하다. ReDim은 배열의 크기를 다시 정하는 문이다. Preserve를 함께 쓰면 기존 요소를 가능한 범위에서 새 배열로 복사한다. 실행 관점에서는 크기가 다른 배열을 만들고 배열 변수가 그 배열을 가리키게 된다고 이해하면 된다.
ReDim Preserve equipment(3)
equipment(3) = "태블릿"
마지막 위치를 3으로 지정했으므로 길이는 4가 된다. 기존의 0, 1, 2 위치에는 이름이 남아 있다. 새로 생긴 3 위치는 처음에 Nothing이므로 태블릿이라는 값을 바로 대입한다. Preserve 없이 ReDim을 실행하면 이전 이름은 유지되지 않고 각 요소가 초기값으로 시작한다.
줄이는 것도 가능하다. 네 요소가 있는 배열에 ReDim Preserve equipment(1)을 실행하면 길이는 2가 되고, 위치 0과 1의 값만 남는다. 잘려 나간 값은 이후 배열에서 읽을 수 없다. Preserve는 크기를 줄일 때 사라지는 요소까지 별도로 보관해 주는 기능은 아니다.
요소를 하나 추가할 때마다 ReDim Preserve를 반복하면 기존 요소를 복사하는 작업도 반복된다. 이 장에서는 크기 변경이 한 번뿐인 작은 목록에 사용한다. 변경 횟수가 많은 자료를 설계할 때는 배열의 크기를 얼마나 자주 바꾸는지도 함께 고려해야 한다.
행과 열로 저장하는 다차원 배열
이름마다 수량을 하나씩 저장하는 것만으로는 보유 수량과 대여 수량을 함께 표현하기 어렵다. 표처럼 두 방향의 위치를 지정하는 이차원 배열을 사용하면 비품 하나에 여러 수량을 대응시킬 수 있다. 두 개 이상의 위치 번호로 요소를 지정하는 배열을 다차원 배열이라고 한다.
Dim quantities As Integer(,) = {
{2, 1},
{1, 0},
{1, 1},
{3, 1}
}
Integer(,)의 쉼표 하나는 위치 번호가 두 개 필요하다는 뜻이다. 첫 번째 위치는 행, 두 번째 위치는 열로 사용한다. 이 의미는 언어가 정해 주는 것이 아니라 프로그램에서 정하는 약속이다. 여기서는 행 0부터 노트북, 프로젝터, 회의용 스피커, 태블릿을 나타낸다. 열 0에는 보유 수량을, 열 1에는 대여 수량을 넣는다.
따라서 quantities(0, 0)은 노트북 보유 수량인 2이고, quantities(0, 1)은 노트북 대여 수량인 1이다. 태블릿의 대여 수량은 quantities(3, 1)로 읽는다. 남은 수량은 같은 행의 열 0에서 열 1을 뺀 값이다.
배열의 각 방향을 차원이라고 한다. GetLength(0)은 첫 번째 차원의 길이, GetLength(1)은 두 번째 차원의 길이를 반환한다. 위 배열에서 각각 4와 2다. 반면 Length는 전체 요소 개수이므로 8이다. 비품별로 한 행씩 처리하는 반복문에서는 GetLength(0)을 사용해야 한다.
| 표현 | 결과 | 이 프로그램에서의 의미 |
|---|---|---|
| quantities.Length | 8 | 모든 수량 칸의 개수 |
| quantities.GetLength(0) | 4 | 비품을 나타내는 행 수 |
| quantities.GetLength(1) | 2 | 수량 종류를 나타내는 열 수 |
| quantities(2, 1) | 1 | 회의용 스피커의 대여 수량 |
다차원 배열에서 ReDim Preserve를 사용하면 마지막 차원의 크기만 바꿀 수 있다. 이 프로그램처럼 행을 비품으로 정한 배열은 첫 번째 차원인 행 수를 Preserve로 늘릴 수 없다. 비품 행을 추가해야 한다면 새 배열을 만들고 필요한 값을 반복문으로 복사하는 등의 방법이 필요하다. 완성 코드에서는 이름 배열을 먼저 늘리고, 수량 배열은 처음부터 네 행으로 만든다.
이름 배열과 수량 배열은 따로 존재하므로 서로의 내용을 자동으로 맞추지 않는다. 이름만 늘리고 수량 행을 추가하지 않으면 새 비품의 수량을 읽을 수 없다. 이번 예제에서는 네 이름과 네 수량 행을 같은 순서로 작성한다. 배열을 읽는 방법과 함께 이 대응 관계도 확인해야 한다.
문자열 정리와 보고서 만들기
String 메서드는 결과를 돌려준다
문자열에는 길이를 읽는 속성과 값을 가공하는 메서드가 있다. 메서드(method)는 값에 연결해서 호출하는 동작이며, 앞 장에서 배운 프로시저 호출과 비슷하게 인수를 전달하고 결과를 받는다. equipment(i).Trim()은 해당 이름의 앞뒤 공백을 제거한 결과를 반환한다.
String의 내용은 만들어진 뒤 직접 바뀌지 않는다. 이를 불변이라고 한다. Trim이나 Replace를 호출하면 가공한 문자열이 반환되며, 기존 변수에 저장된 값을 바꾸려면 반환 결과를 다시 대입해야 한다.
equipment(i) = equipment(i).Trim().Replace(" ", " ")
먼저 Trim이 앞뒤 공백을 정리하고, 그 결과에 Replace를 호출해 두 칸짜리 공백을 한 칸으로 바꾼다. 이번 자료의 회의용 스피커에는 두 칸 공백이 한 곳에 있으므로 이 식으로 정리된다. 임의의 긴 공백을 모두 한 칸으로 만드는 일반적인 처리라고 보아서는 안 된다. 예를 들어 공백 네 칸은 한 번의 Replace 뒤에도 두 칸으로 남을 수 있다.
| 메서드 | 하는 일 | 예 |
|---|---|---|
| Trim | 앞뒤 공백 정리 | " 노트북 "을 "노트북"으로 |
| Replace | 지정한 부분을 다른 문자열로 교체 | 두 칸 공백을 한 칸으로 |
| ToUpperInvariant | 문화권에 따라 달라지지 않는 대문자 변환 | "nb-014"를 "NB-014"로 |
| Substring | 지정한 위치부터 부분 문자열 추출 | "NB-014"의 위치 3부터 "014" |
| Contains | 지정한 문자열의 포함 여부 확인 | "프로젝터"에 "프로"가 있는지 |
| Split | 구분 문자를 기준으로 문자열 나누기 | 쉼표로 구분된 이름을 배열로 |
Substring의 시작 위치도 0부터 센다. "NB-014"에서 N은 위치 0, B는 위치 1, 하이픈은 위치 2다. Substring(3)은 위치 3부터 끝까지인 "014"를 반환한다. 이 코드가 안전한 이유는 관리 번호를 "nb-014"로 고정했기 때문이다. 길이가 제각각인 값을 처리한다면 부분 문자열을 꺼내기 전에 위치가 유효한지 확인해야 한다.
Contains는 찾는 문자열이 있으면 True를, 없으면 False를 반환한다. StartsWith는 문자열의 시작 부분을 확인한다. 완성 코드에서는 StringComparison.Ordinal을 함께 지정한다. 이는 언어별 정렬 규칙 대신 문자열을 구성하는 값의 순서로 비교하는 방식이다. 비품 이름에 정해진 글자가 있는지 확인하는 의도를 코드에 드러낸다.
Split은 문자열 배열을 반환한다. "노트북, 프로젝터"를 쉼표로 나누면 두 번째 요소에는 앞쪽 공백이 남는다. 구분 문자를 없애는 일과 주변 공백을 정리하는 일은 다르므로, 완성 코드에서는 나누어진 각 요소에 Trim을 적용한다. 쉼표를 나타내는 ","c의 c는 문자열이 아니라 문자 하나인 Char 값임을 표시한다.
String의 Length는 내부에서 문자열을 세는 UTF-16 코드 단위의 개수다. UTF-16은 문자를 숫자 단위로 표현하는 방식이다. 이번 관리 번호는 영문자, 숫자, 하이픈만 사용하므로 길이가 눈에 보이는 여섯 글자와 일치한다. 일부 이모지 등에서는 화면에 보이는 글자 수와 Length가 다를 수 있다.
문자열 보간과 StringBuilder
문자열 보간(string interpolation)은 문자열 안에 변수나 계산 결과를 끼워 넣는 표현이다. 큰따옴표 앞에 $를 붙이고 값이 들어갈 곳을 중괄호로 감싼다. 다음 식에서는 name과 available의 현재 값이 문장에 들어간다.
Dim name As String = "노트북"
Dim available As Integer = 1
Dim line As String = $"{name}: 남음 {available}"
보간은 한 줄의 형태를 읽기 쉽게 만드는 데 적합하다. 반면 여러 줄을 반복해서 이어 붙이는 보고서는 StringBuilder를 사용할 수 있다. StringBuilder는 내용을 추가하거나 수정할 수 있는 문자열 작업 공간이다. System.Text에 들어 있으므로 파일 위쪽에 Imports System.Text를 적으면 짧은 이름으로 사용할 수 있다.
Append는 내용을 뒤에 붙이고, AppendLine은 내용을 붙인 뒤 줄바꿈도 추가한다. 마지막에 ToString을 호출하면 작업 공간의 내용을 하나의 String 값으로 얻는다. 이 값은 Console.Write로 출력할 수 있다. 이미 각 줄 끝에 줄바꿈이 있으므로 완성 코드에서는 보고서 전체를 WriteLine으로 출력하지 않는다.
보간과 StringBuilder는 함께 사용할 수 있다. 보간으로 비품 한 개의 출력 문장을 만들고, AppendLine으로 그 문장을 보고서에 추가한다. 아주 짧은 문자열 몇 개를 합칠 때마다 StringBuilder가 필요한 것은 아니다. 여기서는 반복문 안에서 보고서를 쌓는 과정을 명확히 보여 주기 위해 사용한다.
완성 코드
터미널에서 dotnet new console -lang VB 명령으로 만든 프로젝트의 Program.vb 내용을 아래 코드로 바꾼다. .NET 10 SDK가 설치된 macOS와 Linux에서 실행할 수 있다. 파일 맨 앞의 Option Strict On은 데이터 형식이 맞지 않는 대입이나 호출을 엄격하게 검사하도록 한다.
Option Strict On
Imports System
Imports System.Text
Module Program
Sub Main()
Dim equipment As String() = {
" 노트북 ",
"프로젝터",
"회의용 스피커"
}
For i As Integer = 0 To equipment.Length - 1
equipment(i) = equipment(i).Trim().Replace(" ", " ")
Next
ReDim Preserve equipment(3)
equipment(3) = "태블릿"
Dim quantities As Integer(,) = {
{2, 1},
{1, 0},
{1, 1},
{3, 1}
}
Dim assetCode As String = "nb-014".ToUpperInvariant()
Dim serialPart As String = assetCode.Substring(3)
Dim requestItems As String() = "노트북, 프로젝터".Split(","c)
For i As Integer = 0 To requestItems.Length - 1
requestItems(i) = requestItems(i).Trim()
Next
Dim report As New StringBuilder()
report.AppendLine("비품 대여 현황")
report.AppendLine($"등록 비품: {equipment.Length}종")
report.AppendLine($"관리 번호: {assetCode} / 길이: {assetCode.Length}")
report.AppendLine($"번호 부분: {serialPart}")
report.AppendLine($"신청 목록: {requestItems(0)} / {requestItems(1)}")
report.AppendLine("이름 | 보유 | 대여 | 남음")
Dim totalAvailable As Integer = 0
For row As Integer = 0 To quantities.GetLength(0) - 1
Dim owned As Integer = quantities(row, 0)
Dim rented As Integer = quantities(row, 1)
Dim available As Integer = owned - rented
totalAvailable += available
report.AppendLine(
$"{equipment(row)} | {owned} | {rented} | {available}")
Next
report.AppendLine($"대여 가능한 총수량: {totalAvailable}")
If equipment(1).Contains("프로", StringComparison.Ordinal) Then
report.AppendLine("검색 '프로': 프로젝터")
End If
If equipment(2).StartsWith("회의용", StringComparison.Ordinal) Then
report.AppendLine("회의용 비품: 회의용 스피커")
End If
Console.Write(report.ToString())
End Sub
End Module
줄별 해설
Option Strict On과 Imports는 실행문보다 앞에 놓는다. Option Strict On으로 형식 검사를 켜고, Imports System과 Imports System.Text로 Console, StringComparison, StringBuilder가 들어 있는 이름 공간을 불러온다. 이름 공간은 관련 형식의 이름을 묶는 구역이다.
Module Program과 Sub Main은 프로그램의 실행 틀이다. 실행하면 Main 안의 문장을 위에서 아래로 처리한다. 별도의 입력이나 다른 파일은 필요하지 않다.
Dim equipment As String()으로 세 비품 이름을 담는다. 첫 번째 이름에는 앞뒤 공백이, 세 번째 이름에는 중간의 두 칸 공백이 있다. 실제 정리 과정이 드러나도록 일부러 정리 전의 값을 넣었다.
첫 번째 For는 위치 0부터 Length - 1까지 진행한다. 각 이름에 Trim과 Replace를 차례로 적용하고 결과를 같은 요소에 대입한다. 반복이 끝나면 이름은 "노트북", "프로젝터", "회의용 스피커"다.
ReDim Preserve equipment(3)은 마지막 위치를 3으로 바꾼다. 기존 세 이름을 보존하면서 길이가 4인 배열을 준비한다. 바로 다음 줄에서 새 위치 3에 "태블릿"을 넣는다.
Dim quantities As Integer(,)는 네 행과 두 열의 수량 표를 만든다. 이름 배열의 위치와 수량 배열의 행이 같은 비품을 나타낸다. 예를 들어 두 배열에서 위치 또는 행 2는 회의용 스피커다.
assetCode와 serialPart는 관리 번호의 문자열 처리 결과를 담는다. 소문자 번호를 대문자로 바꾸면 "NB-014"가 된다. 그 결과의 위치 3부터 끝까지 꺼내면 "014"가 된다. 숫자처럼 보이지만 serialPart는 문자열이므로 앞의 0이 그대로 남는다.
requestItems와 두 번째 For는 쉼표로 나눈 신청 목록을 정리한다. Split이 두 요소를 만들고, 반복문이 각 요소의 앞뒤 공백을 제거한다. 따라서 신청 목록을 출력할 때 불필요한 공백이 붙지 않는다.
Dim report As New StringBuilder()는 보고서를 쌓을 작업 공간을 만든다. 처음 여섯 번의 AppendLine은 제목, 등록 비품 수, 관리 번호, 번호 부분, 신청 목록, 수량 표의 제목 줄을 추가한다. 보간식의 equipment.Length는 크기를 늘린 뒤의 값인 4다.
totalAvailable은 남은 수량의 합계를 저장한다. 반복문이 시작되기 전에 0으로 둔다. For 안에 선언하면 행마다 0부터 다시 시작하므로 누적 합계를 구할 수 없다.
행을 처리하는 For는 GetLength(0)으로 얻은 행 수만큼 반복한다. owned는 열 0, rented는 열 1에서 읽는다. available은 두 값의 차이다. totalAvailable += available은 기존 합계에 이번 비품의 남은 수량을 더한 뒤 다시 저장한다.
반복문 안의 AppendLine은 이름과 세 수량을 한 줄로 만든다. 노트북에서 태블릿까지 배열의 순서대로 추가된다. 반복문 뒤의 AppendLine은 계산을 마친 총수량 4를 기록한다.
두 If는 포함 여부와 시작 부분 검사를 보여 준다. 정해 둔 두 조건은 모두 True이므로 검색 결과와 회의용 비품 안내가 한 줄씩 추가된다. 마지막 Console.Write는 ToString으로 얻은 전체 보고서를 출력하고, Main이 끝나면 프로그램도 종료된다.
실행 결과
프로젝트 폴더에서 다음 명령을 실행한다.
dotnet run
프로그램이 출력하는 내용은 다음과 같다. 별도의 입력 없이 마지막 줄을 출력하고 끝난다.
비품 대여 현황
등록 비품: 4종
관리 번호: NB-014 / 길이: 6
번호 부분: 014
신청 목록: 노트북 / 프로젝터
이름 | 보유 | 대여 | 남음
노트북 | 2 | 1 | 1
프로젝터 | 1 | 0 | 1
회의용 스피커 | 1 | 1 | 0
태블릿 | 3 | 1 | 2
대여 가능한 총수량: 4
검색 '프로': 프로젝터
회의용 비품: 회의용 스피커
남은 수량은 차례대로 1, 1, 0, 2이므로 합계는 4다. 회의용 스피커는 한 개를 보유하지만 한 개 모두 대여 중이므로 남은 수량이 0이다. 등록 비품의 종류 수인 4와 대여 가능한 총수량인 4는 이번 자료에서 우연히 같다. 두 값은 서로 다른 의미를 갖는다.
실무에서 자주 틀리는 것
Length를 마지막 위치로 사용한다
다음 반복문은 세 요소 배열에서 위치 3까지 읽으려고 한다. 사용할 수 있는 마지막 위치는 2이므로 실행 중 배열 범위를 벗어나는 오류가 난다.
틀린 코드다.
Dim names As String() = {"노트북", "프로젝터", "태블릿"}
For i As Integer = 0 To names.Length
Console.WriteLine(names(i))
Next
고친 코드다.
Dim names As String() = {"노트북", "프로젝터", "태블릿"}
For i As Integer = 0 To names.Length - 1
Console.WriteLine(names(i))
Next
For의 끝값도 반복에 포함된다는 점을 기억한다. 길이는 요소 개수이고, 마지막 위치는 길이보다 하나 작다. 위치가 필요 없다면 For Each name As String In names로 값을 순서대로 읽는 방법도 있다.
ReDim 뒤에도 기존 이름이 남는다고 생각한다
Preserve를 생략하면 크기를 바꾸기 전의 값이 보존되지 않는다. 다음 코드에서 names(0)은 Nothing이므로 "노트북"이 출력되지 않는다.
틀린 코드다.
Dim names As String() = {"노트북", "프로젝터"}
ReDim names(2)
names(2) = "태블릿"
Console.WriteLine(names(0))
고친 코드다.
Dim names As String() = {"노트북", "프로젝터"}
ReDim Preserve names(2)
names(2) = "태블릿"
Console.WriteLine(names(0))
기존 값을 이어서 사용할 목적이라면 Preserve가 필요하다. 새로 늘어난 위치에는 별도로 값을 넣어야 한다. 보존되는 기존 위치와 새로 생긴 위치를 구분해서 확인한다.
이차원 배열의 Length로 행을 반복한다
두 행과 두 열이 있는 배열의 Length는 4다. 이를 행 수로 사용하면 존재하지 않는 행 2에 접근하게 된다.
틀린 코드다.
Dim counts As Integer(,) = {{2, 1}, {1, 0}}
For row As Integer = 0 To counts.Length - 1
Console.WriteLine(counts(row, 0))
Next
고친 코드다.
Dim counts As Integer(,) = {{2, 1}, {1, 0}}
For row As Integer = 0 To counts.GetLength(0) - 1
Console.WriteLine(counts(row, 0))
Next
행을 순회하면 GetLength(0)을, 열을 순회하면 GetLength(1)을 사용한다. 두 방향의 모든 값을 각각 처리하려면 행 반복문 안에 열 반복문을 넣을 수 있다. 이번 보고서는 열의 의미가 고정되어 있으므로 두 열을 직접 읽는다.
문자열 메서드가 원래 값을 수정한다고 생각한다
Trim의 반환값을 사용하지 않으면 name에는 공백이 있는 원래 문자열이 남는다. 대괄호를 붙여 출력하면 공백이 남았다는 사실을 확인하기 쉽다.
틀린 코드다.
Dim name As String = " 노트북 "
name.Trim()
Console.WriteLine($"[{name}]")
고친 코드다.
Dim name As String = " 노트북 "
name = name.Trim()
Console.WriteLine($"[{name}]")
고친 코드의 출력은 [노트북]이다. Replace와 ToUpperInvariant도 반환 결과를 저장하거나 다른 식에서 사용해야 한다. StringBuilder의 AppendLine은 작업 공간 자체에 내용을 추가한다는 점에서 String의 가공 메서드와 다르다.
한눈에 보기
| 목적 | 표현 | 확인할 점 |
|---|---|---|
| 이름 여러 개 저장 | Dim names As String() = {...} | 첫 위치는 0이다 |
| 기존 값을 남기며 크기 변경 | ReDim Preserve names(3) | 3은 길이가 아니라 마지막 위치다 |
| 표 형태의 수량 저장 | Dim counts As Integer(,) | 행과 열의 의미를 정한다 |
| 비품 행 수 읽기 | counts.GetLength(0) | Length는 전체 칸 수다 |
| 이름의 공백 정리 | name = name.Trim() | 반환 결과를 다시 저장한다 |
| 문자열 나누기 | text.Split(","c) | 결과는 문자열 배열이다 |
| 값을 포함한 한 줄 만들기 | $"{name}: {count}" | 중괄호 안에 식을 적는다 |
| 여러 줄의 보고서 만들기 | AppendLine과 ToString | 줄바꿈을 중복해서 붙이지 않는다 |
배열에서는 개수와 위치를 구분하고, 다차원 배열에서는 각 방향의 길이를 구분한다. 문자열에서는 가공 결과를 어디에 저장할지 확인한다. 이 세 가지를 점검하면 이번 프로그램의 저장과 출력 흐름을 따라가기 쉽다.
배열과 문자열의 세부 동작을 확인할 때는 Visual Basic 배열 문서, ReDim 문 문서, String API 문서, StringBuilder API 문서를 참고할 수 있다.
연습 문제
다음 문제는 완성 코드의 일부를 수정해서 해결한다. 지정된 변경 외의 데이터와 출력 순서는 유지한다.
- 태블릿 다음에 "웹캠"을 추가한다. 웹캠은 두 개를 보유하고 한 개를 대여 중이다. 이름 배열을 늘리는 부분과 수량 배열을 고치고, 등록 비품 수와 대여 가능한 총수량을 구한다.
- 비품별 출력 줄에서 남은 수량이 0이면 "대여 불가", 1 이상이면 "대여 가능"을 끝에 붙인다. 조건문으로 상태 문자열을 정하고, 이를 문자열 보간에 넣는다.
- 완성 코드의 관리 번호 처리에서 serialPart를 Substring(2)로 얻도록 바꾼다. 번호 부분의 출력이 어떻게 달라지는지 설명한다.
- 신청 목록을 "노트북, 프로젝터, 태블릿"으로 바꾼다. 두 요소를 직접 지정하던 출력문을 반복문과 StringBuilder로 고쳐 모든 항목을 " / "로 연결한다. 마지막 항목 뒤에는 구분자를 붙이지 않는다.
정답과 해설
비품과 수량을 함께 추가한다
기존의 ReDim과 태블릿 대입 부분을 다음 코드로 바꾼다. 마지막 위치를 4로 지정하면 이름 배열의 길이는 5가 된다.
ReDim Preserve equipment(4)
equipment(3) = "태블릿"
equipment(4) = "웹캠"
수량 배열 선언은 다음과 같이 다섯 행으로 바꾼다. 웹캠을 마지막 이름에 추가했으므로 수량도 마지막 행에 추가한다.
Dim quantities As Integer(,) = {
{2, 1},
{1, 0},
{1, 1},
{3, 1},
{2, 1}
}
웹캠의 남은 수량은 1이다. 등록 비품 수는 5종, 대여 가능한 총수량은 5가 된다. 기존 반복문은 배열의 길이와 행 수를 읽으므로 반복 횟수를 별도로 고칠 필요가 없다.
남은 수량으로 상태를 정한다
행 반복문 안에서 available을 계산한 다음 아래 상태 결정 코드를 넣는다. 기존의 비품별 AppendLine은 아래 출력문으로 바꾼다. totalAvailable을 누적하는 줄은 유지한다.
Dim status As String
If available = 0 Then
status = "대여 불가"
Else
status = "대여 가능"
End If
report.AppendLine(
$"{equipment(row)} | {owned} | {rented} | {available} | {status}")
표의 제목 줄도 "이름 | 보유 | 대여 | 남음 | 상태"로 바꾼다. 원래 네 비품 자료에서는 회의용 스피커만 "대여 불가"다. 이 풀이는 보유 수량과 대여 수량이 올바르게 주어져 남은 수량이 음수가 되지 않는다는 이번 자료의 조건을 따른다.
부분 문자열의 시작 위치를 확인한다
Dim serialPart As String = assetCode.Substring(2)
위치 2는 하이픈이므로 serialPart는 "-014"가 된다. 출력 줄은 "번호 부분: -014"로 달라진다. Substring은 시작 위치의 문자를 포함한다. 시작 위치 앞의 문자는 결과에 들어가지 않는다.
신청 항목 수만큼 출력 문자열을 만든다
신청 목록 선언을 다음과 같이 바꾼다. 기존의 Trim 반복문은 그대로 사용한다.
Dim requestItems As String() =
"노트북, 프로젝터, 태블릿".Split(","c)
기존 report.AppendLine($"신청 목록: ...") 줄을 아래 코드로 바꾼다. 제목과 관리 번호 등 다른 보고서 줄은 그대로 둔다.
Dim requestText As New StringBuilder()
For i As Integer = 0 To requestItems.Length - 1
If i > 0 Then
requestText.Append(" / ")
End If
requestText.Append(requestItems(i))
Next
report.AppendLine($"신청 목록: {requestText.ToString()}")
첫 요소 앞에는 구분자를 붙이지 않고, 두 번째 요소부터 구분자를 먼저 붙인다. 그러면 마지막 요소 뒤에 구분자가 남지 않는다. 변경된 출력 줄은 "신청 목록: 노트북 / 프로젝터 / 태블릿"이다. 항목 수가 달라져도 배열의 길이를 따라 같은 방식으로 처리한다.