C++ Primer 第三章 Strings, Vectors, and Arrays
- 3.1. Namespace using Declarations
- 3.2. Library string Type
- 3.2.1. Defining and Initializing strings
- Direct and Copy Forms of Initialization
- 3.2.2. Operations on strings
- Reading and Writing strings
- Using getline to Read an Entire Line
- The string empty and size Operations
- Comparing strings
- Assignment for strings
- Adding Two strings
- Adding Literals and strings
- 3.2.3. Dealing with the Characters in a string
- Range-Based for
- Change the Characters in a string
- Processing Only Some Characters
- 3.3. Library vector Type
- 3.3.1. Defining and Initializing vectors
- List Initializing a vector
- Creating a Specified Number of Elements
- Value Initialization
- List Initializer or Element Count
- 3.3.2. Adding Elements to a vector
- vectors Grow Efficiently
- 3.3.3. Other vector Operations
- Computing a vector Index
- Subscripting Does Not Add Elements
- 3.4. Introducing Iterators
- 3.4.1. Using Iterators
- Iterator Operations
- Iterator Types
- The begin and end Operations
- 3.4.2. Iterator Arithmetic
- 3.5. Arrays
- 3.5.1. Defining and Initializing Built-in Arrays
- Complicated Array Declarations
- 3.5.3. Pointers and Arrays
- The Library begin and end Functions
- Pointer Arithmetic
- Subscripts and Pointers
- 3.6. Multidimensional Arrays
- Using a Range for with Multidimensional Arrays
- Pointers and Multidimensional Arrays
3.1. Namespace using Declarations
Namespaces
- A using declaration lets us use a name from a namespace without qualifying the
name
with a namespace_name :: prefix.
using namespace::name;
- Code inside headers ordinarily should not use using declarations.
3.2. Library string Type
-
A string is a variable-length sequence of characters.
-
To use the string type, we must include the string header.
-
Because it is part of the library, string is defined in the std namespace.
#include <string>
using std::string;
3.2.1. Defining and Initializing strings
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 cccccccccc
Direct and Copy Forms of Initialization
-
When we initialize a variable using =, we are asking the compiler to copy initialize the object by copying the initializer on the right-hand side into the object being created.
-
Otherwise, when we omit the =, we use direct initialization.
-
When we have a single initializer, we can use either the direct or copy form of initialization.
-
When we initialize a variable from more than one value, such as in the initialization of s4 above, we must use the direct form of initialization:
string s5 = "hiya"; // copy initialization
string s6("hiya"); // direct initialization
string s7(10, 'c'); // direct initialization; s7 is cccccccccc
string s8 = string(10, 'c'); // copy initialization; s8 is cccccccccc
Difference between direct and copy Initialization: Is there a difference between copy initialization and direct initialization?
3.2.2. Operations on strings
Reading and Writing strings
// 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;
}
-
The string input operator reads and discards any leading whitespace (e.g., spaces, newlines, tabs).
-
Like the input and output operations on the built-in types, the string operators return their left-hand operand as their result.
string s1, s2;
cin >> s1 >> s2; // read first input into s1, second into s2
cout << s1 << s2 << endl; // write both strings
Reading an Unknown Number of strings:
int main()
{
string word;
while (cin >> word) // read until end-of-file
cout << word << endl; // write each word followed by a new line
return 0;
}
Using getline to Read an Entire Line
This function reads the given stream up to and including the first newline and stores what it read—not including the newline—in its string argument.
If the first character in the input is a newline, then the resulting string is the empty string.
The string empty and size Operations
std::string::size
// read input a line at a time and discard blank lines
while (getline(cin, line))
if (!line.empty())
cout << line << endl;
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;
Return: size returns a string::size_type value.
Although we don’t know the precise type of string::size_type, we do know that it is an unsigned type big enough to hold the size of any string.
Admittedly, it can be tedious to type string::size_type. Under the new standard, we can ask the compiler to provide the appropriate type by using auto or decltype.
auto len = line.size(); // len has type string::size_type
size
和 length
相同,返回字符串长度(不包括末尾的空字符)。
Comparing strings
-
The equality operators (== and !=) test whether two strings are equal or unequal, respectively.
-
The relational operators <, <=, >, >= test whether one string is less than, less than or equal to, greater than, or greater than or equal to another.
-
These operators use the same strategy as a (case-sensitive) dictionary:
-
If two strings have different lengths and if every character in the shorter string is equal to the corresponding character of the longer string, then the shorter string is less than the longer one.
-
If any characters at corresponding positions in the two strings differ, then the result of the string comparison is the result of comparing the first character at which the strings differ.
-
Assignment for strings
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 now the empty string
Adding Two strings
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
string s1 = "hello", s2 = "world"; // no punctuation in s1 or s2
string s3 = s1 + ", " + s2 + '\n';
When we mix strings and string or character literals, at least one operand to each + operator must be of 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
string s6 = (s1 + ", ") + "world";
The subexpression s1 + ", " returns a string, which forms the left-hand operand of the second + operator. It is as if we had written:
string tmp = s1 + ", "; // ok: + has a string operand
s6 = tmp + "world"; // ok: + has a string operand
string s7 = ("hello" + ", ") + s2; // error: can't add string literals
3.2.3. Dealing with the Characters in a string
cctype Functions:
Range-Based for
The body of a range for
must not change the size of the sequence over which it is iterating.
for (declaration : expression)
statement
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
On each iteration, the next character in str will be copied into c.
string s("Hello World!!!");
// punct_cnt has the same type that s.size returns; see § 2.5.3 (p. 70)
decltype(s.size()) punct_cnt = 0;
// count the number of punctuation characters in s
for (auto c : s) // for every char in s
if (ispunct(c)) // if the character is punctuation
++punct_cnt; // increment the punctuation counter
cout << punct_cnt
<< " punctuation characters in " << s << endl;
Change the Characters in a string
string s("Hello World!!!");
// convert s to uppercase
for (auto &c : s) // for every char in s (note: c is a reference)
c = toupper(c); // c is a reference, so the assignment changes the char in s
cout << s << endl;
Processing Only Some Characters
if (!s.empty()) // make sure there's a character to print
cout << s[0] << endl; // print the first character in s
3.3. Library vector Type
vector vs. list in STL
Standard Template Library Programmer’s Guide
C++ benchmark – std::vector VS std::list VS std::deque
c++ vector
STL Containers
- A vector is a collection of objects, all of which have the same type.
- A vector is a class template.
Templates are not themselves functions or classes.
Instead, they can be thought of as instructions to the compiler for generating classes or functions. - The process that the compiler uses to create classes or functions from templates is called instantiation.
vector<int> ivec; // ivec holds objects of type int
vector<Sales_item> Sales_vec; // holds Sales_items
vector<vector<string>> file; // vector whose elements are vectors
3.3.1. Defining and Initializing vectors
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> svec(ivec2); // error: svec holds strings, not ints
List Initializing a vector
vector<string> articles = {"a", "an", "the"};
Creating a Specified Number of Elements
vector<int> ivec(10, -1); // ten int elements, each initialized to -1
vector<string> svec(10, "hi!"); // ten strings; each element is "hi!"
Value Initialization
vector<int> ivec(10); // ten elements, each initialized to 0
vector<string> svec(10); // ten elements, each an empty string
There are two restrictions on this form of initialization:
-
Some classes require that we always supply an explicit initializer.
If our vector holds objects of a type that we cannot default initialize, then we must supply an initial element value; -
When we supply an element count without also supplying an initial value, we must use the direct form of initialization.
List Initializer or Element Count
-
When we use parentheses, we are saying that the values we supply are to be used to construct the object.
-
When we use curly braces, {…}, we’re saying that, if possible, we want to list initialize the object.
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
- On the other hand, if we use braces and there is no way to use the initializers to list initialize the object, then those values will be used to construct the object.
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"
Although we used braces on all but one of these definitions, only v5 is list initialized.
3.3.2. Adding Elements to a vector
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 elements, values 0 . . . 99
// read words 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
}
vectors Grow Efficiently
If differing element values are needed, it is usually more efficient to define an empty vector and add elements as the values we need become known at run time.
3.3.3. Other vector Operations
To use size_type, we must name the type in which it is defined. A vector type always includes its element type:
vector<int>::size_type // ok
vector::size_type // error
Computing a vector Index
// count the number of grades by clusters of ten: 0--9, 10--19, . .. 90--99, 100
vector<unsigned> scores(11, 0); // 11 buckets, all initially 0
unsigned grade;
while (cin >> grade) { // read the grades
if (grade <= 100) // handle only valid grades
++scores[grade/10]; // increment the counter for the current cluster
}
Subscripting Does Not Add Elements
vector<int> ivec; // empty vector
for (decltype(ivec.size()) ix = 0; ix != 10; ++ix)
ivec[ix] = ix; // disaster: ivec has no elements
for (decltype(ivec.size()) ix = 0; ix != 10; ++ix)
ivec.push_back(ix); // ok: adds a new element with value ix
3.4. Introducing Iterators
3.4.1. Using Iterators
// the compiler determines the type of b and e;
// 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
Iterator Operations
string s("some string");
if (s.begin() != s.end()) { // make sure s is not empty
auto it = s.begin(); // it denotes the first character in s
*it = toupper(*it); // make that character uppercase
}
Iterator Types
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 but not write elements
string::const_iterator it4; // it4 can read but not write characters
The begin and end Operations
If the object is const, then begin and end return a const_iterator; if the object is not const, they return iterator:
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
auto it3 = v.cbegin(); // it3 has type vector<int>::const_iterator
3.4.2. Iterator Arithmetic
We can also subtract two iterators so long as they refer to elements in, or one off the end of, the same vector or string.
The result is the distance between the iterators.
The result type is a signed integral type named difference_type.
A classic algorithm that uses iterator arithmetic is binary search.
// 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) {
if (sought < *mid) // is the element we want in the first half?
end = mid; // if so, adjust the range to ignore the second half
else // the element we want is in the second half
beg = mid + 1; // start looking with the element just after mid
mid = beg + (end - beg)/2; // new midpoint
}
3.5. Arrays
-
Like a vector, an array is a container of unnamed objects of a single type that we access by position.
-
Unlike a vector, arrays have fixed size; we cannot add elements to an array.
3.5.1. Defining and Initializing Built-in Arrays
- The dimension must be known at compile time, which means that the dimension must be a constant expression.
unsigned cnt = 42; // not a constant expression
constexpr unsigned sz = 42; // constant expression
// constexpr see § 2.4.4 (p. 66)
int arr[10]; // array of ten ints
int *parr[sz]; // array of 42 pointers to int
string bad[cnt]; // error: cnt is not a constant expression
string strs[get_size()]; // ok if get_size is constexpr, error otherwise
string strs[3];
-
We cannot use auto to deduce the type from a list of initializers.
-
As with vector, arrays hold objects. Thus, there are no arrays of references.
const unsigned sz = 3;
int ia1[sz] = {0,1,2}; // array of three ints with values 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
int a2[] = {0, 1, 2}; // an array of three elements, not three-dimensional
- Character arrays have an additional form of initialization: We can initialize such arrays from a string literal。
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!
The dimension of a1 is 3; the dimensions of a2 and a3 are both 4. The definition of a4 is in error.
- We cannot initialize an array as a copy of another array, nor is it legal to assign one array to another:
Complicated Array Declarations
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
int *(&arry)[10] = ptrs; // arry is a reference to an array of ten pointers
3.5.3. Pointers and Arrays
- When we use an array as an initializer for a variable defined using auto, the deduced type is a pointer, not an array:
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
- It is worth noting that this conversion does not happen when we use decltype. The type returned by decltype(ia) is array of ten ints:
// 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
ia3[4] = i; // ok: assigns the value of i to an element in ia3
The Library begin and end Functions
arrays are not class types, so these functions are not functions. Instead, they take an argument that is an array:
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 returns a pointer to the first, and end returns a pointer one past the last element in the given array. These functions are defined in the iterator header.
Pointer Arithmetic
int *b = arr, *e = arr + sz;
while (b < e) {
// use *b
++b;
}
int i = 0, sz = 42;
int *p = &i, *e = &sz;
// undefined: p and e are unrelated; comparison is meaningless!
while (p < e)
Although the utility may be obscure at this point, it is worth noting that pointer arithmetic is also valid for null pointers and for pointers that point to an object that is not an array.
If p is a null pointer, we can add or subtract an integral constant expression whose value is 0 to p.
In the latter case, the pointers must point to the same object, or one past that object.
Subscripts and Pointers
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]
3.6. Multidimensional Arrays
Strictly speaking, there are no multidimensional arrays in C++. What are commonly referred to as multidimensional arrays are actually arrays of arrays.
Using a Range for with Multidimensional Arrays
constexpr size_t rowCnt = 3, colCnt = 4;
int ia[rowCnt][colCnt];
size_t cnt = 0;
for (auto &row : ia) // for every element in the outer array
for (auto &col : row) { // for every element in the inner array
col = cnt; // give this element the next value
++cnt; // increment cnt
}
note
To use a multidimensional array in a range for, the loop control variable for all but the innermost array must be references.
Had we neglected the reference and written these loops as:
for (auto row : ia)
for (auto col : row)
our program would not compile.
As before, the first for iterates through ia
, whose elements are arrays of size 4. Because row is not a reference, when the compiler initializes row it will convert each array element (like any other object of array type) to a pointer to that array’s first element.
As a result, in this loop the type of row is int*. The inner for loop is illegal. Despite our intentions, that loop attempts to iterate over an int*.
Pointers and Multidimensional Arrays
int ia[3][4]; // array of size 3; each element is an array of ints of size 4
int (*p)[4] = ia; // p points to an array of four ints
p = &ia[2]; // p now points to the last element in ia
// print the value of each element in ia, with each inner array on its own line
// p points to an array of four ints
for (auto p = ia; p != ia + 3; ++p) {
// q points to the first element of an array of four ints; that is, q points to an int
for (auto q = *p; q != *p + 4; ++q)
cout << *q << ' ';
cout << endl;
}
// p points to the first array in ia
for (auto p = begin(ia); p != end(ia); ++p) {
// q points to the first element in an inner array
for (auto q = begin(*p); q != end(*p); ++q)
cout << *q << ' '; // prints the int value to which q points
cout << endl;
}