[C++] 스터디 CPPALOM 2주차: 열혈 C++ 프로그래밍 Chap 3-4
(파워 포인트 파일(.pptx)을 Markdown으로 변환하여 업로드하였음)
# <br>2주차 - 열혈 C++ 프로그래밍 Chapter 3-4 # <br>구조체 * C언어에서의 구조체 * “값의 집합” * C++에서의 구조체 선언 * C++에서의 구조체 변수의 선언 ```C++ struct Car { char gamerID[ID_LEN]; int fuelGauge; int curSpeed; }; ``` ```C++ Car basicCar; Car simpleCar; ``` * 자료형이란: * 값의 집합과 연산의 집합 * C언어에서는 값의 집합만 지원하므로, 다음과 같이 연산의 집합은 전역 함수로 선언해야 한다. * 이는 error-prone한 구조이다. ```C++ void ShowCarState(Car &car) { cout<<"소유자ID: "<<gamerID<<endl; cout<<"연료량: "<<fuelGauge<<"%"<<endl; cout<<"현재속도: "<<curSpeed<<"km/s"<<endl<<endl; } ``` C++에서는 연산 역시 구조체에 선언할 수 있음 ```C++ struct Car { char gamerID[CAR_CONST::ID_LEN]; int fuelGauge; int curSpeed; void ShowCarState(); }; void Car::ShowCarState() { cout<<“ 사용자ID: "<<gamerID<<endl; cout<<“ 연료량: "<<fuelGauge<<"%"<<endl; cout<<“ 현재속도: "<<curSpeed<<"km/s"<<endl<<endl; } ``` # <br>클래스 * 클래스의 선언 * struct 키워드를 class로 쓰면 된다. * 클래스와 구조체의 차이: * 접근 지시자의 차이 * 구조체는 기본이 public, 클래스는 기본이 private. ```C++ class Car { char gamerID[CAR_CONST::ID_LEN]; int fuelGauge; int curSpeed; void ShowCarState(); }; void Car::ShowCarState() { cout<<“ 사용자ID: "<<gamerID<<endl; cout<<“ 연료량: "<<fuelGauge<<"%"<<endl; cout<<“ 현재속도: "<<curSpeed<<"km/s"<<endl<<endl; } ``` # <br>접근제어 지시자 ```C++ class Car { private: char gamerID[CAR_CONST::ID_LEN]; int fuelGauge; int curSpeed; public: void InitMembers(char * ID, int fuel); void ShowCarState(); void Accel(); void Break(); }; void Car::InitMembers(char * ID, int fuel) { strcpy(gamerID, ID); fuelGauge=fuel; curSpeed=0; }; ... int main(void) { Car run99; run99.InitMembers("run99", 100); run99.Accel(); run99.Accel(); run99.Accel(); run99.ShowCarState(); run99.Break(); run99.ShowCarState(); return 0; } ``` * public * 어디서든 접근 허용 * protected * 상속 관계에 놓여있을 때, 유도 클래스에서의 접근 허용 * private * 클래스 내에서만 접근 허용 * 일반적인 선언: * 세부 구현인 변수는 private으로 * 인터페이스인 함수는 public으로 # <br>주의점 * private은 클래스에 대한 접근제어 지시자이다. * per-class basis이지, per-object basis가 아니다. * 서로 다른 객체이더라도, 같은 클래스라면 직접 접근할 수 있다! ```C++ class Vector2D { private: int x; int y; public: Vector2D(int x, int y) : x(x), y(y) {} int getX(); int getY(); Vector2D plus(Vector2D other) { // return Vector2D(x + other.getX(), y + other.getY()) return Vector2D(x + other.x, y + other.y); } }; ``` # <br>객체, 멤버변수, 멤버함수 * 클래스: * 설계도 * 객체: * 설계도를 통해서 생성된 실체(instance) * 멤버변수: * 클래스 내의 변수 * 멤버함수: * 클래스 내의 함수 # <br>파일분할 * 클래스의 선언과 정의를 분리한다. * 이는 Java에서 interface의 쓰임과 유사하다. * 즉, 같은 헤더의 내용을 정의하더라도 서로 다른 방식으로 구현된 여러 .cpp 파일들이 충분히 존재할 수 있다. * 이들은 Linker를 통해서 선택될 수 있다. # <br>파일분할 예시 ```C++ <Car.h> #ifndef __CAR_H__ #define __CAR_H__ namespace CAR_CONST { enum { ID_LEN =20, MAX_SPD =200, FUEL_STEP =2, ACC_STEP =10, BRK_STEP =10 }; } class Car { private: char gamerID[20]; int fuelGauge; int curSpeed; public: void InitMembers(char * ID, int fuel); void ShowCarState(); void Accel(); void Break(); }; #endif ``` 선언과 관련한 includ는 헤더 파일에, 구현과 관련한 include는 cpp 파일에! ```C++ <Car.cpp> #include <iostream> #include <cstring> #include "Car.h" using namespace std; void Car::InitMembers(char * ID, int fuel) { strcpy(gamerID, ID); fuelGauge=fuel; curSpeed=0; }; void Car::Accel() { if(fuelGauge<=0) return; else fuelGauge-=CAR_CONST::FUEL_STEP; if((curSpeed+CAR_CONST::ACC_STEP)>=CAR_CONST::MAX_SPD) { curSpeed=CAR_CONST::MAX_SPD; return; } curSpeed+=CAR_CONST::ACC_STEP; } ... ``` # <br>객체 활용하기 * 생성 * 일반적인 변수의 선언방식과 동적 할당방식이 있다. * 함수 호출 * <객체 이름>.<멤버함수 이름> ```C++ ClassName objName; ClassName* ptrObj = new ClassName; FruitSeller seller; FruitBuyer buyer; FruitSeller* objPtr1 = new FruitSeller; FruitBuyer* objPtr = new FruitBuyer; ``` ```C++ FruitSeller seller; seller.InitMembers(1000, 20, 0); FruitBuyer buyer; buyer.InitMembers(5000); buyer.BuyApples(seller, 2000); ``` # <br>정보은닉과 캡슐화 * Information Hiding * 필요한 정보만을 드러낸다. * Encapsulation * 관련있는 값의 집합과 연산의 집합을 묶는다. * 철학 * 정보 은닉: API와 세부 구현을 분리하며, 클라이언트는 API만 의존한다. * 캡슐화: 특정한 행위를 할 때, 사용자는 “편리”해야 한다. * 세부적인 내용들을 클라이언트가 일일이 조작하는 것 대신, 거대한 블랙박스에서 클라이언트 입력과 출력만을 제어한다. * 이를 테면, Façade Pattern. # <br>생성자와 소멸자 * 객체 생성 시 단 한 번만 호출되는 함수 * 특징: * 클래스의 이름과 함수의 이름이 동일하다. * 반환형이 선언되어 있지 않으며, 실제로 반환하지 않는다. * 오버로딩이 가능하다. * 매개변수에 디폴트 값을 설정할 있다. ```C++ #include <iostream> using namespace std; class SimpleClass { int num1; int num2; public: SimpleClass() { num1=0; num2=0; } SimpleClass(int n) { num1=n; num2=0; } SimpleClass(int n1, int n2) { num1=n1; num2=n2; } void ShowData() const { cout<<num1<<' '<<num2<<endl; } }; ``` ```C++ int main(void) { SimpleClass sc1; sc1.ShowData(); SimpleClass sc2(100); sc2.ShowData(); SimpleClass sc3(100, 200); sc3.ShowData(); return 0; } ``` # <br>멤버 이니셜라이저 Rectangle 클래스는 Point 두 개를 지닌다. 멤버 초기자를 이용해 이를 손쉽게 초기화할 수 있다. ```C++ #ifndef __RECTANGLE_H_ #define __RECTANGLE_H_ #include "Point.h" class Rectangle { Point upLeft; Point lowRight; public: Rectangle(const int &x1, const int &y1, const int &x2, const int &y2); void ShowRecInfo() const; }; #endif ``` ```C++ #include <iostream> #include "Rectangle.h" using namespace std; Rectangle::Rectangle(const int &x1, const int &y1, const int &x2, const int &y2) :upLeft(x1, y1), lowRight(x2, y2) { // empty } void Rectangle::ShowRecInfo() const { } ``` ```C++ class AAA { public: AAA() { cout<<"empty object"<<endl; } void ShowYourName() { cout<<"I'm class AAA"<<endl; } }; class BBB { private: AAA &ref; const int # public: BBB(AAA &r, const int &n) : ref(r), num(n) { } void ShowYourName() { ref.ShowYourName(); cout<<"and"<<endl; cout<<"I ref num "<<num<<endl; } }; ``` * 멤버 이니셜라이저의 이점: * 초기화의 대상을 명확히 인식할 수 있다. * 성능에 약간의 이점이 있다. * const와 참조자 역시 선언이 가능해진다! ```C++ int main(void) { AAA obj1; BBB obj2(obj1, 20); obj2.ShowYourName(); return 0; } ``` # <br>디폴트 생성자 * 생성자는 반드시 최소 하나를 갖는다! * 만약 생성자를 선언하지 않으면, 디폴트 생성자가 삽입된다. * 디폴트 생성자는 인자를 받지 않으며, 내부적으로 아무런 일도 하지 않는 생성자이다. * 다음 둘은 동일하다: * 주의점: * malloc()을 이용해 생성하면 생성자는 호출되지 않는다. ```C++ class AAA { public: AAA() { } void ShowYourName() { cout<<"I'm class AAA"<<endl; } }; ``` ```C++ class AAA { public: void ShowYourName() { cout<<"I'm class AAA"<<endl; } }; ``` # <br>private 생성자 * 만약 클래스 내부에서 객체를 생성할 것이라면, private으로 선언하여도 된다. * 참고: * Singleton Pattern ```C++ #include <iostream> using namespace std; class AAA { private: int num; public: AAA() : num(0) {} AAA& CreateInitObj(int n) const { AAA * ptr=new AAA(n); return *ptr; } void ShowNum() const { cout<<num<<endl; } private: AAA(int n) : num(n) {} }; ``` ```C++ int main(void) { AAA base; base.ShowNum(); AAA &obj1=base.CreateInitObj(3); obj1.ShowNum(); AAA &obj2=base.CreateInitObj(12); obj2.ShowNum(); delete &obj1; delete &obj2; return 0; } ``` # <br>소멸자 * 객체 소멸 시 반드시 호출되는 함수 * 특징: * 클래스의 이름 앞에 ‘~’가 붙은 형태의 이름을 갖는다. * 반환형이 선언되어 있지 않으며, 실제로 반환하지 않는다. * 매개변수는 void형으로 선언되어야 하기 때문에 오버로딩도, 디폴트 값 설정도 불가능하다. * 이 역시 선언하지 않을 시 자동으로 기본 소멸자가 삽입된다. ```C++ class AAA { public: AAA() { } ~AAA() { } }; ``` # <br>소멸자의 활용 * 소멸자를 이용해 객체 소멸 과정에서 처리해야 할 일들을 자동으로 처리할 수 있다. * 첨언: * 따라서, 동적으로 객체를 할당하는 일만을 처리하는 클래스를 선언하고 생성자와 소멸자를 이용해 이를 편리하게 다룰 수 있다. (클래스 바깥의 사용자는 이를 신경 쓰지 않아도 됨) * 이러한 것들을 컨테이너 클래스라고 한다. * 가령, vector 클래스 ```C++ #include <iostream> #include <cstring> using namespace std; class Person { private: char * name; int age; public: Person(char * myname, int myage) { int len=strlen(myname)+1; name=new char[len]; strcpy(name, myname); age=myage; } void ShowPersonInfo() const { cout<<"�̸�: "<<name<<endl; cout<<"����: "<<age<<endl; } ~Person() { delete []name; cout<<"called destructor!"<<endl; } }; ``` ```C++ int main(void) { Person man1("Lee dong woo", 29); Person man2("Jang dong gun", 41); man1.ShowPersonInfo(); man2.ShowPersonInfo(); return 0; } ``` # <br>객체 배열 ```C++ class Person { private: char * name; int age; public: Person(char * myname, int myage) { int len=strlen(myname)+1; name=new char[len]; strcpy(name, myname); age=myage; } Person() { name=NULL; age=0; cout<<"called Person()"<<endl; } void SetPersonInfo(char * myname, int myage) { name=myname; age=myage; } void ShowPersonInfo() const { cout<<"이름: "<<name<<", "; cout<<"나이: "<<age<<endl; } ~Person() { delete []name; cout<<"called destructor!"<<endl; } }; ``` 일반적인 변수 선언과 같이 선언할 수 있다. 단, 기본 생성자만을 호출할 수 있다. ```C++ int main(void) { Person parr[3]; char namestr[100]; char * strptr; int age; int len; for(int i=0; i<3; i++) { cout<<"이름: "; cin>>namestr; cout<<"나이: "; cin>>age; len=strlen(namestr)+1; strptr=new char[len]; strcpy(strptr, namestr); parr[i].SetPersonInfo(strptr, age); } parr[0].ShowPersonInfo(); parr[1].ShowPersonInfo(); parr[2].ShowPersonInfo(); return 0; } ``` # <br>객체 포인터 배열 ```C++ class Person { private: char * name; int age; public: Person(char * myname, int myage) { int len=strlen(myname)+1; name=new char[len]; strcpy(name, myname); age=myage; } Person() { name=NULL; age=0; cout<<"called Person()"<<endl; } void SetPersonInfo(char * myname, int myage) { name=myname; age=myage; } void ShowPersonInfo() const { cout<<"이름: "<<name<<", "; cout<<"나이: "<<age<<endl; } ~Person() { delete []name; cout<<"called destructor!"<<endl; } }; ``` 배열을 선언하는 대신, 객체 포인터 배열을 선언해 기본생성자 호출 없이 초기화해줄 수 있다. ```C++ int main(void) { Person * parr[3]; char namestr[100]; char * strptr; int age; int len; for(int i=0; i<3; i++) { cout<<"이름: "; cin>>namestr; cout<<"나이: "; cin>>age; parr[i]=new Person(namestr, age); } parr[0]->ShowPersonInfo(); parr[1]->ShowPersonInfo(); parr[2]->ShowPersonInfo(); delete parr[0]; delete parr[1]; delete parr[2]; return 0; } ``` # <br>this 포인터 * 객체 자신의 주소 값을 의미하는 포인터 * 특히, 멤버변수를 참조할 때 유용하게 사용함. * 가끔은 다음과 같은 엄격한 규칙을 적용하기도 함: * 멤버변수를 참조할 때에는 반드시 this 키워드를 붙여 참조한다. ```C++ #include <iostream> #include <cstring> using namespace std; class SoSimple { int num; public: SoSimple(int n) : num(n) { cout<<"num="<<num<<", "; cout<<"address="<<this<<endl; } void ShowSimpleData() { cout<<num<<endl; } SoSimple * GetThisPointer() { return this; } }; ``` ```C++ class SoSimple { int num; public: SoSimple(int n) { this->num = numn; } }; ``` # <br>Self-Reference의 반환 * 객체 자신을 참조할 수 있는 참조자를 반환하는 함수를 구성할 수 있다. * 참고: * Chaining * Builder Pattern ```C++ #include <iostream> using namespace std; class SelfRef { private: int num; public: SelfRef(int n) : num(n) { cout<<"SelfRef(int n)"<<endl; } SelfRef& Adder(int n) { num+=n; return *this; } SelfRef& ShowTwoNumber() { cout<<num<<endl; return *this; } }; ``` ```C++ int main(void) { SelfRef obj(3); SelfRef &ref=obj.Adder(2); obj.ShowTwoNumber(); ref.ShowTwoNumber(); ref.Adder(1).ShowTwoNumber().Adder(2).ShowTwoNumber(); return 0; } ```
댓글
댓글 쓰기