728x90

객체 배열

: 객체를 기반으로 객체 배열의 선언이 가능하다.


클래스 이름이 SoSimple일 때, 객체 기반의 배열을 다음과 같이 선언한다.


SoSimple arr[10];


동적 할당

SoSimple * ptrArr = new SoSimple[10];


이러한 형태의 배열 선언은, 열개의 SoSimple 객체가 모여서 배열을 구성하는 형태이다.

이 경우에도 생성자는 호출이 되나, 배열 선언과정에서는 호출할 생성자를 별도로 명시하지 못한다. (생성자에 인자를 전달하지 못함)


위와 같이 객체 기반 배열이 생성되려면 다음 형태의 생성자가 반드시 정의되야 하낟.

So Simple() { . . . .}


#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;
	}
	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;
	}
};

int main(void)
{
	Person parr[3];		// 객체 기반 배열의 선언

	char namestr[100];
	char * strptr;
	int age;
	int len;

	for (int i = 0; i<3; i++)	// for문을 통해 이름, 나이 입력받아서 객체를 초기화
	{
		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;
}


Output :

called Person()

called Person()

called Person()

이름: 가가가가가가

나이: 1

이름: 나나나나나나나나

나이: 2

이름: 다다다다다다다다다

나이: 3

이름: 가가가가가가, 나이: 1

이름: 나나나나나나나나, 나이: 2

이름: 다다다다다다다다다, 나이: 3

called destructor!

called destructor!

called destructor!



객체 포인터 배열

: 객체의 주소 값 저장이 가능한 포인터 변수로 이뤄진 배열


#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;
	}
	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 << "" << endl;		// 소멸자 호출과 delete 연산을 확인하기 위함
	}
};

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);	// 객체를 생성해, 주소값을 배열에 저장

	}
	for (int i = 0; i < 3; i++)
	{
		parr[i]->ShowPersonInfo();
		delete parr[i];		// new 연산을 한만큼 delete 연산 진행
	}

	return 0;
}


Output : 

이름: 가가가가, 나이: 22

이름: 나나나나나, 나이: 33

이름: 다다다다다다다, 나이: 44


이름: 가가가가, 나이: 22


이름: 나나나나나, 나이: 33


이름: 다다다다다다다, 나이: 44



this 포인터의 이해

: 멤버 함수 내에서 객체 자신을 가리키는 용도로 사용되는 포인터


#include <iostream>
#include <cstring>
using namespace std;

class SoSimple
{
	int num;
public:
	SoSimple(int n) : num(n)	// 생성자, 멤버 이니셜라이저 int num=n;
	{
		cout << "num=" << num << ", ";
		cout << "address=" << this << endl;
	}

	void ShowSimpleData()
	{
		cout << num << endl;
	}

	SoSimple * GetThisPointer()
	{
		return this;	
		// this를 반환. 이는 this가 가리키는 객체의 포인터를 반환하라는 의미로, 반환형이 SoSimple * 형 
	}
};

int main(void)
{
	SoSimple sim1(100);
	SoSimple * ptr1 = sim1.GetThisPointer();	// sim1 객체의 주소값을 * ptr1 에 저장	
	cout << ptr1 << ", ";		// ptr1에 저장된 주소값 출력
	ptr1->ShowSimpleData();		// ptr1이 가리키는 객체의 ShowSimpleData 함수 호출

	SoSimple sim2(200);
	SoSimple * ptr2 = sim2.GetThisPointer();
	cout << ptr2 << ", ";
	ptr2->ShowSimpleData();
	return 0;
}


Output:

1
2
3
4
num=100, address=0xffeb3708
0xffeb3708, 100
num=200, address=0xffeb3704
0xffeb3704, 200


this는 객체 자신을 가리키며, 위치한 객체내에서 자료형과 주소 값이 결정된다.

0xffeb3708에 할당된 sim1 객체 내에서는 this는 sim1 형의 포인터이면서 그 주소 값은 0xffeb3708 이 된다.



this 포인터 활용

객체의 주소 값으로 접근가능한 대상은 멤버 변수이다.

따라서 this->num1은 멤버 변수 num1을 의미한다.


위의 생성자는 아래의 같은 형태의 생성자로 대신할 수도 있다.


// this 포인터가 아닌 이니셜라이저를 통한 초기화 예

1
2
3
4
5
6
TwoNumber (int num1, int num2)
    : num1(num1), num2(num2)    
    // 이니셜라이저의 괄호안 num1은 매개변수 num1을 의미
{
    // empty
}
cs


멤버 이니셜라이저에서는 this포인터 사용불가. 



Self-Reference의 반환

: 객체 자신을 참조할 수 있는 참조자


this포인터를 이용해, 객체가 자신의 참조에 사용할 수 있는 참조자의 반환문을 구성 가능


#include <iostream>
using namespace std;

class SelfRef
{
private:
	int num;
public:
	SelfRef(int n) : num(n)
	{
		cout << "객체생성" << endl;
	}
	SelfRef& Adder(int n)
	{
		num += n;
		return *this;	// 이는 객체 자신의 포인터가 아닌 객체 자신을 반환
		// 함수의 반환형이 참조형(SelfRef &)이므로 객체 자신을 참조할 수 있는 참조자 반환
	}
	SelfRef& ShowTwoNumber()
	{
		cout << num << endl;
		return *this;
	}
};

int main(void)
{
	SelfRef obj(3);
	SelfRef &ref = obj.Adder(2);	// 객체 obj의 참조값을 반환, 참조자 ref가 객체 obj의 참조자가 됨

	obj.ShowTwoNumber();
	ref.ShowTwoNumber();

	ref.Adder(1).ShowTwoNumber().Adder(2).ShowTwoNumber();	
	return 0;
}


Output:

1
2
3
4
5
객체생성
5
5
6
8


34행에서  객체 ref의 Adder함수가 호출. Adder함수는 참조값을 반환하므로, 반환된 참조 값을 가지고 ShowTwoNumber 함수 호출이 가능하다.

마찬가지로 ShowTwoNumber가 반환하는 참조 값을 이용해 다시금 함수를 이어서 호출 할 수 있게 된다.


이는 두 함수 Adder, ShowTwoNumber가 객체의 참조 값을 반환하기 때문에 가능하다.



728x90

생성자 (constructor)

: 클래스의 이름과 동일한 이름의 함수이면서,

반환형이 선언되지 않았고 실제 반환하지 않는 함수가 생성자


생성자는 객체 생성시 멤버변수의 초기화에 사용가능 

(객체 생성시 생성자가 딱 한번 호출)



- 위에서 생성자가 정의되었으니 객체 생성과정에서 자동호출되는 생성자에게 전달할 인자의 정보를 추가


SimpleClass sc(20) // 생성자에 20 전달 (stack 할당)

SimpleClass * ptr = new SimpleClass(30); // (heap에 동적 할당)



생성자도 오버로딩과 매개변수의 default값 설정이 가능하다.


생성자의 오버로딩

#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;
	}
};

int main(void)
{
	SimpleClass sc1;
	sc1.ShowData();

	SimpleClass sc2(100);
	sc2.ShowData();

	SimpleClass sc3(100, 200);
	sc3.ShowData();

	return 0;
}


Output:

1
2
3
0 0
100 0
100 200



매개변수의 default 값 설정

#include <iostream>
using namespace std;

class SimpleClass
{
	int num1;
	int num2;

public:	
	SimpleClass(int n1=0, int n2=0)
	{
		num1=n1;
		num2=n2;
	}

	void ShowData() const
	{
		cout<<num1<<' '<<num2<<endl;
	}
};

int main(void)
{
	SimpleClass sc1;
	sc1.ShowData();

	SimpleClass sc2(100);
	sc2.ShowData();

	SimpleClass sc3(100, 200);
	sc3.ShowData();
	return 0;
}


Output:

1
2
3
0 0
100 0
100 200



클래스에 생성자가 하나도 정의되어 있지 않아도, 객체 생성시 생성자가 호출

-> 컴파일러에 의해 다음과 같은 default 생성자가 자동 삽입 

SimpleClass() { }


이는 new 연산자를 이용하는 객체의 생성에도 해당된다.


단, 다음과 같이 new연산자 대신 malloc 함수를 이용하면 생성자는 호출되지 않는다.

AAA * ptr = (AAA*)malloc(sizeof(AAA));    // AAA 클래스의 크기 정보만 바이트 단위로 전달



객체의 생성 예들

SimpleClass * ptr1=new SimpleClass();    (o)


SimpleClass * ptr1=new SimpleClass;    (o)


SimpleClass sc1;   (o)


SimpleClass sc1(); (x)

-> 이러한 형태의 객체 생성은 불가

컴파일러가 함수의 원형 선언인지 객체 생성인지 판단 불가

(sc1 함수이며, 인자가 void형이고 반환형이 SimpleClass인 함수라고 볼 수도 있다.)


다음예제와 같이 SimpleClass sc1()같은 유형의 문장은 함수의 원형선언으로 볼 수도 있으므로, 

이러한 유형의 문장은 객체 생성이 아닌 함수의 원형선언에만 사용가능하다.


#include <iostream>
using namespace std;

class SimpleClass
{
	int num1;
	int num2;

public:
	SimpleClass(int n1=0, int n2=0)
	{
		num1=n1;
		num2=n2;
	}

	void ShowData() const
	{
		cout<<num1<<' '<<num2<<endl;
	}
};

int main(void)
{
	SimpleClass sc1();    // 함수의 원형 선언!
	SimpleClass mysc=sc1(); // sc1 함수를 호출해, 이 때 반환되는 객체의 값으로 mysc 객체를 초기화
	mysc.ShowData();

	return 0;
}

SimpleClass sc1()
{
	SimpleClass sc(20, 30);
	return sc;
}


Output:

1
20 30



Member Initializer(멤버 이니셜라이저)를 이용한 멤버 초기화



위에서 Point 클래스에 생성자를 적용하고 객체를 생성하는 것은 무리가 없다.

하지만 Rectangle 클래스의 생성자 정의는 큰 문제가 있다. Rectangle 클래스는 두 개의 Point 객체를 멤버로 지니고 있기 때문이다. (upLeft, lowRight)


위의 클래스에서 생성자가 삽입되었으므로 default 생성자는 호출되지 않는다.


그렇다고 하여 void 생성자를 별도로 추가한다면? 생성자도 오버로딩이 되기 때문에 객체는 생성되겠지만 유효한 값을 전달받지 못하게 된다. (쓰레기 값)

따라서 Rectangle 객체가 생성될 때, 두 개의 Point 객체가 생성되며, 각 인자가 유효한 값을 전달받을 수 있는 생성자를 호출 할 수 있어야 한다.


이 문제 해결을 위해 '멤버 이니셜라이저'를 이용하면, 객체의 초기화를 수월하게 해줄 생성자를 호출할 수 있게 된다.


// 생성자가 추가된 Rectangle 클래스의 선언

1
2
3
4
5
6
7
8
9
class Rectangle
{
    Point upLeft;
    Point lowRight;
 
public:
    Rectangle(const int &x1, const int &y1, const int &x2, const int &y2);
    void ShowRecInfo() const;
};
cs


// Rectangle 클래스의 생성자 정의

1
2
3
4
5
Rectangle::Rectangle(const int &x1, const int &y1, const int &x2, const int &y2)
            :upLeft(x1, y1), lowRight(x2, y2)
{
    // empty
}
cs


다음의 문장이 '멤버 이니셜라이저'를 뜻하며, 이것을 통해 수월하게 멤버 초기화가 가능하다.

:upLeft(x1, y1), lowRight(x2, y2)


"객체 upLeft의 생성과정에서 x1, y1을 인자로 전달받는 생성자 호출,

객체 lowRight의 생성과정에서 x2, y2을 인자로 전달받는 생성자 호출"



범위지정 연산자 ::

: namespace외에도 명시적(explicitly)으로 클래스(구조체)멤버나 전역변수 지정



다음은 Point, Rectangle 클래스에 생성자를 추가한 예제이다.


 Point.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#ifndef __POINT_H_
#define __POINT_H_
 
class Point
{
    int x; 
    int y;    
public:
    Point(const int &xpos, const int &ypos);
    int GetX() const;
    int GetY() const;
    bool SetX(int xpos);
    bool SetY(int ypos);
};
 
#endif
cs


 Point.cpp

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
32
33
34
35
#include <iostream>
#include "Point.h"
using namespace std;
 
Point::Point(const int &xpos, const int &ypos)
{
    x=xpos;
    y=ypos;
}
 
int Point::GetX() const {return x;}
int Point::GetY() const {return y;}
 
bool Point::SetX(int xpos)
{
    if(0>xpos || xpos>100)
    {
        cout<<"벗어난 범위의 값 전달"<<endl;
        return false;
    }
 
    x=xpos;
    return true;
}    
bool Point::SetY(int ypos)
{
    if(0>ypos || ypos>100)
    {
        cout<<"벗어난 범위의 값 전달"<<endl;
        return false;
    }
 
    y=ypos;
    return true;
}
cs


 Rectangle.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#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
 
cs


 Rectangle.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#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
{
    cout<<"좌 상단: "<<'['<<upLeft.GetX()<<", ";
    cout<<upLeft.GetY()<<']'<<endl;
    cout<<"우 하단: "<<'['<<lowRight.GetX()<<", ";
    cout<<lowRight.GetY()<<']'<<endl<<endl;
}
cs


 RectangleConstructor.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
#include<iostream>
#include "Point.h"
#include "Rectangle.h"
using namespace std;
 
int main(void)
{
 
    Rectangle rec(1155);
    rec.ShowRecInfo();
    return 0;
}
 
cs


Output : 

좌 상단: [1, 1]

우 하단: [5, 5]



멤버 이니셜라이저를 이용한 변수 및 const 상수(변수) 초기화
const는 변수를 상수화 하는 키워드이다. const 선언에 의해 상수화된 변수를 가리켜  'const 변수'라고도 하고 'const 상수'라고도 한다. 즉, 같은 의미


 객체가 아닌 멤버 변수의 초기화

: 이렇게 초기화 하는 경우 멤버 변수가 선언과 동시에 초기화


1
2
3
4
5
6
7
8
9
10
11
class SoSimple
{
private:
    int num1;
    int num2;
public :
    SoSimple(int n1, int n2) : num1(n1)
    {
        num2=2;
    }
};
cs

위의 클래스에서 생성자의 몸체에서 보인 초기화,

num2=2;


는 다음의 두 문장과 같다.

int num2;

num2=n2;


하지만 이니셜라이저를 이용한 다음의 초기화,

num1(n1)


는 다음의 문장과 같다.

int num1=n1;




따라서 이러한 특성을 이용해 const 멤버변수도 이니셜라이저를 이용해 초기화 가능하다.

#include <iostream>
using namespace std;

class FruitSeller
{
	const int APPLE_PRICE;
	int numOfApples;
	int myMoney;
public:
	FruitSeller(int price, int num, int money)
		: APPLE_PRICE(price), numOfApples(num), myMoney(money)
	{
	}
	int SaleApples(int money)  
	{
		int num=money/APPLE_PRICE;
		numOfApples-=num;
		myMoney+=money;
		return num;
	}
	void ShowSalesResult() const
	{
		cout<<"남은 사과: "<<numOfApples<<endl;
		cout<<"판매 수익: "<<myMoney<<endl<<endl;
	}
};

class FruitBuyer
{
	int myMoney;
	int numOfApples;
public:
	FruitBuyer(int money)
		: myMoney(money), numOfApples(0)
	{
	}
	void BuyApples(FruitSeller &seller, int money)
	{
		numOfApples+=seller.SaleApples(money);
		myMoney-=money;
	}
	void ShowBuyResult() const
	{
		cout<<"현재 잔액: "<<myMoney<<endl;
		cout<<"사과 개수: "<<numOfApples<<endl<<endl;
	}
};

int main(void)
{
	FruitSeller seller(1000, 20, 0);
	FruitBuyer buyer(5000);
	buyer.BuyApples(seller, 2000);
	
	cout<<"과일 판매자의 현황"<<endl;
	seller.ShowSalesResult();
	cout<<"과일 구매자의 현황"<<endl;
	buyer.ShowBuyResult();

	return 0;
}

Output:
1
2
3
4
5
6
7
8
과일 판매자의 현황
남은 사과: 18
판매 수익: 2000

과일 구매자의 현황
현재 잔액: 3000
사과 개수: 2


이렇게 생성자의 몸체에서 초기화 하는 방법 대신에 이니셜라이저를 이용한 변수 및 const 상수의 초기화 방법은 이 점이 있다.

const 멤버 변수를 이니셜라이저를 이용한 초기화가 가능한 거 외에도 다음의 두 가지 이점이 있다.


- 초기화 대상을 명확히 인식가능

- 성능에 약간의 이점



멤버 변수로 참조자 선언하기 (이니셜라이저를 이용)

이니셜라이저가 선언과 동시에 초기화 되는 특성을 이용해, 참조자의 초기화도 가능

1
2
3
4
5
6
7
8
9
10
11
class BBB
{
private:
    AAA &ref;
    const int &num;
 
public:
    BBB(AAA &r, const int &n) // 생성자
        :ref(r), num(n) // 멤버 이니셜라이저
    {    // 빈 생성자 몸체
    }
cs


private 생성자

: 클래스 내부에서만 객체의 생성을 허용


다음 예제에서 클래스 외부에서는 9행의 public 생성자를 기반으로 객체를 생성한다.


10행에서는 19행에 정의된 private 생성자 기반으로 AAA 객체를 생성 및 반환하고 있다.

이 떄, heap 영역에 생성된 객체를 참조의 형태로 반환하고 있다.


#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) {}
};

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;
}


Output:

1
2
3
0
3
12



소멸자 (Destructor)

: 객체 소멸시 호출되며 다음과 같은 형태를 갖는다.


클래스의 이름앞에 ~ 가 붙음

반환형이 선언되어 있지않고, 실제 반환도 없다.


default 생성자와 마찬가지로 소멸자를 정의하지 않았다면 아무런 일을 하지 않는 default 소멸자가 자동 삽입된다.


1
2
3
4
class AAA
{    
// empty class
};
cs

이는 다음의 클래스 정의와 100% 동일하다.


1
2
3
4
5
6
class AAA
{
public:
AAA() { }
~AAA() { }
};
cs


이러한 소멸자는 대게 생성자에서 할당한 리소스의 소멸에 사용된다.

다음 예제에서 new키워드를 이용해 동적할당한 메모리 공간을 소멸시키는 소멸자를 볼 수 있다.


13-14행 : 필요한 만큼 메모리 공간을 사용하기 위해 문자열의 길이만큼 메모리 공간을 동적 할당

23행 : 소멸자를 통해 할당된 메모리 공간을 소멸 시킴. delete 키워드를 이용하며 소멸되었음을 확인할 수 있는 출력문장이 삽입


#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;
	}
};

int main(void)
{
	Person man1("Lee dong woo", 29);
	Person man2("Jang dong gun", 41);
	man1.ShowPersonInfo();
	man2.ShowPersonInfo();

	return 0;
}



Output :

이름: Lee dong woo

나이: 29

이름: Jang dong gun

나이: 41

called destructor!

called destructor!




'Study > C++' 카테고리의 다른 글

복사 생성자, 얕은 복사와 깊은 복사, 임시 객체  (0) 2016.09.28
객체 배열과 this포인터  (0) 2016.09.27
class  (0) 2016.09.25
CPP-Object Oriented Programming (객체지향 프로그래밍)  (0) 2016.09.23
c++ Class  (0) 2016.09.22
728x90

정보 은닉 (Information)

클래스의 멤버변수를 private로 선언, 해당 변수에 접근하는 함수를 별도로 정의해, 

 안전한 형태로 멤버 변수의 접근을 유도한다.


Point 클래스의 정보은닉

1
2
3
4
5
6
7
8
9
10
11
12
13
class Point
{
private:
    int x; 
    int y;    
 
public:
    bool InitMembers(int xpos, int ypos);
    int GetX() const;
    int GetY() const;
    bool SetX(int xpos);
    bool SetY(int ypos);
};
cs

8 행 : 벗어난 범위의 값 저장을 막는 초기화 함수

9-12 행 : 정보 은닉으로 인해 추가되는 접근(access) 함수들 


get은 값을 반환, set은 값을 저장하는 함수로 엑세스 함수를 추가하는 것이 일반적


1
2
3
4
5
6
7
8
9
10
11
bool Point::SetX(int xpos)
{
    if(0>xpos || xpos>100)
    {
        cout<<"벗어난 범위의 값 전달"<<endl;
        return false;
    }
 
    x=xpos;
    return true;
}    
cs



Rectangle 클래스의 정보 은닉

1
2
3
4
5
6
7
8
9
10
class Rectangle
{
private:
    Point upLeft;
    Point lowRight;
 
public:
    bool InitMembers(const Point &ul, const Point &lr);
    void ShowRecInfo() const;
};
cs



1
2
3
4
5
6
7
8
9
10
11
bool Rectangle::InitMembers(const Point &ul, const Point &lr)
{
    if(ul.GetX()>lr.GetX() || ul.GetY()>lr.GetY())
    {
        cout<<"잘못된 위치정보 전달"<<endl;
        return false;
    }
    upLeft=ul;
    lowRight=lr;
    return true;
}
cs

3-7 행 : 좌 상단과 우 하단이 바뀌는 것을 막는다. (우 하단의 좌표값이 더 작은것은 말이 안되므로)



const 함수


멤버 함수의 const 선언

: 동일 클래스에 선언된 멤버 변수의 값을 변경하지 못하도록 선언

1
2
3
4
// const함수내에서는 동일 클래스에 선언된 멤버 변수의 값을 변경하지 못함
int GetX() const;
int GetY() const;
void ShowRecInfo() const;
cs



다음과 같은 경우는 주의!

const 함수내에서는 const 선언되지 않은 함수를 호출하지 못함


다음과 같은 함수 호출은 컴파일 에러를 일으킨다. 

GetNum함수가 값을 변경할 수 있는 가능성이 내포되어 있기 때문이며 이를 해결하려면 GetNum함수에 const선언을 추가해야 한다.

1
2
3
4
5
6
7
8
9
// 멤버 함수 GetNum, ShowNum
int GetNum()
{
    return num;
}
void ShowNum() const
{
    cout<<GetNum()<<endl;    // 컴파일 에러
}
cs

const로 상수화된 객체를 대상으로도 const 멤버함수만 호출 가능. 역시 이를 해결하려면 GetNum함수에 const선언을 추가해야 함. 
1
2
3
4
5
// GetNum이 const 선언되지 않았다고 가정
void InitNum(const EasyClass &easy)
{
    num=easy.GetNum();    // 컴파일 에러 
}
cs



캡슐화 (Encapsulation)
: 관련있는 모든 것들을 하나의 클래스 안에 묶는 것

아래의 class들은 감기알약에 대해 나름의 캡슐화를 한 것이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class SinivelCap    // 콧물 처치용 캡슐
{
public
    void Take() const {cout<<"콧물이 싹~ 납니다."<<endl;}
};
 
class SneezeCap    // 재채기 처치용 캡슐
{
public
    void Take() const {cout<<"재채기가 멎습니다."<<endl;}
};
 
class SnuffleCap   // 코막힘 처치용 캡슐
{
public
    void Take() const {cout<<"코가 뻥 뚫립니다."<<endl;}
};
 
cs

하지만 다음과 같은 조건이라면 이는 캡슐화가 제대로 되지 않은 것이다.
 1. 코 감기가 하나의 증상이 아닌 여러 증상을 동반한다.
 2. 복용에 대한 순서나 방법이 정해져있다.

따라서 이 경우 제대로 된 캡슐화가 필요하며, 다음과 같이 관련 클래스를 하나로 묶을 필요성이 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class CONTAC600 
{
private:
    SinivelCap sin;
    SneezeCap sne;
    SnuffleCap snu;
 
public:
    void Take() const
    {
        sin.Take();
        sne.Take();
        snu.Take();
    }
};
cs

4-6행 : 코 감기와 관련된 캡슐을 하나로 묶음
9행 : 약의 복용 순서를 정의


1
2
3
4
5
class ColdPatient
{
public:
    void TakeCONTAC600(const CONTAC600 &cap) const { cap.Take(); }
};
cs

위의 ColdPatient 클래스는 CONTAC600 클래스의 내용이 수정 및 추가되라도 바뀔 필요가 없다.


 캡슐화 관련 예제 

#include <iostream>
using namespace std;

class SinivelCap    // 콧물 처치용 캡슐
{
public:
	void Take() const { cout << "콧물이 싹~ 납니다." << endl; }
};

class SneezeCap    // 재채기 처치용 캡슐
{
public:
	void Take() const { cout << "재채기가 멎습니다." << endl; }
};

class SnuffleCap   // 코막힘 처치용 캡슐
{
public:
	void Take() const { cout << "코가 뻥 뚫립니다." << endl; }
};

class CONTAC600
{
private:
	SinivelCap sin;
	SneezeCap sne;
	SnuffleCap snu;

public:
	void Take() const
	{
		sin.Take();
		sne.Take();
		snu.Take();
	}
};

class ColdPatient
{
public:
	void TakeCONTAC600(const CONTAC600 &cap) const
	{
		cap.Take();
	}
};

int main(void)
{
	CONTAC600 cap;
	ColdPatient sufferer;
	sufferer.TakeCONTAC600(cap);
	return 0;
}


Output:

1
2
3
콧물이 싹~ 납니다.
재채기가 멎습니다.
코가 뻥 뚫립니다.


해당 함수가 속한 객체의 멤버를 변경할 수 없도록 const 함수



캡슐화의 이점

: A 클래스가 잘 캡슐화 되었다면, 이 A클래스가 변경되더라도,

A와 연관된 다른 클래스는 변경될 필요가 없거나 변경 범위가 최소화된다는 것이다.





'Study > C++' 카테고리의 다른 글

객체 배열과 this포인터  (0) 2016.09.27
생성자와 소멸자, 클래스  (0) 2016.09.26
CPP-Object Oriented Programming (객체지향 프로그래밍)  (0) 2016.09.23
c++ Class  (0) 2016.09.22
c++ 기초3  (0) 2016.09.21
728x90

Object Oriented Programming (객체지향 프로그래밍)

 

C++은 객체지향 언어

C언어가 절차지향적 언어라면 C++은 객체지향 언어

 

"나는 과일장수에게 두 개의 사과를 구매했다!"

위 문장에서 object(객체)는 나, 과일장수, 사과

 

OOP는 A.P.I.E

A(Abstraction, 추상화)

P(Polymorphism, 다형성)

I(Inheritance, 상속성)

E(Encapsulation, 은닉성 = 캡슐화)

 

- 설계이전에 분석이 필요

(요구사항)분석-설계-구현

 

1.분석 단계 = 객체 모델링

-객체 구성요소(속성), 동작(기능) 추출

 

현실에서의 요소 = 객체

객체를 프로그래밍으로 옮긴 것 = 인스턴스

: 혼용해도 딱히 틀리진 않다

 

 

객체를 이루는 것은 데이터와 기능

프로그램상에서 바라보는 과일장수

- 과일장수는 과일을 판다                            (행동 behavior)

- 과일장수는 사과 20개, 오렌지 10개를 보유    (상태 state)

- 과일장수의 판매 수익은 현재 50,00원           (상태 state)

 

객체는 하나 이상의 상태정보(데이터) + 하나 이상의 행동(기능)으로 구성

 

상태 정보 : 변수를 통해서 표현(변수에 상태 정보 저장)

행동 : 함수를 통해서 표현

 

 

과일 장수의 상태 정보 (변수로 표현하며, 사과를 판다고 가정)

 - 보유 사과 수    -> int numOfApples;

 - 판매 수익        -> int myMoney;

 

과일 장수의 행위 (판매를 함수로 표현)

 

1
2
3
4
5
6
7
int SaleApples(int money)      // 함수의 인자는 사과 구매 금액
{
    int num = money / 1000;    // 사과는 개당 1000원
    numOfApples -= num;        //  사과수 감소  
    myMoney += money;          // 판매 수익 발생
    return num;                // 판매된 사과의 수를 반환
}
cs
 
 
class는 객체를 생성하기 위한 틀
 
변수 초기화 함수와 과일장수의 판매수익, 남는 사과 개수를 보여주는 함수도 정의
 
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
class FruitSeller
{
private:
    int APPLE_PRICE;
    int numOfApples;
    int myMoney;
    
public:
    void InitMembers(int price, int num, int money)
    {
        APPLE_PRICE=price;
        numOfApples=num;
        myMoney=money;
    }
    int SaleApples(int money)  
    {
        int num=money/APPLE_PRICE;
        numOfApples-=num;
        myMoney+=money;
        return num;
    }
    void ShowSalesResult()
    {
        cout<<"남은 사과: "<<numOfApples<<endl;
        cout<<"판매 수익: "<<myMoney<<endl<<endl;
    }
};
cs
위에서 사과의 판매 가격을 의미하는 멤버변수 APPLE_PRICE를 상수화 하고 싶지만 다음과 같은 이유로 '현재 공부한 내용에서는 불가능'하다.
 
 클래스의 멤버변수 선언문에서 초기화까지 하는 것은 허용하지 않는다. const int APPLE_PRICE=1000;    (x)
 상수는 선언과 동시에 초기화 해야한다. const int APPLE_PRICE;    (x)
 
이 문제는 후에 생성자(Constructor)를 공부하게 되면 해결할 수 있게 된다.
 
 
 
구매자인 나(me)를 표현하는 클래스 정의
과일 구매자를 뜻하는 FruitBuyer 클래스
 
멤버 변수
 소유한 현금 -> int myMoney;
 소유한 사과 -> int numOfApples;
 
 
과일구매자의 기능
: 과일의 구매 (함수 BuyApple)
 
이를 기반으로 정의한 FruitBuyer 클래스
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class FruitBuyer
{
    int myMoney;        // private:
    int numOfApples;    // private:
 
public:
    void InitMembers(int money)
    {
        myMoney = money;
        numOfApples = 0;
    }
    void BuyApples(FruitSeller &seller, int money)
    {
        numOfApples += seller.SaleApples(money);
        myMoney -= money;
    }
    void ShowBuyResult()
    {
        cout << "현재 잔액: " << myMoney << endl;
        cout << "사과 개수: " << numOfApples << endl << endl;
    }
};
cs
 
3,4행에서 클래스에 아무런 선언이 존재하지 않을 때 private로 간주 (구조체는 public)
 
 
클래스 기반의 두 가지 객체 생성 방법  (클래스의 실체화 : 객체화)
앞서말했듯이 클래스는 객체를 생성하기 위한 틀에 불가하다.
 
그러니 객체를 생성하지 않고 두 클래스 안에 존재하는 변수에 접근하고, 함수를 호출하는 것은 불가능하다. 
 
C++에서 객체를 생성하는 두 가지 방법은 다음과 같다.
 
ClassName objName;                // 일반적 변수 선언방식
ClassName * ptrobj = new ClassName; // 동적 할당방식 (heap 할당)
 
 
일반적 변수 선언방식으로 FruitSeller 클래스와 FruitBuyer 클래스 객체 생성
 FruitSeller seller;
 FruitBuyer buyer;
 
이를 동적 할당 (heap 할당)
 FruitSeller * objPtr1 = new FruitSeller;
 FruitBuyer * objPtr2 = new FruitBuyer;
 
 
 
Message Passing (객체간의 메시지 전달)
 
 "과일장수(seller) 아저씨, 사과 2000원어치 주세요"
-> '나'라는 객체가 '과일장수'라는 객체로부터 '과일'객체를 구매하는 행위의 표현
(이 행위의 결과는 아래 예제에서 보듯이 사과의 갯수가 증가하고 보유 현금이 줄어드는 것이 됨)
 
이는 하나의 객체가 다른 하나의 객체에게 메시지를 전달하는 것과 같다.
과일장수(seller) 아저씨, 사과 2000원어치 주세요
이처럼 하나의 객체가 다른 하나의 객체에게 메시지를 전달하는 것은 함수 호출을 기반으로 하며,
이러한 형태의 함수호출을 'Message Passing (객체간의 메시지 전달)' 이라고 한다.
 
사과 장수 시뮬레이션 예제
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 
#include <iostream> using namespace std; class FruitSeller { private: int APPLE_PRICE; int numOfApples; int myMoney; public: void InitMembers(int price, int num, int money) { APPLE_PRICE = price; numOfApples = num; myMoney = money; } int SaleApples(int money) { int num = money / APPLE_PRICE; numOfApples -= num; myMoney += money; return num; } void ShowSalesResult() { cout << "남은 사과: " << numOfApples << endl; cout << "판매 수익: " << myMoney << endl << endl; } }; class FruitBuyer { int myMoney; // private: int numOfApples; // private: public: void InitMembers(int money) { myMoney = money; numOfApples = 0; } void BuyApples(FruitSeller &seller, int money) { numOfApples += seller.SaleApples(money); myMoney -= money; } void ShowBuyResult() { cout << "현재 잔액: " << myMoney << endl; cout << "사과 개수: " << numOfApples << endl << endl; } }; int main(void) { FruitSeller seller; seller.InitMembers(1000, 20, 0); FruitBuyer buyer; buyer.InitMembers(5000); buyer.BuyApples(seller, 2000); cout << "과일 판매자의 현황" << endl; seller.ShowSalesResult(); cout << "과일 구매자의 현황" << endl; buyer.ShowBuyResult(); return 0; } 


Output:
1 2 3 4 5 6 7 8 
과일 판매자의 현황 남은 사과: 18 판매 수익: 2000 과일 구매자의 현황 현재 잔액: 3000 사과 개수: 2
 
 
 
 
 
 
 
 
 
 
 
 
 

 

'Study > C++' 카테고리의 다른 글

생성자와 소멸자, 클래스  (0) 2016.09.26
class  (0) 2016.09.25
c++ Class  (0) 2016.09.22
c++ 기초3  (0) 2016.09.21
c++ 기초  (0) 2016.09.19
728x90

C++에서의 구조체


c++에서는 구조체 변수 선언시,

struct 키워드의 생략을 위한 typedef 선언 불필요


c의 구조체 변수 초기화         c++에서 구조체 변수 초기화

struct Car basicCar;      Car basicCar


#include <iostream>
using namespace std;

#define ID_LEN  20
#define MAX_SPD  200
#define FUEL_STEP 2
#define ACC_STEP 10
#define BRK_STEP 10

struct Car
{
	char gamerID[ID_LEN]; // 소유자ID
	int fuelGauge;  // 연료량
	int curSpeed;  // 현재속도
};

void ShowCarState(const Car &car)
{
	cout << "소유자ID: " << car.gamerID << endl;
	cout << "연료량: " << car.fuelGauge << "%" << endl;
	cout << "현재속도: " << car.curSpeed << "km/s" << endl << endl;
}
void Accel(Car &car)
{
	if (car.fuelGauge <= 0)
		return;
	else
		car.fuelGauge -= FUEL_STEP;

	if (car.curSpeed + ACC_STEP >= MAX_SPD)
	{
		car.curSpeed = MAX_SPD;
		return;
	}

	car.curSpeed += ACC_STEP;
}
void Break(Car &car)
{
	if (car.curSpeed < BRK_STEP)
	{
		car.curSpeed = 0;
		return;
	}

	car.curSpeed -= BRK_STEP;
}

int main(void)
{
	Car run99 = { "run99", 100, 0 };
	Accel(run99);
	Accel(run99);
	ShowCarState(run99);
	Break(run99);
	ShowCarState(run99);

	Car sped77 = { "sped77", 100, 0 };
	Accel(sped77);
	Break(sped77);
	ShowCarState(sped77);

	return 0;
}

Output :

소유자ID: run99

연료량: 96%

현재속도: 20km/s


소유자ID: run99

연료량: 96%

현재속도: 10km/s


소유자ID: sped77

연료량: 98%

현재속도: 0km/s



바로 위 예제의 함수들은 구조체 Car에 종속적인 함수들이지만 전역함수의 형태를 띠고 있다.

따라서 다른 영역에서 이 함수를 호출하는 실수를 할 수도 있기 때문에 연관된 함수를 하나의 데이터형으로 묶을 필요성이 있다.


c++에서는 구조체 안에 함수 삽입 가능

위의 예제에서 Car에 종속적인 함수들을 구조체 안에 함께 묶을 수도 있다.


#include <iostream>
using namespace std;

#define ID_LEN		20
#define MAX_SPD		200
#define FUEL_STEP	2
#define ACC_STEP	10
#define BRK_STEP	10

struct Car	// 함수를 멤버로 지니는 구조체
{
	char gamerID[ID_LEN];	// 소유자ID
	int fuelGauge;			// 연료량
	int curSpeed;			// 현재속도

	void ShowCarState()
	{
		cout << "소유자ID: " << gamerID << endl;
		cout << "연료량: " << fuelGauge << "%" << endl;
		cout << "현재속도: " << curSpeed << "km/s" << endl << endl;
	}
	void Accel()
	{
		if (fuelGauge <= 0)
			return;
		else
			fuelGauge -= FUEL_STEP;

		if (curSpeed + ACC_STEP >= MAX_SPD)
		{
			curSpeed = MAX_SPD;
			return;
		}

		curSpeed += ACC_STEP;
	}
	void Break()
	{
		if (curSpeed<BRK_STEP)
		{
			curSpeed = 0;
			return;
		}

		curSpeed -= BRK_STEP;
	}
};

int main(void)
{
	Car run99 = { "run99", 100, 0 };	// 초기화의 대상은 변수
	run99.Accel();			// 구조체 내에 선언된 변수에 접근하듯이 함수 호출
	run99.Accel();
	run99.ShowCarState();
	run99.Break();
	run99.ShowCarState();

	Car sped77 = { "sped77", 100, 0 };
	sped77.Accel();
	sped77.Break();
	sped77.ShowCarState();
	return 0;
}

Output은 동일



구조체안에 enum 상수의 선언

#define ID_LEN    20

( ...)

위의 예제에서는 매크로 상수들이 존재하는데 이들 상수는 구조체 Car에게만 의미가 있는 상수들이기에, 상황에 따라 구조체 내에 포함시키는 것이 좋을수도 있다.

이러한 경우에는 구조체안에 열거형 enum 상수를 선언할 수 있다.


struct Car

{

enum

{

ID_LEN = 20,

MAX_SPD =200,

FUEL_STEP =2

};

char gamerID[ID_LEN];

( ...)

}


혹은 하나의 namespace에  enum 상수의 선언을 하기도 한다.

namespace CAR_CONST

{

enum

{

ID_LEN = 20,

MAX_SPD =200,

FUEL_STEP =2

};

}



구조체 안에는 함수의 선언을 넣고, 함수의 정의는 외부로 뺄수도 있다.


//구조체 안에 삽입된 함수의 선언

struct Car

{

. . . .

void ShowCarState();

void Accel();

. . . . 

};


//구조체 외부에 선언된 함수의 정의

void Car::ShowCarState()

{

. . . . 

}

void Car::Accel()

{

. . . .

}


구조체 안에 함수가 정의되어 있으면, inline 선언된 것으로 간주


함수의 정의가 구조체 외부에 있을 때, inline 처리를 하기를 바란다면

 다음과 같이 명시적으로 inline 선언을 하기도 한다.

inline 함수 -> http://smart2016.tistory.com/88


inline void Car::Break() { . . . . }



클래스와 구조체의 차이점

키워드 struct 를 대신해 class를 사용했을 때, 외형적 차이는 별반 다를게 없어 보인다.

하지만 단순히 키워드만 class로 바꾼다면 선언된 멤버의 접근이 불가능해진다.


class에는 멤버에 대한 접근 제어 지시자를 통해 접근하어야 한다.


접근 제어 지시자

 private

 클래스의 내부에서만 접근가능.


 public

 클래스의 내부/외부 모든 곳에서 접근 가능


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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)
{
    strncpy_s(gamerID, ID, sizeof(CAR_CONST::ID_LEN));
    fuelGauge=fuel;
    curSpeed=0;
};
cs

객체, 멤버 변수와 멤버 함수

위에서 Car class를 대상으로 생성된 변수를

'객체(object)'라고 한다.


위에서 Car 클래스내에 선언된 변수를 가리켜

'멤버 변수'라고 한다.


위에서 Car 클래스내에 정의된 함수를 가리켜

'멤버 함수'라고 한다.


struct(구조체) 와 class(클래스)는 동일
흔히 구조체는 클래스 이전에 선언되고 주로 여러종류의 기억공간을 선언하기 위한 형태로만 보는 경우가 일반적인데, 이는 틀린 생각이다. 구조체 역시 클래스와 동일하게 생성자와 소멸자를 지정할 수 있고, 코딩이 가능하며 내부호출과 캡슐화가 가능하다. 따라서, 하나의 물체(Object) 를 하나의 구조체(struct) 로 선언하고 사용할 수 있다. 따라서, 구조체 내에서도 모든 함수와 명령을 클래스와 동일하게 사용 가능하다. 다만, 멤버에 대해 접근 지정자를 써 주지 않는 경우 구조체는 public이 되고, 클래스는 private이 된다는 차이가 있다.




C++에서의 일반적인 파일 분할


클래스의 선언은 헤더파일에 삽입

클래스의 멤버 함수의 정의는 소스파일


Car.h

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
#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
 
cs


Car.cpp

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
32
33
34
35
36
37
38
39
40
41
42
43
#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::ShowCarState()
{
    cout<<"소유자ID: "<<gamerID<<endl;
    cout<<"연료량: "<<fuelGauge<<"%"<<endl;
    cout<<"현재속도: "<<curSpeed<<"km/s"<<endl<<endl;
}
 
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;
}
 
void Car::Break()
{
    if(curSpeed<CAR_CONST::BRK_STEP)
    {
        curSpeed=0;
        return;
    }
    curSpeed-=CAR_CONST::BRK_STEP;
}
cs


RacingMain.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include "Car.h"
 
int main(void)
{
    Car run99;
    run99.InitMembers("run99"100);
    run99.Accel();
    run99.Accel();
    run99.Accel();
    run99.ShowCarState();
    run99.Break();
    run99.ShowCarState();
    return 0;
}
cs



다만, inline함수는 컴파일 과정에서 함수의 몸체 부분이 함수 호출 문장으로 대체되기 때문에 다음과 같이 헤더파일에 함께 정의되어야 한다.


CarInline.h

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#ifndef __CARINLINE_H__
#define __CARINLINE_H__
 
#include <iostream>
using namespace std;
 
namespace CAR_CONST
{
    enum
    {
        ID_LEN        =20,
        MAX_SPD        =200,
        FUEL_STEP    =2,
        ACC_STEP    =10,
        BRK_STEP    =10
    };
}
 
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();
};
 
inline void Car::ShowCarState()
{
    cout<<"소유자ID: "<<gamerID<<endl;
    cout<<"연료량: "<<fuelGauge<<"%"<<endl;
    cout<<"현재속도: "<<curSpeed<<"km/s"<<endl<<endl;
}
 
 
inline void Car::Break()
{
    if(curSpeed<CAR_CONST::BRK_STEP)
    {
        curSpeed=0;
        return;
    }
    curSpeed-=CAR_CONST::BRK_STEP;
}
 
#endif
 
cs


CarInline.cpp

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
#include <cstring>
#include "CarInline.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;
}
 
cs


main은 동일



'Study > C++' 카테고리의 다른 글

생성자와 소멸자, 클래스  (0) 2016.09.26
class  (0) 2016.09.25
CPP-Object Oriented Programming (객체지향 프로그래밍)  (0) 2016.09.23
c++ 기초3  (0) 2016.09.21
c++ 기초  (0) 2016.09.19
728x90

새로운 자료형 bool


c++에서 true와 false는 참,거짓 의미하는 1바이트 data

#include <iostream>
using namespace std;

int main(void)
{
	int num = 10;
	int i = 0;

	cout << "true: " << true << endl;	// true는 참을 의미하는 1바이트 data지만 정수로 변환되어 1 출력
	cout << "false: " << false << endl;	// 정수로 변환되어 0 출력

	while (true)
	{
		cout << i++ << ' ';
		if (i>num)
			break;
	}
	cout << endl;

	cout << "sizeof 1: " << sizeof(1) << endl;			// 정수 1, 0과는 다름 개념임을 보여줌
	cout << "sizeof 0: " << sizeof(0) << endl;
	cout << "sizeof true: " << sizeof(true) << endl;	// true와 false는 참,거짓 의미하는 1바이트 data
	cout << "sizeof false: " << sizeof(false) << endl;
	return 0;
}


Output:

1
2
3
4
5
6
7
true: true
false: false
0 1 2 3 4 5 6 7 8 9 10 
sizeof 1: 4
sizeof 0: 4
sizeof true: 1
sizeof false: 1



true와 false는 bool type data

bool type 변수는 true와 false정보를 저장 가능



#include <iostream>
using namespace std;

int main()
{
	int num;
	bool height;

	cout << "당신의 키 입력(cm) : ";
	cin >> num;

	if (num > 180) height = true;
	else height = false;

	cout << boolalpha;	// bool 변수의 값을 1과 0 대신에 true와 false로 출력하도록 함
	cout << "키가 180이 넘는가? " << height << endl;

	return 0;
}


Output :

당신의 키 입력(cm) : 175

키가 180이 넘는가? false



Reference (참조자)

변수는 할당된 메모리 공간에 붙여진 이름이며, 그 이름을 통해 해당 메모리 공간에 접근이 가능


int num1=2010;


위의 변수 선언으로 2010으로 초기화된 메모리 공간에 num1이라는 이름이 붙게 됨


int &num2=num1;


위에서 num2앞에 붙은 &연산자는 참조자의 선언을 뜻하며, 변수 num1에 대한 참조자 num2를 선언해라는 의미.


이 결과, num1이라는 이름이 붙은 메모리 공간에 num2이라는 이름이 더 하나 붙게 됨


num2=3047;


위 문장으로 인해 num1, num2라는 이름이 붙은 메모리 공간에 3047이 저장됨


- 관련 예제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
using namespace std;

int main()
{
	int num = 10;
	int &ref = num;

	ref = 20;
	cout << "val : " << num << endl;
	cout << "ref : " << ref << endl;

	cout << "val : " << &num << endl;
	cout << "ref: " << &ref << endl;

	return 0;
}


Output:

1
2
3
4
val : 20
ref : 20
val : 0xfff84d48
ref: 0xfff84d48


변수 num과 참조자 ref가 동일한 메모리 공간을 참조하게 되며 주소값도 같음을 알 수 있다.


참조자의 수에는 제한이 없으며, 참조자를 대상으로 참조자를 선언하는 것도 가능



참조자의 선언 가능 범위


참조자는 변수에 대해서만 선언 가능. (배열이 아닌 배열요소도 변수로 간주)

선언과 동시에 누군가를 참조해야 함.


int &ref=20;    (x)

상수를 대상으로 참조자 선언 불가


int &ref;    (x)

미리 참조자를 선언후, 후에 누군가를 참조하는 것도 불가


int &ref=NULL;    (x)

NULL 초기화 불가



배열 요소(배열이 아님)를 대상으로 참조자 선언 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
using namespace std;

int main()
{
	int arr[3] = { 1,2,3 };
	int &ref1 = arr[0];
	int &ref2 = arr[1];
	int &ref3 = arr[2];

	cout << ref1 << endl;
	cout << ref2 << endl;
	cout << ref3 << endl;

	return 0;
}


Output:

1
2
3
1
2
3


다음과 같이 포인터 변수를 대상으로 참조자 선언 가능

#include <iostream>
using namespace std;

int main()
{
	int num = 12;
	int * ptr = &num;
	int ** dptr = &ptr;

	int &ref = num;
	int * (&pref) = ptr;
	int ** (&dpref) = dptr;

	cout << ref << endl;
	cout << * pref << endl;
	cout << ** dpref << endl;

	return 0;
}


Output:

1
2
3
12
12
12



다음의 함수 정의는 Call-by-value도 될수 있고 Call-by-reference도 될 수 있다.

int * SimpleFunc(int * ptr)

{

. . . .

}


다음과 같이 정의되면 Call-by-value

int * SimpleFunc(int * ptr)

{

return ptr+1;

}


주소 값이 전달되었냐가 아닌, 주소값이 참조의 도구로 사용되었냐가 구분의 기준이 됨


c++에서는 주소값을 이용한 Call-by-reference외에도 (혹자는 이를 Call-by-address라 부르기도 한다)

참조자를 이용한 Call-by-reference도 존재한다.



참조자를 이용한 Call-by-reference


#include <iostream>
using namespace std;

void SwapByRef2(int &ref1, int &ref2)
{
	int temp = ref1;
	ref1 = ref2;
	ref2 = temp;
}

int main()
{
	int val1 = 10;
	int val2 = 20;

	SwapByRef2(val1, val2);
	cout << "val 1 : " << val1 << endl;
	cout << "val 2 : " << val2 << endl;

	return 0;
}


Output:

1
2
val 1 : 20
val 2 : 10


참조자를 이용해 값을 증가시키는 함수, 부호를 바꾸는 함수 정의하고

각 함수의 호출결과 확인

#include <iostream>
using namespace std;

void Func(int &ref)
{
	ref++;
}

void Func2(int &ref)
{
	ref = ref*(-1);
}

int main()
{
	int val1 = 10;
	Func(val1);		
	cout << "val 1 : " << val1 << endl;

	Func2(val1);
	cout << "val 1 : " << val1 << endl;
	
	return 0;
}


Output:

1
2
val 1 : 11
val 1 : -11



포인터 변수 ptr1, ptr2가 가리키는 대상이 바뀌도록 하는 SwapPointer 함수 정의하고 결과를 출력해 확인

#include <iostream>
using namespace std;

void SwapPointer(int * (&ptr1), int *(&ptr2))
{
	int * temp = ptr1;
	ptr1 = ptr2;
	ptr2 = temp;
}

int main()
{
	int val1 = 10;
	int val2 = 20;

	int * ptr1 = &val1;
	int * ptr2 = &val2;

	SwapPointer(ptr1, ptr2);
	
	cout << "ptr 1 -> " << *ptr1 << endl;		
	cout << "ptr 2 -> " << *ptr2 << endl;
	
	return 0;
}


Output:

1
2
ptr 1 -> 20
ptr 2 -> 10



참조자 기반 함수 정의의 단점


int num=24;

HappyFunc(num);

cout<<num<<endl;


C언어 관점에서 본다면 100% 24가 출력될거 같지만, C++에서는 얼마가 출력될지 알 수 없다.


void HappyFunc(int prm) { . . . .}      // 위와 같이 함수가 정의되어 있다면 24가 출력되겠지만

void HappyFunc(int &ref) { . . . . }    //  참조자가 매개변수에 있다면 num에 저장된 값을 변경할 수도 있다.


그렇기 때문에 참조자를 사용하는 경우 함수 원형을 확인해야하고, 참조자가 매개변수 선언에 있을 때

값의 변화가 일어나는지 함수의 몸체(body)까지 살펴볼 필요성이 생긴다.


이는 함수의 호출문장을 보고 함수 특성을 판단할 때 단점이 된다.

따라서 참조자의 활용이 쉬움에도 명확한 코드를 위해 포인터를 사용하는 경우도 있다.


다음과 같이 참조자 ref에 const 키워드를 이용하면 함수의 몸체까지 들여다볼 필요없이, 함수의 원형을 확인하는 정도로 그칠 수 있다.


void HappyFunc(const int &ref) { . . . . }    // 함수 HappyFunc내에서 참조자 ref를 이용한 값의 변경은 하지 않겠다는 const 선언



Return Type이 Reference Type인 경우


#include <iostream>
using namespace std;

int & RefRetFuncOne(int &ref)
{
	ref++;
	return ref;
}

int main()
{
	int num1 = 1;	
	int &num2 = RefRetFuncOne(num1);	// 함수호출 후 참조자를 반환하며, 이를 참조자 &num2에 저장
	
	num1++;		// 변수 num1과 참조자 num2의 값을 1씩 증가, 각각 3이 됨
	num2++;		// 마찬가지로 1씩 증가해 각각 4가 됨
	cout << "num1 : " << num1 << endl;
	cout << "num2 : " << num2 << endl;
		
	return 0;
}

Output:
1
2
num1 : 4
num2 : 4



바로 위 예제와 달리 일반 변수에 참조자 반환값 저장

#include <iostream>
using namespace std;

int & RefRetFuncOne(int &ref)
{
	ref++;
	return ref;
}

int main()
{
	int num1 = 1;	
	int num2 = RefRetFuncOne(num1);	// 함수호출 후 참조자를 반환하지만 참조자가 아닌 일반변수를 선언하고 반환값을 저장
	
	num1+=1;	
	num2+=100;	
	cout << "num1 : " << num1 << endl;
	cout << "num2 : " << num2 << endl;
		
	return 0;
}


Output:

1
2
num1 : 3
num2 : 102


반환형이 참조형인 경우

: 위의 두 예제와 같이 반환대는 대상을 참조자로 받거나 변수로 받을 수 있다.



반환형이 참조자가 아닌 경우

: ref를 반환하든, 변수에 저장된 값을 반환하든 차이는 없다.


int RefRetFuncTwo(int &ref) // 반환형이 참조형( int & )은 아님

{

ref++;

return ref;

}


반환형이 값의 형태라면 참조자로 그 값을 받을 수 없다.

(상수를 참조하게 되므로)


int num2=RefRetFuncTwo(num1); (o)

int &num2=RefRetFuncTwo(num1); (x)




아래와 같이 지역변수를 참조의 형태로 반환하는 것은 문제가 된다.


int & RetuRefFunc(int n)

{

int num=20;

num+=n;

return num; // 지역변수의 반환

}


int &ref = RetuRefFunc(10); // ?? ref가 참조하는 대상이 소멸



const 참조자의 잘못된 사용


const int num=20;

int &ref=num;

ref+=10;

cout<<num<<endl;


위에서 num을 const선언해놓고 참조자를 통해 값을 변경하는 것은 const 선언의 이유를 잃게 하므로 에러



따라서 const 참조자는 프로그램의 안정성을 위해 다음과 같이 쓰는것이 좋다.


const int num=20;

const int &ref=num; // ref로 참조는 하되 값을 변경하지 않겠단 의미

const int &ref=50; // const참조자를 통해 상수 참조 가능



const참조자가 상수를 참조가능한 이유

: 리터럴 상수는 참조자로는 참조할 수 없게 되어 있다. 

하지만 const참조자로 참조할 경우에는 허용하며, 상수를 메모리 공간에 임시적으로 저장한다.


이러한 것이 가능하도록 한 이유는 아래와 같이 매개변수 형이 참조자인 경우에 상수를 전달할 수 있도록 하기 위함이다.


int Adder(const int &num1, const int &num2)

{

return num1 + num2;

}



malloc과 free함수를 대신하는 new와 delete


c언어에서의 malloc과 free 함수 

https://rutel.tistory.com/76


크기를 바이트 단위로 계산하지않고 type에 맞게 할당


int * ptr=new int;

double *arr=new arr[7];


이렇듯 new연산자로 할당된 메모리 공간은,

아래와 같이 delete함수 호출을 통해 소멸한다.


delete ptr; // 앞서 할당된 int형 변수 소멸

delete []arr; // 앞서 할당된 double형 배열 소멸



c에서 heap영역으로의 접근은 pointer를 통해서만 이루어졌지만 c++에서는 reference를 통한 접근도 가능.


참조자를 통해 heap에 접근하기


int * ptr = new int;

int & ref = *ptr; // heap영역에 할당된 변수에 대한 참조자 선언

ref=20;

cout<< * ptr << endl;





C++에서 C언어 표준함수 호출하기


c언어에 대응하는 c++ 헤더파일의 이름 규칙

: c를 더하고 .h를 빼면 된다.


C언어 헤더 C++ 헤더

#include <stdio.h> #include <cstdio>

#include <stdlib.h> #include <cstdlib>



c++의 표준함수는 std라는 namespace안에 선언되어 있음

또한 표준 C에 대응하는 표준 C++함수는 C++문법을 기반으로 변경 및 확장되었다 (함수 오버로딩을 통해 개선하는 등)


따라서, 가급적 C++에 표준함수를 호출하는 것이 좋다.


'Study > C++' 카테고리의 다른 글

생성자와 소멸자, 클래스  (0) 2016.09.26
class  (0) 2016.09.25
CPP-Object Oriented Programming (객체지향 프로그래밍)  (0) 2016.09.23
c++ Class  (0) 2016.09.22
c++ 기초  (0) 2016.09.19
728x90

CPP 예제들


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* 5개 정수 입력받아 합을 출력하는 프로그램
*/
#include <iostream>
 
int main()
{
    int num = 0;
    int sum = 0;
    for(int i=0; i<5; i++)
    { 
        std::cout << i + 1 << "번째 정수 입력: " ;
        std::cin >> num;
        sum = sum + num;
    }
    std::cout << "합계 :" << sum << std::endl;
    return 0;
}
cs



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* 이름과 전화번호를 문자열 형태로 입력받아 출력하는 프로그램
*/
#include <iostream>
 
int main()
{
    char name[100];
    char phoneNum[200];
 
    std::cout << "이름 : ";
    std::cin >> name;
 
    std::cout << "전화번호 : ";
    std::cin >> phoneNum;
 
    std::cout << "이름은 " << name << "이고 " <<
        "전화번호는 " << phoneNum << "이다 "<<std::endl;
    return 0;
}
 
cs



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* 숫자를 입력받아 그 숫자에 해당하는 단을 출력하는 구구단 프로그램
    (5 입력시 구구단 5단 출력)
*/
#include <iostream>
 
int main()
{
    int dan;
    std::cout << "구구단 출력할 단 입력 : ";
    std::cin >> dan;
    
    for (int i = 1; i < 10; i++)
        std::cout << dan << " X " << i << " = " << dan*<< std::endl;
 
    return 0;
}
cs



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
/* 급여 계산 프로그램
    기본 급여는 50만원, 물품 판매 가격의 12
    예) 판매금액이 100만원일 때, 50+100x0.12 = 62 (만원)
    
    판매금액 입력받아 급여를 출력 (-1이 될때까지 입력이 계속되야 함)
*/
#include <iostream>
 
int main()
{
    int pay;
    
    while(1)
    {
    std::cout << "판매 금액 입력 (만원단위, -1 입력시 종료) : ";
    std::cin >> pay;
 
    if (pay == -1)        
        break;    
 
    std::cout << "이번 달 급여: " << 50 + pay*0.12 << " 만원\n";
    }
    std::cout << "프로그램 종료" << std::endl;
        
    return 0;
}
cs



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
32
33
34
35
36
37
38
39
40
41
42
/* main함수에서 필요로 하는 swap 함수를 오버로딩으로 구현
*/
 
#include <iostream>
 
void swap(int *ptr1, int *ptr2)
{
    int temp = *ptr1;
    *ptr1 = *ptr2;
    *ptr2 = temp;
}
 
void swap(char *ptr1, char *ptr2)
{
    char temp = *ptr1;
    *ptr1 = *ptr2;
    *ptr2 = temp;
}
 
void swap(double *ptr1, double *ptr2)
{
    double temp = *ptr1;
    *ptr1 = *ptr2;
    *ptr2 = temp;
}
 
int main()
{
    int num1 = 20, num2 = 30;
    swap(&num1, &num2);
    std::cout << num1 << " " << num2 << std::endl;
 
    char ch1 = 'A', ch2 = 'Z';
    swap(&ch1, &ch2);
    std::cout << ch1 << " " << ch2 << std::endl;
 
    double dbl1 = 1.111, dbl2 = 5.555;
    swap(&dbl1, &dbl2);
    std::cout << dbl1 << " " << dbl2 << std::endl;
    
    return 0;
}
cs


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
/* 매개 변수의 디폴트 값 지정 형태를
함수 오버로딩의 형태로 재구현 (main함수 변경없이 동일한 실행결과가 나오도록)
*/
 
#include <iostream>
 
int BoxVolume(int length, int width, int height)
{
    return length*width*height;
}
 
int BoxVolume(int length, int width)
{
    return length*width*1;
}
 
int BoxVolume(int length)
{
    return length * 1 * 1;
}
 
 
int main()
{
    std::cout << "[3, 3, 3] : " << BoxVolume(333<< std::endl;
    std::cout << "[5, 5, D] : " << BoxVolume(55<< std::endl;
    std::cout << "[7, D, D] : " << BoxVolume(7<< std::endl;
    //    std::cout<<"[D, D, D] : "<<BoxVolume()<<std::endl;  lengh에는 default값이 지정되지 않았으므로 컴파일 에러
    return 0;
}
cs


inline 함수

매크로 함수는 함수가 인라인화 되어 성능향상이 있다는 장점이 있지만,
복잡한 함수의 정의는 어렵다는 단점이 있다. 

이를 위해 c++에서는 인라인 함수라는 것을 제공한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
 
inline int SQUARE(int x)
{
    return x*x;
}
 
int main()
{
    std::cout << SQUARE(5<< std::endl;
    std::cout << SQUARE(12<< std::endl;
    return 0;
}
cs

키워드 inline을 이용한 함수의 인라인화는 컴파일러에 의해 처리가 된다.
매크로 함수에 비해 디버깅이 용이하며, 컴파일러가 판단할 때 함수의 인라인화가 오히려 성능에 해가 된다면 inline 키워드를 무시하기도 한다.

다만 위의 inline 함수는 자료형에 의존적이게 되며 데이터 손실이 생길 수 있다.

inline int SQUARE(int x) { return x*x; }
int 형 기반으로 정의된 위의 inline 함수는 함수 호출 문장에서 데이터 손실이 발생하게 된다.

std::cout<< SQUARE(3.15);    // 0.15가 손실되어서 3 x 3의 결과인 9가 출력

함수의 오버로딩을 이용해 문제를 해결하고자 하면 이는 한번만 정의하면 되는 매크로 함수의 장점과는 멀어지므로 사용하지 않는다.
그래서 이러한 문제를 해결하기 위해서 template이라는 것을 이용해 (매크로 함수와 마찬가지로) 자료형에 의존적이지 않는 함수로 완성시킨다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>

template <typename T>
inline T SQUARE(T x)
{
	return x*x;
}

int main()
{
	std::cout << SQUARE(5.5) << std::endl;
	std::cout << SQUARE(12) << std::endl;
	return 0;
}

Output:
1
2
30.25
144


namespace (네임 스페이스)
: 이름을 붙여놓은 공간

이름 공간을 나눠 그 안에 함수를 정의하거나 변수를 선언한다면, 같은 이름으로 인해 충돌되는 문제가 발생하지 않는다.

#include <iostream>

namespace BestComImpl
{
	void SimpleFunc(void)
	{
		std::cout<<"BestCom이 정의한 함수"<<std::endl;
	}
}

namespace ProgComImpl
{
	void SimpleFunc(void)
	{
		std::cout<<"ProgCom이 정의한 함수"<<std::endl;
	}
}

int main(void)
{
	BestComImpl::SimpleFunc();
	ProgComImpl::SimpleFunc();
	return 0;
}

Output:
1
2
BestCom이 정의한 함수
ProgCom이 정의한 함수

위의 예제에서 보듯이 namespace를 나누면 매개변수 형이 동일한 함수가 있어도 충돌문제가 발생하지 않는다.

이름 공간을 지정할 때는 범위지정 연산자(scope resolution operator)인 :: 을 사용한다.


이름 공간 기반에서 함수 선언과 정의의 분리 
#include <iostream>

namespace BestComImpl
{
	void SimpleFunc(void);
}

namespace ProgComImpl
{
	void SimpleFunc(void);
}

int main(void)
{
	BestComImpl::SimpleFunc();
	ProgComImpl::SimpleFunc();
	return 0;
}

void BestComImpl::SimpleFunc(void)
{
	std::cout<<"BestCom이 정의한 함수"<<std::endl;
}	

void ProgComImpl::SimpleFunc(void)
{
	std::cout<<"ProgCom이 정의한 함수"<<std::endl;
}

Output:
1
2
BestCom이 정의한 함수
ProgCom이 정의한 함수


다음 예제에서와 같이 namespace를 둘 이상의 영역으로 나눌 수도 있으며, 동일한 namespace에 정의된 함수는 직접 호출이 가능하다

#include <iostream>

namespace BestComImpl
{
	void SimpleFunc(void);
}

namespace BestComImpl		// 3,8행과 같이 namespace를 둘 이상의 영역으로 나뉘어 선도 가능
{
	void PrettyFunc(void);
}

namespace ProgComImpl
{
	void SimpleFunc(void);
}

int main(void)
{
	BestComImpl::SimpleFunc();	
	return 0;
}


void BestComImpl::SimpleFunc(void)
{
	std::cout << "BestCom이 정의한 함수" << std::endl;
	PrettyFunc();					// 동일 이름공간
	ProgComImpl::SimpleFunc();		// 다른 이름공간
}

void BestComImpl::PrettyFunc(void)
{
	std::cout << "So Pretty!!" << std::endl;
}

void ProgComImpl::SimpleFunc(void)
{
	std::cout << "ProgCom이 정의한 함수" << std::endl;
}


Output:

1
2
3
BestCom이 정의한 함수
So Pretty!!
ProgCom이 정의한 함수



다음과 같이 namespace를 중첩시키는 것도 가능하다.


#include <iostream>

namespace Parent
{
	int num = 2;

	namespace SubOne
	{
		int num = 3;
	}

	namespace SubTwo
	{
		int num = 4;
	}
}

int main(void)
{
	std::cout << Parent::num << std::endl;
	std::cout << Parent::SubOne::num << std::endl;
	std::cout << Parent::SubTwo::num << std::endl;
	return 0;
}


Output:

1
2
3
2
3
4



/* 하나의 소스파일을 다음과 같이 3개의 파일로 분할 컴파일 하기

헤더파일  : main 함수를 제외한 나머지 두 함수의 선언 삽입

소스파일1 : main 함수를 제외한 나머지 두 함수의 정의 삽입

소스파일2 : main 함수만 삽입

*/


온라인에서 컴파일 및 실행해보기

-> https://goo.gl/eHaIhO



namespace에 대해 공부했기 때문에 std::cout은 namespace std에 선언된 cout이라는 의미임을 알 수 있다.




using을 이용한 namespace 명시


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>

namespace Hybrid
{
	void HybFunc()
	{
		std::cout << "So Simple function!" << std::endl;
		std::cout << "In namespace Hybrid!" << std::endl;
	}
}

int main()
{
	using Hybrid::HybFunc;	
	// namespace Hybrid에 정의된 HybFunc를 namespace지정없이 호출하겠단 선언
	HybFunc(); // 14행의 using선언을 통해 namespace 지정없이 HybFunc 함수 호출
	return 0;
}

Output:
1
2
So Simple function!
In namespace Hybrid!


위의 예제는 using선언이 main함수내에 존재하는데, 이러한 경우 지역변수의 성격과 동일하다.

따라서 프로그램 전체 영역에 효력을 미치게 하려면 전역변수와 같이 함수밖에 선언을 한다.


함수밖에 using선언

1
2
3
4
5
6
7
8
9
#include <iostream>
using std::cout;
using std::endl;

int main()
{
	cout << "Hello, World!" << endl;
	return 0;
}


Output:

1
Hello, World!



다음과 같이 namespace std에 선언된 모든 것에 접근할 때에 namespace 지정을 생략하도록 선언할 수도 있다.

하지만 이와 같은 선언은 이름충돌이 발생할 확률이 상대적으로 높아지기에 적절한 판단하에 쓰는 것이 중요하다.

1
2
3
4
5
6
7
8
#include <iostream>
using namespace std;

int main()
{
	cout << "Hello, World!" << endl;
	return 0;
}


Output:

1
Hello, World!




namespace 중첩시 별칭을 주기위한 선언


#include <iostream>
using namespace std;

namespace AAA
{
	namespace BBB
	{
		namespace CCC
		{
			int num1;
			int num2;
		}
	}
}

int main()
{	
	namespace ABC = AAA::BBB::CCC;		// AAA::BBB::CCC를 namespace ABC로 별칭함
	AAA::BBB::CCC::num1 = 20;	// 별칭없이 중첩된 namespace에 차례로 접근
	ABC::num2 = 30;				// 별칭을 이용해 namespace에 접근

	cout << AAA::BBB::CCC::num1 << endl;	// 별칭없이 접근
	cout << ABC::num2 << endl;				// 별칭 ABC를 통해 접근

	return 0;
}


Output:

1
2
20
30



지역변수 이름이 전역변수와 이름이 같은 경우, 전역변수는 지역변수에 의해 가려진다.

SimpleFunc 함수내에서 전역변수 val에 접근할 때에는 범위지정 연산자 :: 를 사용하면 된다.

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>

int val = 100;		// 전역변수 선언

int main()
{	
	int val = 20;	// 지역변수
	val += 3;		// 지역변수 val의 값 3 증가
	::val += 7;		// 전역변수 val의 값 7 증가
	return 0;
}


'Study > C++' 카테고리의 다른 글

생성자와 소멸자, 클래스  (0) 2016.09.26
class  (0) 2016.09.25
CPP-Object Oriented Programming (객체지향 프로그래밍)  (0) 2016.09.23
c++ Class  (0) 2016.09.22
c++ 기초3  (0) 2016.09.21
728x90

이진 트리 구현의 예


BinaryTree.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#ifndef __BINARY_TREE_H__
#define __BINARY_TREE_H__
 
typedef int BTData;
 
typedef struct _bTreeNode
{
    BTData data;
    struct _bTreeNode * left;
    struct _bTreeNode * right;
} BTreeNode;
 
/*** BTreeNode 관련 연산들 ****/
BTreeNode * MakeBTreeNode(void);
BTData GetData(BTreeNode * bt);
void SetData(BTreeNode * bt, BTData data);
 
BTreeNode * GetLeftSubTree(BTreeNode * bt);
BTreeNode * GetRightSubTree(BTreeNode * bt);
 
void MakeLeftSubTree(BTreeNode * main, BTreeNode * sub);
void MakeRightSubTree(BTreeNode * main, BTreeNode * sub);
 
#endif
cs


BinaryTree.c

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <stdio.h>
#include <stdlib.h>
#include "BinaryTree.h"
 
BTreeNode * MakeBTreeNode(void)
{
    BTreeNode * nd = (BTreeNode*)malloc(sizeof(BTreeNode));
 
    nd->left = NULL;
    nd->right = NULL;
    return nd;
}
 
BTData GetData(BTreeNode * bt)
{
    return bt->data;
}
 
void SetData(BTreeNode * bt, BTData data)
{
    bt->data = data;
}
 
BTreeNode * GetLeftSubTree(BTreeNode * bt)
{
    return bt->left;
}
 
BTreeNode * GetRightSubTree(BTreeNode * bt)
{
    return bt->right;
}
 
void MakeLeftSubTree(BTreeNode * main, BTreeNode * sub)
{
    if(main->left != NULL)
        free(main->left);
 
    main->left = sub;
}
 
void MakeRightSubTree(BTreeNode * main, BTreeNode * sub)
{
    if(main->right != NULL)
        free(main->right);
 
    main->right = sub;
}
 
cs


void MakeLeftSubTree(BTreeNode * main, BTreeNode * sub)

{

if(main->left != NULL)

free(main->left);


main->left = sub;

}


위의 함수에서 왼쪽 서브트리가 하나의 노드라면 문제없지만,

그렇지 않다면 나머지 노드는 잃어버리게 되고 메모리 누수로 이어진다.


둘 이상의 노드로 이루어진 서브트리를 완전히 삭제하려면

서브 트리를 구성하는 모든 노드를 대상으로 free함수를 호출해야 한다.



BinaryTreeMain.c

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
#include <stdio.h>
#include "BinaryTree.h"
 
int main(void)
{
    BTreeNode * bt1 = MakeBTreeNode();    
    BTreeNode * bt2 = MakeBTreeNode();
    BTreeNode * bt3 = MakeBTreeNode();
    BTreeNode * bt4 = MakeBTreeNode();    // 노드 bt 1~4 생성
 
    SetData(bt1, 1);    // bt1에 1저장
    SetData(bt2, 2);
    SetData(bt3, 3);
    SetData(bt4, 4);
 
    MakeLeftSubTree(bt1, bt2);    // bt2를 bt1의 왼쪽 자식 노드로
    MakeRightSubTree(bt1, bt3);    // bt3을 bt1의 오른쪽 자식 노드로
    MakeLeftSubTree(bt2, bt4);    // bt4를 bt2의 왼쪽 자식 ㅗ드로
 
    // bt1의 왼쪽 자식 노드의 데이터 출력 
    printf("%d \n",
        GetData(GetLeftSubTree(bt1)));
 
    // bt1의 왼쪽 자식 노드의 왼쪽 자식 노드의 데이터 출력
    printf("%d \n",
        GetData(GetLeftSubTree(GetLeftSubTree(bt1))));
 
    return 0;
}
cs


Output :

2

4





이진 트리의 순회(Traversal)

이진 트리의 순회 방법은 재귀적(recursive)이다.


루트 노드를 방문하는 시점에 따라 세 가지 순회 방법으로 나뉜다.







서브 트리가 존재하는 이진 트리의 중위 순회



// 이진 트리 전체를 중위 순회하는 함수

void InorderTraverse(BTreeNode * bt)

{

if(bt == NULL)    // bt가 NULL이면 재귀 탈출! 

return;


InorderTraverse(bt->left); 

printf("%d \n", bt->data); 

InorderTraverse(bt->right); 

}




재귀함수와 관련된 코드를 이해할 떄는 단순히 거슬러가는것이 아니라,

복사본을 만들고 호출하고 호출된 순서의 역순으로 반환이 이뤄진다고 이해하면 쉽다.


BinaryTreeTraverseMain.c

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
32
#include <stdio.h>
#include "BinaryTree.h"
 
void InorderTraverse(BTreeNode * bt)
{
    if(bt == NULL)    // bt가 NULL이면 재귀 탈출! 
        return;
 
    InorderTraverse(bt->left); 
    printf("%d \n", bt->data); 
    InorderTraverse(bt->right); 
}
 
int main(void)
{
    BTreeNode * bt1 = MakeBTreeNode();
    BTreeNode * bt2 = MakeBTreeNode();
    BTreeNode * bt3 = MakeBTreeNode();
    BTreeNode * bt4 = MakeBTreeNode();
 
    SetData(bt1, 1);
    SetData(bt2, 2);
    SetData(bt3, 3);
    SetData(bt4, 4);
 
    MakeLeftSubTree(bt1, bt2);
    MakeRightSubTree(bt1, bt3);
    MakeLeftSubTree(bt2, bt4);
 
    InorderTraverse(bt1);
    return 0;
}
cs


Output :

4

2

1

3




함수 포인터의 활용

typedef void VisitFuncPtr(BTData data);    // (*VisitFuncPtr)로 대신 가능


void형 포인터는 형 정보가 존재하지 않기에 어떠한 주소값도 저장이 가능하다. (하지만 형정보가 없기에 메모리 접근을 위한 * 연산은 불가능)


1
2
3
4
5
6
7
8
9
void InorderTraverse(BTreeNode * bt, VisitFuncPtr action)
{
    if(bt == NULL)
        return;
 
    InorderTraverse(bt->left, action);
    action(bt->data);
    InorderTraverse(bt->right, action);
}
cs


action이 가리키는 함수를 통해 방문 진행



action(bt->data);    // 노드의 방문


VisitFuncPtr 형을 기준으로 정의된 함수

void ShowIntData(int data)

{

printf("%d ", data);

}


action에 전달되는 함수의 내요에 따라 노드의 방문결과가 결정.
위와 같이 data가 전달되면 노드의 방문 결과는 출력(printf)이 된다.

BinaryTreeTraversalAdded.7z


output :

1 2 4 5 3 6

4 2 5 1 3 6

4 5 2 6 3 1




트리의 삭제 함수 정의하기

1
2
3
4
5
6
7
8
9
10
11
void DeleteTree(BTreeNode * bt)
{
    if (bt == NULL)
        return;
 
    DeleteTree(bt->left);
    DeleteTree(bt->right);
 
    printf("트리 data 삭제: %d \n", bt->data);
    free(bt);
}
cs


main함수에서는 DeleteTree(bt1); 와 같이 호출한다. 호출되면 bt1이 가리키는 노드를 루트 노드로 하는 트리 전부가 지워된다.


루트 노드가 마지막에 삭제되어야 하기 때문에 반드시 후위순회(Postorder Traversal)의 과정을 거쳐야 한다.


output :

1 2 4 5 3 6

4 2 5 1 3 6

4 5 2 6 3 1

트리 data 삭제: 4

트리 data 삭제: 5

트리 data 삭제: 2

트리 data 삭제: 6

트리 data 삭제: 3

트리 data 삭제: 1



'Data Structure' 카테고리의 다른 글

자료 구조 - Stack  (0) 2016.11.05
연결리스트와 파일 입출력  (0) 2016.11.02
Binary Tree  (0) 2016.09.12
Doubly Linked List  (0) 2016.09.09
Circular Linked List  (0) 2016.09.09
728x90

Tree는 단순한 데이터의 저장이 아닌 "데이터의 표현을 위한 도구"이다.


자료구조를 공부하는데 있어서 시야를 넓게 가지고 깊게 사고하려는 노력이 필요하다. 좁은 시야에서 다 이해했다고 착각은 금물


 


이진 트리의 조건

: 루트 노드를 중심으로 두 개의 서브 트리로 나뉘어짐 (empty set도 node로 간주)

나뉘어진 두 서브 트리도 모두 이진트리


이러한 특성에서 그 구조가 재귀적임을 보인다.







Tree의 level과 높이



각 노드의 깊이(depth)는 루트 노드에서 자신까지 가는 경로의 길이이다. 

트리의 특정 깊이를 가지는 노드의 집합을 레벨(level)이라 부르기도 한다. 루트 노드의 깊이는 0이다.


트리의 높이(height)는 루트 노드에서 가장 깊이 있는 노드까지 가는 경로의 길이이다. 루트 노드만 있는 트리의 높이는 0이다.








full binary tree(정 이진 트리)

leaf node(자식이 없는 노드)가 아닌 모든 노드가 2개의 자식을 가진 트리이다.


complete binary tree(완전 이진 트리)

위에서 아래로 왼쪽에서 오른쪽으로 채워진 트리를 말한다.


끝 부분(마지막 레벨)을 제외하고 모든 노드가 채워진(마지막 레벨을 제외하고는 모든 노드가 자식노드를 2개를 가진) 이진 트리. 

마지막 레벨의 노드들은 왼쪽으로 채워져 있다. 마지막 레벨이 다 채워질 수도 있다.



perfect binary tree(포화 이진 트리)

모든 단말 노드의 깊이가 같은 정 이진 트리이다.

동시에 complete binary tree이기도 하다. (모든 레벨에 노드가 꽉 차야하므로, 역은 성립하지 않음)




이진 트리 구현의 두가지 방법


배열 기반, 이진 트리

위에서 아래, 왼쪽에서 오른쪽으로 노드에 번호를 부여



양방향 연결리스트 기반, 이진트리


실제 구현

하나의 노드는 그 자체로 이진 트리 (entry set도 node이다)

따라서 노드를 표현한 구조체는 실제로 이진 트리를 표현한 구조체가 된다.


// 노드를 표현한 구조체

typedef struct _bTreeNode

{

BTData data;

struct _bTreeNode * left;

struct _bTreeNode * right;

}BtreeNode;


이진 트리의 모든 노드는 직/간접적으로 연결되어 있으며

루트 노드의 주소 값만 기억하면 이진 트리 전체를 가리키는 것과 다름 없다.



이진 트리 자료구조의 ADT (헤더파일에 선언되는 함수들)


BTreeNode * MakeBTreeNode(void); // 노드 생성



BTData GetData(BTreeNode * bt); // 노드에 저장된 데이터 반환


void SetData(BtreeNode * bt, BTData data); // 노드에 데이터 저장


노드에는 함수를 통한 접근이 좋다.



하나의 노드도 이진 트리란 사실을 인지. 


BTreeNode * GetLeftSubTree(BTreeNode * bt); // 왼쪽 서브 트리 주소값 반환

BTreeNode * GetRightSubTree(BTreeNode * bt); // 오른쪽 서브 트리 주소값 반환



 D의 주소값이 곧 왼쪽 서브트리의 주소값이 된다.




void MakeLeftSubTree(BTreeNode * main, BTreeNode * sub); // main의 왼쪽 서브트리로 sub 연결

void MakeRightSubTree(BTreeNode * main, BTreeNode * sub); // main의 오른쪽 서브트리로 sub 연결


위의 함수는 노드 뿐만 아니라 트리를 대상으로도 적용되는 함수들이다.





이진트리를 생성하는 main함수 예


int main(void)

{

BTreeNode * ndA = MakeBTreeNode();

BTreeNode * ndB = MakeBTreeNode();

BTreeNode * ndC = MakeBTreeNode();    // 노드 A, B, B 생성

SetData(ndA, 1);

SetData(ndB, 2);

SetData(ndB, 3);


MakeLeftSubTree(bt1, bt2);    // 노드A의 왼쪽 자식 노드로 노드 B 연결

MakeRightSubTree(bt1, bt3);  // 노드A의 오른쪽 자식 노드로 노드 C 연결


( ... )

}





'Data Structure' 카테고리의 다른 글

연결리스트와 파일 입출력  (0) 2016.11.02
이진 트리의 구현과 순회(Traversal)  (0) 2016.09.12
Doubly Linked List  (0) 2016.09.09
Circular Linked List  (0) 2016.09.09
연결리스트2  (0) 2016.09.08
728x90

Doubly Linked List (양방향 연결리스트)





typedef struct _node

{

Data data;

struct _node * next;

struct _node * prev;    // 이전 노드를 가리키는 멤버가 추가됨

} Node;



양방향 연결리스트의 LNext


왼쪽 노드의 주소값을 얻을 수 있기 때문에,

단순 연결리스트와 달리 before를 유지할 필요 없음



int LPrevious(List * plist, Data * pdata);




첫번째 노드 추가


newMode->next = NULL;

-> newMode->next = plist->head;



첫번째 노드를 추가할 때 plist->head는 NULL이기 때문에 위의 바뀐 문장으로 두가지 역할을 할 수 있다. 



두번째 노드의 추가는 다음과 같이 이뤄진다.



void LInsert(List * plist, Data data)

{

Node * newNode = (Node*)malloc(sizeof(Node));

newNode->data = data;


newNode->next = plist->head;        // (1) 새 노드의 next가 이전 노드에 연결

if(plist->head != NULL)                    // NULL이 아니기 때문에

plist->head->prev = newNode; // (2) 이전 node의 prev가 새 노드에 연결


(...생략)

}

void LInsert(List * plist, Data data)

{

Node * newNode = (Node*)malloc(sizeof(Node));

newNode->data = data;


(...생략)

newNode->prev = NULL;    // (1)

plist->head = newNode;    // (2)


(plist->numOfData)++;

}



데이터 조회

LFirst, LNext는 사실상 차이가 없으며

LPrevious함수는 prev를 통해 이전 노드의 위치를 쉽게 가리킬 수 있다는 점에서 차이가 있다.


int LPrevious(List * plist, Data * pdata)

{

if(plist->cur->prev == NULL)

return FALSE;


plist->cur = plist->cur->prev;

*pdata = plist->cur->data;


return TRUE;

}




원형 연결 리스트의 활용 예제

: 직원 정보 등록 프로그램 (원형 연결리스트 기반)



- 직원 정보 등록 가능

직원 정보는 사번과 이름으로 구성



- 직원 정보를 담을 구조체 정의

구조체를 기반으로 네명의 직원정보를 원형 연결리스트에 저장 (임의 결정, 입력받지 않고 미리 결정해도 됨)

직원 사번은 int형 변수에 담음

원형 연결리스트에는 구조체 변수의 주소 값을 저장



- 직원은 순서대로 돌아가며 당직근무

등록순서대로 당직근무

(A,B,C순 직원등록시 당직근무도 A-B-C-A-B...)



- 직원 이름과 숫자를 이용해 당직자 확인

직원의 이름과 숫자를 인자로 전달받는 함수 정의


- 이 함수는 다음과 같은 직원의 정보 반환

예를 들어 '이수정'과 숫자 7이 전달되면 이수정의 당직근무 7일후

누가 당직근무를 하는지에 대한 정보 반환


'Data Structure' 카테고리의 다른 글

이진 트리의 구현과 순회(Traversal)  (0) 2016.09.12
Binary Tree  (0) 2016.09.12
Circular Linked List  (0) 2016.09.09
연결리스트2  (0) 2016.09.08
연결리스트  (0) 2016.09.05

+ Recent posts