7 분 소요

실습 5

문제 설명

문자열 s의 길이가 4 혹은 6이고, 숫자로만 구성돼있는지 확인해주는 함수, solution을 완성하세요. 예를 들어 s가 “a234”이면 False를 리턴하고 “1234”라면 True를 리턴하면 됩니다.

제한사항

s는 길이 1 이상, 길이 8 이하인 문자열입니다. s는 영문 알파벳 대소문자 또는 0부터 9까지 숫자로 이루어져 있습니다.

입출력 예

s return
“a234” false
“1234” true

내가 제출한 코드

#include <string>

using namespace std;

bool solution(string s)
{
    bool answer = true;
    if(s.size() != 4 && s.size() != 6)
        answer = false;

    for(int i = 0; i < s.size(); i++)
        if (s[i] < '0' || s[i] > '9')
            answer = false;

    return answer;
}

교수님 코드

sol 1. MyString 구현

#include <iostream>
#include <string>

using namespace std;

class MyString
{
private:
    void initPstr();

protected:
    char *pstr;

public:
    MyString();
    ~MyString();
    int length();
    void setString(const char *t);
    friend ostream &operator<<(ostream &a, const MyString &b);
};
class FiveString : public MyString
{
public:
    bool solve();
};
bool FiveString::solve()
{
    int len = length();
    if (len == 4 || len == 6)
    {
        for (int i = 0; i < 10 && pstr[i] != '\0'; i++)
        {
            if (pstr[i] < '0' || pstr[i] > '9')
            {
                return false;
            }
        }
    }
    else
    {
        return false;
    }
    return true;
}

MyString::MyString()
{
    pstr = NULL;
    initPstr();
}
MyString::~MyString()
{
    if (pstr != NULL)
        delete[] pstr;
}
void MyString::initPstr()
{
    pstr = new char[10];
}
int MyString::length()
{
    int i;
    for (i = 0; i < 10; i++)
    {
        if (pstr[i] == '\0')
            break;
    }
    return i;
}
// t를 입력받은 것을 pstr로 복사
void MyString::setString(const char *t)
{
    for (int i = 0; i < 10; i++)
    {
        pstr[i] = t[i];
        if (t[i] == '\0')
        {
            break;
        }
    }
}
ostream &operator<<(ostream &a, const MyString &b)
{
    a << b.pstr;
    return a;
}

int main()
{
    FiveString my;
    my.setString("1234");
    cout << my.length() << ":" << my << ":" << my.solve() << endl;
    return 0;
}

MyString 클래스를 통해서 처음 입력값을 받고자 한다. 여기서 MyString 클래스는 단순한 string을 정의할 수 있는 클래스임을 기억해야 된다.

  1. 가장 먼저 필요할 것 같은 멤버 함수는 string을 char *형으로 바꾸어 주는 함수가 필요할 것 같다. 근데 얘는 처음에 인스턴스를 생성할 때, 초기화할 수 있으므로 생성자를 통해서 구현할 수 있다. 여기서 초기화 함수는 private로 하고 싶지만 생성자는 main에서도 부를 수 있어야 하므로 public이므로 private에 initPstr() 함수를 따로 만든다.
  2. 디폴트로 소멸자도 만든다.
  3. 문자열의 길이만큼 반복문을 돌리면서 하나씩 확인을 해야될 것 같기 때문에 length() 라는 함수도 필요할 것 같다.
  4. string 형을 char * 형으로 바꾸기 위한 함수도 필요할 것 같다. setString()
  5. 출력을 할 때, « 함수를 통해서 클래스 이름만 적어도 멤버 변수를 출력할 수 있도록 만들고 싶다.

FiveString 클래스를 통해서 문제를 해결할 수 있는 클래스를 만든다. 여기서 pstr 변수가 필요하기 때문에 MyString을 상속받는다.

오퍼레이터 오버로딩

ostream &operator<<(ostream &a, const MyString &b)
{
    a << b.pstr;
    return a;
}

« 을 통해서 클래스 이름만 적어도 pstr을 출력하고 싶다. 근데 오퍼레이터 오버로딩을 안해놓으면 cout의 « 로 인식을 할 수가 없다. 따라서 내가 직접 재정의를 해주어야 된다. 리턴 데이터형은 ostream으로 한다. (out stream)

main

int main()
{
    FiveString my;
    my.setString("1234");
    cout << my.length() << ":" << my << ":" << my.solve() << endl;
    return 0;
}

sol 2. string 클래스 상속

위에서는 MyString 클래스를 통해서 문제를 풀기 위한 최소한의 string 클래스를 직접 구현했다. 하지만 string 클래스를 상속받게 된다면 위의 MyString 클래스를 정의하는 부분은 필요없게 된다.

#include <iostream>
#include <string>

using namespace std;

class FiveString : public string
{
public:
    bool solve();
    FiveString(const char *a);
    int length();
};
FiveString::FiveString(const char *a) : string(a)
{
    ;
}
int FiveString::length()
{
    return string::length() * 2;
}
bool FiveString::solve()
{
    int len = string::length(); // 3. 원래 string 클래스에 legnth가 있어
    if (len == 4 || len == 6)
    {

        for (int i = 0; i < 10 && (*this)[i] != '\0'; i++)
        {
            if ((*this)[i] < '0' || (*this)[i] > '9')
            {
                return false;
            }
        }
    }
    else
    {
        return false;
    }
    return true;
}
bool solution(string s)
{
    FiveString my(s.c_str()); // const char을 받는 것으로 FiveString 생성자를 정의했는데, 들어가는 것은 string 이므로 c_str을 사용해서 string으로 받은 값을 const의 char *로 바꾸어서 리턴해준다.
    return my.solve();
}

처음에 입력받은 string 형을 초기화 해야되는데 fivestring my = "!2345" 을 통해서 하게 된다면 오퍼레이터 = 에 대한 정의를 다시 해주어야 되고 귀찮으니까 오퍼레이터 오버로딩 대신 생성자를 통해서 FiveString의 멤버 변수를 초기화 시켜주었다.

FiveString::FiveString(const char *a) : string(a)
{
    ;
}

을 통해서 초기화를 했는데, 멤버 변수에 대한 초기화는 FiveString 클래스가 할 일이 아니라 나의 부모인 string 클래스가 처리해야될 일이다. parent 클래스를 명시적으로 호출할 수 있는 방법이 있다. 자식 생성자 뒤에 string(a)를 붙히게 되면 a에 대한 처리를 부모 클래스인 생성자가 해주세요. 라고 부탁을 하는 것이다.
자식 클래스를 만들게 되면 나만 만들어지는게 아니라 부모 클래스도 같이 만들어진다. 내가 부모꺼를 물려받아야되니까. 내 안에 부모 클래스가 있고, 그 바깥에 자식 클래스가 사알짝 쌓여있는 구조로 생각하면 된다. FiveString이라는 객체를 메인 함수에서 만드는 순간, FiveString만 만들어진 것 같지만 실제로 그 안에는 string 클래스도 만들어진다.

FiveString의 my 인스턴스가 만들어지면 FiveString에 대한 생성자가 호출이 되고, 이때, 부모 클래스의 생성자도 불리게 된다. 그런데 내가 명시적으로 부모의 여러 생성자 중에서 누구를 부를지를 명시하지 않으면 부모의 디폴트 클래스가 기본적으로 불려진다. 내가 원하는게 디폴트 생성자가 아니라면 명시저그로 부모 클래스를 선택할 수 있다.
부모 생성자를 선택할 수 있는 문법이 : 부모 클래스 이름(파라미터) 가 된다. 이렇게 된다면 원래 string이 char *를 받는 생성자가 있고, 그 생성자를 명시적으로 호출해준 것이다. 결국, FiveString의 생성자에서 명시적으로 string 클래스 생성자를 호출해서 멤버 변수를 초기화 하였으므로 solve 함수만 정의를 해주면 된다.

solve 함수 정의

bool FiveString::solve()
{
    int len = string::length();
    if (len == 4 || len == 6)
    {

        for (int i = 0; i < 10 && (*this)[i] != '\0'; i++)
        {
            if ((*this)[i] < '0' || (*this)[i] > '9')
            {
                return false;
            }
        }
    }
    else
    {
        return false;
    }
    return true;
}

string 클래스에 원래 length 함수가 정의되어 있다. 그냥 고~대로 사용!
기존 string 클래스에서는 [] 연산자에 대한 정의를 해놓았는데, 이 [] 연산자에 대한 것은 내 부모가 해 놓은 것을 가져다 쓰고 싶다. string 클래스의 [] 연산자는 포인터 변수에 대해서 정의되어 있지 않고, 값에 대해서 정의가 되어 있다. 그렇기 때문에 *를 사용해서 포인터 변수인 this의 값에 대해서 [] 연산자를 사용할 수 있도록 하였다.

cout 에 대한 오버로딩?!

cout << my.length() << ":" << my << ":" << my.solve() << endl;

이렇게 하게 되면 따로 오버로딩을 안했는데 출력이 잘 된다? 왜? string에 대해서는 « 연산자에 대해서 구현이 되어있기 때문이다. 내 안에는 진자 부모의 객체가 같이 있는제 실제 동작할 때에는 string 클래스처럼 취급을 해준다. 부모 클래스인 척을 할 수 있다. (그니까 부모 클래스의 모든 멤버 함수들이 다 상속이 되었으므로 다 사용을 할 수 있다.) 근데 반대의 경우는 안된다. 즉, 자식 클래스에서만 정의한 함수에 대해서 부모 클래스는 사용을 할 수 없다.

string 클래스를 상속받아서 사용함으로써 상속의 위대함을 느낄 수 있다. 왜? 우리는 string 클래스에 대한 소스코드가 없다. 근데 마음대로 string 클래스를 FiveString으로 확장을 했다. 오픈 소스가 없었을 옛날의 경우 기존에 만들어져있던 코드를 활용하여 확장을 할 수 있다는 것 자체가 굉장히 혁신(?) 이었고, 이런게 상속의 힘이다.

main

bool solution(string s)
{
    FiveString my(s.c_str());
    return my.solve();
}

Inheritance

부모 클래스에서 물려받지 않는 것

  1. 생성자와 소멸자는 물려받지 않는다.
  2. operator = 에 대한 멤버는 물려받지 않는다.
  3. friend 클래스는 물려받지 않는다.

부모 클래스와 자식 클래스 관계 확인하기

#include <iostream>
#include <string>

using namespace std;

class MyString
{
private:
    void initPstr();

protected:
    char *pstr;

public:
    MyString();
    ~MyString();
    int length();
    void setString(const char *t);
    friend ostream &operator<<(ostream &a, const MyString &b);
};

class FiveString : public MyString
{
public:
    bool solve();
    FiveString();
};
bool FiveString::solve()
{
    int len = length();
    if (len == 4 || len == 6)
    {
        for (int i = 0; i < 10 && pstr[i] != '\0'; i++)
        {
            if (pstr[i] < '0' || pstr[i] > '9')
            {
                return false;
            }
        }
    }
    else
    {
        return false;
    }
    return true;
}
FiveString::FiveString()
{
    cout << "FiveString::FiveString()" << endl;
}
MyString::MyString()
{
    cout << "MyString::MyString() " << endl;
    pstr = NULL;
    initPstr();
}
MyString::~MyString()
{
    if (pstr != NULL)
        delete[] pstr;
}
void MyString::initPstr()
{
    pstr = new char[10];
}
int MyString::length()
{
    int i;
    for (i = 0; i < 10; i++)
    {
        if (pstr[i] == '\0')
            break;
    }
    return i;
}
// t를 입력받은 것을 pstr로 복사
void MyString::setString(const char *t)
{
    for (int i = 0; i < 10; i++)
    {
        pstr[i] = t[i];
        if (t[i] == '\0')
        {
            break;
        }
    }
}
ostream &operator<<(ostream &a, const MyString &b)
{
    a << b.pstr;
    return a;
}

int main()
{
    FiveString my;
    my.~FiveString();
    return 0;
}

>>>
MyString::MyString()
FiveString::FiveString()

MyString 클래스를 상속받은 FiveString 클래스의 인스턴스를 생성했을 때, MyString 클래스의 생성자가 먼저 생성되고, 그 다음 FiveString 클래스의 생성자가 생성이 된다. 코드에는 FiveString 에 대한 인스턴스 밖에 없지만 부모 클래스를 먼저 호출한 다음 자식 클래스의 생성자를 호출하는 것을 확인할 수 있다.

또한 부모 클래스는 자식 클래스가 만들어질 때 같이 만들어졌다가 같이 사라진다. 가장 위에 있는 조상부터 차곡차곡 만들어지고 소멸될 때는 가장 자식 클래스부터 차곡차곡 없어진다.

댓글남기기