7강입니다 constructor 01

생성자와 소멸자에 대해 설명합니다.

sturct 를 클래스로 변경했습니다. class로 변환 시 안에 모든 데이터 멤버들을 public으로 전환해줘야합니다. 이렇게 변환 시 바꾸기 전 sturct와 유사하게 변환됩니다. 

#include <iostream>



class KPeople 
{
public:
	int age; // 데이터 멤버(date member)
	char name[12];

	void PrintPeople() // 멤버 함수 (member function)
	{
		printf("%s : %i\r\n", name, age);
	}
	void SetAge(int age_) 
	{
		age = age_;
	}
	void SetName(const char* name_)
	{
		strcpy_s(name, name_);
	}
};

void main() 
{
	KPeople a;
	a.SetAge(48);
	a.SetName("John");
	a.PrintPeople();
}

 

 

클래스를 설계하다 보면 객체를 만들자 마자 초기화를 해 줄 필요가 있습니다. 위 코드에선 age, name을 초기화 할 필요가 있다고 과정해보겠습니다. 초기화하지 않은 변수를 접근하는걸 위험하기 떄문입니다.

그리고 어떤 이유에서 객체를 파기하기전에 마무리 해줄 필요가 있다면 멤버 함수로 정의 할 수 있습니다.

class KPeople 
{
public:
	int age; // 데이터 멤버(date member)
	char name[12];

	
	void Initialize() 
	{
		age = 0;
		name[0] = 0;
	}

	void Finalize() 
	{
		// 마지막에 해체 전 실행될 멤버 함수
	}

	void PrintPeople() // 멤버 함수 (member function)
	{
		printf("%s : %i\r\n", name, age);
	}
	void SetAge(int age_) 
	{
		age = age_;
	}
	void SetName(const char* name_)
	{
		strcpy_s(name, name_);
	}
};

위와 같이 멤버 함수의 Initialize와 Finalize를 만들어주고 사용하게 되면 아래와 같습니다.

void main() 
{
	KPeople a;
	a.Initialize();

	a.SetAge(48);
	a.SetName("John");
	a.PrintPeople();
	
	a.Finalize();
}

따로 메인에서 초기화를 안할 시 쓰레기값이 들어가 잘못된 결과값이 날 올 수 있기때문에 외에도 여러 상황에서도 사용 가능합니다. 이와 같이 객체를 만들자 마자 초기화하는 함수를 생성자라고합니다. 반드시 자기 자신과 똑같은 이름을 가져야합니다. 객체를 만들자마자 자동으로 호출되기때문에 함수 호출 이름을 명시 할 방법이 없습니다. 함수가 종료되기전에 Finalize도 자동으로 명시 할 방법 또한 없습니다. 그래서 특별한 이름을 가져야합니다.

그래서 C++에서는 객체를 만들자 마자 자동으로 호출되는 초기화 함수를 생성자라 합니다.

클래스의 생성자를 정의하는 방법은 아래와 같습니다.

KPeople() //constructor
{
  age = 0;
  name[0] = 0;
  printf("constructor()\r\n");
}

소멸자 함수는 특별한 문법을 따라줘여합니다. 그리고 소멸자는 객체가 파괴되기 바로 전에 호출되는 함수입니다.

~KPeople() //destructor
{
	printf("constructor()\r\n");
}

이렇게 하는 경우 이제는 자동으로 호출되므로 따로 생성자, 소멸자를 만들고 main에 올릴 필요 없습니다.

void main() 
{
	KPeople a;
	a.SetAge(48);
	a.SetName("John");
	a.PrintPeople();
}

이제 KPeople을 동적할당 해보겠습니다.

	KPeople* p;
	p = (KPeople*)malloc(sizeof(KPeople));
    
	free(p);

malloc이 리턴하는 값은 보이드 포인터이기 때문에 캐스팅을 해줘야합니다. (KPeople*) 그리고 사이즈는 KPeople만큼 필요하므로 KPeople을 넣어줍니다. 그리고 메모리 해제 또한 해줘야합니다. 이렇게 될 시 이제 p를 통해서 접근이 가능하게됩니다.

요기서 빌드 시 쓰레기값이 나옵니다. 생성자에서 초기화를 해줬지만 쓰레기 값이 나오는 이유는 메모리 동적 할당 시 생성자를 자동 호출해주지 않습니다. 그래서 p를 이용해서 생성자를 호출해주는 부분을 직접 작성해줘야합니다. 소멸자도 마찬가지 입니다.

void main() 
{
	KPeople* p;
	p = (KPeople*)malloc(sizeof(KPeople));
	p->KPeople::KPeople();

	p->~KPeople();
	free(p);
}

KPeople만 입력 시 클래스 이름과 같아 모호함이 발생하므로 KPeople에 생성자를 호출한다고 명시해줍니다. 소멸자 경우 ~가 붙어있어 상관없습니다.

하지만 전에도 말했듯이 C++에선 new와 delete를 사용합니다.

	KPeople* p;
	p = (KPeople*)operator new (sizeof(KPeople)); // new 함수 ( new function)
	p->KPeople::KPeople();

	p->~KPeople();
	operator delete(p);

operator new 또한 보이스 포인터이므로 (KPeople*)로 캐스팅 해줍니다. operator new 경우 전역함수라 생각하면됩니다. operator new를 함수 이름이라고 생각하는게 나중에 헷갈리지 않는다고 합니다. 

하지만 이또한 생성자를 따로 호출해줘야합니다. 하지만 C++ 설계자들이 동적으로 할당 하고 자동으로 생성자를 호출 하도록 operator new와 생성자를 호출하도록 new expression을 만들었습니다 .

	p = new KPeople;
    
	delete p;

이것의 의미는 동적으로 KPeople만큼 메모리에 할당하고 자동으로 생성자 자동으로 호출하라는 뜻입니다.

소멸자 경우도 마찬가지 입니다. p의 경우는 KPeople 포인터입니다.

하지만 그럼 operator new는 필요없는거 아니냐 할 수 있지만 new expression은 오버로딩이 불가합니다 그래서 new의 오버로딩이 필요한 경우 operatorn new로 오버로딩을 해야합니다. 하지만 동적으로 메모리를 할당할 일이 있다면 new expression, delete expression을 사용하면 됩니다.

8강입니다.

 생성자의 파라미터가 들어갈 시 () 안에 들어갑니다. 없을 시 생략해도됩니다. 있어도 생략안하고 적어도 작동합니다.

p = new KPeople();

이런 식으로 처리 가능합니다.

p = new KPeople[3];

연속적으로 메모리 할당이 가능합니다. 뜻은 KPeople의 크기의 메모리를 3개를 할당한것입니다. new 에 []를 사용 시 delete[] p 와 같이 딜리트에도 표시해줘야합니다. 물론 3개를 만들기때문에 생성자 3번 소멸자 3번이 작동합니다.

그리고 접근 시엔 아래와 같이 접근 가능합니다. 0 , 1, 2 등 직접적인 접근이기 때문에 포인터가 아닌 객체가 되므로 . 을 사용합니다.

p[0].PrintPeople();
p[1].PrintPeople();
p[2].PrintPeople();

그리고 오버로딩을 생성자를 오버로딩 할 수 있습니다.

class KPeople 
{
public:
	int age; // 데이터 멤버(date member)
	char name[12];

	
	KPeople() //constructor
	{
		age = 0;
		name[0] = 0;
		printf("constructor()\r\n");
	}
	KPeople(const char* name_) // overloading
	{
		age = 0;
		strcpy(name, name_);
		printf("constructor(char*)\r\n");
	}

	~KPeople() //destructor
	{
		printf("destructor()\r\n");
	}

	void PrintPeople() // 멤버 함수 (member function)
	{
		printf("%s : %i\r\n", name, age);
	}
	void SetAge(int age_) 
	{
		age = age_;
	}
	void SetName(const char* name_)
	{
		strcpy_s(name, name_);
	}
};
// 1번째 생성자
KPeople p;

// 2번째 생성자
KPeople p("John");
KPeople p = "John"; // 암시적인 생성자 호출 implicit construton call

 

void Test( KPeople p )
{
	p.PrintPeople();
}

void main()
{
	KPeople p = "John";
	Test("Mike");
}

암시적 생성자 호출을 할 시 이런식으로 사용 가능하지만 이렇게 사용 할 시 문제가 될 수 있습니다. 그래서 생성자 호출을 할 때 명시적인 호출만 받겠다라고 지정할 수 있습니다.

explicit KPeople(const char* name_)
{
  age = 0;
  strcpy(name, name_);
  printf("constructor(char*)\r\n");
}

생성자에 explicit을 앞에 붙여주면 됩니다. 이렇게 될 시 암시적 호출을 할 시 컴파일 에러를 발생 시킵니다. 이렇게 바꾸고 위위 코드에서 컴파일 할 시 오류를 발생하는걸 볼 수 있습니다. 그래서 사용을 하려면 아래와 같이 수정합니다.

void main()
{
	KPeople p ("John"); // implicit construction call
	
    	KPeople temp = KPeople("Mike");
	Test(KPeople("Mike")); //temporary object
}

KPeople temp에 생성자로 KPeople("Mike")를 전달 시키는 겁니다.

Test 함수에 직접 단독으로 쓰인 경우는 KPeople("Mike")를 KPeople타입에 객체 파라미터로 "Mike"를 전달하고 임시 스택에 만들어지는 임시 객체를 생성한다는 뜻입니다. 그래서 이문장은 생성자를 호출합니다. 

근데 이런식으로 코드를 짠 후 컴파일 해서 결과를 보면 생성자 3번 소멸자가 4번이 호출되는 현상이 생깁니다.

9강입니다.

위와 같이 할 시 생성자가 p , temp, KPeople , Test 에서의 KPeople p 에서 생성자가 총 4번 생성 소멸자가 똑같이 4번 생성되어야하지만 생성자가 3번만 생성됩니다. 왜 그런지 메모리를 그려보겠습니다.

정리도 할겸 천천히 메모리를 그려가며 내려가겠습니다. 항상 주소는 임의에 값을 적어논겁니다.

class KPeople 
{
public:
	int age; // 데이터 멤버(date member)
	char name[12];
    
void main() 
{
	KPeople p("John");

메인에 들어왔을때 p가 할당됩니다. p는 KPeople 타입으로 int 4바이트 char[12] 12바이트 총 16바이트를 차지합니다. name의 시작주소는 5004 +4된 값이 됩니다. 5000+15까지 차지하는 16바이트의 크기의 메모리가 됩니다. 

	KPeople() //default constructor
	{
		age = 0;
		name[0] = 0;
		printf("constructor()\r\n");
	}
	explicit KPeople(const char* name_)
	{
		age = 0;
		strcpy(name, name_);
		printf("constructor(char*)\r\n");
	}

"John" 전달합니다. 그렇게되면 2번째 생성자가 호출되어서 name을 복사하고 age를 0으로 만듭니다.

이렇게 들어옵니다. 하지만 age랑 name이라는 값은 이곳에 표기되어있지 않습니다. 혼동을 피하기 위해서 지워줍니다. 그리고 이제 temp 객체를 만듭니다.

KPeople temp = KPeople("John");

Temp를 만들었습니다 . 다음 KPeople("Mike")를 임시로 호출합니다.

Test(KPeople("Mike")); //temporary object

이 객체는 어디로 만들어질지 모릅니다. 실제로 스택에 만들어질 확률이 높습니다. 주소와 이름이 없습니다. 그래서 temporary object 라 부릅니다.

이렇게 생성자가 총 3번 호출됐습니다. 임시로 만들어진 객체를 이제 Test에 전달됩니다.

void Test(KPeople p) 
{
	p.PrintPeople();
}

파라미터로 KPeople p를 메모리에 할당합니다. 요기서 생성자가 3번만 호출된 이유를 알 수 있습니다. Test 함수를 진행하며 KPeople p가 생성된게 아닌 임시객체로 생성된 ?를 그대로 복사한 형태여서 Test 함수에선 생성자를 호출하지 않습니다.

이렇게 Test 함수를 작동하면 출력하고 일을 끝낸 Test 함수를 메모리 해제하며 그전에 소멸자가 호출됩니다.

그리고 이제 main으로 돌아가 스택메모리에서 순서대로 메모리 해제 전에 소멸자가 호출됩니다. 그래서 총 4번 호출이 됩니다.

생성자와 소멸자가 쌍으로 안이루어질때 오류가 발생할 수 있습니다. 그럼 근본적인 문제인 객체가 복사가 되는 경우에 호출되는 특별한 생성자를 정의를 해줘야합니다. 이걸 copy constructor라고 합니다.

copy constructor는 객체가 복사되는 문법을 표현해주기 위해서 자기 자신의 타입에 레퍼런스(&)를 매개변수로 받습니다. 복사되는 시점에 다시 복사를 막기 위해서 입니다. 

KPeople(const KPeople& rhs) // copy constructor
	{
		printf("copy constructor()\r\n");
	}

문법 기억

class KPeople 
{
public:
	int age; // 데이터 멤버(date member)
	char name[12];

	
	KPeople() //default constructor
	{
		age = 0;
		name[0] = 0;
		printf("constructor()\r\n");
	}

	explicit KPeople(const char* name_)
	{
		age = 0;
		strcpy_s(name, name_);
		printf("constructor(char*)\r\n");
	}

	KPeople(const KPeople& rhs) // copy constructor
	{
		age = rhs.age;
		strcpy_s(name, rhs.name);
		printf("copy constructor()\r\n");
	}
	~KPeople() //destructor
	{
		printf("destructor()\r\n");
	}
};

void Test(KPeople p) 
{
	p.PrintPeople();
}

void main() 
{
	KPeople p("John"); // 암시적인 생성자 호출 implicit construton call
	KPeople temp = KPeople("John");
	Test(temp); //temporary object
}

오류 시 strcpy_s를 사용합니다. 요기서 주의 해야할 점은 이미 복사한 값을 호출 할 시 복사 생성자가 호출된다라는 점입니다.

Test(temp)로 변경 이유는 KPeople("Mike")를 작성 시 생성자 한번 복사 생성자 한번이 호출이 되는데 컴파일 최적화로 생성자 한번만 호출되어 복사생성자를 호출하지 않는 문제가 있어서 이미 복사된 값을 사용했습니다. 위 메모리를 그리며 정리한걸 봐도 ?를 Test로 복사한 형태라서 생성 후 복사 호출되는게 맞지만 최적화된것 같습니다. 

객체가 복사되는 시점과 생성되는 시점을 이해하면 좋습니다.

10강입니다. 

객체를 만드는 팩토리 함수를 만듭니다.

KPeople GetPeople()
{
	KPeople t;
	return t;
}
or
KPeople GetPeople()
{
	return KPeople("Mary"); //RVO
}


void main() 
{
	KPeople t;
	t = GetPeople();
	//Test(t); // temporary object
}

 

생성자가 2번 작동합니다. 하지만 복사생성자가 작동하지 않는건 아닙니다. class 내에서 private 처리해버리면 오류가 발생합니다. 이유는 RVO에 의해 스킵될뿐 복사생성자를 검사를 하지 않는게 아니기 때문입니다.

RVO는 컴파일 최적화 형식으로 생성자를 최소화 합니다.

'프로그래밍 공부 > C++' 카테고리의 다른 글

C++ (1)  (0) 2021.07.07

C++ 강좌를 보면서 배운걸 정리합니다.

유니티 포트폴리오로는 C++ 회사에 지원하기 힘든 것 같아 언리얼 엔진4을 만져보기 위해 공부하는 중입니다.

포트폴리오로 만들기 위해서 어떤 방향으로 해야할지는 C++ 공부 후 검토해볼 생각입니다.

참고 강좌 : C++

struct 강좌는 총 3강입니다.

사용할 구조체 형태입니다.

struct People // type definition
{
	int age; // member variable
	char name[10];
}/*a*/; 

People 부분은 태그네임이라고 합니다. struct People이라는 타입으로 정의된 동시에 a라는 변수로 정의된 상태입니다. 구조체는 타입도 정의할 뿐만 아니라 변수도 정의할 수 있는 특별한 구조를 가지고 있습니다.

그리고 구조체로 타입을 정의 할때 변수를 정의 할 수 있지만 가독성을 위해 변수를 바로 선언하지 않고 이후 사용 할때 다시 변수로 선언하여 사용합니다.

구조체를 사용 시 

void main ()
{
    People a;
    a.age = 28;
    strcpy_s(a.name, "Hello");

    //strut People b; strut People이 타입 이름
    People b; // strut 생략가능

    b.age = 29;
    strcpy_s(b.name, "World");
}

strut에 경우 C에선 생략이 불가능합니다. strcpy_s에 경우 문자열을 복사하는 함수입니다. 

이렇게 작성 후 출력을 해보겠습니다.

void PrintPeople(People A) {
	printf("%s : %i\r\n", a.name, a.age);
}

PrintPeople(a);
PrintPeople(b);

결과 값

Hello : 28

World : 29

 

2강 정리입니다. 아래는 전에 코드를 정리했습니다.

struct People // type definition
{
	int age; // member variable
	char name[10];
};

void main()
{
	People a;
	People b;
	 
	a.age = 47;
	strcpy_s(a.name, "Hello");

	b.age = 48;
	strcpy_s(b.name, "World");

	PrintPeople(a);
 	PrintPeople(b);
}

구조체를 main의 스택 메모리를 그리면 아래 그림이 됩니다.

내부 단편화 - 표기된건 10바이트지만 윈도우에서 효율적인 메모리 관리를 위해 4바이트씩 끊어서 사용 
그래서 실제로 12바이트를 차지하고 마지막 2바이트가 비어있게 됩니다.

age 부분은 주소값이지만 편의를 위해 age로 표기했습니다.

실제로 위와 같은 구조를 가졌는지 테스트를 해보도록 하겠습니다.

	char *cp = (char*)&a;

캐스팅(형변환)을 한 이유는 &a의 시작 주소는 구조체의 시작 주소이므로 char포인터에 대입이 불가해서 캐스팅을 해줬습니다.

 

그리고 테스트를 위해 e를 출력해보겠습니다.

 위 그림 처럼 메모리를 그린게 맞다면 메모리 위치를 계산 시 +5를 할 시 e를 찾을 수 있습니다.

	printf("%c\r\n", cp[5]);

*(cp + 5) 브레킷연산자를 사용해 cp[5]로 출력하면 e가 나옵니다.

void main()
{
	People a;
	People b;
	 
	a.age = 27;
	strcpy_s(a.name, "Hello");
	char *cp = (char*)&a;
	printf("%c\r\n", cp[5]);

	b.age = 46;
	strcpy_s(b.name, "World");

	PrintPeople(a);
}

 이 상황에서 PrintPeople 함수를 호출 시 

PrintPeople 만큼의 메모리를 할당합니다.

메모리 몇번째인지는 함수호출이 일어나면서 중간에 메모리를 차지하므로 메모리 정확한 계산은 힘듭니다.

프린터피플의 스택프레임, 메인 스택 프레임을 나눠지며 메인에서 정의한 a 값이 프린트피플에서 할당한 A 공간으로 복사가 됩니다.

위와 같이 되므로 프린터피플에서 a.age의 값을 바꿔도 값은 변경되지 않습니다. 단순하게 복사한 값이기 때문입니다. 이렇게 함수 안에서 변경된 값은 함수 밖 값에 영향을 줄 수 없습니다. 이걸 함수에서 변경을 하기 위해선 포인터를 전달해야합니다.

3강 정리입니다.

void PrintPeople(People a) {
	printf("%s : %i\r\n", a.name, a.age);
	++a.age;
}

포인터를 사용하기전 테스트를 해봤습니다. 위 함수에 ++a.age를 추가합니다.

함수를 실행 시 PrintPeople 프레임 age에 +1이 늘어나지만 함수를 빠져나오는 PrintPeople 값이 팝되어서 main 값엔 영향이 없습니다.

a를 변경되지 않고 +1 계산된 값을 출력하고 싶은거면 맞지만 의도가 PrintPeople에서 계산값된 값을 main에 있는 age 값을 변경하고 싶으면 위와 같이 구조체를 전달해서는 바끼지 않습니다.

 ++a.age부분을 제거 후 다시 함수를 만듭니다.

void SetAge(People *A, int newAge) 
{
	(*A).age = newAge;
}

void main()
{
	SetAge( &a, a.age + 1 );
	PrintPeople(a);
}

필요한 부분만 적었습니다. a = 47입니다.

SetAge는 매개변수로 들어온 구조체(A).age를 newAge로 들어온 값으로 바꿔주는 역할을 합니다.

원래 함수는 값의 변경이 안되었는데 이번 함수의 다른 점은 변경을 필요로 한 값을 포인터로 넘겨주는겁니다. 메모리로 가서 직접 수정을 하는것이죠. 그리고 코드상에선 함수에게 줄 땐 & 주소연산자로 넘겨주고 함수에선 * 참조연산자로 받습니다. 이렇게 받으면 A는 현재 구조체 a를 복사한게아닌 구조체 a를 가르키는 포인터입니다. 그래서 (*A)를 사용했습니다. 이렇게 한 이유는 A는 더이상 구조체가 아니기 때문에 . 을 사용할 수 없습니다.

그래서 (*A) *A포인터가 가르키는 내용이 이게 구조체가 됩니다.

그리고 그값에 newAge를 대입하고 SetAge 함수를 빠져나옵니다. 그러면 SetAge부분이 스택에서 팝됩니다.

그리고 PrintPeople( a ) 부분이 실행되면서 함수가 호출됩니다. 이때 SetAge에 의해서 a.age는 47에서 48로 대입된 상태입니다. 48이 출력됩니다. 

메모리 또한 위에서 본것과 같이 a 구조체를 복사 스택에 push 출력 후 pop됩니다. 그리고 메인으로 돌아옵니다.

해보고 알 수 있는건 포인터로 전달 시 값에 대해서 변경 가능한 점과 포인터 경우 4바이트만 사용하여 메모리를 효율좋게 사용 할 수 있습니다.

void PrintPeople(People *A)
{
	printf("%s : %i\r\n", (*A).name, (*A).age);
}

// main 부분

	PrintPeople(&a);

구조체가 클 경우 단순 복사만 해도 메모리에 부담이 갑니다. 그래서 포인터로 메모리 최적화를 요 할 수 있습니다.

4강입니다.

구조체에 접근 하기 위해선 . 을 사용합니다. 하지만 구조체 포인터일 때는 People *A;에 A는 구조체가 아닌 포인터입니다. 그래서 *A가 가르키는 내용이 구조체입니다. 포인터는 가르키는것이기 때문에 연산전에는 포인터인채이고 연산자 우선 순위로 인해 먼저 계산이 안될 수 있으므로 (*A)로 표기해줍니다. 하지만 구조체를 메모리 효율을 위해 포인터를 사용하는 일이 많습니다. 매번 (*A)를 적긴 많이 번거롭습니다. 그래서 만들어진게 arrow operator 이며 dot operator 도 있습니다. 둘다 구조체를 위한 오버레이터입니다. dot operator는 구조체 자체가 선언되었을때 구조체 멤버로 접근합니다. arrow operator는 구조체 포인터로 선언되었을때 구조체 멤버로 접근합니다. 요기서 dot operator는 이때까지 써온 . 이란걸 알 수 있습니다. 그럼 arrow operator는 어떻게 사용하냐면 (*A).age ===> A -> age , * . 가 -> 로 바뀐 형식입니다.

void PrintPeople(People *A) {
	printf("%s : %i\r\n", A->name, A->age);
}

void SetAge(People *A, int newAge) 
{
	A->age = newAge;
}

arrow operator 로 수정한 함수 코드입니다.  - > 띄어씌기는 안됨

	People *b;
    
	b -> age = 48;
	strcpy_s(b -> name, "World");
    
   	PrintPeople(b);

이런식의 예제도 만들 수 있습니다. 구조체 변수로 생성하지 않고 포인터로 바로 생성 시는 dot이 아닌 arrow operator로 표기하며 포인터를 받는 함수의 경우 & 주소연산자를 사용하지말고 바로 포인터인 b를 넘겨주면 됩니다.

하지만 요기서 오류가 발생합니다. 이유는 그림으로 보면 이렇습니다.

b -> age = 48; 대입을 하는 순간 할당되어있지 않은 메모리를 사용해서 에러가 발생하는 상태입니다. 그래서 구조체가 가르킬 곳을 메모리를 할당한 다음 사용해줘야합니다.

void main()
{
	People *b;

	b = (People*)malloc(sizeof(People));

	b -> age = 48;
	strcpy_s(b -> name, "World");

	SetAge( b, b->age + 1 );
	PrintPeople(b);

	free(b);
}

malloc으로 가르키고 있는 곳에 메모리를 할당 해주고 sizeof 해서 사이즈를 정해줍니다. 하지만 이렇게 하면 void리턴이라서 오류가 납니다. malloc은 자동적으로 자료형을 정해주지 않아서 캐스팅을 해주어야합니다. 그래서 (People*) 를 적어줍니다.

코드를 보면 포인터는 따로 초기화 하지 않아 갈비지로 초기화 되어있습니다. 그리고 malloc으로 People (14) 사이즈 만큼 할당 됩니다. 위치는 알 수 없습니다. 임의로 정하겠습니다. malloc을 쓰고 나선 항상 free()를 사용해줍니다. 스택 메모리 처럼 그려놨지만 malloc이나 new 같은 동적 할당 메모리는 크기를 넓게 쓰기 위해 만들어진 곳이라서 heep 메모리에 저장됩니다. 

	b -> age = 48;
	strcpy_s(b -> name, "World");

 

위에 값을 기입하게 됩니다.  그 후에 

	SetAge( b, b->age + 1 );
	PrintPeople(b);

그 후에 함수를 실행하게 될 시 b 이미 포인터인 녀석을 넘겨주게 됩니다. 포인터 b를 넘겨주고 b를 구조체화하여 age 값에 +1 하여 함수를 계산합니다.

그후 출력합니다. 또한 밑에 함수도 정상 출력됩니다.

5강입니다.

메모리 계산 부분에서 실수를 해서 그림을 다시 고쳤습니다.

메모리 할당을 malloc로 하고 free로 했었는데 C++에선 같은 역할을 하지만 다른 형식이 있습니다. new 와 delete입니다. new는 메모리 할당을 하고 생산자 Constructro 라는 특수한 함수를 호출하고 delete는 메모리 해제를 하기 전에 파괴자라는 특별한 함수를 호출 합니다.

약간 다릅니다. 다른 부분은 나중에 따로 포스팅하겠습니다.

C++의 구조체 대해서 알아보겠습니다. C++ 구조체는 구조체 안에 함수를 가질 수 있습니다.

struct People // type definition
{
	int age; // member variable
	char name[10];  
	
	void PrintPeople(People *this_) { // member function
		printf("%s : %i\r\n", this_->name, this_->age);
	}

	void SetAge(People * this_, int newAge)
	{
		this_->age = newAge;
	}
};

특이한 형태입니다 구조체안에 들어간 함수는 이제 함수라고 하지 않고 멤버 함수라고 합니다.

호출 방법은 구조체에 접근 후 멤버함수를 호출하면됩니다.

6강입니다.

위와 같이 사용하게 될 시 위 함수를 보면 매개변수로 this_ 라는 포인터를 따로 main에서 받아 함수를 실행하는데 구조체에 멤버함수가 포함되어있는 구조체에서 멤버함수를 사용 시 구조체를 매개변수로 받을 일이 있을 시 매개변수가 아닌 함수내에서 this로 사용할 수 있습니다. 위와 같이 멤버함수는 항상 this 자기 자신을 항상 매개변수로 전달하는데 이걸 this 콜이라고 합니다.

	void PrintPeople()  // member function
	{
		printf("%s : %i\r\n", this->name, this->age);
	}

	void SetAge(int newAge)
	{
		this->age = newAge;
	}

 

멤버함수를 수정한 코드입니다. 그래서 PrintPeople은 전달하는 인자가 0개가 아닌 1개입니다.

그리고 5강을 통해 C의 함수와 C++의 함수가 다르는걸 알 수 있었습니다. 실제로 오버로딩때문에 C++에서 함수를 정의하면 함수이름 그대로 유지하지 않고 컴파일러가 변경합니다. 이걸 네임 맹글링 (Name Mangling)이라고 합니다. 그리고 멤버함수 역시 그냥 함수와 다릅니다. 자기 자신인 객체의 시작주소가 항상 전달됩니다. 그리고 멤버 함수의 경우 this라고 적어되지만 보통 this->를 생략해서 사용한다고 합니다.

	void PrintPeople()  // member function
	{
		printf("%s : %i\r\n", name, age);
	}

	void SetAge(int newAge)
	{
		age = newAge;
	}

 

void main()
{
	People *b;

	b = new People;

	b -> age = 48;
	// C 호출 관례 
	// C++ 호출 관례
	strcpy_s(b -> name, "World");
    
	// this 호출 관례
	b->SetAge( b->age + 1 );
	b->PrintPeople();

	delete b;
}

등 구별이 가능합니다. 함수 호출 관례에 대해선 나중에 따로 다루겠습니다.

그래서 이렇게 변수와 함수를 다 가진 구조체를 class라고 합니다. 그래서 struct를 class로 바꿔주면 됩니다. 하지만 접근 권한 문제가 발생하는데 아래와 같이 수정하면 됩니다.

class People // type definition
{
public:
	int age; // member variable
	char name[10];

	void PrintPeople()  // member function
	{
		printf("%s : %i\r\n", name, age);
	}

	void SetAge(int newAge)
	{
		age = newAge;
	}
};

링크드 리스트를 작성하기 위해서는 구조체 포인터를 자기자신의 멤버로 사용해야합니다.

그리고 구조체 멤버를 int 같은 타입이 아니라 특정한 몇 비트를 차지하도록 비트 필드 구조체를 만들수도 있습니다. 또 공용체는 구조체는 멤버를 위해서 모두가 메모리가 할당됩니다. 이런 멤버들 중에서는 가장 큰 멤버를 위해서만 메모리 할당이 됩니다. 하지만 이런것들은 나중에 따로 알아보고 지금은 문법에 집중하겠습니다.

'프로그래밍 공부 > C++' 카테고리의 다른 글

C++ (2)  (0) 2021.07.08

+ Recent posts