본문 바로가기

C++

[TIL 21장] 이니셜라이저, const 멤버 변수/함수, static 멤버 변수/함수, this

오늘의 TIL 목차 (22.09. 01)

 

  • 이니셜 라이저(feat. const 멤버 변수)
  • 클래스 const 멤버 변수/함수
  • 클래스 static 멤버 변수/함수
  • this

이니셜 라이저


[ 이니셜 라이저 ]

: const 멤버 변수(상수)를 초기화하기 위한 문법 ( 상수 멤버 변수가 없어도 사용됨 )

클래스명::생성자 : 변수명(초기화값) // : 변수명(초기화값)이 이니셜라이저
{
}

-

class CDainn
{
private:
	// 이니셜 라이저를 사용하지 않으면 m_ciA는 쓰레기값이 상수화 되어버림
	// 생성자 몸체는 '대입을 통한 초기화'라 쓰레기값이 상수화된 상수는 몸체 안에서 초기화 불가능(대입 X)
	const int m_ciA; 
	int m_iB;
public:
	CDainn();
    ~CDainn();
}
// 생성자 정의부
CDainn::CDainn() : m_icA(), m_iB(10) //정적변수, 전역변수는 Data영역의 BSS에 의해 자동 0초기화
{
};
// 기본 생성자 실행 시 m_icA는 0, m_iB는 10으로 초기화

※ 생성자에서 이니셜 라이저 사용 시 상수 변수를 초기화하지 않으면 실행 오류

 

■ 기능 / 주의

[객체 생성 순서]
1. 메모리 할당
2. 이니셜 라이저 실행 (생략 가능)
3. 생성자 몸체 코드 실행
4. 객체 생성
* 메모리 할당과 생성자 실행 2가지 조건이 충족되어야지만 객체 생성
* C시절의 calloc, malloc, free는 객체를 자동 생성 및 실행하지 않기 떄문에 올바른 객체 생성 X
  • 클래스 내에 const 상수가 있으면 무조건 모든 생성자에 이니셜라이저로 const 상수를 초기화해줘야 한다.
  • 구조체는 이니셜 라이저 사용 불가능, 포인터는 가능
    - 몸체 안에서 memset함수 이용하여 전체 초기화 또는 구조체 멤버 변수 지정하여 대입
  • 이니셜라이저는 상수뿐만 아니라 속도가 빠르기 떄문에 변수들을 초기화하는데도 주로 사용된다.
  • 생성자 몸체 코드 안의 초기화는 '대입을 통한 초기화', 이니셜 라이저는 '선언과 동시에 초기화'와 유사하다.
    - 따라서 상수 멤버 변수는 몸체 코드 안에서 불가(이니셜 라이저로만 가능)하고 이니셜라이저의 속도가 훨씬 빠르다.
  • 생성자의 매개변수로 바로 초기화가 가능하다. ( ex. Dainn::Dainn(int _iA) : m_iA(_iA) {} )

■ 예시

 

읽기 전용 함수 ( =const 멤버 함수 )


[ 읽기 전용 함수  ( =const 멤버 함수 ) ]

: const 멤버 함수로 멤버 변수를 읽기만 가능(쓰기 불가능)하게 하는 함수

class CDainn
{
public:
	반환타입 함수명(매개변수) const;
};

반환타입 함수명(매개변수) const // 읽기 전용 함수
{
	몸체 코드
}

■ 기능 / 주의

  • const 함수는 클래스 내의 멤버로서만 존재
  • const 함수 오버로딩이 가능 ( 외부 함수에서 클래스 객체 생성 시 const 클래스 객체로 생성하면 const 함수 실행 )
  • const 함수 내에선 const 함수만 사용 가능(일반 멤버함수 사용 불가능), 일반 함수내에선 const 함수 사용 가능 
  • const 클래스 객체는 클래스 내의 const 함수만 실행 가능(일반 멤버함수 실행 불가능)
  • 읽기 전용 함수 내에 선언된 변수는 대입 가능(cin도 가능), 멤버 변수만 대입 등 쓰기 불가능 (읽기만 가능)

■ 예시

- string, strcpy_s(), memset(), 이니셜 라이저, const 멤버 함수 오버로딩 및 읽기 전용 함수 다 응용

#include "stdafx.h"
#include <string> // string 함수 사용 시 헤더 포함

struct tInfo
{
	char szName[10];
	int iA;
	const int ciA;
	string sString;

	tInfo() : ciA(0) {}; // const 변수는 이니셜 라이저로 초기화 필수 (안해주면 기본 생성자 없어서 실행 불가능)
};

class cDainn
{
	tInfo m_tInfo;
	const int m_ciX;
	float m_fiY;

public: // 생성자 및 소멸자 (선언 안해줘도 됨, 자동 실행되기 때문 but 선언 시 정의부까지 꼭 써줘야함)
	cDainn();
	~cDainn();

public:
	void Initialize();

public:
	void Render();
	void Render() const;
};

// 기본 생성자
cDainn::cDainn() : m_ciX(100), m_fiY(10.f) // 생성자에서는 객체 초기화만
{
	// ZeroMemory(&m_tInfo,sizeof(m_tInfo)) // #include <windows.h> 헤더파일 포함 시 가능
	memset(&m_tInfo, 0, sizeof(m_tInfo)); // 구조체 초기화
    
	
}

// 소멸자
cDainn::~cDainn()
{

}

// 초기화 함수
void cDainn::Initialize()
{
	char sCopy[10];
	cout << "문자열을 입력하시오: ";
	cin >> m_tInfo.sString; 
    // 구조체 문자열 멤버 변수에 string문자열 대입
    // char 문자 배열에 string 문자열 대입 시 c_str()함수를 이용해 string을 char로 변환해야함
	strcpy_s(m_tInfo.szName, sizeof(m_tInfo.sString), m_tInfo.sString.c_str()); 
	m_tInfo.iA = m_ciX; // 구조체 멤버 변수 iA에 상수 멤버변수 값 대입
	++m_tInfo.iA; // iA 1 증가
}

// 출력 함수
void cDainn::Render()
{
	Initialize();
	cout << "[일반 Render 함수]" << endl;
	int iTemp = 0;
	cout << "숫자를 입력하시오: ";
	cin >> iTemp;
	cout << "구조체 이름 : " << m_tInfo.szName << endl;
	iTemp += m_ciX;
	cout << "입력 받은 값 + const 멤버 변수 = " << iTemp << endl;
}

void cDainn::Render() const
{
	//Initialize(); // 읽기 전용 함수 내에선 읽기 전용 함수만 참조 가능
	cout << "[const Render 함수]" << endl;
	int iTemp = 0;
	cout << "숫자를 입력하시오: ";
	cin >> iTemp;
	cout << "구조체 이름 : " << m_tInfo.szName << endl; // Initialize에서 값 안 받았으니 memset한 것에 따라 '널' 출력
	iTemp += m_ciX;
	cout << "입력 받은 값 + const 멤버 변수 = " << iTemp << endl;
}

void main(void)
{
	cDainn cD; // 기본 생성자 클래스 객체
	const cDainn CCD; // 읽기 전용 함수만 불러오는 기본생성자 const 클래스 객체

	cD.Render(); // 일반 Render 함수 실행
	//CCD.Initialize(); // const 클래스 객체는 const 멤버 함수만 불러오기 가능
	CCD.Render(); // const Render 함수 실행
}

※ const가 붙은 것은 합리적이든 비합리적이든 프로그래머 사이에서 건들지 않기로 약속한 것

 

static 변수 / 함수 ( = 클래스 변수 / 함수 )


[ 클래스 변수 ]

: static 변수는 함수 및 사용자 정의 자료형(구조체, 클래스) 내에서만 사용할 수 있는 지역 변수의 특성을 가지면서,

데이터는 Data 영역에 저장되어 프로그램 종료 시까지 소멸되지 않는 전역 변수의 특성을 가지고 있다.

: 클래스 내의 static 변수를 클래스 변수라고 하며, 클래스 내부에 선언되어 있지만 멤버 변수가 아닌 독립적인 존재이다.

class 클래스명
{
	static 자료형 클래스변수명; // 클래스 변수는 멤버 변수 X
}

//초기화 방법
자료형 클래스명::클래스변수(초기화값);

-

class cDainn
{
private:
	static int _isA;
}

// int cDainn::_isA(0);과 동일
int cDainn::_isA; // 정적 변수 및 전역 변수는 Data의 Bss 영역에 의해 자동 0 초기화됨

 

■ 기능 / 주의

  • 클래스 변수는 클래스와 개별적인 존재로 class의 크기에도 포함되지 않는다.
    - class와 객체 생성 시점, 공간 할당 위치, 메모리 할당 시점이 다르다. 
  • 클래스 변수는 외부에서 초기화를 해줘야된다. ( 클래스 내부에서 불가능 )
    - 멤버 변수가 아니기 때문, 클래스 변수는 존재 여부를 알려주는 역할만 한다.

class CObj
{
private:
	static int m_siA;
};

sizeof(CObj); 크기는 1byte ( 클래스 변수 미포함 )
- class는 몸체에 아무것도 없어도 1byte를 기본으로 가진다.

 

[ 클래스 함수 ]

: 클래스 내의 static 함수를 클래스 함수라고 부르며, class와 개별적인 존재이다. (멤버 함수X)

class 클래스명
{
public:
	static Sum(int _iA) {return ++_iA};
}

※ 클래스 함수 내에서는 클래스 멤버 변수 및 함수 사용 불가능, 클래스 변수는 사용 가능

 

■ 기능 / 주의

  • 클래스 함수 내에서는 클래스 멤버 변수 및 멤버 함수 사용 불가능, 클래스 변수 및 함수는 사용 가능
    - 클래스와 메모리 할당 시점이 다르기 때문
  • 클래스 함수는 외부 함수에서 호출할 때 클래스 객체 없이 소속을 밝혀 불러올 수 있다.
    - 클래스명::클래스함수 ( 멤버 함수처럼 '클래스객체.클래스함수' 도 가능)
[ 클래스 함수 호출 방법 ]
cDainn 클래스가 있다고 할 때,

1. 일반적인 멤버 함수 호출 방법
클래스 객체 선언 cDainn cD;
멤버변수 호출 방법 cD.클래스함수명();

2. 소속을 밝혀 호출하기 - 클래스 객체 선언 안해도 됨
cDainn::클래스함수명(); // 멤버 함수가 아니기 때문에 가능

■ 예시

1. static 함수는 멤버 함수 호출 불가 및 클래스 객체 없어 소속으로 호출 가능

#include "stdafx.h"

class cDainn
{
private:
	static int siA; // static은 멤버 변수가 아니므로 객체 할당 시점이 달라서 존재 여부만 알려주는 용도
	int m_iB;

public:
	cDainn() : m_iB(100)
	{
		//siA = 10;
	}

public: // 반환 함수
public:
	void Render(int _iA)
	{
		cout << siA << "\t" << m_iB << endl;
	}
	
	static void Render()
	{
		cout << "값 : ";
	}

	static void Plus() // 클래스 함수 내에서는 멤버 변수/함수 사용 불가능
	{
		Render(); // 함수 오버로딩에 의해 static Render 실행, 클래스 함수 사용 가능
		//Render(10); // 멤버 함수 사용 불가능
		cout << ++siA << endl; // 클래스 변수 사용 가능
		//cout << m_iB << endl; // 멤버 변수 사용 불가능


	}
};

// 정적변수와 전역변수는 초기화하지 않아도 Data의 Bss에 의해 자동 0 초기화
// static 변수는 멤버 변수가 아니기 때문에 외부에서 초기화해야됨
// static 객체 생성 후 class 객체 생성되는 듯 (그래서 class 생성자에 siA=10하면 10이 출력됨) 그래도 쓰지마
int cDainn::siA; 


void main(void)
{
	cDainn cD;
	cD.Plus();
	cDainn::Plus(); // 클래스 변수/함수는 멤버가 아니기 때문에 소속:: 으로 호출 가능
	//cout << cD.m_iB << endl; //private이라 이렇게 못해, Get_함수 선언해서 return으로 해
}

 

2. 각 개별된 같은 클래스 타입의 동적 배열이 static 변수의 값을 전역변수처럼 이어서 사용

#include "stdafx.h"

struct tInfo
{
	char szName[10];
	int iA; // 문자 배열 인덱스
};

class CObj
{
private:
	int m_iA; // 문자열에 대입할 아스키코드값
	static int s; // 문자열에 대입할 아스키코드값 static 타입
	tInfo _stC;
	// _stC대신 _stD를 사용했다면 cobj[1 - 2]에 따로 대입하지 않아도
	// cobj[0]에서 대입한 값이 Data영역에 남아있어 [1 - 2]에도 szName에 값이 존재할 것임
	// _stC의 경우 각각의 동적배열(힙 메모리)에서 개별로 값을 가지기 때문에 초기값인 널이 존재. 
	static tInfo _stD; 
	
public: // 생성자
	CObj() : m_iA(65), _stC() // 구조체도 초기화됨
	{
	}

public:
	void Initialize();
	void Static();
	void Zero();

public:
	void Render();
	static void sRender();
};

tInfo CObj::_stD = {}; // static 구조체 초기화 ( ={}; 안해줘도 자동 0 초기화)
int CObj::s(65); // 문자열에 대입할 값, 65 == A

void CObj::Initialize() // 구조체 문자열 szName에 알파벳 대입해주는 멤버 함수
{

	// 클래스 내 함수에서 멤버 변수 값 변경 시 함수가 종료되어도 소멸이 아닌 클래스 내에 그대로 유지된다.
	// cobj[0 - 1 - 2]마다 0번째부터 채워지는 이유는 애초에 처음부터 3개의 cobj가 개별로 존재
	// 구조체 _stC의 문자열 szName에 값 채우기
	cout << "Render" << endl;

	for (; _stC.iA < 10; ++_stC.iA) // 구조체의 문자열의 크기까지 증가하며 대입
	{
		_stC.szName[_stC.iA] = m_iA; // m_iA는 65로 알파벳 대입
		cout << _stC.szName[_stC.iA] << "\t";
		++m_iA; // 문자열에 A부터 1씩 증가하며차례로 대입
	}
	cout << endl;

	// 함수가 종료되면 m_iA는 소멸되기 때문에 기존 m_iA인 65임
}

void CObj::Static() // Initialize()와 동일한데 알파벳을 담당하는 변수가 static 변수(클래스 변수)
{
	// 구조체 _stC의 문자열에 값 채우기
	// ???? 여기는 _stC.iA와 m_iA가 0으로 초기화 안되고 10, 75 유지
	cout << "Render" << endl; 

	//m_iA = 65; // 아스키코드로 65는 A

	for (; _stC.iA < sizeof(_strC.szName); ++_stC.iA) // 문자열의 크기만큼 대입
	{
		_stC.szName[_stC.iA] = s; // 클래스 변수(s=65인 상태)로 알파벳 대입
		cout << _stC.szName[_stC.iA] << "\t";
		++s; // 클래스변수(static 변수)라 다른 CObj 타입 동적 배열에서도 기존의 값이 이어서 들어감.
	}
	cout << endl;
}

void CObj::Zero() // 메모리 초기화 함수
{
	memset(&_stC.szName, 0, sizeof(_stC.szName));
	//_stC.iA = 0;
}

void main(void)
{
	CObj* cobj = new CObj[3]; // 클래스 CObj 타입 동적 배열 3개 생성
	cout << "[ 일반적인 동적 배열 ]" << endl;
	cobj[0].Initialize();
	cobj[1].Initialize();
	cobj[2].Initialize();

	/*
	for(int i = 0; i<3; ++i) // 각 동적 배열의 문자열 초기화 
		cobj[i].Zero();

	// 각 동적 배열 안의 멤버 변수 값이 그대로 유지되어 있기 때문에
	// 현재 _stC.iA와 m_iA는 10, 75인채로 이어서 될거임.
	// 근데 문자열 대입하는 함수 조건이 _stc.iA<10까지 넣는거라 문자열에 값이 안 넣어질 뿐
	/*cobj[0].Initialize();
	cobj[1].Static();
	cobj[2].Static();
	*/
	
	// 아예 개별적인 cobj배열(cobj 클래스 구조만 같을 뿐)로 존재(포인터도 각각 있음)
	// 이때 당연히 각 배열들은 기본 생성자에 의한 초기값을 갖고 있는데
	// static 변수는 배열이 개별적인 존재임에도 불구하고 해당 변수는 공용으로 이어서 사용하는 것
	cout << "[ static변수 사용 ]" << endl;
	cobj[3].Static();
	cobj[4].Static();
	cobj[5].Static();
    
    delete[] cobj; // 동적 할당은 꼭 해제해줄 것
    cobj = nullptr; // dangling 포인터 발생을 방지하기 위해 nullptr 대입
}

 

this


[ this ]

: 객체의 주소를 저장한 포인터 변수 ( 자기 자신을 가리킴 )

■ 기능 / 주의

  • this는 객체의 주소를 담는 포인터로, 객체 밖의 외부 공간에 메모리가 할당되어 있다.
  • 모든 매개변수에는 보이지 않지만 this 포인터를 받게 되어있다. ( 어디서든 this 사용 가능 )
  • this는 멤버 함수 내에서만 사용 가능하다. ( 자기 자신을 알아야 하기 때문 )
class CDainn
{
    CDainn& THIS() // CDainn&으로 반환하여 복사 생성자 일어나지 않도록 함
    {
        return *this; // this는 자기자신의 주소이므로 *this는 CDainn 그 자체
    }
    
    void Print() { cout << this << endl; }
}

void main(void) {
	CDainn cd;
    cout << &cd << endl; // &는 cd의 주소, 둘 다 주소 동일
    cd.Print(); // this는 cd 객체 주소
    cd1.THIS().Print(); // THIS()로 자기자신을 반환하였으니 멤버 Print() 호출 가능
}

출력 결과 : 셋 다 똑같은 CDainn 객체 cd의 주소 ( this는 자기자신 주소이므로 )