Oh-My-C-P-P 02
개인적으로 공부한 C++ 내용을 공유합니다.
new와 delete
C언어에서 malloc
과 free
는 C++에서 new
와 delete
에 대응된다.
#include <stdlib.h>
#include <iostream>
// in C
int *a = (int *)malloc(sizeof(int) * 1);
free(a)
// in C++
int *a = new int;
delete a;
// in C++
int *a = new int[10];
delete[] a;
배열의 형태로 new
를 하게될 경우 delete[]
로 메모리 해제를 해야한다. 다음은 배열의 new
와 delete
예시 코드입니다.
#include <iostream>
int main() {
int arr_size;
std::cout << "array size : ";
std::cin >> arr_size;
int *list = new int[arr_size];
for (int i = 0; i < arr_size; i++) {
std::cin >> list[i];
}
for (int i = 0; i < arr_size; i++) {
std::cout << i << "th element of list : " << list[i] << std::endl;
}
delete[] list;
return 0;
}
*추가정보: C++은 C와 다르게 중간에 변수선언이 허용된다. 하지만 동일한 변수를 선언 하는 것은 허용되지 않는다. 중간에 허용된 변수도 변수가 들어있는 블록이 끝나면 사라진다.
객체지향언어 C++
typedef struct Animal {
char name[30]; // 이름
int age; // 나이
int health; // 체력
int food; // 배부른 정도
int clean; // 깨끗한 정도
} Animal;
typedef struct Bird {
char name[30]; // 이름
int age; // 나이
int health; // 체력
int food; // 배부른 정도
int clean; // 깨끗한 정도
// 여기까지는 Animal 과 동일하다.
int height; // 나는 고도
} Bird;
typedef struct Fish {
char name[30]; // 이름
int age; // 나이
int health; // 체력
int food; // 배부른 정도
int clean; // 깨끗한 정도
// 여기까지는 Animal 과 동일하다.
int deep; // 현재 깊이
} Fish;
위에 3가지 구조체가 있다. 각각의 형태는 비슷한데, Bird와 Fish의 경우 약간의 차이점이 있다. 바로 새로운 변수가 하나 더 필요한것이다. C에서는 이러한 변수 하나때문에 Animal이라는 통합된 타입을 사용하지 못하고 새로운 Bird와 Fish 타입을 새로 만들어줘야하고 만약 Bird와 Fish의 행동을 정의하려고 한다면 각각의 함수를 또 만들어줘야하는데, 함수 개수 또한 늘어나게 될 것이다. 그리고 행동을 정의하는 함수의 형태에 문제가 생긴다. 예를 들어 Bird 구조체가 사용하는 fly는 함수를 만들었다고 했을 때, 문법상 표현이 fly(Bird)이므로 “Bird가 fly한다.” 의 구조가 아니라 “fly가 Bird한다.” 구조가 되기 때문에 가독성에도 문제가 생긴다.
위와 같은 문제점들을 해결하고자 C++에서는 객체라는 개념을 만들었고 객체는 자신의 상태를 알려주는 변수(variable)과 자신이 하는 행동들(ex. play, sleep, fly등등)을 수행하는 함수(method)들로 이루어졌다.
그리고 객체가 현실 세계에서 존재하는 것들을 나타내기 위해서는 추상화(abstraction)라는 과정이 필요하다.
이러한 개념으로 C++에서는 객체를 만들때 객체의 변수(인스턴스 변수라고 한다.)와 함수(인스턴스 메소드라고 한다.)를 구성하는 것이 가능하도록 해준다. 또한 외부에서 직접 인스턴스 변수의 값을 바꿀 수 없고 항상 인스턴스 메소드를 통해서 간접적으로 조절하게 가능하는 캡슐화(Encapsulation)을 할 수 있다.
캡슐화를 하게되면 아래와 같이 접근을 제한시킨다.
Aniamal animal
// 초기화 과정 생략
animal.food += 100; // --> 불가능
animal.increase_food(100); // --> 가능
캡슐화는 왜 필요한가? 라고 누군가 질문을 했을 때 캡슐화에 대한 장점을 구체적으로 설명할 것이 많지만 간단히 말하면, “객체가 내부적으로 어떻게 동작하는지 몰라도 사용할 줄 알게 된다.”라고 말할 수 있다. 예컨대 animal.increase_food(100);
을 하면 내부적으로 food
변수 값이 100 증가하는 것 뿐만이 아니라 다른 인스턴스 변수들의 증감도 될 수 있을 것이다. 만약 increase_food
함수를 사용하지 않았다면 아래와 같이 여러가지 처리를 프로그래머가 직접 해주어야 된다.
animal.food += 100;
animal.weight += 10;
// ... 여러가지 처리
하지만 이것은 프로그래머가 food
를 100 늘리는 과정에서 정확히 어떠한 일들이 일어나는지 알아야지만 가능하다. 따라서 이는 상당히 피곤한 작업이 되고 더욱이 대형 프로젝트에서는 객체들을 한 사람이 설계하는 것이 아니기 때문에 다른 사람이 작성한 것을 읽고 완벽히 이해해야만 한다. 이렇기 때문에 캡슐화는 상당히 필요하고 중요하다.
클래스(Class)
C++ 상에서 객체를 만드는 장치, 즉 설계도는 바로 클래스(class) 이다.
다음과 같이 하나의 클래스를 만들어 볼 수 있다.
#include <iostream>
class Animal {
private:
int food;
int weight;
public:
void set_animal(int _food, int _weight) {
food = _food;
weight = _weight;
}
void increase_food(int inc) {
food += inc;
weight += (inc / 3);
}
void view_stat() {
std::cout << "이 동물의 food : " << food << std::endl;
std::cout << "이 동물의 weight : " << weight << std::endl;
}
}; // 세미콜론 필수 !!
int main() {
Animal animal;
animal.set_animal(100, 50);
animal.increase_food(30);
animal.view_stat();
return 0;
}
먼저 main
함수에서 Animal
클래스의 인스턴스를 어떻게 생성하였는지 살펴 보면, 기존의 구조체에서 구조체 변수를 생성할 때처럼 struct
와 같은 타입을 명시하지 않고 만든 객체의 이름 Animal
을 같이 선언해주면 된다. 이와 같이 Animal animal;
을 했으면 Animal
클래스의 인스턴스 animal
을 만들게 된 것이다.
class Animal {
private:
int food;
int weight;
public:
void set_animal(int _food, int _weight) {
food = _food;
weight = _weight;
}
void increase_food(int inc) {
food += inc;
weight += (inc / 3);
}
void view_stat() {
std::cout << "이 동물의 food : " << food << std::endl;
std::cout << "이 동물의 weight : " << weight << std::endl;
}
};
본격적으로 클래스가 어떻게 되어 있는지 살펴보면, 위는 Animal
이라는 클래스를 나타낸 것으로 Animal
클래스를 통해서 생성될 임의의 객체에 대한 설계도이다. 따라서 Animal
클래스를 통해 생성되는 객체는 food, weight
라는 변수를 갖고 있고 set_animal, increase_food, view_stat
이라는 함수들을 갖고 있다. Animal
클래스 상에서 이들을 지칭할 때 각각 멤버 변수(member variable)와 멤버 함수(member function) 라고 부른다.
즉, Animal animal
과 같이 생성된 객체에서는 하나의 인스턴스를 만든 것이므로 인스턴스 변수, 인스턴스 함수라고 부르고 클래스 상에서는 멤버 변수, 멤버 함수라고 부르는 것이다. 그렇기 때문에 멤버 변수와 멤버 함수는 실재 하는 것이 아니라 설계상에 내용이고 인스턴스가 만들어져야 비로소 세상에 나타나는 것이다.
private:
int food;
int weight;
먼저 멤버 변수들을 정의한 부분을 보면 처음 보는 키워드 private:
가 있을 것이다. 이러한 키워드를 접근 지시자
라고 한다. 외부에서 이러한 멤버들에 접근을 할 수 있는가 없는가를 지시해주는 키워드이다. private
키워드의 경우 아래에 쓰여진 것들은 모두 객체 내에서 보호되고 있다라는 의미이다. 다시말해
// in class
void set_animal(int _food, int _weight) {
food = _food;
weight = _weight;
}
위 와 같이 객체 안에서 food
와 weight
에 접근하는 것은 가능한 일이지만
// not in class
int main() {
Animal animal;
animal.food = 100;
}
이것처럼 객체 밖에서 인위적으로 food
에 접근하는 것은 불가능 하다는 것이다. 반면에 public
키워드의 경우, 외부에서 마음껏 이용할 수 있게 된다.
public:
void set_animal(int _food, int _weight) {
food = _food;
weight = _weight;
}
void increase_food(int inc) {
food += inc;
weight += (inc / 3);
}
void view_stat() {
std::cout << "이 동물의 food : " << food << std::endl;
std::cout << "이 동물의 weight : " << weight << std::endl;
}
위에 코드와 같이 class 내부를 정의하면 아래와 같이 사용할 수 있다.
animal.set_animal(100, 50);
animal.increase_food(30);
animal.view_stat();
*추가정보: private
나 public
과 같은 키워드를 명시하지 않았다면 기본 적으로 private
로 설정이 된다. 또한 객체 자신의 것은 위에 멤버 함수처럼 멤버 변수를 변수명 그대로 사용해서 접근할 수 있다.