Skip to content

Latest commit

 

History

History
158 lines (137 loc) · 7.1 KB

3-이것저것 복습하기.md

File metadata and controls

158 lines (137 loc) · 7.1 KB

셋째주, 이것저것 복습하기

첫째 날 - 함수 포인터와 함수 객체

우리는 C에서 함수 포인터를 아마 배웠을 거에요. 한번 복습해 보자면

int add(int a, int b)
{
  return a + b;
}

위와 같은 add 함수가 있을 때

int (*func)(int, int) = add;

func와 같은 함수 포인터를 만들 수 있습니다. 앞의 int는 반환 형, 괄호 안의 int, int는 인자를 나타내겠죠. 함수를 이렇게 변수처럼 저장할(가리킬) 수 있는 것은 함수명은 함수의 시작 지점에 대한 정보를 가지고 있기 때문입니다.

우리는 클래스를 배웠으니 클래스의 멤버 함수도 포인터로 만들어 볼 수 있을 것 입니다.

class something{
public:
  void func(int x, int y) {
    return x * y;
  }
};

당연한 말이지만 클래스의 멤버 함수기 때문에 void (*func)(int, int)와 같은 꼴로는 선언할 수 없습니다. 함수 포인터를 정의해 줄 때에는 함수의 원형과 같도록 선언해야 하는데 어떤 클래스의 멤버 함수인지에 해당하는 정보가 빠졌기 때문입니다. 제대로 작성하려면

void (something::*func)(int, int)

와 같이 선언해야 합니다.

함수 객체는 객체를 마치 함수처럼 호출해 사용하는 것인데, C++에서는 다양한 연산자들을 오버라이딩 해서 사요할 수 있습니다. 그 중에 () 연산자도 있어서, ()연산자에 오버라이딩을 사용하면 마치 함수처럼 사용할 수 있습니다.

class Func {
public:
  int add(int x) {
    sum += x;
  }
  int operator()(int a) {
    return sum * a;
  }
private:
  int sum = 0;
};
...
  Func func;
  func.add(10);
  func.add(20);
  int x = func(3);
...

당연하게도 x는 90이 되겠죠.

함수 객체는 여러 장점이 있습니다. 함수 형태가 같아도 객체 타입이 다르면 다른 타입으로 인식하고, 주소를 전달하는 방식의 함수 포인터는 인라인이 될 수 없지만 함수 객체는 인라인이 될 수 있고 컴파일러가 쉽게 최적화 가능합니다. 실제로 STL의 많은 구현은 이 함수 객체를 통해서 이루어 집니다.

이제 이 함수 객체도 귀찮다고 생각한 프로그래머들은 람다를 만들어 냅니다.(물론 꼭 그런 이유 때문은 아니지만) 람다는 함수 객체보다 훨씬 간편합니다. 람다에 대해서는 저번 주에 간단하게 설명했었는데 많이 써봐야 익숙해질 거에요.

둘째 날

오늘은 캡슐화와 정보은닉, 상속에 대해서 조금 배워봅시다.

캡슐화와 정보은닉

클래스는 공개되어야 할 것과 숨겨야 할 것을 나눠서 최소한의 정보만 공개하고 불필요한 side effect를 최소화할 수 있습니다. 우리가 사용했던 public 키워드와 private, protected 키워드로 멤버 변수, 함수의 접근을 제한할 수 있습니다. 아래의 클래스를 봅시다.

class Student {
public:
  int id;
private:
  int grade;
};
...
  Student a;
  cout << a.grade << endl; // << Error

Student클래스 외부에서 private으로 선언된 grade 변수에 접근할 수 없습니다. private으로 선언된 변수와 함수는 클래스 내부에서만 사용할 수 있고 외부에서는 접근이 불가능 하므로 들어낼 필요가 없거나 불필요한 변경을 막기 위해 사용할 수 있습니다.

캡슐화는 일단 함수와 변수를 묶는 것을 말합니다. 아래의 클래스를 보도록 합시다.

class Student {
private:
  int grade;
public:
  void setGrade(int value) {
    if (0 <= value && value <= 99)
      grade = value;
  }
  int getGrade() {
    return grade;
  }
}; 

grade는 private변수로 선언해 외부에서 바꿀 수 없지만 public으로 선언된 두 함수 setGrade getGrade로 접근하거나 값을 바꿀 수 있습니다. 특히 grade는 setGrade를 통해서 만 수정이 가능하니 여러 예외를 값을 변경하는 순간에 처리하고 줄일 수 있습니다.

C#같은 좀 더 최신의 언어에서는 getter, setter를 사용해 이러한 캡슐화를 좀 더 편하게 사용하고 있습니다.

상속

상속을 사용하면 기존의 클래스를 토대로 비슷한 클래스를 정의할 수 있습니다. 코드의 재 사용을 직접 느낄 수 있는 가장 좋은 방법이죠. 아래의 두 클래스, 사람과 사람을 상속받은 학생 클래스를 봅시다.

class Person {
private:
  string name;
  int age;
public:
  string getName() { return name; }
  int getAge() { return age; }
};
class Student : public Person {
private:
  int id;
  string major;
public:
  string getMajor() { return major; }
  int getId() { return id; }
};

Student는 Person의 public을 상속받기 때문에(class Student : public Person을 잘 보세요.) getName, getAge같은 매서드들을 호출할 수 있습니다.

   Student a;
   cout << a.getName() << endl;

위 코드와 같이 말이죠.

위 코드는 변수들이 초기화 되어있지 않기 때문에 적절한 초기 값을 넣고 실행해야 합니다.

물론 private Person으로 상속하여 name, age 변수에 접근할 수 도 있습니다.

상속은 흔히 is a 관계라 합니다. 즉 위의 예제에선 Student is a Person이라는 소리죠.

상속받은 자식은 부모 매서드를 그대로 가져올 수도 있지만 부모의 매서드를 재정의 할 수도 있습니다. 아래의 코드를 위의 예제의 Student - public:에 추가해 봅시다.

  string getName() {
    return Person::getName() + ": " + major;
  }

위 함수는 Person으로 부터 상속받은 getName함수를 덮어씁니다. Student클래스들은 getName() 함수를 호출하면 이름과 전공이 같이 나오게 됩니다.

오늘의 과제는 출석을 부르는 프로그램을 작성하는것 입니다. 아래의 명세를 따라주세요.

  • 사람은 이름과 나이를 가지고 있습니다.
    • 사람은 나이와 이름을 불려질 수 있습니다.
  • 유치원생은 사람이고, 유치원 이름과 반 이름을 가지고 있습니다.
    • 유치원생의 반은 ["햇님반", "꽃님반", "달님반"] 중 하나 입니다.
    • 유치원생의 이름을 부르면 이름 앞에 반 이름, 이름 뒤에 **"네네 선생님"**을 붙여서 말합니다.
  • 초등학생은 사람이고, 학교 이름과 학년, 반 번호를 가지고 있습니다.
    • 초등학생의 반은 6반 보다 작거나 같습니다.
    • 초등학생의 이름을 부르면 이름 앞에 학교 이름, 학년, , 이름 뒤에 **"입니다."**를 붙여서 말합니다.
  • 중학생은 사람이고, 학교 이름과 학년, 반 번호를 가지고 있습니다.
    • 중학생의 반은 8반 보다 작거나 같습니다.
    • 중학생의 이름을 부르면 이름 앞에 학교 이름, 이름 뒤에 "이다." 를 붙여서 말합니다.
  • 어떤 반에 유치원생 3명, 초등학생 5명 중학생 7명이 있습니다.
  • 이들에 대한 정보는 랜덤 합니다.
  • 이들 모두에 대한 이름을 한번씩 불러서 출력합니다.

셋째 날