0%

Book Reading: C++ Primer Plus, Chapter 4

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
2
char slifs[4] {'h', 'i', 1122011, '\0'}; // not allowed
char tlifs[4] {'h', 'i', 112, '\0'}; // 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
2
3
char str1[20] = "something";
char str2[20] = "anything";
str1 = str2; // not valid

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
2
3
4
cout << "I'd give my right arm to be" " a great violinist.\n";
cout << "I'd give my right arm to be a great violinist.\n";
cout << "I'd give my right ar"
"m to be a great violinist.\n";

For string class, you can declare it as a simple variable:

1
2
string str1; // empty string
string str2 = "panther";

You can also list-initialization for string objects.

For assignement, concatenation, and appending:

  1. Assignment
    • C-style: Array is not assignable, use strcopy or strncopy instead.
    • string: Can do assignment.
  2. Concatenation & Appending:
    • C-style: Use strcat or strncat.
    • string: Use +.

Note when you use strcpy:

1
2
char food[10] = "carrots";
strcpy(food, "a picnic basket filled with many goodies");

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
2
strncpy(food, "a picnic basket filled with many goodies", 9);
food[9] = '\0';

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
2
3
4
5
6
7
struct torgle_register
{
unsiged int SN : 4; // 4 bits for SN
unsiged int : 4; // 4 bits unused
bool gooIn : 1; // 1 bit
bool goodTorgle : 1; // 1 bit
}

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
2
3
4
5
6
7
8
9
union one4all {
int int_val;
long long_val;
double double_val;
}

one4all pail;
pail.inv_val = 15; // store an int
pail.double_val = 1.38; // sture a double, int value is lost

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
2
3
spectrum band;
band = blue; // valid
band = 2000; // invalid

Only assignment operator is defined for enums. In particular, arithmetic operations are not defined:

1
2
3
band = orange; // valid
++band; // invalid
band = orange + red; // invalid

Enums are of integer type and can be promoted to type int, but int types are not converted automatically to the enum type:

1
2
3
int color = blue; // valid, enum promoted to int
band = 3; // invalid, int not converted to enum
color = 3 + red; // valid, red 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
2
enum bit {one = 1, two = 2, four = 4, eight = 8};
bit myflag = bit(6); // valid, however it won't be equal to any defiend value

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
2
long *fellow;
*fellow = 223; // place a value in never-never land

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
2
char *str;
str = "test";

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
2
int *pt;
pt = (int *) 0xB8000000;

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
2
3
int *ps = new int;
// ...
delete ps;

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
2
int *psome = new int [10];
delete [] psome;

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
2
pointername = pointername + 1; // valid
arrayname = arrayname + 1; // not allowed

Taking the address of an array is another case in which the name of an array is not interpreted as its address:

1
2
3
short tell[10]; // array of 20 bytes
cout << tell << endl; // displays &tell[0]
cout << &tell << endl; // displays address of whole array

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
2
short (*p)[20]; // represents a pointer to a short array
short *p[20]; // represents an array of pointers to short