따라하며 배우는 C++
8. 객체지향의 기초
8.1 객체지향 프로그래밍과 클래스
데이터와 기능이 합쳐 있는 것을 Object라고 한다.
물론 Structure도 기능을 넣을 수 있지만, 복잡한 것을 할 때는 class가 좋다.
#include <iostream>
using namespace std;
class Freind
{
public: //access specifier (public, private, protected)
string name;
string address;
int age;
double height;
double weight;
void print()
{
cout << name << " " << address << " " << age << " " << height << " "
<< weight << endl;
}
};
int main()
{
return 0;
}
변수와 기능을 묶어놓은 것이 개념적인 단어로 "Object"
이것을 프로그래밍 언어로 구현한 키워드가 "class"
실제로 메모리를 차지하도록 사용자 정의형처럼 선언해주는 것이 instanciation.
실제로 그런 변수가 instance. (메모리가 있는 실제)
클래스의 멤버라는 것을 표현하기 위해
m_name;
name_;
_name;
라고 정의하기도 한다.
8.2 캡슐화, 접근 지정자, 접근 함수
Encapsulation, Access Specifiers, Access Functions
#include <iostream>
using namespace std;
class Date
{
//기본은 private. public이라고 해야 외부에서 접근이 된다.
private:
int m_month;
int m_day;
int m_year;
public:
//같은 클래스이면 private라도 접근 가능
//access function
void setDate(const int& month_input, const int& day_input,
const int& year_input)
{
m_month = month_input;
m_day = day_input;
m_year = year_input;
}
void setMonth(const int& month_input) //setters
{
m_month = month_input;
}
const int& getMonth() //getters
{
return m_day;
}
void copyFrom(const Date& origin)
{
m_month = origin.m_month;
m_day = origin.m_day;
m_year = origin.m_year;
}
};
int main()
{
Date today;
//today.m_month = 8; 만약 멤버가 public이 아니면 이렇게 접근 불가.
today.setDate(8, 4, 2025); //바깥에서의 접근은 우회해야 하므로 encapsulation
Date copy;
copy.copyFrom(today); //다른 인스턴스의 func에서 접근하고 있다.
//같은 클래스의 다른 인스턴스에 들어가 있는 멤버도 접근 가능.
return 0;
}
이를테면 멤버의 이름을 바꿀 때 getter, setter를 사용해야 바꾸는 것이 용이해진다.
8.3 생성자 Constructors
#include <iostream>
using namespace std;
class Fraction
{
private:
int m_numerator;
int m_denominator;
public:
void print()
{
cout << m_numerator << " / " << m_denominator << endl;
}
};
int main()
{
Fraction frac;
frac.print(); //초기화하지 않으면 오류
return 0;
}
#include <iostream>
using namespace std;
class Fraction
{
private:
int m_numerator = 0;
int m_denominator = 1; //기본값 설정 가능.
public:
Fraction() //class 이름과 동일하면 자동으로 생성자.
{
m_numerator = 0;
m_denominator = 1;
}
void print()
{
cout << m_numerator << " / " << m_denominator << endl;
}
};
int main()
{
//생성자는 선언이 됨과 동시에 실행됨
Fraction frac;
//그래서 실제로는 생성자도 함수라 frac()의 의미를 갖고 있으나
//C++에서는 생성자 매개변수가 하나도 없을 경우 ()를 생략함.
return 0;
}
#include <iostream>
using namespace std;
class Fraction
{
private:
int m_numerator = 0;
int m_denominator = 1; //기본값 설정 가능.
public:
Fraction() //class 이름과 동일하면 자동으로 생성자.
{
m_numerator = 0;
m_denominator = 1;
}
Fraction(const int &num_in, const int&den_in = 3)
{
m_numerator = num_in;
m_denominator = den_in;
}
void print()
{
cout << m_numerator << " / " << m_denominator << endl;
}
};
int main()
{
Fraction one_thirds(1);
one_thirds.print();
Fraction two_thirds(2, 3);
two_thirds.print();
return 0;
}
물론 생성자를 만들어주지 않더라도 디폴트 생성자가 존재한다.
생성자의 초기값이 없으면 쓰레기값이 들어갈 수 있다.
//생성자 = 생성될 때 자동으로 호출되는 함수
생성자도 오버로딩 모호성 오류가 있을 수 있다.
Fraction base = Fraction{ 1, 3 };
도 가능.
Fraction base {1, 3}; 도 가능.
//여기서 public일 때는 위의 uniform initialization으로도 초기화가 가능하지만
//private면 불가하다.
//또한 uniform initalization은 형 변환을 아예 지원 안해준다. (int->float도 불가)
#include <iostream>
using namespace std;
class Second
{
public:
Second()
{
cout << "class Second constructor()" << endl;
}
};
class First
{
Second sec;
public:
First()
{
cout << "class Fisrst constructor()" << endl;
}
};
int main()
{
First fir;
return 0;
}
8.4 생성자 멤버 초기화 목록. Member Initializer List
#include <iostream>
using namespace std;
class Something
{
private:
int m_i;
double m_d;
char m_c;
public:
Something()
{
m_i = 1;
m_d = 3.14;
m_c = 'a';
}
};
int main()
{
return 0;
}
이렇게 초기화 값을 넣었던 것을.
#include <iostream>
using namespace std;
class Something
{
private:
int m_i;
double m_d;
char m_c;
public:
Something() : m_i(1), m_d(3.14), m_c('a')
{
}
void print()
{
cout << m_i << " " << m_d << " " << m_c << endl;
}
};
int main()
{
Something sm;
sm.print();
return 0;
}
이렇게 쓰는 것도 가능.
Something() : m_i{1}, m_d{3.14}, m_c{'a'}
위의 표현도 가능. 하지만 형변환이 더욱 엄격해진다.
#include <iostream>
using namespace std;
class Something
{
private:
int m_i;
double m_d;
char m_c;
int m_arr[5];
public:
Something() : m_i(1), m_d(3.14), m_c('a'), m_arr{ 1, 2, 3, 4, 5 }
{
}
void print()
{
cout << m_i << " " << m_d << " " << m_c << endl;
for (auto& e : m_arr) cout << e << " ";
cout << endl;
}
};
int main()
{
Something sm;
sm.print();
return 0;
}
배열 초기화도 가능. 단, ()는 사용 불가.
#include <iostream>
using namespace std;
class B
{
private:
int m_b;
public:
B(const int & m_b_in) : m_b(m_b_in)
{ }
};
class Something
{
private:
int m_i;
double m_d;
char m_c;
int m_arr[5];
B m_b;
public:
Something() : m_i(1), m_d(3.14), m_c('a'), m_arr{ 1, 2, 3, 4, 5 }, m_b(m_i - 1)
{
}
void print()
{
cout << m_i << " " << m_d << " " << m_c << endl;
for (auto& e : m_arr) cout << e << " ";
cout << endl;
}
};
int main()
{
Something sm;
sm.print();
return 0;
}
클래스 멤버를 초기화하는 것도 가능.
멤버 초기화의 경우, ","를 엔터로 분리하면 멤버 초기화를 삽입하고 삭제하기 용이하다.
class Something
{
private:
int m_i = 100;
double m_d = 100.0;
char m_c = 'F';
int m_arr[5] = { 100, 200, 300, 400, 500 };
B m_b{ 1024 };
public:
Something() : m_i(1), m_d(3.14), m_c('a'), m_arr{ 1, 2, 3, 4, 5 }, m_b(m_i - 1)
{
}
둘 다 초기화할 경우 생성자가 더 우선순위가 높다.
생성자 안에서
m_i = 2 ; 이런 식으로 다시 대입을 하면 다시 대입한 값이 최종값이 된다.
8.5 위임 생성자
#include <iostream>
using namespace std;
class Student
{
private:
int m_id;
string m_name;
public:
Student(const string& name_in)
:m_id(0), m_name(name_in)
{}
Student(const int& id_in, const string& name_in)
:m_id(id_in), m_name(name_in)
{}
//수정 및 초기화는 한 군데서만 이루어지는 게 베스트다.
void print()
{
cout << m_id << " " << m_name << endl;
}
};
int main()
{
Student
return 0;
}
생성자가 생성자를 가져다쓰는 것도 가능.
class Student
{
private:
int m_id;
string m_name;
public:
Student(const string& name_in)
:Student(0, name_in)
{}
Student(const int& id_in, const string& name_in)
:m_id(id_in), m_name(name_in)
{}
//수정 및 초기화는 한 군데서만 이루어지는 게 베스트다.
void print()
{
cout << m_id << " " << m_name << endl;
}
};
#include <iostream>
using namespace std;
class Student
{
private:
int m_id;
string m_name;
public:
Student(const string& name_in)
:Student(0, name_in)
{}
Student(const int& id_in, const string& name_in)
:m_id(id_in), m_name(name_in)
{}
//수정 및 초기화는 한 군데서만 이루어지는 게 베스트다.
void print()
{
cout << m_id << " " << m_name << endl;
}
};
int main()
{
Student st1(0, "Jack");
st1.print();
Student st2("Dash");
st2.print();
return 0;
}
//이런 방식을 "위임 생성자"라고 한다.
class Student
{
private:
int m_id;
string m_name;
public:
Student(const string& name_in)
{
init(0, name_in);
}
Student(const int& id_in, const string& name_in)
{
init(id_in, name_in);
}
//수정 및 초기화는 한 군데서만 이루어지는 게 베스트다.
void print()
{
cout << m_id << " " << m_name << endl;
}
void init(const int& id_in, const string& name_in)
{
m_id = id_in;
m_name = name_in;
}
};
아예 init 함수를 별도로 만들 수도 있다.
8.6 소멸자 destructor
#include <iostream>
using namespace std;
class Simple
{
private:
int m_id;
public:
Simple(const int& id_in)
:m_id(id_in)
{
cout << "Constructor " << m_id << endl;
}
~Simple() //소멸자. 소멸자는 매개변수가 없다. ~는 틸다.
{
cout << "Destructor " << m_id << endl;
}
};
int main()
{
Simple s1(0);
Simple s2(1);
return 0;
}
소멸자는 instance가 메모리에서 해체될 때 내부에서 자동으로 호출된다.
동적할당으로 만들어질 경우엔 영역을 벗어나도 자동으로 메모리가 해제되지 않기 때문에
(Simple *s1 = new Simple(0);)
메모리를 해제할 때에만 소멸자가 호출된다.
소멸자를 프로그래머가 직접 호출하는 건 대부분 호출되지 않는다.
(delete s1;)
#include <iostream>
using namespace std;
class IntArray
{
int* m_arr = nullptr;
int m_length = 0;
public:
IntArray(const int length_in)
{
m_length = length_in;
m_arr = new int[m_length];
cout << "Constructor " << endl;
}
int size() { return m_length; }
};
int main()
{
while (true)
{
IntArray my_int_arr(1000);
//
}
return 0;
}
메모리 누수가 발생한다.
생성자 안에서 new가 있기 때문에 delete를 해 주어야 하기 때문이다.
또한 private 멤버면 외부에서 delete로 접근해 해당 멤버를 삭제해주지 못한다.
~IntArray()
{
if(m_arr != nullptr) delete[] m_arr;
}
이렇게 소멸자를 생성하면 더 안전한 코딩이 가능.
//nullptr을 delete하려 해도 문제가 발생할 수 있다.
소멸자를 코딩하기도 귀찮다면 vector를 사용하면 된다(내부에서 자동으로 딜리트 됨)
8.7 thie 포인터와 연쇄 호출 Chaining Member Functions
#include <iostream>
using namespace std;
class Simple
{
private:
int m_id;
public:
Simple(int id)
{
setID(id);
cout << this << endl; //this는 자기 자신의 주소 출력
}
void setID(int id) { m_id = id; }
int getID() { return m_id; }
};
int main()
{
Simple s1(1), s2(2);
s1.setID(2);
s2.setID(4);
cout << &s1 << " " << &s2 << endl;
return 0;
}
클래스의 함수는 instance끼리 공유된다.
여기서 각자의 instance를 구분할 수 있는 키워드가 바로 this.
생성자에서 함수를 호출한다면, this->setID(id)에서 this-> 부분이 생략되어 있는 것이다.
(*this).setID(id)도 똑같은 기능!!
(즉, 보이지 않는 포인터가 들어가 실행되는 것처럼 작동한다)
#include <iostream>
using namespace std;
class Calc
{
int m_value;
public:
Calc(int init_value)
:m_value(init_value)
{}
void add(int value) { m_value += value; }
void sub(int value) { m_value -= value; }
void mult(int value) { m_value *= value; }
void div(int value) { m_value /= value; }
void print() { cout << m_value << endl; }
};
int main()
{
Calc cal(10);
cal.add(10);
cal.sub(1);
cal.mult(10);
return 0;
}
이렇게 인수에 .을 찍어 메소드를 호출하는 것이 불편할 수 있다.
#include <iostream>
using namespace std;
class Calc
{
int m_value;
public:
Calc(int init_value)
:m_value(init_value)
{}
Calc& add(int value) { m_value += value; return *this; }
Calc& sub(int value) { m_value -= value; return *this; }
Calc& mult(int value) { m_value *= value; return *this; }
Calc& div(int value) { m_value /= value; return *this; }
void print() { cout << m_value << endl; }
};
int main()
{
Calc cal(10);
//cal.add(10);
//cal.sub(1);
//cal.mult(10); //이렇게도 사용 가능.
cal.add(10).sub(1).mult(2).print();
return 0;
}
8.8 클래스 코드와 헤더 파일.
#pragma once
#include <iostream>
//헤더 안에선 using namespace를 함부로 쓰지 않도록 하자.
//include하는 모든 파일에 영향을 주기 때문.
class Calc
{
int m_value;
public:
Calc(int init_value)
:m_value(init_value)
{}
Calc& add(int value);
Calc& sub(int value);
Calc& mult(int value);
Calc& div(int value);
void print() { using namespace std; cout << m_value << endl; }
};
위는 Clac.h
#include "Calc.h""
Calc& Calc::add(int value) { m_value += value; return *this; }
Calc& Calc::sub(int value) { m_value -= value; return *this; }
Calc& Calc::mult(int value) { m_value *= value; return *this; }
Calc& Calc::div(int value) { m_value /= value; return *this; }
위는 Calc.cpp
이렇게도 class를 정리할 수 있다.
번거롭다면 h파일에서 해당 메소드 > 우클릭 > 빠른 작업 및 리팩토링 > 정의 위치 옮기기를 이용하면
자동으로 cpp 파일로 정의가 옮겨간다. (자동 inline화)
#include "Calc.h""
inline Calc::Calc(int init_value)
:m_value(init_value)
{}
Calc& Calc::add(int value) { m_value += value; return *this; }
Calc& Calc::sub(int value) { m_value -= value; return *this; }
Calc& Calc::mult(int value) { m_value *= value; return *this; }
Calc& Calc::div(int value) { m_value /= value; return *this; }
void Calc::print() { using namespace std; cout << m_value << endl; }
생성자 정의도 옮길 수 있다.
이 때 cpp 파일은 다른 파일에 영향을 주지 않기 때문에 using namespace std를 위에 적어도 괜찮다!
h에 template에 정의한 기능을 놔두는 경우도 꽤 많다.
8.9 클래스와 const
#include <iostream>
using namespace std;
class Something
{
public:
int m_value = 0;
void setValue(int value) { m_value = value; }
int getValue() { return m_value; }
};
int main()
{
const Something something;
//something.setValue(3); //오류
//const로 선언한 Object는 변경 불가, 즉 멤버 인수들이 const로 바뀐 것과 같다.
// cout << something.getValue() < endl; 이것도 오류가 난다.
//왜냐하면 멤버 함수가 const냐 아니냐만 판단하기 때문.
return 0;
}
#include <iostream>
using namespace std;
class Something
{
public:
int m_value = 0;
void setValue(int value) { m_value = value; }
//set에는 넣을 수 없다(애초에 수정하는 함수이므로)
int getValue() const{ return m_value; }
//기능을 const로 넣은 것
};
int main()
{
const Something something;
//something.setValue(3); //오류
//const로 선언한 Object는 변경 불가, 즉 멤버 인수들이 const로 바뀐 것과 같다.
cout << something.getValue() << endl;
//왜냐하면 멤버 함수가 const냐 아니냐만 판단하기 때문.
return 0;
}
const로 쓸 수 있는 건 많이 쓰는게 좋다.
#include <iostream>
using namespace std;
class Something
{
public:
int m_value = 0;
void setValue(int value) { m_value = value; }
//set에는 넣을 수 없다(애초에 수정하는 함수이므로)
int getValue() const{ return m_value; }
//기능을 const로 넣은 것
};
void print(Something st)
{
cout << st.m_value << endl;
cout << &st << endl;
}
int main()
{
Something something;
print(something);
cout << &something << endl;
return 0;
}
이 경우 매개변수에 객체가 들어와도 생성자는 한번 호출된다.
기본적인 copy 생성자가 숨어 있기 때문이다.
Something(const Something& st_in)
{
m_value = st_in.m_value;
}
위와 같은 역할을 하며, 수동으로 만들어줄 수 있다.
(Copy Constructor)
void print(const Something &st)
{
cout << st.getValue() << endl;
cout << &st << endl;
}
이렇게 해야 매개변수가 사본으로 만들어지지 않고, 인수 그 자체가 넘어간다.
#include <iostream>
#include <string>
using namespace std;
class Something
{
public:
string m_value = "default";
const string & getValue() const {
cout << "const version" << endl;
return m_value;
}
string& getValue() {
cout << "non-const version" << endl;
return m_value;
}
};
int main()
{
Something something;
const Something something2;
something.getValue();
something2.getValue();
return 0;
}
선언에 따라 자동으로 오버로딩 될 수 있다.
전자는 something.geValue() = 1; 이 가능하지만 something2.getValue() =2; 는 불가.
멤버 기능이 const일 때 return도 일반적으로 const로 한다.
8.10 정적 멤버 변수
#include <iostream>
#include <string>
using namespace std;
class Something
{
public:
int m_value = 1;
};
int main()
{
Something st1;
Something st2;
st1.m_value = 2;
cout << &st1.m_value << " " << st1.m_value << endl;
cout << &st2.m_value << " " << st2.m_value << endl;
return 0;
}
위에선 st1과 st2의 값들이 다르게 나온다.
#include <iostream>
#include <string>
using namespace std;
class Something
{
public:
static int s_value;
};
int Something::s_value = 1;
int main()
{
Something st1;
Something st2;
st1.s_value = 2;
cout << &st1.s_value << " " << st1.s_value << endl;
cout << &st2.s_value << " " << st2.s_value << endl;
return 0;
}
그러나 위에선 놀랍게도 같게 나온다!
인수들이 없을 때도 Something::s_value를 호출하면 메모리가 존재한다.
밑에서 Something::s_value = 1024; 로 직접 접근도 가능하다.
메모리에 정적으로 존재하기 때문이다.
정적 변수는 cpp 파일에 두는 것이 좋다(중복 선언 문제 발생)
class 안에서 바로 static int s_value = 1; 초기화하는 것도 불가하다.
#include <iostream>
#include <string>
using namespace std;
class Something
{
public:
static const int s_value = 2;
};
int main()
{
Something st1;
Something st2;
cout << &st1.s_value << " " << st1.s_value << endl;
cout << &st2.s_value << " " << st2.s_value << endl;
return 0;
}
const이면 값을 수정 못하지만 class 안에서만 초기화 가능!
#include <iostream>
#include <string>
using namespace std;
class Something
{
public:
static constexpr int s_value = 1; //컴파일 타임에 초기화가 정해지는 경우
};
int main()
{
Something st1;
Something st2;
cout << &st1.s_value << " " << st1.s_value << endl;
cout << &st2.s_value << " " << st2.s_value << endl;
return 0;
}
상수 선언할 때도 쓰이고, 싱글톤 패턴에서도 많이 쓰인다.
8.11 정적 멤버 함수
#include <iostream>
#include <string>
using namespace std;
class Something
{
public:
static int s_value;
//같은 클래스의 모든 인스턴스에서 접근 가능
public:
int getValue()
{
return s_value;
}
};
int main()
{
cout << Something::s_value << endl;
Something s1;
cout << s1.getValue() << endl;
cout << s1.s_value() << endl;
return 0;
}
여기서 static을 private로 할 경우 바로 접근이 불가하다.
#include <iostream>
#include <string>
using namespace std;
class Something
{
private:
static int s_value;
//같은 클래스의 모든 인스턴스에서 접근 가능
public:
static int getValue()
{
return s_value;
}
};
int Something::s_value = 1024;
int main()
{
cout << Something::getValue() << endl;
Something s1;
cout << s1.getValue() << endl;
return 0;
}
instance에 상관없이 static 함수를 만들어 접근 가능
(static은 반드시 초기화 값이 있어야 함)
static int getValue()
{
return s_value; //여기선 this->s_value도 쓸 수 없다.
//this로 접근할 수 있는 멤버를 전부 접근할 수 있다.
}
int temp()
{
return this->s_value + this->m_value;
}
#include <iostream>
#include <string>
using namespace std;
class Something
{
private:
static int s_value;
int m_value;
//같은 클래스의 모든 인스턴스에서 접근 가능
public:
static int getValue()
{
return s_value; //여기선 this->s_value도 쓸 수 없다.
//this로 접근할 수 있는 멤버를 전부 접근할 수 있다.
}
int temp()
{
return this->s_value;
}
};
int Something::s_value = 1024;
int main()
{
cout << Something::getValue() << endl;
Something s1, s2;
cout << s1.getValue() << endl;
//int(Something:: * fptr1)() = &(s1.temp); //불가능.
//instance의 function은 class에 묶여 있어서 instance 하나 당 호출이 불가하다.
int (Something:: * fptr1)() = &Something::temp;
cout << (s2.*fptr1)() << endl;
return 0;
}
s2의 포인터를 기능에 넣어서 작동하는 형태이므로, instance를 주어야 기능이 동작할 수 있다.
(기능에 this 포인터가 넣어져야 작동 가능)
#include <iostream>
#include <string>
using namespace std;
class Something
{
private:
static int s_value;
int m_value;
//같은 클래스의 모든 인스턴스에서 접근 가능
public:
static int getValue()
{
return s_value; //여기선 this->s_value도 쓸 수 없다.
//this로 접근할 수 있는 멤버를 전부 접근할 수 있다.
}
int temp()
{
return this->s_value;
}
};
int Something::s_value = 1024;
int main()
{
cout << Something::getValue() << endl;
Something s1, s2;
cout << s1.getValue() << endl;
//int(Something:: * fptr1)() = &(s1.temp); //불가능.
//instance의 function은 class에 묶여 있어서 instance 하나 당 호출이 불가하다.
int (Something:: * fptr1)() = &Something::temp;
cout << (s2.*fptr1)() << endl;
int (*fptr2)() = &Something::getValue; //특정 인수와 연결시키지 않아도 동작!
return 0;
}
class Something
{
private:
static int s_value;
int m_value;
//같은 클래스의 모든 인스턴스에서 접근 가능
public:
Something()
:m_value(123) //s_value(1024)로 초기화 불가. static은 생성자에서 초기화 불가
//static constructor는 C에서 지원하지 않는다.
{}
static int getValue()
{
return s_value; //여기선 this->s_value도 쓸 수 없다.
//this로 접근할 수 있는 멤버를 전부 접근할 수 있다.
}
int temp()
{
return this->s_value;
}
};
class Something
{
public:
class _init
{
public:
_init()
{
s_value = 1234;
}
};
private:
static int s_value;
int m_value;
//같은 클래스의 모든 인스턴스에서 접근 가능
static _init s_initializer;
public:
static int getValue()
{
return s_value; //여기선 this->s_value도 쓸 수 없다.
//this로 접근할 수 있는 멤버를 전부 접근할 수 있다.
}
int temp()
{
return this->s_value;
}
};
int Something::s_value = 1024; //이건 우선순위가 떨어짐
Something::_init Something::s_initializer;
8. 12 친구 함수와 클래스 friend
class A
{
private:
int m_value = 1;
friend void doSomething(A& a);
};
void doSomething(A& a)
{
cout << a.m_value << endl;
}
class A
{
private:
int m_value = 1;
friend void doSomething(A& a, B& b);
};
class B
{
private:
int m_value = 2;
friend void doSomething(A& a, B& b);
};
void doSomething(A& a, B& b)
{
cout << a.m_value << " " << b.m_value << endl;
}
위의 코드는 에러, A 다음에 class B가 선언되어 있어 A에선 B에 접근이 불가하기 때문.
class B;
class A
{
private:
int m_value = 1;
friend void doSomething(A& a, B& b);
};
class B
{
private:
int m_value = 2;
friend void doSomething(A& a, B& b);
};
void doSomething(A& a, B& b)
{
cout << a.m_value << " " << b.m_value << endl;
}
forward declaration을 하면 해결됨
class B;
class A
{
private:
int m_value = 1;
friend class B;
};
class B
{
private:
int m_value = 2;
public:
void doSomething(A& a)
{
cout << a.m_value << endl;
}
};
friend 함수 뿐 아니라 클래스도 선언 가능.
class A;
class B
{
private:
int m_value = 2;
public:
void doSomething(A& a);
};
class A
{
private:
int m_value = 1;
friend void B::doSomething(A& a);
};
void B::doSomething(A& a)
{
cout << a.m_value << endl;
}
8.13 익명 객체 anonymous object
#include <iostream>
#include <string>
using namespace std;
class A {
public:
A()
{
cout << "Constructor" << endl;
}
~A()
{
cout << "Destructor" << endl;
}
void print()
{
cout<< "Hello" << endl;
}
};
int main()
{
A().print(); //이것도 가능, 그러나 instance가 없으니 재사용 불가.
A().print();//생성되자마자 바로 지워진다. r-value처럼 작동하는 것.
return 0;
}
#include <iostream>
#include <string>
using namespace std;
class Cents
{
private:
int m_cents;
public:
Cents(int cents) { m_cents = cents; }
int getCents() const { return m_cents; }
};
Cents add(const Cents& c1, const Cents& c2)
{
return Cents(c1.getCents() + c2.getCents());
}
int main()
{
cout << add(Cents(6), Cents(8)).getCents() << endl;
return 0;
}
8.14 클래스 안에 포함된 자료형 Nested types
#include <iostream>
using namespace std;
class Fruit
{
public:
enum FruitType
{
APPLE, BANANA, CHERRY,
};
private:
FruitType m_type;
public:
Fruit(FruitType type) : m_type(type)
{}
FruitType getType() { return m_type; }
};
int main()
{
Fruit apple(Fruit::APPLE);
if (apple.getType == APPLE)
{
cout << "Apple" << endl;
}
return 0;
}
클래스 안에 enum을 넣을 수 있다.
소속을 Fruit:: 이런 식으로 구체적으로 표시만 해주면 된다.
class InnterClass {}; 혹은 struct InnerStruct{}; 처럼 클래스 안에 임시 데이터 타입을 넣을 수 있다.
8.15 실행 시간 측정하기
#include <iostream>
#include <vector>
#include <algorithm>
#include <random>
#include <chrono> //시간에 관련된 라이브러리
using namespace std;
class Timer
{
using clock_t = chrono::high_resolution_clock;
using second_t = chrono::duration<double, ratio<1>>;
chrono::time_point<clock_t> start_time = clock_t::now(); //시작 시간
public:
void elapsed()
{
chrono::time_point<clock_t> end_time = clock_t::now();//끝난 시간.
cout << chrono::duration_cast<second_t>(end_time - start_time).count() << endl;
}
};
int main()
{
random_device rnd_device;
mt19937 mersenne_engine{ rnd_device() };
vector<int> vec(10);
for (unsigned int i = 0; i < vec.size(); ++i) vec[i] = i; //초기화
shuffle(begin(vec), end(vec), mersenne_engine);
//순서대로 초기화된 벡터를 랜덤으로 섞어 주는 것.
for (auto& e : vec) cout << e << " ";
cout << endl;
Timer timer; //타이머 구동 시작
sort(begin(vec), end(vec)); //sorting
timer.elapsed(); //타이머 끝
return 0;
}
여기서 release 모드로 바꾸면 실행 속도가 더 빨라진다!
'개발 공부 > C++' 카테고리의 다른 글
따라하며 배우는 C++ 10. 객체 사이의 관계들에 대해 (0) | 2020.07.02 |
---|---|
따라하며 배우는 C++ 9. 연산자 오버로딩 (0) | 2020.07.02 |
따라하며 배우는 C++ 7. 함수 (0) | 2020.07.01 |
따라하며 배우는 C++ 6. 행렬, 문자열, 포인터, 참조 (0) | 2020.06.30 |
따라하며 배우는 C++ 5. 흐름제어 (0) | 2020.06.30 |