Strings, Vectors, and Arrays
Built-in Type: A type defined directly by the C++ language.
Library Type: A type provided by the C++ standard library.
string: A variable-length sequence of characters.
vector: A variable-length sequence of objects of the same type.
Array: A built-in fixed-size sequence of elements.
3.1 Namespace using Declarations
Namespace: A scope used to organize names.
std Namespace: Contains the names defined by the C++ standard library.
Scope Operator ::: Accesses a name inside a scope.
std::cout << "Hello\n";
Here, cout is accessed from the std namespace.
using Declaration
using Declaration: Makes one namespace member directly accessible without repeatedly writing the namespace name.
Syntax:
using namespace_name::name;
Example:
#include <iostream>
using std::cin;
using std::cout;
using std::endl;
int main()
{
int a = 0;
int b = 0;
cin >> a >> b;
cout << a + b << endl;
}
Each using declaration introduces one name.
using std::cin;
using std::cout;
using std::endl;
Headers and using
Headers should ordinarily not contain using declarations because every source file that includes the header would also receive those declarations.
Prefer qualified names in headers:
std::string name;
rather than introducing standard-library names globally.
Required Headers
Each library feature requires its corresponding header.
#include <iostream>
#include <string>
#include <vector>
3.2 Library string Type
std::string: A standard-library type representing a variable-length sequence of characters.
Required header:
#include <string>
1) Defining and Initializing strings
A string can be initialized in several ways.
#include <string>
std::string s1;
std::string s2 = "hello";
std::string s3(s2);
std::string s4(5, 'a');
The resulting values are:
s1 = ""
s2 = "hello"
s3 = "hello"
s4 = "aaaaa"
Copy Initialization
Copy Initialization: Uses = when initializing an object.
std::string s = "hello";
Direct Initialization
Direct Initialization: Initializes the object directly using parentheses.
std::string s("hello");
Multiple constructor arguments require direct initialization.
std::string s(5, 'x');
This creates:
xxxxx
2) Operations on strings
Important string operations include:
| Operation | Meaning |
|---|---|
os << s | Writes s |
is >> s | Reads one whitespace-separated word |
getline(is, s) | Reads an entire line |
s.empty() | Tests whether s is empty |
s.size() | Returns the number of characters |
s[n] | Accesses character n |
s1 + s2 | Concatenates strings |
s1 = s2 | Assigns one string to another |
s1 == s2 | Tests equality |
s1 != s2 | Tests inequality |
Reading a string
The input operator reads until whitespace.
#include <iostream>
#include <string>
int main()
{
std::string word;
std::cin >> word;
std::cout << word << '\n';
}
Input:
Hello World
Result:
Hello
Only the first word is read.
Reading Multiple Strings
A stream can be used as a loop condition.
std::string word;
while (std::cin >> word)
{
std::cout << word << '\n';
}
The loop continues while input succeeds.
getline
getline reads an entire line, including spaces.
std::string line;
while (std::getline(std::cin, line))
{
std::cout << line << '\n';
}
Input:
Hello C++ World
The complete line is stored in line.
empty() and size()
std::string s = "hello";
if (!s.empty())
{
std::cout << s.size() << '\n';
}
Output:
5
string::size_type
size() returns string::size_type, an unsigned type suitable for representing string sizes.
std::string s = "hello";
std::string::size_type length = s.size();
auto is commonly used instead.
auto length = s.size();
Avoid unnecessary signed/unsigned mixing when working with size().
Comparing strings
Strings can be compared directly.
std::string s1 = "hello";
std::string s2 = "world";
if (s1 != s2)
{
std::cout << "different\n";
}
Relational operators compare strings in lexicographical order.
std::string a = "apple";
std::string b = "banana";
if (a < b)
{
std::cout << "apple comes first\n";
}
String Assignment
std::string s1 = "hello";
std::string s2 = "world";
s1 = s2;
After the assignment:
s1 = "world"
String Concatenation
Strings can be joined using +.
std::string first = "Hello";
std::string second = "World";
std::string result = first + " " + second;
Result:
Hello World
A string can also be extended with +=.
std::string s = "Hello";
s += " World";
Literals and string
At least one operand of each + operation must be a string.
Valid:
std::string s = "Hello";
std::string result = s + " World";
Invalid:
// "Hello " + "World"
Both operands above are string literals, not std::string objects.
3) Dealing with the Characters in a string
Individual characters can be processed using iteration or subscripting.
Range-Based for
Range-Based for: Iterates through every element of a sequence.
std::string s = "Hello";
for (auto c : s)
{
std::cout << c << '\n';
}
The loop variable c receives a copy of each character.
Counting Characters
The <cctype> header provides character classification functions.
#include <cctype>
#include <string>
std::string s = "Hello, World!";
decltype(s.size()) punct_count = 0;
for (auto c : s)
{
if (std::ispunct(c))
{
++punct_count;
}
}
Important functions include:
| Function | Meaning |
|---|---|
isalnum(c) | Letter or digit |
isalpha(c) | Letter |
isdigit(c) | Digit |
islower(c) | Lowercase letter |
isupper(c) | Uppercase letter |
isspace(c) | Whitespace |
ispunct(c) | Punctuation |
tolower(c) | Converts to lowercase |
toupper(c) | Converts to uppercase |
Modifying Characters
A reference loop variable allows modification of the original string.
std::string s = "hello";
for (auto& c : s)
{
c = std::toupper(c);
}
After the loop:
HELLO
Without &, only a copy of each character would be modified.
String Subscript
The [] operator accesses a character at a particular position.
std::string s = "hello";
char first = s[0];
first contains:
h
String indexing begins at zero.
A valid index satisfies:
0 <= index < s.size()
Modifying with a Subscript
std::string s = "hello";
s[0] = 'H';
Result:
Hello
Checking Before Subscripting
std::string s = "hello";
if (!s.empty())
{
s[0] = 'H';
}
Subscripting outside the valid range results in undefined behavior.
3.3 Library vector Type
vector: A variable-length collection of objects of the same type.
Container: An object that contains other objects.
Class Template: A template from which specific class types are generated.
Required header:
#include <vector>
A vector type includes its element type.
std::vector<int> numbers;
std::vector<std::string> words;
vector itself is a template, not a complete type.
std::vector<int>
is a specific type generated from the template.
Vectors can contain other vectors.
std::vector<std::vector<int>> matrix;
A vector cannot contain references because references are not objects.
1) Defining and Initializing vectors
Empty Vector
std::vector<int> values;
values initially contains no elements.
Copying a Vector
std::vector<int> v1{1, 2, 3};
std::vector<int> v2(v1);
std::vector<int> v3 = v1;
Both v2 and v3 contain:
1 2 3
List Initialization
std::vector<int> values{1, 2, 3, 4, 5};
Each value becomes an element.
Specified Number of Elements
std::vector<int> values(10);
This creates ten int elements value-initialized to zero.
Count and Value
std::vector<int> values(10, 5);
This creates ten elements, each with value 5.
Parentheses vs. Braces
These definitions have different meanings.
std::vector<int> v1(10);
std::vector<int> v2{10};
v1 contains ten zeros.
v2 contains one element:
10
Likewise:
std::vector<int> v1(10, 1);
std::vector<int> v2{10, 1};
v1:
1 1 1 1 1 1 1 1 1 1
v2:
10 1
2) Adding Elements to a vector
push_back(): Adds an element to the end of a vector.
std::vector<int> values;
values.push_back(10);
values.push_back(20);
values.push_back(30);
Result:
10 20 30
Building a Vector at Run Time
When values are not known beforehand, an empty vector can be filled dynamically.
std::vector<int> values;
for (int i = 0; i != 10; ++i)
{
values.push_back(i);
}
The vector becomes:
0 1 2 3 4 5 6 7 8 9
Reading Values into a Vector
std::vector<int> values;
int value = 0;
while (std::cin >> value)
{
values.push_back(value);
}
Do Not Change Size During Range for
Do not add elements to a vector while a range-based for is iterating over that same vector.
Avoid:
for (auto value : values)
{
// values.push_back(value);
}
Changing the vector size can invalidate the loop's internal iterators.
3) Other vector Operations
Important operations include:
| Operation | Meaning |
|---|---|
v.empty() | Tests whether the vector is empty |
v.size() | Number of elements |
v.push_back(t) | Adds t |
v[n] | Accesses element n |
v1 = v2 | Copies elements |
v1 == v2 | Tests equality |
v1 != v2 | Tests inequality |
Range-Based for
std::vector<int> values{1, 2, 3};
for (auto value : values)
{
std::cout << value << '\n';
}
Modifying Elements
Use a reference when modifying the original elements.
std::vector<int> values{1, 2, 3};
for (auto& value : values)
{
value *= 2;
}
Result:
2 4 6
vector::size_type
A vector provides a type suitable for representing its size.
std::vector<int> values{1, 2, 3};
std::vector<int>::size_type size = values.size();
Usually:
auto size = values.size();
is simpler.
Computing a Vector Index
A vector subscript accesses an existing element.
std::vector<int> values{10, 20, 30};
std::cout << values[1];
Output:
20
The valid range is:
0 <= index < values.size()
Grade Counter Example
A computed index can select an element.
std::vector<unsigned> scores(11, 0);
unsigned grade = 0;
while (std::cin >> grade)
{
if (grade <= 100)
{
++scores[grade / 10];
}
}
The eleven vector elements count grades in these groups:
0-9
10-19
20-29
...
90-99
100
For example:
grade = 65
65 / 10 = 6
Therefore:
++scores[6];
increments the appropriate counter.
Subscripting Does Not Add Elements
This is invalid:
std::vector<int> values;
values[0] = 10;
The vector is empty, so element 0 does not exist.
Use push_back():
std::vector<int> values;
values.push_back(10);
3.4 Introducing Iterators
Iterator: An object used to access elements of a container or characters of a string indirectly.
An iterator conceptually behaves somewhat like a pointer.
1) Using Iterators
begin()
Returns an iterator to the first element.
auto begin = values.begin();
end()
Returns an iterator one position past the last element.
auto end = values.end();
end() does not refer to an actual element and must not be dereferenced.
Iterator Loop
std::vector<int> values{10, 20, 30};
for (auto it = values.begin(); it != values.end(); ++it)
{
std::cout << *it << '\n';
}
*it accesses the element referred to by the iterator.
Empty Container
For an empty container:
values.begin() == values.end()
Iterator Operations
| Operation | Meaning |
|---|---|
*iter | Access element |
iter->member | Access member of element |
++iter | Move to next element |
--iter | Move to previous element |
iter1 == iter2 | Compare positions |
iter1 != iter2 | Compare positions |
Modifying Through an Iterator
std::vector<int> values{1, 2, 3};
for (auto it = values.begin(); it != values.end(); ++it)
{
*it *= 2;
}
Result:
2 4 6
Iterator Types
A non-const container provides a modifiable iterator.
std::vector<int>::iterator it;
A const_iterator can read but not modify elements.
std::vector<int>::const_iterator it;
cbegin() and cend()
These functions always return const iterators.
for (auto it = values.cbegin();
it != values.cend();
++it)
{
std::cout << *it << '\n';
}
Arrow Operator ->
For a container of objects, -> accesses a member of the current element.
std::vector<std::string> words{"hello", "world"};
for (auto it = words.begin(); it != words.end(); ++it)
{
std::cout << it->size() << '\n';
}
This:
it->size()
is equivalent to:
(*it).size()
Iterator Invalidation
Operations that change a vector's size may invalidate existing iterators.
std::vector<int> values{1, 2, 3};
auto it = values.begin();
values.push_back(4);
After push_back(), it may no longer be valid.
Do not continue using an iterator after an operation that may invalidate it.
2) Iterator Arithmetic
vector and string iterators support arithmetic operations.
std::vector<int> values{10, 20, 30, 40};
auto it = values.begin();
auto third = it + 2;
third refers to:
30
Iterator Difference
auto first = values.begin();
auto last = values.end();
auto distance = last - first;
The result is the number of elements between the two iterators.
Midpoint
auto mid =
values.begin() +
values.size() / 2;
Iterator Arithmetic Operations
| Operation | Meaning |
|---|---|
iter + n | Move forward n elements |
iter - n | Move backward n elements |
iter += n | Advance iterator |
iter -= n | Move iterator backward |
iter1 - iter2 | Distance between iterators |
<, <=, >, >= | Compare positions |
Iterator arithmetic must involve positions belonging to the same sequence.
3.5 Arrays
Array: A fixed-size sequence of objects of the same type.
Unlike a vector, the number of array elements cannot change after the array is defined.
1) Defining and Initializing Built-in Arrays
Array syntax:
type name[size];
Example:
int values[10];
This defines an array containing ten int elements.
The dimension must be a constant expression.
constexpr unsigned size = 10;
int values[size];
List Initialization
int values[5] = {1, 2, 3, 4, 5};
The dimension can be omitted when an initializer list is present.
int values[] = {1, 2, 3, 4, 5};
The compiler determines the size.
Partial Initialization
int values[5] = {1, 2};
The remaining elements are value initialized.
Conceptually:
1 2 0 0 0
Arrays Cannot Contain References
Invalid:
// int& refs[10];
Character Arrays
A character array can be initialized from a string literal.
char text[] = "hello";
The array contains:
h e l l o \0
The terminating null character is part of the array.
Therefore:
char text[6] = "hello";
is valid.
No Array Copy or Assignment
Built-in arrays cannot be copied directly.
int a[] = {1, 2, 3};
int b[3];
// Invalid
// b = a;
Likewise, one array cannot be initialized by directly copying another built-in array.
Complicated Array Declarations
Array of Pointers
int* pointers[10];
pointers is an array containing ten pointers to int.
Pointer to Array
int values[10];
int (*p)[10] = &values;
p is a pointer to an array of ten ints.
Reference to Array
int values[10];
int (&ref)[10] = values;
ref is a reference to the entire array.
2) Accessing Array Elements
Array subscripts begin at zero.
int values[] = {10, 20, 30};
std::cout << values[0];
Output:
10
Range-Based for
int values[] = {10, 20, 30};
for (auto value : values)
{
std::cout << value << '\n';
}
Use a reference to modify the original elements.
for (auto& value : values)
{
value *= 2;
}
Bounds
For an array of size N, valid indices are:
0 ... N - 1
Access outside the valid range is undefined behavior.
int values[3] = {1, 2, 3};
// Invalid access
// values[3]
3) Pointers and Arrays
In most expressions, an array is converted to a pointer to its first element.
int values[] = {10, 20, 30};
int* p = values;
This is equivalent to:
int* p = &values[0];
auto and Arrays
int values[10];
auto p = values;
p is deduced as:
int*
decltype and Arrays
decltype preserves the array type.
int values[10];
decltype(values) copy = {};
copy is another array of ten ints.
Pointers Are Iterators
Pointers can move through an array.
int values[] = {10, 20, 30};
int* p = values;
++p;
p now points to:
values[1]
Library begin() and end()
The standard library provides functions for obtaining array boundaries.
#include <iterator>
int values[] = {10, 20, 30};
int* first = std::begin(values);
int* last = std::end(values);
first points to the first element.
last points one position past the final element.
Array traversal:
for (auto p = std::begin(values);
p != std::end(values);
++p)
{
std::cout << *p << '\n';
}
Pointer Arithmetic
Adding to a pointer moves it by elements, not bytes.
int values[] = {10, 20, 30, 40};
int* p = values;
p += 2;
p now points to:
values[2]
Pointer Difference
auto first = std::begin(values);
auto last = std::end(values);
auto count = last - first;
For a four-element array:
count = 4
Pointer Dereference and Arithmetic
int values[] = {10, 20, 30};
int* p = values;
std::cout << *(p + 1);
Output:
20
Therefore:
values[1]
and
*(values + 1)
access the same element.
4) C-Style Character Strings
C-Style String: A null-terminated character array.
char text[] = "hello";
The terminating '\0' marks the end of the string.
<cstring>
The <cstring> header provides C-style string functions.
#include <cstring>
Important functions include:
| Function | Meaning |
|---|---|
strlen(p) | Length excluding '\0' |
strcmp(p1, p2) | Compare strings |
strcpy(p1, p2) | Copy string |
strcat(p1, p2) | Append string |
strlen
char text[] = "hello";
std::cout << std::strlen(text);
Output:
5
Comparing C-Style Strings
Do not compare their contents with ordinary pointer comparison.
Use:
const char a[] = "hello";
const char b[] = "hello";
if (std::strcmp(a, b) == 0)
{
std::cout << "equal\n";
}
Destination Size
Functions such as strcpy and strcat require sufficient destination storage.
char destination[20] = "Hello ";
const char source[] = "World";
std::strcat(destination, source);
The destination must have enough space for all characters and the final null terminator.
Prefer std::string
For normal C++ text handling, prefer:
std::string text = "hello";
over manually managing C-style character arrays.
5) Interfacing to Older Code
Modern C++ sometimes needs to interact with APIs that use arrays or C-style strings.
C-Style String to std::string
const char text[] = "hello";
std::string s = text;
string::c_str()
c_str() provides a pointer to a null-terminated representation of a string.
std::string s = "hello";
const char* p = s.c_str();
The returned pointer should not be assumed to remain valid after operations that modify s.
Array to vector
An array range can initialize a vector.
int values[] = {0, 1, 2, 3, 4, 5};
std::vector<int> v(
std::begin(values),
std::end(values)
);
The vector contains:
0 1 2 3 4 5
A partial range can also be copied.
std::vector<int> v(values + 1, values + 4);
Result:
1 2 3
Library Type Preference
For general C++ programming, prefer:
std::vector over built-in arrays
iterators over raw pointer traversal
std::string over C-style strings
unless low-level array or pointer behavior is specifically required.
3.6 Multidimensional Arrays
Multidimensional Array: An array whose elements are themselves arrays.
Strictly speaking, C++ multidimensional arrays are arrays of arrays.
Example:
int matrix[3][4];
This means:
3 rows
4 integers per row
12 integers total
Initializing Multidimensional Arrays
Nested braces make the row structure explicit.
int matrix[3][4] =
{
{0, 1, 2, 3},
{4, 5, 6, 7},
{8, 9, 10, 11}
};
Partial Initialization
int matrix[3][4] =
{
{0},
{4},
{8}
};
The remaining elements are value initialized.
The rows become conceptually:
0 0 0 0
4 0 0 0
8 0 0 0
Subscripting a Multidimensional Array
One subscript is used for each dimension.
int value = matrix[1][2];
This accesses row 1, column 2.
Partial Subscript
matrix[1]
refers to the entire second row.
A reference can bind to that row.
int (&row)[4] = matrix[1];
Iterating with Nested for Loops
constexpr std::size_t row_count = 3;
constexpr std::size_t column_count = 4;
int matrix[row_count][column_count];
for (std::size_t row = 0;
row != row_count;
++row)
{
for (std::size_t column = 0;
column != column_count;
++column)
{
matrix[row][column] =
row * column_count + column;
}
}
The resulting values are:
0 1 2 3
4 5 6 7
8 9 10 11
Range-Based for
When iterating over multidimensional arrays, the outer loop variable must be a reference so that the inner array does not decay into a pointer.
for (auto& row : matrix)
{
for (auto value : row)
{
std::cout << value << ' ';
}
std::cout << '\n';
}
To modify the elements:
for (auto& row : matrix)
{
for (auto& value : row)
{
value = 0;
}
}
Pointers and Multidimensional Arrays
For:
int matrix[3][4];
matrix is an array whose elements are arrays of four ints.
In most expressions it converts to:
int (*)[4]
A pointer can therefore be declared as:
int (*p)[4] = matrix;
p points to one row.
Incrementing p moves to the next row.
++p;
Now p points to:
matrix[1]
Array of Pointers vs. Pointer to Array
These declarations are different:
int* p1[4];
int (*p2)[4];
p1:
array of four pointers to int
p2:
pointer to an array of four int
The parentheses are essential.
Iterating with Pointers
for (auto p = std::begin(matrix);
p != std::end(matrix);
++p)
{
for (auto q = std::begin(*p);
q != std::end(*p);
++q)
{
std::cout << *q << ' ';
}
std::cout << '\n';
}
p moves through rows.
q moves through the elements of each row.
Type Aliases
A type alias can simplify array declarations.
using int_array = int[4];
int matrix[3][4];
int_array* p = matrix;
This:
int_array* p;
is equivalent to:
int (*p)[4];
The alias makes the relationship between the pointer and each row easier to read.
Essential Study Checklist
std::stringrepresents variable-length character sequences.std::vector<T>represents variable-length collections ofT.usingdeclarations introduce individual namespace members.- Headers should generally avoid
usingdeclarations. string >>reads one whitespace-separated word.getline()reads an entire line.string::size()returns an unsigned size type.- Range-based
foris useful for processing every element. - Use
auto&when a range-based loop must modify the original element. vector::push_back()adds elements.vector[n]accesses only an element that already exists.- Vector subscripting does not create elements.
begin()refers to the first element.end()refers to one position past the last element.- An iterator must not dereference
end(). - Vector size changes may invalidate iterators.
- Iterator arithmetic is supported by
vectorandstringiterators. - Built-in arrays have a fixed size.
- Array indices begin at zero.
- Arrays normally convert to pointers to their first elements.
std::begin()andstd::end()can safely obtain array bounds.- Pointer arithmetic moves by elements.
- C-style strings end with
'\0'. - Prefer
std::stringover C-style strings for normal text handling. - An array range can initialize a
vector. - A multidimensional array is actually an array of arrays.
int (*p)[4]is a pointer to an array of fourints.int* p[4]is an array of four pointers.- Outer range-for variables over multidimensional arrays should be references.
- Type aliases can simplify complicated array types.