요약
텍스트는 프로그램에서 가장 흔한 데이터다. 이름, 메시지, 파일의 줄, 사용자의 명령이 모두 텍스트다. 이 수업은 텍스트를 C++에서의 본모습, 곧 고유한 연산을 가진 문자의 시퀀스로 다룬다. 먼저 std::string 타입을 소개하는데, 이 타입은 텍스트용 char 벡터처럼 만들고, 연결하고, 비교하고, 순회한다. 이어서 그 검색과 추출 연산(find, substr, starts_with, contains)과, find 함수가 아무것도 찾지 못했다고 알리는 값인 std::string::npos 값을 다룬다. 또 텍스트를 복사하지 않고 관찰하는 뷰인 std::string_view 타입과, 뷰가 의존하는 조건, 곧 관찰되는 텍스트가 계속 존재해야 한다는 조건을 다룬다. 끝으로 남의 코드를 읽기 위한 C 문자열과, C++가 단어를 비교하고 정렬하는 순서를 다룬다.
학습 목표
std::string타입의 연산(size,+,find,substr,starts_with,contains)을 사용해 텍스트 문제를 푼다.find함수의 결과를 해석하여std::string::npos값과 비교하고, 그 결과를 아무 수처럼 다루면 무슨 일이 일어나는지 예측한다.- 함수에서 텍스트를
std::string_view타입으로 받고, 뷰가 자신이 관찰하는 문자열보다 오래 살아서는 안 되는 이유를 예를 들어 설명한다. - C 문자열을 쓰는 코드를 읽고
std::string타입과의 차이, 곧 널 종료 문자, 고정된 크기, 컴파일되지 않는 두 리터럴의 합을 설명한다.
문자열 std::string
만들기, 연결하기, 순회하기
문자, 불리언, 형 변환, 상수 수업은 문자 char 타입을 표가 글자로 바꿔 주는 작은 수로 소개했다. 텍스트는 문자들의 나열이다. 그렇다면 텍스트는 std::vector<char> 타입에 저장할 수 있고, 벡터 수업이 시퀀스에 대해 가르친 내용이 텍스트에도 그대로 통할 것이다. 그런데 텍스트에는 아무 시퀀스에나 요구하지 않는 연산이 요구된다. 다른 텍스트를 이어 붙이고, 그 안에서 단어를 찾고, 한 부분을 떼어 내고, 한 번에 출력하는 일이다. 여기서 전용 타입인 문자열, 곧 <string> 헤더의 std::string 타입이 나온다. 이것은 텍스트용 char 벡터다.
#include <format>#include <print>#include <string>int main(){ const std::string first{"Ada"}; std::string greeting{"Hola, " + first}; greeting += '.'; std::println("{} (문자 {}개)", greeting, greeting.size()); std::println("첫 문자 “{}”, 마지막 문자 “{}”", greeting[0], greeting.back()); int spaces{0}; for (const char c : greeting) { if (c == ' ') { ++spaces; } } std::println("공백: {}", spaces); const std::string code{std::format("{}-{:03}", "MAT", 7)}; std::println("{} {}", code, code == "MAT-007");}Hola, Ada. (문자 10개)첫 문자 “H”, 마지막 문자 “.”공백: 1MAT-007 true7번 줄은 리터럴로 문자열을 만든다. 8번 줄은 그 문자열을 스페인어 인사말 “Hola, ”(“안녕, ”)와 연결한다. 문자열과 리터럴 사이의 + 연산자는 두 쪽의 문자를 차례로 이어 놓은 새 문자열을 만든다. 9번 줄은 += 연산자로 끝에 문자 하나를 덧붙이는데, 이 연산자는 문자열이나 리터럴도 받는다. 나머지는 벡터에 대해 이미 아는 내용이다. size() 함수는 문자를 세고(10개다. 쉼표, 공백, 마침표도 문자이기 때문이다), greeting[0] 원소가 첫 문자, back() 함수의 결과가 마지막 문자이며, 13번 줄부터 17번 줄까지의 반복문은 반복문 수업의 범위 기반 형식으로 문자열을 한 문자씩 순회한다. 벡터처럼 문자열도 push_back, pop_back, empty 함수를 쓸 수 있고, GCC 15의 라이브러리는 최적화하지 않을 때 operator[] 연산자에서 인덱스를 검사한다(최적화하면 이 검사는 사라진다).
마지막 두 줄은 함수로 설계하기 수업이 예고한 내용을 실현한다. <format> 헤더의 std::format 함수는 std::println 함수와 같은 자리표시자와 같은 형식 지정을 받지만({:03} 지정은 세 자리가 될 때까지 0으로 채운다), 결과를 출력하는 대신 문자열로 반환한다. 나중에 저장하거나 비교하거나 함수에 넘길 텍스트를 만드는 방법이 이것이다. 그리고 == 연산자는 두 문자열을 내용으로 비교한다. 크기가 같고 같은 문자가 같은 순서로 있으면 두 문자열은 같다. 따라서 code == "MAT-007" 식의 값은 true 값이다. 왼쪽 텍스트는 여러 부분으로 만들어졌고 오른쪽 텍스트는 리터럴인데도 그렇다.
비교
문자열은 == 연산자와 != 연산자 말고도 <, <=, >, >= 연산자를 쓸 수 있으며, 이들은 문자열을 사전식 순서, 곧 사전의 순서로 비교한다. 두 문자열을 나란히 훑어 가다가, 처음으로 다른 위치에서 더 작은 문자가 결정한다. 아무 차이 없이 한쪽이 먼저 끝나면 더 짧은 쪽이 더 작다. 그래서 스페인어 단어 “casa”는 “cosa”보다 작다. 둘째 위치에서 'a' < 'o' 관계가 성립하기 때문이다. 또 “mar”는 “marea”보다 작다. 앞의 것이 뒤의 것의 접두부이기 때문이다. 이 연산자들은 문자열을 비교한다. 따옴표로 쓴 두 리터럴 사이에서는 다른 것을 비교하게 되는데, 리터럴은 std::string 타입이 아니라 배열이기 때문이며, 이는 C 문자열에 관한 절에서 보게 된다. 어느 문자가 “더 작은지”는 알파벳이 아니라 그 문자의 코드가 결정한다. 거기서 무엇이 따라 나오는지는 이 수업의 마지막 절이 보여 준다.
콘텐츠를 불러오는 중…