Static arrays are always allocated on the stack and have a fixed size at compile time.
They also have automatic storage duration, which means they are automatically destroyed when they go out of scope.
int arr[5] = {1,2,3,4,5}; // with assignment
int arr[] {1,2,3,4,5}; // implicit size and initializer
int x = arr[7]; // access element, compile-time warning: out of bounds
arr[2] = 42; // set element
std::array<int,5> arr = {1,2,3,4,5}; // with assignment
std::array<int,5> arr {1,2,3,4,5}; // with initializer
int x = arr[7]; // access element, compile-time error: out of bounds
arr[2] = 42; // set element
Dynamic arrays are allocated on the heap and have a variable size at runtime.
Because they are allocated on the heap, they have to be manually destroyed, except if they were created using make_unique or make_shared.
The size of the array is not stored anywhere, so it has to be stored separately (and passed to functions if necessary).
int* arr = new int[5]; // allocate memory, initialize with default
int* arr = new int[5] {1,2,3,4,5}; // with initializer (array size optional)
int x = arr[0];
arr[2] = 42;
sizeof(arr); // size of pointer, not array!
delete[] arr; // free memory
// Could also look like this:
int arr[5]; // allocate memory, initialized with default
int arr[5] = {1,2,3,4,5}; // with initializer (array size optional)
int x = arr[0];
arr[2] = 42;
sizeof(arr); // size of array (sizeof(int) * 5)!
// sizeof only works like this in the same scope as the array was declared
delete[] arr;
unique_ptr<int[]> arr = std::make_unique<int[]>(5); // allocate memory, initialize with default
unique_ptr<int[]> arr = std::make_unique<int[]>(5) {1,2,3,4,5}; // with initializer
int x = arr[0]; // access element, cannot be compile-time checked for out of bounds
arr[2] = 42; // set element
// free not required, done by dtor of unique_ptr
C++ introduced vectors, which are “enhanced” arrays. They can be resized dynamically and store their size and capacity.
The behavior of vectors is similar to the one of Java’s ArrayList.
std::vector<int> vec(5); // allocate memory, initialize with default constructor
std::vector<int> vec(5, 42); // with initializer
int x = vec[0]; // access element, cannot be compile-time checked for out of bounds
vec[2] = 42; // set element
vec.size(); // get number of elements
vec.capacity(); // get number of elements that can be stored without reallocation
vec.push_back(42); // add element to end
class PointArray {
size_t m_capacity;
size_t m_size;
std::unique_ptr<Point[]> m_data;
public:
PointArray(size_t capacity = 0)
: m_capacity(capacity)
, m_size(0)
, m_data(std::make_unique<Point[]>(capacity))
{
std::cout << "ctor, capacity = " << m_capacity << std::endl;
}
~PointArray()
{
std::cout << "dtor, capacity = " << m_capacity << std::endl;
}
void pushBack(const Point& p) {
if (m_size == m_capacity) {
// Array is full
// 1. Allocate new bigger array
m_capacity *= 2 + 1;
auto temp = std::make_unique<Point[]>(m_capacity);
// 2. Copy old data to new array
for (size_t i = 0; i < m_size; i++)
temp[i] = m_data[i];
// 3. Set m_data to new array
m_data = std::move(temp); // Required to move unique_ptr instead of copying it
}
m_data[m_size++] = p;
}
friend std::ostream& operator<<(std::ostream& os, const PointArray& pa) {
os << "[";
if (pa.m_size > 0)
os << pa.m_data[0];
for(size_t i = 1; i < pa.m_size; i++)
os << "," << pa.m_data[i];
return os << "]";
}
};
In C, strings are character arrays, terminated by the null character ('\0').
C also allows for initialization using string literals instead if array initializers.
char s[] = "Hello, World!"; // initialize with string literal
// char s[5] = "hello"; // compile-time error: string too long because of null character!
sizeof(s); // string length + 1 (for null character)
// sizeof only works like this in the same scope as the string was declared
char* t = "Hello, World!"; // t points to the first character
sizeof(t); // size of pointer, not string!
const char str[] = "hello"; // read-only array (c string), elements cannot change
char const str[] = "hello"; // equivalent
const char* str = "hello"; // equivalent
// str[1] = 'a'; // error: cannot be reassigned
#include <string>
auto s = "Hello, World!"s; // no a c string!
s.size(); // string length, or s.length()
s.c_str(); // get c string (const char*)
// other convenience methods available: https://en.cppreference.com/w/cpp/string/basic_string
// string_view as wrapper for c strings
std::string_view sv = "Hello, World!";
The default character type can store one byte. This is not enough for all characters, e.g. for emojis.
This is why there are different character types, which can store more bytes (UTF-8, UTF-16, UTF-32).
char c = 'a'; // 1 byte
wchar_t wc = L'ä'; // 1-4 bytes
char8_t c8 = u8'a'; // 1 byte
char16_t c16 = u'ൺ'; // 2 bytes
char32_t c32 = U'👍'; // 4 bytes
// Same goes for strings
char* cs = "Hello, World!"; // 1 byte per character
string s = "Hello, World!"; // UTF-8
wstring ws = L"Hello, World!"; // multiple bytes per character
// wchar_t* ws = L"Hello, World!"; // same as wstring
u8string s8 = u8"Hello, World!"; // UTF-8
// char8_t* s8 = u8"Hello, World!"; // same as u8string
u16string s16 = u"Hello, World!"; // UTF-16
// char16_t* s16 = u"Hello, World!"; // same as u16string
u32string s32 = U"Hello, World!"; // UTF-32
// char32_t* s32 = U"Hello, World!"; // same as u32string
Raw string literals are string literals that are not interpreted by the compiler.
They are useful for strings that contain many escape sequences.
auto t = "abc \"def\" \\ghi\\ \n" // quote and backslash are escaped
auto s = R"(abc "def" \ghi\)" // Raw string literal, no escaping required
// If the string contains ')"' an additional delimiter can be specified to avoid errors:
auto u = R"x("(First line.\nSecond line...)")x"; // delimiter is 'x'