음악, 삶, 개발

3. Strings, Vectors, and Arrays 본문

개발 공부/C++ Primer (5th Edition)

3. Strings, Vectors, and Arrays

Lee_____ 2020. 8. 28. 20:20

소개

Chapter 2 에서 다루었던 내장 type 들에 추가하여,

C++ 는 추상 데이터 type 에 대한 풍부한 라이브러리를 제공한다.

이중 가장 중요한 라이브러리 type 은,

가변길이의 문자열을 제공하는 string 과 가변 크기의 컬렉션을 정의하는 vector 이다.

stringvector와 관련된 것은 iterator라고하는 동반자 (companion) type으로,

string의 문자 또는 vector의 요소에 액세스하는 데 사용된다.

라이브러리에 의해 정의된 string vector type들은 원시적인 내장 array type 의 추상화 (abstraction) 이다.

이 Chapter 에서는 array 를 다루고, 라이브러리 vector string type 을 소개한다.

Chapter 2 에서 다루었던 내장형 type 들은 C++ 언어가 직접적으로 정의한것들이다.

이러한 type들은 숫자 또는 문자와 같이 대부분의 컴퓨터 하드웨어에있는 기능을 나타낸다.

표준 라이브러리는 컴퓨터 하드웨어가 일반적으로 직접 구현하지 않는 추가적인 high-level type들을 정의한다.

이 Chapter 에서는, 가장 중요한 라이브러리 type 들인 string vector 를 소개할것이다.

string 은 가변길이의 문자열이다.

vector 는 주어진 type 의 객체를 저장하는 가변길이의 객체 sequence 이다.

결과적으로, array 는 라이브러리가 제공하는 string vector type 보다 사용하기 불편하다.

이 라이브러리에 대한 탐구를 시작하기전에,

먼저 라이브러리에 정의 된 이름에 대한 액세스를 단순화하는 메커니즘을 살펴 보자.


3.1 Namespace using Declarations

지금까지, 우리의 프로그램들은 우리가 사용하는 라이브러리 이름이 std namespace 안에 있다고 

명시적으로 나타냈었다.

예를 들어, 표준 input 을 읽기위해 std::cin 이라고 작성했었다.

이 이름들은 scope 연산자 :: 를 사용한다.

이는 컴파일러가 왼쪽 피연산자의 범위에서 오른쪽 피연산자의 이름을 찾아야 함을 의미한다.

따라서 std::cin 은 namespace std 안에 cin 이라는 이름을 우리가 사용하고싶다고 말하는것이다.

이 표기법으로 라이브러리 이름을 참조하는 것은 번거로울 수 있다.

다행히, namespace 멤버들을 사용하는 쉬운 방법이 있다.

가장 안전한 방법은 using 선언이다.

18.2.2(p. 793) 에서 namespace 로부터 이름을 사용하는 다른 방법을 다룰것이다.

using 선언은 namespace 안에 이름을 namespace_name:: 접두사를 붙히지않고 사용할수있게해준다.

using 선언은 아래와 같은 형태로 작성한다.

using namespace::name;

using 선언이 완료되었다면, 이제 이름에 바로 접근할수있다.

#include <iostream>

// using declaration; when we use the name cin, we get the on from the namespace std
using std::cin;

int main() {

    int i;
    cin >> i;       // ok : cin is a synonym for std::cin
    cout << i;      // error : no using delcaration; we must use the full name
    std::cout << i; // ok : explicitly use cout from namespace std
    return 0;
    
}

A Separate using Delcaration Is Required for Each Name

각각의 using 선언이 각 이름에 필요하다.

using 선언은 단일 namespace 멤버를 소개한다.

이 행동은 우리가 사용하는 이름을 지정할수있게해준다.

예를 들어, 라이브러리 이름을 위해 using 선언들을 사용하는 프로그램을 작성해보자.

#include <iostream>

// using declarations for names from the standard library
using std::cin;
using std::cout; using std::endl;

int main() {

    cout << "Enther two number : " << endl;
    int v1, v2;
    cin >> v1 >> v2;
    cout << "The sum of " << v1 << " and " << v2 << " is " << v1 + v2 << endl;
    return 0;

}

cin, cout, endl 에 대한 using 선언은 우리가 이 이름들을 std:: 접두사 없이 사용할수있음을 의미한다.

C ++ 프로그램은 자유 형식이므로 각 using 선언을 한 줄에 넣거나 여러 개를 한 줄에 결합 할수있다.

이때 중요한것은 사용하는 각 이름에 대해 using 선언이 있어야하며 각 선언은 세미콜론으로 끝나야한다는 것이다.

Header Should Not Include using Declarations

헤더안에 코드들은 일반적으로 using 선언을 사용해서는안된다.

이유는 헤더안의 내용들이 프로그램이 include 하는곳에 복사되기때문이다.

헤더가 using 선언을 하면, 이 헤더를 포함하는 모든 프로그램이 똑같은 using 선언을 얻는다.

결과적으로, 이 특정 라이브러리 이름의 사용을 의도하지않았던 프로그램이 예상치못한 이름 충돌에 직면할수있다.

A Note to the Reader

이 시점부터 예제에서는 표준 라이브러리에서 사용하는 이름에 대해 using 선언이 만들어 졌다고 가정한다.

따라서 텍스트 및 코드 예제에서 std :: cin이 아닌 cin을 참조한다.

또한 코드 예제를 짧게 유지하기 위해 using 선언과 #include 지시문 표시하지 않을것이다.
부록 A의 표 A.1 (p. 866)에는 이 책에서 사용하는 표준 라이브러리의 이름과 해당 헤더가 나열되어 있다.

 

Warning

 

독자는 컴파일하기 전에 예제에 적절한 #includeusing 선언을 추가해야한다는 점을 알고 있어야한다.


3.2 Library string Type

string 은 가변길이의 문자열이다.

string type 을 사용하기위해서는, 헤더에 #include <string> 을 해야한다.

string 은 라이브러리의 일부이기때문에, std namespace 를 사용한다.

#include <string>
using std::string;

이 섹션에서는, 가장 일반적인 string 연산에 대해 다루고,

9.5(p. 360) 에서 추가적인 연산을 다룰것이다.

 

Note

 

표준은 라이브러리 type이 제공하는 연산을을 지정하는 것 외에도

구현자에게 효율성 요구 사항을 부과한다.

결과적으로 라이브러리 type은 일반 용도로 충분히 효율적이다.


3.2.1 Defining and Initializing strings

각 클래스는 해당 type 의 객체가 어떻게 초기화될지를 정의한다.

클래스는 일반적으로 각 type 의 객체를 초기화하는 매우 다양한 방법들을 정의할것이다.

각 방법은 우리가 제공하는 initializer의 수 또는 이러한 initializer의 type에 따라 다른 방법들과 구별되어야한다.

Table 3.1 은 string 을 초기화하는 가장 일반적인 방법들을 소개한다.

string s1; // default initialization; s1 is the empty string
string s2 = s1; // s2 is a copy of s1
string s3 = "hiya"; // s3 is a copy of the string literal
string s4(10, 'c'); // s4 is cccccccc

Table 3.1. Ways to Initialize a string

string 을 의 default 초기화는 empty string 이다.

즉, 아무 문자도 없는 string 이라는것이다.

초기화할때 string 리터럴을 제공하면, 리터럴의 문자들(하지만 리터럴끝에 null 캐릭터는 제외하고)이 

새롭게 생성된 string 으로 복사된다.

문자와, 문자의 수를 제공하면, string 은 주어진 문자의 많은 복사본을 포함한다.

Direct and Copy Forms of Initialization

2.2.1(p. 43) 에서 우리는 C++ 가 여러가지 다른 형태의 초기화 방식들을 보았다.

string 을 사용하여, 우리는 서로가 어떻게 다른지 이해할수있을것이다.

변수를 = 을 사용하여 초기화할때,

우리는 컴파일러에게 오른쪽 initializer를 복사(copy)하여 객체를 초기화하도록 요청하는것이다.

= 을 생략하면, 직접 초기화 (direct initialization) 이다.

단일 initializer 를 가지고있다면, copy 혹은 direct 둘다 사용할수있다.

위의 s4 와 같이, 만약 변수를 1개 이상의 값으로 초기화하려하면, 반드시 direct 형태의 초기화를 사용해야한다.

string s5 = "hiya"; // copy initialization
string s6("hiya"); // direct initialization
string s7(10, 'c'); // direct initialization; s7 is cccccccccc

여러 값을 사용하려면 복사 할 임시(temporary) 객체를 명시적으로 생성하여 초기화의 복사 형식을 간접적으로 사용할 수 있다.

string s8 = string(10, 'c'); // copy initialization; s8 is cccccccccc

s8 의 initializer인 string(10, 'c') 는 주어진 크기와 문자값을 사용하여 string 객체를 생성한후,

s8 로 값을 복사한다.

아래와 깉이 작성할수도있다.

string temp(10, 'c'); // temp is cccccccccc
string s8 = temp; // copy temp into s8

s8 을 초기화하기위해 사용한 이 코드가 legal 이라도, 

이것은 s7과 같은 초기화방식보다 가독성이 좋지않고, 어떠한 장점을 가지고있지않다.


3.2.2 Operations on strings

객체 생성 및 초기화 방법을 정의하는 것과 함께 클래스는 클래스 type의 객체가 수행 할 수있는 연산도 정의한다.

우리의 Sales_item 클래스의 isbn 함수처럼, 클래스는 이름에 의해 호출되는 연산을 정의할수있다.

클래스는 또한 <<, + 같은  다양한 연산자가 클래스 type 의 객체에게 무엇을 의미하는지를 정의할수도있다.

Table 3.2 는 가장 기본적인 string 연산을 나열한다.

 

Table 3.2. string Operations

Reading and Writing strings

우리가 Chapter 1 에서 보았듯이, 우리는 iostream 라이브러리를,

int double 같은 내장형 type 의 값을 읽고, 쓰기위해 사용한다.

이와 동일한 IO 연산자를 string 을 읽고 쓰기위해 사용할수있다.

// Note: #include and using declarations must be added to compile this code

int main() {

    string s; // empty string
    cin >> s; // read a whitespace-separated string into s
    cout << s << endl; // write s to the output

    return 0;

}

이 프로그램은 s 라는 빈 string 으로 시작한다.

다음 줄은 표준 input 을 읽고, 읽은것을 s 에 저장한다.

string 입력 연산자는 선행 공백 (예 : 공백, 줄 바꿈, 탭)을 읽고 버린다.

그런 다음 다음 공백 문자가 나타날 때까지 문자를 읽는다.

따라서, 이 프로그램의 입력이 Hello World! 라면, 

출력은 추가 공간없는 Hello 일것이다.

내장 type 의 입,출력 연산들처럼, string 연산자는 결과로 왼쪽 피연산자를 return 한다.

따라서,  여러개의 읽기 쓰기를 묶을수있다.

string s1, s2;
cin >> s1 >> s2; // read first input into s1, second into s2
cout << s1 << s2 << endl; // write both strings

위의 프로그램에 Hello World! 를 입력하면,

출력은 "HelloWorld!" 이다.

Reading an Unknown Number of strings

1.4.3(p. 14) 에서 우리는 길이를 알수없는 int 값을 읽는 프로그램을 작성했었다.

이와 유사하게, string 을 읽는 프로그램을 만들어보자.

int main() {

    string word;

    // read until end-of-file
    while (cin >> word) {

        cout << word << endl; // write each word followed by a new line

    } 

    return 0;

}

이 프로그램에서 우리는 int 가 아닌 string 일 읽는다.

while 의 조건문은 우리의 이전 프로그램과 유사하게 실행된다.

이 조건은 읽기를 완료후 stream 을 테스트한다.

stream 이 유효하다면 ( 파일의 끝, 또는 유효하지않은 입력이 아니라면) while 의 body 가 실행된다.

body 는 우리가 읽은 값을 표준 출력으로 인쇄한다.

end-of-file 또는 유효하지않은 input 을 만나면 while 을 빠져나온다.

Using getline to Read an Entire Line

가끔 우리는 우리의 입력의 빈 공간(whitespace)을 무시하고싶지않을수있다.

이런 경우, >> 연산자대신 getline 함수를 사용한다.

getline 함수는 input 스트림과 string 을 받는다.

이 함수는 첫 번째 줄바꿈을 포함하여 주어진 스트림을 읽고 

줄바꿈을 포함하지 않고 읽은 내용을 string 인자에 저장한다.

getline줄바꿈을 본 후에는 입력의 첫 번째 문자이더라도 읽기를 중지하고 반환한다.

입력의 첫 문자가 줄바꿈이면 결과 string은 빈 string이다.

input 연산자처럼, getline 은 istream 의 인자를 return 한다.

결과적으로, 입력 연산자를 조건으로 사용할 수있는 것처럼 getline을 조건으로 사용할 수 있다.

예를 들어, 한 줄에 한 단어를 쓴 이전 프로그램을 다시 작성하여 한 번에 한 줄씩 쓸 수 있다.

int main() {

    string line;

    // read input a line at a time until end-of-file
    while (getline(cin, line)) {

        cout << line << endl;

    }

    return 0;

}

줄은 줄바꿈을 포함하지 않기 때문에 우리는 직접 작성해야한다.

평소처럼 endl을 사용하여 현재 줄을 끝내고 buffer를 플러시한다.

 

Note

 

getline이 return하는 줄바꿈은 버려진다.

줄바꿈은 문자열에 저장되지 않는다.

The string empty and size Operations

empty 함수는 string 이 empty 인지 아닌지를 나타내는 bool 을 return 한다.

Sales_itemisbn 멤버처럼, empty string 의 멤버이다.

이 함수를 호출하기위해서, empty 함수를 호출하고자하는 객체를 지정하기위해 . 연산자를 사용한다.

이제 비어있지않은 줄만 인쇄하도록 우리의 프로그램을 수정해보자.

// read input a line at a time and discard black lines

while (getline(cin, line)) {

    if (!line.empty()) {

        cout << line << endl;

    }

}

이 조건문은 논리 연산자 ! (NOT) 을 사용한다.

이 연산자는 피연산자의 bool 값의 역(inverse) 을 return 한다.

이 경우, str empty 라면 조건은 true 다.

size 멤버는 string 의 길이 (즉, 문자의 수) 를 return 한다.

문자의 수가 80 이상인 줄만 출력하기위해 size 를 사용할수있다.

string line;

// read input a line at a time and print lines that are longer than 80 characters
while (getline(cin, line)) {

    if (line.size() > 80) {

        cout << line << endl;

    }

}

The string::size_type Type

size 가 return 하는것은 unsigned int 라고 생각할것이다.

대신에, size string::size_type 의 값을 return 한다.

이 type 은 약간의 설명이 필요하다.

string 클래스와 대부분의 다른 라이브러리 type은 여러 동반자 (companion) type을 정의한다.

이러한 동반자 type을 사용하면 기계로부터 독립적 인 방식으로 라이브러리 type을 사용할수있다.

size_type 은 동반자 type중 하나다. 

string 에 정의된 size_type 을 사용하기위해서, string 클래스에 정의된 size_type 이라고 말하는 

scope 연산자 :: 를 사용한다.

string::size_type 이 정확히 어떠한 type 인지 몰라도, 

어떠한 string size 든 보관하기에 충분한 unsigned type 이라는것을 우리는 알수있다.

string size 연산의 결과값을 저장하는 변수는 string::size_type 이어야한다.

C+11

물론 string :: size_type을 입력하는 것은 tedious 하다.

C+11 의 새로운 표준에서는, 

auto 또는 decltype (§ 2.5.2, p. 68)을 사용하여 컴파일러에게 적절한 type을 제공하도록 요청할수있다.

auto len = line.size(); // len has type string::size_type

size unsigned type 을 return 하기때문에,

signed unsigned 를 mix 하는것은 예측할수없는 (surprising) 결과를 가질수있음을 기억하는것이 매우 중요하다.

예를 들어, n 이라는 int 가 있다면, int 는 음수를 저장할수있다.

따라서, s.size() < n 는 거의 true 를 산출할것이다.

왜냐면 unsigned signed 을 비교할때, n 안에 음수는 매우 큰 unsigned 값으로 변환되기때문이다.

 

Tip

 

우리는 unsigned int 사이의 변환으로 인해 발생하는 문제를,

size() 를 사용하는 표현식에 int 를 사용하지않음으로써 피할수있다.

Comparing strings

string 클래스는 string 들을 비교하는 몇가지 연산자들을 정의한다.

이 연산자들은 string 들의 각 문자를 비교하도록 작동한다.

이 비교들은 각 문자의  대소문자 구분을 한다. (case-sensitive) 

동등(equality) 연산자 == 또는 !=  는 2개의 string들이 서로 같은지 같지않은지를 비교한다.

두 string 이 똑같은 문자들과 똑같은 길이를 가지고있다면 이 둘은 서로 equal 이다.

관계 (relational) 연산자 <, <=, >, >= 는 

string 이 다른 string 보다 작은지, 작거나 같은지, 큰지, 크거나 같은지를 테스트한다.

이 연산자들은 case-sensitive 인 사전과 동일한 전략을 사용한다.

 

1. 두 string의 길이가 다르고 짧은 string의 모든 문자가 긴 string의 해당 문자와 ​​같으면 짧은 string이 긴 string보다 작다.

2. 두 string의 해당 위치에있는 문자가 다른 경우 string비교 결과는 string이 다른 첫 번째 문자를 비교 한 결과이다.

 

예를 들어보자.

string str = "Hello";
string phrase = "Hello World";
string slang = "Hiya";

첫번째 규칙에 따라, strphrase 보다 작다.

2번째 규칙에 따라, slang strphrase 보다 크다.

Assignment for strings

일반적으로 라이브러리 type들은 내장 type을 사용하는 것만 큼 쉽게 라이브러리 type을 사용하도록 만들기위해 노력한다.

따라서, 라이브러리 type 의 대부분은 대입(assignment) 를 지원한다.

string 의 경우, string 객체를 다른 객체에 대입할수있다.

string st1(10, 'c'), st2; // st1 is cccccccccc; st2 is an empty string
st1 = st2; // assignment : replace contents of st1 with a copy of st2
                  // both st1 and st2 are nwo the empty string

Adding Two strings

string을 더하면 왼쪽과 오른쪽 피연산자를 합친 새 string이 생성된다.

즉, string에 더하기 연산자 (+)를 사용하면 결과는 왼쪽 피연산자에있는 문자와 오른쪽 피연산자의 복사 본인 string이 된다.

복합 할당 (compound assignment) 연산자 (+=) (§ 1.4.1, p. 12)는 오른쪽 피연산자를 왼쪽 string에 추가한다.

string s1 = "hello, ", s2 = "world\n";
string s3 = s1 + s2; // s3 is hello, world\n
s1 += s2; // equivalent to s1 = s1 + s2

Adding Literals and strings

앞서 2.1.2(p. 35) 에서 보았듯이, 

주어진 type에서 예상 type으로의 변환이있는 경우 다른 type이 예상되는 한 type을 사용할 수 있다.

string 라이브러리를 사용하면 문자 리터럴과 문자열 리터럴 (§ 2.1.3, p. 39)을 string로 변환 할수있다.

string 이 필요한 위치에, 이 리터럴들을 사용할수있기때문에, 

우리의 이전 프로그램을 다시 작성해보자.

string s1 = "hello", s2 = "world"; // no punctuation in s1 or s2
string s3 = s1 + ", " + s2 + '\n';

string 과 문자 리터럴을 합칠때, + 연산자 앞뒤로 사용되는 피연산자중 하나는 만드시 string type 이어야한다.

string s4 = s1 + ", "; // ok : adding a string and a literal
string s5 = "hello" + ", "; // error : no string operand
string s6 = s1 + ", " + "world"; // ok : each + has a string operand
string s7 = "hello" + ", " + s2; // error : can't add string literals

s4s5 의 초기화는 오직 단일 연산을 하고있기때문에, 이 초기화가 legal 인지 아닌지를 판단하는것은 쉽다.

s6 의 초기화는 당황스러울수있다. 

하지만 그러나 입력 또는 출력 표현식을 함께 연결할 때와 거의 동일한 방식으로 작동한다 (§ 1.2, p. 7).

string s6 = (s1 + ", ") + "world";

위의 코드에서 (s1 + ", ") 는 두 번째 + 연산자의 왼쪽 피연산자를 형성하는 string을 return한다.

아래와 같이 위의 코드를 작성할수도있다.

string tmp = s1 + ", "; // ok + has a string operand
s6 = temp + "world"; // ok : + has a string operand

반면에 s7 의 초기화는 illegal 이다.

s7 의 코드에 괄호를 쳐서 다시 보자.

string s7 = ("hello" + ", ") + s2; // error: can't add string literals

이제 첫 번째 하위 표현식이 두 개의 문자 리터럴을 추가한다는 것을 쉽게 알 수 있다.
이런 방법은 가능하지않으므로, error 이다.

 

Warning

 

역사적 이유와 C와의 호환성 때문에 문자열 리터럴은 표준 라이브러리 string이 아니다.

문자열 리터럴과 라이브러리 string을 사용할 때 이러한 type이 서로 다르다는 점을 기억하는 것이 중요하다.


3.2.3 Dealing with the Characters in a string

우리는 가끔 string 의 각 문자를 다룰 필요가 생긴다.

string 이 공백 (whitespace) 를 가지고있는지,

또는 각 문자를 소문자로 바꾸기위해서, 또는 주어진 문자가 있는지 없는지 등등이다.

이러한 종류의 프로세싱은 문자 자체에 접근하는 방법과 관련이 있다.

가끔은 모든 문자를 프로세스할 필요가 있을것이다.

가끔은 오직 특정한 문자만을 프로세스하고싶을수도있고,

또는 어떠한 조건이 만나면 프로세싱을 멈추고자할수도있다.

문자 처리의 다른 부분은 문자의 특성을 알고 변경하는 것이다.

이런 작업은 표 3.3 (뒷면)에 설명 된 라이브러리 함수 세트에 의해 처리된다.

이 함수는 cctype 헤더안에 정의되어있다.

Table 3.3. cctype Functions

Advice : Use the C++ Verions of C Library Headers

C 라이브러리 헤더대신 C++ 버전의 라이브러리를 사용하라.

C ++ 용으로 특별히 정의 된 기능 외에도 C ++ 라이브러리는 C 라이브러리를 통합한다.

C 의 헤더에는 name .h 형식의 이름이 있습니다.

이러한 헤더의 C ++ 버전은 c 이름으로 명명된다.

즉, .h 접미사를 제거하고 이름 앞에 문자 c를 붙인다.

이 c 는 이 헤더가 C 라이브러리의 일부임을 나타낸다.

따라서 cctype은 ctype.h와 동일한 내용을 갖지만 C ++ 프로그램에 적합한 형식이다.

특히 cname 헤더에 정의 된 이름은 std 네임 스페이스 내부에 정의되어 있지만 .h 버전에 정의 된 이름은 그렇지 않다.

일반적으로 C ++ 프로그램은 .h 버전 이름이 아닌 헤더의 cname 버전을 사용해야한다.

이렇게하면 표준 라이브러리의 이름이 std 네임 스페이스에서 일관되게 발견된다.

.h 헤더를 사용하면 프로그래머가 C에서 상속 된 라이브러리 이름과 C ++에 

고유 한 라이브러리 이름을 기억해야하는 부담이 있다.

C+11 : Processing Every Character? Use Ranged-Based for

문자열의 모든 문자에 대해 뭔가를하고 싶다면, 가장 좋은 방법은 새로운 표준 인 range for 문을 사용하는 것이다.

이 문은 주어진 시퀀스의 요소를 반복하고 해당 시퀀스의 각 값에 대해 일부 작업을 수행한다.

아래와 같이 작성할수있다.

for (declaration : expression) {

    statement

}

구문 형식은 expression이 시퀀스를 나타내는 type의 객체이고 

선언은 시퀀스의 기본 요소에 액세스하는 데 사용할 변수를 정의한다.

각 iteration에서 선언의 변수는 expression의 다음 요소 값에서 초기화된다.

string은 일련의 문자를 나타내므로 string을 range for 문에서 사용할 수 있다.

간단한 예로서 range for를 사용하여 string의 각 문자를 자체 출력 행에 인쇄 할 수 있다.

string str("some string");

// print the characters in str one character to a line
for (auto c : str) { // for every char in str
 
    cout << c << endl; // print the current character followed by a newline

}

for 루프는 변수 cstr과 연관시킨다.

루프 제어 변수를 다른 변수와 같은 방식으로 정의한다.

이 경우 auto (§ 2.5.2, p. 68)를 사용하여 컴파일러가 c의 유형을 결정할 수 있도록한다.

이 경우에는 char이다.

갹 iteration 마다 str의 다음 문자가 c로 복사된다.

따라서 이 루프를 "문자열 str의 모든 문자 c에 대해, 무언가를 해라" 라고 읽을 수 있다.

이 경우 "무언가"는 문자와 개행 문자를 인쇄하는것이다.

좀 더 복잡한 예로서 range forispunct 함수를 사용하여 string의 구두점(!) 문자 수를 계산해보자.

string s("Hello World!!!");

// punct_cnt has the same type that s.size returns : see 2.5.3(p. 70)
decltype(s.size()) punch_cnt = 0;

// count the number of punctuation characters in s
// for every char in s
for (auto c : s) {

    // if the character is punctuation
    if (ispunch(c)) {

        ++punch_cnt; // increment the punctuation counter

    }

}

cout << punch_cnt << " punctuation characters in " << s << endl;

이 프로그램의 출력은 다음과 같다.

위의 코드에서, 우리는 punch_cnt 를 선언하기위해 decltype 을 사용하고있다.

punch_cnt 의 type 은 s.size 를 호출함으로써 반환되는 string::size_type 이다.

string 의 각 문자를 처리(process) 하기위해 range for 를 사용하고있다.

여기서는 각 문자가 구두점(!) 인지 체크하고있다.

그렇다면, 증가 연산자를 사용하여 counter 1을 더한다.

range for 가 끝이나면, 결과를 인쇄한다.

Using a Range for to Change the Characters in a string

string의 문자 값을 변경하려면 루프 변수를 reference type으로 정의해야한다 (§ 2.3.1, p. 50).

reference 는 주어진 객체의 다른 이름 일뿐임을 기억해야한다.

참조를 제어 변수로 사용하면 해당 변수가 시퀀스의 각 요소에 차례로 바인딩된다.

reference를 사용하여 reference가 바인딩되는 문자를 변경할 수 있다.

구두점을 세는 대신 string을 모두 대문자로 변환하려고한다고 가정해보자.

이를 위해 라이브러리 toupper 함수를 사용할 수 있습니다.

이 함수는 문자를 가져와 해당 문자의 대문자 버전을 반환한다.

전체 string을 변환하려면 각 문자에 대해 toupper를 호출하고 결과를 해당 문자에 다시 넣어야한다.

string s("Hello World!!!");

// convert s to uppercase
// for every char in s (note : c is a reference)
for (auto& c : s) {

    // c is a reference, so the assignment changes the char in s
    c = toupper(c); 

}

cout << s << endl;

위의 프로그램은 아래와 같이 출력한다.

각 iteration 에서, cs 의 다음 문자를 참조한다.

c 로 대입을 하면, s 의 문자가 변경된다.

// c is a reference, so the assignment changes the char in s
c = toupper(c); 

따라서, 위의 코드를 실행하는것은, c 가 바인딩된 문자의 값을 변경하는것이다.

이 루프가 완료되면, str 안에 모든 문자는 대문자가 된다.

Processing Only Some Characters?

range for 는 모든 문자를 처리하려고할때 잘 작동한다.

하지만 가끔 우리는 오직 하나의 문자 또는 조건에 부합하는 몇몇의 문자만을 접근하고싶을수도있다.

예를 들어, string 의 첫번째 단어의 첫번째 문자만을 대문자로 만들고싶다고해보자.

string 의 각 문자를 접근하는 방법은 2가지가 있다.

 

1. subscript [ ] 를 사용한다 

2. iterator 를 사용한다.  (iterator 에 대해서는 3.4(p. 106) 에서 자세히 다루도록 한다.)

 

subscript 연산자 [ ] 는 접근하려는 문자의 위치를 나타내는 string :: size_type (§ 3.2.2, p. 88) 값을 사용한다.

이 연산자는 주어진 위치의 문자에 대한 reference 를 return 한다.

string 의 subscript 는 0 에서 시작한다. 

s 가 2개의 문자로 이루어진 string 이라면, s[0] 은 첫번째 문자, s[1] 은 두번째 문자이며,

마지막 문자는 s[s.size() - 1] 이다.

 

Note

 

string 을 subscript 하기위해 사용하는 값은 반드시 >= 0 이거나 < size () 이어야한다.

이 범위를 벗어난 index 를 사용하면 결과는 undefined 이다.

string 을 subscript 하면 역시 undefined 이다.

 

subscript 안에 사용되는 값은 "subscript " 또는 "index" 라고 부른다.

index 는 정수형 값을 산출하는 어떠한 표현식이든 될수있다.

하지만, index 가 signed type 을 가지고있다면, 이 값은 string::size_type 을 나타내는 unsigned type 으로 변환된다.

아래의 예는 subscript 연산자를 사용하여 string 의 첫번째 문자를 인쇄한다.

// make sure there's a character to print
if (!s.empty()) {

    cout << s[0] << endl;  // print the first character in s

}

이 문자를 접근하기전에, s 가 empty 가 아닌지 체크해야한다.

우리는 subscript 를 사용할때마다, 반드시 주어진 위치에 값이 있는지 확인해야한다.

s 가 empty 라면 s[0]undefined 이기때문이다.

string const 가 아닌한, subscript 연산자가 return 하는 문자에 새로운 값을 대입할수있다.

예를 들어, 첫번째 문자를 대문자화 할수있다.

string s("some string");

// make sure there's a character in s[0]
if (!s.empty()) {

    s[0] = toupper(s[0]); // assign a new value to the first character in s

}

이 프로그램의 출력은 아래와 같다.

Using a Subscript for Iteration

다른 예로, s 의 첫번째 단어를 모두 소문자로 바꿔보자.

// process characters in s until we run out of characters or we hit a whitespace
for (decltype(s.size()) index = 0;) {

    if (index != s.size() && !isspace(s[index])) {

        ++index;

    }

    s[index] = toupper(s[index]); // capitalize the current character

}

위 프로그램의 결과는 아래와 같다.

위의 for 루프는 s 를 subscript 하기위해 index 를 사용한다.

index 에 적절한 type 을 지정하기위해 decltype 을 사용한다.

index 0 으로 초기화함으로써, 첫번째 iteration 은 s 의 첫번째 문자에서 시작된다.

각 iteration 에서 s 의 다음 문자를 찾기위해 index 를 증가시킨다.

루프의 body 에서는 현재 (current) 문자를 대문자로 변경한다.

이 루프에서 새로운 점은 for 안에 있는 조건문이다.

이 조건문은 논리 연산자 && (AND) 를 사용한다.

이 연산자는 두 피연산자가 모두 truetrue이고 그렇지 않으면 false이다.

이 연산자의 중요한 부분은 왼쪽 피연산자가 true인 경우에만 오른쪽 피연산자를 평가한다는 것이다.

이 경우 index가 범위 내에 있다는 것을 알지 못하면 s를 subscript하지 않을 것임을 보장한다.

즉, s[index] 는 오직 index s.size() 와 같지않을때만 실행된다.

결과적으로 index s.size() 를 넘어선 값으로 절대 증가하지못하며, 

index 는 항상 s.size() 보다 작을것임을 알수있다.

Caution : Subscripts are Unchecked

우리가 subscript 를 사용할때, 반드시 subscript 가 범위안에 있는지 확인해야한다.

즉, subscript 는 반드시 string size() 보다 >= 0 && < 해야한다. (이상이거나 작아야한다)

subscript 를 사용하는 코드를 단순화하는 방법은 string::size_type 을 subscript 로 사용하는것이다.

이 type 은 unsigned 이기때문에, subscript 가 0 보다 작을수없음을 보장할수있다.

size_type 의 값을 subscript 로 사용할때, 우리의 subscript 가 size() 보다 작은지만 확인하면된다.

 

Warning

 

라이브러리는 subscript 의 값을 체크하지않는다.

범위 밖의 subscript 를 사용할때의 결과는 undefined 이다.

Using a Subscript for Random Access

이전 예제에서 우리는 순서대로 각 문자를 대문자로 표시하기 위해 한 번에 한 위치 씩 subscript를 진행했다.

하지만 subscript를 계산하고 표시된 문자를 직접 가져올 수도 있다.

즉 순서대로 문자에 액세스 할 필요가 없다는 이야기이다.

예를 들어 0에서 15 사이의 숫자가 있고 해당 숫자의 16 진수 (hexadecimal) 표현을 생성한다고 가정 해보자.

이를 위해, 16 개의 16 진수 "숫자"를 포함하도록 초기화 된 string을 사용할수있다.

const string hexdigits = "0123456789ABCDEF"; // possible hex digits

cout    << "Enter a series of numbers between 0 and 15" 
        << " separated by spaces. Hit ENTER when finished : "
        << endl;

string result; // will hold the resulting hexify'd string
string::size_type n; // hold numbers from the input

while (cin >> n) {

    // ignore invalid input
    if (n < hexdigits.size()) {

        result += hexdigits[n]; // fetch the indicated hex digit

    })

}

cout << "Your hex number is : " << result << endl;

input :

output:

위의 코드에서,

우리는 16진수 숫자 0 부터 F 까지를 hdexdigits 를 초기화함으로써 저장한다.

이때 이 string 은 값이 변하면안되기때문에 앞에 const 를 붙인다.

루프안에서 hexdigits 을 subscript 하기위해 input 값 n 을 사용한다.

예를 들어, n15라면, 결과값은 F.

n12라면, 결과값은 C 가 된다.

이후, 숫자를 result 에 추가(append) 하고, 모든 input 을 다 읽었으면 print 한다.

subscript 를 사용할때, 우리는 subscript 가 범위안에 있는지 어떻게 확인할지 생각해야한다.

이 프로그램에서, 우리의 subscript nstring::size_type, 즉 unsigned type 이다.

결과적으로, n0 과 같거나 크다는것을 보장받는다.

hexdigits 을 subscript 하기위해 n 을 사용하기전에,

이 subscript 가 hexdigits size 보다 작은지 확인해야한다.


3.3 Library vector Type

vector 는 모든 type 이 동일한 객체들의 collection 이다.

collection 속 모든 객체는 연관된 index 를 가지고있으며,

우린 이 index 를 통해 각 객체에 접근할수있다.

vector 는 다른 객체들을 "contain (포함)" 하기때문에 container 라고도 불린다.

여러 container 들에대해 PART II 에서 더 자세히 배울것이다.

vector 를 사용하기위해서, 우리는 적절한 헤더를 include 해야한다.

앞으로 우리가 볼 예제들에서 아래의 것들이 이미 선언되어있다고 가정한다.

#include <vector>
using std::vector;

vector 는 클래스 template (템플릿) 이다.

C++ 는 클래스 템플릿과 함수 템플릿을 둘다 가지고있다.

템플릿을 작성하는것은 C++ 에 대한 깊은 이해가 요구된다.

따라서, 어떻게 우리만의 템플릿을 만들수있는지는 Chapter 16 전까지는 배우지않을것이다.

다행히도, 우리가 템플릿을 어떻게 직접 만드는지 알 필요없이, 이미 만들어진 템플릿을 사용할수있다.

템플릿 자체는 함수나 클래스가 아니다.

대신에, 템플릿은 클래스 또는 함수를 생성하기 위해 컴파일러에 대한 지침으로 생각할 수 있다.

컴파일러가 템플릿으로부터 클래스나 함수를 생성하는 과정을 instantiation (인스턴스화) 이라고 부른다.

우리는 템플릿을 사용할 때 컴파일러가 인스턴스화 할 클래스 또는 함수의 종류를 지정한다.

클래스 템플릿의 경우 추가 정보를 제공하여 인스턴스화 할 클래스를 지정하며, 그 특성은 템플릿에 따라 다르다.

어떻게 정보를 지정하는지는 똑같다.

템플릿 이름 뒤에있는 한 쌍의 꺾쇠 괄호 < > 안에 정보를 제공한다.

vector 의 경우, 우리가 제공해야할 추가적인 정보는 vector 가 저장할 객체의 type 이다.

vector<int> ivec; // ivec holds objects of type int
vector<Sales_item> Sales_vec; // holds Sales_itmes
vector<vector<string>> file; // vector whoes elements are vectors

위의 예에서, 컴파일러는 vector 템플릿으로부터 3개의 다른 type 들인

vector<int>, vector<Sales_item>, vector<vector<string>> 을 생성한다.

 

Note

 

vector 는 type 이 아니라 템플릿이다.

vector 로부터 생성되는 type 은 vector<int> 처럼 각 element 의 type 을 반드시 포함해야한다.

우리는 vector 가 어떤 type 의 객체든 저장할수있도록 정의할수있다.

reference 는 객체가 아니기때문에, reference 들의 vector 는 가질수없다.

하지만 reference 가 아닌 내장형 type 과 대부분의 클래스 type 들은 vector 로 가질수있다.

C+11

이전 버전의 C++에서는 요소 자체가 벡터 (또는 다른 템플릿 유형) 인 벡터를 정의하기 위해 

약간 다른 구문을 사용했다는 점은 주목할 가치가 있다.

과거에는 외부 vector의 닫는 꺾쇠 괄호와 해당 요소 유형 사이에 공백을 제공해야했다.

vector <vector <int > >가 아니라 vector <vector <int>>이다.

 

Warning

 

몇몇 컴파일러는 오래된 vector 선언 스타일을 요구하는 경우가 있다. ex) vector<vector<int>/* 빈칸 한칸 */ >


3.3.1 Defining and Initializing vectors

다른 클래스 type처럼, vector 템플릿은 우리가 어떻게 vector 를 정의하고 초기화할지 컨트롤한다.

Table 3.4 는 vector 를 정의하는 가장 일반적인 방법들을 소개한다.

 

Table 3.4. Ways to Initialize a vector

vector 의 default 초기화는 지정한 type 의 빈 vector 를 생성한다.

vector<string> svec; // default initialization; svec has no elements

이렇게 빈 vector는 거의 사용되지 않는 것처럼 보일 수 있다.

그러나 곧 살펴 보겠지만 런타임에 element를 vector에 (효율적으로) 추가할수있다.

실제로 vector를 사용하는 가장 일반적인 방법은 런타임에 값이 알려지면 요소가 추가될수있도록,

초기에 비어있는 vector를 정의하는 것이다.

vector를 정의 할 때 요소에 대한 초기 값을 제공 할 수도 있습니다. 예를 들어 다른 vector에서 요소를 복사 할 수 있다.

vector를 복사 할 때 새 벡터의 각 요소는 원래 vector의 해당 요소의 복사본이다. 

이때 두 vector는 동일한 type 이어야한다.

vector<int> ivec; // initially empty

// give ivec some values
vector<int> ivec2(ivec); // copy elements of ivec into ivec2
vector<int> ivec3 = ivec; // copy elements of ivec into ivec3
vector<string> sevc(ivec2); // error : svec holds strings, not ints

C+11 : List Initializing a vector

vector 의 element 값을 제공하는 또 다른 방법은

C+11 의 새 표준에 따라 중괄호 { } 로 묶인 0 개 이상의 초기 요소 값 목록으로 vector 를 초기화 (§ 2.2.1, p. 43) 할수있다.

vector<string> articles = {"a", "an", "the"};

위의 vector 에는 세 가지 요소가 있다.

첫 번째는 string "a", 두 번째는 "an", 마지막은 "the".

우리가 보아왔듯이, C++ 는 다양한 초기화 형태를 제공한다.

전부는 아니지만 많은 경우에 이러한 형태의 초기화를 서로 바꿔서 사용할 수 있다.

지금까지 초기화 형식이 중요한 두 가지 예를 보았다.

복사 초기화 양식을 사용할 때 (즉, =) (§ 3.2.1, p. 84)는  하나의 initializer만 제공 할 수 있다.

in-class initializer(§ 2.6.1, p. 73)를 제공 할 때 복사 초기화를 사용하거나 중괄호 { } 를 사용해야한다.

세 번째 제한은 initializer가 중괄호 { } 로 묶인 목록 초기화를 사용하여 요소 값 목록을 제공해야한다는것이다.

vector<string> v1{"a", "an", "the"}; // list initialization
vector<string> v2("a", "an", "the"); // error

Creating a Specified Number of Elements

또한 vector 를 count 와 element 값으로 초기화할수있다.

count 는 vector 가 얼마나 많은 element 를 가질지 결정하고, 

element 값은 이 element 들에 각 초기값을 제공한다.

vector<int> ivec(10, -1); // ten int elements, each initialized to -1
vector<sring> svec(10, "hi!"); // ten strings : each element is "hi!"

Value Initialization

vector 를 초기화하기위해 값을 생략하고 size 만 제공할수도있다.

이 경우, 라이브러리는 value-initialized element initializer 를 제공한다.

라이브러리가 생성한 값은 container 의 각 element 를 초기화하는데에 사용된다.

element initializer 의 값은 vector 가 저장하는 element 들의 type 에 따라 다르다.

vector int 같은 내장형 type 을 저장한다면, element initializer 의 값은 0 이다.

만약 element 가 string 같은 클래스 type 이라면, 

element initializer 는 default 초기화이다.

vector<int> ivec(10); // ten elements, each initialized to 0
vector<string> svec(10); // then elements, each an empty string

이 초기화 형태에는 2가지 제한사항이 있다.

첫번째는, 몇몇 클래스는 언제나 명시적인 initializer 를 요구한다는것이다.

만약 vector 가 default 초기화할수없는 type 의 객체를 가지고있다면,

이런 type 의 객체는 vector 에 size 만을 제공하여 초기화할수없으며,

반드시 element 의 초기값을 제공해야한다.

두번째는, 초기값없이 element count 만을 제공할때, 우리는 반드시 초기화의 direct 형태를 사용해야한다.

vector<int> vi = 10; // error : must use direct initialization to supply a size

위의 코드에서는 10을 사용하여 vector 를 만들도록 vector 에 지시한다.

10 개의 값으로 초기화 된 요소가있는 vector 가 필요하다

우리는 vector 10을 "복사"하지 않는다.

따라서 copy 초기화 = 를 사용할수없다.

이런 제한사항에 대해 7.5.4(p. 296) 에서 더 자세히 다룬다.

List Initializer or Element Count?

경우에 따라 초기화의 의미는 initializer를 전달하기 위해 중괄호 { } 를 사용하는지 괄호 ( ) 를 사용하는지에 따라 다르다.

예를 들어, vector<int> 를 단일 int 값으로 초기화한다면,

이 값은 vector 의 size 를 나타낼수도있고, element 의 값을 나타낼수도있다.

이와 유사하게, 2개의 int 값을 제공한다면, 이 값들은 size 와 초기값 또는 vector 의 두 element 값 일수도있다.

따라서, 우리가 의도한 바에 따라 { } 또는 ( ) 를 적절히 사용해야한다.

vector<int> v1(10); // v1 has ten elements with value 0
vector<int> v2{10}; // v2 has one element with value 10
vector<int> v3(10, 1); // v3 has ten elements with value 1
vector<int> v4{10, 1}; // v4 has two elements with values 10 and 1

우리가 ( ) 를 사용하면, 우리가 제공하는 값을 객체를 구성하는 데 사용한다는 의미이다.

따라서 v1v3은 initializer를 사용하여 각각 vector 의 크기와 크기 및 요소 값을 결정한다.

반면, { } 를 사용하는것은 가능하면 객체를 list 초기화하고싶다는것이다.

즉, { }의 값을 element initializer 의 list 로 사용하는 방법이 있다면 클래스는 그렇게 할 것이다.

list 초기화가 불가능한 경우에는 객체를 초기화하는 다른 방법이 고려된다.

v2v4를 초기화 할 때 제공하는 값은 element 값으로 사용할 수 있다.

이 객체들은 list 초기화되며, 결과적으로 vector 는 1개 또는 2개의 element 를 가진다.

반면에 {  } 와 list 초기화를 사용하여 객체를 초기화 할 수있는 방법이 없는 경우 해당 값이 객체를 생성하는 데 사용된다.

예를 들어, stringvector 를 list 초기화하려한다면, 우리는 반드시 string 으로 사용될수있는 값을 제공해야한다.

이 경우 이것이 list 초기화인지지 아니면 주어진 크기의 vector를 construct 하는지에 대한 혼동이 없다.

vector<string> v5{"hi"}; // list initialization: v5 has one element
vector<string> v6("hi"); // error : can't construct a vector from a string literal
vector<string> v7{10}; // v7 has ten default-initialized elements
vector<string> v8{10, "hi"}; // v8 has ten elements with value "hi"

위의 코드에서 { } 를 사용한 코드중, 오직 v5 만이 list 초기화이다.

vector 를 list 초기화하기위해, { } 안의 값은 반드시 element 의 type 과 일치해야한다.

string 을 초기화하기위해 int 를 사용할수없으며, v7 v8 을 위한 initializer 는 element initializer 가 될수없다.

list 초기화가 가능하지않은 경우, 컴파일러는 주어진 값으로 해당 객체를 초기화할수있는 다른 방법을 스스로 찾는다.


3.3.2 Adding Elements to a vector

vectorelement 를 직접적으로 초기화하는 것은 알려진 초기 값의 수가 적거나,

다른 vector의 복사본을 만들거나, 모든 element를 동일한 값으로 초기화하려는 경우에만 가능하다.

일반적으로 우리가 vector를 만들 때는 얼마나 많은 element 가 필요할지, 또는 이 element 들의 값이 무엇이 될지 모른다.

우리가 모든 값을 알고 있다하더라도, 다른 초기 element 값이 많으면 vector를 만들 때 지정하는 것이 번거로울것이다.

예르 들어, 우리가 vector 를 값이 0 부터 9로 초기화 하려한다면, list 초기화를 하면 된다.

하지만 element 가 0 부터 999 까지로 하고싶다면 어떻게 할것인가?

이때 list 초기화는 매우 어려울것이다.

이런 경우에, 빈 vector를 만들고 push_back이라는 vector 멤버를 사용하여 런타임에 element를 추가하는 것이 바람직할것이다.

push_back 연산은 값을 말그대로 "push" 하여, vector 의 "back" 즉, 마지막 element 로 밀어넣는다.

예를 보자.

vector<int> v2; // empty vector

for (int i = 0; i != 100; ++i) {

    v2.push_back(i); // append sequential integers to v2

} // at end of loop, v2 has 100 elments, values 0 ... 99

위의 코드에서, 우리는 100 개의 element 를 가질것이라는것을 알고있다하더라도, v2 를 empty vector 로 정의하였다.

각 iteration 은 v2 의 새로운 element 로 정수를 순차적으로 추가한다.

우리는 vector에 몇 개의 element가 있어야하는지 런타임까지 알 수없는 vector를 만들 때

위의 코드와 동일한 접근 방식을 사용한다.

예를 들어, 우리가 입력을 읽어야한다면, 우리가 읽는 값들을 vector 에 저장한다.

// read works from the standard input and store them as elements in a vector
string word;
vector<string> text; // empty vector
while (cin >> word) {

    text.push_back(word); // append word to text

}

Key Concept : vectors Grow Efficiently

표준에서는 vector 구현이 런타임에 element를 효율적으로 추가 할 수 있어야한다.

vector 는 효율적으로 성장하기 때문에 우리는 특정 크기의 vector 를 정의 할 필요가 없으며,

이렇게 함으로써 성능이 저하 될 수 있다.

이 규칙의 예외는 모든 element에 실제로 동일한 값이 필요한 경우이다.

각 element 들의  값이 서로 달라야할 경우에는, 

일반적으로 빈 vector를 정의하고 필요한 값이 런타임에 알려지면 element를 추가하는 것이 더 효율적이다.

또한 § 9.4 (p. 355)에서 볼 수 있듯이 vector element를 추가 할 때 런타임 성능을 더욱 향상시킬 수있는 기능을 제공한다.

vector 로 시작하여 런타임에 element 를 추가하는 것은,

C 및 대부분의 다른 언어에서 기본으로 제공하는 array를 사용하는 방법과 완전히 다르다.

특히 당신이 C 또는 Java를 사용하는 데 익숙하다면, vector 의 크기를 지정하는것이 제일 좋다고 생각할것이다.

사실은, C++ 에서는 그 반대가 일반적이다.

Programming Implications of Adding Elements to a vector

우리가 vector 에 쉽고 효율적으로 element 를 추가할수있다는 사실은,

많은 프로그래밍 task 들을 매우 단순화해준다.

하지만,  이러한 단순성은 프로그램에 새로운 의무를 부과한다.

우리는 반드시 루프가 vector의 크기를 변경하더라도, 이 루프가 올바른지 확인해야한다.

vector의 동적 특성이 암시하는 다른 의미는 vector를 사용할수록 더 명확해질것이다.

하지만 미리 알아야할 한가지가 있다.

5.4.3(p. 188) 에서 다루겠지만, 우리는 range for 루프를 vector 의 element 를 추가하기위해 사용할수없다.

 

Warning

 

range for 루프의 body는 iterating 동안 sequence 의 크기를 변경해서는 안된다.


3.3.3 Other vector Operations

push_back 이외에, vector 는 string 의 연산들과 비슷한 연산들을 제공한다.

아래는 중요한 vector 연산들이다.

Table 3.5. vector Operations

vector 의 element 를 접근하는 방법은 string 의 각 문자에 접근하는 방법과 동일하다.

예를 들어, vector 의 모든 element 들을 프로세싱하기위해 range for 루프를 사용할수있다.

vector<int> v{1, 2, 3, 4, 5, 6, 7, 8, 9};

// for each element in v (not: i is a reference)
for (auto& i : v) {

    i *= i; // square the element value 

}

// for each element in v
for (auto i : v) {

    cout << i << " "; // print the element

}

cout << endl;

 

위 코드에서는 첫 번째 루프에서 제어 변수 ireference로 정의함으로써, i를 사용하여 v의 요소에 새 값을 대입한다.

이때 i 의 type은 auto 를 사용하여 추론(deduce)하도록 한다.

이 루프는 새로운 형태의 복합 할당 연산자(compound assignment operator)를 사용한다(§ 1.4.1, p. 12).

지금까지 살펴본 것처럼 += 는 오른쪽 피연산자를 왼쪽에 추가하고 결과를 왼쪽 피연산자에 저장한다.

*= 연산자는 왼쪽 및 오른쪽 피연산자를 곱하여 결과를 왼쪽에 저장한다는 점을 제외하면 비슷하게 작동한다.

두 번째 range for 루프는 는 각 요소를 인쇄한다.

empty size 멤버는 string 의 멤버들처럼 작동한다.

vector 가 element 를 가지고있는지, 아닌지에 따라 empty bool 을 return 한다.

size vector 의 element 수를 return 한다.

size 멤버는 vector type 에 해당하는 size_type 의 값을 return 한다.

 

Note

 

size_type을 사용하려면 반드시 정의 된 type의 이름을 지정해야한다.

vector type 은 언제나 element type 을 포함한다.

vector<int>::size_type // ok
vector::size_type // error

같음 (equality) 및 관계 (relational) 연산자는 string (§ 3.2.2, p. 88) 에서 사용하는것과 동일한 동작을한다.

vector 의 element 수가 같고 해당 element의 값이 모두 같은 경우 두 vector 는 같다.

관계 연산자는 사전 순서를 적용한다.

vector 의 크기가 다르지만 공통 element가 같으면 element가 더 적은 vector element가 더 많은 vector 보다 작다.

element의 값이 다른 경우 vector 간의 관계는 다른 첫 번째 element 간의 관계에 의해 결정된다.
vector 의 요소를 비교할 수있는 경우에만 두 vector 를 비교할 수 있다.

string과 같은 일부 클래스 type은 같음 및 관계 연산자의 의미를 정의한다.

우리의 Sales_item 클래스와 같은 다른 것들은 그렇지 않다.

Sales_item이 지원하는 유일한 작업은 § 1.5.1 (p. 20)에 나열된 작업이다.

이러한 연산에는 같음 또는 관계 연산자가 포함되지 않다.

따라서, 결과적으로 두 vector <Sales_item> 의 객체를 비교할 수 없다.

Computing a vector Index

subscript 연산자 (§ 3.2.3, p. 93)를 사용하여 주어진 element를 가져올 수 있다.

string과 마찬가지로 vector의 subscript는 0에서 시작한다.

subscript 의 type은 해당 size_type이다.

vectorconst가 아니라고 가정하면, subscript 연산자가 반환 한 element에 쓸 수 있다.

또한 § 3.2.3 (p. 95)에서했던 것처럼 인덱스를 계산하고 해당 위치에서 element를 직접 가져올 수 있다.
예를 들어, 0에서 100까지 범위의 성적 모음이 있다고 가정해보자.

10의 다양한 클러스터에 속하는 성적 수를 계산하려고한다.

0과 100 사이에는 101 개의 가능한 성적이 있다.

이 등급은 11 개의 군집으로 표시 될 수 있다.

각각 10 개 등급의 군집 10 개와 100 점 만점에 대한 군집 1 개. 첫 번째 군집은 0 ~ 9의 등급을 계산하고,

두 번째 군집은 10 ~ 19의 등급을 계산한다.

최종 클러스터는 100 점을 달성 한 점수를 계산한다.

이 방법으로 점수를 매겼을때,

입력이 아래와 같다면,

출력은 아래와 같아야한다.

위의 출력은 30점 밑으로의 점수가 없음을 나타내며, 

30점대에 1명, 40점대에 1명, 50점대 없음, 60점대 2명, 70점대 3명, 80점대 2명, 90점대 4명, 100점대 1명이다.

우리는 11 개 element가있는 vector를 사용하여 각 클러스터에 대한 카운터를 보유할것이다.

해당 등급을 10으로 나누어 특정 등급에 대한 군집 지수를 결정할 수 있다.

두 개의 정수를 나누면 분수 부분이 잘린 정수를 얻는다.

예를 들어 42/10은 4, 65/10은 6, 100/10은 10이다.

클러스터 인덱스를 계산했으면 이를 사용하여 vector를 subscript하고 증가 할 카운터를 가져올 수 있다.

// count the number of grades by clusters of ten : 0--9, 10--19, .... 90--99, 100
vector<unsigned> scores(11, 0); // 11 buckets, all initally 0
unsigned grade;

// read the grades
while (cin >> grade) {

    // handle only valid grades
    if (grade <= 100) {

        ++scores[grade / 10]; // increment the counter for the current cluster

    }

}

위의 코드는, 클러스터 수를 저장할 vector를 정의하는 것으로 시작한다.

이 경우 각 element가 동일한 값을 갖기를 원하므로 11 개 element를 모두 대입하여, 각 element는 0으로 초기화된다.

while 조건은 grade 를 읽는다.

루프 내에서는 읽은 등급이 유효한 값인지 (즉, 100보다 작거나 같은지) 확인한다.

grade가 유효하다고 가정하면 등급에 대한 적절한 카운터를 증가시킨다.

위의 코드에서,  increment 를 수행하는 statement는 C ++ 프로그램의 간결한 코드 특성을 보여주는 좋은 예이다.

++scores[grade/10]; // increment the counter for the current cluster

위의 코드는 아래와 동일하다.

auto ind = grade / 10; // get the bucket index 
scores[ind] = scores[ind] + 1; // increment the count

위의 코드는 grade10으로 나누어 버킷 지수를 계산하고, 나눗셈 결과를 사용하여 scores를 색인화한다.

scores 를 subscript 하는것는 이 grade에 적합한 카운터를 가져온다.

주어진 범위에서 각 점수의 발생을 나타 내기 위해 해당 element의 값을 증가시킨다.

지금까지 살펴본 바와 같이 subscript를 사용할 때 index가 범위 내에 있다는 것을 어떻게 아는지 생각 해봐야한다 (§ 3.2.3, p. 95).

이 프로그램에서, 우리는 입력이 범위 0100 사이에있는 유요한 grade 인지 확인한다.

이 index 들은 0scores.size() - 1 사이의 범위이다.

Subscripting Deos Not Add Elements

C++ 를 새로 공부하는 프로그래머들은 vector 를 subscript 하는것이, element 를 추가하는것이라고 생각한다.

하지만 그렇지만 않다.

아래의 코드는 ivec 에 10개의 element 를 추가한다.

vector<int> ivec;   // empty vector

for (decltype(ivec.size()) ix = 0; ix != 10; ++ix) {

    ivec[ix] = ix;  // disaster: ivec has no elements

}

불행히도, 위의 코드는 error 다.

ivec 은 empty vector 이며, 따라서 subscript 할 element 가 없다!

우리가 보았듯이, 이 loop 안에서 element 추가하는 올바른 방법은 push_back 을 사용하는것이다.

for (decltype(ivec.size()) ix = 0; ix != 10; ++ix) {

    ivec.push_back(ix); // ok : adds a new element with value ix

}

Warning

 

vector string 을 subscript 하는것은 존재하는 element 를 얻는것이지, 새로운 element 를 추가하는것이 아니다.

Caution : Subscript Only Elements that are Known to Exist!

subscript 연산자 [ ]는, 오직 존재하는 element 를 얻기위해 사용하는것임을 이해하는것이,

너무나도, 매우매우 중요하다.!!!!

예를 들어,

vector<int> ivec;      // empty vector
cout << ivec[0];       // error: ivec has no elements!

vector<int> ivec2(10); // vector with ten elements
cout << ivec2[10];     // error: ivec2 has elements 0 . . . 9

존재하지않은 element 를 subscript 하는것은, 컴파일러가 알려주지않는 error 다.

이로인해, 우리가 run-time 에서 얻는 값은 undefined 이다.

존재하지않은 element 를 subscript 하려는 시도는, 불행히도 매우 매우 흔하게 발생되는 프로그래밍 error 다.

존재하지않는 element 를 subscript했을때의 결과를 buffer overflow 라고 부른다.

이런 버그는 많은 프로그램들에 보안 문제를 야기하는 주된 원인이다.

 

Tip

 

subscript 가 범위에 확실히 있도록 하는 가장 좋은 방법은, 

가능하면 range for 루프를 사용하는것이다. 


3.4 Introducing Iterators

vector 의 element 나, string 의 문자를 접근하기위해서, subscript 를 사용할수있지만,

동일한 목적으로 사용할수있는 iterator 라는 메카니즘이 있다.

Part II 에서 자세히 다루겠지만, vector 이외에도, 라이브러리는 몇가지 다른 종류의 container 를 정의하고있다.

모든 라이브러리 container 들은 iterator 를 가지고있으며, 

이 라이브러리들중 몇몇만이 subscript 연산자를 지원한다.

기술적으로 말하면, string 은 사실 container type 이 아니며, container 연산의 많은 부분을 지원하는것이다.

string vector 처럼 subscript 연산자를 가지고있으면서, iterator 또한 가지고있다.

pointer (2.3.2 p.52) 처럼, iterator 는 우리에게 객체에 대한 우회적인 접근을 제공한다.

iterator 에 경우, 객체는 container 안에 element 또는 string 안에 문자이다.

iterator 는 element 를 얻기위해 사용할수있으며, 특정 element 에서 다른 element 로 이동하는 연산또한 할수있다.

pointer 처럼, iterator 는 valid 이거나 invalid 이다.

유효한 iterator element 를 나타내거나 컨테이너의 마지막 element 를 지나는 위치를 나타낸다.

다른 iterator 의 값은 invalid 이다.


3.4.1 Using Iterators

pointer 와 달리, iterator 를 구하기위해 주소 연산자를 사용하지않는다.

대신에, iterator 를 가지고있는 type 들은 iterator 를 return 하는 멤버를 가지고있다.

특히, 이 type 들은 begin end 라는 이름의 멤버를 가지고있다.

begin 멤버는 첫번째 element (또는 첫번째 문자) 를 나타내는 iterator 를 return 한다.

// the compiler determines the type of b and e; see § 2.5.2 (p. 68)
// b denotes the first element and e denotes one past the last element in v
auto b = v.begin(), e = v.end(); // b and e have the same type

end 에 의해 return 되는 iterator 는, 해당 container (또는 string) 의 "one past the end" 에 위치한 iterator 이다.

이 iterator는 container의 "끝에있는" 존재하지 않는 element를 나타낸다.

이 iterator 는 우리가 모든 element 를 process 했음을 알리기위해 사용된다.

end 를 return 하는 iterator 는 off-the-end iterator 또는 end iterator 라고 부른다.

만약 container 가 비어있다면, begin end 가 return 하는 iterator 는 동일하다.

 

Note

 

container 가 비어있다면, begin end 가 return 하는 iterator 는 동일하며,

이것들은 모두 off-the-end iterator 이다.

 

일반적으로, 우리는 iterator 가 가지고있는 정확한 type 에 대해 모르거나, 신경쓰지않는다.

위의 예제에서, 우리는 auto 를 사용하여 be 를 정의했다.

결과적으로 이 변수들은 begin end 멤버에 의해 return 되는 type 이다.

이 type 들에 대해 p. 108 에서 더 자세히 다룬다.

Iterator Operations

iterator 는 Table 3.6 에 나와있는 몇가지 연산만을 지원한다.

우리는 2개의 유효한 iterator 를 == 또는 != 를 사용하여 비교(compare) 할수있다.

iterator 가 동일하면, 똑같은 element 를 가지고있음을 의미하거나,

이들은 둘다 똑같은 container 안에 off-the-end iterator 임을 의미한다.

이외에 경우에 이 둘은 서로 동일하지않다.

Table 3.6. Standard Container Iterator Operations

pointer 처럼, iterator 또한 iterator 가 나타내는 element 를 얻기위해 iterator 를 역참조(dereference) 할수있다.

pointer 처럼, 우리는 오직 유효한 (valid) iterator, 즉 element 를 나타내는 iterator 만을 역참조해야한다.

유효하지않은(invalid) iterator 를 역참조하거나, off-the-end iterator 를 역참조한 경우의 결과는 undefined 이다.

우리가 3.2.3(p. 94) 에서 작성한 string 의 첫번째 문자를 대문자로 바꾸는 프로그램을,

subscript 대신 iterator 를 사용하여 재작성해보자.

string s("some string");

// make sure s is not empty
if(s.begin() != s.end()) {

    auto it = s.begin(); // it denotes the first character in s

    *it = toupper(*it); // make that character uppercase

}

원래의 프로그램처럼, 우리는 먼저 s 가 empty 가 아닌지를 체크한다.

이 경우, 우리는 begin end 가 return 하는 iterator 를 비교한다.

이 iterator 가 equal 이라면 string 은 empty 인것이다.

하지만 동일하지않다면, s 는 최소 1개의 문자를 가지고있음을 의미한다.

if 의 body 에서, 우리는 begin 이 return 하는 iterator 를 it 에 대입함으로써, 첫번째 문자에 대한 iterator 를 얻는다.

그후, 이 iterator 를 역참조하여 문자를 얻고, 이 문자를 toupper 함수의 인자로 넘긴다.

또한,  toupper 에 의해 return 된 s 의 첫번째 문자를 대입하기위해, 대입의 왼쪽에 있는 it 를 역참조한다.

우리의 원래 프로그램처럼, 이 새로운 프로그램의 출력 또한 아래와 같이 동일하다.

Moving Iterators from One Element to Another

iterator 는 특정 element 에서 다음 element 로 이동하기위해 증감 연산자 ++ 를 사용한다.

iterator 를 증가시키는것은 이론적으로 정수를 증가시키는 연산과 비슷하다.

정수의 경우, ++ 는 정수에 1 을 더한다.

iterator 의 경우, ++ 는 iterator 를 다음 위치로 이동시키는것이다.

 

Note

 

end 로 return 된 iterator 는 element 를 나타내지않기때문에, 증가시키거나 역참조 할수없다.

 

string 의 첫번째 단어를 대문자로 바꾸는 우리의 프로그램을,

iterator 에 증감연산자를 사용하여 재작성해보자.

// process characters in s until we run out of characters or we hit a whitespace
for (auto it = s.begin(); it != s.end() && !isspace(*it); ++it) {

    *it = toupper(*it); // capitalize the current character

} 

이 루프는 3.2.3(p. 94) 처럼, s 안에 문자들을 순회(iterate) 하며,

공백 문자 (whitespace) 를 만나면 멈춘다.

하지만 이 루프는 이 문자들을 subscript 가 아닌 iterator 로 접근한다.

이 루프는, s 의 첫번째 문자를 나타내는 s.begin() 으로 it 를 초기화하면서 시작한다.

조건문은 itsend 에 도달했는지 체크한다.

end 에 도달하지않았다면, 조건문은 isspace 에 현재의 문자를 전달하기위해 it 을 역참조한다.

각 iteration 의 마지막에는, s 의 다음 문자에 접근하기위해 iterator 를 ++ 한다.

이 루프의 body 는 우리의 이전 프로그램의 if 문장과 동일하다.

현재의 문자를 toupper 에 넘기고, return 된 대문자를 다시 it 를 역참조하여 대입한다.

Key Concept : Generic Programming

C 나 Java 에서 C++ 로 넘어온 프로그래머들은 우리가 위의 for 루프에서,

< 대신 != 를 사용하는것을 보고 놀랄수있다.

C++ 프로그래머들은 != 를 습관처럼 사용한다.

이는 C++ 프로그래머들이 subscript 보다 iterator 를 사용하는 이유와 비슷하다.

이러한 코딩 스타일은 라이브러리가 제공하는 모든 종류의 container 에 동일하게 적용된다.

우리가 보아왔듯이, 오직 vector string 같이 몇개의 라이브러리들만이 subscript 연산자를 제공한다.

모든 라이브러리 컨테이너들은 ==!= 연산자를 정의하는 iterator 를 가지고있다.

이 iterator 들의 대부분은 < 연산자를 가지고있지않다.

C++ 에서 iterator와 != 를 일상적으로 사용하면,

처리중인 정확한 컨테이너 type이 무엇인지에 대해 걱정할 필요가 없다.

Iterator Types

우리가 vector 의 type  또는 string size_type 멤버의 정확한 type 이 무엇인지 모르는것처럼,

일반적으로 iterator 의 정확한 type 을 모르거나, 알 필요가 없다.

대신에, size_type 처럼, iterator 를 가지고있는 라이브러리 type 은,

iterator 와 const_iterator 라는, 실제 iterator type 을 나타내는 type 들을 정의한다.

vector<int>::iterator it; // it can read and write vector<int> elements
string::iterator it2; // it2 can read and write characters in a string
vector<int>::const_iterator it3; // it3 can read butn ot write elemtns
string::const_iterator it4; // it4 can read, but not write characters

const_iterator const 포인터처럼 작동한다.

const pointer 처럼, const_iterator 는 읽을수만있고, element 를 write 할수는없다.

const가 아닌, iterator type 의 객체는 read 와 write 둘다 할수있다.

vector string const 라면, const_iterator type 을 사용해야한다.

const 가 아닌 vector string 에서는, iterator const_iterator 를 둘다 사용할수있다.

Terminology : Iterators and Iterator Types

iterator 라는 용어는 3개의 다른 entity 를 나타내기위해 사용된다.

iterator 는 말그대로 iterator 라는 개념을 의미할수있고,

또는 컨테이너에 의해 정의된 type iterator 를 의미할수도있고,

또는 iterator 라는 객체를 의미할수있다.

이해해야 할 중요한 것은 개념적으로 관련된 type 의 collection 이 있다는것이다.

type 은 공통된 action 들을 지원하는 iterator 이다.

이 action 들은 컨테이너안에 element 에 우리가 접근할수있게해주며,

또한 특정 element 에서 다른 element 로 이동할수있게 해준다.

각 컨테이너 클래스는 iterator 라는 이름의 type 을 정의하며,

이 iterator type 은 개념적인 iterator 의 action 들을 정의한다.

The begin and end Operations

begin end 가 return 하는 type 은 해당 객체가 const 인지 아닌지에 따라 다르다.

해당 객체가 const 라면, begin end const_iterator 를 return 한다.

해당 객체가 const 가 아니라면, 그냥 iterator 를 return 한다.

 

vector<int> v;
const vector<int> cv;
auto it1 = v.begin(); // it1 has type vector<int>::iterator
auto it2 = cv.begin(); // it2 has type vector<int>::const_iterator

하지만 종종 이 default behavior 는 우리가 원하는것이 아닐때가 있다.

이 이유에 대해서는 6.2.3(p. 213) 에서 자세히 설명할것이다.

대부분 우리가 읽기만하고, 객체에 쓰기는 필요하지않은 경우, const type 을 사용하는것이 best practice 이다.

C+11

C+11 의 새로운 표준은 , const_iterator type 을 얻기위해, cbegin cend 라는 2가지 새로운 함수를 소개한다.

auto it3 = v.cbegin(); // it3 has type vector<int>::const_iterator

begin end 가 멤버인것처럼, 이 멤버들은 첫번째 element 에 대한 iterator 와,

"one past the last element" 를 나타내는 iterator 를 return 한다.

하지만, vector string const 인지 아닌지의 여부와 상관없이 cbegin cend 는,  const_iterator 를 return 한다.

Combining Dereference and Member Access

우리가 iteator 를 역참조하면, iterator 가 나타내는 객체를 얻는다.

이 객체가 클래스 type 이면, 우리는 아마 이 객체의 멤버를 접근하고싶을것이다.

예를 들어, string 들이 들어가있는 vector 라면, 

우리는 각 element 들이 empty 인지 확인해야할것이다.

vector 의 iterator 를 it 이라고 가정하면, it 가 나타내는 string 이 empty 인지 

아래와 같은 코드로 확인할수있다.

(*it).empty()

4.1.2(p. 136) 에서 자세히 다루겠지만,

(*it).empty() 에 사용된 기호 ( ) 가 중요하다.

( ) it 를 역참조하는 결과가, . 연산자가 아닌 it 를 역참조하라고 말하는것이다.

( ) 를 사용하지않으면, . 연산자가 it 에 에 적용된다.

(*it).empty(); // dereferences it and calls the member empty on the resulting objec
*it.empty(); // error: attempts to fetch the member named empty from it
            //     but it is an iterator and has no member named empty
            

2번째 표현식은 it 라는 객체의 empty 라는 member 를 구하는 요청으로 해석된다.

하지만 it 는 iterator 이고, empty 라는 멤버를 가지고있지않다.

따라서, 2번째 표현식은 error 다.

이런 방식의 표현식을 단순화하기위해, C++ 언어는 -> 연산자를 정의한다.

-> 연산자는 역참조와 멤버 접근을 단일 연산으로 결합한다.

즉, it->mem(*it).mem 과 동일하다.

예를 들어, 텍스트 파일의 데이터를 저장하는 text 라는 이름의 vector<string> 이 있다고 가정해보자.

vector 의 각 element 는 문장이거나, 문단의 break 을 나타내는 빈 string 일것이다.

따라서, text 를 빈 element 를 만나기전까지 순회하는 루프를 작성해보자.

// print each line in text up to the first blank line.
for (auto it = text.cbegin(); it != text.cend() && !it->empty(); ++it) {

    cout << *it << endl;

}

우리는 text 의 첫번째 element 를 나타내는 it 를 초기화함으로써 시작한다.

이 루프는 우리가 text 의 모든 element 를 프로세스할때까지 또는 빈 element 를 발견할때까지 계속된다.

빈 element 를 발견하지않는한, 현재의 element 를 인쇄한다.

위의 코드에서 루프가 텍스트의 element를 읽지만 쓰지 않기 때문에,

cbegincend를 사용하여 iteration을 제어한다는 점에 주목할 필요가 있다.

Some vector Operations Invalidate Iterators

몇몇 vector 연산은 iterator 를 무효화한다.

§ 3.3.2 (p. 101)에서 vector가 동적으로 성장할 수 있다는 사실의 의미가 있음을 언급했다.

또한 이러한 의미 중 하나는 for 루프 범위 내의 vector에 요소를 추가 할 수 없다는 것입니다.

또 다른 의미는 vector의 크기를 변경하는 모든 연산 (예 : push_back)이 잠재적으로

모든 iterator를 해당 vector로 무효화한다는 것입니다.

iterator가 무효화되는 방법에 대해서는 § 9.3.6 (353 페이지)에서 자세히 살펴 보겠다.

 

Warning

 

 iterator 를 사용하는 루프가 iterator 가 나타내는 컨테이너에 element 를 추가하지말아야한다는것을 기억하라.


3.4.2 Iterator Arithmetic

iterator 를 증가시키는것은 iterator 를 one element 움직인다.

모든 라이브러리 컨테이너들은 increment 를 지원하는 iterator 를 가지고있다.

이와 유사하게, ==!= 를 2개의 유효한 interator 를 비교하기위해 사용할수있다.

string vector 를 위한 iterator 는 iterator 가 여러칸 움직일수있는 추가적인 연산을 제공한다.

그리고 관계 연산자 또한 지원한다.

이 연산들을 iterator artithmetic 이라고 부르며, Table 3.7 에서 설명한다.

 

Table 3.7. Operations Supported by vector and string Iterators

Arithmetic Operations on Iterators

우리는 정수형 값과 iterator 를 더하거나 (add) 뺄수(subtract) 있다.

이렇게하면, 앞뒤로 여러칸 (여러 element) 움직인 iterator 을 return 한다.

우리가 이렇게 정수값과 iterator 를 더하거나 뺄때, 결과는 반드시 동일한 vector (또는 string) 의 element 를 나타내거나,

연관된 vector (또는 string) 의 "one past the end" 를 나타내야한다.

예를 들어, 우리는 vector 의 중간에 가까운 element 에 대한 iterator 를 계산할수있다.

// compute an iterator to the element closest to the midpoint of vi
auto mid = vi.begin() + vi.size() / 2;

위의 코드에서, vi 가 20개의 element 를 가지고있다면, vi.size() / 2 는 10 이다.

이 경우, mid vi.begin() + 10 과 동일하다.

subscript 가 0 에서 시작하다는것을 기억한다면, 이 element 는 vi[10] 가 동일하며, "10 past the first" 이다.

두개의 iterator 가 동일한지 비교하는것 이외에도, <, <=, >, >= 과 같은 관계 연산자를 사용하여,

vector string iterator 를 비교할수있다.

이 iterator 들은 반드시 유효해야하며, 반드시 동일한 vector string 안에서 element 를 나타내야한다.

예를 들어, it mid 와 동일한 vector 의 iterator 라고 가정한다면,

우리는 itmid 의 "before or after" element 를 나타내는 iterator 인지 비교해볼수있다.

if (it < mid) {
    
    // process elements in the first half of vi

}

또한, 2개의 iterator 가 동일한 vector string 의 element 또는 one-off-end 를 나타내고있다면,

이 두 iterator 를 뺄수도있다. (substract)

거리 란 다른 하나를 얻기 위해 하나의 iterator를 변경해야하는 양을 의미한다.

결과 type 은 difference_type 이라는 이름의 signed 정수형 type 이다.

vector string 은 둘다 difference_type 을 가지고있다.

이 type 은 signed 이며, 따라서 빼기(subtraction) 은 음수값을 가질수있다.

Using Iterator Arithmetic

iterator 산술을 사용하는 클래식 알고리즘은 이진 검색이다.

이진 검색은 정렬 된 시퀀스에서 특정 값을 찾는다.

시퀀스 중간에 가장 가까운 element를보고 작동합니다.

element가 우리가 원하는 element이면 완료됩니다.

그렇지 않고, 해당 element가 원하는 것보다 작으면 거부 된 element 다음의 element 만 살펴보면서 검색을 계속한다.

중간 element가 원하는 것보다 크면 전반부 만보고 계속한다.

축소 된 범위에서 새로운 중간 element를 계산하고 element를 찾거나 element가 부족할 때까지 계속 찾는다.

다음과 같이 iterator 를 사용하여 이진 검색을 수행 할 수 있다.

// text must be sorted
// beg and end will denote the range we're searching
auto beg = text.begin(), end = text.end();
auto mid = text.begin() + (end - beg)/2; // original midpoint

// while there are still elements to look at and we haven't yet found sought
while (mid != end && *mid != sought) {

    // is the element we want in the first half?
    if (sought < *mid) {

        end = mid; // if so, adjust the range to ignore the second half

    }

    // the element we want is in the second half
    else {

        beg = mid + 1; // start looking with the element just after mid

    }

    mid = beg + (end - beg) / 2;  // new midpoint

}
Lee : 이제부터 번역하기귀찮아서, 구글 번역기 돌림.

위의 코드는 세 개의 반복자를 정의하는 것으로 시작합니다.

beg는 범위의 첫 번째 요소가되고 마지막 요소를 지나서 하나가 끝나며 중간에 가장 가까운 요소가 중간이됩니다.

이 반복기를 초기화하여 text라는 vector <string>의 전체 범위를 나타냅니다.
루프는 먼저 범위가 비어 있지 않은지 확인합니다. 

mid가 end의 현재 값과 같으면 검색 할 요소가 부족한 것입니다. 

이 경우 조건이 실패하고 잠시 종료됩니다. 

그렇지 않으면 mid는 요소를 참조하고 mid가 원하는 요소를 나타내는 지 확인합니다. 

그렇다면 완료되고 루프를 종료합니다.

처리 할 요소가 아직 있으면 while 내부의 코드는 끝을 이동하거나 구걸하여 범위를 조정합니다. 

mid로 표시된 요소가 추구 된 것보다 크면, seek가 텍스트에 있으면 mid로 표시된 요소 앞에 나타날 것입니다. 

따라서 mid 이후의 요소를 무시할 수 있으며, mid to end를 할당하여 수행합니다. 

*mid가 찾는 것보다 작은 경우 요소는 mid로 표시된 요소 다음의 요소 범위에 있어야합니다.

이 경우 beg가 mid 직후의 요소를 표시하도록하여 범위를 조정합니다.

mid가 우리가 원하는 것이 아니라는 것을 이미 알고 있으므로 범위에서 제거 할 수 있습니다.
잠시 후에 mid는 end와 같거나 우리가 찾고있는 요소를 나타냅니다. mid가 end와 같으면 요소가 텍스트에없는 것입니다.


3.5 Arrays

array vector (3.3 p.96) 와 유사한 데이터 구조다.

하지만 array 는 성능과 유연성간에 절충안을 제공한다.

array vector 처럼, 우리가 위치로 접근할수있는 단일 type 의 객체의 컨테이너이다.

하지만, vector 와 달리, array 는 고정된 크기를 가지고있으며, array 에는 element 를 추가할수없다.

array 는 고정된 크기를 가지고있기때문에, 특정 애플리케이션에 대해 더 나은 run-time 성능을 제공한다.

하지만, 이런 run-time 성능의 이점은 유연성(flexibility) 의 부재로 부터 오는것이다.

 

Tip

 

만약 당신이 얼마나 많은 element 가 필요할지 모른다면, vector 를 사용하라.


3.5.1 Defining and Initializing Built-in Arrays

array 는 복합 (compound) type (2.3, p.50) 이다.

array 의 선언자는 a[d] 의 형태이며,

a 는 정의할 이름, d 는 array 의 차원(dimension) 이다.

이 dimension 은 element 의 수를 지정하며, 반드시 0 보다 커야한다.

array 안에 element 의 수는 array type 의 일부이다.

결과적으로, 이 dimension 은 반드시 컴파일 타임에 알수있어야한다.

즉, dimension 은 반드시 constant expression (2.4.4, p.65) 이어야한다.

unsigned cnt = 42; // not a constant expression
constexpr unsigned sz = 42; // constant expression (2.4.4 p.66)

int arr[10]; // array of ten ints
int* parr[sz]; // array of 42 pointers to int
string band[cnt]; // error : cnt is not a constant expression
string strs[get_size()]; // ok if get_size is constexpr, error otherwise

Warning

 

내장 type 의 변수처럼, 함수안에서 내장형 type 으로 default 초기화된 array 는 undefined 값을 가진다.

 

우리가 array 를 정의할때는 반드시 array 의 type 을 지정해야한다.

array 의 type 은 초기화 list 의 type 을 추정하는 auto 를 사용할수없다.

vector 처럼, array 는 객체를 저장한다. 따라서,  reference 의 array 또한 존재하지않는다.

Explicitly Initializing Array Elements

array 의 요소들은 list 초기화 할수있다.

이렇게하면, dimension 을 생략할수있다.

dimension 을 생략하면, 컴파일러는 initializer 의 수에서 이를 추론한다.

dimension 을 지정하면, initializer 의 수는 지정된 size 를 초과하면안된다.

dimension 이 initializer 의 수보다 크면, initializer 는 첫번째부터 차례대로 element 에 사용되고,

나머지 element 는 값이 초기화된다.

const unsigned sz = 3;
int ia1[sz] = {0, 1, 2}; // array of three ints with value 0, 1, 2
int a2[] = {0, 1, 2}; // an array of dimension 3
int a3[5] = {0, 1, 2}; // equivalent to a3[] = {0, 1, 2, 0, 0}
string a4[3] = {"hi", "bye"}; // same as a4[] = {"hi", "bye", ""}
int a5[2] = {0, 1, 2}; // error : too many initializers

Character Arrays Are Special

문자 array 는 초기화의 다른 추가적인 형태를 가진다.

이런 array 는 string 리터럴로 초기화할수있따.

이런 형태로 초기화할때, string 리터럴은 null 문자로 끝난다는것을 기억하는것이 중요하다.

null 문자는 리터럴의 문자와 함께 array 에 복사된다.

char a1[] = {'C', '+', '+'}; // list initialization, no null
char a2[] = {'C', '+', '+', '\0'}; // list initialization, explicit null
char a3[] = "C++";                 // null terminator added automatically
const char a4[6] = "Daniel";       // error: no space for the null!

a1 의 차원은 3 이다. a2a3 의 차원은 둘다 4 이다.

a4 의 정의는 error 다.

리터럴이 오직 6개의 문자를 명시적으로 포함한다하더라도, array 의 크기는 리터럴과 null 을 저장하기위해 최소 7 이어야한다.

No Copy or Assignment

array 는 다른 array 를 복사함으로써 초기화할수없으며,

또한 array 를 다른 array 에 대입하는것은 illegal 이다.

int a[] = {0, 1, 2}; // array of three ints
int a2[] = a;        // error: cannot initialize one array with another
a2 = a;              // error: cannot assign one array to another

Warning

 

몇몇 컴파일러는 컴파일러의 확장으로 array 대입을 허용한다.

하지만 언제나 표준이 아닌 기능은 피하는것이 좋다.

이런 확장 기능을 사용하는 프로그램들은 다른 컴파일러에서 작동하지않을것이다.

Understanding Complicated Array Declarations

array 도 vector처럼, 어떠한 type 의 객체든 저장할수있다.

예를 들어, pointer 의 array 가 있다.

array 는 객체이기때문에, 우리는 array 에 대한 pointer 와 reference 둘다 정의할수있다.

pointer 를 저장하는 array 를 정의하는것은 매우 직관적인 반면,

array 에 대한 pointer 나 reference 를 정의하는것은 꽤 복잡하다.

int* ptrs[10];            //  ptrs is an array of ten pointers to int
int& refs[10] = /* ? */;  //  error: no arrays of references
int (*Parray)[10] = &arr; //  Parray points to an array of ten ints
int (&arrRef)[10] = arr;  //  arrRef refers to an array of ten ints

default 로, type 수정자는 오른쪽에서 왼쪽으로 bind 된다.

ptrs 의 정의를 읽는것은 오른쪽에서 왼쪽이다. (2.3.3, p.58)

우리는 10개의 크기를 가진 array 를 정의하고, 이 이름은 ptrs 이며, 이는 int 에 대한 pointer 를 저장한다.

반면, Paarray 의 정의를 오른쪽에서 왼쪽으로 읽는것으 도움이 되지않는다.

왜냐면 array 의 차원은 선언되는 이름을 따르기 때문에,

array 선언을 오른쪽에서 왼쪽으로 읽는 것이 아니라 안쪽에서 바깥쪽으로 읽는 것이 더 쉬울 수 있다.

안쪽에서 바깥쪽으로 읽으면 Parray 의 type을 훨씬 쉽게 이해할 수 있다.

먼저 ( ) 안에 *ParrayParray 가 포인터라는것을 의미한다.

이후 오른쪽을 보면, Parray 가 크기 10 의 array 을 가리킨다는것을 알수있다.

이후 왼쪽을 보면, array 의 element 는 int 라는것을 알수있다.

따라서, Parray 10개의 int array 를 가리키는 포인터이다.

이와 유사하게, (&arrRef)arrRef 가 reference 임을 의미하며, 이 reference 가 참조하는 type 은

크기 10 의 array 이다.

이 array 는 int 라는 type 의 element 를 저장한다.

물론, 얼마나 많은 type 수정자를 사용할수있는지에 대한 제한은 없다.

int* (&arry)[10] = ptrs; // arry is a reference to an array of ten pointers

위의 선언을 안쪽에서 바깥쪽으로 읽어보자.

arry 는 reference 다.

오른쪽을 보면, arry 는 크기가 10인 array 를 가리킨다.

왼쪽을 보면, element 의 type 은 int 에 대한 pointer 이다.

따라서, arry 는 10 개의 pointer 를 가진 array 에 대한 reference 다.

 

Tip

 

배열의 이름으로 시작하여 안쪽부터 읽어서 배열 선언을 이해하는 것이 쉬울것이다.


3.5.2 Accessing the Elements of an Array

라이브러리 vector string type 처럼, array 의 element 에 접근하기위해,

range for 루프 또는 subscript [ ] 연산자를 사용할수있다.

일반적으로 index 는 0 에서 시작한다.

10개의 element 를 가진 array 라면, index 1 부터 10 이 아니라, 0 부터 9 까지이다.

array 를 subscript 하기위해서, 일반적으로 size_t 라는 type 의 변수를 정의해야한다.

size_t는 메모리에있는 모든 객체의 크기를 보유 할 수있을만큼 충분히 큰 machine-specific 한,

unsigned type 이다.

size_t type 은 cstddef 헤더에 정의되어있으며, 이는 C 라이브러리 stddef.h 의 C++ 버전이다.

array 가 고정된 크기를 가진다는 점 이외에는, 

array 를 우리가 vector 를 사용하듯이 사용하면 된다.

예를 들어, 3.3.3(p. 104) 에서 작성했던 점수 프로그램을 array 를 작성하여 재구현해보자.

// count the number of grades by clusters of ten: 0--9, 10--19, ... 90--99, 100
unsigned scores[11] = {}; // 11 buckets, all value initialized to 0
unsigned grade;

while (cin >> grade) {

    if (grade <= 100) {

        ++scores[grade / 10]; // increment the counter for the current cluster

    }

}

위의 프로그램과 p.104 의 프로그램의 확실한 차이는 scores 의 선언이다.

이 프로그램에서 scores 는 11개의 unsigned element 를 가진 array 이다.

확실하지않은 차이는 이 프로그램의 subscript 연산자는 C++언어 자체가 정의한점이라는것이다.

p.104 의 프로그램에서 사용된 subscript [ ] 연산자는 vector 라는 템플릿 라이브러리가 정의한것이고,

vector type 의 피연산자에 적용된다.

string 이나 vector 의 경우, 모든 array 를 순회하고하자한다면 range for 루프를 사용하는것이 최고다.

예를 들어, 우리는 scores 를 아래와 같이 인쇄할수있다.

// for each counter in scores
for (auto i : scores) {

    cout << i << " "; // print the value of that counter

}

cout << endl;

"차원(dimension)" 은 각 array type 의 일부이기때문에,

시스템은 scores 안에 얼마나 많은 element 가 있는지 알고있다.

range for 루프를 사용한다는것의 의미는 우리가 이 순회를 우리 스스로 관리할 필요가 없음을 의미한다.

Checking Subscript Values

string vector 처럼, subscript 값이 범위안에 있는지,

즉, index 값이 0 보다 크거나 같고, array 의 크기보다 작은지 확인하는것은 프로그래머의 몫이다.

세부 사항에 주의를 기울이고 코드를 철저히 테스트하는 것이

프로그램이 array 의 경계를 가로 지르는 것을 막을 수있다.

subscript 가 범위를 벗어난다해도, 프로그램은 여전히 컴파일되지만

이는 매우 치명적일수있다.

 

Warning

 

보안 문제의 가장 일반적인 원인은 buffer overflow 버그이다.

이러한 버그는 프로그램이 subscript를 확인하지 못하고,

실수로 array 또는 유사한 데이터 구조 범위를 벗어난 메모리를 사용할 때 발생한다.


3.5.3 Pointers and Arrays

C++ 에서 포인터와 배열은 밀접하게 얽혀있다.

특히 곧 보겠지만, 배열을 사용할때, 컴파일러는 일반적으로 배열을 포인터로 변환한다.

우리는 주소 연산자 &를 사용하여, 객체의 포인터를 얻는다. (2.3.2 p.52)

& 연산자는 어떠한 객체에든 적용될수있다.

배열의 요소들은 객체이다.

우리가 배열을 subscript 할때, 결과는 배열 의 해당 위치에 있는 객체이다.

다른 객체들처럼, 우리는 배열 요소에 대한 포인터를, 요소의 주소를 가져옴으로써 얻을수있다.

string num[] = {"One", "two", "three"}; // array of strings
string* p = &nums[0]; // p points to the first element in nums

하지만 배열은 특별한 property 를 가지고있는데,

우리가 배열을 사용하면, 컴파일러는 자동적으로 첫번째 요소에 대한 포인터로 대체한다.

string* p2 = nums; // equivalent to p2 = &nums[0]

Note

 

대부분의 표현식에서, 우리가 배열 type 의 객체를 사용할때,

우리는 사실 배열의 첫번째 요소에 대한 포인터를 사용하는것이다.

배열에 대한 연산이 실제로 포인터에 대한 연산이라는 사실에는 다양한 의미가 있다.

이러한 의미 중 하나는 auto (§ 2.5.2, p. 68)를 사용하여 정의 된 변수의 initializer로 배열을 사용할 때,

추론 된 type은 배열이 아니라 포인터라는 것이다.

int ia[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; // ia is an array of ten ints
auto ia2(ia); // ia2 is an int* that points to the first element in ia
ia2 = 42; // error : ia2 is a pointer, and we can't assign an int to a pointer

ia 는 10개의 int 의 배열이지만,  ia 를 initializer 로 사용하면, 

컴파일러는 우리가 아래와같이 작성한것으로 간주한다.

auto ia2(&ia[0]); // now it's clear that ia2 has type int*

반면, decltype (§ 2.5.3, p. 70)을 사용할 때는 이 변환이 발생하지 않는다는 점은 주목할 가치가 있다.

decltype(ia) 가 return 하는 type 은 10개의 int 의 배열이다.

// ia3 is an array of ten ints
decltype(ia) ia3 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

ia3 = p; // error : can't assign an int* to an array
int3[4] = i; // ok : assigns the value of i to an element in ia3

Pointers Are Iterators

배열의 요소를 나타내는 포인터는 앞서 2.3.2 (p.52) 에서 다룬것들보다 추가적인 연산을 가지고있다.

특히, 배열 요소들에 대한 포인터는 vector string 의 iterator 와 동일한 연산을 지원한다. 

예를 들어, 배열의 특정 요소에서 다음 요소로 이동할때 ++ 연산자를 사용할수있다.

int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int* p = arr; // p points to the first element in arr
++p; // p points to arr[1]

vector 의 요소들을 순회하기위해 iterator 를 사용하는것처럼,

pointer 는 배열의 요소들을 순회하기위해 사용될수있다.

물론, 이렇게 하기위해서는 첫번째 요소와 one past last 요소에 대한 포인터를 얻어야한다.

우리가 앞서 보았듯이, 첫번째 주소를 얻는 방법은 배열 자체를 사용하거나, 

첫번째 요소의 주소를 가져오면 된다.

반면 off-the-end 포인터는 배열의 다른 특별한 property 를 사용하여 얻을수있다.

우리는 배열의 마지막 요소의 one past the last (마지막 요소의 다음). 즉 존재하지않는 요소의 주소를 다음과 같이 얻을수있다.

int* e = &arr[10]; // pointer just past the last element in arr

위의 코드에서, 우리는 존재하지않는 요소를 index 하기위해 subscript 연산자를 사용하였다.

arr 은 10개의 요소를 가지고있다, 그리고 arr 의 마지막 요소의 index 는 9 이다.

이 요소로 할수있는 유일한 작업은 주소를 가져오는 일이다.

off-the-end iterator 처럼, off-the-end 포인터는 요소를 가리키지않는다.

결과적으로, off-the-end 포인터는 역참조 할수없고, ++ 또한 할수없다.

이 포인터를 사용하여, arr 의 각 요소를 print 하는 루프를 작성해보자.

for (int* b = arr; b != e; ++b) {

    cout << *b << endl; // print the elements in arr

}

C+11 : The Library begin and end Functions

위의 코드와 같이, off-the-end 포인터를 계산할수도있지만,

이렇게 하는것은 error 가 발생할 우려가 다분하다.

포인터를 쉽고 안전하게 사용하기위해, C+11 의 새로운 라이브러리는 begin end 라는 2개의 함수를 제공한다.

이러한 함수는 비슷한 이름의 컨테이너 멤버처럼 작동한다 (§ 3.4.1, p. 106).

하지만 배열은 클래스 type 이 아니다. 

따라서, 이 함수들은 멤버 함수가 아니다. 대신에, 이 함수들은 배열을 인자로 받는다.

int ia[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; // ia is an array of ten ints
int* beg = begin(ia); // pointer to the first element in ia
int* last = end(ia); // pointer one past the last element in ia

begin 은 첫번째 포인터를 return 하며, end 는 주어진 배열의 one-past-the-last 요소의 포인터를 return 한다.

이 함수들은 iterator 헤더에 정의되어있다.

begin end 를 사용하는것은, 배열의 각 요소를 처리하는 루프를 더 쉽게 작성할수있게 해준다.

예를 들어, arr int 값들을 저장하는 배열이라고 가정하면,

arr 의 첫번째 음수 (negative value) 를 아래와 같이 찾을수있다.

// pbeg points to the first and pend points just past the last element in arr
int* pbeg = begin(arr);
int* pend = end(arr);

// find the first negative element, stopping if we've seen all the elements
while (pbeg != pend && *pbeg >= 0) {

    ++pbeg;

}

위의 코드에서, 우리는 pbeg pend 라는 2개의 int 포인터를 정의하며 시작한다.

pbeg 는 첫번째 요소를 나타내며, pend 는 배열의 one-past-the-last-element 를 가리킨다.

pbeg 는 요소를 가리키기때문에, 역참조할수있고, 이 요소가 음수인지 체크할수있다.

이렇게 되면, 조건은 실패하고 루프를 빠져나오게된다.

그렇지않다면, 우리는 다음 요소를 보기위해 포인터를 ++ 한다.

 

Note

 

내장형 배열의 "one-past-the-end" 포인터는 vector end 연산이 return 하는 iterator 와

동일한 방식으로 작동한다.

특히, off-the-end 포인터는 역참조할수없고, ++ 할수없다.

Pointer Arithmetic (포인터 산술)

배열의 요소를 나타내는 포인터는 Table 3.6 (p. 107) 과 Table 3.7(p. 111) 의

모든 iterator 연산을 사용할수있다.

(역참조, ++, 비교, 정수값 더하기, 2개의 포인터끼리 빼기등)

이 연산들은 iterator 에 적용되는것과 동일한 의미를 가진다.

만약 포인터에 정수형값을 더하거나 빼면, 결과는 새로운 포인터이다.

이 새 포인터는 원래 포인터 앞 (또는 뒤)에 주어진 숫자 요소를 가리킨다.

constexpr size_t sz = 5;
int arr[sz] = {1, 2, 3, 4, 5};
int* ip = arr; // equivalent to int *ip = &arr[0]
int* ip2 = ip + 4; // ip2 points to arr[4], the last element in arr

ip4를 더하는 결과는 ip 가 원래 가리켰던 요소로부터 4개 앞으로간 요소를 가리키는 포인터이다.

포인터에 정수값을 더하는 결과는 반드시 동일한 배열의 요소를 가리키는 포인터이거나,

"juce past the end" 포인터여야한다.

// ok ok: arr is converted to a pointer to its first element; p points one past the end of arr
int* p = arr + sz; // use catution -- do not dereference!
int* p2 = arr + 10; // error : arr has only 5 elements; p2 has undefined value

 

위의 코드는 arr sz 를 더하는데, 

이때 컴파일러는 arr arr 의 첫번째 요소를 가리키는 포인터로 변환한다.

sz 를 이 포인터에 더하면, sz 만큼의 위치를 나아간 새 포인터를 얻는다. (여기서는 5)

즉, arr 의 one-past-last-element 를 가리킨다.

one-past-the-last-element 보다 큰 값으로 포인터를 계산하는것은 error 다.

하지만 이런 error 를 컴파일러는 발견하지못한다.

iterator 처럼, 두개의 포인터를 빼는것은 두 개의 포인터간의 거리를 나타낸다.

이 포인터는 반드시 동일한 배열의 요소를 가리키는 포인터여야만한다.

auto n = end(arr) - begin(arr); // n is 5, the number of elements in arr

두개의 포인터를 뺀 결과는 ptrdiff_t 라는 라이브러리 type 이다. 

size_t 처럼, ptrdiff_t type 은 machine-specific type 이며, cstddef 헤더에 정의되어있다.

빼기(subtraction) 는 음수인 distance 를 산출할수있기때문에,

ptrdiff_t 는 signed 정수형 type 이다.

우리는 배열의 요소를 가리키는 포인터들을 비교하기위해 관계연산자를 사용할수있다.

예를 들어, 아래와 같이 arr 의 요소를 순회할수있다.

int* b = arr; 
int* e = arr + sz;

while (b < e) {

    // use *b
    ++b;
    
}

하지만, 2개의 연관성이 없는 객체를 가리키는 포인터에는 관계연산자를 사용할수없다. (즉, 비교할수없다)

int i = 0;
int sz = 42;
int* p = &i;
int* e = &sz;

// undefined: p and e are unrelated; comparison is meaningless!
while (p < e) {

}

이 시점에서 유틸리티가 모호 할 수 있지만,

포인터 산술은 null 포인터 (§ 2.3.2, p. 53) 및 배열이 아닌 객체를 가리키는 포인터에도 유효하다.

후자의 경우 포인터는 동일한 객체 또는 해당 객체를 지나는 객체(one past that object) 를 가리켜야한다.

만약 pnull 포인터이면 값이 0에서 p까지 인 정수 상수 표현식 (§ 2.4.4, p. 65)을 더하거나 뺄 수 있다.

또한, 두 개의 null 포인터를 서로 뺄 수도 있습니다.이 경우 결과는 0 이다.

Interaction between Dereference and Pointer Arithmetic

포인터에 정수 값을 추가 한 결과는 포인터이다.

결과 포인터가 요소를 가리킨다고 가정하면 결과 포인터를 역 참조 할 수있다.

int ia[] = {0, 2, 4, 6, 8}; // array with 5 elements of type int
int last = *(ia + 4); // ok : initializes last to 8, the value of ia[4]

*(ia + 4) 표현식은 ia를지나 4 개의 요소 주소를 계산하고 결과 포인터를 역 참조한다.

이 표현식은 ia[4] 로 쓰는것과 동일하다.

§ 3.4.1 (p. 109)에서 역 참조 및 점 연산자를 포함하는 식에는 괄호가 필요하다는 점을 다시 기억해보라.

마찬가지로 이 포인터 더하기 주위의 괄호는 필수적이다.

last = *ia + 4;  // ok: last = 4, equivalent to ia[0] + 4

위와 같이 작성하는것은, ia 를 역참조한후, 이 역참조된 값에 4 를 더하라는것이다.

왜 이렇게 작동하는지는 4.1.2(p.136) 에서 자세히 다룬다.

Subscripts and Pointers

지금까지 살펴본 것처럼 배열 이름을 사용할 때 대부분의 위치에서 실제로 해당 배열의 첫 번째 요소에 대한 포인터를 사용한다.

컴파일러가 이 변환을 수행하는 위치는 우리가 배열을 subscript 할 때이다.

int ia[] = {0, 2, 4, 6, 8}; // array with 5 elemtns of type int

ia[0]을 쓰면 배열의 이름을 사용하는 표현식이다.

배열을 subscript 할 때 실제로 해당 배열의 요소에 대한 포인터를 subscript 한다.

int i = ia[2];  // ia is converted to a pointer to the first element in ia
                         // ia[2] fetches the element to which (ia + 2) points
int* p = ia;    // p points to the first element in ia
i = *(p + 2);   // equivalent to i = ia[2]

포인터가 배열의 요소 (또는 마지막 요소를 지나는 요소)를 가리키는 한 모든 포인터에서 subscript 연산자를 사용할 수 있다.

int* p = &ia[2];    // p points to the element indexed by 2
int j = p[1];       // p[1] is equivalent to *(p + 1),
                               // p[1] is the same element as ia[3]
int k = p[-2];    // p[-2] is the same element as ia[0]

이 마지막 예제는 subscript 연산자가있는 vector string과 같은 라이브러리 type과 배열 간의 중요한 차이점을 지적한다.

라이브러리 type은 subscript과 함께 사용되는 인덱스를 unsigned 값으로 강제한다.

하지만 내장 subscript 연산자는 그렇지 않다.

내장 subscript와 함께 사용되는 index는 음수 값일 수 있다.

물론 결과 주소는 원래 포인터가 가리키는 배열의 요소 (또는 배열의 끝을지나)를 가리켜야한다.

 

Warning

 

vector string의 subscript와 달리 내장 subscript 연산자의 인덱스는 unsinged가 아니다.


3.5.4 C-Style Character Strings

Warning

 

C++는 C 스타일 문자열을 지원하지만 C++ 프로그램에서 사용해서는 안된다.

C 스타일 문자열은 놀랍도록 풍부한(?) 버그 소스이며 많은 보안 문제의 근본 원인이다.

또한 사용하기가 더 어렵다.

 

문자열 리터럴은 C++가 C에서 상속하는보다 일반적인 구조의 인스턴스이다.

C 스타일 문자열은 type이 아니다.

대신 문자열을 표현하고 사용하는 방법에 대한 규칙이다.

이 규칙을 따르는 문자열은 문자 배열에 저장되고 null로 종료된다.

null terminated 는 문자열의 마지막 문자 다음에 null 문자 ( '\ 0')가 옴을 의미한다.

반적으로 우리는 이러한 문자열을 조작하기 위해 포인터를 사용한다.

C Library String Functions

표준 C 라이브러리는 표 3.8에 나열된 C 스타일 문자열에서 작동하는 함수 세트를 제공한다.

이러한 함수는 C 헤더 string.h의 C + 버전 인 cstring 헤더에 정의된다.

 

Table 3.8. C-Style Character String Functions

Warning

 

Table 3.8의 함수는 해당 문자열 매개 변수를 확인하지 않는다.

 

이러한 루틴에 전달 된 포인터는 null로 끝나는 배열을 가리켜야한다.

char ca[0] = {'C', '+', '+'}; // not null terminated
cout << strlen(ca) << endl; // disaster : ca isn't null terminated

위의 경우, cachar 의 배열이지만, null terminated 되지않는다.

이 결과는 undefined 이다.

이 호출의 가장 가능성있는 효과는 strlennull 문자를 만날 때까지 ca 뒤에 오는 메모리를 계속 조사한다는것이다.

Comparing Strings

두 C 스타일 문자열을 비교하는 것은 라이브러리 string을 비교하는 방법과는 상당히 다르다.

두 라이브러리 string을 비교할 때 일반 관계형 또는 같음 연산자를 사용한다.

string s1 = "A string example";
string s2 = "A different string";
if (s1 < s2) // false : s2 is less than s1

유사하게 정의 된 C 스타일 문자열에서 이러한 연산자를 사용하면 문자열 자체가 아닌 포인터 값을 비교한다.

const char ca1[] = "A string example";
const char ca2[] = "A different string";
if (ca1 < ca2) // undefined : compare two unrelated addresses

배열을 사용할 때 실제로 배열의 첫 번째 요소에 대한 포인터를 사용하고 있음을 기억하라. (§ 3.5.3, p. 117). 

따라서 이 조건은 실제로 두 개의 const char* 값을 비교한다.

이러한 포인터는 동일한 객체를 나타내지않으므로, 비교가 정의되지 않는다.

포인터 값보다는 문자열을 비교하기 위해 strcmp를 호출 할 수 있다.

이 함수는 문자열이 같으면 0을 반환하고,

첫 번째 문자열이 두 번째 문자열보다 크거나 작은 지 여부에 따라 양수 또는 음수 값을 반환한다.

if (strcmp(ca1, ca2) < 0) // same effect as string comparison s1 < s2

Caller Is Responsible for Size of a Destination String

C 스타일 문자열을 연결하거나 복사하는 것도 라이브러리 string에 대한 동일한 작업과 매우 다르다.

예를 들어 위에서 정의한 두 string s1s2를 연결하려면 직접 연결할 수 있다.

// initialize largeStr as a concatenation of s1, a space, and s1
string largeStr = s1 + " " + s2;

위의 코드처럼, 두 배열 ca1ca2에 대해 동일한 작업을 수행하면 오류가 발생한다.

표현식 ca1 + ca2는 두 개의 포인터를 추가하려고 한다. 이는 불법이며 의미가 없다. 
대신 strcatstrcpy를 사용할 수 있다.

그러나 이러한 함수를 사용하려면 결과 문자열을 저장할 배열을 전달해야한다.

전달하는 배열은 끝에 null 문자를 포함하여 생성 된 문자열을 저장할 수있을만큼 충분히 커야한다.

여기에 표시되는 코드는 일반적인 사용 패턴이지만 심각한 오류가 발생할 가능성이 있다.

// disastrous if we miscalculated the size of largeStr
strcpy(largeStr, ca1);     // copies ca1 into largeStr
strcat(largeStr, " ");     // adds a space at the end of largeStr
strcat(largeStr, ca2);     // concatenates ca2 onto largeStr

문제는 largeStr에 필요한 크기를 쉽게 잘못 계산할 수 있다는 것이다.

또한 largeStr에 저장하려는 값을 변경할 때마다 크기를 올바르게 계산했는지 다시 확인해야한다.

불행히도 이 코드와 유사한 프로그램이 널리 배포된다.

이러한 코드가있는 프로그램은 오류가 발생하기 쉬우며 종종 심각한 보안 누출로 이어진다.

 

Tip

 

대부분의 응용 프로그램에서 C 스타일 문자열보다 라이브러리 string을 사용하는 것이 더 안전 할뿐만 아니라  더 효율적이다.


3.5.5 Interfacing to Older Code

많은 C++ 프로그램은 표준 라이브러리보다 이전에서 만들어졌으며,

string vector type을 사용하지 않는다.

또한 많은 C ++ 프로그램은 C 또는 C ++ 라이브러리를 사용할 수없는 다른 언어로 작성된 프로그램과 인터페이스 한다.

따라서 최신 C++로 작성된 프로그램은 배열 및 / 또는 C 스타일 문자열을 사용하는 코드에 인터페이스해야 할 수 있다.

C++ 라이브러리는 인터페이스를보다 쉽게 관리 할 수있는 기능을 제공한다.

Mixing Library strings and C-Style Strings

§ 3.2.1 (p. 84)에서 우리는 문자열 리터럴로 string을 초기화 할 수 있음을 보았다.

string s("Hello World");  // s holds Hello World

보다 일반적으로, 문자열 리터럴을 사용할 수있는 모든 곳에서 null로 끝나는 문자 배열을 사용할 수 있다.

- null로 끝나는 문자 배열을 사용하여 string을 초기화하거나 대입할수있다.

- null로 끝나는 문자 배열을 문string 더하기 연산자에 대한 하나의 피연산자 (두 피연산자는 아님)로 사용하거나,

string 복합 할당 (+=) 연산자의 오른쪽 피연산자로 사용할 수 있다.

 

하지만 역기능은 제공되지 않는다.

C 스타일 문자열이 필요할 때 라이브러리 string을 사용하는 직접적인 방법은 없다.

예를 들어 string으로 문자 포인터를 초기화하는 방법은 없다.

그러나 우리가 원하는 것을 달성하기 위해 자주 사용할 수있는 c_str이라는 string 멤버 함수가 있다.

string s("Hello World"); // s holds hello world
char* str = s; // error : can't initialize a char* from a string
const char* str = s.c_str(); // ok

이름 c_str은 함수가 C 스타일 문자열을 반환 함을 나타낸다.

즉, string의 문자와 동일한 데이터를 보유하는 null 종료 문자 배열의 시작에 대한 포인터를 리턴한다.

포인터의 유형은 const char* 이므로 배열의 내용을 변경할 수 없다.
c_str에 의해 반환 된 배열은 무기한 유효하다고 보장 할 수 없다.

s 값을 변경할 수있는 s를 이후에 사용하면 이 배열이 무효화 될 수 있다.

 

Warning

 

프로그램이 str()에 의해 반환 된 배열의 내용에 계속 액세스해야하는 경우 프로그램은 c_str에 의해 반환 된 배열을 복사해야한다.

Using an Array to Initialize a vector

§ 3.5.1 (p. 114)에서 우리는 내장 배열을 다른 배열로 초기화 할 수 없다고 언급했다. 

vector 로 배열을 초기화 할 수도 없다.. 

그러나 배열을 사용하여 vector 를 초기화할수는있다.

이를 위해 첫 번째 요소의 주소와 복사하려는 마지막 요소의 주소를 지정한다.

int int_arr[] = {0, 1, 2, 3, 4, 5};
// ivec has six elements; each is a copy of the corresponding element in int_arr
vector<int> ivec(begin(int_arr), end(int_arr));

ivec를 구성하는 데 사용되는 두 포인터는 ivec의 요소를 초기화하는 데 사용할 값 범위를 표시한다.

두 번째 포인터는 복사 할 마지막 요소 하나를 가리 킨다.

이 경우 라이브러리 시작 및 종료 함수 (§ 3.5.3, p. 118)를 사용하여 int_arr의 첫 번째 요소와,

마지막 요소의 마지막 요소에 대한 포인터를 전달했다.

결과적으로 ivec에는 6개의 요소가 있으며 각 요소는 int_arr의 해당 요소와 동일한 값을 갖는다.

지정된 범위는 배열의 하위 집합이 될 수 있다.

// copies three elements: int_arr[1], int_arr[2], int_arr[3]
vector<int> subVec(int_arr + 1, int_arr + 4);

이 초기화는 세 가지 요소로 subVec을 만든다.

이러한 요소의 값은 int_arr[1]에서 int_arr[3]까지의 값을 복사 한 것이다.

Advice: Use Library Types Instead of Arrays

포인터와 배열은 놀랍게도 오류가 발생하기 쉽다.

문제의 일부는 개념적이다.

포인터는 저수준 조작에 사용되며 실수를하기 쉽다.

특히 포인터와 함께 사용되는 선언 구문 때문에 다른 문제가 발생한다.

최신 C++ 프로그램은 기본 제공 배열 및 포인터 대신 vector및 iterator를 사용해야하며,

C 스타일 배열 기반 문자열 대신 string 을 사용해야한다.


3.6 Multidimensional Arrays

Lee : 6 페이지나 되서, 매일 하나 소 챕터씩 하겠음..너무 지루함

Initializing the Elements of a Multidimensional Array

Subscripting a Multidimensional Array

Using a Range for with Multidimensional Arrays

Note

Pointers and Multidimensional Arrays

Note

Note

Type Aliases Simplify Pointers to Multidimensional Arrays


Summary

가장 중요한 라이브러리 유형에는 vectorstring이 있다. 

string은 가변 길이 문자 시퀀스이고 vector는 단일 유형 객체의 컨테이너이다.
iterator를 사용하면 컨테이너에 저장된 객체에 간접적으로 액세스 할 수 있다.

iteratorstringvector의 요소에 액세스하고 탐색하는 데 사용된다. 
배열 요소에 대한 배열과 포인터는 vector string 라이브러리에 대한 저수준 아날로그를 제공한다.

일반적으로 라이브러리 클래스는 언어에 내장 된 저수준 배열 및 포인터 대안보다 우선적으로 사용되어야한다.


Defined Terms

begin

buffer overflow

C-style strings

class template

compiler extension

container

copy initialization

difference_type

direct initialization

empty

end

getline

index

instantiation

iterator

iterator arithmetic

null-terminated string

off-the-end iterator

pointer arithmetic

ptrdiff_t

push_back

range for

size

size_t

size_type

string

using declarations

value initialization

vector

++ operator

[ ] operator

-> operator

<< operator

>> operator

! operator

&& operator

|| operator