The Essentials of C++, David hunter

Chapter 8. Classes and Objects

8.1 Class Definition

  • Just as a structure definition encapsulates a set of related data items, a class definition encapsulates related data items and functions that manipulate them. A simple class definition for a “vehicle” data type is shown below:

  • 구조체 정의가 관련 데이터 항목을 캡슐화하는 것과 마찬가지로, 클래스 정의는 관련 데이터 항목과 그것들을 조작하는 함수를 캡슐화합니다. “차량” 데이터 유형에 대한 간단한 클래스 정의는 다음과 같이 나와 있습니다:

class Vehicle { // class 이름의 첫글자는 대문자로 쓰는 것이 관례이다.
public:
    int wheels;
    float weight;
    void initialize(float in_weight, int in_wheels = 4);
    float wheel_loading();
    void honk();
};
  • The elements defined within a class (wheels, weight, initialize, etc.) are referred to as “members” of the class.

  • 클래스 내에서 정의된 요소들 (wheels, weight, initialize 등)은 해당 클래스의 “멤버”로 불립니다.

  • Function class members are called “methods” to distinguish them from “free standing” functions. Typically, only the method prototypes appear within the class definition. Subsequent to the class definition, the methods would be elaborated as shown below.

  • 클래스 멤버 함수는 “메서드”라고 불리며, 이를 독립 함수와 구별하기 위해 사용됩니다. 일반적으로 클래스 정의 내에서는 메서드의 프로토타입만 나타납니다. 클래스 정의 이후에는 아래와 같이 메서드가 자세하게 정의됩니다.

void Vehicle::initialize(float in_weight, int in_wheels = 4)
{
    weight = in_weight;
    wheels = in_wheels;
}
  • The syntax vehicle::initialize specifies that the specific initialize method being implemented is the one included in the vehicle class, as opposed to some other initialze method, or an initialize function not associated with any class.

  • vehicle::initialize 문법은 “vehicle” 클래스에 속한 initialize 메서드를 구현한다는 것을 명시합니다. 다른 initialize 메서드나 어떤 다른 클래스와 관련된 것이 아니라는 것을 나타냅니다.

  • It is traditional to place a class definition in header file and the implementation of its methods in a corresponding source code file. Following this practice, the vehicle class definition would be present in “vehicle.h” and its implementation in “vehicle.cc”.

  • 전통적으로 클래스 정의는 헤더 파일에 위치하고 해당 메서드의 구현은 해당하는 소스 코드 파일에 놓는 것이 관례입니다. 이 관례를 따르면 “vehicle” 클래스 정의는 “vehicle.h”에 있고 해당 메서드의 구현은 “vehicle.cc”에 있을 것입니다.

  • It is possible to include a complete method definition within the bounds of a class definition, but doing so automatically marks the function as inline. Consequently, this should only be done for very small (several lines) functions.

  • 클래스 정의 내에 완전한 메서드 정의를 포함하는 것은 가능하지만, 이렇게 하면 해당 함수가 자동으로 인라인으로 표시됩니다. 따라서 이는 매우 작은 (몇 줄) 함수에 대해서만 수행해야 합니다.

  • The example below shows how the vehicle class would be used. The components of a class variable are selected. Class variables, such as s1 and s2 in the example, are called “objects” in C++. The process of selecting a method from a particular object is often described as sending a “message” to that object. Thus, the statement s2.honk() is interpreted as sending s2 the honk() message. Objects therefore receive messages and execute their methods appropriately.

  • 아래 예제는 Vehicle 클래스가 어떻게 사용되는지 보여줍니다. 클래스 변수의 구성 요소가 선택됩니다. C++에서는 s1 및 s2와 같은 클래스 변수를 “객체”라고 부릅니다. 특정 객체에서 메서드를 선택하는 과정은 종종 해당 객체에 “메시지”를 보내는 것으로 설명됩니다. 따라서 문장 s2.honk()는 s2에게 honk() 메시지를 보내는 것으로 해석됩니다. 따라서 객체는 메시지를 수신하고 메서드를 적절하게 실행합니다.

int main() {
    vehicle s1, s2;
    s1.initialize(2000);
    std::cout << s1.wheel_loading();
    s2.initialize(25, 2);
    s2.honk();
    // ...
    return 0;
}

8.2 Public and Protected Fields 공개 및 보호된 필드

  • The keyword public in a class definition specifies that all members of the class are accessible by functions and methods outside the class itself. It is more typical to divide a class definition into public and protected sections. Variables and methods defined within the protected section of a class are accessible only by the methods defined within that class (and its subclasses, as will be shown later). The creation of separate public and protected section is called “information hiding” and is a fundamental principle of object oriented programming.

  • 클래스 정의에서의 키워드 public은 클래스의 모든 멤버가 클래스 외부의 함수와 메서드에서 접근 가능하다는 것을 지정합니다. 일반적으로 클래스 정의를 공개(public) 및 보호(protected) 섹션으로 나누는 것이 더 일반적입니다. 클래스 내의 보호(protected) 섹션에 정의된 변수 및 메서드는 해당 클래스 내에서 정의된 메서드만 접근할 수 있습니다(그리고 나중에 보여줄 하위 클래스에서도 접근 가능합니다). 공개 및 보호 섹션을 분리하여 사용하는 것을 “정보 은닉”이라고 하며, 이것은 객체 지향 프로그래밍의 기본 원칙 중 하나입니다.

  • It is convenient to think of a class as defining a set of attributes and behaviors. Methods specify behaviors and variables (wheels, weight) specify attributes. Usually, the public section of a class is used to define methods only while the variables representing attributes are placed in the protected section of a class. Methods are usually provided to set and access class attributes. A modified vehicle class incorporating these principles is presented below. Since the number of wheels and base weight of a vehicle are presumably unchangeable values, only accessor methods are provided. Inline method definitions are used.

  • 클래스를 속성과 동작의 집합으로 정의하는 것이 편리합니다. 메서드는 동작을 지정하고 변수(예: wheels, weight)는 속성을 나타냅니다. 일반적으로 클래스의 public 섹션은 메서드를 정의하는 데 사용되며, 속성을 나타내는 변수(예: wheels, weight)는 클래스의 protected 섹션에 배치됩니다. 클래스 속성을 설정하고 액세스하기 위한 메서드를 일반적으로 제공합니다. 이러한 원칙을 적용한 수정된 vehicle 클래스는 아래에 제시되어 있습니다. 차량의 바퀴 수와 기본 무게는 변경할 수 없는 값으로 가정되므로 액세서 메서드만 제공됩니다. 인라인 메서드 정의가 사용됩니다.

class Vehicle {
public:
    void initialize(float in_weight, int in_wheels = 4);
    int GetWheels()
    {
        return wheels;
    };
    int GetWeight()
    {
        return weight;
    };
    float wheel_loading();
    void honk();
protected:
    int wheels;
    float weight;
};

8.3 Specialized Methods

8.3.1 Constructor Methods

  • Because initialization methods are so common, a class definition usually specifies a “constructor” method. Constructor methods are automatically called when an object is declared. They are given the name of the class itself and must not be given a return type specification. Thus, the initialize vehicle method can be replaced with a vehicle constructor method.

  • 초기화 메서드가 매우 일반적이기 때문에 클래스 정의는 일반적으로 “생성자” 메서드를 지정합니다. 생성자 메서드는 객체가 선언될 때 자동으로 호출됩니다. 이들은 클래스 자체의 이름을 가지며 반환 유형 명세를 가질 수 없습니다. 따라서 initialize vehicle 메서드를 vehicle 생성자 메서드로 대체할 수 있습니다.

class Vehicle {
public:
    Vehicle(float in_weight, int in_wheels = 4)
    {
        weight = in_weight;
        wheels = in_wheels;
    }
    // ... rest as before ...
};
  • Given this definition, statements such as vehicle v1, v2(2000), v3(25, 2) could be used to create vehicle objects.

  • 이 정의를 기반으로 vehicle v1, v2(2000), v3(25, 2)와 같은 문장을 사용하여 vehicle 객체를 생성할 수 있습니다.

  • A more concise syntax for constructor methods allows the parameter list of a constructor method to be followed by one or more initialization expressions of the form variable(value). The syntax is illustrated below.

  • 생성자 메서드의 더 간결한 구문을 사용하면 생성자 메서드의 매개변수 목록 다음에 variable(value) 형식의 하나 이상의 초기화 표현식을 사용할 수 있습니다. 다음과 같은 문법을 예로 들 수 있습니다.

class Vehicle {
    public:
        Vehicle(float in_weight, int in_wheels = 4) // 생성자 정의 문법 내에서 초기화
        { 
            weight = in_weight;
            wheels = in_wheels;
        }
        // ... rest as before ...
};

추가 해설
이 스타일은 생성자 정의 내에서 변수 초기화를 별도로 수행합니다.
생성자 본문 내에서 변수에 직접 값을 할당하는 방식입니다.
변수 초기화가 생성자 본문에서 이루어지므로 초기화 목록이 사용되지 않습니다.

  • 초기화 목록의 사용 예
class vehicle {
    public:
        // Vehicle(float in_weight, int in_wheels = 4) : { wheels(in_wheels), weight(in_weight) }; // 원문, 오류
        Vehicle(float in_weight, int in_wheels = 4) : weight(in_weight), wheels(in_wheels) {}
        // ...rest as before ...
};

추가 해설
이 스타일은 생성자 초기화 목록을 사용하여 변수를 초기화합니다.
초기화 목록을 사용하면 생성자 본문 내에서 변수 초기화를 직접 처리하지 않고, 생성자 초기화 목록을 통해 초기화를 수행합니다.
초기화 목록을 사용하면 코드를 더 간결하게 작성할 수 있으며, 효율적인 초기화를 보장합니다.
초기화 목록을 사용하면 코드가 더 간결해지고 명확해질 수 있으므로 일반적으로 권장되는 방식입니다.

  • Since C++ allows for function overloading, classes may provide several constructor methods. In fact, it is recommended that all classes may provide a constructor method that takes no parameters. This is referred to as a “default” constructor.

  • C++에서는 함수 오버로딩이 허용되므로 클래스는 여러 개의 생성자 메서드를 제공할 수 있습니다. 사실, 모든 클래스가 매개변수 없이 호출될 수 있는 생성자 메서드를 제공하는 것이 권장됩니다. 이를 “기본” 생성자라고 합니다.

class vehicle {
    public:
        // vehicle(float in_weight, int in_wheels = 4) : wheels(in_wheels), weight(in_weight) {; // 원문, 오류
        vehicle(float in_weight, int in_wheels = 4) : wheels(in_wheels), weight(in_weight) {}
        vehicle(): wheels(0), weight(0) {}; // 기본생성자: 객체를 생성할 때, 멤버 변수를 0으로 초기화
        // ... rest as before ...
};

8.3.2 Destructor Methods

  • Some data types may require action to be taken when an object of that type is deallocated. These actions can be defined in a “destructor” method. Destructor methods are identified by the class name preceded by the “~” mark. For example, the destructor method for the vehicle class would be called ~vehicle. Destructor methods are needed for classes that contain dynamically allocated data structures such as linked lists or dynamic arrays but are not truly necessary for other classes. Like constructors, destructor methods should not be given a return type specification.

  • 일부 데이터 유형은 해당 유형의 객체가 해제될 때 조치가 필요할 수 있습니다. 이러한 조치는 “소멸자” 메서드에서 정의할 수 있습니다. 소멸자 메서드는 클래스 이름 앞에 “~” 기호가 붙어 식별됩니다. 예를 들어, vehicle 클래스의 소멸자 메서드는 ~vehicle로 호출됩니다. 소멸자 메서드는 동적으로 할당된 데이터 구조를 포함하는 클래스에 필요하지만 다른 클래스에는 별로 필요하지 않습니다. 생성자와 마찬가지로 소멸자 메서드는 반환 유형 지정을 받아서는 안 됩니다.

8.3.3 Copy Methods

  • Along with constructors and destructors, it is also common to associate copy methods with class types. (These are called “default copy constructors” by some authors.) Copy method prototypes have the form type (const& type) and specify how to construct a copy of an object. Copy methods are automatically called when, for example, an object is passed by value into a function. It is particularly important to specify copy constructors for object that contain dynamically allocated data structures. Return types should not be specified for copy constructors.

  • 생성자와 소멸자와 함께, 클래스 유형과 연관된 복사 메서드를 연결하는 것도 일반적입니다. (일부 저자들은 이러한 것을 “기본 복사 생성자”라고 부릅니다.) 복사 메서드의 원형은 type(const& type) 형식을 가지며 객체의 복사본을 어떻게 생성할지를 지정합니다. 예를 들어, 객체가 함수로 값으로 전달될 때 복사 메서드가 자동으로 호출됩니다. 동적으로 할당된 데이터 구조를 포함하는 객체에 대한 복사 생성자를 명시하는 것은 특히 중요합니다. 복사 생성자에 대한 반환 유형은 지정해서는 안 됩니다.

추가 설명
복사 생성자를 쓰면, 기존 객체의 내용을 새로운 객체에 복사하여 두 객체가 동일한 데이터를 가진다. 메모리 주소 값이 서로 다르고 값이 같음.
클래스가 동적으로 할당된 데이터 구조일 때, 객체 복사 시 동적 데이터도 적절하게 복사되어 메모리 누수나 예기치 않은 동작을 방지할 수 있다.
메모리 누수? 같은 메모리를 가리키고 있어서 해제를 못하기 때문에 누수라고 표현한 것.

type(const type& other) // type: 클래스 이름, other: 복사할 객체
String(const String& other)

추가 예시

#include <iostream>
#include <cstring>

class String {
public:
    // 생성자
    String(const char* str) {
        length = strlen(str);
        data = new char[length + 1];
        strcpy(data, str);
    }

    // 복사 생성자
    String(const String& other) {
        length = other.length;
        data = new char[length + 1];
        strcpy(data, other.data);
    }

    // 소멸자
    ~String() {
        delete[] data;
    }

    // 문자열 출력
    void print() const {
        std::cout << data << std::endl;
    }

private:
    char* data;
    size_t length;
};

int main() {
    String original("Hello, World!");
    String copy = original; // 복사 생성자 호출
    original.print();
    copy.print();

    return 0;
}

8.3.4 Operator Methods

추가 해설
클래스에서 연산자의 동작을 정의하기 위해 사용되는 특별한 메서드입니다. C++에서 연산자는 클래스 객체 간의 연산을 지원하는 데 사용됩니다. 예를 들어, + 연산자는 두 숫자를 더하는 데 사용되지만, 연산자 오버로딩을 통해 사용자 정의 클래스에서도 동일한 연산을 수행할 수 있습니다.

연산자 메서드는 클래스에서 특정 연산자를 정의하고 해당 연산을 수행하는 방법을 제공합니다. 이를 통해 사용자 정의 클래스에서 기본 연산자를 확장하고 객체 간의 사용자 지정 연산을 수행할 수 있습니다.

예를 들어, 아래와 같이 할당 연산자(=)를 오버로딩하여 사용자 정의 클래스에서 할당 동작을 정의할 수 있습니다.

class Complex {
public:
    double real;
    double imag;

    // Constructor
    Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) {}

    // Overloading the + operator (+)
    Complex operator+(const Complex& other) const {
        return Complex(real + other.real, imag + other.imag);
    }
};

int main() {
    Complex a(2.0, 3.0);
    Complex b(1.0, 2.0);
    Complex c = a + b; // This will call the overloaded + operator
    return 0;
}


위의 코드에서, Complex 클래스에서 + 연산자를 오버로딩하였습니다. 이로써 Complex 클래스 객체 간의 덧셈 연산을 사용자 정의한 방식으로 수행할 수 있습니다. Complex 클래스의 operator+ 메서드는 두 개의 Complex 객체를 받아서 더한 결과를 새로운 Complex 객체로 반환합니다.

  • Many classes will want to provide operator methods. For example, a matrix or complex number class would presumably overload the arithmetic operators. It is customary for all classes, however, to overload the assignment operator. When defined as a method for a given class, the assignment operator has a single parameter which is a reference to an object of that class, copies that object, then returns a reference to the object being assigned to. The keyword this is used within a method definition as a pointer to the object receiving the message (the “host” object). The general form of an assignment operator method is therefore

  • 많은 클래스는 연산자 메서드를 제공하려고 할 것입니다. 예를 들어, 행렬이나 복소수 클래스는 산술 연산자를 오버로드하게 될 것입니다. 그러나 모든 클래스에서 할당 연산자(=)를 오버로드하는 것이 관례입니다. 클래스 내에서 메서드로 정의될 때 할당 연산자는 해당 클래스의 객체에 대한 참조를 가지며, 그 객체를 복사한 다음 할당되는 객체에 대한 참조를 반환합니다. 메서드 정의 내에서 키워드 “this”는 메시지를 받는 객체(호스트 객체)를 가리키는 포인터로 사용됩니다. 따라서 할당 연산자 메서드의 일반적인 형식은 다음과 같습니다.

class_type& operator=(const class_type& source)
{
    // ... copy source into host object ...
    return *this // return a reference to the object itself (not a pointer to the object)
}

추가 해설
할당 연산자 =는, 하나의 매개변수를 가지며, 객체에 대한 참조이다.
동작은 전달된 객체를 복사, 할당되는 객체에 대한 참조를 반환한다.

추가 해설
This
[C/C++ 강좌] 55강. This 포인터 Start 3:30

  • The implementation of an assignment operator method is given in the example on the next page, where the complete definition of the vehicle class is presented.

  • 할당 연산자 메서드의 구현은 다음 페이지에 있는 예제에서 제공됩니다. 거기에서는 완전한 vehicle 클래스 정의가 제시됩니다.

class vehicle {
    public:
    // Constructor method
    vehicle(float in_weight, int in_wheels = 4) : wheels(in_wheels), weight(in_weight) {};
    vehicle(): wheels(0), weight(0) {};
    // Copy method
    vehicle(const vehicle& v)
    { wheels = v.wheels; weight = v.weight; };

    // Operator definitions
    vehicle& operator= (const vehicle&); // need some code

    // Accessor methods
    int GetWheels() { return wheels; };
    int GetWeight() { return weight; };

    // Other methods
    float wheel_loading()
    { return (weight / wheels); };
    void honk() {cout << "Beep Beep!\n"; };

    // Destructor method (no action required because no dynamic memory)
    ~vehicle() {};
    protected:
        int wheels;
        float weight;
};

8.4 Friend Classes 친구 클래스

  • Under normal circumstances, the protected members of a class cannot be accessed outside of that class. One way to provide an exception to this is through the friend mechanism. When function or class is defined to be a friend of another class it has access to all of that class’s members. Friendly functions are specified in a class definition by stating freiend prototype and friendly classes by stating friend class_name anywhere within the class definition. It is usual, however, to place friend declarations at the very top of a class definition. The technique of declaring a friendly function is illustrated below.

  • 보통 상황에서 클래스의 보호된 멤버는 그 클래스 외부에서 접근할 수 없습니다. 이를 예외로 처리하는 방법 중 하나는 친구(friend) 메커니즘을 사용하는 것입니다. 함수나 클래스가 다른 클래스의 친구로 정의되면 해당 클래스의 모든 멤버에 접근할 수 있습니다. 친구 함수는 클래스 정의 내에서 “friend 프로토타입”을 명시하여 지정되며, 친구 클래스는 클래스 정의 내에서 “friend class_name”을 명시하여 지정됩니다. 하지만 보통 클래스 정의의 맨 위에 친구 선언을 배치하는 것이 일반적입니다. 친구 함수를 선언하는 기술은 아래에 설명되어 있습니다.

class vehicle {
    friend void reset(vehicle, int, int); // 원문
    // friend void reset(vehicle&, int, int); // 추천. 참조를 전달하는 게 객체를 복사하지 않아도 되어 메모리 및 시간이 감소된다. 객체 변경이 가능하다.
public:
// .. reset as before ...
};

void reset(vehicle v, int in_wheels, int in_weight) // 원문
// void reset(vehicle& v, int in_wheels, int in_weight) // 추천
{
    // note access to protected members of vehicle
    v.wheels = in_wheels;
    v.weight = in_weight;
}
  • The friend mechanism violates the usual class access mechanism and so should be used sparingly. The example provided is not a particularly good use of the mechanism and is provided for pedagogical purposes only.

  • 친구 메커니즘은 일반적인 클래스 접근 메커니즘을 어기므로 조심스럽게 사용해야 합니다. 제공된 예제는 이 메커니즘을 사용하는 좋은 사례가 아니며 교육 목적으로만 제공됩니다.

8.5 A Linked List Class 연결 리스트 클래스

  • To tie all the various class definition features together into a single example, a program is presented in which a linked list of characters is defined. Note that pointers are used with objects in the same manner as structures.

  • 여러 클래스 정의 기능을 하나의 예제로 연결하여 설명하겠습니다. 문자열(linked list)의 연결 리스트(linked list)를 정의하는 프로그램을 제시합니다. 이 프로그램에서는 포인터가 구조체와 같은 방식으로 객체와 함께 사용되는 것을 주목하세요.


#include <iostream>
using namespace std; 

// class ListCell; // 원문
class List; // forward declaration

class ListCell {
// ListCell: 
// - Linked list의 노드. 
// - 데이터 contents, 다음 노드 포인터 Next. 
// - 생성자, 복사 생성자

    friend List; // List의 private 접근 허용
    public:
        // 클래스의 생성자
        ListCell(char c, ListCell *p = NULL) : contents(c), Next(p) {}; 
        // 매개변수 char c, ListCell *p 포인터, 초기값 NULL
        // 초기화 목록 contents(c), Next(p) - 멤버변수 contents, Next를 c, p로 초기화한다.

        // 클래스의 기본 생성자 (default contructor)
        ListCell() : contents('\0'), Next(NULL) {};
        // 매개 변수 없음
        // 초기화 목록 '\0' 문자열 끝을 나타내는 Null 문자, 빈 문자열로 초기화하라는 뜻.
        // NULL 포인터를 NULL로 초기화하라는 뜻 

        // 클래스의 복사 생성자 (copy constructor)
        ListCell(const ListCell& lc)
        // lc: 복사하려는 다른 ListCell 객체 참조
        // 현재의 contents, Next가 lc.contents, lc.Next를 참조해서 복사를 수행한다.
        {
            contents = lc.contents;
            Next = lc.Next;
        }

    // 클래스 내의 할당연산자(=) 정의
    ListCell& operator= (const ListCell& lc)
    // 인자로 다른 ListCell을 받음. 읽기 전용.
    { 
        contents = lc.contents; // 실제 할당 연산
        Next = lc.Next;
        return *this;
        // 현재 객체인 ListCell 객체의 참조를 반환. > 이렇게하면, 아래와 같이 할당 연산자를 연속적으로 사용할 수 있다. 연쇄 할당
        // ListCell cell1; ListCell cell2; ListCell; cell1 = cell2 = cell3; 
    }

        // 클래스의 멤버 함수들
        char GetContents() { return contents; } // contents 반환
        ListCell* GetNext() { return Next; } // Next 반환
        
        // 클래스의 소멸자
        ~ListCell() {};

    // 클래스의 멤버 변수들
    protected:
        char contents;
        ListCell* Next;
};

class List {
// List:
// Linked List의 제어담당

    public:
        // 클래스의 생성자
        List() { First = NULL; }
        // 매개 변수 없음.
        // 멤버 변수 First를 Null로 초기화
        // 초기화 목록(: )은 사용하지 않음
        
        // 클래스의 복사생성자의 prototype. 실제 코드는 클래스 외부에 기술되어 있다.
        List (const List&);
        // 매개 변수 List&, 읽기 전용

        // 클래스의 할당연산자(=) 정의
        List& operator= (List& l);
        // 객체의 멤버 변수 등을 복사하여 값을 복사
        // 매개 변수 List& l, 우변의 List 객체를 받아들임
        // List 객체를 할당 연산의 대상인 *this 객체에 복사하는 역할
        // *this 객체의 참조를 반환하여 연속적인 할당 연산을 가능하게 함. 연쇄할당

        // 멤버 함수 Add
        // 목적: 새로운 문자 char c를 리스트에 추가 함.
        // 주요역할: First 멤버 변수를 업데이트하여 새로운 요소를 리스트의 맨 앞에 추가.
        void add(char c)
        {
            First = new ListCell(c, First);
        }

        // 클래스의 멤버 함수 remove의 prototype. 실제 코드는 클래스 외부에 기술되어 있다. 
        void remove(char);
        // 매개변수 char. 어떤 매개변수를 쓰는 지 이름도 같이 명시하는 것이 더 좋다.
        // 목적: 리스트에서 해당 문자열을 제거
        // 리스트의 첫 번째 노드부터 시작해서 주어진 문자열과 동일한 내용을 가진 노드를 찾고 해당 노드를 제거하는 작업을 수행

        // 클래스의 멤버 함수 IsIn의 prototype. 실제 코드는 클래스 외부에 기술되어 있다.
        int IsIn(char);
        // 매개변수 char. 어떤 매개변수를 쓰는 지 이름도 같이 명시하는 것이 더 좋다.
        // 목적: 리스트 안에 주어진 문자열이 존재하는 지 확인
        // 리스트의 첫 번째 노드부터 시작해서 주어진 문자열과 동일한 내용을 가진 노드를 찾고, 찾으면 1 아니면 0을 반환

        // 클래스의 소멸자
        ~List();

    // 클래스의 멤버 변수
    protected:
        ListCell* First;
};

// 클래스의 복사생성자를 외부에서 정의
List::List(const List& l)
{
    if (l.First == NULL)
        First = NULL;
    else
    {
        First = new ListCell(l.First->contents, NULL);
        ListCell *p1 = l.First->Next;
        ListCell *p2 = First;
        while (p1 != NULL)
        {
            p2->Next = new ListCell(p1->contents, NULL); 
            p2 = p2->Next;
            p1 = p1->Next;
        }
    }
}

// 클래스의 할당연산자(=)를 외부에서 정의
List& List::operator = (List& l)
{
    if (l.First == NULL) 
    { 
        return *this;
    }
    First = new ListCell(l.First->contents, NULL);
    ListCell *p1 = l.First->Next;
    ListCell *p2 = First;
    while (p1 != NULL)
    {
        p2->Next = new ListCell(p1->contents, NULL);
        p2 = p2->Next;
        p1 = p1->Next;
    }
    return *this;
}

// 클래스의 소멸자를 클래스 외부에서 정의함.
List::~List()
{
    ListCell *p = First, *next;
    while (p != NULL)
    {
        next = p->Next;
        delete p;
        p = next;
    }
}

// 클래스의 멤버 함수 InIn을 클래스 외부에서 정의함
int List::IsIn(char c)
{
    ListCell* p = First;
    while (p != NULL)
    {
        if (p->contents == c) break;
        p = p->Next;
    }
    return p != NULL;
}

// 클래스의 멤버 함수 remove을 클래스 외부에서 정의함
void List::remove(char c)
{
    ListCell *p1 = First, *p2 = NULL;
    while (p1->contents !=c)
    {
        p2 = p1;
        p1 = p1->Next;
    }
    p2->Next = p1->Next;
    delete p1;
}


// Main
int main()
{
    List l1, l2;
    l1.add('a');
    l1.add('b');
    l1.add('c');
    l2 = l1;
    l1.remove('b');
    cout << l1.IsIn('b') << l2.IsIn('c') << endl;
    return 0;
}

8.6 Class Nesting

추가 해설
클래스 중첩은 하나의 클래스가 다른 클래스 내부에 정의되는 것을 의미합니다. 이것은 클래스의 범위와 액세스 규칙에 영향을 미치는데, 클래스가 중첩되면 해당 클래스의 멤버에 대한 범위가 영향을 받지만 멤버 액세스 규칙은 변경되지 않습니다.

ListCell 클래스를 List 클래스의 protected 섹션 내에 캡슐화해서 정보은닉을 강화할 수 있습니다.

클래스 중첩은, 클래스의 범위에만 영향을 미치며, 클래스 멤버 액세스 규칙을 변경하지 않습니다. 따라서 ListCell 클래스에 List의 보호 멤버에 대한 액세스를 제공하기 위해 class List를 friend로 선언해야 합니다.

중첩 클래스의 멤버가 인라인으로 구현되지 않은 경우, 메서드를 정의할 때 스코프 해결 연산자(‘::’)를 두 번 사용해야 합니다.

형식 식별자도 클래스 범위 내에서 정의할 수 있으며, 중첩 클래스와 마찬가지로 공개 또는 보호된 액세스를 제공할 수 있습니다. 예를 들어, 클래스 내에서 공개로 액세스할 수 있는 열거형을 정의할 수 있습니다. 클래스 외부에서 이 열거형 상수에 액세스하려면 클래스 이름을 함께 사용하여 한정해야 합니다. 이것은 클래스의 공개 섹션에서 typedef 문으로 생성된 식별자에도 적용됩니다.

  • The class ListCell in the previous example exists solely to aid in the implementation of the class List. It is meaningless outside of List, and programmers have no need to use it since the List class performs all the behaviors necessary for construction and manipulation of lists. Consequently, to enhance information hiding one might want to “hide” the entire ListCell class by enclosing it within the protected section of the List class. The syntax is outlined below.

  • 이전 예제에서의 ListCell 클래스는 List 클래스의 구현을 돕기 위한 것으로, List 외부에서는 의미가 없으며, 프로그래머들은 List 클래스가 목록의 구성 및 조작에 필요한 모든 작업을 수행하기 때문에 사용할 필요가 없습니다. 따라서 정보 은닉을 강화하기 위해 ListCell 클래스 전체를 List 클래스의 protected 섹션 내에 캡슐화하려는 경우가 있을 수 있습니다. 아래에 구문이 개요로 나와 있습니다.

class List {
public:
// ... exactly as before ...
protected:
    // class ListCell; // 원문. 두 번 선언하여 오류
    class ListCell { 
        friend List;
    public:
    // ... exactly as before ...
    protected:
    // ... exactly as before ...
    };

    ListCell* First;
}
  • Because class ListCell is defined within the protected section of List, it is not accessible outside of List, but the List class may use it as necessary.

  • ListCell 클래스가 List 내의 protected 섹션 내에서 정의되었기 때문에 List 외부에서 접근할 수 없지만, List 클래스 내에서 필요한 대로 사용할 수 있습니다.

  • Classes may also be defined within the public section of another class. In these cases, the inner class is publicly accessible, but references to it must be qualified with the scope resolution operator. That is, if class inner is defined within the public section of class outer, inner can only be referenced by using the notation outer::inner.

  • 클래스는 다른 클래스의 public 섹션 내에서도 정의될 수 있습니다. 이러한 경우 내부 클래스는 공개적으로 접근 가능하지만 해당 클래스에 대한 참조는 범위 지정 연산자로 한정되어야 합니다. 즉, 클래스 inner가 클래스 outer의 public 섹션 내에서 정의되었을 때, inner는 outer::inner 표기법을 사용하여만 참조할 수 있습니다.

  • Class nesting merely affects the scope of a class. it does not in any way modify the class member access rules. Therefore, ListCell must still declare class List as a friend in order to provide List access to its protected members.

  • 클래스 중첩은 클래스의 범위에만 영향을 미치며, 클래스 멤버 액세스 규칙을 어떠한 방식으로든 수정하지 않습니다. 따라서 ListCell은 여전히 List의 보호 멤버에 대한 액세스를 제공하기 위해 class List를 friend로 선언해야 합니다.

  • If members of a nested class are not implemented inline, the scope resolution operator must be used twice when the method is defined.

  • 중첩 클래스의 멤버가 인라인으로 구현되지 않은 경우, 메서드를 정의할 때 스코프 해결 연산자(::)를 두 번 사용해야 합니다.

class outer {
    // ...
    class inner {
        int a_method();
        // ...
    }
    // ...
};
int outer::inner::a_method()
{
    // ...
}
  • Type identifiers can also be defined within the scope of a class and either public or protected access can be provided, just as with nested classes. For example, the class below contains a publicly accessible enumerated type definition. External to the class, access to the enumerated constants must be qualified by the class name in which they are defined. The same would be true for any identifiers created by typedef statements within the public section of a class.

  • 형식 식별자도 클래스의 범위 내에서 정의할 수 있으며 중첩 클래스와 마찬가지로 공개 또는 보호된 액세스를 제공할 수 있습니다. 예를 들어, 아래 클래스에는 공개로 액세스할 수 있는 열거 형 정의가 포함되어 있습니다. 클래스 외부에서 열거형 상수에 액세스하려면 해당 상수가 정의된 클래스 이름으로 한정해야 합니다. 동일한 원칙이 클래스의 공개 섹션에서 typedef 문으로 생성된 식별자에도 적용됩니다.

class code_table {
public:
    enum instruction { add, sub, mult, div, mov, cmp, jmp };
    // ...
    int lookup(instruction i);
    // ...
};

int main()
{
    // int i = lookup(code_table::mult); // 원문
    code_table myTable; // 원문에는 없음. 수정
    int i = myTable.lookup(code_table::mult); // 수정
    // ...
    return 0;
}

8.7 Iterators

추가 해설
클래스(class)의 iterator(반복자)는 컨테이너 클래스 내부의 요소들을 반복적으로 접근하고 조작하는 데 사용되는 개체입니다. C++의 표준 라이브러리(STL)에서는 많은 컨테이너 클래스들에 대한 반복자가 제공되며, 사용자 정의 클래스에서도 반복자를 구현할 수 있습니다.

반복자란? 반복자는 컨테이너 내의 요소에 순차적으로 접근하거나 수정하기 위한 인터페이스를 제공하는 객체입니다. 반복자는 포인터와 유사한 역할을 하며, 컨테이너 내의 요소에 접근하고 조작할 수 있는 메서드를 제공합니다.

STL에서의 반복자 C++ 표준 라이브러리(STL)에서는 다양한 컨테이너(예: 벡터, 리스트, 맵)에 대한 반복자를 제공합니다. 이러한 반복자는 컨테이너의 요소에 접근하는 데 사용됩니다. 예를 들어, 벡터의 반복자를 사용하여 벡터 내의 요소를 순회하거나 수정할 수 있습니다.

사용자 정의 클래스에서의 반복자 사용자 정의 클래스에 대해서도 반복자를 구현할 수 있습니다. 이 경우, 클래스 내에서 반복자를 정의하고 해당 클래스의 요소에 접근하는 방법을 정의해야 합니다. 이렇게 하면 클래스 객체를 반복적으로 순회하거나 수정할 수 있게 됩니다.

반복자의 종류
- begin(): 컨테이너의 첫 번째 요소를 가리키는 반복자를 반환합니다.
- end(): 컨테이너의 끝을 나타내는 반복자를 반환합니다.
- rbegin(): 역순으로 순회하기 위한 반복자를 반환합니다.
- rend(): 역순으로 순회하기 위한 끝을 나타내는 반복자를 반환합니다.

반복자 사용 예시

#include <iostream>
#include <vector>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};

    // 벡터의 시작부터 끝까지 반복자를 사용하여 출력
    for (std::vector<int>::iterator it = numbers.begin(); it != numbers.end(); ++it) {
        std::cout << *it << " ";
    }

    return 0;
}

std::vector의 멤버함수들

// push_back()
std::vector<int> numbers;
numbers.push_back(1);
numbers.push_back(2);

// pop_back()
std::vector<int> numbers = {1, 2, 3};
numbers.pop_back(); // 벡터에서 3을 제거

// size()
std::vector<int> numbers = {1, 2, 3};
int size = numbers.size(); // size에 3 저장

// empty
std::vector<int> numbers;
bool isEmpty = numbers.empty(); // isEmpty에 true 저장

// clear
std::vector<int> numbers = {1, 2, 3};
numbers.clear(); // 벡터가 비워짐

// at
std::vector<int> numbers = {1, 2, 3};
int element = numbers.at(1); // element에 2 저장

// front()
std::vector<int> numbers = {1, 2, 3};
int firstElement = numbers.front(); // firstElement에 1 저장

// back()
std::vector<int> numbers = {1, 2, 3};
int lastElement = numbers.back(); // lastElement에 3 저장

// erase()
std::vector<int> numbers = {1, 2, 3};
numbers.erase(numbers.begin() + 1); // 두 번째 요소(인덱스 1)를 제거

// insert()
std::vector<int> numbers = {1, 2, 3};
numbers.insert(numbers.begin() + 1, 4); // 두 번째 위치(인덱스 1)에 4를 삽입

추가 해설
C++ 표준 라이브러리(STL)에서는 다양한 종류의 반복자(iterator)를 제공합니다. 이러한 반복자들은 다양한 컨테이너 클래스와 알고리즘과 함께 사용됩니다. 다음은 주요한 STL 반복자들의 종류를 나열한 것입니다:

입력 반복자 (Input Iterator):
순차적으로 값을 읽을 수 있는 반복자입니다. 값을 읽을 수 있지만 값을 쓸 수는 없습니다. 대표적으로 istream_iterator가 있습니다.

출력 반복자 (Output Iterator):
순차적으로 값을 쓸 수 있는 반복자입니다. 값을 쓸 수 있지만 값을 읽을 수는 없습니다. 대표적으로 ostream_iterator가 있습니다.

전진 반복자 (Forward Iterator):
컨테이너의 요소를 한 번만 순회할 수 있는 반복자로, 순차적인 읽기와 쓰기 작업이 가능합니다. 대표적으로 std::forward_iterator가 있습니다.

양방향 반복자 (Bidirectional Iterator):
컨테이너의 요소를 양방향으로 순회할 수 있는 반복자입니다. 즉, 앞뒤로 이동하면서 읽기와 쓰기 작업이 가능합니다. 대표적으로 std::bidirectional_iterator가 있습니다.

임의 접근 반복자 (Random Access Iterator):
컨테이너의 요소를 임의의 위치에서 접근할 수 있는 가장 강력한 반복자입니다. 산술 연산(덧셈, 뺄셈)을 통해 임의 위치로 이동할 수 있으며, 읽기와 쓰기 작업이 모두 가능합니다. 대표적으로 std::random_access_iterator가 있습니다.
이러한 반복자들은 C++의 다양한 컨테이너 클래스(예: 벡터, 리스트, 맵)와 함께 사용되며, 알고리즘 함수(예: std::for_each, std::find, std::sort)와 조합하여 데이터를 처리하고 조작하는 데 유용합니다. 반복자를 이용하면 컨테이너의 내용을 순회하고 수정할 수 있으며, 다양한 데이터 구조와 알고리즘을 구현할 때 핵심 역할을 합니다.

원문

  • Iterators are objects that allow a programmer to sequentially process every object within a collection of objects. For example, in the code below the variable i functions as an iterator object for the array a.

  • 반복자(Iterators)는 프로그래머가 객체의 모음(collection) 내의 모든 객체를 순차적으로 처리할 수 있게 해주는 객체입니다. 예를 들어, 아래 코드에서 변수 i는 배열 a에 대한 반복자 객체로 작동합니다.

int a[100];
// ... put some data in a
for (int i = 0; i < 100; ++i)
    cout << a[i];
  • Whenever a programmer creates a class that serves to store a collection of objects, it is usual to also define a friendly iterator class for that collection. For example, the List class defined earlier is a collection class, therefore an iterator class for lists is defined below.

  • 프로그래머가 객체 컬렉션을 저장하는 역할을 하는 클래스를 만들 때, 해당 컬렉션용 친구(iterator) 클래스를 정의하는 것은 일반적입니다. 예를 들어, 앞에서 정의한 List 클래스는 컬렉션 클래스이므로 아래에 목록용(iterator) 클래스가 정의되어 있습니다.

#include <iostream>
using namespace std;

class List {
    friend ListIter;
    public:
    // ... as seen before
    protected:
        ListCell* First;
};

class ListIter {
    public:
        ListIter() { ptr = NULL; } // default constructor
        ListIter(List& l) (ptr l.First;) // constructor that sets iterator to point to a list
        void reset(List& l) (ptr = l.First;) // reset iterator to point to another list
        char operator()() // return item currently pointed to by iterator
        void operator++() // pointer iterator to next item in list
        void operator=(char c) { ptr->contents = c; } // change the data item in the node pointed to by the iterator
        int operator!() { return ptr != NULL; } // return TRUE if more items remain in the list being processed, FALSE otherwise

    protected:
        ListCell* ptr;
};

char ListIter::operator()()
{
    if (ptr != NULL)
        return ptr->GetContents();
    else
        // return NULL; // 원문
        return '\0'; // nullptr 대신 null 문자 반환
};

void ListIter::operator++()
{
    if (ptr != NULL) ptr = ptr->GetNext();
};

int main() {
    List l;
    l.add('a');
    l.add('b');
    l.add('c');
    for (ListIter i(l); !i; ++i)
        cout << i();
    
    return 0;
}

8.8 Static Class Members 정적 클래스 멤버

추가 해설
정적(static) 클래스 멤버란 클래스의 모든 인스턴스 간에 공유되는 멤버 변수 또는 멤버 함수를 의미합니다. 이러한 정적 멤버는 특정 인스턴스에 종속되지 않으며, 클래스 자체에 속해 있습니다. 다음은 정적 클래스 멤버에 대한 주요 특징입니다

클래스 레벨의 공유, 클래스 이름을 통해 접근, 생성자 및 소멸자와 관련 없음, 클래스가 로드될 때 초기화되고 프로그램이 종료될 때까지 존재, 공유 데이터 저장 - 주로 클래스 수준에서 데이터를 공유하거나 클래스 수준의 동작을 제공하는 데 사용, 예를 들어, 클래스의 모든 인스턴스가 공유하는 상태 정보나 카운터 변수를 저장, static 키워드 사용

#include <iostream>

class MyClass {
public:
    static int staticVariable; // 정적 멤버 변수
    static void staticFunction() { // 정적 멤버 함수
        std::cout << "This is a static function." << std::endl;
    }
};

// 정적 멤버 변수의 초기화
int MyClass::staticVariable = 0;

int main() {
    // 정적 멤버 변수에 접근
    MyClass::staticVariable = 42;
    std::cout << "Static Variable: " << MyClass::staticVariable << std::endl;

    // 정적 멤버 함수 호출
    MyClass::staticFunction();

    return 0;
}
  • Normally, every instance of a class contains individual instances of the class member attributes. For example, every vehicle object has individual weight and wheels variables. However, a class member that is declared as static is shared among all the instances of the class. Only one copy of a static class member exists regardless of how many instances of the class have been created.

  • 일반적으로 클래스의 각 인스턴스는 해당 클래스의 멤버 속성의 개별 인스턴스를 포함합니다. 예를 들어, 각각의 차량 객체는 개별적인 무게(weight)와 바퀴(wheels) 변수를 가집니다. 그러나 static으로 선언된 클래스 멤버는 클래스의 모든 인스턴스 사이에서 공유됩니다. 클래스의 여러 인스턴스가 생성되었더라도 static 클래스 멤버의 하나의 복사본만 존재합니다.

  • Static class members are often used to store data that is shared among all instances of a class. In tehse cases duplicating the data in every object is wasteful. Static members are also useful when there is information that must be communicated to all instances of a class, for example a condition flag or a countervariable.

  • 정적(static) 클래스 멤버는 종종 클래스의 모든 인스턴스 간에 공유되는 데이터를 저장하는 데 사용됩니다. 이러한 경우 모든 객체에 데이터를 복제하는 것은 낭비입니다. 정적 멤버는 또한 클래스의 모든 인스턴스에 전달되어야 하는 정보가 있는 경우에 유용합니다. 예를 들어, 상태 플래그 또는 카운터 변수와 같은 정보를 모든 클래스 인스턴스에 전달해야 하는 경우입니다.

8.9 Type Conversion Methods 형 변환 메서드

  • The code below defines a fraction class. The function overloading facility is used to provide several different constructor functions, and the multiplication operator is overloaded for fractions.

  • 아래의 코드는 분수(Fraction) 클래스를 정의합니다. 이 코드에서는 함수 오버로딩 기능을 사용하여 여러 다른 생성자 함수를 제공하고, 분수에 대한 곱셈 연산자를 오버로딩합니다.

#include <iostream>

class Fraction {
public:
    Fraction(int top, int bot) : numerator(top), denominator(bot) {}
    Fraction(int top) : numerator(top), denominator(1) {}
    Fraction() : numerator(0), denominator(1) {}
    int top() { return numerator; }
    int bot() { return denominator; }

    friend Fraction operator*(const Fraction& f1, const Fraction& f2) {
        return Fraction(f1.numerator * f2.numerator, f1.denominator * f2.denominator);
    }

private:
    int numerator, denominator;
};

int main() {
    Fraction frac1(1, 2);
    Fraction frac2(3, 4);
    Fraction result = frac1 * frac2;
    std::cout << "Result: " << result.top() << "/" << result.bot() << std::endl;
    return 0;
}
  • The constructor fraction(int) describes how to construct a fraction given an integer. It can also serve as a function for converting integers to fractions. In fact, it will be automatically used in this manner whenever the need arises as in the sequence below.

  • 생성자 fraction(int)은 정수를 기반으로 분수를 만드는 방법을 설명합니다. 또한 정수를 분수로 변환하는 함수로 사용될 수 있습니다. 실제로, 필요한 경우와 같이 이러한 방식으로 자동으로 사용됩니다. 아래의 순서에서처럼 필요할 때 자동으로 사용됩니다.

{
    // ... do something
}

int main()
{
    int i;
    // ...
    fn(i); // i will be converted to a fraction and fn invoked
    // ...
    return 0;
}
  • In this manner constructor methods for a given class serve as functions that convert from some other class to that class.

  • 이러한 방식으로, 주어진 클래스의 생성자 메서드는 다른 클래스에서 해당 클래스로 변환하는 함수로 작동합니다.

  • It is also possible to specify type conversion in the opposite direction. For example, one may wish the system to be able to automatically convert from fractions to floats. This can be accomplished by adding the following method to the fraction class:

  • 반대 방향의 형 변환도 지정할 수 있습니다. 예를 들어, 분수에서 실수(floats)로 자동 변환되도록 시스템을 원할 수 있습니다. 이를 위해 분수 클래스에 다음과 같은 메서드를 추가할 수 있습니다:

class Fraction {
    public:
    // ...
    operator float()
    {
        return static_cast<float>(numerator) / static_cast<float>(denominator);
    };
    protected:
    // ...
};
  • A method of the form class::operator type() specifies an algorithm for converting a class object into a value of the specified type. In this case, a fraction is converted to a float by performing floating point division on the numerator and denominator. Given this method, the following sequence can be performed:

  • class::operator type() 형태의 메서드는 클래스 객체를 지정된 타입의 값으로 변환하는 알고리즘을 지정합니다. 이 경우, 분수는 분자와 분모에 대한 부동 소수점 나눗셈을 수행하여 float로 변환됩니다. 이 메서드가 제공된 경우 다음과 같은 순서를 수행할 수 있습니다.

fraction f1(2,3);
float x;
x=f1 + 0.2; // x is 0.866667
  • The automatic type conversions that one can create in C++ are a great convenience, but can also result in ambiguous expressions such as the one below.

  • C++에서 생성할 수 있는 자동 형 변환은 편의를 제공하지만, 아래와 같이 모호한 표현식을 일으킬 수도 있습니다.

fraction f1(2, 3), f2;
f2 = f1 * 2; // ambiguous conversion!
  • The second statement is ambiguous because the compiler is unable to decide whether to perform float or fraction multiplication. The solution is to use explicit type casting:

  • 두 번째 문장은 모호합니다. 컴파일러는 실수(float) 또는 분수(fraction) 곱셈을 수행할지 결정할 수 없습니다. 해결책은 명시적인 형 변환을 사용하는 것입니다

f2 = f1 * (fraction) 2;