클래스(class)는 객체 지향 프로그래밍(OOP)에서 특정 객체를 생성하기 위해 변수와 메서드를 정의하는 일종의 틀이다. 객체를 정의하기 위한 상태(멤버 변수)와 메서드(함수)로 구성된다. C++의 클래스는 C언어의 구조체(Struct)와 같이 개발자가 정의하는 새로운 데이터 타입으로 클래스는 일반적으로 클래스 선언부(class declaration)와 클래스 구현부(class implementation)로 나누어 작성된다.
<클래스 사용 예시>
#include<iostream>
using namespace std;
class Circle { // 클래스 선언
public:
int radius; // 멤버 변수
double getArea(); // 멤버 함수
};
double Circle::getArea() { // 멤버 함수
return 3.14 * radius * radius;
}
int main()
{
Circle a; // 객체 생성
a.radius = 1; // a라는 객체의 멤버 변수 radius의 값 1로 지정
cout << a.getArea() << endl; // a라는 객체의 멤버 함수 호출
}
위의 코드는 a라는 객체를 생성하여 객체 내의 멤버 변수 radius에는 값을 1로 지정하고 객체를 통해 멤버 변수 호출 시(a.getArea()) getArea() 함수는 객체 a의 멤버 변수 radius를 이용하여 값을 리턴한다.
클래스 선언부
클래스는 class 키워드와 클래스 이름으로 선언한다.
class Circle { // Circle 이름의 클래스 선언
...
}; // 반드시 세미콜론(;)으로 종료
클래스의 멤버는 변수와 함수로 구성되며 멤버 변수는 클래스 선언부에서 초기화될 수 있으며 멤버 함수는 원형 형태로 선언되며, 리턴 타입, 매개 변수, 리스트 등이 모두 선언되어야 한다.
클래스 구현부
클래스 구현부에서는 클래스 선언부에 선언된 멤버 함수의 코드를 구현한다.
double Circle::getArea() {
return 3.14 * radius * radius;
}
범위 지정 연산자(::)를 사용하는 이유는 같은 이름의 함수가 다른 클래스에도 있을 수 있기 때문이다.
마치 "도원동"이라는 동네가 서울 용산구의 도원동인지, 대구 달서구의 도원동인지 헷갈릴 수 도 있는 것처럼 말이다.
그리고 클래스를 클래스 선언과 구현으로 분리하여 작성하는 이유는 클래스의 재사용을 위해서이다. 클래스를 사용하고자 하는 다른 C++ 파일에서는 컴파일 시 클래스의 선언 부만 필요하기 때문이다.
객체의 생성과 활용
int main()
{
Circle a; // 객체 생성
a.radius = 1; // a라는 객체의 멤버 변수 radius의 값 1로 지정
cout << a.getArea() << endl; // a라는 객체의 멤버 함수 호출
}
객체를 생성하는 방법은 "클래스명 객체이름"으로 생성할 수 있으며 Circle a;라는 코드는 "Circle이라는 클래스에 a라는 객체를 생성한다."라는 뜻이다.
객체의 멤버에 접근을 하려고 할 시엔 a.radius = 1;처럼 "객체이름. 멤버"를 통해 객체의 멤버에 접근할 수 있다.
생성자
클래스는 객체가 생성될 때 자동으로 실행되는 생성자(constructor)라는 특별한 멤버 함수를 통해 객체를 초기화한다.
- 생성자의 목적은 객체가 생성될 때 필요한 초기 작업을 하기 위함이다.
- 생성자 함수는 오직 한 번만 실행된다.
- 생성자 함수의 이름은 클래스 이름과 동일하게 작성되어야 한다.
- 생성자 함수의 원형에 리턴 타입을 선언하지 않는다.
class Circle {
Circle(); // 매개변수 없는 생성자
Circle(int r); // 매개변수 있는 생성자
Circle(double r); // 매개변수 있는 생성자
};
Circle::Circle() {
...
}
Circle::Circle(int r) {
...
}
Circle::Circle(double r) {
...
}
생성자 함수는 함수 실행을 종료하기 위해 return문을 사용할 수 있다. 그러나 어떠한 값도 리턴하면 안 된다. 값을 리턴 시 컴파일 오류가 발생한다.
또한 아래의 코드와 같이
Circle(); // 매개변수 없는 생성자
Circle(int r); // 매개변수 있는 생성자
Circle(double r); // 매개변수 있는 생성자
생성자는 한 클래스에 여러 개를 만들 수 있으며, 매개 변수 개수나 타입이 서로 다르게 선언되어야 한다.
#include<iostream>
using namespace std;
class Circle {
public:
int radius;
Circle(); // 매개변수 없는 생성자
Circle(int r); // 매개변수 있는 생성자
Circle(double r); // 매개변수 있는 생성자
double getArea();
};
Circle::Circle() {
radius = 1; // 반지름 값 초기화
cout << "반지름이 " << radius << "인 원 생성" << endl;
}
Circle::Circle(int r) {
radius = r; // 반지름 값 초기화
cout << "반지름이 " << radius << "인 원 생성" << endl;
}
Circle::Circle(double r) {
radius = r; // 반지름 값 초기화
cout << "반지름이 " << radius << "인 원 생성" << endl;
}
double Circle::getArea(){
return 3.14 * radius * radius;
}
int main()
{
Circle a; // 매개변수 없는 생성자 호출
double area = a.getArea();
cout << "a 면적은 " << area << endl;
Circle b(30); // 매개변수 있는 생성자 호출
area = b.getArea();
cout << "b 면적은 " << area << endl;
Circle c(7.7); // 매개변수 있는 생성자 호출
area = c.getArea();
cout << "c 면적은 " << area << endl;
}
위의 코드는 매개변수가 없는 객체 a, int형 매개변수를 가진 객체 b, double형 매개변수를 가진 객체 c를 생성하여 생성자를 호출하고(객체가 생성될 때 자동적으로 실행된다.), 각 객체 내의 멤버 함수에서 반환되는 값을 변수 area에 저장하여 출력하는 코드이다.
위임 생성자
Circle::Circle() {
radius = 1; // 반지름 값 초기화
cout << "반지름이 " << radius << "인 원 생성" << endl;
}
Circle::Circle(int r) {
radius = r; // 반지름 값 초기화
cout << "반지름이 " << radius << "인 원 생성" << endl;
}
이러한 코드는 비슷한 코드가 중복으로 작성되어 비효율적으로 보인다. 따라서 위임 생성자라는 다른 생성자를 호출 가능한 생성자가 있는데 이 기능을 이용하면 아래와 같이 코드를 간소화할 수 있다.
Circle::Circle():Circle(1) {} // Circle(int r)의 생성자 호출
Circle::Circle(int r) {
radius = r; // 반지름 값 초기화
cout << "반지름이 " << radius << "인 원 생성" << endl;
}
Circle() 생성자가 호출되면 Circle() 생성자는 자신의 코드를 실행하기 전에 Circle(int r) 생성자를 호출하여, r에 1을 넘겨주어 radius를 1로 초기화하고 반지름과 원을 대신 출력하게 한다.
※ 생성자는 꼭 있어야 하는가?
생성자가 없는 클래스에 대해서 컴파일러는 기본 생성자를 자동으로 삽입한다. 이를 디폴트 생성자(default constructor)라고도 부르며, 매개변수가 없는 생성자이다.
소멸자
모든 생명체가 언젠가는 흙으로 돌아가는 것처럼 객체 또한 역시 언젠가는 소멸된다. 객체가 소멸되면 객체 메모리는 반환된다. 또한 객체 생성 시 생성자 함수가 실행되는 것처럼 객체 소멸 시 소멸자 함수가 반드시 실행된다. 소멸자(destructor)는 객체가 소멸되는 시점에서 자동으로 호출되는 클래스의 멤버 함수이다.
- 소멸자의 목적은 객체가 사라질 때 필요한 마무리 작업을 위함이다.
- 소멸자의 이름은 클래스 이름 앞에 ~을 붙인다.
- 소멸자는 리턴 타입이 없으며 어떤 값도 리턴해서는 안 된다.
- 소멸자는 오직 한 개만 존재하며 매개 변수를 가지지 않는다.
- 소멸자가 선언되어 있지 않으면 기본 소멸자(default destructor)가 자동으로 생성된다.
#include<iostream>
using namespace std;
class Circle {
public:
int radius;
Circle(); // 매개변수 없는 생성자
Circle(int r); // 매개변수 있는 생성자
~Circle(); // 소멸자 선언
double getArea();
};
Circle::Circle():Circle(1) {}
Circle::Circle(int r) {
radius = r; // 반지름 값 초기화
cout << "반지름이 " << radius << "인 원 생성" << endl;
}
Circle::~Circle() { // 소멸자 구현
cout << "반지름이 " << radius << "인 원 소멸" << endl;
}
double Circle::getArea(){
return 3.14 * radius * radius;
}
int main()
{
Circle a; // 매개변수 없는 생성자 호출
Circle b(30); // 매개변수 있는 생성자 호출
return 0;
}
main()의 스택에 a, b의 순서로 객체가 생성되며, return 0; 문이 실행되면 생성된 순서의 반대로 b객체의 소멸자와 a객체의 소멸자가 실행된다.
<지역 객체와 전역 객체의 생성 및 소멸 순서>
#include<iostream>
using namespace std;
class Circle {
public:
int radius;
Circle(); // 매개변수 없는 생성자
Circle(int r); // 매개변수 있는 생성자
~Circle(); // 소멸자 선언
double getArea();
};
Circle::Circle():Circle(1) {}
Circle::Circle(int r) {
radius = r; // 반지름 값 초기화
cout << "반지름이 " << radius << "인 원 생성" << endl;
}
Circle::~Circle() { // 소멸자 구현
cout << "반지름이 " << radius << "인 원 소멸" << endl;
}
double Circle::getArea(){
return 3.14 * radius * radius;
}
Circle global_a(1000);
Circle global_b(2000);
void f()
{
Circle f_a(100);
Circle f_b(200);
}
int main()
{
Circle main_a; // 매개변수 없는 생성자 호출
Circle main_b(30); // 매개변수 있는 생성자 호출
f();
}
멤버 접근 지정자(access specifier)
객체 지향 언어에서는 객체를 캡슐화하고, 외부에서 접근 가능한 공개 멤버와 외부의 접근을 허용하지 않는 비공개 멤버를 구분한다. 디폴트 접근 지정은 private이다.
● private 멤버 (비공개)
private 접근 지정으로 선언된 멤버로서, 클래스 내의 멤버 함수들에게만 접근이 허용된다.
● public 멤버 (공개)
public 접근 지정으로 선언된 멤버로서, 클래스 내외를 막론하고 프로그램의 모든 함수에게 접근이 허용된다.
● protected 멤버 (보호)
protected 접근 지정으로 선언된 멤버로서, 클래스 내의 멤버 함수와 이 클래스를 상속받은 파생 클래스의 멤버 함수에게만 접근이 허용된다.
class Circle {
int radius; // private(디폴트)
double getArea(); // private(디폴트)
public:
int radius1; // public
double getArea1(); // public
private:
int radius2; // private
double getArea2(); // private
protected:
int radius3; // protected
double getArea3(); // protected
};
위의 코드에서 아무런 접근 지정이 없는 radius와 getArea()는 디폴트인 private로 처리된다.
인라인 함수
함수 호출 시 실행을 마치고 돌아오는 과정에서 시간 소모가 발생한다. 만약 함수 내의 계산시간보다 함수를 호출하고 리턴하는 데 따른 시간 소모가 더 크다면 함수 호출의 오버헤드가 상대적으로 커서 프로그램 실행시간이 길어지는 원인이 된다. 이를 함수 호출 오버헤드(overhead)라고 한다.
#include<iostream>
using namespace std;
int odd(int x)
{
return (x % 2);
}
int main()
{
int sum = 0;
for (int i = 1; i < 10000; i++)
{
if (odd(i))
sum += i;
}
cout << sum << endl;
}
위와 같이 odd() 함수 코드의 x%2를 계산하는 시간보다 odd() 함수의 호출에 따른 오버헤드가 더 크며, 10000번의 함수 호출로 인해 엄청난 오버헤드 시간이 소모된다.
인라인 함수는 위와 같이 짧은 코드로 구성된 함수에 대해, 함수 호출 오버헤드로 인한 프로그램의 실행 속도 저하를 막기 위해 도입된 기능이다. 컴파일러는 인라인 함수를 호출하는 곳에 인라인 함수의 코드를 그대로 삽입하여 함수 호출이 일어나지 않게 한다. 이렇게 되면, 함수 호출 오버헤드가 없어지기 때문에 실행 속도가 빨라진다.
인라인 함수의 장점은 속도 향상이지만 호출하는 곳이 여러 군데 있다면 그만큼 파일 전체의 크기가 늘어나는 단점이 있다.
구조체 vs 클래스?
C++에서는 C언어와 호환성을 위해 구조체(struct)를 지원한다. C++구조체는 C 구조체에 기능을 확장하여 클래스와 동일한 구조와 기능을 가진다. C++구조체와 클래스는 기능적으로 동일하다. 멤버 변수뿐 아니라 생성자와 소멸자를 비롯한 멤버 함수를 가질 수 있으며, 다른 구조체나 클래스에게 상속 가능하고 다른 구조체나 클래스를 상속받을 수도 있다. 멤버들은 접근 지정자로 지정되며 멤버 활용 방법 또한 클래스와 완전히 동일하다. 한 가지 차이점은 클래스의 디폴트 접근 지정이 private인 반면 구조체는 디폴트 접근 지정이 public이다.
'🟦C++' 카테고리의 다른 글
[C/C++] 2차원 배열 정렬하는 방법 (vector) (1) | 2020.11.28 |
---|---|
[C/C++] STL sort 정렬 함수 (오름차순, 내림차순) 예제코드 (0) | 2020.11.22 |
[C/C++] endl 와 \n의 속도차이 (0) | 2020.06.06 |
[C/C++] 배열 & 포인터 예제 (0) | 2020.05.15 |
[C++] 명품 C++ Programming 2장 연습, 실습 문제풀이 (6) | 2020.05.14 |