algorithm
자료구조 : 데이터를 저장하는 구조
알고리즘 : 데이터를 어떻게 사용할 것인가?
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main()
{
srand(static_cast<unsigned int>(time(nullptr)));
vector<int> v;
for (int i = 0; i < 100; i++)
{
int num = rand() % 100;
v.push_back(num);
}
// find()
{
int number = 50;
vector<int>::iterator itFind = find(v.begin(), v.end(), number);
if (itFind == v.end())
cout << "not found" << endl;
else
cout << "found" << endl;
}
// find_it()
{
struct CanDivideBy11
{
bool operator()(int n)
{
return (n % 11) == 0;
}
};
vector<int>::iterator itFind = find_if(v.begin(), v.end(), CanDivideBy11());
if (itFind == v.end())
cout << "not found" << endl;
else
cout << "found" << endl;
}
// count_if()
{
struct isOdd
{
bool operator()(int n)
{
return (n % 2) != 0;
}
};
int n = count_if(v.begin(), v.end(), isOdd());
cout << n << endl;
// all_of() / any_of() / none_of()
bool b1 = all_of(v.begin(), v.end(), isOdd());
bool b2 = any_of(v.begin(), v.end(), isOdd());
bool b3 = none_of(v.begin(), v.end(), isOdd());
cout << b1 << "\t" << b2 << "\t" << b3 << endl;
}
// for_each()
{
struct MultiplyBy3
{
void operator()(int& n)
{
n = n * 3;
}
};
for_each(v.begin(), v.end(), MultiplyBy3());
}
// remove() / remove_if()
{
struct isOdd
{
bool operator()(int n)
{
return (n % 2) != 0;
}
};
vector<int>::iterator it = remove_if(v.begin(), v.end(), isOdd());
// 1 4 3 5 8 2 => 4 8 2 5 8 2
v.erase(it, v.end());
//v.erase(remove_if(v.begin(), v.end(), isOdd());
}
return 0;
}
auto
Modern C++ (C++11)부터 사용된 문법
형식 연역(type deduction)이라고도 불림
추론 규칙은 생각보다 복잡할 수 있음
int main()
{
int& reference = a;
const int cst = b;
auto test1 = reference;
auto test2 = cst;
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
for (vector<int>::size_type i = 0; i < v.size(); i++)
{
int& data = v[i];
//auto& data = v[i];
data = 100;
}
return 0;
}
기본 auto
는 const
와 &
를 무시함
가독성이 저하되기 때문에 auto
만 사용하는 것은 권장하지 않음
단, 타이핑이 길어지는 경우에는 사용함
중괄호 초기화 {}
int main()
{
int a = 0;
int b(0);
int c{0};
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
vector<int> v2(10, 1);
vector<int> v3{1, 2, 3, 4};
return 0;
}
중괄호 초기화는 vector
등의 컨테이너와 잘 어울림
축소 변환을 방지할 수 있음(축소 변환시 에러 발생)
함수 선언과 변수 초기화를 명확히 구분할 수 있음
여러개를 초기화할시 initializer_list<>
생성자를 사용함
이는 다른 생성자보다 우선권이 높기 때문에 문제가 생길 수 있음
nullptr
기존에는 0과 NULL
을 사용
void Test(int a)
{
...
}
void Test(void* ptr)
{
...
}
int main()
{
Test(0);
Test(NULL);
Test(nullptr);
}
함수가 오버로딩 되어있을 경우 0이나 NULL
을 사용하면 포인터를 활용할 때 문제가 생길 수 있음
즉, 위와 같은 경우 Test(0)
과 Test(NULL)
은 모두 int
형을 매개변수로 하는 버전으로 작동함
반면 nullptr
은 명확히 포인터 타입을 가리키기 때문에 Test(nullptr)
은 void*
형을 매개변수로 하는 버전으로 작동함
또한 가독성 측면에서도 nullptr
의 사용은 필수적임
class NullPtr
{
public:
//어떤 타입의 포인터와도 치환 가능
template<typename T>
operator T* () const
{
return 0;
}
// 어떤 타입의 멤버 포인터와도 치환 가능
template<typename C, typename T>
operator T C::* () const
{
return 0;
}
private:
// 주소값 &을 막음
vodi operator&() const;
};
using
typedef vector<int>::iterator Vecit;
typedef int id;
using id2 = int;
typedef void(*MyFunc)();
using MyFunc2 = void(*)();
template<typename T>
typedef std::vector<T> List; // 불가능함
template<typename T>
using List2 = std::list<T> // 가능함
using
이 typedef
보다 직관성이 좋음using
은 typedef
와는 달리 템플릿을 이용할 수 있음
기본적으로 typedef
보다 using
이 모든 면에서 뛰어남
enum class
enum PlayerType
{
PT_Knight,
PT_Archer,
PT_Mage
};
double value = PT_Knight;
enum
의 경우 unscoped enum
으로 enum
에 설정되어있는 이름은 전역 사용 범위를 가지고있음
암묵적인 변환이 가능함
enum class ObjectType
{
Player,
Monster,
Projectile
};
double value = ObjectType::Plyaer; // 불가능
enum class
의 경우 scoped enum
으로 설정되어있는 이름이 영역 안에서만 유효함
암묵적인 변환이 불가능함
delete (삭제된 함수)
class Knight
{
public:
private:
void operator=(const Knight* k); // 사용은 가능하나 문제가있음
void operator=(const Knight& k) = delete;
private:
int _hp = 100;
}
기존에는 사용하지 않는 함수를 private
에 정의되지 않은 함수로 만들어줌
그러나 private
에 정의하더라도 꺼내서 쓸 수 있는 경우가 발생할 수 있음private
에 선언한 함수의 구현부를 만들지 않을 시에는 함수의 사용을 방지할 수 있으나, 문제를 조기에 발견할 수 없음
함수 뒤에 = delete
를 붙여줄 경우 더이상 사용하지 않을 함수라는 것을 확실하게 나타낼 수 있음
override, final
class Player
{
public:
virtual void Attack()
{
...
}
};
class Knight : public Player
{
public:
// 재정의(override)
virtual void Attack() override
{
...
}
// 오버로딩(overloading) : 함수 이름 재사용
void Attack(int a)
{
...
}
};
int main()
{
Knight* knight = new Knight();
knight->Attack();
Player* player = new Knight();
player->Attack();
return 0;
}
virtual
로 지정한 함수의 최초 사용이 어디였는지를 알 수 없음
따라서 재정의한 함수라는 것을 나타태기 위해 함수 뒤에 override
키워드를 붙여 사용할 수 있음
마지막으로 사용될 함수의 경우 final
을 붙여 더이상 재정의되지 않도록 만들 수 있음
오른값 참조(rvalue reference)
lvalue(왼값) : 단일식을 넘어서 계속 지속되는 개체
rvalue(오른값) : lavlue가 아닌 나머지 (임시 값, 열거형, 람다, i++ 등)
class Pet
{
};
class Knight
{
public:
Knight()
{
cout << "Knight()" << endl;
}
Knight(const Kngiht& knight)
{
cout << "const Knight&" << endl;
}
~Knight()
{
if (_pet)
delete _pet;
}
// 이동 생성자
Knight(Knight&& knight)
{
}
void operator=(const Knight& knight)
{
cout << "operator=(const Knight&)" << endl;
_hp = knight._hp;
if (knight._pet)
_pet = new Pet(*knight._pet);
}
// 이동 대입 연산자
void operator=(Knight&& knight) noexcept
{
cout << "operator(Knight&&)" << endl;
_hp = knight._hp;
_pet = knight._pet;
knight._pet = nullptr;
}
public:
int _hp = 100;
Pet* _pet = nullptr;
};
void TestKnight_Copy(Knight knight) {}
void TestKnight_LValueRef(Knight& knight) {}
void TestKnight_ConstLValueRef(const Knight& knight) {}
void TestKnight_RValueRef(Knight&& knight) {} // 이동 대상
int main()
{
Knight k1;
TestKnight_Copy(k1);
TestKnight_LValueRef(k1);
TestKnight_LValueRef(Knight()); // Knight()로 생성된 객체는 Lvalue가 아니므로 불가능
TestKnight_ConstLValueRef(Knight()); // const가 붙은 rvalue는 가능
TestKnight_RValueRef(k1); // lvalue는 불가능
TestKnight_RvalueRef(Knight()); // rvalue는 가능
TestKnight_RvalueRef(static_cast<Knight&&>(k1));
Knight k2;
k2._pet = new Pet();
k2._hp = 1000;
Knight k3;
k3 = static_cast<Kngiht&&>(k2);
k3 = std::move(k2); // 오른값 참조로 캐스팅, 이름 후보중 하나가 rvalue_cast
std::unique_ptr<Knight> uptr = std::make_unique<Knight>();
std::unique_ptr<Knight> uptr2 = std::move(uptr);
return 0;
}
rvalue로 참조할시 원본 데이터가 유지되지 않아도 됨
rvalue로 참조하는 것은 원본은 날려도 된다는 힌트를 주는 쪽에 가까움
따라서 이동시 얕은 복사도 사용 가능
전달 참조(forwarding reference)
원래는 보편 참조(universal reference) 라는 이름을 가지고있었음
class Knight
{
public:
Knight() { cout << "기본 생성자" << endl; }
Knight(const Knight&) { cout << "복사 생성자" << endl; }
Knight(Knight&&) noexcept { cout << "이동 생성자" << endl; }
}
void TestRValueRef(Knight&& k)
{
}
void Test_Copy(Knight k)
{
}
template<typename T>
void Test_ForwardingRef(T&& param)
{
// 왼값 참조일 경우 복사
// 오른값 참조일 경우 이동
Test_Copy(std::forward<T>(param));
}
int main()
{
Knight k1;
Test_RvalueRef(std::move(k1)); // rvalue_cast
test_ForwardingRef(k1); // lvalue
test_ForwardingRef(std::move(k1)); // rvalue
auto&& k2 = k1; // lvalue
auto&& k3 = std::move(k1); // rvalue
Knight k;
Knight& k4 = k; // lvalue
Knight&& k5 = std::move(k); // rvalue
Test_RvalueRef(k5); // 오른값 참조를 참조 - 불가능
Test_RvalueRef(std::move(k5)); // 오른값 참조 - 가능
}
전달 참조는 형식 연역(type deduction)이 일어날때만 발생lvalue
를 넣어주면 lvalue
참조, rvalue
를 넣어주면 rvalue
참조함
함수의 케이스를 줄일 수 있어 편리함
단, 이 때 lvalue
와 rvalue
둘 중 어느 것을 받더라도 같은 동작을 해야함
오른값 : 왼값이 아님 = 단일식에서 벗어나면 사용할 수 없음
오른값 참조 : 오른값만 참조할 수 있는 참조 타입
람다(lambda)
함수 객체를 빠르게 만드는 문법
람다 자체가 C++11의 새로운 기능은 아님
enum class ItmeType
{
None,
Armor,
Weapon,
Jewelry,
Consumable
};
enum class Rarity
{
Common,
Rare,
Unique
};
class Item
{
public:
Item() {}
Item(int itemid, Rarity rarity, ItemType type)
: _itemid(itemid), _rarity(rarity), _type(type) {}
public:
int _itemid = 0;
Rarity _rarity = Rarity::Common;
ItemType _type = ItemType::None;
};
int main()
{
vector<Item> v;
v.push_back(Item(1, Rarity::Common, ItemType::Weapon));
v.push_back(Item(2, Rarity::Common, ItemType::Armor));
v.push_back(Item(3, Rarity::Rare, ItemType::Jewelry));
v.push_back(Item(4, Rarity::Unique, ItemType::Weapon));
{
struct IsUniqueItem
{
FindItemByItemid(int itemid) : _itemid(itemid)
{
}
bool operator()(Item& item)
{
return item._rarity == Rarity::Unique;
}
int _itemid;
};
auto findit = std::find_if(v.begin(), v.end(), IsUniqueItem());
int itemid = 4;
auto findit = std::find_if(v.begin(), v.end(), FindItemByItemid(itemid);
if (findit != v.end())
cout << "아이템 ID : " << findit->_itmeid << endl;
}
{
// 람다 표현식(lambda expression)
// 클로저 (closure) : 람다에 의해 만들어진 실행시점 객체
auto isUniqueLambda = [](Item& item)
{
return item._rarity == Rarity::Unique;
};
auto findit = std::find_if(v.begin(), v.end(),
[](Item& item) { return item._rarity == Rarity::Unique; });
auto findByItemidLambda = [=](Item& item) { return Item.itemid == _itemid; };
auto findit = std::find_it(v.begin(), v.end(), findByItemidLambda);
if (findit != v.end())
cout << "아이템 ID : " << findit->_itmeid << endl;
}
{
class Knight
{
public:
auto ResetHpJob()
{
auto f = [this]()
{
this->_hp = 200;
};
return f;
}
public:
int _hp = 100;
}
Knight* k = new Knight();
auto job = k->ResetHpJob();
delete k;
job(); // 메모리 오염 발생
}
}
[캡처](인자값) { 구현부 }의 형태로 사용[]
: 캡처(capture), 함수 객체 내부에 변수를 저장하는 개념과 유사
- 기본 캡처 모드는 값(복사) 방식(
=
)과 참조 방식(&
) 중에 선택할 수 있음 - 변수마다 캡처 모드를 지정해서 사용하는 것도 가능함
- 캡처 모드를 일괄적으로 설정하는 것을 지양해야함
스마트 포인터(smart pointer)
class Knight
{
public:
Knight() { cout << "Knight 생성" << endl; }
~Knight() { cout << "Knight 소멸" << endl; }
void Attack()
{
if (_target)
{
_target->_hp -= _damage;
cout << "Hp : " << _target->_hp << endl;
}
}
public:
int _hp = 100;
int _damage = 10;
Knight* _target = nullptr;
};
int main()
{
Knight* k1 = new Knight();
Knight* k2 = new Knight();
k1->_target = k2;
delete k2;
k1->Attack(); // 크래시는 발생하지 않으나 이미 해제된 메모리를 참조하여 엉뚱한 결과가 발생함
return 0;
}
class RefCountBlock
{
public:
int _refCount = 1;
};
template<typename T>
class SharedPtr
{
public:
SharedPtr() {}
SharedPtr(T* ptr) : _ptr(ptr)
{
if (_ptr != nullptr)
{
_block = new RefCountBlock();
cout << "RefCount : " << _block->_refCount << endl;
}
}
SharedPtr(const SharedPtr* sptr) : _ptr(sptr._ptr), _block(sptr._block)
{
if (_ptr != nullptr)
{
_block->refCount++;
cout << "RefCount : " << _block->_refCount << endl;
}
}
void operator=(const SharedPtr& sptr)
{
_ptr = sptr._ptr;
_block = sptr._block;
if (_ptr != nullptr)
{
_block->refCount++;
cout << "RefCount : " << _block->_refCount << endl;
}
}
~SharedPtr()
{
if (_ptr != nullptr)
{
_block->_refCount--;
cout << "RefCount : " << _block->_refCount << endl;
if (_block->_refCount == 0)
{
delete _ptr;
delete _block;
cout << "Delete data" << endl;
}
}
}
public:
T* _ptr = nullptr;
RefCountBlock* _block = nullptr;
}
int main()
{
SharedPtr<Knight> k2;
{
SharedPtr<Knight> k1(new Knight());
k2 = k1;
}
}
class Knight
{
public:
Knight() { cout << "Knight 생성" << endl; }
~Knight() { cout << "Knight 소멸" << endl; }
void Attack()
{
if (_target)
{
_target->_hp -= _damage;
cout << "Hp : " << _target->_hp << endl;
}
}
public:
int _hp = 100;
int _damage = 10;
shared_ptr<Knight>() _target = nullptr;
};
int main()
{
shared_ptr<Knight> k1 = make_shared<Kngiht>();
{
shared_ptr<Knight> k2 = make_shared<Kngiht>();
k1->_target = k2;
}
k1->Attack();
return 0;
}
스마트 포인터 : 포인터를 알맞은 정책에 따라 관리하는 객체 (포인터를 래핑해서 사용)
shared_ptr
,weak_ptr
,unique_ptr
존재- Modern C++에서는 기존 포인터는 거의 사용하지 않는다고 보면 됨
int main()
{
shared_ptr<Knight> k1 = make_shared<Kngiht>();
{
shared_ptr<Knight> k2 = make_shared<Kngiht>();
k1->_target = k2;
k2->_target = k1;
}
k1->Attack();
return 0;
}
순환구조일 경우 shared_ptr
에서는 문제가 발생할 수 있음
이 경우 weakcount
를 가지고있는 weak_ptr
을 활용할 수 있음
단, weak_ptr
은 직접적으로 메모리 해제에 관여하지 않으므로 명시적으로 메모리 체크 후 shared_ptr
로 변환하는 과정이 요구됨
unique_ptr
은 일반적인 복사를 막아 다른 곳에 넘겨줄 수 없음
'개인공부 > Rookiss C++ 프로그래밍 입문' 카테고리의 다른 글
Chapter 11 - STL (0) | 2023.03.29 |
---|---|
Chapter 10 - 콜백 함수 (0) | 2023.03.29 |
Chapter 9 - 디버깅 (0) | 2023.03.29 |
Chatper 8 - 실습 (0) | 2023.03.29 |
Chapter 7 - 동적 할당 (0) | 2023.03.29 |
댓글