4.1 캐스팅
4.1-1 캐스팅이란,
Def) 캐스팅(Casting)
형변환. 자료형 A에서 자료형 B로 변환하는 것을 의미.
Def) 암시적 캐스팅(Implicit Casting)
컴파일러가 자료형을 자동으로 변환해주는 것.
단, 형변환이 허용되고 프로그래머가 명시적으로
형변환을 안할 경우에만 해당됨.
ex. int num1 = 3;
long num2 = num1; // 암시적 캐스팅
Def) 명시적 캐스팅(Explicit Casting)
프로그래머가 자료형 변환을 위한 코드를 직접 작성한 것.
C++에는 4가지 명시적 캐스팅이 존재함.
이를 C++ 스타일 캐스팅이라고 함.
- static_cast<자료형>
- reinterpret_cast<자료형>
- const_cast<자료형>
- dynamic_cast<자료형>
4.1-2 C 스타일 캐스팅
Note) C에서는 아래와 같이 코드를 작성했었음.
내부적으로는 C++ 스타일 캐스팅 4가지 중 하나가 진행됨.
즉, 뭔가 명확하지 않음.
int num1 = 3;
long num2 = (long)num1;
Note) C 스타일 캐스팅은 명백한 실수를 컴파일러가 잡지 못함.
C++ 스타일 캐스팅이 이런 문제를 해결함.
다만, 기계는 C와 C++을 구분하지 못함.
C에 있는 것들이 기계가 좀 더 가까움.
Note) C 스타일 캐스팅 Vs. static_cast<자료형>
아래 C 스타일 캐스팅 코드들은 정적 캐스팅과 같음.
즉, 컴파일 과정에서 모든게 결정됨.
각 괄호는 나중에 템플릿 프로그래밍에서 배움.
또한, static_cast는 값 캐스팅인지 개체 포인터 캐스팅인지가 중요함.
float f = 3.f;
int num = (int)f; // == static_cast<int>(f); [값 캐스팅]
Animal* myPet = new Cat(2, "Coco");
Cat* myCat = (Cat*)myPet; // == static_cast<Cat*>(myPet); [개체 포인터 캐스팅]
Note) C 스타일 캐스팅 Vs. reinterpret_cast<자료형>
아래 C 스타일 캐스팅 코드는 reinterpret_cast와 같음.
Animal* myPet = new Cat(2, "Coco");
unsigned int myPetAddress = (unsigned int)myPet; // == reinterpret_cast<unsigned int>(myPet);
// myPet이 포인터이므로, 저장된 주소값을 화면에 출력하고자 unsigned int형으로 반환하고픔.
cout << "Address: " << hex << myPetAddress << endl;
// 단, unsigned integer가 pointer의 크기보다 크다는 보장은 없음.
// 대부분의 컴퓨터에서 이렇게 돌고있을 뿐임.(즉, 포터블 하지 않다는 것.)
// C++14/C++17에서 포인터 출력에 쓰이는 자료형이 나오긴 함.
// 플랫폼에 따라 32bit / 64bit / ... 로 변함.
Note) C 스타일 캐스팅 Vs. const_cast<자료형>
void Foo(const Animal* ptr)
{
Animal* ptrToAnimal = (Animal*)ptr; // == const_cast<Animal*>(ptr);
ptrToAnimal->SetAge(5);
// 함수의 시그니처를 무시했기 때문에 굉장히 나쁜코드.
}
Note) C 스타일 캐스팅 Vs. dynamic_cast<자료형>
근데, 아까보았던 static_cast와 똑같음.
static_cast는 컴파일 중에 결정되고,
dynamic_cast는 런타임 중에 결정된다고 생각하자.
자세한 설명은 뒤에서 다시 함.
Cat* myCat = (Cat*)myPet; // == dynamic_cast<Cat*>(myPet);
4.1-3 static_cast<자료형>
Def) static_cast<자료형>(변수명)
값 캐스팅: 값을 유지하기 위해 노력하는 캐스팅.
두 primitive type 간의 변환. 값을 유지(단, 반올림 오차 제외)하고
이진수 표기가 달라질 순 있음. ex. 3.5f가 3이 될 때
int num1 = 3; // 0000 0000 0000 0011
short num2 = static_cast<short>(num1); // 0000 0011
float f = 3.f; // 0100 0000 0100 0000
int num3 = static_cast<int>(f); // 0000 0000 0000 0011
개체 포인터 캐스팅: 자료형 체크 후 부모 클래스간의 자식 클래스 캐스팅
컴파일 시에만 자료형 체크 가능. 런타임 도중엔 여전히 크래시 날 수 있음.
실체는 자식 클래스지만, 무늬는 부모 클래스로 저장되어 있는 상황에서
자식 클래스의 다형적이지 않은 메서드를 호출하고 싶을때 사용.
Animal* myPet = new Cat(2, "Coco");
Cat* myCat = static_cast<Cat*>(myPet);
Dog* myDog = static_cast<Dog*>(myPet);
myDog->GetDogHouse();
// 컴파일은 됨. 그러나 위험함.
// myDog의 실체는 Cat이라, Dog 클래스의 House 멤버를
// 가지고 있지 않아서 크래시가 날 수도 있음.
// 또는 GetDogHouse() 멤버 함수가 Dog 클래스의 가상 테이블에 3번째 멤버 함수라면
// Cat 클래스의 가상 테이블 3번째 멤버 함수가 실행될 수도 있음.
House* myHouse = static_cast<House*>(myPet);
// 컴파일 에러. 자료형 체크(부모 클래스와 자식 클래스 관계인지)에서 실패함.
Def) 업 캐스팅과 다운 캐스팅
자식 클래스에서 부모 클래스로의 static_cast<>를 Up-casting,
부모 클래스에서 자식 클래스로의 static_cast<>를 Down-casting이라 함.
Ex04010301)
<hide/>
// main.cpp
#include <iostream>
#include <iomanip>
using namespace std;
int main()
{
const int num = 10;
cout << showbase << hex << uppercase;
const float f = static_cast<float>(num);
cout << "int: " << num << endl
<< "float: " << *(int*)(&f) << endl;
const short s = static_cast<short>(num);
cout << "int: " << num << endl
<< "short: " << s << endl;
return 0;
}
Ex04010302)
<hide/>
// Animal.h
#pragma once
class Animal
{
public:
Animal();
Animal(const int& age);
virtual ~Animal();
private:
int mAge;
};
<hide/>
// Animal.cpp
#include <iostream>
#include "Animal.h"
using namespace std;
Animal::Animal()
: mAge(0)
{
}
Animal::Animal(const int& age)
: mAge(age)
{
}
Animal::~Animal()
{
}
<hide/>
// Cat.h
#pragma once
#include "Animal.h"
class Cat : public Animal
{
public:
Cat();
Cat(const int& age, const char* name);
virtual ~Cat();
const char* GetName() const;
private:
char* mName;
};
<hide/>
// Cat.cpp
#include <iostream>
#include <cstring>
#include "Cat.h"
using namespace std;
Cat::Cat()
: Animal(0)
, mName(nullptr)
{
}
Cat::Cat(const int& age, const char* name)
: Animal(age)
, mName(nullptr)
{
size_t length = strlen(name) + 1;
mName = new char[length];
memcpy(mName, name, length);
}
Cat::~Cat()
{
delete[] mName;
mName = nullptr;
}
const char* Cat::GetName() const
{
return mName;
}
<hide/>
// Dog.h
#pragma once
#include "Animal.h"
class Dog : public Animal
{
public:
Dog();
Dog(const int& age, const char* name);
virtual ~Dog();
const char* GetHomeAddress() const;
private:
char* mHomeAddress;
};
<hide/>
// Dog.cpp
#include <iostream>
#include <cstring>
#include "Dog.h"
using namespace std;
Dog::Dog()
: Animal(0)
, mHomeAddress(nullptr)
{
}
Dog::Dog(const int& age, const char* name)
: Animal(age)
, mHomeAddress(nullptr)
{
size_t length = strlen(name) + 1;
mHomeAddress = new char[length];
memcpy(mHomeAddress, name, length);
}
Dog::~Dog()
{
delete[] mHomeAddress;
mHomeAddress = nullptr;
}
const char* Dog::GetHomeAddress() const
{
return mHomeAddress;
}
<hide/>
// main.cpp
#include <iostream>
#include <iomanip>
#include "Animal.h"
#include "Cat.h"
#include "Dog.h"
using namespace std;
int main()
{
Animal* pet1 = new Cat(2, "Navi");
Animal* pet2 = new Cat(2, "Soul");
Cat* cat = static_cast<Cat*>(pet1);
Dog* dog = static_cast<Dog*>(pet2);
Dog* catDog = static_cast<Dog*>(pet1);
cout << "cat's name: " << cat->GetName() << endl;
cout << "dog's house address: " << dog->GetHomeAddress() << endl;
cout << "catDog's house address: " << catDog->GetHomeAddress() << endl;
delete pet1;
delete pet2; // 왜 cat, dog, catDog은 해제 안해도 될까?
return 0;
}
4.1-4 reinterpret_cast<자료형>
Def) reinterpret_cast<자료형>(변수명)
연관 없는 두 포인터 자료형 사이의 변환을 허용함.
포인터와 포인터 아닌 자료형 사이의 형변환도 허용.
단, 이진수 표기가 달라지지 않는 것이 핵심.
이진수 데이터를 A 자료형이 아닌 B 자료형으로 변환.
int* signedNum = new int(-10);
unsigned int* unsignedNum = reinterpret_cast<unsigned int*>(signedNum);
// 같은 이진수 데이터지만, 하나는 -10으로 다른 하나는 4294967286으로 해석됨.
delete signedNum;
Note) reinterpret_cast<자료형>(변수명)은 상당히 위험함.
그래서 static_cast<자료형>(변수명)가 나왔다고 말해도 과언이 아님.
다만, 꼭 필요한 때가 있긴 함.
Note) static_cast Vs. reinterpret_cast
int* signedNum = new int(-10);
unsigned int* unsignedNum1 = reinterpret_cast<unsigned int*>(signedNum);
// 컴파일 성공. 허나, 이진수 데이터는 더이상 -10으로 해석되지 않음.
unsigned int* unsignedNum2 = static_cast<unsigned int*>(signedNum);
// 컴파일 에러. 유효하지 않는 형변환이기 때문.
delete signedNum;
Ex04010401)
<hide/>
// Animal.h
#pragma once
class Animal
{
public:
Animal();
Animal(const int& age);
virtual ~Animal();
void Speak();
private:
int mAge;
};
<hide/>
// Animal.cpp
#include <iostream>
#include "Animal.h"
using namespace std;
Animal::Animal()
: mAge(0)
{
}
Animal::Animal(const int& age)
: mAge(age)
{
}
Animal::~Animal()
{
}
void Animal::Speak()
{
cout << "Animal is now speaking." << endl;
}
<hide/>
// main.cpp
#include <iostream>
#include <iomanip>
#include "Animal.h"
using namespace std;
int main()
{
Animal* pet1 = new Animal(2);
pet1->Speak();
unsigned int pet1Address = reinterpret_cast<unsigned int>(pet1);
cout << showbase << hex << "32bit: " << pet1Address << endl;
cout << "64bit: " << pet1 << endl;
delete pet1;
return 0;
}
4.1-5 const_cast<자료형>
Def) const_cast<자료형>(변수명)
const 또는 volatile attribute를 제거할 때 사용.
단, const_cast로 자료형을 바꾸는건 불가능.
Animal* myPet = new Cat(2, "Coco");
const Animal* petPtr = myPet;
// ... 234줄 ...
Animal* myAnimal1 = (Animal*)petPtr;
// 대부분의 프로그래머들은 const를 지우기 위해 형변환 하진 않음.
// 대부분 이런식으로 몇백줄 뒤라 const를 못 봄.
Animal* myAnimal2 = (Animal*)petPtr;
// 그래서 이렇게 petPtr이 Cat*인 줄 알고, Animal*로 바꾸려하는 사례가 더 많음.
// 따라서, C 스타일 캐스팅은 지양해야함. C++ 스타일 캐스팅을 쓰자.
Animal* myAnimal3 = const_cast<Animal*>(petPtr);
// 컴파일 성공. 자료형 변환이 아니고, const attribute를 지우기 위함이기 때문.
Cat* myCat = const_cast<Cat*>(petPtr);
// 컴파일 실패. 자료형 변환을 할거면 static_cast를 해야함.
// 어찌되었든, C++ 스타일 캐스팅이 프로그래머의 의도를 명백히 보여줄 수 있음.
Note) 포인터 자료형에 사용할 때만 유효함.
값 자료형에는 언제나 복사되기 때문에 딱히 의미 없음.
Note) 하지만, const_cast<>를 쓰려고 하는
대부분의 경우에는 무언가 잘못되고 있는 것.
주의해서 사용해야하고, 그만큼 쓰이는 곳이 흔하지 않음.
Note) const_cast<>가 올바르게 쓰이는 경우
제 3자가 만든 라이브러리의 변수에 const가 잘못 걸린 경우.
void WriteLine(char* ptr); // 뭔가 별로인 외부 라이브러리
void OurWriteLine(const char* ptr) // 우리 프로그램에서 새로 만든 wrapper 함수.
{
WriteLine(const_cast<char*>(ptr));
}
4.1-6 dynamic_cast<자료형>
Def) dynamic_cast<자료형>(변수명)
주로 런타임 중에 안전하게 다운 캐스팅하기 위해서 쓰임.
반드시 부모 클래스에 가상 함수가 하나 이상 존재해야 함.
왜냐하면 이걸 근거로 다운 캐스팅이 진행되기 때문.
Note) 그러나 dynamic_cast<>를 쓰려면 컴파일 중에
RTTI(Real-Time Type Information, 실시간 자료형 정보)를 켜야함.
C++ 프로젝트는 대부분 성능을 중요시 하기 때문에 끄는 것이 보통.
동일한 이유로 예외도 꺼버림. 만약 예외 처리가 배우고 싶다면
POCU 아카데미에서 풀코스를 수강하면 좋을듯. 자세히 가르쳐줌.
Note) 즉, 실제로는 RTTI가 안켜져 있기에 dynamic_cast와
static_cast는 동일하게 동작하게 됨.
RTTI가 켜져 있다면 다름. RTTI가 켜지면 성능 저하가 심함.
4.1-7 캐스팅 규칙
Note) C++의 고질적인 문제점은 복잡할 정도로 기능이 많음.
그래서 제대로된 규칙, 즉 베스트 프렉티스를 정해야만 함.
규칙은 제일 안전한 것에서부터 가장 위험한 것 순으로 사용하는 것.
Note) 기본적으로는 static_cast를 사용하자.
만약 A 클래스와 B 클래스가 상속 관계가 아니라면 컴파일 에러.
Note) 꼭 필요한 경우에만 reinterpret_cast와 const_cast를 쓰자.
포인터와 비 포인터 사이의 변환의 경우,
꼭 reinterpret_cast가 필요할 때가 있음. 이때 사용.
즉, 서로 연관 없는 자료형 사이의 변환은
정말 확신할 때만 사용하도록 하자.
const_cast는 외부 라이브러리가
덜 안전하게 작성되었을 때 사용하도록 하자.
Note) RTTI가 안켜져 있다면, dynamic_cast는 없는셈 치자.
static_cast와 다를바 없기 때문.
Note) 위 캐스팅 규칙과 함께, C++ 스타일 캐스팅을 사용하자.
더이상 모호한 C 스타일 캐스팅은 사용하지 말자.
4.2 inline / static / extern
4.2-1 인라인 함수
Note) 함수도 메모리 어딘가에 "할당"되어 있음.
함수를 호출하기 위해 필요한 단계는 아래와 같음.
1. 변수들을 스택 메모리에 푸시
2. 함수 주소로 점프
3. 함수 코드 실행
4. 호출자 함수로 다시 점프
5. 1번 단계에서 넣어뒀던 변수들을 팝
위 단계는 물론 calling convention에 따라 달라질 수 있음.
Note) 함수 호출에 너무 많은 비용이 들어감.
게다가 CPU 캐시에 최적이 아닐수도 있음.
ex. callee는 cache 메모리에 있고, caller는 hdd에 있다면?
모던 CPU 아키텍쳐에서는 이런게 더 영향을 줌.
Note) "모든걸 함수로 만들자."
그런데 위와 같은 잘못된 조언이 떠돌아 다님.
그냥 cout하면 되는데, 굳이 wrapper 함수를 만들어서 실행할 정도.
이러면 남도 해당 wrapper 함수를 쓸 수 있으므로
유지보수면에서도 안좋음.
Note) 물론 연산자 오버로딩 보다는 함수 작성이 나음.
하지만 함수 호출에 필요한 오버헤드는 떠안기에 부담스러움.
이럴때의 해법이 바로 인라인 함수.
Def) 인라인 함수(Inline Function)
inline 키워드가 달린 함수.
코드의 가독성과 성능 두 마리 토끼를 잡은 문법.
inline int GetAge() { return mAge; }
Note) 사실상 복붙과 비슷함. 함수 호출 대신 코드가 복붙됨.
이는 매크로 함수와 매우 비슷함.
그럼 매크로 함수는 왜있을까? inline을 굳이 만들 필요가?
Note) 매크로 함수는 디버깅이 힘들다.
인라인 함수는 디버깅이 수월함.
call stack에 함수명도 보이고, 중단점도 걸 수 있음.
게다가 스코프도 준수하기 때문에 무조건 전역 함수가 아님.
매크로 함수는 스코프를 준수하지 않아서 그냥 전역 함수.
그러니, 정말 정말 매크로 함수가 필요한게 아니면 인라인 함수 쓰자.
Note) 인라인 함수의 두 가지 주의점
1. inline 키워드는 컴파일러에게 주는 "힌트"
즉, 실제로는 인라인이 안될 수도 있음.
오히려 컴파일러는 우리가 인라인 지정하지 않은 함수도
인라인 함수로 최적화 할 수도 있음.
2. 인라인 함수 구현이 헤더 파일에 위치해야 함.
복붙 하려면 컴파일 단계에서 그 구현체가 보여야 하기 때문.
다른 함수처럼 소스 파일(cpp)에 구현 하면 안보임.
3. 실행 파일의 크기가 증가하기 쉬움.
동일한 코드를 여러 번 복붙하기 때문.
그래서 남용하면 안됨. 실행 파일이 작을 수록
CPU 캐시와 잘 동작함. 즉 속도가 빨라질 수 있음.
Note) __forceinline
__는 표준에서 지원하는 기능이 아니란 의미.
inline 보다 조금 더 inline 될 확률이 높음.
Note) 간단한 함수는 인라인으로 작성하자.
특히 getter나 setter.
Ex04020101)
<hide/>
// Animal.h
#pragma once
class Animal
{
public:
Animal();
Animal(const int& age);
virtual ~Animal();
inline int GetAge() { return mAge; }
private:
int mAge;
};
<hide/>
// Animal.cpp
#include <iostream>
#include "Animal.h"
using namespace std;
Animal::Animal()
: mAge(0)
{
}
Animal::Animal(const int& age)
: mAge(age)
{
}
Animal::~Animal()
{
}
<hide/>
// main.cpp
#include <iostream>
#include <iomanip>
#include "Animal.h"
using namespace std;
int main()
{
Animal* pet = new Animal(2);
cout << "pet's age: " << pet->GetAge() << endl;
delete pet;
return 0;
}
4.2-2 static
Def) static 키워드
스코프에 따라 접근이 제한되는 전역 변수.
여기서 스코프란, 파일 / 네임스페이스 / 클래스 / 함수
파일만 빼고 나머지는 중괄호로 스코프를 표시함.
Note) 만약 파일 스코프에 정적 변수가 선언되었다면,
해당 파일에서만 정적 변수를 사용할 수 있음.
근데 이에 반대되는 개념도 있음. extern 키워드.
extern 키워드는 다른 파일의 전역 변수를 가져와서
쓸 수 있게끔 하는 키워드.
Note) static 키워드가 사용되는 곳.
- 멤버 함수의 스코프 속 정적 변수
- 전역 함수의 스코프 속 정적 변수
- 정적 멤버
- 정적 멤버 함수
Note) 함수의 스코프 속 정적 변수
해당 스코프에서만 접근 가능한 전역 변수가 됨.
함수가 반환되면 해당 전역 변수는 접근 불가.
다만 전역 변수이기에, 값은 유지됨.
Def) 정적 멤버
클래스당 하나의 사본만 존재함.
개체 메모리 레이아웃의 일부가 아님.
즉, 개체마다 사본이 존재하는게 아님.
클래스 메모리 레이아웃에 포함되어 있음.
또한 전역 변수처럼 미리 만들어져서 .exe 파일안에
필요한 메모리가 잡혀 있음. 오직 하나라서 컴파일러가 앎.
Note) 정적 멤버는 반드시 .cpp 파일에서 초기화 해주어야 함.
즉, .h 파일에는 해당 정적 멤버의 선언만 있음.
컴파일 단계에서 '어디 파일에 있을거다'라는 힌트정도.
Note) 정적 멤버 베스트 프렉티스
1. 함수 안에 정적 변수를 넣지 말자.
OOP 기준. 클래스 안에다가 넣어도 동일하므로
함수 속 대신에 클래스 속에 넣어두자.
함수 구현체를 안보고 헤더 파일만 봐도
정적 변수를 볼 수 있기 때문임.
2. 전역 변수 대신 정적 멤버를 쓰자.
전역 변수를 쓰고 싶다면 public을 붙히는 식으로 작성.
스코프를 제한해서 유지보수를 쉽게 하기 위함.
3. 위에 따라, C 스타일의 정적 변수를 쓰지 말자.
다만, 컴파일 속도 빠르게 하고 싶다고
헤더 파일 손대지 않고 C 스타일 정적 변수를 쓸수도.
원칙적으로는 클래스 안에 정적 멤버를 선언하는게 맞다.
Def) 정적 멤버 함수
해당 클래스의 정적 멤버에만 접근 가능.
클래스의 개체가 없어도 정적 멤버 함수를 호출할 수 있음.
Note) 비 정적 메서드에서 정적 멤버에 접근할때 주의하자.
정적 메서드에서 비정적 멤버에 접근은 불가능함.
다만 그 반대의 경우에서 문제가 생길 수 있음.
예로들어, 유저의 ID를 저장하는 정적 멤버가 있다고 해보자.
유저 A가 자신의 사진첩을 보던 중에, 유저 B가 동시 접속.
정적 멤버가 유저 B의 ID로 업데이트 됨.
유저 A는 모종의 이유로 새로고침하였는데, 갑자기 유저 B의
사진들로 사진첩이 채워져 버림. 엄청난 사생활 침해. 대참사.
Ex04020201)
<hide/>
// Cat.h
#pragma once
#include "Animal.h"
class Cat : public Animal
{
public:
Cat();
Cat(const int& age, const char* name);
virtual ~Cat();
static const char* GetAnimalType();
private:
static const char* mAnimalType;
char* mName;
};
<hide/>
// Cat.cpp
#include <iostream>
#include <cstring>
#include "Cat.h"
using namespace std;
const char* Cat::mAnimalType = "Cat";
Cat::Cat()
: Animal(0)
, mName(nullptr)
{
}
Cat::Cat(const int& age, const char* name)
: Animal(age)
, mName(nullptr)
{
size_t length = strlen(name) + 1;
mName = new char[length];
memcpy(mName, name, length);
}
Cat::~Cat()
{
delete[] mName;
mName = nullptr;
}
const char* Cat::GetAnimalType()
{
return mAnimalType;
}
<hide/>
// main.cpp
#include <iostream>
#include <iomanip>
#include "Animal.h"
#include "Cat.h"
using namespace std;
int main()
{
Cat* myCat1 = new Cat(1, "Cho");
Cat* myCat2 = new Cat(2, "Nana");
Cat* myCat3 = new Cat(3, "Star");
cout << "myCat1's type: " << myCat1->GetAnimalType() << endl;
cout << "myCat2's type: " << myCat2->GetAnimalType() << endl;
cout << "myCat3's type: " << myCat3->GetAnimalType() << endl;
cout << "Cat's type: " << Cat::GetAnimalType() << endl;
delete myCat1;
delete myCat2;
delete myCat3;
return 0;
}
Ex04020202)
<hide/>
// Math.h
#pragma once
class Math
{
public:
static int ceil(const float& value);
static int floor(const float& value);
static int round(const float& value);
private:
Math() {};
};
<hide/>
// Math.cpp
#include "Math.h"
int Math::ceil(const float& value)
{
int intValue = static_cast<int>(value);
if (value == static_cast<float>(intValue)) { return intValue; }
return intValue + 1;
}
int Math::floor(const float& value)
{
return static_cast<int>(value);
}
int Math::round(const float& value)
{
return static_cast<int>(value + 0.5f);
}
<hide/>
// main.cpp
#include <iostream>
#include "Math.h"
using namespace std;
int main()
{
cout << Math::ceil(0.5) << endl;
cout << Math::ceil(3.2) << endl;
cout << Math::floor(0.5) << endl;
cout << Math::round(0.3) << endl;
return 0;
}
'C++ > 문법 정리' 카테고리의 다른 글
Chapter 06. C++11/14/17 (0) | 2022.05.08 |
---|---|
Chapter 05. 템플릿과 파일 시스템 (0) | 2022.05.07 |
Chapter 03. 상속과 다형성 (0) | 2022.05.05 |
Chapter 02. 클래스 (0) | 2022.05.01 |
Chapter 01. 입출력 기초 (0) | 2022.04.28 |
댓글