본문 바로가기
2CHAECHAE 학교생활/객체지향프로그래밍(C++)

[ 객체지향프로그래밍(C++) 9주차 ① ]

by 2CHAE._.EUN 2022. 5. 1.

[ 9주차 -1 ]

 

friend 함수

 

멤버가 아닌 함수가 클래스의 전용 멤버를 접근할 수 있도록 허용하는 것으로 함수를 클래스의 freind로 선언한다.

함수의 프로토타입을 클래스 선언의 public 구역에 포함하고 앞에 friend 키워드를 쓴다.

 

같은 클래스에 있는 멤버들은 private 변수에 접근할 수 있지만 클래스 밖에서 외부 접근 하는 경우는 private

변수에 접근할 수 없다. 즉 private 변수에 외부 접근을 허용해주는 것이 friend이다.

 

사용 이유 : 

ⓐ 연산자를 중복하는데 유용

ⓑ I/O 함수의 생성을 단순화

ⓒ 둘 또는 그 이상의 클래스들이 프로그램의 다른 부분과 상대적으로 연관될 수 있는 멤버들을 포함한다.

 

class cl{

public:
	friend void frnd(cl ob);
    ..
};

 

#include <iostream>
using namespace std;

class myclass {
private:
	int a, b;
public:
	myclass(int i, int j) {
		a = i;
		b = j;
	}
	friend int sum(myclass x);
};


//sum()은 어떠한 클래스의 멤버 함수가 아니다. myclass와는 아무 상관이 없다.
int sum(myclass x) {

	//sum()이 myclass의 friend이므로 a,b에 직접 접근할 수 있다.
	return x.a + x.b;

}

int main() {
	myclass n(3, 4); // 생성자 함수를 통해 3,4가 저장이 된다.
	cout << sum(n);
	return 0;
}

// 7

 

sum()은 myclass의 외부에 선언되었기 때문에 myclass와는 아무 상관이 없고 friend를 지정해줬기 때문에 myclass의

private 변수에 언제든지 접근할 수 있다.

 

sum(n)을 통해 n이 x라는 매개변수에 복사가 된다. x 매개변수는 n 객체가 그대로 들어가게 된다. 그래서 n이 가지고
있는 a,b와 x가 가지고 있는 a,b는 같은 값이다. 복사가 되고 3+4를 해서 7이 리턴이 된다.

 

sum()이 friend로 지정되지 않았다면 외부 접근에 해당해서 에러가 난다. 즉 friend로 지정해주면 지정된 함수가

어느 클래스의 private 변수에 외부 접근하는 것을 허용해준다. 

 

또한 어떤 클래스의 멤버인 함수가 다른 클래스의 friend가 될 수 있다.

 

#include <iostream>
using namespace std;

const int IDLE = 0;
const int INUSE = 1;
class C2; // 전방선언
class C1 {
private:
	int status; // off라면 IDLE, on이라면 INUSE
public:
	void set_status(int state);
	int idle(C2 b); // C1의 멤버이다.
};
class C2 {
private:
	int status;
public:
	void set_status(int state);
	friend int C1::idle(C2 b); // friend 함수
	// idle()이 C2의 friend가 되도록 선언할 때 범위 지정 연산자(::)을 사용
	// idle()는 C2의 private 변수들에 언제든지 접근할 수 있다. 
};

void C1::set_status(int state) {
	status = state;
}

void C2::set_status(int state) {
	status = state;
}

//idle()은 C1의 멤버이나, C2의 friend이다.
int C1::idle(C2 b) { 
	//status는 C1이 가지고 있는 status이고 b.status는 C2가 가진 status이다.
	if (status || b.status) {
		return 0;
	}
	else {
		return 1;
	}
}

int main() {

	C1 x;
	C2 y;

	x.set_status(IDLE);
	y.set_status(IDLE);

	/*
	idle()이 C1의 멤버이기 때문에 C1형 객체의 status 변수를 직접 접근할 수 있다.
	따라서 C2형의 객체만이 idle()에 전달될 필요가 있다.
	*/
	if (x.idle(y)) {
		cout << "Screen Can Be Used.\n";
	}
	else {
		cout << "Pop-up In Use.\n";
	}

	x.set_status(INUSE);

	if (x.idle(y)) {
		cout << "Screen Can Be Used.\n";
	}
	else {
		cout << "Pop-up In Use.\n";
	}

}

// Screen Can Be Used.
// Pop-up In Use.

 

즉, 다른 클래스에 소속되어있는 멤버 함수를 friend로 지정해주면 friend로 지정된 함수 입장에서는 다른 클래스

형의 객체를 인자로 받아서 그 객체의 private 멤버변수에 언제든지 접근할 수 있다.

 

 

생성자의 중복

 

다른 유형의 함수처럼 생성자 함수도 여러 형태를 선언하고 그 동작을 정의할 수 있다.

 

클래스 이름과 동일한 생성자가 매개변수의 자료형이 모두 다르고, 매개변수의 개수가 모두 다를 경우 이름은

중복이라도 선언이 가능하다.

 

#include <iostream>
#include<cstdlib>
#include<ctime>
using namespace std;

class timer {
private:
	int seconds;
public:
	
	timer(char* t) { //문자열로 초를 명시한다.
		seconds = atoi(t); //atoi()는 문자열을 정수로 변환해준다. 
	}
	timer(int t) { //정수로 초를 명시한다.
		seconds = t;
	}
	timer(int min, int sec) { // 분과 초로 시간을 명시한다.
		seconds = min * 60 + sec;
	}
	void run();
};

void timer::run() { 
	clock_t t1; // clock_t는 시간을 저장할 수 있는 cpp 라이브러리에 저장된 클래스
	t1 = clock(); // clock()은 현재 시스템 시간을 리턴해준다.
	while ((clock() / CLOCKS_PER_SEC - t1 / CLOCKS_PER_SEC) < seconds) {
		cout << "\a"; //벨을 울린다.
	}
}

int main() {

	timer a(10), b('20'), c(1, 10);

	a.run(); //10초 count
	b.run(); //20초 count
	c.run(); //1분 10초 count

	return 0;


}

 

* atoi() : 문자열을 실수로 변환해서 리턴해준다. 

→ 정수로 변환될 수 없는 문자인 경우에는 0이 리턴된다. ex) "aa"

→ 정수 + 정수로 변환될 수 없는 문자열일 경우는 정수로 리턴된다. ex) "12ab" → 12

 

 

동적 초기화 ( Dynamic Initialization )

 

1. 지역 변수와 전역 변수 모두 실행 시에 초기화되는 것을 말한다.

2. 동적 초기화에서는 변수를 실행시에 그 변수가 선언된 시점에서 입력을 받아서 유효한 C++식을 사용해서 초기화

   할 수 있다. 변수를 다른 변수 또는 함수 호출을 이용하여 초기화할 수 있다.

3. 동적 초기화를 생성자에 적용하기

- 실행시에만 알려지는 정보를 이용하여 필요한 객체를 정확히 생성할 수 있다.

- timer() 생성자 중복 예에서 사용

  → 그 순간에 사용 가능한 데이터의 형식에 가장 가까운 생성자를 사용할 수 있는 융통성을 제공한다.

 

다양한 형태로 생성자 함수를 중복시키서 사용할 경우

 

#include <iostream>
#include<cstdlib>
#include<ctime>
using namespace std;

class timer {
private:
	int seconds;
public:
	
	timer(char* t) { //문자열로 초를 명시한다.
		seconds = atoi(t); //atoi()는 문자열을 정수로 변환해준다. 
	}
	timer(int t) { //정수로 초를 명시한다.
		seconds = t;
	}
	timer(int min, int sec) { // 분과 초로 시간을 명시한다.
		seconds = min * 60 + sec;
	}
	void run();
};

void timer::run() { 
	clock_t t1; // clock_t는 시간을 저장할 수 있는 cpp 라이브러리에 저장된 클래스
	t1 = clock(); // clock()은 현재 시스템 시간을 리턴해준다.
	while ((clock() / CLOCKS_PER_SEC - t1 / CLOCKS_PER_SEC) < seconds) {
		cout << "\a"; //벨을 울린다.
	}
}

int main() {

	timer a(10); // 동적 초기화가 아닌 매개변수를 이용
	a.run(); //10초 count
	cout << "Enter number of seconds : ";

	/*
	객체 b와 c는 사용자가 입력한 정보를 이용해서 생성한다.
	다양한 초기화 형식을 가짐으로써 객체를 초기화할 때 변환의 필요성을 없애준다.
	*/
	char str[80];
	cin >> str; // 입력된 문자열을 이용해서 b timer을 실행
	timer b(str); // 실행시에 초기화한다.
	b.run();
	cout << "Enter minutes and seconds : ";

	int min, sec;
	cin >> min >> sec;
	timer c(min, sec); // 실행시에 초기화한다.
	c.run();

	return 0;

}

 

필요시에 따라 실행시 입력받아서 결정되는 값을 초기값으로 사용하면 생성자 중복을 통해 다양한 형태의 

생성자를 사용할 수 있고 다양한 초기 값을 사용할 수 있다.

 

 

실습문제

 

하나의 좌표를 나타내는 Point 클래스를 정의한다. 생성자 함수는 x와 y의 초기값을 매개변수로 받아들인다.

 

class point{
private:
	int x,y;
public :
	//멤버함수
};

 

각 좌표값에 대해 원점까지의 거리를 계산하여 반환하는 origin_distance 멤버함수를 작성하고, main 함수를 작성해서

실행한다.

 

point p1(3,4), p2(-1,-5);

cout << "p1의 원점 거리 : " << p1.origin_distance();
cout << "p2의 원점 거리 : " << p1.origin_distance();