1776
always follows immediately after the cell with address 1775
and precedes the one with 1777
, and is exactly one thousand cells after 776
and exactly one thousand cells before 2776
.&
), known as address-of operator. For example:
|
|
myvar
to foo
; by preceding the name of the variable myvar
with the address-of operator (&
), we are no longer assigning the content of the variable itself to foo
, but its address.myvar
is placed during runtime in the memory address 1776
.
|
|
25
to myvar
(a variable whose address in memory we assumed to be 1776
).foo
the address of myvar
, which we have assumed to be 1776
.myvar
to bar
. This is a standard assignment operation, as already done many times in earlier chapters.&
).foo
in the previous example) is what in C++ is called a pointer. Pointers are a very powerful feature of the language that has many uses in lower level programming. A bit later, we will see how to declare and use pointers.*
). The operator itself can be read as "value pointed to by".
|
|
baz
equal to value pointed to by foo
", and the statement would actually assign the value 25
to baz
, since foo
is 1776
, and the value pointed to by 1776
(following the example above) would be 25
.foo
refers to the value 1776
, while *foo
(with an asterisk *
preceding the identifier) refers to the value stored at address 1776
, which in this case is 25
. Notice the difference of including or not including the dereference operator (I have added an explanatory comment of how each of these two expressions could be read):
|
|
&
is the address-of operator, and can be read simply as "address of"*
is the dereference operator, and can be read as "value pointed to by"&
can be dereferenced with *
.
|
|
|
|
myvar
was myvar=25
. The second one uses the address-of operator (&
), which returns the address of myvar
, which we assumed it to have a value of 1776
. The third one is somewhat obvious, since the second expression was true and the assignment operation performed on foo
was foo=&myvar
. The fourth expression uses the dereference operator (*
) that can be read as "value pointed to by", and the value pointed to by foo
is indeed 25
.foo
remains unchanged, the following expression will also be true:
|
|
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.type * name;
type
is the data type pointed to by the pointer. This type is not the type of the pointer itself, but the type of the data the pointer points to. For example:
|
|
int
, the second one to a char
, and the last one to a double
. Therefore, although these three example variables are all of them pointers, they actually have different types: int*
, char*
, and double*
respectively, depending on the type they point to.*
) 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, but which is also written with an asterisk (*
). They are simply two different things represented with the same sign.
|
|
firstvalue is 10 secondvalue is 20 |
firstvalue
nor secondvalue
are directly set any value in the program, both end up with a value set indirectly through the use of mypointer
. This is how it happens:mypointer
is assigned the address of firstvalue using the address-of operator (&
). Then, the value pointed to by mypointer
is assigned a value of 10
. Because, at this moment, mypointer
is pointing to the memory location of firstvalue
, this in fact modifies the value of firstvalue
.secondvalue
and that same pointer, mypointer
.
|
|
firstvalue is 10 secondvalue is 20 |
&
) by "address of", and asterisks (*
) by "value pointed to by".p1
and p2
, both with and without the dereference operator (*
). The meaning of an expression using the dereference operator (*) is very different from one that does not. When this operator precedes the pointer name, the expression refers to the value being pointed, while when a pointer name appears without this operator, it refers to the value of the pointer itself (i.e., the address of what the pointer is pointing to).
|
|
*
) for each pointer, in order for both to have type int*
(pointer to int
). This is required due to the precedence rules. Note that if, instead, the code was:
|
|
p1
would indeed be of type int*
, but p2
would be of type int
. Spaces do not matter at all for this purpose. But anyway, simply remembering to put one asterisk per pointer is enough for most pointer users interested in declaring multiple pointers per statement. Or even better: use a different statemet for each variable.
|
|
|
|
mypointer
and myarray
would be equivalent and would have very similar properties. The main difference being that mypointer
can be assigned a different address, whereas myarray
can never be assigned anything, and will always represent the same block of 20 elements of type int
. Therefore, the following assignment would not be valid:
|
|
|
|
10, 20, 30, 40, 50, |
[]
) were explained as specifying the index of an element of the array. Well, in fact these brackets are a dereferencing 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. For example:
|
|
a
is a pointer, but also if a
is an array. Remember that if an array, its name can be used just like a pointer to its first element.
|
|
|
|
myptr
), never the value being pointed (i.e., *myptr
). Therefore, the code above shall not be confused with:
|
|
*
) in the pointer declaration (line 2) only indicates that it is a pointer, it is not the dereference operator (as in line 3). Both things just happen to use the same sign: *
. As always, spaces are not relevant, and never change the meaning of an expression.
|
|
char
always has a size of 1 byte, short
is generally larger than that, and int
and long
are even larger; the exact size of these being dependent on the system. For example, let's imagine that in a given system, char
takes 1 byte, short
takes 2 bytes, and long
takes 4.
|
|
1000
, 2000
, and 3000
, respectively.
|
|
mychar
, as one would expect, would contain the value 1001. But not so obviously, myshort
would contain the value 2002, and mylong
would contain 3004, even though they have each been incremented only once. The reason is that, when adding one to a pointer, the 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 pointer.
|
|
++
) 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. This also applies to expressions incrementing and decrementing pointers, which can become part of more complicated expressions that also include dereference operators (*
). Remembering operator precedence rules, we can recall that postfix operators, such as increment and decrement, have higher precedence than prefix operators, such as the dereference operator (*
). Therefore, the following expression:
|
|
*(p++)
. And 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 originally by the pointer (the address it pointed to before being incremented).
|
|
|
|
++
has a higher precedence than *
, both p
and q
are incremented, but because both increment operators (++
) are used as postfix and not prefix, the value assigned to *p
is *q
before both p
and q
are incremented. And then both are incremented. It would be roughly equivalent to:
|
|
const
. For example:
|
|
p
points to a variable, but points to it in a const
-qualified manner, meaning that it can read the value pointed, but it cannot modify it. Note also, that the expression &y
is of type int*
, but this is assigned to a pointer of type const int*
. This is allowed: a pointer to non-const can be implicitly converted to a pointer to const. But not the other way around! As a safety feature, pointers to const
are not implicitly convertible to pointers to non-const
.const
elements is as function parameters: 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.
|
|
11 21 31 |
print_all
uses pointers that point to constant elements. These pointers point to constant content they cannot modify, but they are not constant themselves: i.e., the pointers can still be incremented or assigned different addresses, although they cannot modify the content they point to.
|
|
const
and pointers is definitely tricky, and recognizing the cases that best suit each use tends to require some experience. In any case, it is important to get constness with pointers (and references) right sooner rather than later, but you should not worry too much about grasping everything if this is the first time you are exposed to the mix of const
and pointers. More use cases will show up in coming chapters.const
with pointers, the const
qualifier can either precede or follow the pointed type, with the exact same meaning:
|
|
const
, as for historical reasons this seems to be more extended, but both are exactly equivalent. The merits of each style are still intensely debated on the internet.cout
, to initialize strings and to initialize arrays of characters.const char
(as literals, they can never be modified). For example:
|
|
"hello"
, and then a pointer to its first element is assigned to foo
. If we imagine that "hello"
is stored at the memory locations that start at address 1702, we can represent the previous declaration as:foo
is a pointer and contains the value 1702, and not 'h'
, nor "hello"
, although 1702 indeed is the address of both of these.foo
points to a sequence of characters. And because pointers and arrays behave essentially in the same way in expressions, foo
can be used to access the characters in the same way arrays of null-terminated character sequences are. For example:
|
|
'o'
(the fifth element of the array).*
) for each level of indirection in the declaration of the pointer:
|
|
7230
, 8092
, and 10502
, could be represented as:c
, which is a pointer to a pointer, and can be used in three different levels of indirection, 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'
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 undetermined length and undetermined dereferencing properties).void
pointers a great flexibility, by being able to point to any data type, from an integer value or a float to a string of characters. In exchange, they have a great limitation: the data pointed by them cannot be directly dereferenced (which is logical, since we have no type to dereference to), and for that reason, any address in a void
pointer needs to be transformed into some other pointer type that points to a concrete data type before being dereferenced.
|
|
y, 1603 |
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 example, sizeof(char)
is 1, because char
is has always a size of one byte.
|
|
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.nullptr
keyword:
|
|
p
and q
are null pointers, meaning that they explicitly point to nowhere, and they both actually compare equal: all null pointers compare equal 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:
|
|
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
).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. One refers to the value stored in the pointer, and the other to the type of data it points to.*
) is inserted before the name:
|
|
8 |
minus
is a pointer to a function that has two parameters of type int
. It is directly initialized to point to the function subtraction
:
|
|
Previous: Character sequences | Index | Next: Dynamic memory |