언어/C++

생성자와 접근제어

빨대도둑 2022. 10. 27. 18:13

생성자

초기화를 담당하는 특수한 함수이다. 생성자를 사용하면 객체의 생성과 동시에 초기화가 가능하다.

> 변수의 경우 초기화를 하지 않으면 쓰레기값을 가지는 경우가 종종 있다. 객체도 마찬가지이다. 객체도 초기화하지 않으면 쓰레기값을 가진다. 객체가 생성된 후에는 반드시 객체를 초기화하여야 한다. 객체의 초기화에는 멤버 변수의 값을 초기화하는 것뿐만 아니라 객체의 동작에 필요한 메모리 공간이나 기타 자원들을 할당하는 것도 포함된다. 

> 객체를 생성할 때 객체를 초기화하는 함수가 자동으로 호출된다면 매우 편할 것이다. 이것이 생성자이다. 

class Time{
public: 
	int hour;
    int minute;
    
    //생성자
    Time(int h, int m){
    	hour=h;
        minute=m;
    }
 	void print(){
    	cout<<hour<<":"<<minute<<endl;
    }
};

 

- 생성자는 클래스 이름과 똑같은 멤버함수이다. 생성자는 객체를 초기화하는데 사용된다. 인수가 있는 생성자가 정의되어 있는데 개발자가 잊어버리고 값들을 주지 않으면 컴파일러가 오류를 발생한다. 

1> Time a	//오류, 초기값이 없음
2> Time b(10,25)	//예전의 방법
3> Time c{10,25}	//최신 방법
4> Time d={10,25};	//간결하지 않음
  1. 객체 a을 선언한 문장은 오류이다. Time 생성자를 호출하는데 필요한 인수들을 제공하지 않았기 때문이다.
  2. 객체 b를 선언한 방식은 예전부터 많이 사용했던 방법이다. 하지만 이 방식은 () 기호를 사용하는데, () 기호는 함수 선언하는데도 사용되기 때문에 혼돈할 위험성이 있다. 그래서 최근에는 장려되지 않는다.
  3. 최근의 표준적인  방법이다. 변수의 이름 뒤에 {}기호를 사용하여 초기값을 제공한다. 보편적인 초기화 방법이다. 이 방법은 일반적인 변수에 초기값을 줄 때도 사용할 수 있다. 
  4. 이 방법도 좋지만 '=' 때문에 약간은 간결하지 못한 방법이다. 하지만 가독성은 좋다.

 

생성자 중복 정의

- 생성자도 멤버함수의 일종이라고 생각할 수 있다. 따라서 생성자도 중복 정의될 수 있다.  

#include <iostream>
using namespace std;

class Time {
public:
	int hour;
	int minute;

	//생성자 중복 정의
	Time() {
		hour = 0;
		minute = 0;
	}
    //생성자 중복 정의
	Time(int h, int m) {
		hour = h;
		minute = m;
	}
	void print() {
		cout << hour << ":" << minute << endl;
	}
};

int main() {
	Time a;		//매개변수가 없는 생성자가 호출됨
	Time b(10, 25);	//매개변수가 있는 생성자가 호출됨

	a.print();
	b.print();

	return 0;
}

- 위의 예제에서 2개의 Time클래스의 객체 a와 b가 생성되었다. b는 2개의 인수로 초기화되었다. 하지만 a는 인수가 없는 생성자로 초기화되었다. 인수가 없는 생성자를 기본생성자라고 한다. 기본 생성자는 객체를 생성시 인수를 주지 않으면 자동으로 호출된다. 

> 생성자도 함수의 일종이기 때문에 매개 변수는 디폴트 값을 가질 수 있다.

 

멤버 초기화 리스트

Time(int h, int m){
	hour=h;
    minute=m;
}

->최신 방법
Time(int h, int m):hour(h),minute(m){
}

Time(int h, int m):hour{h},minute{m}
{
}

Time(int h=0, int m=0):hour{h},minute{m}
{
}

 - 이것을 초기화리스트라고 부른다. 초기화 리스트를 사용하면 멤버들을 좀 더 쉽게 초기화할 수 있다. 

 


「소멸자」: 객체가 소멸될때 호출되는 함수

> 생성된 객체가 범위를 벗어나면 객체는 소멸된다. 이때마다 호출되는 특정한 멤버함수가 소멸자이다. 소멸자는 클래스 이름에 (~) 물결표 접두사를 붙여서 만든다. 

- 소멸자는 생성자와 마찬가지로 값을 반환하거나 매개 변수를 사용할 수 없다. 소멸자는 파일을 닫거나 메모리를 반환하는 작업과 같이 프로그램을 종료하기 전에 자원을 반납하는데 매우 유용하게 사용될 수 있다. 

#include <string.h>

class MyString {
private:
	char *s;
	int size;

public:
	MyString(char* c) {
		size = strlen(c) + 1;
		s = new char[size];
		strcpy(s, c);
	}
	~MyString() {
		delete[]s;
	}

};

int main() {
	MyString str("abcedfghijk");
}

 

 

「접근 제어」

> 외부에서 특정한 멤버 변수나 멤버함수에 접근하는 것을 제어하는 것. private, public등의 접근 지정자를 멤버 변수나 멤버 함수 앞에 붙여서 접근을 제어한다. 

- private를 붙이면 전용멤버로 되어서 클래스안에서만 사용할 수 있다. public을 붙이면 외부 클래스들이 사용할 수 있다. 

 

전용멤버

- 클래스 내부에서만 접근이 허용된다. 멤버 정의시에 앞에 private를 적어주면 이후에 정의되는 모든 멤버는 전용멤버가 된다. 만약 멤버 앞에 접근 지정자가 생략되면 자동적으로 전용멤버가 된다. 전용 멤버는 클래스 내부에서만 사용이 가능하다. 일반적으로 정보 은닉의 취지에 따라서 멤버 변수는 전용멤버로 선언하는 것이 좋다. 

- 공용멤버는 다른 모든 클래스들이 사용할 수 있다. 공용 멤버를 선언하기 전에 접근 지정자인 public을 적어주면 된다. public 이우에 선언된 모든 멤버는 다른 접근 지정자가 나오기 전까지 공용멤버가 된다. 공용멤버는 객체를 통해 접근할 수 있다. 

 

설정자

- private으로 선언된 멤버 변수들을 읽어서 외부로 전달하는 함수를 제공하는 것을 접근자(getter)라고 하며, 외부에서 안전하게 멤버 변수들을 변경할 수 있는 함수를 제공하는 것을 설정자 (setter)라고 한다. 

- 접근자 함수는 앞에 get을 붙이고 설정자 함수는 앞에 set을 붙이는 것이 불문율이다. 

- 접근자와 설정자는 크기가 작으므로 클래스 내부에 정의하는 것이 보통이다. 

#include <iostream>
using namespace std;

class Time {
public:
	Time(int h, int m);
	void inc_hour();
	void print();

	//접근자와 설정자 정의
	int getHour() { return hour; }
	int getMinute() { return minute; }
	int setHour() { hour=h; }
	int setMinute() { minute = m; }

private:
	int hour;
	int minute;
};

Time::Time(int h, int m) {
	hour = h;
	minute = m;
}

void Time::inc_hour() {
	++hour;
	if (hour > 23)
		hour = 0;
}

void Time::print() {
	cout << hour << ":" << minute << endl;
}
int main() {
	Time a(0, 0);

	a.setHour(6);
	a.setMinute(30);

	a.print();
	return 0;
}

 

접근자와 설정자를 사용하는 이유

- 정보은닉 때문이다. 클래스와 인터페이스와 구현이 분리되면 구현을 변경하기 쉬워진다.

- 설정자에서 매개 변수를 통해 잘못된 값이 넘어오는 경우, 이를 사전에 차단할 수 있다. 즉 새로운 값이 적절한지 판단할 수 있다. 

- 멤버 변수값을 필요할 때마다 계산하여 반환할 수 있다

- 접근자만을 제공하면 자동적으로 읽기만 가능한 멤버 변수를 만들 수 있다. 또한 접근자는 반환하는 데이터의 형식을 제어할 수 있어서 외부 코드가 실제 데이터 표현 방식을 모르게 할 수 있다. 

 


「객체와 함수」

객체가 함수의 매개 변수로 전달되는 경우

- 함수는 인수의 기본적인 값에 의하여 매개 변수로 전달된다. 이것은 인수가 객체일 때도 마찬가지이다. 

- 안정성을 장점으로 가지며 함수에서 매개 변수를 어떻게 변경하건 원본 객체에는 영향을 주지 않는다. 하지만 객체의 크기가 커지면 객체를 복사하는 것도 상당히 시간이 걸린다. 

- 매개 변수 객체의 생성자는 호출되지 않지만 매개 변수 객체의 소멸자는 호출된다. 매개 변수 객체의 생성자가 호출되지 않는 이유는  다른 객체의 값이 복사되어야 하기 때문이다.

 

객체의 참조자가 함수의 매개변수로 전달되는 경우

- 참조자란 변수에다가 하나의 이름을 더 주는 것이다. 참조자는 &기호를 이용하여 정의한다.

int i;	//변수 정의
int& j;	//참조자 정의

- 참조자를 통하여 변수의 값을 변경하면 원본 변수를 변경하는 것이나 마찬가지이다. 객체에 대한 참조자도 마찬가지의 역할을 한다. 

- 참조자로 객체를 받으면 객체의 이름이 하나 더 생기는 것과 마찬가지이다. 따라서 참조자를 통하여 객체를 변경하면 원 객체를 변경하는 것이다. 

- 일반적으로 객체를 값으로 전달하는 것보다 객체의 참조자를 전달하는 편이 더 효율적이다. 객체의 크기가 큰 경우에 객체를 복사하는 시간이 많이 걸리기 때문이다. 

 

함수가 객체를 반환하는 경우

- 함수가 객체를 반환할 대도 객체의 내용이 복사될 뿐 원본이 전달되지 않는다.

 

 

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

복사생성자  (0) 2022.11.14
포인터  (0) 2022.11.07
클래스와 객체  (0) 2022.10.27
함수  (0) 2022.10.22
제어구조, 배열  (0) 2022.10.21