12. 클래스와 동적 메모리 대입, +추가 필요

2023. 7. 25. 23:25·C++/C++

◆static 클래스 멤버

  • static 클래스 멤버는 그 클래스의 모든 객체가 공유한다.
  • static 기억 공간에 저장된다.
  • 클래스 선언 바깥에서 별개의 구문을 사용하여 독립적으로 초기화해주어야 한다.
  • 초기화 선언은 메서드 구현 파일(cpp)에 넣는다.
  • ※ static 멤버가 정수형이나 열거형의 const이면 클래스 선언 자체에서 초기화할 수 있다. 
    • enum { INT = 10 } 같은 상수 표현을 대체한다.

 

◆생성자에서 new를 사용하여 메모리를 대입했을 때는, 대응하는 파괴자에서 delete를 사용하여 그 메모리를 해제해야 한다.

 

◆프로그램에서 하나의 객체를 다른 객체로 초기화하면, 컴파일러는 복사 생성자라고 부르는 생성자를 자동으로 발생시킨다.

StringBad sailor = sports;
StringBad sailor = StringBad(sports);

 

◆◆특별 멤버 함수

  • ▷C++는 다음과 같은 멤버 함수를 자동으로 제공한다.
  • 디폴트 생성자 : 생생자를 전혀 정의하지 않았을 경우
  • 디폴트 파괴자 : 디폴트 파괴자를 정의한 지 않았을 경우
  • 복사 생성자 : 복사 생성자를 정의하지 않았을 경우
  • 대입 연산자 : 대입 연산자를 정의하지 않았을 경우
  • 주소 연산자 : 주소 연산자를 정의하지 않았을 경우
    • 암시적 주소 연산자는 호출한 객체(즉, this 포인터의 값)의 주소를 리턴한다.

 

◆디폴트 생성자

  • 사용자가 어떤 생성자를 정의했다면, C++는 디폴트 생성자를 제공하지 않는다.
  • 매개변수를 사용하는 생성자들도, 모든 매개변수들에 디폴트 값을 제공한다면, 디폴트 생성자가 될 수 있다.

●매개변수도 사용하지 않고 아무 일도 하지 않는 생성자(디폴트된 디폴트 생성자)

Cat::Cat() {}	//암시적 디폴트 생성자

※ 단, 디폴트 된 디폴트 생성자와 모든 매개변수들에 디폴트를 제공한 디폴트 생성자가 두 개 존재해선 안된다.

아래 같은 경우 불가능.

Cat() {age = 0;};		// 생성자 #1
Cat(int n = 0) {age = n;};	// 모호한 생성자 #2

◆복사 생성자

  • 어떤 객체를 새로 생성되는 객체에 복사하는 데 사용된다.
  • 값 전달에 의한 함수 매개변수 전달을 포함한 초기화 작업에 사용된다.
  • ※ 암시적 복사 생성자는 값으로 얕은 복사를 하는 것으로 힙 메모리를 복사할 경우 포인터를 복사해 간다.
  • ※ 클래스가 new에 의해 초기화되는 포인터들을 멤버로 가지고 있을 경우에, 포인터 자체를 복사하는 것이 아니라, 그 포인터가 지시하는 데이터를 복사하는 복사 생성자를 정의해야 한다. 이것을 깊은 복사(deep copy)라 한다. 복사의 다른 한 가지 형태(멤버별 복사 또는 얕은 복사)는 포인터 값만을 복사한다. 얕은 복사는, 포인터가 지시하는 데이터들을 복사하기 위해 깊게(deeply) 파고들지 않고, 포인터 정보만 얕게(shallowly) 살짝 들어 복사한다.

●일반적인 복사 생성자

Class_name(const Class_name &);

●복사 생성자를 사용하는 종류

Cat BabyBlack(Black);	//Cat(const Cat Black &)을 호출한다.
Cat BabyBlack = Black;	//Cat(const Cat Black &)을 호출한다.
Cat BabyBlack = Cat(Black);	//Cat(const Cat Black &)을 호출한다.
Cat * BabyBlack = new Cat(Black);	//Cat(const Cat Black &)을 호출한다.
  • ※ 객체를 값으로 전달하면 복사 생성자가 호출되기 때문에, 참조로 전달하는 것이 더 좋다.

 

●복사 생성자가 주로 사용되는 경우

1. 객체가 함수에 인수로 전달될 때

2. 함수가 객체를 반환값으로 반환할 때

3. 새로운 객체를 같은 클래스 타입의 기존 객체와 똑같이 초기화할 때


◆대입 연산자

  • 오버로딩 대입 연산자는 하나의 객체를 기존의 다른 객체에 대입할 때 사용된다.
  • 대입 연산자는 객체를 초기화할 때 반드시 사용되는 것은 아니다. (복사 생성자를 쓰는 경우, 혹은 대입)
    • 초기화는 항상 복사 대입 연산자를 호출한다, 컴파일러에 따라 대입 연산자를 호출할지 말지는 다르다.

●일반적인 대입 연산자

Class_name & Class_name::operator=(const Class_name &);

 

●C++는 이 구문을 두 단계로 처리한다.

Cat BabyBlack, Black;
BabyBlack = Black;

1. 복사 생성자를 사용하여 임시 객체를 먼저 생성한 후

2. 대입을 사용하여 그 값들을 그 새로운 객체에 복사한다.

  • 즉 초기화는 복사 생성자를 항상 호출한다. 또한 = 연산자를 사용하는 형식들은 대입 연산자를 호출할 수도 있다.

 

◆대입에서 발생하는 문제의 해결책

깊은 복사를 하는 대입 연산자 정의를 사용자가 직접 제공하는 것이다.

 

복사 생성자와 비슷하지만, 다음과 같은 점에서 몇 가지 차이가 있다.

  • 타깃 객체가, 이전에 대입된 데이터를 참조하고 있을 수도 있으므로, 그 함수는 delete []를 사용하여 이전의 의무를 해제해 주어야 한다.
  • 그 함수는 어떤 객체를 자기 자신에게 대입하지 못하게 막아야 한다. 그것을 막지 않으면, 앞에서 설명한 메모리의 해제가, 내용을 다시 대입하기도 전에 그 객체의 내용을 먼저 지울 수 있다.
  • 그 함수는 호출한 객체에 대한 참조를 리턴한다. 

ex)

S1 = S2 = S3;
S1.operator=(S2.operator=(S3));

//대입 연산자
StringBad & StringBad::operator=(const StringBad & st)
{
	if(this == &st)
    		return *this;
	delete [] str;	//옛 문자열을 해제한다.
	len = st.len;
	str = new char[len +1];	//새로운 문자열을 위한 공간 확보
	str::strcpy(str,st.str);	//문자열을 복사한다.
	return *this;	//호출한 객체에 대한 참조를 리턴
}
  • ※ 복사 생성자에서 생성된 값을 대입 연산자로 채우는 것으로, 대입 연산자는 새로운 객체를 만들지 않는다.

 


◆개선된 디폴트 생성자

String::String()
{
	len =0;
    str = new char[1];
    str[0] = '\0';
}

●str = new char[1] 로 초기화해준 이유

▷파괴자의 경우 delete [] str인데 디폴트 생성자가 str = new char라면 []가 없어 호환이 어렵기 때문이다.

 

 

●C++ Null Pointer

▷nullptr 이라는 키워드를 제공하여 null pointer를 표시한다. 혹은 0을 사용할 수 있다.


 

🔍 C++에서 생성자가 있을 경우, 비교 연산에서 암시적 변환이 일어나는 이유

C++에서는 ==, <, != 등의 비교 연산자를 오버로딩하여 사용자 정의 타입끼리 비교할 수 있습니다. 이때, 생성자가 정의되어 있으면 다른 타입끼리 비교할 때 암시적 변환(implicit conversion) 이 일어나기도 합니다.

그 원리를 알아보겠습니다.


✅ 예제: 사용자 정의 문자열 클래스

class String {
private:
    char* str;

public:
    // const char* → String 으로 변환하는 생성자
    String(const char* s);

    // 비교 연산자 오버로딩
    friend bool operator==(const String& st1, const String& st2);
};

그리고 이렇게 정의된 비교 연산자:

bool operator==(const String& st1, const String& st2) {
    return std::strcmp(st1.str, st2.str) == 0;
}

🔁 암시적 변환이 일어나는 상황

String answer("love");

if ("love" == answer) {
    // 암시적 변환 발생!
}
  • "love"는 const char* 타입
  • answer는 String 타입

이때 "love"는 String 클래스의 생성자 String(const char*)를 통해 자동으로 String("love")로 변환됩니다.
즉, 이 코드는 이렇게 해석됩니다:

if (String("love") == answer)

두 인자가 모두 String이 되므로, 우리가 오버로딩한 operator==가 호출됩니다.


🔐 조건: 생성자가 존재해야 함

이 암시적 변환은 다음 조건을 만족할 때에만 일어납니다:

  • 생성자가 explicit이 아닐 것
  • 변환 대상 타입으로 변환 가능한 생성자가 존재할 것

🚫 다음과 같이 explicit 키워드를 사용하면 암시적 변환이 차단됩니다:

explicit String(const char* s);

이 경우 "love" == answer는 컴파일 에러가 발생합니다. 명시적 변환만 가능하기 때문입니다:

if (String("love") == answer)  // ✅ 명시적 변환 OK
if ("love" == answer)          // ❌ 암시적 변환 불가

✨ 실용 예시

이러한 암시적 변환은 문자열 비교뿐 아니라, std::string, std::chrono::duration, std::complex 등 다양한 C++ 표준 라이브러리 타입에서도 활용됩니다.
암시적 변환이 잘 활용되면 코드를 더 자연스럽고 직관적으로 만들 수 있습니다.


🧠 정리

항목 설명

암시적 변환 발생 조건 생성자가 존재하고 explicit이 아닐 경우
동작 방식 "love" → String("love")로 변환 후 비교
주의점 explicit 생성자는 암시적 변환을 막음
장점 코드가 직관적으로 보이고, 오버로딩된 연산자가 잘 작동함

🔚 마무리

C++의 생성자 기반 암시적 변환은 매우 강력한 기능이지만, 의도치 않은 버그를 유발할 수 있기 때문에 꼭 필요한 경우에만 허용하고, 그 외에는 explicit을 사용하는 습관을 들이는 것이 좋습니다.


operator==를 friend 함수로 정의하는 이유는 다음과 같습니다:

  1. 좌변이 사용자 정의 타입이 아닐 수 있기 때문에 ("love" == answer 같은 경우)
  2. 양쪽 피연산자를 대칭적으로 처리하기 위해 (둘 다 같은 타입으로 비교)
  3. 클래스의 private 멤버에 접근하기 위해

 

◆[] 표기를 사용하여 개별 문자에 접근하기

●[] 기호는 하나의 연산자이다.

▷operator[]() 라는 메서드를 사용하여 이 연산자를 오버로딩할 수 있다.

▷첫 번째 피연산자[두 번째 피연산자]

String opera("The Magic Flute");

//진행 과정
opera[4]
opera.operator[](4) 	//String::operator[](int i)
char & String::operator[](int i)
{
	return str[i];
}

 

 

 

const String answer("futile");
cout << answer[1]; // 컴파일 할 때 에러가 발생한다.

●const String 객체의 경우 인덱싱 호출이 데이터를 변경하지 않는다는 약속을 하지 않기 때문에, operator[]()의 제2의 버전을 제공할 수 있다.

//const String 객체를 사용하기 위해
const char & String::operator[](int i) const
{
	return str(i);
}

◆static 클래스 멤버 함수

▷멤버 함수를 static으로 선언하는 것이 가능하다.

※함수 정의와 함수 선언이 분리되어 있다면, 키워드 static은 함수 정의가 아니라  함수 선언에 나타나야 한다.

1. static 멤버 함수는 객체에 의해 호출될 필요가 없다.

2. static 멤버 함수는 어떤 특정 객체

와도 결합하지 않기 때문에, 사용할 수 있는 데이터 멤버는 static 데이터 멤버밖에 없다.

▷static 멤버 함수를 사용하여, 클래스 인터페이스의 몇 가지 상황을 어떻게 처리할 것인지 제어하는, 클래스 사용 범위의 플래그를 설정할 수 있다.


◆대입 연산자 오버로딩에 대한 보충

대입 연산을 할 경우, 복사 연산자로 임시 객체를 만들고 대입 연산을 진행한 후에 임시 객체를 파괴하게 된다.

이에 대한 방안으로 오버로딩된 대입 연산자 정의를 사용자가 직접 제공하여 불필요한 임시 객체 생성을 없앨 수 있다.


get(char *, int의 오래된 버전들은 빈 줄을 읽었을 때 false로 평가하지 않는다. 그러한 버전들의 경우에, 빈 줄이 입력 되면 문자열의 첫 문자는 널 문자이다. 이 예제는 다음과 같은 코드를 사용하고 있다.

if(!cin || temp[0] =='\0')	//입력이 실패했거나 입력이면
	break;

◆◆생성자에 new를 사용할 때 주의할 사항

●생성자에서 new를 사용하여 포인터 멤버를 초기화한다면, 파괴자에 반드시 delete를 사용해야 한다.

●new와  delete의 사용은 서로 어울려야 한다. new는 delete와 짝을 이루고, new []는 delete []와 짝을 이루어야 한다.

●생성자가 여러 개일 경우에는, 모두 대괄호를 사용하든지 아니면 모두 대괄호 없이 사용하든지, 모든 생성자가 같은 방법으로 new를 사용해야 한다. 파괴자는 하나밖에 없으므로, 모든 생성자가 그 파괴자와 어울려야 한다. 그러나 하나의 생성자에서 new를 사용하여 포인터를 초기화하고, 다른 생성자에서 널 포인터로 초기화하는 것은 허용이 된다. 그 이유는 (대괄호고 있건 없건) delete 연산자를 널 포인터에 적용할 수 있기 때문이다.

●깊은 복사를 통해 하나의 객체를 다른 객체로 초기화하는, 복사 생성자를 정의해야 한다. 일반적으로 그러한 복사 생성자는 다음과 같아야 한다.

String::String(const String & st)
{
	num_strings++;
    len = st.len;
    str = new char [len+1];
    std::strcpy(str,st.str);
}

▷특히, 복사 생성자는 복사되는 데이터를 보관할 기억 공간을 대입해야 한다. 복사 생성자는 데이터의 주소가 아니라 데이터 그 자체를 복사해야 한다. 또한 그 과정에서 값이 영향을 받을 수 있는, 모든 static 클래스 멤버들을 갱신해야 한다.

●깊은 복사를 통해 하나의 객체를 다른 객체에 대입하는, 대입 연산자를 정의해야 한다. 일반적으로 그 클래스 메서드는 다음과 같아야 한다.

String & String::operator=(const String & st)
{
	if(this == &st)
    	return this;
    delete [] str;
    len = st.len;
    str = new char[len+1];
    std::strcpy(str, st.str);
    return *this;
}

▷특히, 그 메서드는 자기 자신에 대입하는지 검사해야 한다. 그것은 멤버 포인터가 이전에 지시하던 메모리를 해제해야 한다. 데이터의 주소가 아니라 데이터 자체를 복사해야 한다. 그리고 호출한 객체에 대한 참조를 리턴해야 한다.

 

◆사용하지 말아야 할 것과 사용해도 좋은 것

▷다음의 코드의 단편은, 사용하지 말아야 할 두 가지 예와, 한 가지 좋은 생성자의 예를 보여 준다.

●1.

String::String()
{
	str = "디폴트 문자열";
    len = std::strlen(str);
}

▷파괴자가 디폴트 객체에게 대해 호출되었을 때, str에 delete를 적용한다. new로 초기화되지 않은 포인터에 delete를 적용했을 때, 결과를 알 수 없지만 아마도 나쁠 것이다.

◎다음의 생성자들은 문제가 없을 것이다.

String::String()
{
	len = 0;
    str = new char [1];
    str[0] = '\0';
}

String::String()
{
	len = 0;
    str = 0; //또는, C++11. str = nullptr
}

String::String()
{
	static const char * s = "C++";	//한 번만 초기화된다.
    len = std::strlen(s);
    str = new char [len + 1];
    std::strcpy(str,s);
}

 

●2.

String::String(const char * s)
{
	len = std::strlen(s);
    str = new char;		//[]가 없다.
    std::strcpy(str, s)	//공간이 확보되지 않았다.
}

▷new를 적용하지만 정확한 크기의 메모리를 요청하지 못한다.

▷또한 대괄호 없이 new를 사용하는 것은, 올바른 다른 생성자들과 일관성이 없다.

 

●3.

String::String(const String &st)
{
	len = st.len;
    str = new char[len + 1];
    std::strcpy(str,st.str);
}

▷문제가 없다.

 

●다음은 앞의 생성자들과 어울리지 못하는 파괴자의 예이다.

String::~String()
{
	delete str;	//안된다, delete [] str 이어야 한다.
}

▷생성자들이 문자들의  배열을 요청하고 있으므로, 파괴자는 배열을 삭제할 수 있어야 한다.


 

◆const가 아닌 객체에 대한 참조 리턴

cout과 함께 사용하기 위한 대입 연산자의 오버로딩과 << 연산자의 오버로딩이, const가 아닌 객체를 리턴하는 두 일반적인 예이다.

 

대입 연산자가 const라면 아래의 코드가 동작하지 않는다.

a = b = c;

const 자료형이 일반 매개변수에는 들어 갈 수 없기 때문이다.

 

operator<<()의 리턴 값은 연쇄 적인 출력에 사용되어야 하여 ostream &가 요구된다.

  • ostream 클래스는 public 복사 생성자를 만들지 않는다.
  • cout에 대한 참조를 리턴하는 것은, 호출하는 함수의 사용의 범위 안에 이미 cout이 들어 있기 때문에, 문제를 일으키지 않는다.

◆◆객체 리턴에 대한 관찰

●멤버 함수 또는 독립함수가 객체를 리턴할 때, 사용자는 몇 가지 중에서 선택할 수 있다.

▷객체에 대한 참조, 객체에 대한 const 참조, 객체, const 객체를 리턴할 수 있다. 

 

◆const 객체에 대한 참조 리턴

●const 참조를 사용하는 주된 이유는 효율성이다.

▷객체를 리턴하는 것은 복사 생성자를 호출하지만, 참조를 리턴하는 것은 그렇지 않다.

▷참조는 호출하는 함수가 실행중일 때 존재하는 객체에 대한 참조여야 한다.

▷매개변수가 const 참조라면 리턴형도 const가 되어야 한다.

 

◆const가 아닌 객체에 대한 참조 리턴

●cout과 함께 사용하기 위한 대입 연산자의 오버로딩과 << 연산자의 오버로딩이, const가 아닌 객체를 리턴하는 두 일반적인 예이다.

●operator=()의 리턴값은 연쇄적인 대입에 사용된다.

String s1("Good stuff");
String s2, s3;
s3 = s2 = s1;

▷참조를 사용하면, 그 함수가 String 복사 생성자를 호출하여 새로운 String 객체를 만드는 일을 피할 수 있다.

 

●operator<<() 의 리턴값은 연쇄적인 출력에 사용된다.

String s1("Good stuff");
cout << s1 << "is coming!";

▷여기서, operator<<(cout,s1)의 리턴값은 문자열 "is coming!"을 출력하는 데 사용되는 객체가 된다. 

▷여기서 리턴형은 ostrream이 아니라 ostream &이 되어야 한다.

▷ostream 리턴형을 사용하면, ostream 복사 생성자의 호출을 요구할 것이다.

▷ostream 클래스는 public 복사 생성자를 만들지 않는다.

 

◆객체 리턴

●리턴되는 객체가 피호출 함수에 지역적이면, 함수가 종료될 때 그 지역적인 객체가 파괴자를 호출하기 때문에, 참조로 리턴하면 안된다.

●이러한 경우에는 참조가 아니라 객체를 리턴해야 한다. 일반적으로, 오버로딩 산술 연산자들이 이 범주에 속한다. 

 

◆const 객체 리턴

●앞에서 설명 한 객체 리턴에 대입연산자를 사용해 rvalue를 lvalue로 쓰려는 시도가 일어날 수 있다.

//operator+() 반환 값에 vector(100)을 대입
vector(1) + vector(2) = vector(100)

●하지만 객체 리턴은 사용 후 파괴되기 때문에 문제가 된다.

●이를 해결하기 위해 리턴형에 const 객체로 선언하는 것이다.

 


◆◆객체를 지시하는 포인터

 


●new에 의한 객체 초기화

일반적으로, Class_name이 클래스 이름이고 value가 Type_name형의 값이면, 다음과 같은 구문은

Class_name * pclass = new Class_name(value);

다음과 같은 생성자를 호출한다.

Class_name(Type_name);

다음과 같은 사소한 변환도 이루어질 수 있다.

Class_name(const Type_name &);

또한, int를 double로 변환하는 것과 같은, 원형 일치에 의해 호출되는 변환은 모호성이 없는 한 이루어질 것이다. 다음과 같은 형식의 초기화는 디폴트 생성자를 호출한다.

Class_name * ptr = new Class_name;

◆new와 delete

◆파괴자들이 호출될 때 

●객체가 자동 변수이면 그 객체의 파괴자는, 프로그램이 그 객체가 정의된 블록을 벗어날 때 호출된다.

●객체가 static 변수이면, 그 객체의 파괴자는 프로그램이 종료될 때 호출된다. 

●new에 의해 생성된 객체라면, 그 객체의 파괴자는 그 객체에 대해 명시적으로 delete를 사용할 때 호출된다.


◆◆테크닉의 복습

◆<<연산자의 오버로딩

  • cout과 함께 사용하여 객체의 내용을 출력할 수 있도록 Much Less-Than(<<) 연산자 함수를 정의한다.
ostream & operator<<(ostream & os, const c_name & obj)
{
	os<<...;
    return os;
}

 

◆변환 함수들

  • 어떤 하나의 값을 클래스형으로 변환하려면, 다음과 같은 원형을 가지는 클래스 생성자를 작성한다.
c_name(type_name value);
  • 클래스형을 다른 데이터형으로 변환하려면 클래스 멤버 함수를 작성한다.
operator type_name();

▷암시적 변환이 이루어지지 않게 하려면, 생성자를 선언할 때 키워드 explict를 사용해야 한다.

 

 

◆생성자가 new를 사용하는 클래스

클래스 멤버에 의해 지시되는 메모리를 대입하기 위해 new 연산자를 사용하는 클래스를 설계할 때, 몇 가지 점에서 주의할 필요가 있다.

  • new에 의해 대입된 메모리를 지시하는 클래스 멤버는, 클래스 파괴자에서 delete 연산자를 그것에 적용해야 한다. 
  • 파괴자가 클래스 멤버인 어떤 포인터에 delete를 적용하여 메모리를 해제한다면, 그 클래스의 모든 생성자들은 new를 사용하거나, 그 포인터를 널 포인터로 설정함으로써, 그 포인터를 초기화해야 한다.
  • 생성자들은 new [] 또는 new를 사용할 수 있으나 둘을 섞어서 사용할 수 없다. 생성자가 new[] 를 사용한다면, 파괴자도 delete []를 사용해야 한다. 생성자가 new를 사용한다면, 파괴자도 delete를 사용해야 한다.
  • 기존의 메모리를 지시하는 포인터를 복사하지 않고, 새로운 하나의 객체를 다른 객체로 초기화할 수 있다. 복사 생성자는 다음과 같은 형식의 원형을 가져야 한다.
className(const className &);
  • 다음과 같은 원형으로 함수 정의를 가지며, 대입 연산자를 오버로딩하는 클래스 멤버 함수를 정의해야 한다.(여기서 c_pointer는 c_name의 멤버이고 type_name을 지시하는 포인터형이다.) 다음의 예는 생성자가 new []를 사용하여 c_pointer 변수를 초기화한다고 가정한다.
c_name & c_name::operator=(const c_name & cn)
{
	if(this == & cn)
    	return *this;
    delete [] c_pointer;
    //type_name형 단위로 size개만큼 복사하도록 설정
    c_pointer = new type_name[size];
    //그 다음에 cn.c_pointer가 지시하는 데이터를
    //c_pointer가 지시하는 위치에 복사한다.
    ...
    return *this;
}

◆멤버 초기자 리스트 문법

  • ClassY가 클래스이고 mem1, mem2, mem3가 클래스 데이터 멤버라면, 클래스 생성자는 다음과 같은 문법을 사용하여 그 데이터 멤버들을 초기화시킬 수 있다.
ClassY::ClassY(int n, int m) : meml(n), mem2(0), mem3(n*m + 2)
{
	...
}

개념적으로 이들 초기화는 중괄호 안의 코드가 실행되기 전, 객체가 생성될 때 이루어진다, 다음과 같은 사항에 주의해야 한다.

  • 이 형식은 생성자에만 사용할 수 있다.
  • static이 아닌 const 데이터 멤버를 초기화하려면 이 형식을 사용해야 한다.
  • 참조 데이터 멤버를 초기화하려면 이 형식을 사용해야 한다.

데이터 멤버들은, 멤버 초기자 리스트에 나열된 순서가 아니라, 클래스 선언에 선언된 순서대로 초기화된다.

덧붙여, 멤버 초기자 리스트에 사용되는 괄호 형식은 일반적인 초기화에서도 사용할 수 있다.

'C++ > C++' 카테고리의 다른 글

[C++] Rule of three  (0) 2023.08.27
Overloading  (0) 2023.07.28
11. 클래스의 활용  (0) 2023.07.19
10. 객체와 클래스  (0) 2023.07.17
9. 메모리 모델과 이름 공간  (0) 2023.07.14
'C++/C++' 카테고리의 다른 글
  • [C++] Rule of three
  • Overloading
  • 11. 클래스의 활용
  • 10. 객체와 클래스
메카인
메카인
  • 메카인
    메카인의 지식창고
    메카인
  • 전체
    오늘
    어제
    • 분류 전체보기
      • 코딩 공부
        • TIL(Today I Learn)
        • TIL
        • 백준(C++)
        • Python
        • 알고리즘
        • 프로젝트 회고
      • C++
        • C++
        • C++ STL
        • C,C++ mCoding yotube
      • 게임개발
        • 언데드서바이벌_골드메탈_클론코딩
        • 3D_골드메탈_클론코딩
        • 유니티_문제해결
        • 게임 수학
      • Unreal 공부
        • UE5 GameDev
        • Unreal Engine 4 C++ The Ult..
      • 교재 문제 풀이
        • 운영체제
      • 정보처리기사
        • 정처기 요약
        • 정처기 오답노트
      • 학교수업
        • 데이터베이스
        • 프로그래밍 언어론
        • 리눅스 시스템
        • 네트워크
      • 일상
        • 주식
        • 독서
        • 일기
      • (비공개 전용)
        • memory
        • Build
        • OOP
        • Smart Pointer
        • lamda
        • 게임 수학
        • 모던 C++
        • 모던 C++ STL
        • 모던 C++ Concurrency, Paralle..
        • 책
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
    • 글쓰기
    • 블로그 관리
  • 링크

  • 공지사항

    • 공지사항 - 인생과 블로그의 목표
  • 인기 글

  • 태그

    ~
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
메카인
12. 클래스와 동적 메모리 대입, +추가 필요
상단으로

티스토리툴바