본문 바로가기
전문가를 위한 C++

2025/02/20

by 딴짓거리 2025. 2. 20.

반복문

 

while문

주어진 표현식이 true 인 동안 주어진 코드 블럭을 계속해서 반복

break로 즉시 탈출

continue로 즉시 while문의 처음으로가서 표현식 평가

do/while문 - 코드를 먼저 실행 한 후 표현식 평가

for 

범위기반 for - 컨테이너에 담긴 원소에 대해 반목문을 실행하는데 편함

begin(), end() 메서드가 정의된 모든 타입, 표준 라이브러리에서 제공하는 모든 컨테이너에 적용가능

 

c++20 에서는 범위기반 for문에서도 초기자를 쓸 수 있게 되었다.

 

 

초기자 리스트 <initializer_list>

그냥 vector 전달하면 되는거 아닌가? 이거 왜 필요한가 라고 생각이 들었다

 

하지만 vector타입의 매개변수는 따로 vector를 선언해주고 집어넣어야 하며, 쓸데없이heap에 할당이 되며

초기자 리스트에 비해 무거우며 느리다.

#include <iostream>
#include <initializer_list>

void printList(std::initializer_list<int> list) {
    for (int num : list) {
        std::cout << num << " ";
    }
    std::cout << std::endl;
}

int main() {
    printList({1, 2, 3, 4, 5}); // 가능
}
#include <iostream>
#include <initializer_list>

class MyClass {
public:
    MyClass(std::initializer_list<int> values) {
        for (int val : values) {
            std::cout << val << " ";
        }
        std::cout << std::endl;
    }
};

int main() {
    MyClass obj = {10, 20, 30, 40}; // 가능
}

초기자 리스트를 사용하면 이렇게 중괄호를 이용해 값을 바로 전달하는 것이 가능하다.

 

스트링

C - 문자 배열로 표현

C++ - 쉽고 안전한 스트링 타입 제공

 

클래스

객체의 특성을 정의

C++20의 경우 클래스를 정의하는 코드는 모듈 인터페이스 파일cppm (vs에선 ixx)에 작성하고

이를 구현하는 코드는 cppm에 함께 적거나 소스파일 cpp에 작성한다.

 

스코프

스코프 지정 연산자 ::

 

 

균일초기화

c++11 이전에는 타입의 초기화 방식이 일정하지 않았다.

 

struct A {};
class B {};

A a = {1, 2, 3 ...};
B b(1, 2, 3 ...);

struct와 class의 초기화 방법이 달랐다

하지만 c++11부터는 class도 {}로 초기화 할 수 있다.

그리고 둘 다 등호를 생략해도 된다.

 

다른 자료형들도 동일하게 초기화 할 수 있다.

영 초기화도 int e {}; 이런식으로 할 수 있다

 

균일 초기화를 사용하면 축소변환을 방지할 수 있다

좁은 자료형에 넓은 자료형의 데이터를 넣으면 암묵적 형 변환이 일어나서 나도 모르는 사이에 버그가 발생할 수 있는데

균일 초기화를 이용하게 되면 이런 경우 에러메시지를 출력해준다.

 

고의적으로 축소 변환을 하고 싶으면 GSL에서 제공하는 gsl::narrow_cast() 함수를 사용하면 된다.

 

지정 초기자

c++20 에서 묶음 타입의 데이터 멤버를 초기화하는 데 사용

 

묶음타입이란

- public 데이터 멤버만 가짐

- 사영자 정의 생성자나 상속된 생성자가 없음

- virtual 함수가 없음

- virtual, private, protected 베이스 클래스

가 없는 배열 타입 객체, 구조체 객체, 클래스 객체

 

지정 초기자는 점 뒤에 데이터 멤버의 이름을 적는 방식으로 표기함

반드시 데이터 멤버가 선언된 순서를 따라야 함

지정 초기자와 비 지정 초기자를 섞어 쓸 수는 없음

지정 초기자로 초기화되지 않은 데이터 멤버는 모두 디폴트값으로 초기화됨

장점

구조체에 멤버를 추가하더라도 지정 초기자를 이용한 기존 코드는 그대로 작동. 새로 추가된 데이터 멤버는 티폴트 값으로 초기화 됨

 

동적 메모리

컴파일 시간에 크기를 확정할 수 없는 데이터를 다룰 수 있다.

 

스택

현재 실행 중인 함수에 선언된 변수는 모두 최상단의 스택 프레임의 메모리 공간에 담긴다.

새로운 함수가 실행되면 새로운 스택 프레임이 올라온다.

각 함수마다 독립적인 메모리 공간을 제공

함수의 실행이 끝나면 해당 스택 프레임이 삭제된다.

 

프리스토어

완전히 독립적인 메모리 공간

함수가 끝난 후에도 그 안에서 사용하던 변수를 계속 유지하고 싶다.

스택보다 간결한 구조

프리스토어에 할당된 메모리 공간은 직접 할당 해제 해야 한다.

힙과 프리스토어는 C와 C++에서의 메모리공간을 다르게 부르는것 뿐 실제로는 같은 공간을 공유한다(하지만 분명 다른것이라고..) malloc과 new를 구분하기위해 생긴 개념적인 무언

 

포인터

프리스토어에 원하는 타입에 맞는 메모리 공간을 할당

 

nullptr 널 포인터

정상적인 포인터라면 절대로 가지지 않을 특수한 값

부울에선 false 취급

 

포인터가 가리키는 값에 접근하려면 포인터를 역참조 해야함

동적으로 할당한 메모리를 다 쓰고 나면 delete로 해제해야 함

곧바로 nullptr로 초기화하는 것이 좋다

 

c++ 구조체의 포인터를 다루는 방법

* 연산자로 역참조해서 구조체 자체에 접근 한뒤 필드에 접근할때는 . 연산자 사용

(*anEmployee).salary

anEmployee->salary

-> 화살표 연산자로 깔끔하게 표기 가능하다.

 

 

c++ 조건문에서 앞부분의 논리만으로 전체 논리의 답이 나오면 뒤는 계산하지 않는다는 사실을 기억하자

할당되지 않은 포인터에 접근하려면 에러가 나므로 단락 논리로 이를 쉽게 방지 할 수 있다

 

배열을 동적으로 할당할 떄도 프리스토어를 활용한다. new[] 연산자 사용

포인터 변수는 스택 안에 있지만 동적으로 생성된 배열은 프리스토어에 있다

 

마찬가지로 delete[] 를 이용해 해제해줘야함. 메모리 누수 주의

 

널 포인터 상수

C++ 이전에는 NULL 이라는 상수로 널 포인터를 표현함. 실제로 상수 0과 같아서 문제 발생할 수 있음

c++의 nullptr는 정해진 전혀 다른 값이기 때문에 문제가 발생하지 않는다.

 

const 키워드 <- 중요

변경되면 안 될 대상을 선언할 때 사용함

const 지정된 대상을 변경하려는 코드를 작성하면 에러를 발생시킴

 

const 상수

c언어에서는 프로그램을 실행하는 동안 변경되지 않을 값에 이름을 붙일 때 전처리 구문인 #define을 사용했지만

c++에선 const를 사용하는것이 바람직하다.

define은 전처리기에서 코드의 구문과 의미에 신경쓰지 않고 단순히 텍스트 매칭으로 찾아 바꾸지만

const는 코드 문맥 안에서 컴파일러가 평가한다.

 

const 포인터

 

const로 지정할 대상에 따라 주의

포인터 변수 그 자체인지

이 경우엔 ip 자체를 변경 할 수 없으므로 선언과 동시에 초기화 해야 함

 

 

이 포인터 변수가 가리키는 값인지

 

일반적으로 const 키워드는 바로 왼쪽에 나온 대상에 적용된다

예외 문법 조심

 

const 매개변수

함수의 매개변수에 const 키워드를 넣으면 함수 내에서 받아온 매개변수값을 수정하려고 하면 에러가 발생한다.

특히 포인터나 참조로 매개변수를 받을때 값을 수정하면 원본 값이 수정되므로 const로 보호해주는 편이 좋다

 

const 메서드

클래스 멤버변수, 멤버함수 뒤에 const 붙이면 수정할 수 없게 만들 수 있다.

const 함수가 무슨 말이냐 할 수 있지만

함수에 const를 붙이면 함수를 수정할 수 없게 만드는게 아니라

이 함수는 멤버 변수의 값(객체의 상태)을 수정하지 않는다는 것을 명시해주는 것을 말한다.

단, mutable 키워드를 붙인 멤버 변수는 const 함수에서 값을 수정해도 에러가 나지 않는다.

 

constexpr 상수표현식

컴파일 시간에 평가되는 표현식

const int arrSize() { return 10; }
int myArray[arrSize()]; // 에러

constexpr int arrSize() { return 10; }
int myArray[arrSize()]; // 정상

constexpr int arrSize() { return 10; }
int myArray[arrSize() + 1]; // 정상

배열의 크기는 컴파일 타임에 정해져야 함으로 일반적인 const 함수로 배열을 초기화 할 수는 없고

constexpr을 통해 컴파일 타임에 값이 보장된 함수를 사용하면 심지어 +1 처럼 연산을 해도 배열의 크기로 사용할 수 있다.

 

이런식으로 사용할 수도 있다.

 

하지만 constexpr 키워드는 함수가 컴파일 시간에 실행될 수도 있다고 지정하는 것이지

반드시 컴파일 시간에 실행되도록 보장하는 것은 아니다.

 

constexpre double func(double num) { return num * 10; }

constexpre double num{ 10.0 };
constexpre double num2{ func(num) };

컴파일 시간에 평가되는 함수를 컴파일 시간에 평가되는 변수를 매개변수로

컴파일 시간에 평가되는 변수에 초기화 하므로 이 함수는 컴파일 시간에 평가된다.

 

constexpre double func(double num) { return num * 10; }

double num{ 10.0 };
double num2{ func(num) };

하지만 위의 경우 함수가 constexpre라도 일반적인 변수는 런타임에 선언되고 초기화되기 때문에

func 함수는 컴파일 타임에 평가되지 않는다.

 

consteval 키워드를 이용하면 함수를 즉시 실행 함수로 만들 수 있으며

컴파일 타임에 평가되는것을 보장한다.

consteval double func(double num) { return num * 10; }

이 함수를 위처럼 런타임에 평가되는 변수에 사용하면 에러가 발생한다.

 

레퍼런스

변수에 대한 별명. 변수의 주소를 가져오거나 변수에 대한 역참조 연산을 수행하는 작업을 자동으로 처리해주는 특수한 포인터

레퍼런스 변수는 반드시 생성하자마자 초기화 해야한다.

다루는 방법은 일반 변수와 같지만 내부적으로는 포인터로 처리됨

레퍼런스는 가리키는 대상을 변경할 수 없다 <- 포인터 변수와 다르니 주의

레퍼런스에 대한 레퍼런스를 만들 수 없다

정수 리터럴처럼 이름 없는 값에 대해서는 레퍼런스를 생성할 수 없다

const로 참조한 값을 변경할 수 없게 할 수 있다.

 

포인터에 대한 레퍼런스를 만들 수도 있다.

레퍼런스가 가져온 주소는 그 레퍼런스가 가리키는 변수의 주소와 같다.

 

 

 

 

~135p

'전문가를 위한 C++' 카테고리의 다른 글

2025/02/24 ch02. 스트링과 스트링 뷰 다루기  (0) 2025.02.24
2025/02/22 ch02. 스트링과 스트링 뷰 다루기  (1) 2025.02.22
2025/02/21  (0) 2025.02.21
2025/02/19  (0) 2025.02.19
2025/02/18  (0) 2025.02.18