An array is a series of elements of the same type placed in contiguous memory locations that can be individually referenced by adding an index to a unique identifier.
That means that, for example, five values of type int
can be declared as an array without having to delacre 5 different variables (each with its own identifier). Instead, using an arraym the five int values are stored in contiguous memory locations, and all five can be accessed using the same identifier with the proper index.
For example, an array containing 5 integer values of type int
called foo
could be represented as :
When each blank panel represents an element of the array. In this case, these are values of type int.
Like a regular variable, an array must be declared before it is used. A typical declaration for an array in C++ is :
type name [elements];
where type is a valid type (such as int
, float
, …), name
is a valid identifier and the elements
filed (which is always enclosed in square brackets []), specifies the length of the arry in terms of the number of elements.
Therefore, the foo array, with five elements of type int, can be declared as :
int foo [5];
At some point, we need to pass an array to a function as a parameter.
In C++, it is not possible to pass the entire block of memory represented by an array to a function directly as an argument. But what can be passed instead is its address.
In practice, this has almost the same effect, and it is a much faster and more efficient operation.
To accept an array as parameter for a function, the parameters can be declared as the array type, but with empty brackets, omitting the actual size of the array.
void procedure (int arg[])
void procedure (int myarray[][3][4])
In a way, passing an array as argument always loses a dimension.
The reason behind is that, for historical reasons, arrays cannot be directly copied, and thus what is really passed is a pointer. This is a common souce of errors for novice programmers.
The arrays explained above are directly implemented as a language feature, inherited from the C language. They are a great feature, but by restricting its copy and easily decay into pointers, they probably suffer from an excess of optimiztion.
To overcome some of these issues with language ***built-in arrays***, C++ provides an alternative array type as a standard ***container***. It is a type template (a class template, in fact) defined in header .
Suffice it to say that they operate in a similar way to built-in arrays, except that they allow being copied (an actually expensive that copies the entire block of memory, and thus to use with care) and decay into pointers only when explicitly told to do so (by means of its member data
).
By default, regular arrays of local scope (for example, those declared within a function) are left uninitialized. This means that none of its elements are set to any particular value; their contents are undermined at the point the array is declared.
But the elements in an array can be explicitly initialized to specific values when it is declared, by enclosing those initial values in braces {}. For example:
int foo[5] = {16, 2, 77, 40, 12071};
This statement declares an array that can be represented like this:
The number of values between braces {} shall not be greater than the number of elements in the array. If declared with less, the remaining elements are set to their default values (which for fundamental types, means they are filled with zeroes).
When an initialization of values is provided for an array, C++ allows the possibility of leaving the square brackets empty []. In this case, the compiler will assume automatically a size foe the array that matchs the number of values included between the braces {}.
int foo[] = {16, 2, 3};
Finally, the evolution of C++ has led to the adoption of universal initialization also for arrays. Therefore, there is no longer need for the equal sign between the declaration and the initializer.
int foo[] = {10, 20, 30};
int foo[] {10, 20, 30}
Static arrays, and those ***declared directly in a namespace (outsize any function)***, are always initialized. If no explicit initializer is specified, all the elements are default-initialized ( with zeroes, for fundamental types).
The values of any of the elements in an array can be accessed just like the value of a regular variable of the same type. The syntax is :
name [index]
foo[2] = 75;
x = foo[2];
Accessing out-of-range elements do not cause errors on compilation, but can cause errors on runtime.
Bracket [] has two uses performing two different tasks:
int foo[5];
foo[2] = 75;
Multidimensional arrays can be described as “arrays of arrays”. For example, a bidimensional array can be imagined as a two-dimensional table made of elements, all of them of a same uniform data type.
int jimmy[3][5]; // a bidimensinal array of 3 per 5 elements of type int
the way to reference the second element vertically and fourth horizontally in an expression would be:
jimmy[1][3]
Multidimensinal arrays are just an abstraction for programmers, since the same results can be achieved with a simple array, by multiplying its indices:
int jimmy [3][5]; // is equivalent to
int jimmy [15]; // (3 * 5 = 15)
strings are, in fact, sequences of characters, we can represent them also as plain arrays of elements of a character type.
By convention, the end of strings represented in character sequences is signaled by a special character: the null character
, whose literal value can be written as '\0'
(backslash, zero).
Sequences of characters enclosed in double-quotes (") are literal constants. And their type is , in fact, a null-terminated array of characters.
Once string literal has been declared(initialized), they cannot be assigned values,
myword = "bye";
// equal to
myword[] = {'b', 'y', 'e'};
// would not be valid
myword = {'B','y','e', '\0'};
// each of its elements can be assigned a value individually
myword[0] = 'B';
Plain arrays with null-terminated sequences of characters are the typical types used in the C language to represent strings, which is known as C-stings
, difference from from the standard library defined specific type for strings (class string
).
The string literals still always produce null-terminated character sequences, not string
objects.
cin
and cout
support null-terminated sequences directly.
strings
have a dynamic size determind during runtime
, while the size of arrays
is determined on the compilation
, before the program runs.
Variables
can be explained as locations in the computer’s memory
which can be accessed by their identifier
(their name
).
When a variable is declared, the memory needed to store its value is assigned a specific location in memory (its memory address). An OS decides the particular memory locations on runtime.
The address of a variable can be obtained by preceding the name of a variable with an ampersand sign (&)
, known as address-of operator.
foo = &myvar;
This would assign the address of variables myvar
to foo.
The actual address of a variable in memory cannot be known before runtime.
The variable that stores the address of another variable (like
foo
in the previous example) is what in C++ is called apointer
.
Pointers are a very powerful feature of the language that has many uses in lower level programming.
Pointers
are said to "point to " the variable whose address they store.
An interesting property of pointers is that they can be used to access the variable
they point to directly. This is done by preceding the pointer name with the dereference operator (*)
. The operater itself can be read as “value pointed to by
”.
baz = foo; // baz equal to foo (1776)
baz = *foo; // baz equal to value pointed to by foo (25)
The reference and dereference operators are thus complementary:
address of
”*
is the dereference operator, and can be read as “value pointed to by
”Thus, they have sort of opposite meanings : An address obtained with &
can be dereferenced with *
.
Due to the ability of a pointer to directly refer to the value that it points to, a pointer has different properties when it points to a char
than when it points to an int
or a float
. Once dereferenced , the type needs to be known. And for that, the declaration of a pointer needs to include the data type the pointer is going to point to.
The declaration of pointers follows this syntax:
type * name;
where type
is the data type pointed to by the pointer. This type is not the type of the pointer itself, but the data the pointer points to.
int *number;
The asterisk(*
) used when declaring a pointer only means that it is a pointer (it is part of its type compound specifier
), and should not be confused with the dereference operator
seen a bit earlier.
int * p1, *p2; // two pointers
int * pi, p2; // p1 is pointer, p2 is int type, spaces do not matters
int myarray[20];
int * mypointer;
mypointer = myarray;
myarray = mypointer; // not valid
mypointer
and myarray
would be equivalent and would have very similar properties. The main difference being that mypointer
can be asssigned a different address, whereas myarray
can never be assigned anything, and will always represent the same block of 20 elements of type int
.
a[5] = 0;
*(a+5)=0;
Brackets([]) were explained as specifying the index of an index of an element of the array.
In fact these brackets are adereferenceing operator known as offset operator. They dereference the variable they follow just as *
does, but they also add the number between brackets to the address being dereferenced.
int myvar;
int * myptr = &myvar; // pointers can be initialized either to the address of a variable
int * bar = myptr; // or the value of another pointer(or array)
Pointers can be initialized to point to specific locations at the veru moment they are defined.
Only addition
and subtraction
operations are allowed, the other make no sense in the world of pointers
. But both addition and subtraction have a slightly different behavior with pointers, according to the size of the data type
to which they point.
When fundamental data types were introduce, we saw that types have different sizes.
When adding one to a pointer is made to point to the following element of the same type, and, therefore, the size in bytes of the type it points to is added to the pointers.
char * mychar; // 1byte
short * myshort; // 2bytes
++mychar ; // add 1
++myshort; // add 2
mychar = mychar + 1; // add 1
myshort = myshort + 1; // add 2
This is applicable both when adding and subtracting any number to a pointer.
Regarding the increment (++
) and decrement (--
) operators, they both can be used as either prefix or suffix of an expression, with a slight difference in behavior: as a prefix, the increment happens before the expression is evaluated, and as a suffix, the increment happens after the expression is evaluated.
The postfix operators such as increment and decrement have higher precedence than prefix operators, such as the dereference operator (*) .
*p++;
// equal to
*(p++);
What it does is to increase the value of p (so it now points to the next element), but because ++ is used as postfix, the whole expression is evaluated as the value pointed orginally by the the pointer (the address it pointed to before being incremented).
Pointers can be used to access a variable by its address, and this access may include modifying the value pointed. But it is also possible to declare pointer that can access the pointed value to read it, but not to modify it. But not to modify it. For this, it is enough with qualifying the type pointed to by the pointer as const
.
int x;
int y = 10;
const int* p = &y;
x = *p;
*p = x; // error: modifying p, which is const-qualified
A pointer to non-const can be implicityly converted to a pointer to const, But not the other way around!
A function that takes a pointer to non-const
as parameter can modify the value passed as argument, while a function that takes a pointer to const as parameter cannot.
int * p1 = &x; // non-const pointer to non-const int
const int * p2 = &x; // non-const pointer to const int
int const * p2_2 = &x; // same as above
int * const p3 = &x; // const pointer to non-const int
const int * const p4 = &x; // const pointer to const int
The syntax with const and pointers is definitely tricky, and recognizing the cases that best suit each use tends to requeire some experience.
String literal are arrays containing null-terminate character sequences .
const char * foo = "hello";
This declares an array with the literal representation for “hello
”, and then a pointer to its first element is assigned to foo
.
C++ allows the use of pointers that point to pointers, that these, in its turn, point to data (or even to other pointers). The syntax simply requires an asterisk (*) for each level of indirection in the declaration of the pointer:
char a;
char * b;
char ** c;
a = 'z';
b = &a;
c = &b;
each one of them would correspond to a different value:
c
is of type char**
and a value of 8092;*c
is of type char*
and a value of 7230;**c
is of type char
and a value of ‘z’;The void
type of pointer is a special type of pointer.
In C++, void
represents the absence of type.
Therefore, void pointers are pointers that point to a value that has no type (and thus also an undetermind length and undetermined dereferencing properties).
sizeof
is an operator integrated in the C++ language that returns the size in bytes of its argument. For non-dynamic data types, this value is a constant. Therefore, for eaample, sizeof(char)
is 1, because char
has always a size of one byte.
In principle, pointers are meant to point to valid addresses, such as address of a variable or the address of an element in an array. But pointers can actually point to any address, including addresses that do not refer to any valid element. Typical examples of this are uninitialized pointers
and pointers to nonexistent elements of an array:
int *p; // uninitialized pointer (local variable)
int myarray[10];
int *q = myarray+20; // element out of bounds
Neither p
nor q
point to addresses known to contain a value, but none of the above statements causes an error. In C++, pointers are allowed to take any address value, no matter whether there actually is something at that address or not.
What can cause an error is to dereference such a pointer (i.e., actually accessing the value they point to). Accessing such a pointer causes undefined behavior, ranging from an error during runtime to accessing some random value.
But, sometimes, a pointer really needs to explicitly point to nowhere, and not just an invalid address. For such cases, there exists a special value that any pointer type can take: the null pointer value. This value can be expressed in C++ in two ways: either with an integer value of zero
, or with the nullptr
keyword:
int * p = 0;
int * q = nullptr;
Both p and q are null pointers, meaning that they explicitly point to nowhere, and they both actually compare equal: all null pointers compare euqal to other null pointers. It is also quite usual to see the defined constant NULL be used in older code to refer to the null pointer value:
int * r = NULL;
NULL
is defined in several headers of the standard library. and is defined as an alias of some null pointer constant value (such as 0
or nullptr
).
Do not confuse null pointers with void pointers!
A null pointer
is a value that any pointer can take to represent that it is pointing to “nowhere”, while a void
pointer is a type of pointer that can point to somewhere without a specific type.
C++ allows operations with pointers to functions. The typical use of this is for passing a function as an argument to another function.
Pointers to functions are declared with the same syntax as a regular function declaration, except that the name of the function is enclosed between parentheses() and an asterisk(*) is inserted before the name:
int operation (int x, int y, int (*functicall)(int, int))
{
int g;
g = (*functocall)(x, y);
return (g);
}
int main()
{
int m, n;
int (*minus)(int, int) = subtraction;
m = operation(7, 5, addition);
n = operation(20, m, minus);
}
minus
is a pointer to a function that has two parameters of type int
. It is initialized to point to the function subtraction
:
int (* minus)(int, int) = subtraction;
All memory needs were determind before program execution by defining the variables needed. But there may be cases where the memory needs of a program can only determined during runtime. For example , thern the memory needed depends on user input. On these cases, programs need to dynamically allocate memory
, for which the C++ language integrates the operators new
and delete
.
new
and new[]
Dynamic memory is allocated using operator new
.
new
is followed by a data type specifier and , if a sequence of more than one element is required, the number of these within []. It returns a pointer to the beginning of the new block of memory allocated. Its syntax is :
pointer = new type
pointer = new type [number of elements]
int * foo; // 定义一个指针
foo = new int [5];
foo
is a pointer, and thus , the first element pointed to by foo
can be accessed either with the expression foo[0]
or the expression *foo
(both are equivalent). The second element can be accessed either with foo[1]
or *(foo+1)
.
There is a substantial difference between declaring a normal array
and allocating dynamic memory
for a block of memory using new. The most important differece is that the size of a regualr array needs to be a constant expression, and thus its size has to be determined at the moment of designing the program, before it run, whereas the dynamic memory allocation performed by new allows to assign memory dyring runtime using any variable value as size.
C++ provides two standard mechanisms to check if the allocation wa successful:
bad_alloc
is thrown and the program execution is terminated. This exception method is the method used by default by new
nothrow
, the pointer returned by new
is a null pointer
, and the program continues its execution normallydelete
and delete[]
In most cases, memory allocated dynamically is only needed during specific periods of time within a program; once it is no longer needed, it can be freed so that the momeory becomes available again for other requests of dynamic memory. This is the purpose pf operator delete
, whose syntax is:
delete pointer;
delete[] pointer;
The value passed as argument to delete
shall be either a pointer to a memory block previously allocated with new
, or a null pointer
.
It is considerd good practice for programs to always be able to handle failures to allocate memory, either by checking the pointer value (if nothrow
) or by catching the proper exception.
new
and delete
for allocating dynamic memory in C++ were not available in the C language.
A data structure is a group of data elements grouped together under one name. These data elements, known as members, can have different types
and different length
. Data structures can be declared in C++ using the following syntax:
struct type_name{
member_type1 member_name1;
member_type2 member_name2;
member_type3 member_name3;
.
.
} object_names;
t y p e _ n a m e type\_name type_name is a name for the structure type;
o b j e c t _ n a m e object\_name object_name can be a set of valid identifiers for objects that have the type of this structure.
struct product {
int weight; // member_type member_name
double price;
};
product apple;
produce banana, melon;
// equal to
struct product {
int weight;
double price;
} apple, banana, melon;
Once the three objects
of a determined structure
type are declared , its members
can be accessed dirctly. The syntax for that is simply to insert a dot (.
) between the object name and the member name.
One of the features of data structures is the ability to refer to both their members individually or to the entire structure as a whole.
Like any other type, structures can be pointed to by its own type of pointers:
struct movies_t{
string title;
int year;
};
movies_t amovie;
movies_t * pmovie;
pmovie = &amovie;
The
arrow operator (->)
is a dereference operator that is used exclusively with pointers to objects that have members.
This operator serves to access the member of an object directly from its address. For example :
pmovie -> title; // 从指针到其所指对象的member,整体作为一个变量,返回这个title的值
is, for all purposes, equivalent to :
*pmovie.title
// equal to
*(pmovie.title)
Structures can also be nested in such a way that an element of a structure is itself another structure.
A type alias
is a different name by which a type can be identified. In C++, any valid type can be aliased so that it can be referred to with a different identifier.
In C++, there are two syntaxes for creating such type aliases:
Inherited form the C language, uses the typedef keyword
typedef existing_type new_type_name;
// existing_type is any type, either fundamental or compound
typedef char C;
typedef char * pChar;
using new_type_name = existing_type;
using C = char;
using pChar = char *;
using
is more generic, because typedef
has certain limitaions in the realm of templates.
Unions allow one portion of memory to be accessed as different data types. Its declaration and use is similar to the one of structures, but its functionality is totally different:
union type_name{
member_type1 member_name1;
member_type2 member_name2;
...
} object_names;
This creates a new union
type, identifid by type_name
, in which member elements occupy the same physical space in memory. The size of this type is the one of the largest member element.
Each of these memebrs is of a different data type, But since all of them are referring to the same location in memory, the modification of one of the members will affect the value of all of them. It is not possible to store different values in them in a way that each is independent of the others.
When unions are members of a class (or structure), they can be declared with no name. Which means they become anonymous unions.
Enumerated types are types that are defined with a set of custom identifiers, known as enumerators, as possible values. Objects of these enumerated types can take any of these enumerators as value.
enum type_name {
value1,
value2,
...
} object_names;
This declaration includes no other type, neither fundamental nor compound, in its definition, which means this creates a whole new data type from scratch withou basing it on any other existing type.
To create real enum
type which are neither implicitly convertible to int
and that neither have enumerator values of type int
, but the enum
type itself, usding enum class
(or enum struct
) instead of enum
:
enum class Colors {
black,
blue,
..
};