-
Notifications
You must be signed in to change notification settings - Fork 0
CPP
namespace란 어떤 정의된 객체에 대해 어디에 소속되어있는지 지정해주는 것을 의미
namespace header1 {
int foo();
void bar();
}
namespace header2 {
int foo();
void bar();
int main() {
header1::foo();
header2::foo();
}
using namespace std;
위와 같이 어떠한 이름공간을 사용하겠다라고 선언하는 것은 권장되지 않음. std::
를 직접 앞에 붙여 std
의 namespace의 함수이다라고 명시해주는 것이 좋음
C언어에서는 어떠한 변수를 가리키고 싶을 땐 반드시 포인터를 사용해야함. C++에서는 다른 변수나 상수를 가리키는 방법으로 참조자(reference)를 제공함.
함수에서 call by reference로 인자를 설정하면 메모리 복사가 일어나지 않고 포인터처럼 직접 참조를 하기에 빠르다.
void print_array(const std::array<int, 5>& arr)
와 같이 사용가능
int main() {
int a = 3;
int& another_a = a;
int arr[3] = {1, 2, 3};
int(&ref)[3] = arr;
}
another_a
는 a
의 또다른 이름이라고 컴파일러에게 명시하는 것이다. 주의할 점은 레퍼런스가 한번 별명이 되면 절대로 다른 이의 별명이 될 수 없다.
따라서 명시할 점은 초기화와 동시에 참조자를 지정해줘야 한다. 배열을 reference로 지정할 경우 ref[0]
부터 ref[2]
가 각각 arr[0]
부터 arr[2]
의 레퍼런스가 된다.
int& function(int &a) {
a = 5;
return a;
}
int main() {
int b = 2;
int c = function(b);
return 0;
}
function(b)
를 실행한 시점에서 a
는 main
의 b
를 참조하고 있게 된다. 따라서 function
이 리턴한 참조자는 아직 살아있는 변수인 b
를 계속 참조한다.
레퍼런스를 리턴하게 된다면 레퍼런스가 참조하는 타입의 크기와 상관없이 딱 한번의 주소값 복사로 전달이 끝나게 된다.
T* pointer = new T[size]
int main() {
int arr_size;
std::cout << "array size : ";
std::cin >> arr_size;
int *list = new int[arr_size];
delete[] list;
}
포인터 변수 list
에 원하는 datatype의 size만큼 할당해주면 된다. 구조체를 동적할당 하는 예시를 살펴보자.
typedef struct Animal {
char name[30];
int age;
int health;
int food;
int clean;
} Animal;
int main() {
int num;
Animal *list[10];
std::cin >> num;
list[num] = new Animal;
}
#include <iostream>
class Marine {
private:
static int total_marine_num; // 모든 인스턴스가 공유하는 하나의 static 변수
int hp;
int coord_x, coord_y;
bool is_dead;
const int default_damage;
public:
Marine();
Marine(int x, int y);
Marine(int x, int y, int default_damage);
int attack();
void be_attacked(int damage_earn);
void move(int x, int y);
void show_status();
static void show_total_marine(); // 모든 인스턴스가 공유하는 하나의 static 함수
~Marine();
};
// Static variable, method, initialization
int Marine::total_marine_num = 0;
void Marine::show_total_marine() {
std::cout << "전체 마린 수: " << total_marine_num << std::endl;
}
// Initializer list
// 인스턴스 생성과 초기화를 동시에 수행
// reference 변수나, const 변수는 생성과 동시에 초기화가 수행되어야 함
// 따라서 초기화 리스트는 해당 변수들에 대해 특화된 방식이라 할 수 있다.
Marine::Marine()
: hp(50), coord_x(0), coord_y(0), default_damage(5), is_dead(false) {
total_marine_num++;
}
Marine::Marine(int x, int y)
: coord_x(x), coord_y(y), hp(50), default_damage(5), is_dead(false) {
total_marine_num++;
}
Marine::Marine(int x, int y, int default_damage)
: coord_x(x), coord_y(y), hp(50), default_damage(default_damage), is_dead(false) {
total_marine_num++;
}
Marine::~Marine() {
total_marine_num--;
}
void Marine::move(int x, int y) {
coord_x = x;
coord_y = y;
}
int Marine::attack() {
return default_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::cout << " HP : " << hp << std::endl;
std::cout << " 현재 총 마린 수: " << total_marine_num << std::endl;
}
void create_marine() {
Marine marine3(10, 10, 4);
Marine::show_total_marine();
}
int main() {
Marine marine1(2, 3, 5);
Marine::show_total_marine(); // 어떠한 객체도 이 함수를 소유하고 있지 않기 때문에 (클래스)::(static 함수)로 접근
Marine marine2(3, 5, 10);
Marine::show_total_marine();
create_marine();
std::cout << std::endl << "마린 1이 마린 2를 공격 !" << std::endl;
marine2.be_attacked(marine1.attack());
marine1.show_status();
marine2.show_status();
}
private
: 객체 밖에서 인위적으로 접근 불가능, 객체 내부에서만 이용가능 변수, 메서드
public
: private의 특징과 정반대
참고로 키워드 명시를 하지 않는다면 기본적으로 private
으로 지정된다.
static 멤버 변수의 경우, 클래스의 모든 객체들이 '공유'하는 변수로써 다로 존재하는 멤버 변수들과는 달리 모든 객체들이 '하나의' static멤버 변수를 사용하게 된다.
모든 전역 및 static 변수들은 정의와 동시에 값이 자동으로 0으로 초기화 되기 때문에 따로 굳이 초기화 하지 않아도 된다.
또한 멤버 변수들은 클래스 내부에서 초기화하는 것은 불가능하기 때문에 아래와 같이 초기화한다.
int Marine::total_marin_num = 0;
static 함수는 어떤 객체에 종속되는 것이 아니라 클래스에 종속되는 것으로, 이를 호출하는 방법도 (객체).(멤버 함수)가 아니라 아래와 같이 호출한다.
Marine::show_total_marine();
Marine& Marine::be_attacked(int damage_earn) {
hp -= damage_earn; // this->hp = damage_earn; 도 가능
if (hp <= 0) is_dead = true; // if (this->hp <= 0) this->is_dead = true;
return *this;
}
this->hp = damage_earn;
는 이 멤버 함수를 호출하는 객체 자신을 가리키는 것
be_attacked()
함수는 Marine&
을 리턴하게 되는데 위의 경우 *this
를 리턴하게 된다. 이것은 그 객체 자신을 의미하게 된다.
만일 be_attacked()
의 함수의 리턴 타입이 Marine&
이 아니라 Marine
이라면 임시 객체 Marine
을 생성해서, *this
의 내용으로 복사가 되고 이 임시 객체에 대한 be_attacked()
함수가 호출된다.
:: (범위 지정)
, . (멤버 지정)
, .*(멤버 포인터로 멤버 지정)
을 제외한 모든 연산자를 오버로딩 가능\
(리턴 타입) operator(연산자) (연산자가 받는 인자)
class Mystring {
private:
char* string_content;
int string_length;
public:
bool operator==(Mystring &str);
}
bool Mystring::operator==(Mystring &str) {
return !compare(str);
}
char& Mystring::operator[](const int index) {
return string_content[index];
}
컴파일러에서 자동으로 캐스팅 해주는 암시적(implicit) 캐스팅과, 직접 캐스팅을 지정해주는 명시적(explicit) 캐스팅이 존재
c++에서는 다음과 같은 4개의 캐스팅을 지원한다. 가장 많이 쓰게 되는것은 가장 위의 2개 나머진 거의 이용X
-
static_cast
: 언어적 차원에서 지원하는 일반적인 타입 변환 -
const_cast
: 객체의 상수성(const)를 없애는 타입 변환, const int가 int로 변경됨 -
dynamic_cast
: 파생 클래스 사이에서의 다운 캐스팅 -
reinterpret_cast
: 위험을 감수하고 하는 캐스팅으로 서로 관련이 없는 포인터들 사이의 캐스팅
아래의 코드는 완전히 동일한 의미를 가진다.
static_cast<int>(float_variable);
(int)(float_variable);
class Derived: public Base {
private:
std::string s;
public:
Derived(): Base(), s("파생") {
std::cout << "파생 클래스" << std::endl;
}
}
위의 코드와 같이 상속을 할 수 있다. 주의할 점은 Derived의 생성자는 위 처럼 초기화자 리스트에서 기반의 생성자를 호출해서 기반의 생성을 먼저 처리 한 다음에, Derived의 생성자가 실행되어야 한다.
#include <iostream>
#include <string>
#pragma warning(disable:4996)
// virtual, overide
class Employee {
// Manager 클래스에서 상속받아 접근해야 하므로 private 키워드로 지정하면 접근이 불가능
protected:
std::string name;
int age;
std::string position;
int rank;
public:
Employee(std::string name, int age, std::string position, int rank) : name(name), age(age), position(position), rank(rank) {}
Employee(const Employee& employee) {
name = employee.name;
age = employee.age;
position = employee.position;
rank = employee.rank;
}
Employee() {}
// vitual: 가상 함수, 해당 클래스를 상속받는 하위 클래스에서 virtual 키워드의 함수를 재정의(오버라이딩)한다는 뜻
virtual void print_info() {
std::cout << name << " (" << position << " , " << age << ") ==>" << calculate_pay() << "만원" << std::endl;
}
virtual int calculate_pay() {
return 200 + rank * 50;
}
};
class Manager : public Employee {
private:
int year_of_service;
public:
Manager(std::string name, int age, std::string position, int rank, int year_of_service) : Employee(name, age, position, rank), year_of_service(year_of_service) {}
// override: virtual과 똑같은 기능, 다만 키워드를 붙이는 위치가 다름(함수 이름 뒤에), 이 가상함수가 상속받아서 오버라이딩한 함수라는 것을 의미함
int calculate_pay() override{
return 200 + rank * 50 + 5 * year_of_service;
}
void print_info() override{
std::cout << name << " (" << position << " , " << age << " , "
<< year_of_service << "년차) ==> " << calculate_pay() << "만원" << std::endl;
}
};
class EmployeeList {
private:
int alloc_employee;
int current_employee;
Employee** employee_list;
public:
EmployeeList(int alloc_employee) : alloc_employee(alloc_employee) {
employee_list = new Employee* [alloc_employee];
current_employee = 0;
}
void add_employee(Employee* employee) {
employee_list[current_employee] = employee;
current_employee++;
}
int current_employee_num() {
return current_employee;
}
void print_employee_info() {
int total_pay = 0;
for (int i = 0; i < current_employee; i++) {
employee_list[i]->print_info();
total_pay += employee_list[i]->calculate_pay();
}
std::cout << "총 비용: " << total_pay << "만원" << std::endl;
}
~EmployeeList() {
for (int i = 0; i < current_employee; i++) {
delete employee_list[i];
}
delete[] employee_list;
}
};
int main() {
EmployeeList emp_list(10);
emp_list.add_employee(new Employee("노홍철", 34, "평사원", 1));
emp_list.add_employee(new Employee("하동훈", 34, "평사원", 1));
emp_list.add_employee(new Manager("유재석", 41, "부장", 7, 12));
emp_list.add_employee(new Manager("정준하", 43, "과장", 4, 15));
emp_list.add_employee(new Manager("박명수", 43, "차장", 5, 13));
emp_list.add_employee(new Employee("정형돈", 36, "대리", 2));
emp_list.add_employee(new Employee("길성준", 36, "인턴", -1));
emp_list.print_employee_info();
return 0;
}
-
protected
: public과 private에 중간 위치에 있는 접근 지시자, 상속받는 클래스에서는 접근 가능하고 그 외의 기타 정보는 접근 불가능 -
virtual
:virtual
키워드가 붙은 함수를 가상 함수(virtual function)이라고 칭함. 즉, 해당 클래스를 상속받는 하위 클래스에서 해당 키워드의 함수를 재정의(오버라이딩)한다는 뜻 -
override
: 파생 클래스에서 기반 클래스의 가상 함수를 오버라이드 하는 경우,override
키워드를 통해서 명시적으로 나타낼 수 있음
#include <iostream>
class Parent {
public:
Parent() { std::cout << "Parent 생성자 호출" << std::endl; }
~Parent() { std::cout << "Parent 소멸자 호출" << std::endl; }
};
class Child : public Parent {
public:
Child() { std::cout << "Child 생성자 호출" << std::endl; }
~Child() { std::cout << "Child 소멸자 호출" << std::endl; }
};
int main() {
std::cout << "--- 평범한 Child를 만들었을때 ---" << std::endl;
{Child C; }
std::cout << "--- Parent 포인터로 Child를 가리켰을 때 ---" << std::endl;
{Parent* p = new Child;
delete p; }
return 0;
}
--- 평범한 Child를 만들었을때 ---
Parent 생성자 호출
Child 생성자 호출
Child 소멸자 호출
Parent 소멸자 호출
--- Parent 포인터로 Child를 가리켰을 때 ---
Parent 생성자 호출
Child 생성자 호출
Parent 소멸자 호출
Parent 포인터가 Child 객체를 가리킬 때 delete p를 하더라도, p가 가리키는 것은 Parent 객체가 아닌 Child 객체이기 때문에, 위에서 보통의 Child 객체가 소멸되는 것과 같은 순서로 생성자와 소멸자들이 호출되어야 하지만 실제로는, Child 소멸자가 호출되지 않는다. 이는 곧 메모리 누수(memory leakage)
문제로 발생한다. 따라서 Parent의 소멸자를 virtual로 만들면, p가 소멸자를 호출할 때, Child의 소멸자를 성공적으로 호출할 수 있게 된다.
virtual ~Parent() { std::cout << "Parent 소멸자 호출" << std::endl; }
--- 평범한 Child를 만들었을때 ---
Parent 생성자 호출
Child 생성자 호출
Child 소멸자 호출
Parent 소멸자 호출
--- Parent 포인터로 Child를 가리켰을 때 ---
Parent 생성자 호출
Child 생성자 호출
Child 소멸자 호출
Parent 소멸자 호출
이는 Child의 소멸자를 호출하면서, Child 소멸자가 알아서 Parent의 소멸자도 호출해주기 때문이다. 결국 virtual
키워드는 Child는 Parent에서 상속받았다는 것을 컴파일러에게 명시적으로 알려주는 것이다.
이와 같은 이유로, 상속될 여지가 있는 Base 클래스들은 반드시 소멸자를 virtual로 만들어주어야 나중에 문제가 발생할 여지가 없게 된다.
class A {
public:
int a;
};
class B {
public:
int b;
};
class C: public A, public B {
public:
int c;
};
A -> B -> C
의 순서로 생성자가 호출된다.
operator >>
의 특징으로, 모든 공백문자 (띄어쓰기나 엔터, 탭 등)을 입력시에 무시해 버린다는 특징이 있다. 그렇기 때문에, 만일 cin을 통해서 문장을 입력 받는 다면, 첫 단어만 입력받고 나머지를 읽을 수 없다. 따라서 std::getline(string)
을 활용한다.
// 지정한 delimiter를 만나기 전까지 모든 문자를 읽어서 string 객체에 저장 (디폴트 구분자는 개행 문자)
getline(istream& is, string str);
getline(istream& is, string str, char dlim);
string s;
cin.ignore(); //입력 버퍼 비우기
getline(std::cin, s); // 특정 문자가 나올때까지 입력받음, dlim의 default는 "\n"
사용자가 원하는 타입을 넣어주면 알아서 코드를 찍어내는 틀
#include <iostream>
template <typename T, int num>
T add_num(T t) {
return t + num;
}
int main() {
int x = 3;
std::cout << "x : " << add_num<int, 5>(x) << std::endl;
return 0;
}
x : 8
템플릿 인자로 전달할 수 있는 타입들은
-
정수 타입(bool, char, int, long)
, -
포인터 타입
, -
std::nullptr_t(널 포인터)
로 제한되어 있다.
추가적으로 템플릿도 디폴트 인자를 아래와 같이 지정할 수 있다.
template<typename T, int num = 5>
아래의 예제는 템플릿을 이용하여 Vector클래스를 구현하는 예제이다. 크게 Functor
와 Template
두가지 개념이 포함되어 있다.
#include <iostream>
template <typename T>
class Vector {
private:
T* data;
int capacity;
int length;
public:
typedef T value_type;
Vector(int n = 1) : data(new T[n]), capacity(n), length(0) {}
void push_back(int s) {
if (capacity <= length) {
T* temp = new T[capacity * 2];
for (int i = 0; i < length; i++) {
temp[i] = data[i];
}
delete[] data;
data = temp;
capacity *= 2;
}
data[length] = s;
length++;
}
T operator[](int i) {
return data[i];
}
void remove(int x) {
for (int i = x + 1; i < length; i++) {
data[i - 1] = data[i];
}
length--;
}
int size() {
return length;
}
void swap(int i, int j) {
T temp = data[i];
data[i] = data[j];
data[j] = temp;
}
~Vector() {
if (data) {
delete[] data;
}
}
};
template <typename Cont>
void bubble_sort(Cont& cont) {
for (int i = 0; i < cont.size(); i++) {
for (int j = i + 1; j < cont.size(); j++) {
if (cont[i] > cont[j]) {
cont.swap(i, j);
}
}
}
}
template <typename Cont, typename Comp>
void bubble_sort(Cont & cont, Comp & comp) {
for (int i = 0; i < cont.size(); i++) {
for (int j = i + 1; j < cont.size(); j++) {
if (!comp(cont[i], cont[j])) {
cont.swap(i, j);
}
}
}
}
struct Comp1 {
bool operator()(int a, int b) {
return a > b;
}
};
struct Comp2 {
bool operator()(int a, int b) {
return a < b;
}
};
int main() {
Vector<int> int_vec; // 클래스 템플릿 인스턴스화(class template instantiation)
int_vec.push_back(3);
int_vec.push_back(1);
int_vec.push_back(2);
int_vec.push_back(8);
int_vec.push_back(5);
int_vec.push_back(3);
std::cout << "정렬 이전 ---- " << std::endl;
for (int i = 0; i < int_vec.size(); i++) {
std::cout << int_vec[i] << " "; // operator 정의에 의해 사용가능
}
std::cout << std::endl << "기존 메서드 이용하여 오름차순 정렬 ---- " << std::endl;
bubble_sort(int_vec);
for (int i = 0; i < int_vec.size(); i++) {
std::cout << int_vec[i] << " "; // operator 정의에 의해 사용가능
}
Comp1 comp1; // 함수는 아니지만 함수인 척을하는 객체를 함수 객체라고 함(Functor)
bubble_sort(int_vec, comp1);
std::cout << std::endl << "내림차순 정렬 이후 ---- " << std::endl;
for (int i = 0; i < int_vec.size(); i++) {
std::cout << int_vec[i] << " "; // // operator 정의에 의해 사용가능
}
std::cout << std::endl;
Comp2 comp2; // 함수는 아니지만 함수인 척을하는 객체를 함수 객체라고 함(Functor)
bubble_sort(int_vec, comp2);
std::cout << std::endl << "오름차순 정렬 이후 ---- " << std::endl;
for (int i = 0; i < int_vec.size(); i++) {
std::cout << int_vec[i] << " ";
}
std::cout << std::endl;
}
-
template <typename T> == template <class T>
은 동일한 의미를 가진다. - 중첩 의존 타입 이름을 식별하는 용도에서는 반드시 typename을 사용해야 한다.
c++ 컴파일러에게 어떤 키워드는 typedef로 재정의 된 type이라는 것을 알려주기 위해 typename 키워드를 사용해야 한다.
아래의 코드에서 iterator가 vector의 의존타입이기 때문에 iterator가 type이라는 것을 알려주기 위해 typename 키워드를 사용해야 한다.
template <typename T>
void print_vector(std::vector<T>& vec) {
// 전체 벡터를 출력하기
for (typename std::vector<T>::iterator itr = vec.begin(); itr != vec.end();
++itr) {
std::cout << *itr << std::endl;
}
}
int main() {
std::vector<int> vec;
vec.push_back(10);
vec.push_back(20);
vec.push_back(30);
vec.push_back(40);
std::cout << "벡터 상태" << std::endl;
print_vector(vec);
std::cout << "----------------------------" << std::endl;
return 0;
}
typedef Ratio_add<rat, rat2> rat3;
using rat3 = Ratio_add<rat, rat2>;
위 두 문장 모두 동일한 의미를 가진다. using을 사용하였을 경우 typedef 보다 좀 더 직관적인 이해를 할 수 있다는 장점이 존재한다. (C+11부터 사용가능)
- 임의 타입의 객체를 보관할 수 있는 컨테이너
container
- 컨테이너에 보관된 원소에 접근할 수 있는 반복자
iterator
- 반복자들을 가지고 일련의 작업을 수행하는 알고리즘
algorithm
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec;
vec.push_back(10);
vec.push_back(20);
vec.push_back(30);
vec.push_back(40);
for (std::vector<int>::size_type i = 0; i < vec.size(); i++) {
std::cout << "vec 의" << i + 1 << "번째 원소::" << vec[i] << std::endl;
}
}
벡터의 크기를 리턴하는 size()
의 경우, 그 리턴하는 값의 타입은 size_type
멤버 타입으로 정의되어 있다.
// 크기가 10인 벡터 선언
std::vector<int> vec2(10);
// 크기가 10이고, 모든 원소가 3으로 초기화된 벡터 선언
std::vector<int> vec3(10, 3);
// 지정한 초기값으로 이루어진 크기가 5인 벡터 선언
std::vector<int> vec4 = { 1,2,3,4,5 };
// 벡터 배열 생성(행은 가변인지만, 열은 고정)
std::vector<int> vec5[] = { {1,2},{3,4} };
// 2차원 벡터 생성(행과 열 모두 가변)
std::vector<std::vector<int>> vec6;
#include <iostream>
#include <deque>
template <typename T>
void print_deque(std::deque<T>& dq) {
std::cout << "[ ";
for (const auto& elem : dq) {
std::cout << elem << " ";
}
std::cout << "] " << std::endl;
}
int main() {
std::deque<int> dq;
dq.push_back(10);
dq.push_back(20);
dq.push_back(30);
dq.push_back(40);
std::cout << "초기 dq 상태" << std::endl;
print_deque(dq);
std::cout << "맨 앞의 원소 제거" << std::endl;
dq.pop_front();
print_deque(dq);
}
추가적으로 양방향 큐이기 때문에 다음과 같은 함수들 사용가능
dequeue.push_front(10)
dequeue.push_back(50)
dequeue.pop_front()
dequeue.pop_back()
#include <iostream>
#include <set>
template<typename T>
void print_set(std::set<T>& s) {
std::cout << "[ ";
for (typename std::set<T>::iterator itr = s.begin(); itr != s.end(); itr++) {
std::cout << *itr << " ";
}
std::cout << " ] " << std::endl;
}
int main() {
std::set<int> s;
s.insert(10);
s.insert(50);
s.insert(20);
s.insert(40);
s.insert(30);
std::cout << "순서대로 정렬되서 나온다" << std::endl;
print_set(s);
std::cout << "20이 s의 원소인가요? :: ";
auto itr = s.find(20);
if (itr != s.end()) {
std::cout << "Yes" << std::endl;
}
else {
std::cout << "No" << std::endl;
}
std::cout << "25가 s의 원소인가요? :: ";
itr = s.find(25);
if (itr != s.end()) {
std::cout << "Yes" << std::endl;
}
else {
std::cout << "No" << std::endl;
}
}
내부적으로 완전 이진 트리 구조로 구성되어 있는 set, 각각의 원소들은 트리의 각 노드들에 저장되어 있고, 다음과 같은 규칙을 지키며 set안에는 중복된 원소들이 없다.
- 왼쪽에 오는 모든 노드들은 나보다 작다.
- 오른쪽에 있는 모든 노드들은 나보다 크다.
s.find(20)
을 통해 원소의 존재 유무를 파악할 수 있다. 실행시간은 O(log(N))
, 만약 원소가 존재하지 않는다면 s.end()
를 리턴한다.
#include <iostream>
#include <map>
#include <string>
template <typename K, typename V>
void print_map(std::map<K, V>& m) {
for (auto itr = m.begin(); itr != m.end(); itr++) {
std::cout << itr->first << " " << itr->second << std::endl;
}
}
template <typename K, typename V>
void search_and_print(std::map<K, V>& m, K key) {
auto itr = m.find(key);
if (itr != m.end()) {
std::cout << key << " ---> " << itr->second << std::endl;
}
else {
std::cout << key << "은(는) 목록에 없습니다." << std::endl;
}
}
int main() {
std::map<std::string, double> pitcher_list;
pitcher_list.insert(std::make_pair("차우찬", 3.04));
pitcher_list.insert(std::make_pair("장원준", 3.05));
pitcher_list.insert(std::make_pair("헥터", 3.09));
pitcher_list["니퍼트"] = 3.56;
pitcher_list["박종훈"] = 3.76;
pitcher_list["켈리"] = 3.90;
print_map(pitcher_list);
std::cout << "------------------------------" << std::endl;
// [] 연산자는, 맵에 없는 키를 참조하게 되면, 자동으로 값의 디폴트 생성자를 호출해서 원소를 추가해버린다.
// 원소에 대응되는 값을 바꾸고 싶다면 insert가 아닌 []연산자를 활용한다.
std::cout << "박세웅 방어율은? :: " << pitcher_list["박세웅"] << std::endl;
search_and_print(pitcher_list, std::string("오승환"));
search_and_print(pitcher_list, std::string("류현진"));
}
[]
연산자는 맵에 없는 키를 참조하게 되면, 자동으로 값의 디폴트 생성자를 호출해서 원소를 추가해버린다.
map역시 set처럼 중복된 원소를 허락하지 않는다. 이미, 같은 키가 원소로 들어있다면 나중에 오는 insert
는 무시된다.
원소에 대응되는 값을 바꾸고 싶다면 insert
가 아닌 []
연산자로 대응되는 값을 바꿔줄 수 있다.
# Value를 기준으로 정렬, map을 vector로 옮겨줘야함
# comp 함수는 오름차순은 < , 내림차순은 > 형태이다. 다른 블로그에서 보았는데 < 모양이 크레센도와 닮았으니 뒤로 갈수록 큰값이 존재한다고 생각하면 외우기 쉽다.
map<char, int> m;
vector<pair<char, int>> v(m.begin(), m.end());
sort(v.begin(), v.end(), comp);
- 데이터의 존재 유무만 궁금한 경우
$\rightarrow$ set
- 중복 데이터를 허락할 경우
$\rightarrow$ multiset
- 데이터에 대응되는 데이터를 저장하고 싶은 경우
$\rightarrow$ map
- 중복 키를 허락할 경우
$\rightarrow$ multimap
#include <iostream>
#include <vector>
template<typename T>
void print_vector(std::vector<T>& vec) {
std::cout << "[ ";
for (typename std::vector<T>::iterator itr = vec.begin(); itr != vec.end(); ++itr) {
std::cout << *itr << " ";
}
std::cout << "]" << std::endl;
}
template<typename T>
void print_reverse_vector(std::vector<T>& vec) {
std::cout << "[ ";
for (typename std::vector<T>::reverse_iterator itr = vec.rbegin(); itr != vec.rend(); ++itr) {
std::cout << *itr << " ";
}
std::cout << "]" << std::endl;
}
template<typename T>
void print_range_vector(std::vector<T>& vec) {
std::cout << "[ ";
for (const auto &i: vec) {
std::cout << i << " ";
}
std::cout << "]" << std::endl;
}
int main() {
std::vector<int> vec;
vec.push_back(10);
vec.push_back(20);
vec.push_back(30);
vec.push_back(40);
vec.push_back(20);
print_vector(vec);
print_reverse_vector(vec);
print_range_vector(vec);
}
-
std::vector<T>::iterator itr = vec.begin()
은 첫번째 원소를 가리키는 반복자를 리턴 -
std::vector<T>::iterator itr = vec.end()
는 마지막 원소 한 칸 뒤를 가리키는 반복자를 리턴 -
std::vector<T>::reverse_iterator itr = vec.rbegin()
은 마지막 원소를 가리키는 반복자를 리턴 -
std::vector<T>::reverse_iterator itr = vec.rend()
은 첫번째 원소를 가리키는 반복자를 리턴
new 키워드를 사용해 동적으로 할당받은 메모리는, 반드시 delete 키워드를 사용하여 해제해야함. 메모리 누수(memory leak)로부터 프로그램의 안정성을 보장하기 위해 스마트 포인터라는 개념을 도입하였다. 스마트 포인터란 포인터처럼 동작하는 클래스 템플릿으로, 사용이 끝난 메모리를 자동으로 해제해 준다.
보통 new 키워드를 사용해 기본 포인터(raw pointer)가 실제 메모리를 가리키도록 초기화한 후에, 기본 포인터를 스마트 포인터에 대입하여 사용한다. 이렇게 정의된 스마트 포인터의 수명이 다하면, 소멸자는 delete 키워드를 사용하여 할당된 메모리를 자동으로 해제한다. 따라서 new 키워드가 반환하는 주소값을 스마트 포인터에 대입하면, 따로 메모리를 해제할 필요가 없어진다.
unique_ptr은 하나의 스마트 포인터만이 특정 객체를 소유할 수 있도록, 객체에 소유권 개념을 도입한 스마트 포인터이다.
unique_ptr<int> ptr01(new int(5)); // int형 unique_ptr인 ptr01을 선언하고 초기화함.
auto ptr02 = move(ptr01); // ptr01에서 ptr02로 소유권을 이전함.
// unique_ptr<int> ptr03 = ptr01; // 대입 연산자를 이용한 복사는 오류를 발생시킴.
ptr02.reset(); // ptr02가 가리키고 있는 메모리 영역을 삭제함.
ptr01.reset(); // ptr01가 가리키고 있는 메모리 영역을 삭제함.
Person 객체를 가리키는 hong이라는 unique_ptr를 make_unique() 함수를 통해 생성하는 예제이다.
#include <iostream>
#include <memory>
using namespace std;
class Person {
private:
string name_;
int age_;
public:
Person(const string& name, int age); // 기초 클래스 생성자의 선언
~Person() { cout << "소멸자가 호출되었습니다." << endl; }
void ShowPersonInfo();
};
int main(void) {
unique_ptr<Person> hong = make_unique<Person>("길동", 29);
hong->ShowPersonInfo();
return 0;
}
Person::Person(const string& name, int age) {
name_ = name;
age_ = age;
cout << "생성자가 호출되었습니다." << endl;
}
void Person::ShowPersonInfo() { cout << name_ << "의 나이는 " << age_ << "살입니다." << endl; }
생성자가 호출되었습니다.
길동의 나이는 29살입니다.
소멸자가 호출되었습니다.
shared_ptr은 하나의 특정 객체를 참조하는 스마트 포인터가 총 몇 개인지를 참조하는 스마트 포인터이다.
이렇게 참조하고 있는 스마트 포인터의 개수를 참조 횟수(reference count)라고 한다.
참조 횟수는 특정 객체에 새로운 shared_ptr이 추가될 때마다 1씩 증가하며, 수명이 다할 때마다 1씩 감소한다.
따라서 마지막 shared_ptr의 수명이 다하여, 참조 횟수가 0이 되면 delete 키워드를 사용하여 메모리를 자동으로 해제한다.
shared_ptr<int> ptr01(new int(5)); // int형 shared_ptr인 ptr01을 선언하고 초기화함.
cout << ptr01.use_count() << endl; // 1
auto ptr02(ptr01); // 복사 생성자를 이용한 초기화
cout << ptr01.use_count() << endl; // 2
auto ptr03 = ptr01; // 대입을 통한 초기화
cout << ptr01.use_count() << endl; // 3
Person 객체를 가리키는 hong이라는 shared_ptr를 make_shared() 함수를 통해 생성하는 예제이다.
shared_ptr<Person> hong = make_shared<Person>("길동", 29);
cout << "현재 소유자 수 : " << hong.use_count() << endl; // 1
auto han = hong;
cout << "현재 소유자 수 : " << hong.use_count() << endl; // 2
han.reset(); // shared_ptr인 han을 해제함.
cout << "현재 소유자 수 : " << hong.use_count() << endl; // 1
생성자가 호출되었습니다.
현재 소유자 수 : 1
현재 소유자 수 : 2
현재 소유자 수 : 1
소멸자가 호출되었습니다.
Eigen::Matrix3d A = Eigen::Matrix3d::Identity();
Eigen::Vector3d a(0.5, 3, -0.4);
Eigen::Vector3d Aa = A * a;
std::cout << "The multiplication of A * a is " << std::endl << Aa << std::endl; // Result of Aa: 0.5 3 -0.4
Eigen::MatrixXd B = Eigen::MatrixXd::Identity(6, 5);
Eigen::VectorXd b(5);
b << 1, 4, 6, -2, 0.4;
Eigen::VectorXd Bb = B * b;
std::cout << "The multiplication of B * b is " << std::endl << Bb << std::endl; // Result of Bb: 1, 4, 6, -2, 0.4;
Eigen::MatrixXd A(3, 2);
A << 1, 2,
2, 3,
3, 4;
Eigen::MatrixXd B = A.transpose();// the transpose of A is a 2x3 matrix
Eigen::MatrixXd C = (B * A).inverse();// computer the inverse of BA, which is a 2x2 matrix
Eigen::Vector3d v(1, 2, 3);
Eigen::Vector3d w(0, 1, 2);
double vDotw = v.dot(w); // dot product of two vectors(내적)
Eigen::Vector3d vCrossw = v.cross(w); // cross product of two vectors(외적)
Eigen::MatrixXd A = Eigen::MatrixXd::Random(7, 9);
std::cout << "The element at fourth row and 7the column is " << A(3, 6) << std::endl;
Eigen::MatrixXd B = A.block(1, 2, 3, 3); // block(start rows, startcols, num_rows, num_cols)
std::cout << "Take sub-matrix whose upper left corner is A(1, 2)" << std::endl << B << std::endl;
Eigen::VectorXd a = A.col(1); // take the second column of A
Eigen::VectorXd b = B.row(0); // take the first row of B
Eigen::VectorXd c = a.head(3);// take the first three elements of a
Eigen::VectorXd d = b.tail(2);// take the last two elements of b
Eigen::Quaterniond q(2, 0, 1, -3);
std::cout << "This quaternion consists of a scalar " << q.w() << " and a vector " << std::endl << q.vec() << std::endl;
q.normalize();
std::cout << "To represent rotation, we need to normalize it such that its length is " << q.norm() << std::endl;
Eigen::Vector3d v(1, 2, -1);
Eigen::Quaterniond p;
p.w() = 0;
p.vec() = v;
Eigen::Quaterniond rotatedP = q * p * q.inverse();
Eigen::Vector3d rotatedV = rotatedP.vec();
std::cout << "We can now use it to rotate a vector " << std::endl << v << " to " << std::endl << rotatedV << std::endl;
Eigen::Matrix3d R = q.toRotationMatrix(); // convert a quaternion to a 3x3 rotation matrix
std::cout << "Compare with the result using an rotation matrix " << std::endl << R * v << std::endl;
Eigen::Quaterniond a = Eigen::Quterniond::Identity();
Eigen::Quaterniond b = Eigen::Quterniond::Identity();
Eigen::Quaterniond c; // Adding two quaternion as two 4x1 vectors is not supported by the EIgen API. That is, c = a + b is not allowed. We have to do this in a hard way
c.w() = a.w() + b.w();
c.x() = a.x() + b.x();
c.y() = a.y() + b.y();
c.z() = a.z() + b.z();
#include <Eigen/Dense>
Matrix<double, 3, 3> A; // Fixed rows and cols. Same as Matrix3d.
Matrix<double, 3, Dynamic> B; // Fixed rows, dynamic cols.
Matrix<double, Dynamic, Dynamic> C; // Full dynamic. Same as MatrixXd.
Matrix<double, 3, 3, RowMajor> E; // Row major; default is column-major.
Matrix3f P, Q, R; // 3x3 float matrix.
Vector3f x, y, z; // 3x1 float matrix.
RowVector3f a, b, c; // 1x3 float matrix.
VectorXd v; // Dynamic column vector of doubles
double s;
// Basic usage
// Eigen // Matlab // comments
x.size() // length(x) // vector size
C.rows() // size(C,1) // number of rows
C.cols() // size(C,2) // number of columns
x(i) // x(i+1) // Matlab is 1-based
C(i,j) // C(i+1,j+1) //
A.resize(4, 4); // Runtime error if assertions are on.
B.resize(4, 9); // Runtime error if assertions are on.
A.resize(3, 3); // Ok; size didn't change.
B.resize(3, 9); // Ok; only dynamic cols changed.
A << 1, 2, 3, // Initialize A. The elements can also be
4, 5, 6, // matrices, which are stacked along cols
7, 8, 9; // and then the rows are stacked.
B << A, A, A; // B is three horizontally stacked A's.
A.fill(10); // Fill A with all 10's.
// Eigen // Matlab
MatrixXd::Identity(rows,cols) // eye(rows,cols)
C.setIdentity(rows,cols) // C = eye(rows,cols)
MatrixXd::Zero(rows,cols) // zeros(rows,cols)
C.setZero(rows,cols) // C = zeros(rows,cols)
MatrixXd::Ones(rows,cols) // ones(rows,cols)
C.setOnes(rows,cols) // C = ones(rows,cols)
MatrixXd::Random(rows,cols) // rand(rows,cols)*2-1 // MatrixXd::Random returns uniform random numbers in (-1, 1).
C.setRandom(rows,cols) // C = rand(rows,cols)*2-1
VectorXd::LinSpaced(size,low,high) // linspace(low,high,size)'
v.setLinSpaced(size,low,high) // v = linspace(low,high,size)'
VectorXi::LinSpaced(((hi-low)/step)+1, // low:step:hi
low,low+step*(size-1)) //
// Matrix slicing and blocks. All expressions listed here are read/write.
// Templated size versions are faster. Note that Matlab is 1-based (a size N
// vector is x(1)...x(N)).
// Eigen // Matlab
x.head(n) // x(1:n)
x.head<n>() // x(1:n)
x.tail(n) // x(end - n + 1: end)
x.tail<n>() // x(end - n + 1: end)
x.segment(i, n) // x(i+1 : i+n)
x.segment<n>(i) // x(i+1 : i+n)
P.block(i, j, rows, cols) // P(i+1 : i+rows, j+1 : j+cols)
P.block<rows, cols>(i, j) // P(i+1 : i+rows, j+1 : j+cols)
P.row(i) // P(i+1, :)
P.col(j) // P(:, j+1)
P.leftCols<cols>() // P(:, 1:cols)
P.leftCols(cols) // P(:, 1:cols)
P.middleCols<cols>(j) // P(:, j+1:j+cols)
P.middleCols(j, cols) // P(:, j+1:j+cols)
P.rightCols<cols>() // P(:, end-cols+1:end)
P.rightCols(cols) // P(:, end-cols+1:end)
P.topRows<rows>() // P(1:rows, :)
P.topRows(rows) // P(1:rows, :)
P.middleRows<rows>(i) // P(i+1:i+rows, :)
P.middleRows(i, rows) // P(i+1:i+rows, :)
P.bottomRows<rows>() // P(end-rows+1:end, :)
P.bottomRows(rows) // P(end-rows+1:end, :)
P.topLeftCorner(rows, cols) // P(1:rows, 1:cols)
P.topRightCorner(rows, cols) // P(1:rows, end-cols+1:end)
P.bottomLeftCorner(rows, cols) // P(end-rows+1:end, 1:cols)
P.bottomRightCorner(rows, cols) // P(end-rows+1:end, end-cols+1:end)
P.topLeftCorner<rows,cols>() // P(1:rows, 1:cols)
P.topRightCorner<rows,cols>() // P(1:rows, end-cols+1:end)
P.bottomLeftCorner<rows,cols>() // P(end-rows+1:end, 1:cols)
P.bottomRightCorner<rows,cols>() // P(end-rows+1:end, end-cols+1:end)
// Of particular note is Eigen's swap function which is highly optimized.
// Eigen // Matlab
R.row(i) = P.col(j); // R(i, :) = P(:, j)
R.col(j1).swap(mat1.col(j2)); // R(:, [j1 j2]) = R(:, [j2, j1])
// Views, transpose, etc;
// Eigen // Matlab
R.adjoint() // R'
R.transpose() // R.' or conj(R') // Read-write
R.diagonal() // diag(R) // Read-write
x.asDiagonal() // diag(x)
R.transpose().colwise().reverse() // rot90(R) // Read-write
R.rowwise().reverse() // fliplr(R)
R.colwise().reverse() // flipud(R)
R.replicate(i,j) // repmat(P,i,j)
// All the same as Matlab, but matlab doesn't have *= style operators.
// Matrix-vector. Matrix-matrix. Matrix-scalar.
y = M*x; R = P*Q; R = P*s;
a = b*M; R = P - Q; R = s*P;
a *= M; R = P + Q; R = P/s;
R *= Q; R = s*P;
R += Q; R *= s;
R -= Q; R /= s;
// Vectorized operations on each element independently
// Eigen // Matlab
R = P.cwiseProduct(Q); // R = P .* Q
R = P.array() * s.array(); // R = P .* s
R = P.cwiseQuotient(Q); // R = P ./ Q
R = P.array() / Q.array(); // R = P ./ Q
R = P.array() + s.array(); // R = P + s
R = P.array() - s.array(); // R = P - s
R.array() += s; // R = R + s
R.array() -= s; // R = R - s
R.array() < Q.array(); // R < Q
R.array() <= Q.array(); // R <= Q
R.cwiseInverse(); // 1 ./ P
R.array().inverse(); // 1 ./ P
R.array().sin() // sin(P)
R.array().cos() // cos(P)
R.array().pow(s) // P .^ s
R.array().square() // P .^ 2
R.array().cube() // P .^ 3
R.cwiseSqrt() // sqrt(P)
R.array().sqrt() // sqrt(P)
R.array().exp() // exp(P)
R.array().log() // log(P)
R.cwiseMax(P) // max(R, P)
R.array().max(P.array()) // max(R, P)
R.cwiseMin(P) // min(R, P)
R.array().min(P.array()) // min(R, P)
R.cwiseAbs() // abs(P)
R.array().abs() // abs(P)
R.cwiseAbs2() // abs(P.^2)
R.array().abs2() // abs(P.^2)
(R.array() < s).select(P,Q ); // (R < s ? P : Q)
R = (Q.array()==0).select(P,A) // R(Q==0) = P(Q==0)
R = P.unaryExpr(ptr_fun(func)) // R = arrayfun(func, P) // with: scalar func(const scalar &x);
// Reductions.
int r, c;
// Eigen // Matlab
R.minCoeff() // min(R(:))
R.maxCoeff() // max(R(:))
s = R.minCoeff(&r, &c) // [s, i] = min(R(:)); [r, c] = ind2sub(size(R), i);
s = R.maxCoeff(&r, &c) // [s, i] = max(R(:)); [r, c] = ind2sub(size(R), i);
R.sum() // sum(R(:))
R.colwise().sum() // sum(R)
R.rowwise().sum() // sum(R, 2) or sum(R')'
R.prod() // prod(R(:))
R.colwise().prod() // prod(R)
R.rowwise().prod() // prod(R, 2) or prod(R')'
R.trace() // trace(R)
R.all() // all(R(:))
R.colwise().all() // all(R)
R.rowwise().all() // all(R, 2)
R.any() // any(R(:))
R.colwise().any() // any(R)
R.rowwise().any() // any(R, 2)
// Dot products, norms, etc.
// Eigen // Matlab
x.norm() // norm(x). Note that norm(R) doesn't work in Eigen.
x.squaredNorm() // dot(x, x) Note the equivalence is not true for complex
x.dot(y) // dot(x, y)
x.cross(y) // cross(x, y) Requires #include <Eigen/Geometry>
//// Type conversion
// Eigen // Matlab
A.cast<double>(); // double(A)
A.cast<float>(); // single(A)
A.cast<int>(); // int32(A)
A.real(); // real(A)
A.imag(); // imag(A)
// if the original type equals destination type, no work is done
// Note that for most operations Eigen requires all operands to have the same type:
MatrixXf F = MatrixXf::Zero(3,3);
A += F; // illegal in Eigen. In Matlab A = A+F is allowed
A += F.cast<double>(); // F converted to double and then added (generally, conversion happens on-the-fly)
// Eigen can map existing memory into Eigen matrices.
float array[3];
Vector3f::Map(array).fill(10); // create a temporary Map over array and sets entries to 10
int data[4] = {1, 2, 3, 4};
Matrix2i mat2x2(data); // copies data into mat2x2
Matrix2i::Map(data) = 2*mat2x2; // overwrite elements of data with 2*mat2x2
MatrixXi::Map(data, 2, 2) += mat2x2; // adds mat2x2 to elements of data (alternative syntax if size is not know at compile time)
// Solve Ax = b. Result stored in x. Matlab: x = A \ b.
x = A.ldlt().solve(b)); // A sym. p.s.d. #include <Eigen/Cholesky>
x = A.llt() .solve(b)); // A sym. p.d. #include <Eigen/Cholesky>
x = A.lu() .solve(b)); // Stable and fast. #include <Eigen/LU>
x = A.qr() .solve(b)); // No pivoting. #include <Eigen/QR>
x = A.svd() .solve(b)); // Stable, slowest. #include <Eigen/SVD>
// .ldlt() -> .matrixL() and .matrixD()
// .llt() -> .matrixL()
// .lu() -> .matrixL() and .matrixU()
// .qr() -> .matrixQ() and .matrixR()
// .svd() -> .matrixU(), .singularValues(), and .matrixV()
// Eigenvalue problems
// Eigen // Matlab
A.eigenvalues(); // eig(A);
EigenSolver<Matrix3d> eig(A); // [vec val] = eig(A)
eig.eigenvalues(); // diag(val)
eig.eigenvectors(); // vec
// For self-adjoint matrices use SelfAdjointEigenSolver<>