클래스 (1)

Yama·2023년 12월 21일
0

어소트락 수업

목록 보기
20/55

C 구조체와 C++ 구조체의 차이점

typedef struct tag MyStruct // 1
{
	int a;
}tagMyStruct;

struct MyType 				// 2
{
	int a;
}

typedef struct MyType 		// 3
{
	int a;
}MYTYPE;

int main()
{
	tagMyStruct; 			// 4
	MyType t; 				// 5
   	MYTYPE t;				// 6
    
    return 0;
}
  • // 1번은 C스타일 구조체 선언 방식이다.
    • // 4번처럼 typedef로 선언을해서 C구조체를 만든것.
    • 구조체 선언 동시에 이름을 tagMyStruct로 준것이다.
  • // 2번은 C++ 구조체는 맞지만 C컴파일러에서는 오류가 난다
    • // 5번은 오류가 난다.
    • 오류가 안나게 할려면 struct MyType t; 해야함.
  • // 3번은 c 스타일 구조체다.
    • 6번처럼 하면 된다.
  • 결론 : C스타일에서는 구조체 변수를 선언하는데 매우 귀찮다.

클래스

  • C++에서 추가됬다.
  • 자신만의 자료형을 만드는것이라는게 구조체랑 동일하다.
  • 구조체랑 차이점
    1. 생성자, 소멸자
    2. 맴버 함수
    3. 연산자 오버로딩
    • 연산자 오버라이딩은 다른것
    1. 상속
    2. 다형성
    3. 추상화
  • 클래스를 추가하면서 얻는것
    1. 캡슐(데이터 묶는다)
      • 구조체도 똑같은 효과
    2. 은닉성
      • 구조체는 맴버에 어디서든 접근해서 막을 방법이 없었다.(상태를 망침)
    3. 상속, 다형성, 추상화 이런 기능을 구현해야된다.

접근 제한 지정자

class Test
{
private:
	int 	m_i;
    float	m_f;
};

int main()
{
	Test a;
    
    a.m_i = 10;
    a.m_f;
    
	return 0;
}
  • 종류

    1. private

      • 접근 제한하는거.
    2. public

      • 공개하겠다.
    3. protected

      • 상속을 사용할때 쓰는거라 나중에 배움. 상속을 안해주면 private이랑 동일한 효과.
  • private:, 이런식으로 쓰면된다.

  • 구조체는 따지고보면 public 아래에 있는것이다.

클래스 만들고 맴버 변수가 아무것도 없다면 1바이트로 잡아준다.

맴버 함수

#include <iostream>

// 3
class Test
{
private:					// 6 private으로: -> public으로 교체했다 생각.
	int		m_i;
	float	m_f;
    
public: 					

	void SetInt(int _a) 	// 1
    {
		m_i = _a; // 10
    {

	void SetFloat(int _f) 	// 2
	{
		m_f = _f;
	}

};


int main()
{
	Test a; 				// 3
	a.SetInt(10); 			// 4
	a.SetFloat(10.f); 		// 5
    
    Test b = {}; 			// 7
    b.m_i = 10;				// 8
    b.m_f = 10.f;			// 9

	return 0;
}
  • // 6을 private로 그대로 주면 오류가 난다.

    • 맴버를 private 필드에 선언했기 때문에, 직접적으로 수정하는것이 불가능하다.
  • // 6을 public로 바꾸면 밑에 8,9과 오류가 해결된다.

  • // 7은 Test 클래스에 b를 만듬.

    • // 8은 b개체에 접근해서 m_i값에 10을 넣어준것
    • // 9는 b개체에 접근해서 m_f값에 10.f값을 넣어준것.
  • // 1은 입력으로 들어온 값을 m_i에 넣겠다.

  • // 2는 입력으로 들어온 _f를 m_f에 넣겠다.

  • // 3는 Test클래스에 a개체 를 만듬.

  • 맴버 변수가 private일때 맴버 함수를 만들어서 개별 개체에 접근하는 법.

    • // 4는 int값인 10을 받아서 m_i값에 넣어준다
    • // 5는 float값인 10.f를 받아서 m_f값에 넣어준다.

맴버 변수는 private걸어서 막고 맴버 함수에 public해서 오픈하는 이유는 디버깅할떄 필요하다.

  • 디버깅할때 특정 값이 이상해거나 수정할떄 //10에 중단점을 걸고 값이 변경되는지 체크할때 편하게 사용할수 있다.

main.cpp

#include <iostream>

class Test
{
private:
	int		m_i;
	float	m_f;

	void SetInt(/*3.43*//*Test* pTest,*/ int _a)
	{
		//pTest->m_i = _a;
		this->m_i = _a;
	}
	void SetFloat(int _f)
	{
		m_f = _f;
	}
};


int main()
{
	Test a;
	Test b;
	b.SetFloat(200); 				// 4
	Test c;
	Test d;
	Test e;
	Test f;
	//SetInt(100); 					// 1
	//SetInt(&a, 100);				// 2
	//SetFloat(&a, 100);			// 3

	//a.SetInt(10)


	return 0;
}
  • // 1번같이 SetInt에 100을 했다면 Test 개체중 a,b,c,d,e,f어디에 100을 넣을지 모르기 때문에 이렇게 하면 안된다.
  • 기존의 전역함수는 작업의 개체의 원본 주소를 알려달라했었다.
    • // 2,3은 SetInt,SetFloat함수가 구조체였다면.. 저런식으로 줘야함.
  • // 4는 실제 클래스 개체인 b에 SetFloat함수를 호출해서 200을 넣고 싶다면 저렇게 해야한다.
  • 맴버 함수는 해당 클래스의 개체를 통해서 호출한다.
  • Test 클래스가 구조체 였다면 이런식으로 전달주소값을 넘겨주고 값을 줬어야됨.
	void SetInt(Test* pTest, int _a)
	{
		pTest->m_i = _a;
    }

그렇다면 실제 클래스는 어디서 주소를 연결해줄까?

	void SetInt(int _a)
	{
		/*this*/->m_i = _a;
	}
  • this는 지역변수를 자동으로 생성해서 C에서 하던걸(주소값 받아서 넣어주는걸) 자동으로 해준다.
  • 맴버 함수 안에는 자동으로 만들어지는 지역변수 this 포인터가 존재한다.
  • this 포인터 변수에는 해당 맴버함수의 호출시킨 개체의 주소가 들어간다.
  • 맴버함수 안에서 지칭하는 맴버 변수는 앞에 this-> 가 생략되어있다.
    • this-> 적어놔도 무방하다.
  • 맴버함수는 개체를 통해서 호출함으로써 이미 개체의 주소를 넘겨주었다.
  • 전역함수 구조체는 일일이 다 넘겨주었어야했다.

생성자, 소멸자 특징

  • 둘다 맴버 함수다.
  • 생성자
    • 동일한 형태의 클래스랑 같아야 되고 반환값을 안넣는다.
    • 개체가 만들어질떄 자동으로 호출된다.
  • 소멸자
    • 소멸자는 동일한 형태의 클래스로 선언한다.
    • 개체가 해제될때 자동으로 호출된다.
  • 동적 배열과 구조체는은 개체를 초기화할떄 초기화함수를 따로 구현해둬서 했다.
  • 클래스의 생성자는 이니셜라이즈(초기화)는 할 필요없이 생성자를 만들어 두면 알아서 해준다.
  • 클래스는 소멸자를 호출하면 간단하게 해제된다.
a.Test::Test();
  • 생성자 수동 호출하는법
  • 생성자 자동 호출
    • 디스어셈블리 창
    • 문법규칙에 맞춰서 알아서 해준다.(누군가 이미 구현해둠)
a.Test::~Test();
  • 소멸자 수동 호출법.
  • 소멸자 자동 호출
    • 메인함수가 종료되기전(return 0)일떄 자동 해제한다.

생성자 소멸자 생성 규칙

  • 디폴트 생성자,소멸자를 호출해준다.
    • 구현되어있는 생성자가 없다면 아무것도 없는 생성자를 자동으로 만들어준다
    • 소멸자도 마찬가지다.
  • 생성자는 개체가 생성될 떄 호출된다.
  • 소멸자는 개체가 소멸할 떄 호출된다.
  • 생성자 or 소멸자를 구현해 놓지 않으면 위 규칙 지키기 위해서 아무 일도 하지않는 기본 생성자, 기본 소멸자를 구현하며 구현내용은 다음과 같다.
#include <iostream>

class Test
{
private:
	int		m_i;
	float	m_f;


public:

	void SetInt(int _a)
	{

		this->m_i = _a;
	}
	void SetFloat(int _f)
	{
		m_f = _f;
	}
	Test()
		: m_i(0) 				// 5
		, m_f(0.f)				// 6	

	{
		this->m_i = 0; 			// 1
		this->m_f = 0.f; 		// 2
	}
	~Test()
	{

	}
};


int main()
{
	// 선언 
	int a; 						// 2
	// 대입
	a = 0; 						// 3
	//초기화 
	int a = 0;					// 4


	Test a;

	a.SetInt(10);
	a.SetFloat(10.f);
	Test b;
	b.SetFloat(200);

	return 0;
}
  • // 1, 2는 this를 활용해서 대입을 한것이다.
  • // 2는 int a 선언 // 3은 a에 0대입 // 4는 int a = 0으로 초기화
  • // 5, 6은 생성자 생기자마자 초기화해버린것
  • 보통 클래스는 // 1,2방법(대입)이 아니라 5, 6(초기화)방법으로 초기화를 한다.
    • 생성자 맴버 이니셜라이저
    • 대입과 초기화의 시점을 구분해서 생각을 해야된다.
    • 값들을 이니셜라이저로 초기값을 넣어주는게 좋다.

C++은 기초가 중요하당, 디테일한 문법들도 아주 정확하게 암기하고 알고 있어야 한다.

함수 오버로딩

int Add(int a, int b)
{
	return a + b;
}

int Add(int a, int b, int c) 
{
	return a + b + c;
}

float Add(float f1, float f2) 
{
	return f1 + f2;
}
  • 함수의 이름은 같지만, 함수의 인자 개수 or 타입이 달라서 구별할수 있는 여지가 있는 경우에 동일한 함수 이름으로 여러개의 함수를 만들 수 있다.
int Add(int a, int b)
{
	return a + b;
}

float Add(int a, int b)
{
	return a + b;
}
  • 반환타입만 다른 경우는 오버로딩을 할 수 없다.
    • 호출하는 입장에서는 매개변수가 2개를 받아서 똑같은 걸로 봐서 재정의 오류가 발생한다.
int Add(int a, int b)
{
	return a + b;
}

int Add(int a, int b) 
{
	return a - b;
}
  • 둘은 값은 함수로 컴파일러가 인식해서 재정의 오류난다.
  • 함수 오버라이딩과 구분하자
    • 면접 단골 질문
  • 로딩
    • 짐을 나르다.?
  • 오버라이딩
    • 탑승하다 초과해서 탑승했다. 기존의 것을 바꿔썻다.
    • 부모 클래스에 구현된걸 자식 클래스에 똑같이 구현해서 덮어버리는것.

인자를 2개 받는 생성자.

#include <iostream>
class Test
{
private:
	int		m_i;
	float	m_f;


public:
	void SetInt(int _a)
	{

		this->m_i = _a;
	}
	void SetFloat(int _f)
	{
		m_f = _f;
	}
	Test()
		: m_i(0)
		, m_f(0.f)	
	{
	}

	Test(int _i, float _f) 	// 2
		: m_i(_i)
		, m_f(_f)
	{

	}

	Test(int _i) 			// 4
		: m_i(_i)
		, m_f(0.f)
	{

	}
	~Test()
	{

	}
};


int main()
{
	// 6.15
	Test a(1, 2.4f); // 1
    
    Test a(2);		// 3

	return 0;
}
  • // 1는 Test클래스 개체인 a에 인자 두개(int, float)를 전달함.
  • // 2는 int형 1개와 float형 1개를 전달하니까 // 2 로 간다.
  • // 3는 Test클래스 개체인 a에 인자 한개(int)를 전달함.
  • // 4는 int형 인자 1개를 받으니까 //4 로간다.

주의점

int main()
{
	Test a();
	return 0;
}
  • 이건 함수 전방선언했다함.
  • 그래서 기본생성자 호출할때는 ()를 생략하고 호출해야된다.

참고

	a.Test::SetInt(10); 	// 1
    a.SetInt(10);			// 2
    
    a.Test::SetFloat(10.f); // 3
    a.SetFloat(10.f);		// 4
  • 1,2는 같은것이다. 2는 1의 생략버전.
  • 4,3는 같은것이다. 3은 4의 생략버전.

클래스 특징

#include <iostream>

// 1
class MyClass
{
private:
	float		m_f;
	long long	m_ll;

public:
	MyClass(float _f)
		: m_f(_f)
		, m_ll(0)
	{}
	~MyClass()
	{}
};


int main()
{
	MyClass a; // 1
	MyClass a();

	return 0;
}
  • 클래스는 접근 제한 지정자가 아무것도 명시하지 않았으면 private 필드를 기본으로 한다.
  • 생성자가 1개 이상 존재하면, 컴파일러는 기본 생성자를 만들어주지 않는다.
  • 이걸 인자하나를 만들어 주면 클래스를 생성할떄 기본 생성자는 만들어지지 않는다.
  • // 1은 오류가 발생한다.
    • 이유: 기본 생성자가 없기 떄문에, 존재하지 않는 함수를 호출할려고 해서 컴파일러 에러
  • 오버로딩 목록 보는법
    • Ctrl + Shift + 스페이스

강의 코드

main.cpp

#include <stdio.h>

int Add(int a, int b)
{
	return a + b;
}

float Add(float f1, float f2)
{
	return f1 + f2;
}

int Add(int a, int b, int c)
{
	return a + b + c;
}

typedef int INT;
typedef struct tagMyStruct
{
	int a;
}tMyStruct;

typedef struct MyType
{
	int a;
}MYTYPE;



class Test
{
private:
	int		m_i;
	float	m_f;

public:
	void SetInt(int _a)
	{
		this->m_i = _a;
	}

	void SetFloat(float _f)
	{
		this->m_f = _f;
	}

	Test()
		: m_i(0)
		, m_f(0.f)
	{
		// 대입
		//m_i = 0;
		//m_f = 0.f;
	}

	Test(int _i)
		: m_i(_i)
		, m_f(0.f)
	{

	}

	Test(int _i, float _f)
		: m_i(_i)
		, m_f(_f)
	{

	}


	~Test()
	{

	}
};


int main()
{
	//Test b = {};
	//b.m_i = 10;
	//b.m_f = 10.f;
	Test a;
	a.SetInt(10);
	a.SetFloat(10.f);

	Test b;
	b.SetInt(200);



	return 0;
}

main_02.cpp

#include <iostream>

class MyClass
{
private:
	float		m_f;
	long long	m_ll;

public:
	MyClass(float _f)
		: m_f(_f)
		, m_ll(0)
	{}

	~MyClass()
	{}
};

int main()
{

	MyClass a;


	return 0;
}

1차 23.12.21
2차 23.12.22
3차 23.12.25
4차 23.12.26
5차 23.12.27
6차 24.01.02

0개의 댓글