Arrays
Declaring an array:
typeName arrayName[arraySize]
arraySize must be a integer constant, const value or const expression, for which all values are known at compilation time. In particular, arraySize
cannot be a variable whose value is set at run time.
If need to dynamically allocate an array at run time, use new
or malloc
.
To initialize an array:
1 | int cards[4] = {1, 2, 3, 4}; |
When initializing, you can provide fewer value than array elements:
1 | int cards[4] = {1, 2}; |
If you partially initialze an array, complier set the remaining elements to zero.
1 | long totals[500] = {0}; |
This sets all elements to zero.
If you leave the square bracket empty, the complier counts the element for you:
1 | short things[] = {1, 2, 3, 4} |
The compiler makes it an array of 4 elements.
In C++11, you can drop the = sign:
1 | short things[4] {1, 2, 3, 4}; |
Second, you can use empty braces to set all elements to 0:
1 | unsigned int counts[10] = {}; |
Third, list-initilizatoin protect against narrowing (See chapter 3):
1 | char slifs[4] {'h', 'i', 1122011, '\0'}; // not allowed |
If you don’t initialize array that is defined in the function, the values remain undefined.
Also you can’t assign an array to another:
1 | char str1[20] = "something"; |
But interestingly, if the array is within a struct
and you do an assignment for it, the array inside will be copied (Array assignment could be a design flaw).
Strings
There are two way of dealing with strings, C-style string and string
class library.
C-style string ends with null character, so when determining the minimum array size necessary to hold a string, remember to include the terminating null character in your count.
Any two string literals separated only by whitespace (spaces, tabs, and newlines) are automatically joined into one. Below are equivalent to each other:
1 | cout << "I'd give my right arm to be" " a great violinist.\n"; |
For string
class, you can declare it as a simple variable:
1 | string str1; // empty string |
You can also list-initialization for string
objects.
For assignement, concatenation, and appending:
- Assignment
- C-style: Array is not assignable, use
strcopy
orstrncopy
instead. string
: Can do assignment.
- C-style: Array is not assignable, use
- Concatenation & Appending:
- C-style: Use
strcat
orstrncat
. string
: Use+
.
- C-style: Use
Note when you use strcpy
:
1 | char food[10] = "carrots"; |
In this case, the funciton copies the rest of the string into the memory bytes immediately following the array, which can overwrite other memory the program is using. To avoid the problem, you should use strncpy
instead, which takes third argument: the maximum number of characters to be copied. Be aware, when the function run out of space before it reaches the end of the string, it doesn’t add the null character. So you need to do:
1 | strncpy(food, "a picnic basket filled with many goodies", 9); |
One C++11 addiiton is raw string. In a raw string, characters simply stand for themselves (no escape). If you allow a "
inside a string literal, you no longer can use it to delimit the ends of a string. Therefore, raw string use "(
and )"
as delimiters, and they use an R
prefix to identify them as raw string:
1 | cout << R"(Jim "King" Tutt uses "\n" instead of endl.)" << '\n'; |
This would display the following:
1 | Jim "King" Tutt uses "\n" instead of endl. |
If we want to display ()
in raw string, it allows you place additional characters between the opening "
and (
. This implies the same additional characters for final )
and "
. Thus:
1 | cout << R"+*("(Who would't?)", she whispered.)*+" |
would display following:
1 | "(Who wouldn't?)", she whispered. |
You can use any member of the basic character set as part of the delimiter other than the space, the left / right parenthesis, backslash and control chars sunch as tab or newline.
Structures
C++, like C, enables you to specify strcture members that occupy a particular number of bits. This can be handy for creating a data strcture that corresponds, say, to a register on some hardware devices. You can use unnamed fields to provides spacing. Each member is termed a bit field
. Example:
1 | struct torgle_register |
You can inittialize the fields in the usual manner, and you use stardard structure notation to access bit fields:
1 | torgle_register tr = {14, true, false}; |
Unions
A union
is a data format that can hold different data types but only one type at a time. Example:
1 | union one4all { |
One use for a union is to save space when a data item can use two or more formats be never simultaneously.
Enumerations
The C++ enum
provides an alternative to const
for creating symbolic constants.
1 | enum spectrum {red, orange, yellow, green, blue, violet, indigo, ultraviolet};} |
By default, enums are assigned integer values starting with 0 for the first enum, 1 for the second, and so forth.
You can use an enum name to declare a variable of enum type. The only valid values that you can assign to a enum variable without a type cast are the enum values used in defining the type.
1 | spectrum band; |
Only assignment operator is defined for enums. In particular, arithmetic operations are not defined:
1 | band = orange; // valid |
Enums are of integer type and can be promoted to type int
, but int
types are not converted automatically to the enum type:
1 | int color = blue; // valid, enum promoted to int |
Note that previous example band = orange + red
, fails for somewhat involved reason. It is true that + operator is not defined for enums. But it is also true that enums are converted to integers when used in arithmetic expression, so the expresiosn itself is valid, but the result is a type of int
and hence cannot be assigend to the type spectrum
without casting, which also means you can assign int
to an enum
by explicit type cast:
1 | band = specturm(3); |
What if you try to assign to type cast an inappropriate value? The result is undefined.
You can set enum values explicitly by using the assignment operator:
1 | enum bits {one = 1, two = 2, four = 4, eight = 8}; |
The assigned values must be integers. You can also just define some of the enums explicitly:
1 | enum bigstep{first, second = 100, third}; |
In this case, first
is 0 by default. Subsequent uninitialized enums are lager by one than their predecessors, the third
would have the value 101.
Finally, you can create more than on enum with the same value:
1 | enum {zero, null = 0, one, numero_uno = 1}; |
Here, both zero
and null
are 0, and both one
and numero_uno
are 1.
What if you cast int
to enum
with duplicate values? Actually it shouldn’t matter since zero
and null
intrinsically have the same value, and after cast the result is equal to both. But you won’t be able to use it in switch
, since case
requires all constant expression to have distinct value.
Each enum has a range, and you can assign any integer value in the range, even if it is not an enum value, by using a type cast to an enum variable.
1 | enum bit {one = 1, two = 2, four = 4, eight = 8}; |
The range is defined as follow:
- Find the smallest power of two freater the largest value and subtract one, for example, if the largest enum value is 101, then the upper limit is 128 - 1 = 127.
- If the smallest enum value is 0 or greater then 0, then the lower limit is 0. Otherwise, using same approach but toss in a minus sign. For example, if the smallest value is -6, the lower limit is -8 + 1 = -7.
The idea is that the compiler can choose how much space to use to hold an enum. It might use 1 bytes or less for a enum with a small range, and 4 bytes for an eum with long
values.
Pointer
The following declaration creates one pointer (p1) and one ordinary int
(p2):
1 | int *p1, p2; |
When you create a pointer in C++, it allocates memory to hold the address, but it does not allocate memory to hold the data which the address points. Creating space for the data involves a separate step:
1 | long *fellow; |
Given the fellow is not initialized, its valud is undefiend and can have any value. It results in placing value in a place you won’t know where it is, which will potentially crash app and cause hard to trace bugs. But following code works:
1 | char *str; |
The constant "test"
actually represents the address of the string (address of its first element), so the statement assigns the address of "test"
to the str
pointer. Typically, a compiler sets aside an area in memory to hold all the quoted string used in the program source code, associating each stored string with its address.
Pointers are underlying integer but not integer types, which means you cannot directly assign an int
to pointer, you will need to explicit cast:
1 | int *pt; |
To allocate memory, you can use new
(C style malloc
also works) to allocate a block of memory on heap
. The other half is the delete
operator, which enables you to return memory to the memory pool when you are finished with it.
1 | int *ps = new int; |
This removes the memory to which ps
points; it doesn’t remove the pointer ps
itself, you can reuse ps
.
You should not attempt to free a block of memory that you have previously freed. The C++ Standard says the result of such attempt is undefined.
To create and free dynamic array (dynamic binding instead of static binding), you can do following:
1 | int *psome = new int [10]; |
The program does keep track of the amount of memory allocated so that it can be correctly freed at a later time. But that information isn’t publicly available; you can’t use the sizeof
operator, for example, to find the number of bytes in a dynamically allocated array.
It is safe to apply delete
to the null pointer (nothing happens).
Array name is essentially the address of the first element. Thus, in many respects you can use pointer names and array names in the same way. One difference is that you can change the value of a pointer, whereas an array name is a constant:
1 | pointername = pointername + 1; // valid |
Taking the address of an array is another case in which the name of an array is not interpreted as its address:
1 | short tell[10]; // array of 20 bytes |
Numerically, these two addresses are the same, but conceptually &tell[0]
, and hence tell
, is the address of a 2-byte block of memory, whereas &tell
is the address of a 20 byte block of memory. Another way of expressing this is to say that tell
is type pointer-to-short
, or short *
, and &tell
is type pointer-to-array-of-20-shorts
or short (*)[20]
.
Note []
has a higher priority than *
, so types below are different:
1 | short (*p)[20]; // represents a pointer to a short array |