16. string, STL

cyan·2023년 12월 18일

CSED226

목록 보기
4/4

move constructor

#include <iostream>
#include <string>

int main() {
    std::string original = "Hello World";
    std::string moved(std::move(original)); // Move constructor

    std::cout << "Original: " << original << std::endl;
    std::cout << "Moved: " << moved << std::endl;

    return 0;
}

move constructor
original 변수에 들어 있던 string을 moved로 옮긴다. 이 때 original은 비어 있는 상태가 된다.

list initialization constructor: string(initializer_list<char> il)

List의 원소들로 string을 초기화할 수 있게 해 준다.

#include <iostream>
#include <string>
#include <initializer_list>

int main() {
    std::string piano_man = {'L', 
    #'i', 's', 'z', 't'};
    std::string comp_lang = {'L', 'i', 's', 'p'};

    std::cout << "Piano Man: " << piano_man << std::endl;
    std::cout << "Comp Lang: " << comp_lang << std::endl;

    return 0;
}

string input

string str;
cin >> str; //newline까지 읽는다
getline(cin, str);
getline(cin, str, !); //'!' 문자가 나올 때까지 읽는다

string comparison

<, ==, != 등이 overloading 되어있다.
size(str), length(str) (같은 함수임)

find()

smart pointer

auto_ptr (지금은 사용되지 x)

하나의 smart pointer만이 가리킬 수 있도록 한다.(ownership)
즉, ptr1 = ptr2 라고 했을때, ownership이 ptr1로 넘어가고, ptr2는 더이상 해당 메모리를 참조하지 못하게된다.

이런 식으로 =delete를 해줄 수 있다
unique_ptr(const unique_ptr&) = delete;
unique_ptr& operator=(const unique_ptr&) = delete;

unique_ptr

assign시에 소유권을 넘기지 않고 아예 막아버린다.(auto_ptr보다 안전)
그렇다고 완전 막는건 아니고, assign되는 놈이 임시 value라면 허용한다.
예를 들어 함수의 return 값으로 unique_ptr이 넘어왔다면 그 return값을 assign하는 건 괜찮다.
아니면 move semantic을 사용해서 unique_ptr의 ownership을 옮길 수 있다.
std::move() 이용해서 ptr1 = std::move(ptr2); 이런 assign은 괜춘

shared_ptr

reference counting을 이용한다. 특정 memory를 몇개의 pointer가 가리키는지를 체크하는 건데, assign이 발생할때마다 카운팅을 1씩 올리면 된다.
그리고 각 포인터 변수가 해제될때 카운팅을 1씩 줄이고, 마지막 남은 포인터 변수가 해제될때 가리키는 memory도 함께 해제되도록 한다.

{
    string* tmp
    = new string("Some String");
    shared_ptr<string> ptr1(tmp);
    {
        shared_ptr<string> ptr2;
        ptr2 = ptr1;
    }
}
shared_ptr<int> ptr1(new int[n]); (x)
shared_ptr<int[]> ptr2(new int[n]); (o)

STL (Standard Template Library)

  • container (array)
  • algorithm
  • iterator
  • function objects (함수와 같은 역할을 하는 object)
    generic programming의 예시이다.
  • 데이터 타입에 상관 없이 작동하는 코드

OOP와의 차이점

OOP는 데이터의 관리에 집중한다면 generic programming은 알고리즘 자체에 집중한다.

vector Template class

#include <vector> 를 통해 넣을 수 있다.
vector<int> ratings(5); 5개의 int를 담는 벡터이다.

이제 vector의 멤버 함수에 대해 알아보자.
.size() 는 원소의 개수, .swap(바꿀 vector명)은 두 vector를 바꾸는 것,
.push_back() vector 끝에 원소를 집어 넣는다
.erase(scores.begin(), scores.begin()+2) 정해진 범위를 지운다
.insert(position, val) 정해진 위치에 원소를 넣는다.
맨 뒤에 넣고 싶으면 scores.end()로 해야 한다.

iterator

vector<double>::iterator pd; 이런 식으로 iterator를 정의해줘야 한다.
포인터처럼 사용하면 된다.

ex)

vector<double> scores {10,9,8,7};
vector<double>::iterator pd;
pd = scores.begin()

또는 auto pd = scores.begin() 해도 된다.
++pd로 iterator가 다음 원소를 가리키게 할 수 있다.

begin()은 0번째 원소의 주소, end()는 n번째 (끝에서 하나 더 간 것!!)원소의 주소를 가리키는 포인터이다

for (auto pd = scores.begin(); pd != scores.end(); pd++) 이런 식으로 쓰면 된다.

range-based for loop (C++11)

for(double x: prices)
cout<<x<<endl;

iterator의 종류


vector<int>::iterator : random access
list<int>::iterator : bidirectional

위 표를 보면 알겠지만 각 iterator는 계층이 있으므로, 특정 conatainer가 구현하는 iterator가 algorithm이 원하는 iterator의 연산을 모두 지원한다면 당연히 적용될 수 있다.
예를들어 find 함수는 InputInterator를 필요로 하는데, 이는 어떤 종류의 iterator가 와도 상관없다.
반대로 random access iterator가 필요한 알고리즘에 input iterator가 있는 container는 적용될 수 없다.

포인터는 iterator의 요구사항을 모두 충족한다.(iterator의 한 종류라고 보면 된다)
따라서 기본 배열은 STL algorithm을 사용할 수 있다.

Container

container는 단일 object들을 저장.(여기서 object는 built-in type이어도 되고, class object여도 되고)
저장되는 object는 (1)copy construction과 (2)assignment 가 가능해야한다.
(copy ctor이랑 assignment operator 함수를 public에 정의해두란 소리, built-in type이면 다 지원하니까 상관없고)

1. Sequence

원소들이 특정 순서로 배열되어 있는 경우.
forward_list, list, queue, priority_queue, stack, vector
containers 11개 중 6개가 sequnce로 분류된다.
최소 forward iterator를 내부에 구현해야 한다.

2. Associative Container

값에 key를 결합하여 저장한다. 그리고 그 key를 이용하여 값을 찾는다.
tree 구조를 이용하여 구현되므로 검색/삽입이 sequence에 비해 빠르다.
set, multiset, map, multimap
모두 다 key는 고유하다.
set/multiset은 value와 key의 type이 같다.
multiset은 하나의 key에 여러 value가 올 수 있고, set은 1대1.
map/multimap은 value와 key의 type이 달라도 된다.
multimap은 하나의 key에 여러 value가 올 수 있고, map은 1대1.

unordered associative container
associative container처럼 key랑 value 쓰는데,
차이는 hash table로 구현한다는 것(그래서 unordered구만)
hash table이 tree보다 삽입/삭제

가 빠르다.

Function Objects (Functors) = 함수객체

iterator가 포인터의 일반화개념이었다면, functor는 함수의 일반화 개념이다.
함수처럼 func1(arg) 식으로 사용할 수 있는 것
function object로 ()과 같이 사용될 수 있는 것들을 의미한다.
1. 일반 함수 이름
2. 함수 포인터
3. () operator overload한 class의 "object"

predefined functor

#include <functional>에 미리 정의된 functor가 있다.
정확히 말하자면 functor을 만들 수 있는 class template들이 있다. (목록: classes에 operator classes 부분 보면 됨)
언제쓸까?

1) 기본 type들의 functor을 만들 때 쓰면 좋다.
double type은 + 연산이 내부적으로 구현 돼 있기 때문에,
만약 double의 덧셈 연산 functor(좁게 말하자면 함수)을 넘겨주려고 해도 그런 함수가 없어서 못넘겨준다.
우리가 임의로 double add(double x, duoble y){return x+y;} 를 만들수도 있지만, predefined를 이용해 plus add;라고 해 버리면 double형 덧셈 연산을 하는 add functor가 만들어진다.
(내부 구현은 위 함수와 똑같다.)
참고로 여기서 넘겨줄때 sort(~, plus<double>); 이렇게 하면 안 된다.
functor는 object이지 type이 아니다. sort(~, plus<double>()); 로 해야 함.

2) class type이더라도 보통은 사용한다.(해야한다.)
잘 생각해보면, STL 함수들이 원하는 functor는 operator() 일텐데, 보통 class 내에서 +, - 같은 연산만 구현할 뿐이지 "operator()는 덧셈을한다!" 이렇게 정해놓고 operator()가 덧셈을 하도록 구현하진 않는다.
따라서 마찬가지로 predefined를 통해 functor을 만들어서 넘겨주면 좋다.

Algorithms

1) 알고리즘 일반화를 위해 template을 사용하고
2) 컨테이너 내부 데이터 접근 일반화 표현을 위해 iterator을 사용한다.

Nonmodifying sequence operations

  • Operate on each element in a range
  • Ex) find(), for_each()

Mutating sequence operations

  • Operate on each element in a range.
  • But they can mutate or change the contents of a container
  • Ex) transform(), random_shuffle(), copy()

Sorting and related operations

  • Include several sorting functions
  • Also include a variety of other functions including the set operations

Generalized numeric operations

  • Include functions to sum the contents of a range, calculate the inner product, ...
  • #include

The STL and the string Class

string은 STL에 포함되어 있지 않다. 그러나 begin(), end(), rbegin(), rend() 등의 함수를 지원하고 stl interface를 사용 가능하다.

string str = "Good Morning, Good Afternoon, Good Bye!";
transform(str.begin(), str.end(), str.begin(), tolower);

initializer_list

initializer_list header의 initializer_list class template를 통해 가변 길이의 데이터들을 다룰 수 있다.

vector 같은 container들의 ctor 버전 중 하나는 initializer_list type을 인자로 받음으로써 {1,2,3} 같은 초기화 형식을 처리할 수 있는 것이다.
물론 배웠듯 언어자체에서 기본 제공하는 {}도 있다.
그래서 만약 특정 class에 initializer_list 이용한 ctor이 있다면 std::vector a{10}; 같은 경우엔 기본 제공{}가 아닌 initializer_list ctor을 사용하도록 한다.
두 경우 다 narrowing은 안된다.

0개의 댓글