티스토리 뷰

프로그래밍 공부/C++

C++ (1)

빅토리아레몬탄산수 2021. 7. 7. 00:29

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
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG more
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함