객체지향프로그래밍의 시작(2)

은수·2022년 5월 23일

cpp study

목록 보기
4/21

생성자 초기화 리스트 (initializer list)

// 전체코드
#include <iostream>

class Marine {
  int hp;                // 마린 체력
  int coord_x, coord_y;  // 마린 위치
  int damage;            // 공격력
  bool is_dead;

 public:
  Marine();              // 기본 생성자
  Marine(int x, int y);  // x, y 좌표에 마린 생성

  int attack();                       // 데미지를 리턴한다.
  void be_attacked(int damage_earn);  // 입는 데미지
  void move(int x, int y);            // 새로운 위치

  void show_status();  // 상태를 보여준다.
};

Marine::Marine() : hp(50), coord_x(0), coord_y(0), damage(5), is_dead(false) {}

Marine::Marine(int x, int y)
    : coord_x(x), coord_y(y), hp(50), damage(5), is_dead(false) {}

void Marine::move(int x, int y) {
  coord_x = x;
  coord_y = y;
}
int Marine::attack() { return damage; }
void Marine::be_attacked(int damage_earn) {
  hp -= damage_earn;
  if (hp <= 0) is_dead = true;
}
void Marine::show_status() {
  std::cout << " *** Marine *** " << std::endl;
  std::cout << " Location : ( " << coord_x << " , " << coord_y << " ) "
            << std::endl;
  std::cout << " HP : " << hp << std::endl;
}

int main() {
  Marine marine1(2, 3);
  Marine marine2(3, 5);

  marine1.show_status();
  marine2.show_status();
}

초기화 리스트 (initializer list)

  • 생성자 호출과 동시에 멤버 변수들을 초기화 해주는 형태 -> 더 효율적임
    ex. int a = 10;
  • (초기화 리스트를 사용하지 않는다면 생성을 먼저 하고 대입을 수행)
    ex. int a; a = 10;
(생성자 이름) : var1(arg1), var2(arg2) {}]

일반적으로 멤버 초기화 리스트의 일반적인 형태는 위와 같으며, var1과 arg1의 이름이 같아도 됨

Marine::Marine() : hp(50), coord_x(0), coord_y(0), damage(5), is_dead(false) {}

// coord_x는 x로 초기화, is_dead는 false로 초기화
Marine::Marine(int x, int y)
    : coord_x(x), coord_y(y), hp(50), damage(5), is_dead(false) {}

static 변수

static 멤버 변수

  • 객체가 소멸될 때 소멸되는 것이 아닌, 프로그램이 종료될 때 소멸됨
  • 클래스의 모든 객체들이 '공유'하는 변수로써, 각 객체 별로 따로 존재하는 멤버 변수들과 달리 모든 객체들이 하나의 static 멤버 변수 사용 (전역변수 같지만, 클래스 하나에만 종속되는 변수)
  • 모든 static 변수들은 정의와 동시에 값이 자동으로 0으로 초기화됨

static 함수

  • 클래스 전체에 딱 1개만 존재하는 함수
  • 객체가 없어도 그냥 class 자체에서 호출할 수 있음
Marine::show_total_marine();

static 함수는 어떤 객체에 종속되는 것이 아니라, 클래스에 종속되는 것이므로 호출하는 방법이 (객체).(멤버함수)가 아님. 위와 같이 (클래스)::(static 함수) 형식으로 호출


this

  • 객체 자신을 가리키는 포인터 역할
  • this 포인터 사용하는 경우
    - 클래스의 멤버 변수와 매개 변수가 동일할 때
    ex) this->age = age;
    • 객체 자신의 주소를 리턴할 때
Marine& Marine::be_attacked(int damage_earn) {
  this->hp -= damage_earn;
  if (this->hp <= 0) this->is_dead = true;

  return *this;
}

const 함수

  • 변수들의 값을 바꾸지 않고 읽기만하는, 마치 상수 같은 멤버함수를 상수 함수로써 선언
// 상수 함수는 아래와 같은 형태로 선언
(기존의 함수의 정의) const;

// 예시
int attack() const;
int Marine::attack() const { return default_damage; }
  • 상수 함수 내에서 호출 할 수 있는 함수는 다른 상수함수

assign 함수와 memory_capacity

assign 함수

// str에 원래 있었던 문자열 지워지고 abr가 들어가게 됨
str.assign("abc");
  • assign 함수는 = 와 동일한 역할 수행

memory_capacity

  • 현재 할당되어 있는 메모리 공간의 크기를 나타내는 변수를 사용하여 효율성 증대

문자열 삽입하기 (insert)

#include <iostream>

// string.h 는 strlen 때문에 include 했는데, 사실 여러분이 직접 strlen
// 과 같은 함수를 만들어서 써도 됩니다.
#include <string.h>

class MyString {
  char* string_content;  // 문자열 데이터를 가리키는 포인터
  int string_length;     // 문자열 길이
  int memory_capacity;   // 현재 할당된 용량

 public:
  // 문자 하나로 생성
  MyString(char c);

  // 문자열로 부터 생성
  MyString(const char* str);

  // 복사 생성자
  MyString(const MyString& str);

  ~MyString();

  int length() const;
  int capacity() const;
  void reserve(int size);

  void print() const;
  void println() const;

  MyString& assign(const MyString& str);
  MyString& assign(const char* str);

  char at(int i) const;

  MyString& insert(int loc, const MyString& str);
  MyString& insert(int loc, const char* str);
  MyString& insert(int loc, char c);
};

MyString::MyString(char c) {
  string_content = new char[1];
  string_content[0] = c;
  memory_capacity = 1;
  string_length = 1;
}
MyString::MyString(const char* str) {
  string_length = strlen(str);
  memory_capacity = string_length;
  string_content = new char[string_length];

  for (int i = 0; i != string_length; i++) {
    string_content[i] = str[i];
  }
}

MyString::MyString(const MyString& str) {
  string_length = str.string_length;
  memory_capacity = str.string_length;
  string_content = new char[string_length];

  for (int i = 0; i != string_length; i++) {
    string_content[i] = str.string_content[i];
  }
}

MyString::~MyString() { delete[] string_content; }
int MyString::length() const { return string_length; }

void MyString::print() const {
  for (int i = 0; i != string_length; i++) {
    std::cout << string_content[i];
  }
}
void MyString::println() const {
  for (int i = 0; i != string_length; i++) {
    std::cout << string_content[i];
  }

  std::cout << std::endl;
}

MyString& MyString::assign(const MyString& str) {
  if (str.string_length > memory_capacity) {
    // 그러면 다시 할당을 해줘야만 한다.
    delete[] string_content;

    string_content = new char[str.string_length];
    memory_capacity = str.string_length;
  }
  for (int i = 0; i != str.string_length; i++) {
    string_content[i] = str.string_content[i];
  }

  // 그리고 굳이 str.string_length + 1 ~ string_length 부분은 초기화
  // 시킬 필요는 없다. 왜냐하면 거기 까지는 읽어들이지 않기 때문이다.

  string_length = str.string_length;

  return *this;
}
MyString& MyString::assign(const char* str) {
  int str_length = strlen(str);
  if (str_length > memory_capacity) {
    // 그러면 다시 할당을 해줘야만 한다.
    delete[] string_content;

    string_content = new char[str_length];
    memory_capacity = str_length;
  }
  for (int i = 0; i != str_length; i++) {
    string_content[i] = str[i];
  }

  string_length = str_length;

  return *this;
}
int MyString::capacity() const { return memory_capacity; }
void MyString::reserve(int size) {
  if (size > memory_capacity) {
    char* prev_string_content = string_content;

    string_content = new char[size];
    memory_capacity = size;

    for (int i = 0; i != string_length; i++)
      string_content[i] = prev_string_content[i];

    delete[] prev_string_content;
  }

  // 만일 예약하려는 size 가 현재 capacity 보다 작다면
  // 아무것도 안해도 된다.
}
char MyString::at(int i) const {
  if (i >= string_length || i < 0) {
    return 0;
  } else {
    return string_content[i];
  }
}
MyString& MyString::insert(int loc, const MyString& str) {
  // 이는 i 의 위치 바로 앞에 문자를 삽입하게 된다. 예를 들어서
  // abc 라는 문자열에 insert(1, "d") 를 하게 된다면 adbc 가 된다.

  // 범위를 벗어나는 입력에 대해서는 삽입을 수행하지 않는다.
  if (loc < 0 || loc > string_length) return *this;

  if (string_length + str.string_length > memory_capacity) {
    // 이제 새롭게 동적으로 할당을 해야 한다.

    if (memory_capacity * 2 > string_length + str.string_length)
      memory_capacity *= 2;
    else
      memory_capacity = string_length + str.string_length;

    char* prev_string_content = string_content;
    string_content = new char[memory_capacity];

    // 일단 insert 되는 부분 직전까지의 내용을 복사한다.
    int i;
    for (i = 0; i < loc; i++) {
      string_content[i] = prev_string_content[i];
    }

    // 그리고 새롭에 insert 되는 문자열을 넣는다.
    for (int j = 0; j != str.string_length; j++) {
      string_content[i + j] = str.string_content[j];
    }

    // 이제 다시 원 문자열의 나머지 뒷부분을 복사한다.
    for (; i < string_length; i++) {
      string_content[str.string_length + i] = prev_string_content[i];
    }

    delete[] prev_string_content;

    string_length = string_length + str.string_length;
    return *this;
  }

  // 만일 초과하지 않는 경우 굳이 동적할당을 할 필요가 없게 된다.
  // 효율적으로 insert 하기 위해, 밀리는 부분을 먼저 뒤로 밀어버린다.

  for (int i = string_length - 1; i >= loc; i--) {
    // 뒤로 밀기. 이 때 원래의 문자열 데이터가 사라지지 않게 함
    string_content[i + str.string_length] = string_content[i];
  }
  // 그리고 insert 되는 문자 다시 집어넣기
  for (int i = 0; i < str.string_length; i++)
    string_content[i + loc] = str.string_content[i];

  string_length = string_length + str.string_length;
  return *this;
}
MyString& MyString::insert(int loc, const char* str) {
  MyString temp(str);
  return insert(loc, temp);
}
MyString& MyString::insert(int loc, char c) {
  MyString temp(c);
  return insert(loc, temp);
}
int main() {
  MyString str1("very long string");
  MyString str2("<some string inserted between>");
  str1.reserve(30);

  std::cout << "Capacity : " << str1.capacity() << std::endl;
  std::cout << "String length : " << str1.length() << std::endl;
  str1.println();

  str1.insert(5, str2);
  str1.println();

  std::cout << "Capacity : " << str1.capacity() << std::endl;
  std::cout << "String length : " << str1.length() << std::endl;
  str1.println();
}

find 함수

  • find_from 에서 부터 시작해서 가장 첫번째 str 의 위치를 리턴
  • find_from붜 시작해서 string_contentstr이 완벽히 일치하는 부분이 생긴다면 그 위치를 return
int MyString::find(int find_from, MyString& str) const {
  int i, j;
  if (str.string_length == 0) return -1;
  for (i = find_from; i <= string_length - str.string_length; i++) {
    for (j = 0; j < str.string_length; j++) {
      if (string_content[i + j] != str.string_content[j]) break;
    }

    if (j == str.string_length) return i;
  }

  return -1;  // 찾지 못했음
}

compare 함수

int compare(const MyString& str) const;

*thisstr을 비교하는 형태로 이루어짐

int MyString::compare(const MyString& str) const {
  // (*this) - (str) 을 수행해서 그 1, 0, -1 로 그 결과를 리턴한다
  // 1 은 (*this) 가 사전식으로 더 뒤에 온다는 의미. 0 은 두 문자열
  // 이 같다는 의미, -1 은 (*this) 가 사전식으로 더 앞에 온다는 의미이다.

  for (int i = 0; i < std::min(string_length, str.string_length); i++) {
    if (string_content[i] > str.string_content[i])
      return 1;

    else if (string_content[i] < str.string_content[i])
      return -1;
  }

  // 여기 까지 했는데 끝나지 않았다면 앞 부분 까지 모두 똑같은 것이 된다.
  // 만일 문자열 길이가 같다면 두 문자열은 아예 같은 문자열이 된다.

  if (string_length == str.string_length) return 0;

  // 참고로 abc 와 abcd 의 크기 비교는 abcd 가 더 뒤에 오게 된다.
  else if (string_length > str.string_length)
    return 1;

  return -1;
}

explicit

  • 암시적 형 변환을 허용하지 않을 때 붙이는 키워드
  • 명시적으로 형 변환을 막기 때문에 매우 엄격하게 관리됨

암시적 변환?

  • 클래스 멤버 함수를 예로 들어보자
    DoSomethingString(MyString s){ // do sth .... }
  • 원래는 이렇게 쓰이지만
    DoSomethingString(MyString("abc"));
  • 여기서 이런식으로 사용해도, 알아서 찰떡같이 알아듣고 위와같은 코드처럼 변환되는 것.
    DoSomethingString("abc");

mutable

  • const 함수 내부에서는 멤버 변수들의 값을 바꾸는 것이 불가능
  • 그러나 멤버 변수를 mutable로 선언했다면, const 함수에서도 이를 바꿀 수 있음
// error code
class A{
	int data_;
    public :
    A(int data) : data_(data){}
    void DoSomething(intx) const{
    	data = x; // 상수 함수(const)인데 변수 수정 > 오류
    }

// mutable
class A{
	mutable int data_;
    public :
    A(int data) : data_(data){}
    void DoSomething(intx) const{
    	data = x; // const지만 변수가 mutable이므로 정상작동
    }  

mutable 사용하는 이유?

  • DB에 ID를 리턴받는 함수 const로 작성
  • DB에서 빠르게 ID를 찾으려면 캐시 필요
  • 여기서 DB에 있는 캐시를 확인해서 빠르게 ID를 찾아야 하는데, 캐시는 크지 않기 때문에 자주 업데이트 함. 따라서 캐시를 업데이트 하는 작업 필요!
  • 따라서 여기서 캐시 변수를 mutable로 선언

0개의 댓글