[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;
}
```
댓글
댓글 쓰기