When
const
appears in a type, it indicates that the compiler will forbid
attempts to modify objects of that type.
const int lim = 5; // or int const lim = 5; int a = lim + 7; lim++; // error lim = 10; // error
Such constant objects normally have to be initialized when defined, since they can't subsequently take on a value later. However, this does not apply to function parameters, which are initialized on each function call:
void func(const int lim) { . . . }
This doesn't mean that lim
always has the same value on each invocation because, as
an object with automatic storage, it is not
really a single variable, but a template for creating a
variable on each invocation of the function. This use
might help to discipline a programmer in implementing the
function, but it is of little consequence to the caller,
however, because the function already has no means to
affect variables provided by the caller as arguments.
A more interesting use of
const
is with pointers. When taking the address of an
object, the qualifiers of the object's type are preserved
in the pointer type. For example:
const int lim = 5;
The address of lim
is of type
const int *
, not
merely int *
. It
is an error to assign a value of type
const T *
to a variable of type
T *
, as the value then
stored in the variable could be used to modify the object
it points to. Consider this function, which modifies what
its argument points to:
void increment(int *ptr) { ++*ptr; } const int lim = 5; increment(&lim); // error
This would undermine the supposition that other users
of lim
have, i.e., that it is immutable.
In contrast, it is possible to assign a value of type
T *
to a variable of
type
const T *
. Functions that take
strings as
arguments, but don't need to modify them, still have to
be declared as taking a pointer to the start of the
string, which enables them to modify it. strlen
could have been
defined as follows:
size_t strlen(char *in) { char *orig = in; while (*in++) ; return in - orig; }
Of course, the corresponding prototype would then be:
size_t strlen(char *);
The caller, who does not know how the function is implemented, and only sees its prototype, must trust that the function will not mistakenly modify the string. A small modification helps to assure the caller, and helps the implementor to check that he has not inadvertently broken his side of the contract not to modify the string:
size_t strlen(const char *in) { char *orig = in; while (*in++) ; return in - orig; }
Now it can be used with unmodifiable strings like
msg
:
const char msg[] = "foo"; char buf[100]; strlen(msg); // okay strlen(buf); // okay
It is also safe to use with modifiable arrays like
buf
, whose type decays to
char *
,
which is safe to assign to
const char *
.