문자열, 벡터와 배열
3.1 네임스페이스 using 선언
● using 선언 : 라이브러리 이름을 참조하는 일은 다소 귀찮을 수 있다. 다행히 네임스페이스 멤버를 사용하는 더 쉽고 안전한 방법
● using 선언은 이름마다 따로 해야 한다.
#include <iostream>
using std::cin;
using std::cout; using std::endl;
int main() {
...
}
● 헤더에는 using 선언을 포함하지 않는다.
● 주의사항 : 예제 코드를 컴파일하기 전에 적절한 #include와 using 선언을 추가해야 한다.
3.2 string 라이브러리 타입
● string은 가변 길이 문자열이다.
3.2.1 string 정의와 초기화 하기
● 직접 초기화와 복사 초기화
○ 복사 초기화 : =를 사용해 변수를 초기화 하면 오른쪽 피연산자인 초기 값을 복사하는 것
○ 직접 초기화 : =를 생략하는 그 외 경우
string s5 = "hiya"; // 복사 초기화
string s6(10, 'c'); // 직접 초기화이며 s6은 cccccccccc이다
3.2.2 string 연산
● 클래스에서는 객체를 만들고 초기화하는 방법과 함께 해당 클래스 타입 객체로 수행할 수 있는 연산도 정의함
● string 읽고 쓰기
○ 내장 타입에 대한 입력과 출력 연산처럼 string 연산자도 왼쪽 피연산자를 결과로 반환한다.
● 임의 개수 string 읽기
int main() {
string word;
while (cin >> word) { // 파일끝에 도달할 때까지 읽는다.
cout << word << endl; // 한 줄에 한 단어씩 출력한다.
}
return 0;
}
● getline을 사용해 한 줄 전체 읽기
○ 입력 내용에서 공백 문자를 무시하고 싶지 않을 때 >>연산자 대신 getline 함수를 사용한다.
int main() {
string line;
// 파일끝에 도달할 때까지 입력에서 한 번에 한 줄씩 읽는다.
while (getline(cin, line)) {
cout << line << endl;
}
return 0;
}
● getline 에서는 줄바꿈 문자를 만나면 읽기를 멈추고 반환하며 이 문자는 무시한다. 그러므로 줄바꿈 문자는 string에 저장하지 '않는다'.
● string empty와 size연산
○ empty 함수는 이름에서 쉽게 알 수 있는 string이 비었는지 나타내는 bool을 반환한다.
○ size 멤버에서는 string 길이, 즉 string의 문자 수를 반환한다.
● string::size_type 타입
○ size에서는 string::size_type을 반환한다.
○ string 클래스를 비롯해 다른 라이브러리 타입 대부분에서 여러 동반 타입(size_type 포함)을 정의 할 수 있다.
● size()를 사용하는 표현식에서 int를 사용하지 않음으로써 unsigned와 int 사이 변환으로 인한 문제를 피할 수 있다.
● string 비교하기
○ 규칙 1. 두 string 길이가 다르고 짧은 string의 모든 문자가 긴 것이 같은 위치에 있는 문자와 같다면 짧은 것이 긴 것보다 작다.
○ 규칙 2. 두 string에서 대응하는 위치의 어떤 문자가 서로 다를 때 비교 결과는 string이 다른 첫 번째 문자 비교 결과에 따른다.
string str = "Hello";
string phrase = "Hello World";
string slang = "Hiya";
// 규칙 1에 따라 str이 phrase 보다 작고 규칙 2에 따라 slang이 str과 phrase보다 크다.
● string 대입
○ string의 경우에도 한 string을 다른 것에 대입 가능함
● 두 string 더하기
string s1 = "Hello", s2 = " World";
string s3 = s1 + s2;
s1 += s2;
● 상수와 string 덧셈
○ string을 문자열 상수나 문자 상수와 함께 쓸 때는 + 연산자의 피연산자 중 하나는 string 타입 이어야 한다.
● 주의 사항 : 문자열 상수는 string 표준 라이브러리가 아니다. 문자열 상수와 string 라이브러리를 사용할 때 이 타입이 서로 다르다는 점을 반드시 기억해야 한다.
3.2.3 string 내 문자 다루기
● C라이브러리 헤더 대신 C++ 버전을 사용한다.
● 모든 문자를 처리하려면? 범위 기반 for를 사용한다.
○ for문은 지정한 순차열 내 요소를 반복하여 각 요소 값에 어떤 연산을 수행한다.
string str("some string");
// str의 문자를 한 줄에 하나씩 출력한다.
for (auto c : str) { // str의 모든 문자에 대해
cout << c << endl; // 현재 문자와 줄바꿈 문자를 출력한다
}
● 범위 for를 사용해 string 내 문자 변경하기
○ string 내 문자 값을 변경하려면 루프 변수를 참조자 타입으로 정의해야 한다.
string s("Hello World!!!");
// s를 대문자로 변환한다.
for ( auto &c : s) { // s 내 모든 char에 대해
c = toupper (c); // c는 참조자이므로 대입하면 s 내 char을 변경한다
}
cout << s << endl;
● 일분 문자만 처리하려면?
○ string 내 개별 문자에 접근하는 방법에는 첨자와 반복자를 사용하는 두 가지가 있다.
○ 첨자 연산자( [] 연산자 )에서는 접근할 문자의 위치를 나타내는 string::size_type 값 하나를 취하고 지정한 위치의 문자에 대한 참조자를 반환한다.
string s("some string");
if (!s.empty()) { // 출력할 문자가 있는지 확인
s[0] = toupper(s[0]); // s의 첫 문자에 새 값을 대입한다.
● 반복에 첨자 사용
● 첨자 연산은 자동으로 확인하지 않는다.
● 첨자를 사용해 임의 접근하기
○ 첨자를 계산해 지정한 문자를 직접 가져올 수도 있으며 이때는 문자열 내 문자에 접근하지 않아도 된다.
3.3 vector 라이브러리 타입
● vector는 타입이 모두 같은 객체의 모음이다.
● vector는 클래스 템플릿이다.
● 인스턴스화 : 컴파일러에서 템플릿을 사용해 클래스나 함수를 만드는 과정
vector<int> ivec; // ivec은 int 타입 객체를 담는다.
vector<Sales_item> Sales_vec; // Sales_item을 담는다.
vector<vector<sting>> file; // 요소가 vector인 vector
● vector는 템플릿이며 타입이 아니다. vector로 만들 타입에는 vector<int>처럼 요소 타입을 포함해야 한다.
3.3.1 vector의 정의와 초기화하기
vector<int> ivec; // 기본 초기화하므로 ivec에는 요소가 없다.
// ivec에 값을 지정한다.
vector<int> ivec2(ivec); // ivec의 요소를 복사해 ivec2에 넣는다.
vector<int> ivec3 = ivec; // ivec의 요소를 복사해 ivec3에 넣는다.
vector<string> svec(ivec2); // 오류 : svec에서는 int가 아니라 string을 담는다.
● vector를 목록 초기화하기
vector<string> articles = {"a", "an", "the"};
○ 복사 초기화나 중괄호 사용할 것
● 지정한 개수로 요소 수 만들기
○ 개수와 요수 값으로 vector를 초기화 할 수 있다.
vector<int> ivec(10, -1); // int 요소가 10개이며 각각은 -1로 초기화한다.
vector<string> svec(10, "hi");
● 값 초기화
○ 흔히 값을 생략하고 크기만 지정 할 수 있다.
● 목록 초기 값 또는 요소 개수
vector<int> v1(10); // v1은 값이 0인 요소가 10개이다.
vector<int> v2{10}; // v2는 값이 10인 요소가 1개이다
vector<int> v3(10, 1); // v3는 값이 1인 요소가 10개이다.
vector<int> v4{10, 1}; // v4는 값이 10, 1인 요소가 2개이다.
○ 중괄호는 객체를 목록 초기화할 수 있다면 그렇게 하겠다는 뜻이다.
3.3.2 vector에 요소 추가하기
● 일정 범위를 초기화 할때는 이용하기
vector<int> v2;
for (int i = 0; i < 100; i++) {
v2.push_back(i);
}
● vector에 요소를 추가하는 것에 내포된 의미를 프로그래밍하기
● 범위 for 본체에서는 반복하는 동안 순차열의 크기를 변경하지 말아야 한다.
3.3.3 다른 vector 연산
● 이걸 굳이?
● size_type을 사용하려면 이 타입을 정의한 타입 이름을 써야한다. vector 타입에는 항상 요소 타입을 포함한다.
○ ex) vector<int>::size_type
● 첨자 연산은 요소를 추가하지 않는다.
● vector와 string에 첨자 연산자를 사용하면 존재하는 요소를 가져올 뿐 요소를 추가하지 '않는다'.
3.4 반복자 소개
● 반복자를 사용해 포인터처럼 객체에 간접적으로 접근할 수 있다.
● 반복자인 경우 객체는 컨테이너 요소나 string의 문자이다.
3.4.1 반복자 사용하기
● 포인터와 달리 반복자는 주소 연산자를 사용해 얻지 않는다.
● 반복자를 사용하는 타입 : begin 와 end
● 컨테이너가 비었을 때 begin에서 반환하는 반복자는 end와 같다. 즉 둘 모두 끝 지난 반복자가 된다.
● 반복자 연산
○ 유효한 반복자는 == 또는 !=를 사용해 비교 가능
string s("some string");
if ( s.begin() != s.end()) { // s가 비어 있지 않은지 확인한다.
auto it != s.end(); // it는 s의 첫 문자를 나타낸다.
*it = toupper(*it); // 그 문자를 대문자로 만든다
}
● 반복자 이동하기
○ 반복자에서는 증가연산자를 사용해 다음 요소로 이동한다.
○ end에서 반환한 반복자는 요소를 나타내지 않으므로 증가 또는 감소시키지 말아야 한다.
● 반복자 타입
○ 반복자의 정확한 타입 역시 모르며 사실 알 필요 없음
● 역참조와 멤버 접근 결합하기
○ 반복자를 역참조하면 그 반복자로 나타내는 객체를 얻는다.
(*it).empty() // it를 역참조하고 결과 객체에 대해 empty를 호출한다.
*it.empty() // 오류 : it에서 empty로 명명한 멤버를 가져오려 하지만 it는 반복자이며 enpty 멤버가 없다
● 일부 vector 연산은 반복자를 무효화한다.
3.4.2 반복자 산술 연산
● 반복자 산술 연산 : string과 vector에 대한 반복자에서는 한 번에 여러 요소를 이동하는 연산
● 반복자에서 산술 연산
○ 정수 값을 반복자와 더하거나 뺄 때 결과는 같은 vector 또는 string 내 요소를 나타내거나 연관된 vector 또는 string의 마지막 요소 다음을 나타내야 한다.
● 반복자 산술 연산 사용하기
○ 반복자 산술 연산을 사용하는 고전적인 알고리듬은 이진 검색이다.
3.5 배열
● 얼마나 많은 요소가 필요한지 정확히 모르면 vector를 사용한다.
3.5.1 내장 타입 정의와 초기화 하기
● 배열을 정의할 때는 배열 타입을 지정해야 하며, 초기 값 목록에서 타입을 추론하기 위해 auto를 쓸 수 없다.
● 배열 요소를 명시적을 초기화 하기
○ 배열 초기화 못하면 하....
● 문자 배열은 특별하다
○ 상수 + 널문자 생각
● 복사와 대입 금지
● 복잡한 배열 선언 이해하기
int *ptr[10]; // ptrs는 int에 대한 포인터가 10개인 배열이다.
int &refs[10] = /* ? */; // 오류 : 참조자의 배열이 없음
int (*Parray)[10] = &arr; // Parray는 int가 10개인 배열을 가리킨다
int (&arrRef)[10] = arr; // arrRef는 int가 10개인 배열을 참조한다.
int *(&arry)[10] = ptrs; // arry는 포인터가 10개인 배열에 대한 참조자 이다
3.5.2 배열 요소에 접근하기
● 배열 요소에 접근할 때도 범위 for나 첨자 연산자를 사용 가능
3.5.3 포인터와 배열
● 배열 요소는 객체이므로 배열에 첨자 연산을 했을 때 결과는 배열 내 해당 위치에 있는 객체이다.
string nums[] = {"one", "two", "three"} // string 배열
string *p = &num[0]; // p는 nums의 첫 요소를 가리킨다
● 표현식 대부분에서, 배열 타입 객체를 사용할 때 실제로는 해당 배열의 첫 요소에 대한 포인터를 사용한다.
● 포인터는 반복자이다.
○ 배열 요소에 대한 포인터에서는 vector 나 string에 대한 반복자와 같은 연산을 제공한다.
int ar[] = {0,1,2,3,4,5,6,7,8,9};
int *p = arr; // p는 arr의 첫 요소를 가리킨다
++p; // p는 arr[1]을 가리킨다
● begin 와 end 라이브러리 함수
○ 배열은 클래스 타입이 아니므로 이 함수는 멤버 함수가 아니며, 대신 배열을 인자로 취한다.
int ia[] = {0,1,2,3,4,5,6,7,8,9}; // ia는 int가 10개인 배열이다.
int *beg = begin(ia); // ia의 첫 요소에 대한 포인터
int *last = end(ia); // ia의 마지막 요소 바로 다음 위치에 대한 포인터
● 내장 배열의 마지막 요소 '바로 다음' 위치에 대한 포인터는 vector의 end 연산에서 반환하는 반복자와 같은 방식으로 행동한다. 특히 끝 지난 포인터는 역참조하거나 증가시키지 말아야 한다.
● 포인터 산술 연산
○ 정수 값을 포인터에 더하거나 빼면 새로운 포인터를 결과로 얻는다.
○ 두 포인터를 뺀 결과는 ptrdiff_t 라이브러리 타입이다.
● 역참조와 포인터 산술 연산 사이의 상호작용
○ 정수 값을 포인터에 더한 결과는 포인터 그 자체이다.
int ia[] = {0,2,4,6,8}; // int 타입 요소가 5개인 배열
int last = *(ia + 4) // last를 ia[4] 값인 8로 초기화한다.
● 첨자와 포인터
int k = p[-2]; // p[-2]는 ia[0]과 같은 요소이다
○ vector와 string에서 사용하는 첨자와 달리 내장 첨자 연산자의 색인은 unsigned 타입이 아니다.
3.5.4 C형식 문자열
● 문자열은 문자 배열을 저장하고 널 종료를 한다.
● C 라이브러리 문자열 함수
○ 함수에 전달하는 포인터는 반드시 널 문자로 만친 배열에 대한 포인터야 한다.
● 문자열 비교하기
○ 문자열을 비교하려면 strcmp를 호출해야 한다.
● 목적지 문자열의 크기는 호출하는 쪽에서 책임진다.
3.5.5 오래된 코드와 함께 쓰기
● string 라이브러리와 C 형식 문자열 함께 사용하기
● 배열을 사용해 vector 초기화 하기
int int_arr[] = {0, 1, 2, 3, 4, 5};
vector<int> ivec(begin(int_arr), end(int_arr));
3.6 다차원 배열
● 다차원 배열 요소 초기화 하기
○ 초기화 하시면 됩니다.
● 다차원 배열 첨자 연산하기
○ 각 차원 별로 첨자를 사용 해야함
● 다차원 배열에 범위 for 사용하기
○ 다차원 배열을 범위 for에서 쓰려면 가장 안쪽 배열을 제외한 모든 루프 제어 변수를 참조자로 해야 한다.
● 포인터와 다차원 배열
○ 다차원 배열은 실제 배열의 배열임
이 선언에서 괄호는 필수이다.
int *ip[4]; // int에 대한 포인터의 배열
int (*ip)[4]; // int가 4개인 배열에 대한 포인터
● 타입 별칭을 사용하면 다차원 배열에 대한 포인터를 간단히 할 수 있다.