반응형
(42Seoul의 CPP02 과제를 진행하며 공부한 내용을 작성한 TIL을 정리한 글 입니다.)
개념 공부📓
Orthodox Canonical Form
- OCCF 란, 클래스의 기본적인 형식 또는 일반적인 형식을 따르는 규칙 집합
- 이 규칙들은 클래스의 일관성과 호환성을 유지하고, 예기치 않은 동작을 방지하는데 도움을 줌
- C++ 98 버전의 OCCF 는 아래 규칙을 포함함
- 기본 생성자(Default Constructor)
- 복사 생성자(Copy Constructor)
- 소멸자(Destructor)
- 대입 연산자(Assignment Operator)
- 동등 연산자(Equality Operator)
- 복사 할당 연산자(Copy Assignment Operator)
- ostream 연산자(’<<’) 오버로딩
- istream 연산자(’>>’) 오버로딩
- 42 과제에서는 위 규칙들 중 기본 생성자, 복사 생성자, 복사 할당 연산자, 소멸자만을 필수로 요구함
오버로딩(Overloading)
- 같은 이름을 가진 함수나 연산자가 다양한 매개변수로 다른 동작을 수행할 수 있도록 하는 기능
- 동일한 이름의 함수나 연산자를 여러번 정의할 수 있고, 함수나 연산자 호출 시에 전달되는 인자의 유형이나 개수에 따라 적절한 함수나 연산자가 선택됨
- 오버로딩은 함수 오버로딩과 연산자 오버로딩으로 나뉨
- 함수 오버로딩
- 동일한 이름을 가진 함수가 서로 다른 매개변수 목록을 가지도록 허용하는 기능
- 함수의 이름을 재사용하며, 비슷한 동작을 하는 여러 함수를 구현하는데 유용함
- int add(int a, int b) { return a + b; } int add(double a, double b) { return a + b; } int main() { add(1,2); //int add(int a, int b) 함수가 호출됨 add(1.1, 2.2); //int add(double a, double b) 함수가 호출됨 return 0; }
- 연산자 오버로딩
- C++에서 기본적으로 제공되는 연산자(+,-,* 등)에 대해 사용자가 동작을 정의할 수 있게 하는 기능
- 클래스나 구조체에 대해 기존 연산자를 재정의하여 원하는 동작을 수행할 수 있음
- class Vector { private: double x, y; public: Vector(double x, double y) : x(x), y(y) {} Vector operator+(const Vector& other) const { return Vector(x + other.x, y + other.y); } }
- 함수 오버로딩
복사 생성자(Copy Constructor)
- 다른 객체의 값을 복사하여 새로운 객체를 생성하며, 일반적으로 참조로 전달
- 복사생성자는 기본적으로 c++ 컴파일러에서 제공되며, 사용자가 별도로 정의하지 않아도 자동으로 생성됨. 디폴트 복사 생성자는 얕은 복사를 함.
- MyClass obj1; //MyClass 의 객체 obj1을 생성 MyClass obj2(obj1); //obj1을 사용하여 obj2를 복사 생성자 호출
- 복사 생성자는 기존 값을 복사해 전달하는 개념이기 때문에, 값이 변경되지 않도록 const 로 선언한다.
- class MyClass { publick: MyClass(const MyClass& other){ //객체 복사 } }
- 얕은 복사(Shallow Copy)란?
- 단순히 원본 객체의 주소를 복사 대상 객체에 할당하는 것
- 원본 객체와 복사된 객체는 동일한 메모리를 공유함
- class MyClass { public: int* data; MyClass(const MyClass& other) { // 주소만 복사되고 데이터는 공유됨 data = other.data; } };
- 깊은 복사(Deep Copy)란?
- 복사 대상 객체에 새로 메모리를 할당하여 데이터를 복사하는 것
- 원본 객체와 복사된 객체가 다른 메모리를 갖기 때문에, 변경이 발생해도 서로 영향을 주지 않음
- 복사 생성자나 대입 연산자 오버로딩을 통해 수행될 수 있음
- class MyClass { public: int* data; MyClass(const MyClass& other) { // 새로운 메모리를 할당하고 데이터를 복사함 data = new int(*other.data); } ~MyClass() { delete data; // 메모리 해제 } };
대입 연산자(Assignment Operator)
class MyClass {
public:
// 대입 연산자 오버로딩
MyClass& operator=(const MyClass& other) {
// 대입 연산자의 본문에는 객체를 복사하는 로직이 들어감
// 다른 멤버 변수들을 다른 객체에서 복사하여 할당하는 등의 작업을 수행할 수 있음
if (this != &other) { // 자기 자신에게 대입하는지 확인
// 멤버 변수 복사
}
return *this; // 대입 후 자기 자신을 반환
}
};
- 클래스의 객체에 다른 객체의 값을 할당하는 연산자(’=’)
- 클래스의 멤버 변수를 다른 객체의 멤버 변수로 복사하여, 객체 간의 데이터를 교환하거나 복사할 때 주로 사용됨
- 복사 개념이기 때문에 얕은 복사와 깊은 복사의 개념이 적용됨
동등 연산자(Equality Operator)
- 두 개의 값을 비교하고, 동일한 값이면 true를, 다른 값이면 false 를 반환하는 연산자(’==’)
- 기본 데이터 유형(정수, 실수 등) 뿐만 아니라 사용자가 정의한 클래스나 구조체에 대해서도 사용할 수 있음. 이 경우에는 동등 연산자 오버로딩을 통해 사용자가 직접 판단하는 방식을 정의해야 함
class Person
{
private:
std::string name;
int age;
public:
Person(const std::string& name, int age) : name(name), age(age) {}
bool operator==(const Person& other) const
{
return (name == other.name && age == other.age);
}
}
복사 할당 연산자(Copy Assignment Operator)
class MyString
{
private:
char *buffer;
public:
MyString(const char *str)
{
int len = 0;
while (str[len] != '\\0')
len++;
buffer = new char[len + 1];
for (int i = 0; i < len; i++)
buffer[i] = str[i];
buffer[len] = '\\0';
}
MyString& operator=(const MyStyle& other) //복사 할당 연산자
{
if (this == &other) // 자기 자신인지 확인
return *this;
delete[] buffer; // 기존 할당된 메모리 해제
int len = 0;
while (str[len] != '\\0')
len++;
buffer = new char[len + 1]; // 새 메모리 할당
for (int i = 0; i < len; i++)
buffer[i] = other.buffer[i]; // 값 복사
buffer[len] = '\\0';
return *this;
}
}
- 이미 생성된 객체에 대해 다른 객체의 값을 할당하거나, 이미 할당된 메모리 자원을 해제하고 다른 값으로 다시 할당할 때 사용하는 연산자(’=’)
- 일반적으로 반환 형식은 ‘클래스명&’ 이며, 매개변수는 ‘const 클래스명&’
- 복사 생성자와의 차이점은 호출되는 시점과 사용되는 상황에 차이가 있음.
- 복사 생성자 : 객체가 생성될 때, 복사 생성자를 호출하는 경우. 새로운 객체에 대해 기존 객체의 값으로 초기화 하기 위해 사용
- 복사 할당 연산자 : 객체가 이미 생성된 후. 다른 객체의 값으로 할당하여 복사 하기 위해 사용
ostream 연산자(’<<’) 오버로딩
class Person
{
private:
std::string name;
int age;
public:
Person(const std::string& name, int age) : name(name), age(age) {}
std::ostream& print(std::ostream& os) const
{
os << name << age;
return os;
}
}
// ostream 연산자 오버로딩
std::ostream& poerator<<(std::ostream& os, const Person& person)
{
return person.print(os);
}
- ‘<<’ 연산자는 std::ostream 객체에 대한 출력을 수행하는 연산자로, ‘<<’ 연산자를 오버로딩하여 사용자가 정의한 클래스나 구조체에 대한 출력 동작을 지정할 수 있음
- 반환 형식은 출력 스트림 객체를 참조 형식(std::ostream&)으로 반환하고, 매개변수를 출력 스트림 객체(std::ostream&)와 출력하고자 하는 사용자 정의 클래스(또는 구조체) 객체를 받음
istream 연산자(’>>’) 오버로딩
class Person
{
private:
std::string name;
int age;
public:
Person() {}
std::istream& read(std::istream& is)
{
is >> name >> age;
return is;
}
}
std::istream& operator>>(std::istream& is, Person& person)
{
return person.read(is);
}
- ‘>>’ 연산자는 std::istream 객체에서 입력을 수행하는 연산자로, ‘>>’ 연산자를 오버로딩하여 사용자가 정의한 클래스나 구조체에 대한 입력 동작을 지정할 수 있음
- 반환 형식은 입력 스트림 객체를 참조 형식(std::istream&)으로 반환하고, 매개변수를 입력 스트림 객체(std::istream&)와 입력받고자 하는 사용자 정의 클래스(또는 구조체) 객체를 받음
고정소수점
- 정수를 표현하는 비트 수와 소수를 표현하는 비트 수를 미리 정해두고, 해당 비트만 사용하여 숫자를 표현하는 방식
- 한정된 비트에 정수와 소수를 표현해야하기 때문에, 나타낼 수 있는 범위가 한정적인 문제
- 아래 처럼 32비트를 쪼개 실수를 표현
- [ 1 ][ 15 ][ 16 ]
- [sign][integer part][fractional part]
- 처음 1 비트는 sign(부호) 이며, 양수는 0 음수는 1
- 다음 15 비트를 integer(정수부)
- 다음 15 비트를 fractional(소수부)
- 정수부에 정수값을 2진수로 바꾸어 넣고, 소수부에 소수값을 2진수로 바꾸어 넣은 후, 남은 자리는 0으로 채움
부동소수점
- 소수점의 위치를 고정하지 않고, 그 위치를 나타내는 수를 따로 적는 방식으로, 유효숫자를 나타내는 가수와 소수점의 위치를 나타내는 지수로 나누어 표현함
- 부동소수점은 다양한 방식이 있고, 일반적으로는 IEEE754 를 표준으로 함
- 실수 연산이 부정확할 수 있다는 단점이 있음
ex02 구현
- subject 에서 요구하는 내용은 단순히 소수부를 8 비트로 사용하여, int 변수에 fixed-point 값을 저장하라고 한다.
- 그래서 아래처럼 단순하게 비트를 구성하는 것으로 이해하고 진행했다.
- [ 24 ][ 8 ]
- [ 정수부 ][ 소수부 ]
- 기본적인 개념으로는, 만약 10.01 이라는 2진수를 비트에 넣어야 한다면, 비트연산을 통해 8 만큼을 왼쪽으로 밀어줘야 한다. (그래야 정수부에 10이, 소수부에 01이 들어가기 때문)
- [000000000000000000000010][01000000]
- 값을 받아 저장하는 프로세스는 다음과 같다.
- 받아온 10진수 값에 8비트를 왼쪽으로 밀어준다( = 256을 곱한다 )
- 만약 소수값이라면 int에 저장할 수 없기 때문에, roundf 를 사용하여 반올림한 후 저장한다.
- ex
- 값이 10인 경우
- 10 에 256 을 곱한 2560이 int 변수에 저장됨
- 값이 10.01인 경우
- 10.01 에 256 을 곱한 2562.56 을 roundf 로 소숫점을 반올림하여, 2563이 int 변수에 저장됨
- 값이 10인 경우
- 저장한 값을 출력하는 프로세스는 다음과 같다.
- int 변수에 저장된 값에 8비트를 오른쪽으로 밀어준다( = 256을 나눈다 )
- ex
- 값이 10인 경우
- 저장된 2560 에서 256을 나눈 후 10을 출력함
- 값이 10.01인 경우
- 저장된 2563 에서 256을 나눈 후 10.0117을 출력함 (정확한 원래 값으로의 출력은 할 수 없다)
- 값이 10인 경우
- ex
- int 변수에 저장된 값에 8비트를 오른쪽으로 밀어준다( = 256을 나눈다 )
cout 소수값 출력 범위
- cout 은 소수값 출력 시, 기본적으로 6자리까지만 출력됨
- ex
- 1.234567 → 1.23456
- 12.34567 → 12.3456
- 앞의 값이 0인 경우에는 카운팅에 포함되지 않는 것 같다. '0.00390625' 이렇게도 출력됨.
- 만약 소수값이 더 출력되길 원한다면, 아래 함수를 사용하여 설정을 바꿔주면 됨
cout.precision(10);
cout << 10 / 3;
//3.333333333 출력
전위 연산자, 후위 연산자 오버로딩
- 전위 연산자 오버로딩
MyClass& operator++()
{
a += 1;
b += 1;
return *this;
}
- 후위 연산자 오버로딩
- 후위연산자 오버로딩에서는 int 를 매개변수로 받고 있는데, 이 것은 전위연산자와의 구분을 위한 값일 뿐임. 실제로 인자를 받는 것은 아님
- 후위연산자도 전위연산자와 마찬가지로 증감이 즉시 이루어진다. 다만 전위연산자는 증감된 값을 참조형으로 반환하고, 후위연산자는 증감 전의 값을 가진 임시 객체를 만들어 반환하기 때문에, 증감된 값은 이후에 사용할 수 있는 구조
MyClass operator++(int)
{
MyClass tmp(a, b);
a += 1;
b += 1;
return tmp;
}
신발끈 공식
- 삼각형의 넓이를 구하는 공식 중 하나 (다각형도 가능)
- 삼각형의 반시계 방향으로 점 P1, P2, P3 이 있을 때, 붉은 화살표로 이어진 것끼리 곱한 뒤 모두 더하고, 초록 화살표로 이어진 것끼리 곱한 뒤 뺀 후, 절댓값을 구하면 그것이 넓이가 됨
- 위 내용으로 정리한 수식
제가 공부한 내용을 기록하고 있습니다.
혹시 수정이 필요한 부분이 있다면, 댓글로 지적 부탁드립니다!
선한 관심과 도움 감사드립니다😊
반응형
'C++' 카테고리의 다른 글
[C++] 42SEOUL CPP04 개념 공부하기 (0) | 2023.07.28 |
---|---|
[C++] 42SEOUL CPP03 개념 공부하기 (0) | 2023.07.28 |
[C++] 42SEOUL CPP01 개념, 함수 공부하기 (0) | 2023.07.28 |