2025년 9월 9일
개인적으로 공부하면서, C#과 같이 사용하면서 헷갈릴 수 있는 부분(C++과 다른 점)과 C++에서 특정 버전 이상에서만 지원하는 문법을 정리한다.
C++ 버전별 주요 특징

- typedef
typedef double my_type_t;
== using my_type_t = double; (C++11 이상)- enum
enum { };
enum class { }; (C++11 이상)
//c#의 enum과 같이 사용하려면 enum class를 사용한다.
//enum은 스코프를 사용하지 않지만 enum class에서는 C#과 마찬가지로 스코프를 사용한다.
- string
//c++의 string은 std 라이브러리에 포함된다.
//cin으로 문자열을 온전히 받기 위해선
std::getline(std::cin, {변수명})
//이 사용된다.
//cin으로 입력받은 버퍼를 비우기 위해
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
== std::cin.ignore(32767, '\n');
//사용이 필요하다.- 코드에서 긴급한 탈출 (halt)
HALT → exit(0);- 랜덤 난수 생성
#include <cstdlib>
std::srand(5323); //5323 = seed → 시드 넘버 지정
std::srand(static_cast<unsigned int>(std::time(0)); // 시간과 연동하여 시드가 계속 변경됨
std::rand();#include <random> (C++11 이상)
std::random_device rd;
std::mt19937_64 mesenne(rd()); // or mt19937
std::uniform_int_distribution<> dice(1 ,6); // 1~6까지 같은 확률- cin 활용
# 버퍼 지우기
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
== std::cin.ignore(32767, '\n');
# 입력 범위 문제 확인하기 (둘이 같이 사용됨.)
std::cin.fail(); // return true / false
std::cin.clear(); // 내부 상태 플래그 초기화- 배열과 포인터
배열을 정의하면 배열의 이름 자체가 주소가 된다.
다만 배열을 함수의 인자로 넘겨, 매개변수로 배열을 받는다면 이 매개변수는 '포인터'로 취급된다.
때문에 매개변수로 받은 배열의 이름(포인터)은 배열의 주소를 저장하는 다른 주소가 된다.
→ 함수에서 이 (배열로 보이지만 포인터 변수인)배열의 sizeof를 찍으면 4바이트(32비트, 64비트에서는 8바이트)로 출력된다.
- 포인터를 그냥 선언해서는 변수값을 담을 수 없다.
> char *name = "nolda"; (X)
변수값을 담기 위해선 const 선언을 넣으면 사용할 수 있다.
> const char *name = "nolda"; (O)
//참고
(*this).memberValue;
== this->memberValue;- foreach
C#의 foreach와 사용법은 비슷하지만 문법의 차이가 존재한다.
for (int value : array)
{
}
- 메모리 할당
정적으로 할당된 메모리는 stack에 저장되며, stack은 용량이 작고 컴파일 타임에 크기가 결정된다.
동적으로 할당된 메모리는 Heap에 할당된다. Heap 영역은 런타임에 결정된다.- Stack / Heap
Memory의 Segment
1. Code - Program
2. BSS - uninitialized data
3. Data - initialized data
4. Stack
- 로컬 변수, 메인 함수, 메소드 등이 스택으로 쌓임
- 사이즈가 작기 때문에 Stack Overflow 발생 가능
5. Heap
- 동적 할당일 경우 Heap 영역에 저장됨
- const와 포인터
int value = 5;
const int *ptr1 = &value; //ptr1에 저장돼있는 주소를 바꾸는건 가능하지만, 주소가 가리키는 value 값을 바꾸는건 안됨
int *const ptr2 = &value; //ptr2에 저장돼있는 주소 값 바꾸는게 안됨
const int *const ptr3 = &value; //주소 값도 바꿀 수 없고 de-referencing으로 값을 바꿀 수도 없음 (다 안됨)- std::vector
#include <vector>
std::vector<int> array_value;
동적할당 배열에 유용하게 쓰이고 널리 쓰임. C#의 List와 비슷하다.
//size : 사용하는 용량 -> .resize()
//capacity : 총 용량(size에서 가려진 총 용량) -> .reserve
//vector를 stack처럼 사용하기
.push_back();, .pop_back()- std::tuple
여러 개의 반환 값을 return 시킬 수 있다.
#include <tuple>
std::tuple<int, double> getTuple()
{
return std::make_tuple(5, 3.14);
}
int main()
{
std::tuple<int, double> tp = getTuple();
std::get<0>(tp); //int 값
std::get<1>(tp); //double 값
//C++17 이상에서는 아래가 가능하다.
auto[a, b] = getTuple();
}- 함수포인터
int func() { return 5; }
int func2() { return 9; }
//함수포인터 변수 선언
int(*fcnptr)() = func;
-> int(*변수이름)(매개변수)
//다른 함수 할당
fcnptr = func2;
//functional Library (C++11 이상)
#include <functional>
std::functional<리턴타입(매개변수)> fcnptr = func;- 일립시스 (Ellipsis)
//매개변수의 갯수제한을 두지 않고 받는 방법
double findAverage(int count, ...) //count : 매개변수 갯수
{
double sum = 0;
va_list list;
var_start(list, count);
for (int arg = 0; arg < count; ++arg)
{
sum += va_arg(list, int);
}
var_end(list);
return sum / count;
}- 연쇄호출(Chaining)
class 내부 함수의 리턴을 class 자신의 reference 값으로 설정하면,
연쇄적으로 호출이 가능하다.
class Calc
{
int m_value;
Calc& Add(int value) { m_value += value; return *this; }
Calc& Sub(int value) { m_value -= value; return *this; }
};
int main()
{
Calc cal(10);
cal.Add(10).Sub(20).Add(10);
}- friends keyword
class에서 다른 함수의 선언부를 가져와 friend 선언을 해주면 해당 함수에서는 선언된 class의 private 멤버에 접근할 수 있다.
각자 다른 class에서 공통 함수에 friend를 선언할 경우 전방선언이 필요할 수 있음.
순서에 의해 friend 함수에서 멤버 변수 등을 인식하지 못하면, class에는 선언부만 남겨두고,
인식하지 못한 class의 하단에 구현부를 넣어준다.- 익명 변수
class A
{
void print()
{
cout << "A Print" << endl;
}
};
int main()
{
A().print();
A().print(); //위의 객체와 다르기 때문에 생성자와 소멸자가 각각 호출됨.
}- 연산자 오버로딩
//산술 연산자
class Cents
{
private:
int m_cents;
public:
int& getCents() { return m_cents; }
Cents operator + (const Cents &c2) //멤버 함수
{
return Cents(this->m_cents + c2.m_cents);
}
friend Cents operator + (const Cents &c1, const Cents &c2) //friend 함수
{
return Cents(c1.getCents() + c2.getCents());
}
};//입출력 연산자
class Point
{
private:
double m_x, m_y, m_z;
public:
Point(double x = 0.0, double y = 0.0, double z = 0.0)
: m_x(x), m_y(y), m_z(z)
{ }
//outstream
friend std::ostream& operator << (std::ostream &out, const Point &point)
{
out <<<< point.m_x << ", " << point.m_y << ", " << point.m_z;
return out;
}
//instream
friend std::istream& operator >> (std::istream& in, Point& point)
{
in >> point.m_x >> point.m_y >> point.m_z;
return in;
}
};
int main()
{
Point p1(0.0, 0.1, 0.2), p2(3.4, 1.5, 2.0);
Point p3, p4;
cout << p1 << " " << p2 << endl;
cin >> p3 >> p4;
}
//단항 연산자
Cents operator - () const
{
return Cents(-m_cents);
}
bool operator ! () const
{
return (m_cents == 0 ? true : false);
}//비교 연산자
friend bool operator == (const Cents& c1, const Cents& c2)
{
return c1.m_cents == c2.m_cents;
}
friend bool operator < (const Cents& c1, const Cents& c2)
{
return c1.m_cents < c2.m_cents;
}//증감 연산자
Cents& operator ++ () //prefix
{
++m_cents;
return *this;
}
Cents& operator ++ (int) //postfix
{
Cents temp(m_cents);
++(*this);
return *temp;
}//첨자 연산자 []
class IntList
{
private:
int m_list[10];
public:
int& operator [] (const int index)
{
return m_list[index];
}
const int& operator [] (const int index) const
{
return m_list[index];
}
};
int main()
{
IntList list;
list[3] = 10;
//if
IntList *list = new IntList;
list[3] = 10; //X
(*list)[3] = 10; //O
}//형변환
operator int()
{
return m_cents;
}- explicit / delete
class Fraction
{
int m_numerator;
int m_denominator;
Fraction(char) = delete; //사용 못하게 막음
(explicit) Fraction(int num = 0, int den = 1)
: m_numerator(num), m_denominator(den)
{ }
};
void doSomething(Fraction frac)
{
cout << frac << endl;
}
int main()
{
doSomething(7); //원래는 컴파일러에서 Fraction(7)처럼 변환해줌
//만약 Fraction 생성자에 explicit 키워드가 앞에 붙는다면 불가능해짐
}- class 깊은 복사 주의점
//class의 멤버변수로 char *m_data = nullptr;가 정의되어 있을 때,
//이 class를 기본 복사 생성자를 통해 복사하면 새로운 class instance에도 같은 포인터 주소를 가리킨다.
//이 때, 새로운 class instance가 삭제되면, 소멸자에서 해당 포인터 변수가 가리키는 값을 지워버리게 되고,
//원본의 데이터까지 지워버리는 상황이 발생한다. (얕은 복사)
//이 때문에, 깊은 복사를 위해선 별도의 복사 생성자를 정의해줘야 한다.
MyString(const MyString &source) //복사 생성자 (깊은 복사)
{
m_length = source.m_length;
if (source.m_data != nullptr)
{
m_data = new char[m_length];
for (int i = 0; i < m_length; ++i)
m_data[i] = source.m_data[i];
}
else
m_data = nullptr;
}
}
//operator = (대입 연산자) 의 경우에도 비슷하게 정의해줄 수 있음.
MyString& operator = (const MyString &source)
{
if (this == &source)
return *this;
delete[] m_data; //기존에 갖고있던 메모리 할당 해제
m_length = source.m_length;
if (source.m_data != nullptr)
{
m_data = new char[m_length];
for (int i = 0; i < m_length; ++i)
m_data[i] = source.m_data[i];
}
else
m_data = nullptr;
}
}
//std::string을 사용하면 필요 없는 일이다.
- Initializer List 생성자
IntArray(unsigned length)
: m_length(length)
{
m_data = new int[length];
}
IntArray(const std::initializer_list<int> &list)
: IntArray(list.size())
{
int count = 0;
for (auto & element : list)
{
m_data[count] = element;
++count;
}
}- 상속 관련 Keyword
//virtual
상속 구조에서 부모 클래스의 메소드에 virtual 키워드를 사용시
자식 클래스의 객체를 부모 클래스의 포인터에 넣어서 호출해도
자식클래스의 메소드가 호출됨.
부모 클래스의 함수를 자식 클래스에서 오버라이딩한 것으로 인식
- 가상 소멸자
부모 클래스 객체에 자식 클래스를 넣을 경우,
자식 클래스의 동적 할당된 메모리를 지우기 위해 소멸자에도
virtual 키워드를 붙여주면 자식 클래스의 소멸자도 실행됨.
//override
상속 구조에서 자식 클래스의 함수 매개변수 끝에 override 키워드 작성시
오버로딩이 아닌 오버라이드를 의도한것이라고 인식하게 하는 키워드
//final
final 키워드 사용시 자식 클래스에서 더 이상 오버라이딩할 수 없음- 다이아몬드 상속 문제
A라는 부모클래스를 통해 B와 C클래스를 상속받아 생성할 때,
상속 접근제한자에 virtual을 붙여주지 않으면
B와 C클래스 각각 다른 A클래스를 상속받는 문제가 발생할 수 있음.
class B : virtual public A
와 같이 상속받아야 함.- Object Slicing
부모 클래스로부터 상속받아 생성된 자식클래스에 새로운 변수가 있는데,
부모 클래스에 자식 클래스 인스턴스를 넣어버린 경우 데이터 슬라이싱 발생
std::vector를 사용하는 경우
std::vector<std::reference_wrapper<부모 클래스>> vec;
을 사용할 수 있다.- dynamic cast
Derived d1;
Base *base = &d1;
auto *base_to_d1 = dynamic_cast<Derived1*>(base);
Base로 형변환 됐던 변수를 다시 Derived로 형변환
dynamic_cast의 경우 에러 체크를 통해 에러일 경우 nullptr 반환함. (안전한 형변환)
static_cast는 에러 체크를 하지 않음.- Template
//함수 템플릿
template<typename T>
T getMax(T x, T y)
{
return (x > y) ? x : y;
}//클래스 템플릿
<*.h>
template<typename T>
class MyArray
{
private:
int m_length;
T *m_data;
public:
MyArray(int length)
{
m_lenth = length;
m_data = new T [length];
}
void print();
}
...
<*.cpp>
template<typename T>
void MyArray<T>::print()
{
...
}
template void MyArray<char>;
//템플릿 클래스의 멤버함수의 구현부를 cpp 파일로 옮길 경우
explicit instantiation 필요//