Skip to content

Latest commit

 

History

History
765 lines (756 loc) · 56.6 KB

C++ 기초.md

File metadata and controls

765 lines (756 loc) · 56.6 KB

메모

대충 C++ (강의 아님) 먼저 흝어본 후기

  • 생각보다 C랑 많이 다르다.
    • low한 부분을 최대한 다루지 않도록 하는게 보임. 고수준 언어에 가까운 느낌.
    • C가 쉬운 어셈블리어라면, C++은 어려운 자바 느낌. (low, high level 언어 차이 느낌)
  • 비숫한 기능인에 OO 특화된 타입이나 기능이 많다.
    • 특히 버전 올라가면서 키워드나 타입이 계속 추가되는데, 나무위키보니까 C++이 27 버전까지 있음...
      • A라는 문제가 있음 -> B로 해결 -> C라는 문제가 생김 -> D로 해결
      • 근데 B는 예전 해결 방법이라 쓸 수 있는데, 나빠서 사용하면 안됨. 이런게 다른 언어들보다 많아...
    • rust도 대충 보고 타입이 왜이렇게 많나 싶었는데, C++이 더 많은거 같음. 사람들이 rust로 넘어가는 이유를 알 것 같기도 함.

어셈블리어

내가 예전에 공부하면서 배운 내용도 추가한거라, 내 기억이 잘못됐으면 틀린 내용이 있을 수도 있음.

그리고 이후에 기능 설명하면서 C++ 코드로 생성한 어셈블리어를 설명하는데, Nand2Tetris나 이후에 어셈블리어 찾아본 내용이랑 거의 동일해서 정리는 안함.

  • 어셈블리어 수업을 하는데, 나는 m1(arm64)라서 강의 따라서 x86을 실행을 못함.
    • 대신 어셈블리어 대충 이해하고 있으니까 눈으로만 보고 넘김 - 실제 어셈블리어 흝어보기
    • rax, eax, ax, ah, al 등 한 레지스터 값을 여러 범위를 설정해서 쓰고 읽을 수 있음.
    • 메모리 영역
      • data, bss 차이
        • 둘다 전역 변수와 정적 변수를 저장.
        • data는 초기화한 영역, 바이너리 파일에 포함되어야 함.
        • bss는 초기화하지 않은 영역, 0으로 초기화됨, 값 자체는 바이너리에 초기화 안됨.
        • bss 사용 이유: 메모리 사용량 감소(바이너리 파일에 안씀), 프로그램의 초기 로드 시간 단축(별도 값 로드할 필요 없음)
        • data 사용 이유
          • 초기값 미리 설정, 상수 설정을 통한 메모리/성능 최적화
          • GPT 피셜
            • bss 영역은 초기값이 필요 없는 변수들을 저장하지만, 초기값이 필요한 변수들을 bss 영역에 저장할 경우 프로그램 시작 시 값을 설정하는 추가 코드가 필요합니다. 이는 프로그램의 초기화 단계에서 성능 저하를 일으킬 수 있습니다.
            • data 영역에 초기화된 값들을 저장하면, 운영체제가 프로그램 로딩 시 메모리에 이미 초기화된 값을 로드하므로 초기화 시간이 절약됩니다.
        • 둘 다 메모리/성능 면에서 최적화를 위함임.
          • 예전에는 메모리 비용이 정말 비싸서 이런 식으로 최적화(파일 크기 줄이기 등) 해야 했음.
        • C/C++ 입장에서 코드 상으로 초기화되는 값이면 data, 아니면 bss로 정의됨. (전역이나 상수만 해당. 지역변수는 stack에 저장되므로)
      • 메모리는 하나의 큰 1차원 배열이다. (가상 메모리가 가능하게 해줌)
    • 어셈블리어는 데이터 타입이나 그런거 없이 레지스터, 메모리 쓰기/읽기 + 연산 기능을 제공함.
  • 비트와 바이트
    • (관련해서 생각나서 내가 추가한 내용): 패딩
      • 64비트 컴퓨터에서 CPU는 한 번에 64비트(8바이트)를 읽고 쓰는 것이 가장 효율적입니다. 이는 CPU의 데이터 버스 폭이 64비트이기 때문입니다. 따라서 64비트 컴퓨터에서는 데이터를 64비트(또는 그 배수) 단위로 정렬하는 것이 성능상 이점이 있습니다.
      • 패딩: 64비트 컴퓨터에서 데이터를 효율적으로 읽고 쓰기 위해서는 데이터가 8바이트 경계에 맞추어 정렬되어야 합니다. C 구조체에서 패딩은 이러한 정렬을 맞추기 위해 삽입됩니다. 패딩을 통해 CPU가 데이터에 효율적으로 접근할 수 있으며, 이는 전체적인 성능 향상으로 이어집니다.
        • 단점: 패딩은 메모리 내에서의 데이터 정렬과 접근 효율성을 높이기 위해 사용되지만, 패딩은 실제 데이터가 아니기 때문에 메모리 낭비로 이어질 수 있습니다. 특히, 네트워크를 통해 데이터를 송수신할 때는 패딩을 포함시키면 불필요하게 전송 데이터가 커져서 비효율적입니다. 따라서 패딩을 제거하고 송수신하는 것이 일반적입니다. (주로 직렬화/역직렬화 사용)
      • 1. C 기초 정리의 "구조체 패딩" 참고
  • 음수와 양수의 비트 표현(2의 보수), 2/8/10/16 진수, 부동소수점 표현 등
    • 근데 강의 설명이 가볍게 다루는거라 책처럼 체계적이진 않음. 대충 알고 있는 입장에서는 빨리 넘어가니까 좋긴 함. C++ 문법하고는 엄청 직접적으로 관련있는 건 아니니까. (그래도 알아야 잘 쓰긴 하는데, 모든 언어에 통용되는 개념이라)
  • 문자(ASCII)와 엔디안
    • ASCII와 UTF-8은 호환, 다른 UTF-N이랑은 안되는 것도 있음.
    • 리틀 엔디안, 빅 엔디안은 서로 장단점이 반대임
      • 리틀: 특정 데이터에서 초기 값만 읽을 때 처음부터 조금만 읽으면 되서 유리함
        • 초기 값 접근/수정의 용의성
      • 빅: 데이터의 크기(범위)를 빠르게 비교 가능
        • 데이터 크기 비교의 용이성
  • 사칙연산
    • Nand2Tetris랑 문법만 다르지 간단한 동작은 비슷하긴 함.
      • 다른 점은 곱셉/나눗셈이 ALU에 포함되어 있고, 대신 특정 레지스터 값에 결과가 저장되는 등 덧셈/뺄셈과 다름. 아마 최적화되어있어서 일반적인 방식과 다른듯? (정확하진 않음. 추측)
  • 시프트, 논리(AND, OR ...)
    • shift 비트를 우/좌측으로 옮김. 연산이 빨라서 최적화에 사용. (2의 배수 연산, 한 칸 당2배 or 1/2배로 줄일 수 있음. 시프트 시 값을 날리는 특징을 이용하기도 함)
      • 논리적 시프트: 부호 비트 상관없이 전체를 시프트함
      • 산술 시프트: 부호 비트를 유지하며 시프트, 그래서 좌측 시프트 + 오버플로우 발생 시 값이 2배수 연산임을 보장 못함.
    • 논리 연산
      • and는 bitflag(bit가 일종의 논리적인 값, 메모리 효율적인 bool)에서 사용됨. 아니면 subnet mask나
      • xor은 동일한 값으로 2번 사용하면 값이 원본으로 되돌아오는 특징. 대칭키 암호화에서 사용됨. 본인은 xor하면 값을 0으로 초기화 가능. 0으로 할당하는 것보다 더 빠름. (어셈블러 관점에서 생각해보기)
  • 분기/반복문
  • 배열(array)과 주소
    • array(list아님)은 연속된 메모리 주소에 저장된 데이터
    • array의 주소는 array의 시작 지점을 봄, random access
    • 대충 아니까 정리 스킵
  • 함수
    • 어셈블리어 입장에서는 걍 코드 중 하나.
    • 레지스터나 stack pointer에 파라미터+호출자 정보 넣고 함수 정의된 곳으로 jump (ip(다음 연산 주소를 가리키는 포인터)값이 변경됨.)
      • stack이라는 메모리 공간을 사용, heap,stack,bss,data에서 말하는 그 stack 맞음.
      • 반복되는 함수 호출 시 호출자의 정보를 가지고 있기 위해서 + 함수 수행 시 일시적으로 존재하는 local 값을 다루기 위해서
        • 이런 함수 호출 시 sp에 쌓이는 데이터의 집합을 stack frame이라고 함.
          • 일반적으로 리턴 주소 + 파라미터1,2,3 + 로컬 변수 + 기타 정보 를 저장함. 순서는 잘 모르겠음.
          • x86에선 반환 값을 rax(레지스터, 꼭 rax가 아닐수도 있음)를 저장함. 반환 시 sp, ip 값을 알맞게 변경함.
    • x86 어셈블리어에서는 rbp라고 stack frame의 시작 지점을 고정해서 보고 있는 레지스터가 있음.
      • sp는 계속 변하기 때문에 그렇다고 함.
      • rbp를 사용해서 파라미터, 로컬 변수 값에 상대적인 접근을 쉽게 함.
    • 처리하고 기존 데이터 제거(포인터 값 이동, 실제로 제거하는 건 아님)하고 sp에 반환 결과 담고 호출된 주소로 다시 이동

C++ 시작

  • 정수
    • signed, unsigned, int, short, long ...
      • unsigned기준 char 256, short 6.5만 int 42억, longlong(8바이트) 매우 크다.
      • unsigned는 signed 간 변환 등 여러 문제 때문에, 팀마다 호불호가 있다고 함.
    • 오버플로우 언더플로우
    • 주석
    • bss, data 영역의 변수
  • 불리언과 실수(부동소수점)
    • bool - 1바이트
      • 컴퓨터 입장에서 비트 단위의 관리, I/O가 불가능하거나 어렵기 때문
      • 어셈블리 입장에서는 그냥 정수 0 or 1을 비교함.
      • char 같은거 대신 쓰는 이유는 가독성/유지보수성 때문에 사용한다고 보면 됨.
    • 부동소수점(floating-point)
      • IEEE 754 표준 참고
      • 소수점 위치가 고정되지 않는 숫자
      • 데이터 크기 대비 큰 범위를 표시 가능
      • 단 값의 누락 발생, 근사값임. == 시 주의, 반복 연산 시 늘어나는 오차범위 주의
      • float(4바이트, 69자리까지 유효), double(8바이트, 소수점 1517자리까지 유효)
      • GPT 정리
        • 지수 (Exponent): 숫자의 크기를 조정하며, 2의 지수승으로 표현됩니다.
        • 가수 (Mantissa): 숫자의 정밀도를 나타내며, 1.xxxx 형태로 저장됩니다.
        • 구조: floatdouble은 각각 1비트의 부호, 8/11비트의 지수, 23/52비트의 가수로 구성됩니다.
  • 문자와 문자열
    • char: 내부적으로는 정수임. (더하기, 빼기 등 연산 가능) 근데 ASCII 문자랑 대응됨.
      • 0~127를 넘는 값, 음수나 더 큰 값을 사용하면 이상한 문자가 출력됨.
    • Unicode -> UTF8,UTF16 장단점 - 위에 설명 있음. 아니면 검색하던가
    • 문자열: 문자 배열, string 자료형은 문자 배열 + 문자열 관련 기능 추가
      • char 배열의 마지막에는 끝임을 알리기 위해서 00(NULL)이 있어야 함.
  • 산술 연산
    • =, (), +, -, *, /, %, +=, -=, %=, /=, ++, --(이거는 ++a 처럼 앞에 있는게 약간 빠름)
  • 비교/논리 연산
    • 특정 조건/상태 확인
    • 불 대수
    • �단락 평가(short-circuit evaluation): 조건문 만족하면 나머지 스킵하는 거
      • 어셈블리어 까서 보여줌.
  • 비트 연산
    • ~(not), &, |, ^(xor), <<, >>
      • 1 << 3 1을 3번 왼쪽(<<)으로 시프트
    • C++에서 시프트
      • 왼쪽 시프트 (<<)
        • 모든 타입에 대해 논리적 시프트: 비트를 왼쪽으로 이동시키고, 오른쪽에 0을 채웁니다.
        • 부호 있는 정수에서도 논리적 시프트를 사용하지만, 이는 부호 비트가 이동될 수 있음을 의미하며, 결과적으로 값의 부호가 바뀔 수 있습니다.
      • 오른쪽 시프트 (>>)
        • 부호 없는 정수 (unsigned integers)에 대해 논리적 시프트: 비트를 오른쪽으로 이동시키고, 왼쪽에 0을 채웁니다.
        • 부호 있는 정수 (signed integers)에 대해 산술적 시프트: 비트를 오른쪽으로 이동시키고, 왼쪽에 부호 비트(기존 최상위 비트 값)를 채웁니다. 대부분의 컴파일러에서 부호 있는 정수에 대해 산술적 시프트를 수행합니다.
          • 음수(앞이 1)를 >> 하는 경우 1001이면 1100이 된다.
          • 부호에 따라서 0/1 여부가 달라짐.
    • BitFlag: Bit로 상태(Flag) 표시
    • BitMask: BitFlag의 원하는 값을 뽑아내는 Mask
  • const와 메모리 구조
    • const: 초기화와 할당이 동시에 수행, 수정 불가능
      • 특정 하드코딩된 값에 문맥을 제공해줄 수 있음.
    • 메모리 구조
      • .data: 초기화된 고정(전역, 상수)
      • .bss: 초기화 안된 전역. (그 외 있을 수도?)
      • .rodata: 읽기 전용 영역 (const pointer 등)
      • stack: 지역변수, 콜스택
      • heap: 동적 영역
      • 메모리 영역에 대한 구체적인 C++의 표준이 없음. 정확한건 컴파일러 하기 나름.
  • 유의사항
    • 변수의 유효범위: 같은 변수 이름을 사용하는 경우 등
      • 이름 변경, {}를 사용한 스코프 제한 등
    • 연산 우선순위: 가독성, 명확성을 위해 () 사용하기
    • 타입 변환:
      • 작은 범위와 넒은 범위의 연산 시 넒은 범위로 변환 (암시적 형변환)
      • (특히 int와 float는 둘다 같은 바이트지만 float는 더 넒은 범위 표현이 가능하므로 변환됨)
      • 큰 범위를 작은 범위로 변경하는 경우 데이터 분실 주의 (상위 비트 그대로 없어짐)
      • signed, unsigned 사이 간 연산 주의
    • 연산
      • 0으로 나누기
      • 오버/언더플로우
      • 부동소수점 정밀도

코드의 흐름 제어

  • 분기문
    • 꼭 true(1)이나 flase(0)이 아니여도 됨. 0이 아니면 true로 판단함.
    • if-else, switch-case
      • switch-case는 특정(연속된(gap이 적은) 상수 값, Enum) 경우 점프 테이블을 생성해 O(1)로 효율적이다.
        • 아닌 경우 if-else와 비슷하게 동작함.
  • 반복문
    • while, do-while, for
    • continue, break
  • 연습문제(별찍기와 구구단) & 가위바위보
    • 간단한 입출력 문제 구현
    • 컴퓨터의 rand는 구현하기 어렵다. 사실 대부분의 rand 기능은 정말 "랜덤"하다고 보기 어렵다. seed 값을 설정하는 식인데, 이걸 매번 바뀌는 컴퓨터의 상태 or 시간 정보를 가공해서 예측하기 어렵게 한다.
  • 열거형
    • enum
      • 컴파일 시점에 상수 int로 변경
      • 숫자를 지정 안하면 0부터 시작
        • 다음 숫자는 이전숫자 + 1
      • 0, 1, 2, 0(0으로 설정), 1, 2 같은 경우도 됨.
        • 이 경우 0번째 값과 3번째 값이 동등(==)하게 처리됨.
        • 그냥 숫자 0이랑 비교해도 됨.
        • enum의 내부 동작을 이해하는 용도로, 실제로는 관리가 어려우므로 추천하는 방식은 아님.
    • define 매크로
      • enum 역할을 이걸로도 수행 가능.
      • 그러나 매크로는 추천하지 않는다는 내용.
      • 그대로 대체하는 것이라서 연산자 우선순위 등을 고려하지 않음.
        • enum 대비 IDE의 지원을 받기 어려움

함수

  • 함수 기초
    • 정리할거 딱히 없음
  • 스택 프레임
    • 어셈블리어에서 이미 다루기도 했고, 알고 있는 내용이므로 스킵
    • 구조 - 높은 곳 -> 낮은 곳으로 늘어남
      • (높은 곳)
      • 매개변수 - 0번째
      • 반환주소 - 1번째
      • 지역변수 - 2번째
      • (낮은 곳)
  • 지역 변수와 값 전달
    • 지역변수: 스택에 올라감, 콜스택 끝나면 유효하지 않음.
    • 전역변수: 다른 영역(data, bss 등)에 들어감. 모든 영역에서 접근/변경 가능하므로 사용에 주의
    • 값 전달: 스택에 값 자체를 넘겨주므로 호출자에게 영향이 가지 않음.
      • call by value/reference 관련 내용
      • 나는 따지고 보면 포인터도 값이라고 생각하는데, 포인터를 포함해서 조금 특별한 값 들을 reference라고 정의한다고 생각하고 있음.
  • 호출(콜) 스택
    • 함수 선언: 함수 간의 호출을 쉽게하기 위해서 함수의 시그니처를 미리 정의해 주어야 한다.
    • 호출 스택
      • 호출 스택을 통해서 특정 함수가 어떤 함수로부터 호출되었는지 확인 가능.
      • 디버거에서 호출 스택 정보를 확인할 수 있다.
  • 함수 마무리
    • 오버로딩 (C는 지원 안하는데 C++은 지원함)
      • 반환 형식과 이름이 같은 함수(시그니처(파라미터 개수, 타입)가 다름)가 여러개
      • 근데 이거 씹어먹는 C++보면 더 자세함. (대충 자동으로 끼워 맞출 수 있는 타입이면 형변환해서 끼워준다는 내용)
    • 함수 기본값
      • 기본값이 설정된 매개변수가 맨 끝쪽에 있어야 함.
      • 대신 named parameter 처럼 자유롭지는 않음.
    • stack overflow
      • 스택 프레임이 너무 많이 쌓여서, 컴파일 시점에 정해짐 스택 영역이 다 차버려서 더 호출을 못하면 발생하는 에러
  • TextRPG
    • https://gist.github.com/YangSiJun528/d6dd3e4f8d43d31250e51189f9ec1359 에 만든거 정리
    • 간단한 텍스트 게임.
    • 구조체를 사용한 개선
      • 구조체는 모두가 같은 타입이 아닐 때, 패딩이 생겨서 더 큰 크기를 가진다. (컴퓨터가 계산하기 쉽게 함, 컴파일러에 따라 차이가 있을 수 있음 == 표준이 아님)
    • 클래스의 필요성을 보여주는 부분인듯?

포인터

  • 포인터의 필요성
    • 변수: 어떤 값을 특정한 이름으로 담아서 관리하는 거.
      • 컴파일 이후에는 변수라는 개념이 딱히 없음. data 영역 등의 메모리 주소나 stack(이것도 메모리 주소긴 함)에 값이 저장되어 동작하도록 만들어짐
    • 포인터의 필요성
      • 함수 호출 시 원본 데이터의 수정이 필요한 경우.
      • 메모리 효율적으로 값을 다루기 위함.
        • 구조체 등에서 데이터 자체를 넘기면 데이터가 stack에 복사됨.
        • return 해서 변경된 값을 받는 식으로도 간접적으로 원본 수정 가능한데, 이러면 구조체 크기가 크면 전부 복사해야함. 호출자가 결과를 다시 수정해야 하므로 연산이 늘어나고 비효율적.
  • 포인터
    • 특정 자료형의 주소를 가리키는 변수
    • 포인터를 통해서 실제 변수에 접근해서 값을 수정할 수도 있음. (간접참조?)
    • 어셈블러 동작 방식을 보면 이해하기 쉬움.
    • 컴퓨터 환경에 따라서 4byte(32bit) or 8byte(64bit)
    • 타입
      • 읽어야 할 메모리 범위를 알려주기 위해서 필요함. (int는 4byte이므로 4byte만큼 읽어야 함.)
    • 포인터를 사용해서 데이터를 잘못 다루면 다뤄야 할 범위를 넘어서 다른 기존 데이터에 영향을 줄 수 있음.
    • const 사용법
      • const 타입 *이름: 포인터를 불변으로. 즉, 참조하는 대상(포인터의 값)을 변경할 수 없음.
      • 타입 const *이름: 이름을 불변으로. 즉, 참조하는 대상의 값을 변경할 수 없음.
      • 둘 다 사용하는 것도 가능.
  • 포인터 연산
    • +, - 시에는 자료형 크기만큼 증감함. 그게 자연스러움. 다음 데이터가 있을 위치로 이동. 배열을 사용할 떄 필수로 씀.
    • *(용도 다양), &, ->
  • 포인터 실습
    • 내용 자체는 스킵
    • 구조체를 복사하는 방식(어셈블리어를 읽고, 메모리 낭비나 연산 많은 문제를 보여줌)을 포인터를 사용하는 식으로 개선
  • 참조(reference) 기초
    • C에는 없고 C++에서 추가된 개념
    • 내부적으로는 포인터와 동일하게 동작함.
      • 이미지를 보면, 어셈블리어 변환 결과 포인터와 참조의 동작이 동일한 걸 알 수 있다. 어셈블리 코드에서는 pointer 가 스택의 [sp + 16] 위치에 8바이트 크기로 저장되는 모습을 볼 수 있는 반면, reference는 별도의 메모리 공간을 차지하지 않고, number의 메모리 주소를 그대로 사용함.
    • 메모리 상에 존재하지 않는다. 단, 함수의 파라미터로 받는 경우, 포인터와 동일하게 메모리 공간을 가진다. (함수 호출 시에는 주소 값을 넘겨줘야 해서 callee 입장에서는 포인터나 참조 변수나 동일하게 동작하는건데, 이건 뭐... 레퍼런스 자체라기 보다는 함수 호출이나 파라미터의 특징 아닌가? 그래도 메모리 상에 존재하게 되는거긴 하니까...?)
    • 레퍼런스는 초기 선언이 필수적이며, 다른 대상을 참조할 수 없다.
      • 레퍼런스가 메모리 상에 존재하지 않기 떄문에, 항상 참조하는 변수의 값이 선언되어 있어야 한다.
      • 이런 특징을 보장하기 위해 포인터, 배열, 리터럴을 사용할 수 없는 것이다. (포인터는 값이 유효하지 않을 수 있고, 리터럴은 변수가 아니니까)
    • (리터럴, 배열, 포인터, 레퍼런스)를 제외한 변수의 레퍼런스를 사용할 수 있다.
      • 특정 변수를 참조하는 변수. 참조하는 것이기 때문에, 어셈블리어 입장에서는 별도의 변수라기보다는 그 변수 자체이다.
      • const를 사용해서 값을 수정하지 않게 할 수 있음. (사용할 수 있으면 무조건 쓰기)
    • 나는 포인터를 사용하게 쉽게 해주는 목적으로 사용된다고 이해함.
      • 근데? 포인터와 달리 별도의 메모리 공간 대신, 변수의 메모리를 그대로 사용하는...
      • 포인터 사용 시, ->*를 사용하는 접근이 불편하기 때문.
    • 특정 변수의 참조(int& r = int_val). 변수처럼 다루는데, 실제로는 int_val이 변경됨.
    • 주의점
      • 참조하는 대상이 사라지는 댕글링 레퍼런스 (Dangling reference) 발생 주의
        • 예시: 지역변수 a를 참조하는 레퍼런스(참조자) b를 리턴. 호출자로 돌아온 시점에서 a가 만료되었으므로 리턴받은 b에 접근하면 런타임 에러 발생
    • const를 사용해서 해당 참조하는 대상을 수정할 수 없게 가능.
    • 씹어먹는 C++의 설명: https://modoocode.com/141
  • 포인터 vs 참조
    • 성능: 비슷함
    • 사용성: 참조가 더 편함
    • 주의점: 그럼에도 편의성이 좋은게 꼭 좋은건 아니다.
      • 긴 코드의 경우, 코드만 보고 일반 타입인지, 레퍼런스 타입인지 모름.
    • 초기화: 참조는 항상 유효한 값을 가지고 있어야 함. (실무에서는 nullptr 등으로 값을 반환하기도 하는데, 이런게 안됨. 근데 이런건 result 같은걸 쓰는게 좋지 않나 싶은데, 현실적으로는 그게 어려우니까)
    • 결론: 케바케, 구글은 거의 안쓰고, 얼리얼은 쓴다. 성능 차이가 없다시피 하니까 그냥 팀 따라서 별 차이 없는듯.
    • 강사 피설: 본인은 참조 + define으로 수정 가능한 함수임을 명시적으로 구분하는걸 선호한다고 함. 수정 안되면 const
  • 배열 기초
    • 동일한 여러 데이터를 연속된 메모리 구조에 순차적으로 저장.
      • 동일한 타입의 여러개를 하나로 묶어서 관리하기 위함.
    • 배열의 이름 시작 주소를 바라보는 포인터라고 볼 수 있음.
      • 어셈블러 코드랑 메모리 디버깅 해서 확인 가능.
      • 함수로 값을 넘길때도, 포인터와 동일하게 call by references
    • arr[i] 와 같은 문장은 사실 컴파일러에 의해 *(arr + i) 로 변환된다.

    • char 배열, 여러 초기화 기법 등 설명은 생략
    • 문자열(")도 사용하기 쉽게 만들어진 배열, 내부적으로는 char 배열과 동일하게 구성.
  • 포인터 vs 배열
    • 포인터: 주소를 담는 바구니
    • 배열: 바구니의 모음 그 자체. 연속된 메모리 공간
      • 다만, "배열 이름"은 바구니 모음의 "시작 주소"(포인터)
      • 즉, 배열 이름은 포인터지만, 배열은 포인터가 아니다.
      • 배열을 연속된 메모리를 가지는 실체이므로, 사용 시 큰 데이터를 가지고 있다는 걸 알아야 한다.
  • 로또 번호 생성기
    • 정렬(n^2), swap, 랜덤 번호 생성, 총 3가지 기능을 사용해서 구현.
  • 다중 포인터
    • 다중 포인터의 필요성
      • 포인터 변수를 넘겨줘서 포인터 변수의 값을 수정하고 싶은 경우.
      • 즉 포언터 변수의 주소 값을 저장해야 하는 상황에서 필요
  • 다중 배열
    • 내부적으로 1차원 메모리처럼 동작함. ([]를 사용하는, C++의 기본 기능 기준)
    • 어셈블리어의 물리적인 1차원 배열을 C++에서 논리적으로 N차원 배열로 구분하는 느낌.
  • 포인터 마무리
    • 포인터 VS 배열 ver - 포인터와 배열은 다르다.
      • 1중 포인터와 1차원 배열은 상호 호환이 된다.
        • 서로 변환하며 사용할 수도 있고, "배열의 이름"이 포인터이기 때문이다.
      • 2중 포인터와 2차원 배열은 다르다. (2중 이상인 N중 포함)
        • 2중 포인터는 연속된 공간이 아니라, 독립된 공간이 주소를 타고 타고 접근 가능.
        • 2차원 배열은 내부적으로 1차원 배열로, 연속된 공간을 가진다.
    • 포인터 사용 시 주의점
      • C++은 메모리에 접근하고 수정 가능함. 강력하지만 너무 위험하다.
        • 잘못 수정하면 이 부분을 찾기가 너무 어렵다.
        • 강사 피셜: MMORPG에 신입으로 일을 했었는데, 7년 전에 짜놓은 코드에 문제가 있었고 그걸 건드려서 일주일간 계속 크래시가 났었다.
  • TextRPG
    • 이전에 만든거 구조체 + 포인터 사용해서 로직 개선
    • 포인터 실제 예시 설명을 위함인 듯?
  • 연습문제
    • 문자열 관련 함수 구현
      • 문자 배열 다룰 때 주의 할 부분. 길이, 비교 확인 연산 등
    • 여담
      • strcpy는 의도하지 않은 메모리를 다룰 수 있어서(잠재적 에러 상황), 조기에 에러를 발생시키는 strcpy_s를 사용하는게 좋다.
      • 포인터끼리의 == 연산은 주소값이 동일한지 비교한다.
    • 달팽이
      • 간단한 코테? 구현 문제 풀이함.
  • 파일 분할 관리
    • cpp, h 파일 사용법. 함수 중복 정의 문제나 그런거 주의하라는 내용. .h 의 정의가 중복되는 문제와 그 해결 (#pragma once, 근데, CLion에서는 #ifdef사용하는 거 같음. 아마 #pragma는 공식 문법이 아니라 그런듯?)
    • 씹어먹는 시리즈가 더 설명 잘해줘서 그거 보는게 더 나을듯.

객체지향

  • 객체지향의 시작

    • 절차지향 프로그래밍
      • 절차의 의미: 영어로 procedure. procedure는 함수라는 의미로도 사용됨.
        • 기존에 함수를 기반으로 동작했기 때문에 절차지향이라는 이름이 붙음.
    • 객체란?
      • 도메인에 맞는 논리적인 개념? 현실에서 물리적으로 실체를 가질수도 있고, 아닐수도 있음.
      • 상태, 행위를 가짐
      • 엘런 케이의 객체지향 개념 참고하기
    • class로 객체 표현, struct에 행위(함수)와 접근제어자가 붙은 느낌
    • instantiate: 인스턴스화
    • 특정 클래스에 어떤 행위를 명령할 수 있다. (인스턴스에 어떤 행위를 수행한다는 걸 더 직관적으로 보여줌)
    • 내부 동작
      • 함수는 별도의 위치에 존재, 첫번째 인자로 내부적으로 this(내 메모: class 중 상태를 저장하는 부분, struct와 아마 거의 동일함, 패딩도 있는 걸로 알고)를 전달해줌. (C에서 구조체 념겨서 수정하는거랑 내부적으론 동일함)
  • 생성자와 소멸자

    • class의 this는 자기자신을 가리키는 포인터이다. 맴버 변수는 var 내부적으로 this->var1로 동작하는 식
    • 생명주기 관리를 위한 생성자와 소멸자가 있음.
    • 생성자(Constructor)
      • 여러 개 존재 가능
      • 객체 생성 시 초기화 역할
      • 사용
        • 클래스이름 인스턴스이름 = 객체이름(인자1, 인자2)
        • 클래스이름 새로운 인스턴스 이름 : 이 경우 인자가 없는(기본생성자)를 사용함.
        • 클래스이름 인스턴스이름 = 아무 타입
          • 타입 변환 성생자가 사용됨.
      • 생성자가 없으면, 아무 인자+내용도 없는 기본 생성자가 생김. 하나 이상의 생성자가 있으면 기본 생성자는 생성되지 않음.
    • 복사 생성자
      • 자기의 클래스 참조 타입을 받는 함수. (const 타입이름& 인스턴스이름 const를 붙이는게 좋음)
      • 새로운 객체를 반환하는 역할.
    • 타입 변환 성생자
      • 하나의 인수를 가지는 생성자
      • MyClass(int v) 생성자가 있다고 할 때, MyClass obj = 42;처럼 사용될 수 있음.
    • 소멸자(Destructor)
      • delete 인스턴스 이름 시 호출됨
      • ~객체이름()
        • 소멸자는 인수를 가지지 않는다. 반환 값도 정의하지 않는다.
    • 암시적 형변환 - 이건 그냥 함수 호출에도 해당되는거 아닌가
      • 함수 호출 시 일치하는 타입이 없으면 암시적으로 형변환을 통해서 처리 가능한 함수를 찾아서 호출함.
    • 암시적 형변환 예방
      • 함수 호출이나 타입 변환 생성자 등에서 암시적인 형변환이 발생하는데, 의도하지 않는 사용으로 인한 문제가 발생할 수 있다.
      • 생성자나 함수에 explicit 라는 키워드를 통해 암시적 형변환이 일어나지 않게 한다.
        • 그래도 명시적으로 하면, 타입 변환 생성자를 사용할 수 있음. MyClass obj = (MyClass)42;
        • 의도치 않은 실수를 줄인다고만 생각하기
  • 상속성

    • class 클래스명 : 접근제한자 클래스명
      • 접근 제어자로 인해 하위 클래스의 최소 접근 여부가 결정된다.
        • 상속 시 설정한 접근제어가자 protected면 public 함수/필드는 protected가 되지만, private는 더 엄격하므로 private이 유지된다.
    • 내부 동작
      • 메모리 상에서 부모의 필드를 포함해서 가짐. 확장된 구조체 느낌?
    • override
      • override 키워드를 사용해서 virtual 함수 구현 or 오버라이딩 하는 거라는 걸 명시 가능
      • 근데 안써도 문제 없음. 사용하면 명시적인 표현 + 최적화? 기능 지원? 등 장점 있음.
      • (내 상각: 이런거 볼 때마다 약간 Java에서 어노테이션 자꾸 뭐 추가되는 느낌임. 안써도 되는데, 쓰면 좋은거 이런게 계속 추가되는 것 같네.)
    • 생성자/소멸자의 호출
      • 자신의 생성자가 호출되기 전, 부모의 기본 생성자가 선처리된다.
      • 소멸자는 부모의 기본 생성자가 후처리된다.
      • 특정 생성/소멸자를 호출하고 싶다면, 명시적으로 정의해주면 된다.
      • 자원의 할당/해제 관점에서 생각해보면 당연함.
      • 부보 생성자 호출
        • Child(int x, int y) : Parent(x) y은 내가 사용하는거
  • 은닉성(=캡슐화)

    • 추상화와 비슷한 느낌인데, 우발적 복잡성을 낮춘다.
    • 접근 제어자는 public, protected, private가 있음.
    • 상속 접근 지정자에도 접근 제어자를 설정. 근데 실제로는 거의 public만 쓴다고 함.
  • 다형성(polymorphism)

    • 그리스어에서 유래됨. poly(다수의), morph(형태), ism(주의,현상)
    • 동일한 기능의 가지지만 다양한 형태
      • 오버로딩, 오버라이딩, 가상함수 등이 해당됨
    • 정적 바인딩: 컴파일 시점에 바인딩. 일반 함수
    • 동적 바인딩: 실행 시점에 바인딩. 가상 함수
    • 가상 함수
      • 상위 클래스 포인터를 통해 하위 클래스의 함수를 호출하는 기능
        • 가상 함수 테이블을 추가해서 이를 가능하게 함
      • Parent a = new Child(); a.do1()do1()이 Parent에 정의된 함수가 아니라, �Child에 재정의된 함수가 실행하게 하려면 do1()을 가상 함수로 정의해야 함.
      • 함수 앞에 virtual 키워드를 통해서 동작
      • 가상 함수 테이블: 호출되어야 할 함수의 주소가 들어있음.
      • 가상 함수 사용 시 vtable 사용으로 인한 약간의 오버헤드가 생기므로 기본적으로 사용하지 않음.
        • Java나 다른 고수준 언어는 기본적으로 모든 함수가 가상함수임. C++이 약간 저수준에 더 가깝기 때문에 이런 기능이 붙음.
      • 순수 가상 함수: 구현이 없는 함수. 약간 자바의 추상클래스?
      • 추가 참고: https://modoocode.com/211
  • 초기화 - 초기화 리스트

    • 초기화 필요성
      • 처음 할당 시 더미 값이 있음.
      • 초기화 안하면 예상못한 동작이 일어남
    • 초기화 방법
      • 생성자 내
        • 선처리 이후 수행되므로 객체가 이미 초기화됨. 한번만 생성되야 하는 경우 이 영역을 사용할 수 없음.
      • 초기화 리스트
        • Person(std::string name, int age) : name(name), age(age) {}
        • : 필드 이름(값), << 이거
      • C++ 11 문법 (모던)
        • 필드 선언 시 바로 값 주기
        • int age = 0;
    • 주의사항
      • 상속/포함하는 객체의 초기화는 초기화 리스트를 사용하기
        • 포함하거나 상속받는 객제는 생성자의 선처리 과정에서 먼저, 11 모던 문법에서 다시 호출됨
        • 그래서 클래스 A가 B를 필드로 가지고, 11 모던 문법으로 B를 초기화하면 선처리 과정에서 B의 기본 생성자가 호출되고, 그 이후에 모던 문법이 한 번 더 실행됨
        • 참고: https://chatgpt.com/share/efbc3cfc-73c8-49e5-827a-312dd93dfe5d
      • const, 참조 타입의 경우 초기화 리스트 or 모던 문법 필수
  • 연산자 오버로딩

    • 연산자를 오버로딩하는거
      • 자바에서 모든 객체가 공유하는 equals나 그런거 오버로딩하는 것처럼 +,-,% 같은 연산자를 오버로딩 할 수 있음
    • 일반적인 산술/관계/논리/비트/대입 등등 오버로딩 가능
    • 오버로딩 시 2가지 버전이 있음: 전역/멤버 연산자 방법
      • 둘 중 하나만 지원하는 경우도 있어서 둘 다 알아야 함.
    • 대입 연산자
      • Rule of Three/Five : 대입 연산자를 포함한 5개의 연산자는 오버로딩 시 함께 오버로딩해야함.
        • 약간 자바 equals, hashcode 느낌?
    • 복사 대입 연산자/복사 생성자: 이름만 설명하는데, 복사라는 특징 때문에 공통적으로 따로 관리
    • 연산자 오버로딩 시 주의점 설명하는데
      • 솔직히 내 목표나 수준에서 필요한지 잘 모르겠어서 스킵
    • a++가 성능이 더 낮음. a를 복사해서 전달해주기 때문.
      • 기존 값을 이미 수정되었고, 복사해서 전달해줘야만 함. 스택에 있는 값은 반환 시점에 이미 없어짐.
  • 객체지향 마무리

    • struct vs class
      • C++에선 별 차이 없음.
      • C++은 C 파생 언어가 태생(지금은 호환 잘 안될수도 있음) 그래서 호환성을 지키기 위해서 그랬음.
    • static 변수
      • 클래스 자체에 종속적인 개념
        • 그래서 static 함수는 본인이 그걸 알아야 함.
      • 내부: 전역변수처럼 data 영역에 들어감
  • 동적 할당

    • 동적 할당
      • 범위가 동적인 데이터를 가지고 있어야 하는 경우?
      • 스택은 뭔가 많은 데이터를 저장하기보다, 함수들끼리 인자를 자연스럽게 주고받기 위한 용도
      • 전역은 미리 선언되어 있어야 한다.메모리 낭비
      • 생성/소멸 시점을 관리할 수 있는 동적으로 메모리를 할당하는 기능을 동적할당 이라고 함.
    • 메모리 할당은 커널의 시스템 콜이 필요하다.
      • 프로그램 시작 시, 이후 동적 할당 시 시스템 콜을 사용한다.
      • C++에서 제공되는 CRT(C런타임 라이브러리)를 사용해서 동적 할당. 일부 경우에는 직접 시스템 콜 API를 사용하기도 하는데 이는 극한의 최적화를 위해서 드물게 쓰임.
      • malloc, free, new, delete, new[], delete[]
      • void* 보이드 형 포인터 -> 뭐가 있는지 모르는 포인터 - 증감 연산 시, 1byte 씩 이동
      • free 시에 할당을 풀 메모리 크기를 지정하지 않아도 되는 이유?
        • malloc 시에 크기를 추가로 메모리에 저장하도록 내부적으로 구현되어있음
        • 쭉 연결되어 있다기보다는 따로 테이블 같은곳에 저장하는 형태인것 같음? 어쩄든 따로 관리해서 메모리 크기가 없어도 된다는 의미
      • 동적 할당 시 힙 오버/언더 플로우나 free 된 데이터를 제어하는 등의 위험하므로 잘 관리해주어야 함.
      • new / delete
        • malloc / free는 함수, C의 기능
        • new / delete는 연산자, C++의 기능
        • new / delete는 객체의 경우, 생성자와 소멸자를 호출한다.
          • 내부적으로 함수를 호출시키도록 처리해준다.
  • 타입 변환

    • 값 타입 변환: 의미를 유지하기 위해 원본과 다른 비트열 재구성 - 정수 -> 부동소수점 표현
      • 논리적으로 의미가 동일한 경우
    • 참조 타입 변환: 비트열 재구성 없이 '관점'의 변경
      • 대신 이러면 의미가 변함. 논리적으로 의미가 바뀜.
      • (float&)a << 처럼 &가 필요함. 이름이 '참조'인 이유
      • int -> (float&)a 시에는 int의 바이트 열 그대로의 float 표현으로 변경, 값이 int와 비슷하지도 않고 아예 다른 구조가 됨
    • 안전성
      • 안전한 변환: 의미가 항상 100% 일치하는 경우
        • 더 큰 범위의 표현
      • 불완전한 변환: 의미가 100% 일치하지 않을 수 있는 경우
        • 데이터를 나타내는 표현법이 다르거나 더 작은 크기의 타입으로 변환
    • 암시적/명시적
      • 동일하거나 같은 크기의 데이터 타입으로의 변환 시 암시적으로 처리해주기도 함. (int -> float도 ok)
      • 데이터 손실이 가능한 경우 명시적으로 해야 함. (int -> short)
    • 타입 변환 생성자, 타입 변환 연산자
      • 연관 없는 클래스 끼리의 타입 변환을 가능하게 해 줌.
    • 상속 관계의 클래스 변환
      • 리스코프 치환 원칙 생각하기
        • 자식 -> 부모 OK
        • 부모 -> 자식 X
    • 포인터와 콜스택 << 약간 복습 개념
      • 스택은 함수가 종료되면 자동적으로 소멸되지만 동적할당된 애들은 아님
        • (콜스택 생각해보면 스택이 이런식으로 동작하는게 당연함.)
        • 이런 차이를 이해하는게 중요함
      • 호출 시 일반 클래스를 넘기면 복사 생성자를 사용해서 값 자체가 넘어감. = 객체가 새로 생성되고 지워짐
    • 포인터 타입 변환
      • 강제적(참조 타입 변환)으로 포인터 변환 시 잘못된 메모리 위치에 접근할 수 있음.
        • 근데 이걸 (필수적으로) 막을 수 없음. 메모리 오염이 지속적으로 생기면 큰 문제로 번질 수 있고 찾기 어려움.
      • 연관성 있는 클래스끼리의 포인터 타입 변환
        • 부모 -> 자식
          • LSP 아님, 명시적으로 강제하면 되긴 한데 메모리 오염 가능
        • 자식 -> 부모
          • LSP 포함. 암시적으로 가능. 메모리 오염 없음
        • 즉, 명시적으로 변환할 떄는 주의!
          • 작업하다보면 명시적으로 해야할 때가 생김...
          • 여러 하위 클래스를 공통적으로 상위 클래스로 관리한다던가, 이런 상황에서느 특정 경우에 하위 클래스로 명시적 변환이 자주 필요해짐.
        • 여러 하위 클래스를 상위 클래스로 관리할 때, 상위 클래스의 delete를 하는 경우, 상위 클래스의 소멸자만 호출 됨.
          • 이 경우 메모리 누수 발생
          • 상속 관계의 함수 호출에서 자식 객체인 경우 오버로딩 된 함수가 호출되게 하기 위해서는 별도의 테이블 같은게 필요하기 때문에 성능이 낮아져서 기본적으로 사용 안하는거 (가상 함수를 사용해서 원본 객체가 오버로딩 한 함수를 실행되도록 설정해야 함)
          • 그래서 소멸자에 virtual 키워드를 항상 붙여줘야 함.
  • 얕은 복사 / 깊은 복사

    • C++에서 복사는 복사 생성자 or 복사 대입 연산자로 복사
    • 얕은 복사
      • 기본적으로 컴파일러가 암시적으로 만들어줌
      • 메모리에 있는 비트 열을 복사해서 줌
      • 포인터(주소 값)을 가지는 경우 그것도 복사됨
      • 즉, 기본적으로 생성되는 복사 작업은 얕은 복사임
    • 깊은 복사
      • 가지고 있는 포인터 값들도 깊은 복사를 통해서 복사해줘야 함
      • 복사 생성자 / 복사 대입 연산자에서 새로운 객체를 만들어주되, 동일한 값을 가지는 새로운 객체를 제공해서 돌려줘야 한다.
        • 즉, 가지고 있는 다른 객체도 복사 연산자 / 복사 대입 연산자를 지원해야 함. (가지고 있는 객체가 다른 객체를 가지는 경우, 마찬가지로 깊은 복사 기능 필요)
    • 복사 생성자 / 복사 대입 연산자의 동작
      • 암시적 복사 생성자
        • 부모 클래스 -> 맴버 클래스 순으로 복사 생성자 호출
      • 명시적 복사 생성자
        • 부모 클래스 -> 맴버 클래스 순으로 기본 생성자 호출
        • 즉, 부모와 맴버 클래스의 복사를 명시적으로 수행해줘야 함.
          • 컴파일러는 기본적으로 해줘서 그럼
      • 암시적 복사 대입 연산자
        • 부모 -> 맴버 클래스 순으로 복사 대입 연산자 호출
      • 명시적 복사 대입 연산자
        • 알아서 해주는거 없음
      • 즉, 명시적으로 복사 하는 경우 부모, 맴버 클래스의 복사도 직접 해줘야 함 (프로그래머의 책임). 암시적인 경우 컴파일러가 직접 해주는 부분이 있음.
  • 캐스팅 종류 4가지

    • C 언어의 (타입) 대신에 더 C++ 친화적인 캐스팅
      • C 언어의 캐스팅은 너무 강력하고 실수인지 의도인지 알기 어려운 문제가 있음.
      • 캐스팅의 의미를 더 명확하게 해 줌. 약간의 제약 조건을 추가해줌
    • static_cast<type_name>(var_name) 같은 식으로 사용
    • static_cast: 기본적인(상식적인) 타입 변환, 컴파일 시점에 타입 검사를 해서 안전함.
    • dynamic_cast: 다형성(Polymorphism) 클래스 사이의 안전한 타입 변환.
      • RTTI(런타입 타입 정보)환경의 타입 변환.
      • 만약 잘못 된 캐스팅(실제가 아닌 다른 객체로의 다운 캐스팅)의 경우 nullptr 반환하는 특징 << 다형성 보장하는 캐스팅
      • 조금 씀
    • const_cast: const를 붙이거나 때거나 할 때
      • 외부 라이브러리가 const를 허용하지 않을 떄
      • 거의 안 씀
    • reinterpret_cast: 가장 강력한(위험한) 캐스팅
      • 아무 방식으로나 형변환 가능
      • 거의 안 씀
  • 실습

    • 간단한 TextRPG 다시 구현
    • 이제 배운 상속, 생성자, 파일 분리 등의 기능을 사용해서 실습
  • 전방선언

    • Stack, Heap 아니면 어떤 메모리 구조에서라도 객체를 선언하기 위해서 객체의 크기(바이트 수)를 알 수 있어야 한다.
    • 객체가 다른 객체(포인트 X)를 가지는 경우, 가지는 그 객체의 크기 또한 알 수 있어야 한다.
    • 그냥 단순히 포인터 값으로 객체만 가지는 경우 전방선언만으로 사용 가능하다. (클래스가 존재한다는 최소한의 정보만 제공한다.)
      • 하지만 어떤 함수를 사용하거나 객체의 내부(필드)에 접근하고 싶다면, 헤더를 추가해야 한다.
        • 클래스의 내부 구조(메모리, 구체적인 설계도)를 알아야 하기 때문
    • 전반선언 특징
      • 컴파일 시간 단축, 순환 의존성 해결
  • 디버깅

    • 그 인텔리제이 디버깅 공식문서 설명이랑 크게 다르지 않았음.
    • 차이점이라 해봐야 C++이니까 메모리 봐보는 거 정도?
    • 연습문제 풀이도 있음.
      • 안풀었는데 나중에 기회되면 풀기?
      • 연습하기 좋은 문제일거 같긴 함.
      • 대부분 거의 메모리 관련 문제인듯?
  • 콜백 함수

    • 함수 포인터
      • typedef, using 키워드를 사용해서 함수 시그니처에 이름(타입처럼 사용)을 붙일 수 있음.
      • 해당 타입(함수 포인터)에 동일한 시그니처를 가지는 다른 함수의 값을 부여할 수 있음.
        • 사실 함수 또한 주소 값으로 jump하는 것이기 때문에... 포인터로 처리할 수 있음
      • 왜 사용하는가?
        • 함수를 인자로 넘겨줄 수 있음.
        • 정렬 or 필터링 등
        • (근데 사실 그냥 추상 객체 사용하면 안되나 싶은데)
      • 한계점
        • 위 문법으로는 전역/정적 함수만 담을 수 있다.
        • typedef int(클래스이름::*포인터이름)(int, int)
          • 이러면 특정 맴버 함수 포인터를 선언할 수 있다.
    • typedef의 진실
      • 선언 문법에서 typedef를 앞에다 붙이면 됨.
      • int 함수이름(int a, int b) -> typedef + int(*포인터이름)(int, int)
    • 함수 객체(functor)
      • 함수처럼 동작하는 객체
      • 함수 포인터의 단점
        • 시그니처가 맞아야만 한다.
        • 상태를 가질 수 없다.
      • () 연산자를 오버로딩하여 함수처럼 동작하게 할 수 있다.
        • 오버로딩 기능을 사용해서 여러 인자를 처리가능
        • 객체라 상태를 가짐
      • 객체를 인자로 넘겨주고 나중에 필요한 시점에 사용할 수 있다.
    • 템플릿 기초
      • 탬플릿은 매우 방대하고 복잡함.
      • 함수나 클래스를 찍어내는 틀. (자바의 제네릭에서 더 확장된 느낌?)
      • template<typename t1, 타입이름 t2>
      • 함수, 클래스 템플릿이 있음.
      • 함수 템플릿
        • 함수의 타입(주로 인자)를 사용하는 시점에 정하는 것
      • 템플릿 특수화
        • 특정 구체적인 타입의 경우 구현을 변경하게 할 수 있다.
      • 템플릿의 typename말고 특정 타입으로 값을 받고, 그 값이 다른 경우 컴파일러 입장에서 별도의 클래스로 식별된다. (함수와 달리) - template<int SIZE> 이 값이 1, 2 면 둘 다 다른 클래스로 식별함.
    • 콜백 함수
      • 개념 아니까 생략
  • STL(Standard Template Library) - 이거는 코테 풀때 다시 하기. (강의 빠르게 넘기느라 대충 봄)

    • 자주 사용되는 자료구조/알고리즘을 템플릿으로 제공하는 라이브러리
    • 컨테이너: 데이터를 저장하는 객체 (= 자료구조)
    • vector: 동적 배열
      • 임계치를 넘어가면 더 큰 배열을 만들고 기존 값을 복사
        • 즉, 연속된 메모리 구조
      • size: 실제 사용 데이터. capacity: 전체 데이터 크기.
      • 컴파일러마다 늘리는 정도는 다름.
      • 처음에는 resize 횟수가 많아서 capacity를 알면 이리 지정해주는게 좋음.
      • 시간복잡도는 아니까 설명 생략
      • 강의에서는 직접 이터레이터까지 한번 만들어봄
    • 이터레이터(iterator) 객체
      • 객체지만 연산자 오버로딩을 사용해서 외부에서 포인터처럼 사용할 수 있다.
        • 포인터를 wrap하고 추가 기능을 지원한다고 생각해도 됨.
      • 내부적으로 어떤 객체의 이터레이터인지 같은 값을 가짐.
      • 모든 자료구조들이 공통적으로 일관성있는 사용을 가능하게 추상화해주는 역할
        • 모든 자료구조들이 이터레이터를 가진다.
      • const_iterator, reverse_iterator가 있음.
    • list: 연결 리스트
      • 연결 리스트 특징 상 capacity가 없음
      • 시간복잡도 및 내부 구현
      • 이터레이터
        • end()는 논리적인 끝 노드 뒤에 있는 내부적으로 추가된 노드(더미노드)를 가리킨다.
          • 끝 노드임을 확인하기 위헤서 사용한다.
          • 또한 그 추가된 노드는 첫번째 노드와 마지막 노드를 바라보고 있다.
        • 이터레이터를 통해서 중간 데이터를 미리 찾아 놓았다면 업데이터, 삽입/삭제가 더 빠르다.
      • 그 외 vector와 다른 특징으로 인해 사용법에 차이가 있다. +를 사용한 N번째 접근이 안된다거나...
    • deque: double ended queue
      • 내부적으로 배열을 가지는데, resize 시에 별도의 배열을 추가로 생성하고 연결한다. (vector와 같이 데이터 자체를 옮기지 않는다.)
        • vector와 list의 합친 느낌?
      • 메모리 까보면 알 수 있음.
      • 근데 음... 내가 생각하는 deque 구현이랑 좀 다른데 list와 달리 임의 접근이 가능하게 하기 위해서 이런 식으로 만든게 아닐까? 그리고 새롭게 할당하는게 비용이 많이 드니까 앞 뒤로 조금씩 추가하고...
    • map: key, value 형태
      • 연관 컨테이너
      • 균형 이진 트리를 사용해서 만들어짐.
      • 구체적인 설명 생략
      • (트리면 시간복잡도가 상수는 아닐듯?)
      • 단, 다른 언어와 다르게 key가 겹치는 경우 대체하지 않고, (이터레이터, false)를 반환한다. 저장되면 true
        • 추가/수정이 별개의 작업임. 아니면 함수 대신 list[k] = v를 하거나
          • 대신 map[k] 자체가 "없으면 추가" 라는 의미를 내포하므로, 찾는 키가 없어도 0으로 자동 초기화됨.
    • set
      • key만 있는 map 느낌.
      • 집합 표현 용
    • multimap
      • 중복 key를 허용하는 map
      • 추가는 여러 개, 지우는 건 키 값 전부 다 지움(이터레이터 사용해서 일부만도 가능함)
    • multiset
      • 중복을 허용하는 set
    • 알고리즘
      • 자주 사용되고 반복되는 알고리즘의 경우 직접 구현하면 사람마다 구현 방법이 다르고, 표현 방법이 달라서, 이러한 것들을 C++에서 미리 제공하는 것을 사용하는 것이 좋다. (알고리즘 라이브러리)
      • find, find_if, remove, remove_if, all_of, any_of, for_each 등등...
  • Modern C++:

    • 2011(C++ 11 버전) 부터 모던 C++, 전 후 차이가 큰 편
    • auto: (컴파일러의) 타입 추론, 자바 var 같은 느낌
      • 추론 규칙은 생각보다 복잡해질 수 있다.
      • 주의점: 기본 auto는 const, &(레퍼런스)를 무시한다.
        • 힌트를 주거나 캐스팅을 통해서 명시해야함.
      • 반복되는, 타입을 파악하기 쉬운 경우만 auto를 추천, 그 외에는 이해를 위해서 기본 타입 쓰는걸 추천
    • 중괄호 초기화
      • {}를 사용한 초기화
      • 배열이나 다른 경우 {}를 써야 하고, 객체는 생성 방법이 다르고...
      • 초기화 문법을 통일하자는 목적, 컨테이너(자료구조)와 잘 어울림.
      • {}를 사용하면 축소 변환(값 손실이 발생할 수 있는 변환)을 차단함.
        • 좀 더 엄격한 변환을 제공해서 안전한 코드 작성
      • 대신 초기화 시에 initalizer_list를 받는데, 이걸 사용한 생성자의 우선순위가 높아서 다른 생성자가 덮어씌워지는 문제가 생길 수 있다.
      • 팀바팀임
    • nullptr
      • 기존에는 0 or NULL(0 정수) 을 사용했었음.
      • 대신에 사람과 달리 컴파일러 입장에서 정수값으로 인식되면서, 함수나 그런 부분에서 오동작 가능성이 있음.
      • 그래서 nullptr을 사용하는게 국룰. 이건 의견 갈리는게 없음.
      • 내부 구현도 설명해주는데, 대충 읽어서 스킵
    • using
      • type define을 사용하는 이유: 변환 가능성, 긴 타입 간소화
      • using으로 동일한 기능 사용 가능
      • using은 사람이 읽기에 더 직관적임
      • template에도 적용 가능함
        • 기존(type define)에는 약간 우회적으로 했어야 했음.
      • 그래서 이전코드 이해 목적 외에는 using을 사용하는 걸 추천
    • enum class (scoped enum)
      • 이름공간 관리: 이전 enum의 이름을 거의 전역이였음. 그래서 prefix를 사용 해줬어야 했음.
      • 암묵적인 변환 금지: 이전 enum의 경우 내부적으로(컴파일러 입장에서) 숫자 값이라 예상 못한 동작이 있음.
        • 대신 편리하게 바로 조건 확인 불과, 매번 캐스팅 필요
      • 이것도 약간 팀바팀
    • delete
      • 기본적으로 제공되는 특정 기능들을 막아버리고 싶은 경우 (생성자, 대입 연산자 등)
      • 기존에는 private로 정의해서 바깟에서 못쓰게 함.
        • 그러나 내부 or friend 등의 경우 가져다 쓸 수 있다는 문제가 있음.
      • 함수 시그니처 뒤에 = delete 추가 시 명확하게 에러 띄울 수 있음.
    • override, final
      • 국룰임
      • 특정 클래스만 보고 이제 처음 선언한 virtual 함수인지, 언제부터 사용했는지 알기 어려움.
      • 그래서 제정의 시 함수 뒤에 override 키워드를 붙임
        • 명시적으로 알려줄 수 있고, 잘못 사용하면 컴파일러가 에러도 내줌. (안전함)
      • final은 이 이후 자식 객체는 override 할 수 없다는 의미
    • 오른값 참조 (rvalue reference) - 대충 보느라 잘 이해 못했을 수도
      • 배경
        • 모든 C++ 의 가장 중요한 내용. 전/후 속도가 많이 나게 되는 원인
        • 왼값,lvaule: 단일식을 넘어서 계속 지속되는 개체
          • 일반적인 참조의 경우 왼값 참조
        • 오른값,rvalue: lvaule가 아닌 나머지 (임시 값, 열거향, 람다, i++ 등)
      • 이전 사용법
        • 임시 객체를 특정 함수로 넒겨주는 경우, rvalue이므로 함수 호출 시 값을 넘길 수 없다.
        • 대신 파라미터 정의에 const가 붙으면, rvalue를 넘겨줄 수 있다.
        • 왜?
          • 임시 객체를 넘겨주면 어차피 수정해도 없어지는 값임.
          • 그래서 const를 붙으면 임시 객체 여부와 상관 없이 수정을 못하므로 상관 없음.
      • 오른값 참조
        • 타입이름&&를 인자로 받으면 오른값 참조 기능을 제공한다.
        • 오른값 참조만 사용 가능. 일반 객체는 사용 불가능
      • 어셈블리어 입장에서는 별 차이 없음.
        • 함수 입장에서 어차피 스택메모리에서 값을 받는건 동일함.
      • C++ 단에서는 의미가 다름.
        • 임시 객체를 넘겨주는 것이므로, 그냥 값 자체가 해당 함수로 넘어갔다는 의미. (이동 대상)
        • 원본을 유지하지 않아도 됨. 훼손해도 됨.
      • 깊은 복사에 잘 사용됨.
        • 이동 생성자 / 이동 대입연산자 를 사용해서 그냥 임시 객체 자체를 내껄로 정의해버리면 됨.
        • 복사하지 않고 이동하는 것이 효율적이고 빠름.
        • std::move를 사용해서 오른값 참조로 변환
          • 이름이 좀 애매한데, 후보 중 하나가 rvalue_cat였다고 함. 그냥 변환한다는 의미임.
      • 코어한 라이브러리에서는 중요하게 쓰여서, 성능적인 이점을 많이 챙겼다고 함.
      • 스마트 포인터의 소유권? 이전 시 사용됨.
    • 전달 참조 (forwarding reference) C++ 17에서 이름이 봐뀜. 이전에는 보편(univeral) 참조라 부름
      • 템플릿이나 auto 등에서 오른값/왼값을 전부 포함하는 타입? 을 말하는 듯?
      • 형식 연역(type deduction)이 일어날 때 사용됨
        • 사용하는 시점에 타입이 정의되 거
      • 오른값/왼값 참조의 기능을 하나의 함수로 동일하게 정의할 때 유용함.
      • 오른 값은 왼 값이 될 수도 있다. (이거 이해 잘 안가면 강의 다시 보기. 설명하기 애매함.)
        • 대충 18분때쯤부터 보면 될듯?
    • 람다(lambda)
      • (익명) 함수를 빠르게 만드는 문법
      • 새로운 기능은 아니고, 그 기능을 편리하게 사용할 수 있게 해주는 느낌? (템플릿 흑마법을 쓰면 기존에도 되었다고 함.)
      • 람다 표현식을 사용해서 정의 가능 - [캡처](인자){구현}
        • 변수마다 캡처 모드를 지정해서 사용하는 것이 더 안전하긴 함.
    • 스마트 포인터 (smart pointer)
      • 필요성: 포인터는 너무 위험함. 메모리 오염같은 문제.
      • 참조하고 있는 어떤 애를 지워줄 거였으면 2중 하나였어야 함.
        • 다른 애들이 더 이상 사용하지 않을때까지 대기하거나
        • 참조하고 있는 애들을 찾아서 다 명시적으로 지워주거
      • 현대적인 프로젝트에는 코드 안전성을 위해서 성능을 좀 낮추더라도 거의 다 사용한다고 함.
      • 스마트 포인터: 포인터를 알맞는 정책에따라 관리하는 - 객체 포인터를 래핑
        • 어려운 메모리 관리를 편리하게 도와줌.
      • 강의에서는 간단하게 구현하면서 설명하는데, 나중에 기회 되면 보기
      • shared pointer
        • 참조를 가지고 있는 다른 객체가 없어질 때까지 살아있음.
        • 나를 참조하는 다른 애들의 수(cnt) 값을 가지고 있고, 더 이상 참조하지 않으면 제거
        • 단, 서로 참조하고 있는 경우 영원히 제거되지 않음.
          • 순환 구조를 끊어줘야 함
      • week pointer - 이해 잘 안감. shared pointer의 wrap인가?
        • 포인터가 날아갔는지 아닌지 확인할 수 있음.
        • shared pointer로 변환해서 사용해야 함.
      • uinque_ptr
        • 복사가 불가능하고, �전역에서 한 곳만 가지고 있어야 한다.
        • 값을 넘기는 경우 오른 값으로 소유권? 자체를 넘겨줘야 함.
  • 마무리

    • 지금 빠르게 0부터 ~ 자작 OS 책을 만들고 싶어서 대충 봤는데, (중간에 끊기는 좀 그래서)
    • 강의 자체는 좀 자세하고 설명도 잘 해주는 편이라 나중에 C++ 제대로 해야하면 그떄 또 보기
    • C++이 확실히 오래됬다 보니까 기능도 많고, 그거에 따라 팀바팀 사용법도 많이 다르고... 그런 것 같음.