[전문가를 위한 C++] 1장 : C++와 표준 라이브러리 초단기 속성 코스
1.1 C++ 기초
1.1.1 프로그래밍 언어의 공식 예제 'Hello, World!'
1. 주석
2. 모듈 임포트
import <iostream>
3. 전처리 지시자
#include <iostream>
빌드 작업 세단계
- 전처리 : 소스코드에 담긴 메타 정보 처리
- 컴파일 : 소스코드를 머신이 읽을 수 있는 객체 파일로 변환
- 링크 : 앞에서 변환한 여러 객체 파일을 애플리케이션으로 엮음
헤더 파일의 용도
- 소스파일에서 정의할 함수를 선언
- 함수의 호출 방식, 매개변수의 개수와 타입, 리턴 타입 등을 컴파일러에 알려줌
자주 사용하는 전처리 지시자
#include [파일]
- 기능 : 저장할 파일의 내용을 지시자 위에 넣는다
- 사용 예 : 다른 곳에 정의된 함수를 사용할 목적으로 해당 함수의 선언문이 담긴 헤더파일을 가져온다
#define [키] [값]
- 용도 : 코드에서 '키'에 해당하는 부분을 모두 '값'으로 지정한 내용으로 바꾼다
- 사용 예 : C에서는 주로 상수값이나 매크로를 정의하는데 사용
- c++에서 더 나은 메커니즘이 있음
#ifdef [키]
#endif
#ifndef [키]
#endif
#pragma [xyz]
ex) pragma once
- 중복 인클루드를 막는 용도
4. main() 함수
- 프로그램은 항상 main() 함수에서 시작한다
- main() 함수는 int 타입의 값을 리턴
- 리턴 문장을 생략하도 되는데, 자동으로 0을 리턴
- main() 함수는 매개변수를 받지 않거나 다음과 같이 두 개를 받도록 작성할 수 있다
int main(int argc, char* argv[])
argc는 프로그램에 전달할 인수의 개수를 지정하고 argv는 전달할 인수의 값을 담는다
argv[0] 에는 프로그램 이름이 담기는데 공백 스트림으로 지정될 수도 있어서 프로그램 이름을 참조하려면 이 값보다 플랫폼에서 제공하는 기능을 사용하는 것이 좋다
5. I/O 스트림
std::cout << "There are" << 219 << "ways I lve you." << std::endl;
std::cout << std::format("There are {} ways I love you.", 219) << std::endl;
이스케이프 시퀀스(escape sequence)
- \n : 줄바꿈. 커서를 다음 라인의 맨 앞으로 이동시킨다
- \r : 캐리지 리턴. 커서를 현재 라인의 맨 앞으로 이동시킨다
- \t : 탭
- \ : 역슬래시
endl은 스트림에 줄바꿈 문자를 추가한 뒤 현재 버퍼에 있는 내용을 출력장치로 내보낸다. endl은 성능에 영향을 미치기 때문에 루프와 같은 문장에서 남용하면 좋지 않다. 반면 \n도 스트림에 줄바꿈 문자를 추가하지만 버퍼를 자동으로 비우지 않는다.
1.1.2 네임스페이스
코드에서 이름이 서로 충돌하는 문제를 해결하기 위해 나온 개념
스코프 지정 연산자(scope resolution operator (::))
네임스페이스 블록 안에서 접근할 때는 네임스페이스 접두어를 붙이지 않아도 된다.
- 코드의 가독성 향상
using 지시자로 네임스페이스 접두어를 생략할 수도 있다.
헤더 파일 안에서는 절대로 using 문을 작성하면 안된다. 그러면 그 파일을 include 하는 모든 파일에서 using 문으로 지정한 방식으로 호출해야 한다.
1. 중첩 네임스페이스
- 다른 네임스페이스 안에 있는 네임스페이스를 말한다.
2. 네임스페이스 앨리어스
- 네임스페이스의 이름을 다르게 만들거나 또는 더 짧게 만들 수 있다.
namespace MyFTP = MyLibraries::Networking::FTP;
1.1.3 리터럴
- 리터럴은 코드에 표시한 숫자나 스트링과 같은 값을 의미한다.
1.1.4 변수
- 변수를 선언할 때 반드시 값을 대입(초기화)할 필요는 없다. 하지만 초기화하지 않은 변수는 선언할 시점의 메모리값을 기반으로 무작위 값이 대입될 수 있는데, 그냥 사용하면 버그가 발생할 수 있다.
int unitializedInt;
// 균일초기화
int initializedInt { 7 };
균일 초기화(uniform initialization)
균일 초기화는 c++11 버전부터 도입되었다. 기존 대입 문법 대신 균일 초기화를 사용하는 것이 바람직
c++은 강타입(strongly typed) 언어이다. 그러므로 항상 타입을 구체적으로 지정해야한다.
c++에서 자주 사용하는 타입
(signed) int , signed : 부호가 있는 정수(양수와 음수)를 표현하며, 값의 범위는 컴파일러마다 다르다(4바이트)
(signed) short (int) : 작은 범위의 정수(2바이트)
(signed) long (int) : 큰 범위의 정수
(signed) long long (int) : 아주 큰 범위의 정수. (8바이트)
char, unsigned char, signed char : 단일 문자
char8_t, char16_t, char32_t : 단일 n 비트 UTF n 인코딩을 적용한 유니코드 문자
char 타입은 signed char나 unsigned char와 다른 타입이다. 이 타입은 문자로 표현해야 한다. 컴파일러의 종류에 따라 signed로 처리할 수도 있고 unsigned로 처리할 수도 있음
std::byte b { 42 };
1. 숫자 경곗값
numeric_limits<int>::max();
numeric_limits<int>::min();
numeric_limits<int>::lowest();
- min()과 lowest() 가 다르다
- 정수에서는 최솟값(minimum)과 최젓값(lowest value)이 같지만, 부동소수점수에서는 최솟값은 표현 가능한 가장 작은 양의 값인 반면, 최젓값은 표현가능한 가장 작은 음수다.
2. 0 초기화
- 빈 중괄호 {}로 표기한 유니폼 초기화를 영 초기자(zero initializer)라고 한다.
float myFloat {}; int myInt {};
3. 캐스트
- 변수의 타입은 실행 중에 바꿀 수 있다. 이를 캐스트라 한다.
- 변수의 타입을 명시적으로 변환할 수 있음.
- 데이터가 손실될 수 있다는 점에 주의해야 함.
4. 부동소수점수
1.1.5 연산자
연산자 : 변숫값 변경에 사용
- 이항연산자, 단항연산자, 삼항연산자
% : 나머지 연산자, 모드(mod), 모듈로(modulo)
<<, >> : 비트값을 오른쪽에 나온 수만큼 shift tlzlsek.
1.1.6 열거 타입
강타입 열거 타입(strongly typed enumeration type)을 적용하면 변수에 지정할 수 있는 값의 범위를 엄격하게 제한
enum class PieceType { King, Queen, Rook, Pawn };
- c++20 부터는 using enum으로 선언하면 열것값을 길게 풀어 쓰지 않아도 된다.
이 기능은 조심해서 사용!!
1. 예전 타입의 열거 타입
enum PieceType { PieceTypeKing, PieceTypeQueen };
bool ok { false };
enum Status { error, ok };
- 오류 발생. 상위스코프에 같은 이름이 있으면 컴파일 에러가 발생
- 따라서 예전 방식으로 열거 타입을 정의할 때는 멤버 이름을 고유한 이름으로 지정해야 한다.
- 이처럼 예전 방식의 열거 타입을 정의할 때는 고유한 이름으로 지정해야 한다.
열거 타입을 사용할 때는 타입에 안전하지 않은, 즉 타입 언세이프한 예전 방식의 enum보다는 강타입 버전인 enum class로 작성하는 것이 좋다.
1.1.7 구조체
export module employee;
export struct Employee {
char firstInitial;
int salary;
}
import employee;
int main()
{
Employee employee;
}
1.1.8 조건문
if/else 문
- if문 안에 초기자를 넣을 수 있다.
- if(<초기자>; <조건문>)
if(Employee employee { getEmployee() }; employee.salary > 100) {
}
switch문
- switch문도 초기자를 지정할 수 있다.
1.1.9 조건연산자
- [조건] ? [동작1] : [동작 2]
1.1.10 논리연산자
c++은 논리표현식을 평가할 때 단락논ㄹ니(short-circuit logic)을 사용
평가하는 도중에 최종 결과가 나오면 나머지 부분은 평가하지 않는다.
1.1.11 3방향 비교 연산자
c++ 20에서 새로 생김
aka 우주 연산자(spaceship operator)
<=> 연산자는 주어진 표현식의 평가 결과가 비교 대상이 되는 값과 같은지 아니면 그보다 크거나 작은지 알려준다. 이 연산자는 true/false가 아니기 때문에 compare에 정의 되고 std 네임스페이스에 속한 열거 타입으로 리턴한다.
피연산자가 정수 = 강한 순서(strong ordering)
strong_ordering::less : 첫번째 피연산자가 두번째 연산자보다 작다
strong_ordering::greater
strong_ordering::equal
피연산자가 부동소수점 = partial ordering
자신이 정의한 타입에 대해 3방향 비교 연산자
weak_ordering::less
weak_ordering::greater
weak_ordering::equivalent
1.1.12 함수
- 프로그램의 가독성을 높이려면 코드를 간결하고 명확한 함수 단위로 나눠야함
- 이 파일 안에서만 사용 > 선언+구현 모두 소스파일 안에
- 다른 데서도 쓸거다 -> 모듈 인터페이스 파일로부터 익스포트하고, 정의는 모듈 인터페이스 파일이나 모듈 구현 파일에 작성
함수를 선언하는 문장 : 함수 프로토타입(function prototype) 또는 함수 헤더(function header)라 부른다. 함수의 내용을 보지 않고 그 함수에 접근하는 방식만 표현
함수의 리턴 타입을 제외한 함수 이름과 매개변수 목록을 함수 signature라 부른다.
함수의 리턴 타입 추론
auto addNumbers(int number1, int number2)
{
return number1+number2;
}
현재 함수 이름
int addNumbers()
{
cout << __func__ << std::endl;
}
함수 오버로딩
- 이름은 같지만 매개변수 구성은 다른 함수를 여러 개 제공한다는 뜻. 리턴타입만 달라서는 안된다. 매개변수의 타입이나 개수도 달라야한다.
1.1.13 어트리뷰트
- 소스코드에 벤더에서 제공하는 정보나 옵션을 추가하는 메커니즘
[[nodiscard]]
이 함수가 호출될 때 리턴값에 아무런 작업을 하지 않으면 경고 메시지를 출력함.
\[\[nodiscard]] int func()
{
return 42;
}
//cpp 20 부터
\[\[nodiscard("Some explanation")]] int func()
{
return 42;
}
[[maybe_unused]]
int func(int a, int b)
{
return 42;
}
- 컴파일 warning이 생길 수 있다.
int func(int a, \[\[maybe_unused]] int b)
{
return 42;
}
- 이 attribute는 클래스, 구조체, 비 static 데이터 멤버, 유니온, typedef, 변수 등에 지정할 수 있다.
[[noreturn]]
- 호출지점으로 다시 돌아가지 않는다. 주로 프로세스나 스레드 종류와 같이 뭔가가 끝나게 만들거나, 익셉션을 던지는 함수가 여기에 해당
\[\[noreturn]] void forceProgramTermination()
{
std::exit(1);
}
bool isFeaturedLiscensed(int featureId)
{
if(!isDongleAvailable()) {
forceProgramTermination();
}
}
[[deprecated]]
- 지원중단된 대상임을 지정하는데 사용.
- 현재 사용할 수는 있지만 권장하지 않는 대상임을 표시
[[likely]] [[unlikely]]
- c++ 20
- 컴파일러가 최적화 작업을 수행하는데 도움을 줌
1.1.14 C 스타일 배열
int myArray[3];
myArray[0] = 0;
myArray[1] = 0;
myArray[2] = 0;
int myArray[3] = {0};
int myArray[3] = {};
int myArray[3] {};
int myArray[3] = {2};
1.1.15 std::array
- c++에서 하는 것
- C 스타일의 배열에 비해 장점이 많다
- 항상 크기를 정확히 알 수 있다.
- 자동으로 포인터를 캐스트하지 않아서 특정한 종류의 버그를 방지할 수 있고, 반복자로 배열에 대한 반복문을 쉽게 작성할 수 있다.
array<int, 3> arr{9,8,7};
arr.size();
array arr{9,8,7};
// CTAD class template argument deduction(클래스 템플릿 인수 추론) : template 타입을 지정하지 않아도 됨.
1.1.16 std::vector
vector<int> myVector{11,22};
myVector.push_back(33);
myVector.push_back(44);
vector myVector{11, 12}; //OK CTAD
vector myVector; //XXXX
1.1.17 std::pair
pair<double, int> myPair {1.23, 5};
cout << myPair.first << myPair.second << std::endl;
1.1.18 std::optional
1.1.19 구조적 바인딩
- 여러 변수를 선언할 때 array, struct, pair 등에 담긴 원소들을 이용하여 변숫값을 한꺼번에 초기화할 수 있다.
array values {11,12,13};
auto [x,y,z] {values};
- 이때 그냥 auto라 쓰지 않고 auto&나 const auto&를 이용하여 구조적 바인딩으로 비 const에 대한 레퍼런스나 const에 대한 레퍼런스를 생성할 수도 있다.
| 방식 | 복사? | 원본 변경 가능? |
|---|---|---|
| auto | O | ❌ |
| auto& | ❌ | ⭕ |
| const auto& | ❌ | ❌ |
1.1.20 반복문
while
do/while문
for
범위 기반 for문
1.1.21 초기자 리스트
#include <initializer_list>
int makeSum(initializer_list<int> values)
{
}
1.1.23 클래스
클래스 이름부터 적는다.
- 데이터멤버, 메서드를 선언한다.
- 접근 수준을 지정한다.
1.1.24 스코프 지정
스코프 : 변수, 함수, 클래스명과 같이 프로그램에 나오는 모든 이름은 저마다 스코프가 있다. 스코프는 namespace, 함수정의, 등등
class Demo
{
public:
int get() {return 5;}
};
int get() {return 5;}
namespace NS
{
int get() {return 20;}
}
int main()
{
Demo d;
d.get (); //5
NS::get(); //20
::get(); //10
get(); //10
}
1.1.25 균일 초기화
- c++ 11 이전에는 타입의 초기화 방식이 일정하지 않았다.
//c++ 11이전 CircleStruct myCircle1 = {10,10,2.5}; CircleClass myCircle2(10,10,2.5);
//c++ 11 이후
CircleStruct myCircle1 = {10,10,2.5};
CircleClass myCircle2 = {10,10,2.5};
CircleClass myCircle3 {10,10,2.5};
```cpp
int* pArray = new int[4] {0,1,2,3};
균일초기화를 가능한 모든 곳에서 이용하는게 바람직
지정초기자
- c++20 부터 지정초기자가 도입
struct Employee {
char firstInitial;
char lastInitial;
int employeeNumber;
int salary {75000};
};
// 균일초기화
Employee employee {'J','D',42,80000};
// 지정초기화
Employee employee {
.firstInitial = 'J',
.lastInitial = 'D',
.salary = 80000
};
// 초기화 생략도 가능
1.1.26 포인터와 동적 메모리
스택과 프리스토어
스택 : 독립적인 공간, 실행이 끝나면 삭제됨(할당해제 할 필요없음)
스택 프레임
프리스토어 : 스택과는 다른 독립적인 메모리 공간 / 직접 할당해제 해야함
포인터 사용법
- 메모리 공간을 적당히 할당하기만 하면 어떠한 값이라도 free store에 저장 가능
- 정숫값을 프리스토어에 저장하려면 정수 타입에 맞는 메모리 공간을 할당해야함
int* myPointer;
// 초기화 필수
int* myPointer { nullptr };
- 은 이 변수가 정수 타입에 대한 메모리 공간을 가리킨다는 것을 의미
- 동적으로 할당된 프리스토어 메모리를 가리키는 화살표와 같다.
myIntegerPointer = new int;
- 정숫값 하나에 대한 메모리 주소를 가리킨다. 이 포인터가 가리키는 값에 접근하려면 포인터를 역참조(참조해제) 해야 한다. 포인터가 프리스토어에 있는 실제 값을 가리키는 화살표를 따라간다는 뜻
*myPointer = 8;
delete myPointer;
myPointer = nullptr;
포인터를 역참조하려면 반드시 메모리가 할당되어 있어야 한다.
int i{8};
int* myPointer {&i}; // 8이란 값을 가진 변수 i의 주소를 가리키는 포인터
Employee* employee{getEmployee()};
cout << (*employee).salary << endl;
// -> 연산자
cout << employee->salary << endl;
- *연산자로 역참조 해서 구조체 자체(시작지점)에 접근한 뒤 필드에 접근할 때는 . 연산자로 표기
bool isValidSalary {employee && employee->salary > 0};
동적으로 배열할당하기
int arraysize{8};
int* array{new int[arraysize]};
1.1.27 const의 다양한 용도
- 변경되면 안될 대상을 선언할 때 사용
1. const 상수
#define num 2
// 이거 대신 const 사용
const int num {2};
define은 전처리기(preprocessor)에서 처리하고 const는 컴파일러가 처리.
define문은 코드를 메타 수준으로 처리해서 단순히 텍스트 매칭 작업
const는 컴파일러가 평가. 타입이나 스코프를 적용할 수 있다는 장점
const 포인터
int* ip;
ip = new int[10];
ip[4] = 5;
const int* ip;
ip = new int[10];
ip[4] = 5; // 컴파일 에러 발생
int* const ip{nullptr};
ip = new int[10]; // 컴파일 에러
ip[4] = 5; // 에러 : 널포인터 역참조
const키워드는 바로 왼쪽에 나온 대상에 적용된다.
int const* const ip {nullptr};
// ==
const int* const ip {nullptr};
int* const ip; // ip는 int에 대한 포인터에 const
int const* ip; // ip는 const가 적용된 int에 대한 포인터
const 매개변수
- 함수 내에서 매개변수 값을 변경할 수 없도록
2. const 메서드
- 클래스 메서드에도 const를 지정할 수 있다.
- 해당 클래스의 데이터 멤버를 수정할 수 없게 만든다.
- const 메서드 중에서 어느하나라도 데이터멤버를 수정하려고 하면 컴파일 에러가 발생
export class AirlineTicket
{
public:
double calculatePriceInDollors() const;
}
1.1.28 constexpr 키워드
- 상수표현식 : 컴파일시간에 평가되는 표현식
contexpr int getArraySize() {return 32;}
int main()
{
int arr[getArraySize()];
}
1.1.29 consteval 키워드
- constexpr : 함수가 컴파일 시간에 실행될 수 있다고 지정, 보장 X
- 함수가 항상 컴파일 시간에 평가되도록 보장 : c++ 20부터 제공하는 consteval 키워드
1.1.30 레퍼런스
- 레퍼런스(참조)란 변수에 대한 alias다.
- 레퍼런스에 대해 수정한 내용은 그 레퍼런스가 가리키는 변수의 값에 그대로 반영
- 변수의 주소를 가져오거나 변수에 대한 역참조 연산을 수행하는 작업을 자동으로 처리해주는 특수한 포인터
- 또는 변수에 대한 다른 이름(별칭)
레퍼런스 변수
반드시 생성 즉시 초기화
int x {3};
int& xRef {x};
//값 변경
xRef = 10;
레퍼런스 대상 변경
레퍼런스를 초기화하고 나면 그 레퍼런스가 가리키는 대상을 다른 변수로 변경할 수 없고, 그 레퍼런스가 가리키는 변수의 값만 바꿀 수 있다.
int x {3}, y{4};
int& xRef {x};
xRef = y; // xRef가 가리키는 대상이 y로 변경되지 않고 x의 값이 4로 바뀐다.
xRef = &y; // 컴파일 에러
// y의 주소는 포인터이지만 xRef 는 포인터에 대한 레퍼런스가 아닌 int에 대한 레퍼런ㅅ느 이기 때문이다
const 레퍼런스
- 포인터에 const보다 쉬움
- 레퍼런스는 가리키는 대상을 변경할 수 없기 때문에 기본적으로 const. 명시적으로 지정할 필요가 없다.
- 레퍼런스에 대한 레퍼런스를 만들 수 없기 때문에 참조가 한단계 뿐이다.
int z;
const int& zRef {z};
zRef = 4;
- int&에 const를 지정하면 zRef에 다른 값을 대입할 수 없다.
- 하지만 zRef를 const라고 지정한 것은 z에 영향을 미치지 않는다. 바로 z에 접근하면 값 변경 가능
int& unnamedRef1 {5}; // 컴파일 에러
const int& unnamedRef2 {5}; // 정상 작동
포인터에 대한 레퍼런스와 레퍼런스에 대한 포인터
int* intP {nullptr};
int*& ptrRef {intP};
ptrRef = new int;
*ptrRef = 5;
int*& = 포인터 변수 자체를 수정할 수 있게 해주는 참조
- ptrRef는 intP에 대한 reference
- intP는 int에 대한 포인터
초기:intP → null ptrRef → intP
↓ptrRef = new int;intP → heap int ptrRef → same
↓*ptrRef = 5;heap int = 5
int x{3};
int& xRef {x};
int* xPtr {&xRef};
*xPtr = 100;
x에 대한 레퍼런스의 주소를 가져와서 xPtr이 x를 가리키도록 설정
*xPtr에 100을 대입하면 x의 값이 100으로 바뀐다
구조적 바인딩과 레퍼런스
auto& [string, int] {pair}; // 비 const 레퍼런스로 분해
const auto& [string, int] {myPair}; // const 레퍼런스로 분해
2. 레퍼런스 데이터 멤버
- 레퍼런스 데이터 멤버는 반드시 생성자의 본문이 아닌 생성자 초기자에서 초기화
3. 레퍼런스 매개변수
pass by reference.
const 레퍼런스 전달 방식
- 성능이 더 좋아짐
레퍼런스 전달 방식과 값 전달 방식
레퍼런스 전달 방식의 장점
- 큰 객체를 복제하는데 시간이 오래 걸릴 수 있다. 하지만 레퍼런스 전달 방식은 객체에 대한 레퍼런스만 함수에 전달
- 지원 : 값 전달 방식을 허용하지 않는 클래스가 있다.
4. 레퍼런스 리턴값
- 함수나 메서드의 리턴값도 레퍼런스 타입으로 지정할 수 있다. 물론 함수 종료 후에도 계속 남아있는 객체에 대해서만 레퍼런스로 리턴할 수 있다.
5. 레퍼런스와 포인터의 선택 기준
포인터보다 레퍼런스를 사용하는 것이 좋다. 레퍼런스로 충분하지 않을 때만 포인터를 사용한다.
1.1.31 const_cast
- const 속성을 추가하거나 제거하는 다섯가지 캐스트 방법
1.1.32 익셉션
1.1.33 타입 Alias
- 기존에 선언된 타입에 다른 이름을 붙이는 것
using IntPtr = int*;
1.1.34 typedef
- type alias 와 마찬가지로 기존에 선언된 타입에 다른 이름을 붙여줌.
typedef int* IntPtr;
1.1.35 타입 추론
1. auto 키워드
- 함수의 리턴 타입 추론
- 구조적 바인딩 사용
auto&
- auto를 표현식 타입을 추론하는데 사용하면 reference와 const가 제거된다.
auto를 지정하면 reference와 const 지정자가 사라져서 값이 복제된다. 복제 방식으로 전달되지 않게 하려면 auto&나 const auto&로 지정한다.
auto*
2. decltype 키워드
int x {123};
decltype(x) y {456};
- 인수로 전달한 표현식의 타입을 알아낸다
1.4 연습문제
'C++' 카테고리의 다른 글
| [전문가를 위한 C++] 6장 : 재사용을 고려한 설계 (0) | 2026.02.23 |
|---|---|
| [전문가를 위한 C++] 5장 : 객체지향 설계 (0) | 2026.02.23 |
| [전문가를 위한 C++] 4장 : 전문가답게 C++ 프로그램 설계하기 (0) | 2026.02.23 |
| [전문가를 위한 C++] 3장 : 코딩 스타일 (0) | 2026.02.23 |
| [전문가를 위한 C++] 2장 : 스트링과 스트링 뷰 다루기 (0) | 2026.02.23 |



