개인 기록용
-------------------
학습에 참고한 사이트 : learncpp.com

학습한 챕터 : 챕터 5

학습에 참고한 번역 프롬프트 : 해당 원문은 learncpp.com에서 발췌해온 c++ 강의 내용인데, 프로그래밍 용어는 한국어 표준 용어를 사용하되, 이해를 돕기 위해 필요한 경우 원문을 병기해서 한국어로 번역해줘.

-------------------
상수 (Constants)
  • 프로그래밍에서 상수(constant)는 프로그램 실행 중에 변경할 수 없는 값을 의미합니다.
  • C++는 두 가지 유형의 상수를 지원합니다.


이름 있는 상수 (Named constants)

  • 식별자(identifier)와 연결된 상수 값입니다. 때때로 심볼릭 상수(symbolic constants)라고도 불립니다.


리터럴 상수 (Literal constants)

  • 식별자와 연결되지 않은 상수 값입니다.


이름 있는 상수의 종류

C++에서 이름 있는 상수를 정의하는 방법은 세 가지가 있습니다.


  • 상수 변수 (Constant variables)
  • 치환 텍스트가 있는 객체 유사 매크로
  • 열거형 상수


상수 변수가 가장 흔하게 사용되는 이름 있는 상수입니다.


상수 변수 (Constant variables)

  • 상수 변수(constant variable)는 초기화된 후에는 값을 변경할 수 없는 변수를 말합니다. 


const 변수 선언하기

  • 상수 변수를 선언하려면 객체의 타입 옆에 const 키워드를 붙이면 됩니다.
  • Const 변수는 정의할 때 반드시 초기화해야 하며, 그 이후에는 대입을 통해 값을 변경할 수 없습니다.
  • 함수 매개변수도 const 키워드를 사용하여 상수로 만들 수 있습니다.
  • 함수 매개변수를 상수로 만들면 함수 내부에서 매개변수의 값이 변경되지 않도록 컴파일러의 도움을 받을 수 있습니다. 하지만 현대 C++에서는 값 매개변수를 굳이 const로 만들지 않습니다. 왜냐하면 함수가 매개변수의 값을 바꾸더라도 호출자에게는 영향을 주지 않기 때문입니다(함수 종료 시 파괴되는 복사본일 뿐이니까요).
  • 함수의 반환값)의 경우에도, 값으로 const 객체를 반환하는 것은 별 의미가 없습니다. 어차피 파괴될 임시 복사본이기 때문입니다. 또한 const 값을 반환하면 이동 시맨틱(move semantics)과 관련된 특정 컴파일러 최적화를 방해하여 성능 저하를 일으킬 수 있습니다.


변수를 상수로 만들어야 하는 이유

  • 버그 발생 가능성을 줄입니다.
  • 컴파일러 최적화 기회를 제공합니다.
  •  프로그램의 전체적인 복잡성을 줄여줍니다. 코드를 분석하거나 디버깅할 때, const 변수는 값이 변하지 않는다는 것을 알기 때문에 값이 실제로 변하는지, 어떤 값으로 변하는지, 그 값이 올바른지 걱정할 필요가 없습니다.


치환 텍스트가 있는 객체형 매크로 (Object-like macros)

  • 챕터 2에서 치환 텍스트가 있는 객체형 매크로에 대해 논의했습니다.


#include <iostream>

#define MY_NAME "Alex"

int main()

{

    std::cout << "My name is: " << MY_NAME << '\n';

    return 0;

}


전처리기가 이 코드가 포함된 파일을 처리할 때, MY_NAME(7번째 줄)을 "Alex"로 치환합니다.

MY_NAME은 이름이고 치환 텍스트는 상수 값이므로, 이는 이름 있는 상수(named constants)의 한 형태입니다.


그렇다면 왜 이름 있는 상수로 치환 텍스트가 있는 객체형 매크로를 사용하면 안 될까요?

적어도 세 가지의 큰 문제가 있습니다.


  1. 가장 큰 문제는 매크로가 일반적인 C++ 스코프(scoping) 규칙을 따르지 않는다는 점입니다. 매크로가 한 번 #defined 되면, 해당 파일의 나머지 부분에서 나오는 모든 매크로 이름이 치환됩니다. 다른 곳에서 같은 이름을 변수명 등으로 사용하고 있다면 원치 않는 치환이 발생하여 이상한 컴파일 오류로 이어질 수 있습니다.
  2. 두 번째로, 매크로를 사용한 코드는 디버깅하기가 더 어렵습니다.
  3. 세 번째로, 매크로 치환은 C++의 다른 문법들과 다르게 동작하므로 부주의한 실수를 하기가 쉽습니다.


타입 한정자 (Type qualifiers)

  • 타입 한정자(type qualifier)(줄여서 한정자)는 타입에 적용되어 그 타입의 동작 방식을 수정하는 키워드입니다.
  • const가 바꾸는 것: “수정 가능 여부”
  • 상수 변수를 선언할 때 사용하는 const를 const 타입 한정자(const type qualifier) (줄여서 const 한정자)라고 합니다.
  • C++23 기준으로, C++에는 두 가지 타입 한정자만 존재합니다. constvolatile


선택적 읽기 (Optional reading)

  • volatile 한정자는 객체의 값이 언제든지(컴파일러가 예측할 수 없는 방식으로) 변경될 수 있음을 컴파일러에게 알리는 데 사용됩니다.
  • 거의 사용되지 않는 이 한정자는 특정 유형의 최적화를 비활성화합니다.
  • 기술 문서에서는 const와 volatile 한정자를 종종 cv-qualifiers(cv-한정자)라고 부릅니다.


  • cv-unqualified type (cv-비한정 타입):
  • 타입 한정자가 없는 타입
  • 예: int


  • cv-qualified type (cv-한정 타입)
  • 하나 이상의 타입 한정자가 적용된 타입
  • 예: const int


  • possibly cv-qualified type (잠재적 cv-한정 타입)
  • cv-비한정이거나 cv-한정일 수 있는 타입


리터럴 접미사 (Literal suffixes)

  • 리터럴의 기본 타입이 원하는 타입이 아니라면, 접미사(suffix)를 추가하여 리터럴의 타입을 변경할 수 있습니다.
  • 다음은 자주 사용되는 접미사들입니다:

7ded8468f5dc3f8650bbd58b36867c69e529


문자열 리터럴 (String literals)

  • 프로그래밍에서 문자열(string)은 텍스트(이름, 단어, 문장 등)를 표현하는 데 사용되는 순차적인 문자의 집합입니다.
  • C 언어로부터 물려받았기 때문에 종종 C 문자열(C strings) 또는 C 스타일 문자열(C-style strings)이라고 불립니다.
  • 모든 C 스타일 문자열 리터럴에는 암시적인 널 종결자(implicit null terminator)가 있습니다.
  • C 스타일 문자열 리터럴은 프로그램 시작 시 생성되어 프로그램 전체 실행 기간 동안 존재함이 보장되는 const 객체입니다.


  • C 스타일 문자열 리터럴과 달리, std::stringstd::string_view 리터럴은 임시 객체(temporary objects)를 생성합니다.
  • 이 임시 객체들은 생성된 전체 표현식(full expression)이 끝날 때 파괴되므로 즉시 사용되어야 합니다.


매직 넘버 (Magic numbers)

  • 매직 넘버(Magic number)는 의미가 불분명하거나 나중에 변경될 필요가 있는 리터럴(주로 숫자)을 말합니다.
  • 매직 넘버를 사용하는 것은 일반적으로 나쁜 관행으로 간주됩니다. 


8진수(Octal)와 16진수(Hexadecimal) 리터럴

  • 8진수 리터럴(literal)을 사용하려면 리터럴 앞에 0(숫자 0)을 접두사로 붙여야 합니다.
  • int x{ 012 };
  • 8진수는 거의 사용되지 않으므로, 사용을 피하는 것을 권장합니다.


  •  16진수 리터럴을 사용하려면 리터럴 앞에 0x를 접두사로 붙여야 합니다.
  • int x{ 0xF };


2진수 표현에 16진수 사용하기 (Using hexadecimal to represent binary)

  • 16진수 한 자리는 16개의 서로 다른 값을 가질 수 있으므로, 16진수 한 바퀴는 4비트(1111)를 포괄한다고 말할 수 있습니다.
  • 결과적으로, 16진수 두 자리 쌍을 사용하면 1바이트(8비트)를 정확하게 표현할 수 있습니다.


7ded8568f5dc3f8650bbd58b3680756c7987


  • 2진수 값 0011 1010 0111 1111 1001 1000 0010 0110을 가진 32비트 정수를 생각해 봅시다.
  • 길이와 숫자의 반복 때문에 읽기가 쉽지 않습니다.


  • 16진수로는 이 값을 3A7F 9826으로 표현할 수 있으며, 이는 훨씬 간결합니다.
  • 이러한 이유로 16진수 값은 메모리 주소나 (데이터 타입을 알 수 없는) 메모리 상의 원시 데이터(raw data)를 표현하는 데 자주 사용됩니다.


2진수 리터럴 (Binary literals)

  • C++14 이전에는 2진수 리터럴에 대한 지원이 없었습니다.
  • 하지만 16진수 리터럴이 유용한 대안을 제공했습니다.

7ded8668f5dc3f8650bbd58b3688706929be


  • C++14부터는 0b 접두사를 사용하여 직접 2진수 리터럴을 사용할 수 있습니다.


7ded8768f5dc3f8650bbd58b3685766594a6


자릿수 구분자 (Digit separators)

  • 긴 리터럴은 읽기 어려울 수 있기 때문에, C++14에서는 작은따옴표(')를 자릿수 구분자로 사용하는 기능이 추가되었습니다.
  • 자릿수 구분자는 순수하게 시각적인 용도이며 리터럴 값에는 아무런 영향을 주지 않습니다.
  • int bin { 0b1011'0010 };
  • long value { 2'132'673'462 };


10진수, 8진수, 16진수로 값 출력하기

  • 기본적으로 C++은 값을 10진수로 출력합니다. 하지만 std::dec, std::oct, std::hex 입출력 조정자(I/O manipulator)를 사용하여 출력 형식을 변경할 수 있습니다.
  • std::dec : 10진수
  • std::oct : 8진수
  • std::hex : 16진수
  • 한 번 적용된 입출력 조정자는 다시 변경될 때까지 향후 출력에 계속 적용된다는 점에 유의하세요.

7ded8868f5dc3f8650bbd58b3687776a3671


2진수로 값 출력하기

  • std::cout에는 2진수 출력 기능이 내장되어 있지 않기 때문에, 2진수로 값을 출력하는 것은 조금 더 어렵습니다.
  • 다행히 C++ 표준 라이브러리에는 이를 대신해 줄 std::bitset이라는 타입이 있습니다(bitset 헤더에 포함).


  • std::bitset을 사용하려면 std::bitset 변수를 정의하고 저장할 비트 수를 알려주어야 합니다.
  • 비트 수는 반드시 컴파일 시간 상수(compile-time constant)여야 합니다.
  • “컴파일 시간 상수(compile-time constant)”는 프로그램을 실행하기 전, 즉 컴파일(번역)하는 순간에 값이 이미 확정되어 있는 상수를 의미합니다. 
  • std::bitset은 정수형 값(10진수, 8진수, 16진수, 2진수 등 모든 형식 포함)으로 초기화할 수 있습니다.

7ded8968f5dc3f8650bbd58b3680746de4409f


Format / Print 라이브러리를 사용해 2진수 출력하기 (심화)

  • C++20과 C++23에서는 새로운 Format 라이브러리(C++20)와 Print 라이브러리(C++23)를 통해 2진수를 출력하는 더 좋은 옵션을 제공합니다.
  • #include <format> // C++20
  • #include <print> // C++23

7dec8068f5dc3f8650bbd58b36867265c3ae


최적화 (Optimization) 소개

  • 프로그래밍에서 최적화(optimization)는 소프트웨어가 더 효율적으로 작동하도록(예: 더 빠르게 실행되거나 더 적은 리소스를 사용하도록) 수정하는 과정을 말합니다.


  • 어떤 종류의 최적화는 일반적으로 수작업으로 이루어집니다.
  • 프로파일러(profiler)라고 불리는 프로그램을 사용하여 프로그램의 다양한 부분이 실행되는 데 걸리는 시간을 확인하고, 어떤 부분이 전체 성능에 영향을 미치는지 파악할 수 있습니다.
  • 그러면 프로그래머는 이러한 성능 문제를 완화할 방법을 찾을 수 있습니다.
  • 수동 최적화는 시간이 오래 걸리기 때문에, 프로그래머들은 보통 큰 영향을 미치는 고수준의 개선(더 성능이 좋은 알고리즘 선택, 데이터 저장 및 접근 방식 최적화, 리소스 사용량 감소, 작업 병렬화 등)에 집중합니다.


  • 다른 종류의 최적화는 자동으로 수행될 수 있습니다.
  • 다른 프로그램을 최적화하는 프로그램을 최적화기(optimizer, 옵티마이저)라고 합니다.


  • 현대 C++ 컴파일러들은 최적화 컴파일러(optimizing compiler)입니다.
  • 즉, 컴파일 과정의 일부로 프로그램을 자동으로 최적화할 수 있는 능력이 있습니다.
  • 전처리기(preprocessor)와 마찬가지로, 이러한 최적화는 소스 코드 파일을 직접 수정하는 것이 아니라, 컴파일 과정의 일부로서 투명하게 적용됩니다.


as-if 규칙 (The as-if rule)

  • C++에서 컴파일러는 프로그램을 최적화할 수 있는 많은 재량권을 가집니다.
  • as-if 규칙은 컴파일러가 프로그램의 "관찰 가능한 동작(observable behavior)"에 영향을 주지 않는 한, 더 최적화된 코드를 생성하기 위해 프로그램을 원하는 대로 수정할 수 있다는 규칙입니다.


컴파일 시간 평가 (Compile-time evaluation)

  • 현대 C++ 컴파일러는 특정 표현식을 (런타임이 아닌) 컴파일 시간(compile-time)에 완전히 또는 부분적으로 평가할 수 있는 능력이 있습니다.
  • 컴파일러가 표현식을 컴파일 시간에 완전히 또는 부분적으로 평가하는 것을 컴파일 시간 평가(compile-time evaluation)라고 합니다.


상수 접기 (Constant folding)

  • 상수 접기(Constant folding)는 컴파일러가 리터럴 피연산자를 가진 표현식을 그 결과값으로 대체하는 최적화 기법입니다.
  • 상수 접기는 전체 표현식이 런타임에 실행되어야 하는 경우라도, 부분 표현식(subexpression)에 적용될 수 있습니다.

7dec8168f5dc3f8650bbd58b3680716edaa1

  • 위 예제에서 3 + 4는 전체 표현식 std::cout << 3 + 4 << '\n';의 부분 표현식입니다. 컴파일러는 이를 std::cout << 7 << '\n';으로 최적화할 수 있습니다.


상수 전파 (Constant propagation)

  • 상수 전파(Constant propagation)는 컴파일러가 상수 값을 갖는 것으로 알려진 변수를 그 값으로 대체하는 최적화 기법입니다.
  • 상수 전파는 그 결과를 다시 상수 접기로 최적화할 수 있는 형태로 만들기도 합니다.


죽은 코드 제거 (Dead code elimination)

  • 죽은 코드 제거(Dead code elimination)는 실행될 수는 있지만 프로그램의 동작에 아무런 영향을 미치지 않는 코드를 컴파일러가 제거하는 최적화 기법입니다.
  • 변수가 더 이상 필요하지 않아 프로그램에서 제거되었을 때, 우리는 그 변수가 최적화로 제거되었다(optimized out 또는 optimized away)고 말합니다.


Const 변수는 최적화하기 더 쉽습니다

  • 비상수 변수의 경우 상수 전파 최적화 기법을 적용하기 위해 컴파일러는 비상수 변수의 값이 실제로 변경되지 않았다는 것을 파악해야 합니다.
  • 컴파일러가 이를 수행할 수 있는지 여부는 프로그램의 복잡도와 컴파일러 최적화 루틴의 정교함에 달려 있습니다.
  • 가능한 한 상수 변수(constant variable)를 사용함으로써 우리는 컴파일러가 더 효과적으로 최적화하도록 도울 수 있습니다.


용어 정리: 컴파일 시간 상수 vs 런타임 상수

  • C++에서 상수는 때때로 비공식적인 두 가지 범주로 나뉩니다.
  • 컴파일 시간 상수(compile-time constant)
  • 런타임 상수(runtime constant)


  • 컴파일 시간 상수(compile-time constant)는 컴파일 시간에 그 값을 알 수 있는 상수입니다.
  • 리터럴 (Literals)
  • 초기화 식(initializer)이 컴파일 시간 상수인 상수 객체


  • 런타임 상수(runtime constant)는 런타임 컨텍스트에서 값이 결정되는 상수입니다.
  • 상수 함수 매개변수
  • 초기화 식이 비상수이거나 런타임 상수인 상수 객체

7dec8268f5dc3f8650bbd58b3680746b0f983e


  • 실전에서 이러한 용어들을 접하게 되겠지만, C++에서 이 정의들은 그리 유용하지 않습니다.
  • 어떤 런타임 상수(심지어 비상수 변수까지도)는 최적화 목적을 위해 컴파일 시간에 평가될 수 있습니다 (as-if 규칙에 따라).
  • 어떤 컴파일 시간 상수는 컴파일 시간 기능(compile-time features)에 사용될 수 없습니다.


상수 표현식 (Constant expressions)

  • C++ 표준은 "컴파일 타임"이라는 단어를 거의 언급하지 않습니다.
  • 대신 표준은 "상수 표현식(constant expression)"을 정의합니다.
  • 이는 반드시 컴파일 타임에 평가 가능해야 하는 표현식을 말합니다.


  • 상수 표현식이 아닌 표현식은 종종 비상수 표현식(non-constant expression)이라 불립니다.
  • 비공식적으로는 런타임 표현식(runtime expression)이라고도 합니다


가장 일반적으로, 상수 표현식은 다음을 포함합니다.

  • 리터럴 (예: 5,1.2)
  • 상수 표현식으로 표현 가능한 피연산자를 가진 대부분의 연산자 (예: 3+4, 2*sizeof(int))
  • 상수 표현식으로 초기화된 const 정수형 변수 (예: const int x { 5 };). 이는 역사적인 예외이며, 현대 C++에서는 constexpr 변수가 선호됩니다.
  • constexpr 변수
  • 상수 표현식 인수를 가진 constexpr


다음은 상수 표현식에 사용될 수 없습니다.

  • 비상수 변수
  • 상수 표현식 초기화식을 가졌더라도, const 비정수형 변수 (예: const double d { 1.2 };)
  • constexpr이 아닌 함수의 반환값
  • 함수 매개변수 (함수가 constexpr이라도 안 됨).


  • 상수 표현식이 아닌 피연산자를 가진 연산자
  • 예: x나 y가 상수 표현식이 아닐 때 x + y
  • 예: std::cout이 상수 표현식이 아니므로 std::cout << "hello\n"


  • 연산자 new, delete, throw, typeid, 그리고 operator, (쉼표 연산자).
  • 위의 요소 중 하나라도 포함하는 표현식은 런타임 표현식입니다.


대략적인 요약

  • const: “이 *이름(객체)*을 통해 값을 바꾸지 못하게 하겠다” (불변성/읽기 전용)
  • 상수 표현식(constant expression): “컴파일 타임에 값이 확정될 수 있는 식”
  • constexpr: “이 변수/함수는 상수 표현식으로 쓰일 수 있게(컴파일 타임 평가 가능하게) 만들겠다


구분의미값이 컴파일 타임에 확정?대표 용도
const수정 금지(불변)❌일 수도 / ✅일 수도API 안정성, const-correctness
상수 표현식컴파일 타임에 계산 가능한 “식”배열 크기, 템플릿 인자, case, static_assert
constexpr상수 표현식이 될 수 있게 강제/보장✅(변수는 강제)컴파일 타임 계산, 성능/안전성, 템플릿



컴파일 타임 const의 문제점 (The compile-time const challenge)

  • 초기값이 상수 표현식인 정수형 const 변수는 상수 표현식에서 사용할 수 있습니다.
  • 그 외의 모든 const 변수는 상수 표현식에서 사용할 수 없습니다.
  • 하지만 const를 사용하여 상수 표현식용 변수를 만드는 데는 몇 가지 문제가 있습니다.


  1. const를 사용하는 것만으로는 해당 변수가 상수 표현식에서 사용 가능한지 즉각적으로 알기 어렵습니다.

    const int d { someVar }; // d가 상수 표현식에 사용 가능한지 불분명함
    const int e { getValue() }; // e가 상수 표현식에 사용 가능한지 불분명함

  2. const는 컴파일러에게 "이 변수는 반드시 상수 표현식에서 사용 가능해야 한다"고 알리는(그리고 그렇지 않을 경우 컴파일을 중단시키는) 방법을 제공하지 않습니다.
    대신, 조건이 맞지 않으면 조용히 런타임(runtime) 표현식에서만 쓸 수 있는 변수를 생성해 버립니다.

  3. 컴파일 타임 상수 변수를 만들기 위해 const를 사용하는 것은 정수형이 아닌 변수(non-integral variables)에는 적용되지 않습니다. 하지만 정수형이 아닌 변수도 컴파일 타임 상수로 만들고 싶을 때가 많습니다.


constexpr 키워드 (The constexpr keyword)

  • constexpr 변수는 항상 컴파일 타임 상수입니다.
  • 따라서 constexpr 변수는 반드시 상수 표현식으로 초기화되어야 하며, 그렇지 않으면 컴파일 오류가 발생합니다.


변수에서 const와 constexpr의 의미 비교

  • const: 객체의 값이 초기화 이후에 변경될 수 없음을 의미합니다.
  • 초기값은 컴파일 타임에 알 수도 있고, 런타임에 알 수도 있습니다.
  • const 객체는 런타임에 평가될 수도 있습니다.


  • constexpr: 객체가 상수 표현식에서 사용될 수 있음을 의미합니다.
  • 초기값은 반드시 컴파일 타임에 알려져야 합니다.
  • constexpr 객체는 컴파일 타임 또는 런타임에 평가될 수 있습니다.


  • constexpr 변수는 암시적으로 const입니다.
  • 반면 const 변수는 암시적으로 constexpr이 아닙니다
  • *상수 표현식 초기값을 가진 정수형 const 변수는 제외


  • const와 달리 constexpr은 객체의 타입(type)에 포함되지 않습니다.
  • 따라서 constexpr int로 정의된 변수의 실제 타입은 const int입니다
  • constexpr이 객체에 암시적으로 const를 부여하기 때문입니다.


모범 사례 (Best practice)

  • 초기값이 상수 표현식인 모든 상수 변수는 constexpr로 선언하십시오.
  • 초기값이 상수 표현식이 아닌(즉, 런타임 상수인) 모든 상수 변수는 const로 선언하십시오.


용어 정리 (Nomenclature recap)

용어 (Term)정의 (한국어)
컴파일 타임 상수 (Compile-time constant)컴파일 타임에 값이 반드시 알려져야 하는 값 또는 수정 불가능한 객체 (예: 리터럴, constexpr 변수).
constexpr (Constexpr)객체를 컴파일 타임 상수로 선언하는 키워드(그리고 컴파일 타임에 평가될 수 있는 함수를 선언). 비공식적으로는 “상수 표현식(constant expression)”의 약어처럼 쓰이기도 함.
상수 표현식 (Constant expression)컴파일 타임 상수컴파일 타임 평가를 지원하는 연산자/함수만 포함하는 표현식(즉, 컴파일 타임에 계산 가능한 식).
런타임 표현식 (Runtime expression)상수 표현식이 아닌 표현식(즉, 실행 중에 계산되는 식).
런타임 상수 (Runtime constant)값이 바뀌지 않는(수정 불가능한) 값/객체이지만, 컴파일 타임 상수는 아닌 것(값이 런타임에 확정되는 상수).


constexpr 함수 간단 소개 (A brief introduction to constexpr functions)

  • constexpr 함수는 상수 표현식 내에서 호출될 수 있는 함수입니다.
  • constexpr 함수가 속한 상수 표현식이 반드시 컴파일 타임에 평가되어야 하는 경우 해당 함수는 반드시 컴파일 타임에 평가되어야 합니다.


  • 그렇지 않은 경우, constexpr 함수는 컴파일 타임(조건 충족 시) 또는 런타임에 평가될 수 있습니다.
  • 컴파일 타임 실행을 위해서는 모든 인자가 상수 표현식이어야 합니다.


  • constexpr 함수를 만들기 위해서는 함수 선언 시 반환 타입 앞에 constexpr 키워드를 붙입니다.


7dec8468f5dc3f8650bbd58b368871647692


std::getline()을 사용하여 텍스트 입력받기

  • std::getline()은 두 개의 인자가 필요합니다.
  • 첫 번째는 std::cin 입니다.
  • 두 번째는 문자열 변수입니다.


#include <iostream>

#include <string> // std::string과 std::getline을 위해


int main()

{

    std::cout << "Enter your full name: ";

    std::string name{};

    std::getline(std::cin >> std::ws, name); // 텍스트 한 줄 전체를 name으로 읽음


    std::cout << "Enter your favorite color: ";

    std::string color{};

    std::getline(std::cin >> std::ws, color); // 텍스트 한 줄 전체를 color로 읽음


    std::cout << "Your name is " << name << " and your favorite color is " << color << '\n';


    return 0;

}


std::ws란 도대체 무엇인가요?

  • C++는 입력이 받아들여지는 방식을 변경하는 입력 조작자(input manipulators) 도 지원합니다.
  • std::ws 입력 조작자는 std::cin에게 추출 전에 선행 공백(leading whitespace) 을 무시하도록 지시합니다.
  • 선행 공백은 문자열의 시작 부분에 나타나는 모든 공백 문자(스페이스, 탭, 개행)를 말합니다.
  • std::ws는 호출 간에 유지되지 않으므로 각 std::getline() 호출마다 수행해야 합니다.


std::string의 길이

  • std::string에 문자가 몇 개 있는지 알고 싶다면, std::string 객체에 길이를 물어볼 수 있습니다.
  • 이를 수행하는 문법은 이전에 본 것과는 다르지만 꽤 간단합니다.


#include <iostream>

#include <string>


int main()

{

    std::string name{ "Alex" };

    std::cout << name << " has " << name.length() << " characters\n";


    return 0;

}


  • 문자열 길이를 length(name)과 같이 묻는 대신 name.length()라고 쓴다는 점을 주목하세요.
  • 이것은 std::string 내부에 중첩된 멤버 함수(member function) 라고 불리는 특별한 종류의 함수입니다.
  • length() 멤버 함수는 std::string 내부에 선언되어 있기 때문에 문서에서는 때때로 std::string::length()로 표기되기도 합니다.


  • std::string::length()는 부호 없는 정수형 값(대부분 size_t 타입)을 반환한다는 점을 유의하세요.
  • 길이를 int 변수에 할당하고 싶다면, 부호 있는/없는(signed/unsigned) 변환에 대한 컴파일러 경고를 피하기 위해 static_cast를 해야 합니다:
  • int length { static_cast<int>(name.length()) };


  • C++20에서는 std::ssize() 함수를 사용하여 std::string의 길이를 크기가 큰 부호 있는 정수형으로 얻을 수 있습니다.
  • std::cout << name << " has " << std::ssize(name) << " characters\n";


std::string 초기화는 비용이 듭니다

  • std::string이 초기화될 때마다, 초기화에 사용된 문자열의 복사본이 만들어집니다.
  • 문자열을 복사하는 것은 비용이 많이 들기 때문에, 복사 횟수를 최소화하도록 주의해야 합니다.


std::string을 값으로 전달하지 마십시오 (Do not pass std::string by value)

  • std::string이 함수에 값으로 전달(passed by value)되면, std::string 함수 매개변수는 인자로 인스턴스화되고 초기화되어야 합니다.
  • 이는 비용이 많이 드는 복사를 초래합니다.


std::string을 위한 리터럴

  • 우리는 큰따옴표 문자열 리터럴 뒤에 s 접미사를 사용하여 std::string 타입의 문자열 리터럴을 만들 수 있습니다.
  • s는 반드시 소문자여야 합니다.

7dec8568f5dc3f8650bbd58b36857769ed93


std::string_view (C++17)

  • std::string의 초기화(또는 복사) 비용이 비싸다는 문제를 해결하기 위해, C++17에서는 std::string_view를 도입했습니다.
  • std::string_view는 <string_view> 헤더에 존재 합니다.


  • std::string_view는 기존 문자열에 대해 복사본을 만들지 않고 읽기 전용(read-only) 접근을 제공합니다.
  • 읽기 전용이란 보고 있는 값을 접근하고 사용할 수는 있지만, 수정할 수는 없다는 뜻입니다.


std::string_view 리터럴 (Literals)

  • 이중 인용부호로 감싼 문자열 리터럴은 기본적으로 C 스타일 문자열 리터럴입니다.
  • 이중 인용부호 문자열 리터럴 뒤에 sv 접미사(suffix)를 사용하여 std::string_view 타입의 리터럴을 만들 수 있습니다.
  • sv는 반드시 소문자여야 합니다.


7dec8668f5dc3f8650bbd58b36877c6dede9