-
C++ Primer CH3. Strings, Vectors and Arraysc++ 2024. 1. 21. 16:10
3.1 Namespace using Declarations
scope operator
- ex)std::cin → std 네임스페이스에서 cin 네임을 사용하고 싶다.
using namespace std; cin >> val; // using 안쓸 경우, std::cin >> val;
- header파일 내에는 using 선언자(declaration) 사용하면 안됨
- 해당 header 파일을 사용하는 모든 파일 내에서 해당 네임스페이스가 사용되므로 예기치 못한 이름 충돌 발생 가능성 존재.
3.2 Library string Type
string
- variable-length sequence of characters
초기화 방법들
string s1; // default initialization, empty string string s2 = s1; // s2 is a copy of s1 string s3 = "hiya"; // s3 is a copy of the string literal, "hiya" string s4(10, 'c'); // s4 is "cccccccccc"
- string의 맨 끝에는 null character가 있을까?
- 없다. 배열에 문자열을 저장할 때만 컴파일러가 추가해준다.
- = 연산자의 역할은?
- right-hand side쪽 initializer를 복사해서 left-hand variable에 할당한다.
3.2.2 Operations on strings
string operations
os << s is >> s getline(is, s) s.empty() s.size() s[n] s1 + s2 s1 = s2 s1 == s2 s1 != s2 <, <=, >, >=
whitespace 기준으로 끊어서 입력받는 cin
string s; cin >> s // Hello World cout << s << endl; // Hello return 0;
getline 사용해서 전체 라인 읽기
- whitespace 무시하기 싫을 때 사용
- 개행문자 추가 안됨 → endl 추가해서 개행 + buffer flushing
int main() { string line; while (getline(cin, line)) { cout << line << endl; } return 0; }
string이 비어있는지 체크
- string.empty()
- return type: bool(true or false)
string의 크기 반환
- string.size()
- type: size_type
- size_type은 companion types중 하나이다.string::size_type
- -> unsigned type이다.
- -> machine-independent하기 위해서 도입
- int변수(signed)와 크기(unsigned)를 연산할 때 주의해야 하는 이유
- -> size() 반환타입은 unsigned이고, int-unsigned 중 unsigned가 더 우선순위 높으므로 int→unsigned로 타입 캐스팅 되면서, 만약 int가 음수값이었다면 매우 큰 값으로 해석되기 때문
Literal과 string 합치기
-
- 연산으로 합칠 때, + 양쪽 operand중 적어도 한쪽은 string 변수이어야 한다.
string s4 = s1 + ", "; // OK string s5 = "hello" + ", "; // ERROR, + 양쪽 다 literal 이므로. string s6 = s1 + ", " + "world"; // OK, (s1 + ", ") + "world" 이런식으로 처리된다. string s7 = "hello" + ", " + s2; // ERROR, ("hello" + ", ") 여기서 에러 발생
3.2.3 Dealing with the Characters in a string
string을 이루는 모든 character을 다루고 싶다.
Range-Based for
string s("Hello World!!!"); decltype(s.size()) punct_cnt = 0; for (auto c : s) { if (ispunct(c)) { ++punct_cnt; } } cout << punct_cnt << " punctuation characters in " << s << endl;
- ispunct()는 어떤 헤더파일에 있는가?
- -> cctype.h
- isalnum, isalpha 등 많은 함수들이 있다.
string 내의 character을 바꾸려고 Range-Based for 사용하기
ex) string 내의 모든 character를 대문자로 만들고 싶다.
string s("Hello World!!!"); for (auto &c : s) c = toupper(c); cout << s << endl;
- 여기서 &c 대신 c를 사용하면?
- -> c: s의 문자가 c로 "복사"되는 것이기 때문에 c를 대문자로 만들어줘도 문자열에 반영되지 않는다.
- -> &c: 개별 문자를 참조해서 그 문자 자체를 바꿔버린다.
string 내의 일부 문자만 처리하고 싶다면?
- subscript 혹은 iterator 사용
- subscript
- subscript operator: [index]
- index type: string_size(unsigned)
- usage: s[0]
- return type: char type reference
string s("some string"); if (!s.empty()) s[0] = toupper(s[0]);
for (decltype(s.size()) index = 0; index != s.size() && !isspace(s[index]); ++index) s[index] = toupper(s[index])
- for문의 condition 영역에서 &&가 사용되었다. 유의할 점은?
- -> &&의 왼쪽 operand가 true일 때만 오른쪽 operand가 평가된다.
- -> 따라서 문자열을 다 돌았는지 평가한 후 아직 다 돌지 않았을 때, 해당 위치의 문자가 스페이스인지 확인해주어야 index가 범위 내에 있는 경우에만 s[index]가 실행되도록 할 수 있다.
- for문에서 decltype()을 사용하면 좋은 점은?
- -> string.size()의 타입은 unsigned라서 무조건 0 이상이기 때문에 index가 0 미만이 되는걸 방지할 수 있다.
- -> 따라서 index < size()인지만 체크해주면 돼서 편하다.
const string s = "Hello"; for (auto &c : s)
- 위 코드에서 c의 타입은?
- -> const char
- subscript operator: [index]
3.3 Library vector Type
vector
- 타입이 같은 object들의 집합
- container에 속함
- class template이다.
- 벡터는 타입이 아니라 템플릿이다.
- vector<int>, vector<vector<int>>, …
- vector<>에 reference를 넣을 수 있을까?
- -> 넣을 수 없다. reference는 object가 아니기 때문이다.
vector 초기화하는 법
vector<int> vec; // default 초기화, 가장 흔하게 쓰임. 런타임시에 요소 추가됨 vector<int> vec2(vec); // vec의 모든 요소들을 vec2에 복사함. vector<int> vec3 = vec; // vec의 모든 요소들을 vec3에 복사함. vector<string> vec4 = vec; // error: 요소의 타입은 같아야 함. // 리스트로 초기값 넣어주기(list initializing) vector<string> vec = {"one", "two", "three"}; vector<string> vec{"one", "two", "three"}; vector<string> vec(10, "HI"); // 크기만 정해주고 싶을 때(Value Initialization) vector<int> vec(10); // 요소 10개, 모두 0으로 초기화됨 vector<string> vec(10); // 요소 10개, 모두 ""로 초기화됨.
- 크기만 정해주지 못할 경우
- vector의 요소 타입이 특정 클래스인데 default 초기화를 지원하지 않는 경우 무조건 초기자를 공급해줘야 함.
예외상황
vector<string> vec{10}; // list initialization 하려는데 요소값이 10이고 요구타입이 string이라서 // 10은 요소개 10개라는걸로 컴파일러가 해석한다. vector<string> vec{10, "hi"}; // 마찬가지로 list initialization 안되서 "hi"가 10개인걸로 바뀜
- vector의 크기를 지정해주는게 좋나?
- -> 보통 vector 크기 지정하는건 불필요하고 성능면에서도 안좋다.
- 예외: 모든 요소가 실제로 같은 값일 때
vector operations
vector<int> vec{1, 2, 3}; vec.push_back(t) vec.empty() vec.size() v[n] v1 = v2 v1 = {a, b, c,...} v1 == v2 // 크기가 같고 각 인덱스별 요소가 모두 같은지, v1 != v2 <, <=, >, >= for (auto &i : vec) {}
subscripting은 요소를 넣을 수 없다
vector<int> vec; for (decltype(vec.size()) idx = 0; idx != 10; ++idx) vec[idx] = idx; // error: vec은 요소가 없다.
3.4 Introducing Iterators
사용법
string s("some string"); if (s.begin() != s.end()) { // s의 시작위치 = s.begin(), s의 끝 + 1 = s.end() auto it = s.begin(); *it = toupper(*it); } *iter iter->mem ++iter --iter iter1 == iter2 iter1 != iter2
- for문에서 ≤ 10 안쓰고 ≠ 10 쓰는 이유?
- -> c++ 개발자들은 iterator로 순회하는게 익숙하고, it ≠ x.end() 이렇게 쓰는게 익숙하다
- -> ≠나 == operator는 모든 라이브러리 컨테이너에서 정의되어 있으므로 습관화하면 실수 줄일 수 있다.
const vector<int>의 begin() 타입은 vector<int>::const_iterator다.
vector<int>의 begin() 타입은 vector<int>::iterator다.
const 안 붙은 vector 등도 const_iterator 만들고 싶을 때, cbegin, cend를 사용한다.
iterator 멤버에 접근하는 법
// 1. dereference + access (*it).empty() // OK *it.empty() // ERROR, access 먼저 일어남 // 2. arrow operator it->empty() // OK
iterator를 사용하는 루프에서 컨테이너에 요소 추가해서는 안된다.(크기가 바뀌기 때문)
iter + n iter - n iter += n iter -= n iter1 - iter2 >, >=, <, <=
3.5 Arrays
얼마나 많은 요소를 사용할건지 알 수 없으면 vector를 사용해라
배열 초기화
unsigned cnt = 42; constexpr unsigned sz = 42; int arr[10]; // OK, int 10개 배열 int *parr[sz]; // OK, 42개 int*포인터 string bad[cnt]; // cnt가 const 아님 string strs[get_size()]; // get_size()가 constexpr이면 OK, 그 외엔 error
명시적 초기화
const unsigned sz = 3; int a1[sz] = {0, 1, 2}; int a2[] = {0, 1, 2}; int a3[5] = {0, 1, 2}; string a4[3] = {"hi", "bye"}; int a5[2] = {0, 1, 2}; // error
char 배열은 특별하다
char a1[] = {'c', '+', '+'}; // 크기3 char a2[] = {'c', '+', '+', '\\0'}; // 크기4 char a3[] = "C++"; // null terminator가 자동으로 '\\0' 붙여줌. 크기4 const char a4[6] = "Daniel"; // error: a4 크기 6이고 이미 꽉차서 '\\0' 못붙힘
배열은 복사나 할당 불가
int a[] = {0, 1, 2}; int a2[] = a; // error a2 = a; // error
배열 포인터, 배열 참조자
#include <iostream> using namespace std; int main() { int arr[10] = {1, 2, 3, 4, 5}; int (&arrRef)[10] = arr; // arrRef는 arr이라는 object의 참조자이다. int (*Parr)[10] = &arr; // Parr은 arr의 주소값을 가리키는 포인터다. arrRef[0] = 10; cout << arr[0] << endl; *Parr[0] = 13; cout << *Parr[0] << endl; cout << arrRef[0] << endl; int* ptrs[10]; int* (&arry)[10] = ptrs; // arry는 int*를 요소로 가지는 ptrs 배열의 참조자이다. }
배열의 요소에 접근하기
- 인덱스 타입으로 size_t 해야 함
- machine-specific한 충분히 큰 unsigned type
checking subscript values
배열 범위를 넘는 인덱스를 조회하는 경우가 가장 많이 발생하는 보안 문제다.
컴파일도 잘되고 실행도 잘돼서 치명적인 에러 생길 수 있다.
3.5.3 Pointers and Arrays
배열은 보통 컴파일러가 포인터로 바꿔버린다.
string num[] = {"hell", "lo", "world"}; string *p = &nums[0]; // 첫번째 "hello"를 가리키는 포인터 string*p2 = nums; // p2 = &nums[0]과 동일
포인터는 이터레이터다
int arr[] = {1, 2, 3, 4}; int *p = arr; // p는 arr의 첫번째 요소 가리킴 ++p; // p -> arr[1] 가리킴
포인터 대수 연산
배열의 인덱스 타입은 unsigned가 아니라서 음수도 에러가 안뜬다.
3.6 multi dimensional array
size_t cnt = 0; int ia[rowCnt][colCnt]; for (auto &row : ia) for (auto & val : row) { val = cnt; ++cnt; } for (auto row : ia) for (auto val: row) {} // error: 컴파일 안됨 // 둘 간의 차이 // auto &row 결과는 4개의 int를 가지는 배열의 참조자이다. // auto row의 결과는, row가 참조자가 아니라서 컴파일러가 row를 array의 첫번째 원소를 가리키는 포인터 // 로 바꿔버린다. 따라서 내부 for문 돌지 못한다.
int *arr[4]; int (*arr)[4];
- 둘 간의 비교
- -> 두번재는 4개의 int를 가지는 배열을 가리키는 포인터 arr
- -> 첫번째는 4개의 int*요소를 가지는 배열 arr
'c++' 카테고리의 다른 글
C++ Primer CH7. Classes (0) 2024.01.21 C++ Primer CH6. Functions (0) 2024.01.21 C++ Primer CH4. Expressions (0) 2024.01.21 C++ Primer CH2. Variables and Basic Types (0) 2024.01.21 C++ Primer CH1. Getting Started (0) 2024.01.21