1.7 pointers, arrays and references
The most basic data collection type is array - a sequence of elements of the same type allocated continuously in space. This is basically the mechanism provided by the hardware. Arrays with element type char can be declared as follows:
char v[6]; //An array of 6 characters
Similarly, pointers can be declared as follows:
char* p; //Pointer to character
In the declaration statement, [] represents "... Array", * represents "point to...". The subscripts of all arrays start at 0, so V contains six elements, from v[0] to v[5]. The size of the array must be a constant expression. A pointer variable stores the address of an object of a corresponding type:
char* p = &v[3]; //p points to the fourth element of v char x = *p; //*P is the object to which p refers
In the expression, the leading unary operator * represents the content of... And the leading unary operator & represents the address of. The following figure can be used to represent the result of the above initialization definition.
Consider the task of copying 10 elements of one array to another array:
void copy_fct(){ int v1[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; int v2[10]; //v2 will become a copy of v1 for(auto i = 0; i!=10; i++) //Copy element v2[i] = v1[i]; //... }
The above for statement can be interpreted as follows: "set i to 0. When i is not equal to 10, copy the ith element and increment i". The increment operator + + performs a simple plus 1 operation when acting on an integer or floating-point variable. C + + also provides a simpler for statement, called range for statement, which can traverse a sequence in the simplest way:
void print(){ int v[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; for(auto x:v) //For each x in v cout<<x<<'\n'; for(auto x : {10, 21, 32, 43, 54, 65}) cout<<x<<'\n'; //... }
The first range for statement can be interpreted as "traversing each element of v from beginning to end, putting a copy into x and printing". Note that when we initialize an array with a list, we do not need to specify its size. The range for statement can be used for any sequence of elements.
If you do not want to copy the value from v to variable x, but just make x refer to an element, you can write the following code:
void increment(){ int v1[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; for(auto& x : v) //For each x in v, add 1 ++x; //... }
In a declaration statement, the unary post operator & represents a reference to. A reference is similar to a pointer, except that we do not need to use the leading operator * to access the referenced value. Moreover, a reference cannot reference other objects after initialization.
References are particularly useful when specifying arguments to a function. For example:
void sort(vector<double>& v); //Sort v(v is a double vector)
By using references, we guarantee that we will not copy my when we call sort(my_vec)_ VEC, which is really good for my_ VEC sorts rather than its copies.
In another case, we don't want to change the argument and want to avoid the cost of parameter copy. At this time, we should use const reference. For example:
double sum(const vector<double>&)
It is very common for functions to accept arguments of const reference type.
Operators used in declaration statements (such as &, *, []) are called declaration operators:
T a[n] //T[n]: an array of n t's T* p //T *: p is the pointer to t T& r //T &: r is a reference to t T f(A) //T(A): function f accepts an argument of type A and returns a result of type T
Null pointer
Our goal is to ensure that the pointer always points to an object, so that the dereference operation of the pointer is legal. When there is really no object to point to or need to represent the concept of "no object available" (for example, reaching the end of the list), we give the pointer value nullptr("null pointer"). All pointer types share the same nullptr:
double* pd = nullptr; Link<Record>* lst = nullptr; //A pointer to the Record Link int x = nullptr; //Error: nullptr is a pointer, not an integer
When accepting a pointer argument, it is usually wise to check whether it points to something:
int count_x(const char* p, char x) //Count the number of occurrences of x in p [] //Suppose p points to a zero terminated array of characters (or nothing) { if(p==nullptr) return 0; int count = 0; for(; *p != 0; ++p) if(*p == x) ++count; return count; }
There are two points worth noting: one is how to use + + to move the pointer to the next element of the array; Second, in the for statement, if you do not need initialization, you can omit it.
count_ The definition of X () assumes that char is a c_style string, that is, the pointer points to a zero terminated char array. The characters in the string literal are immutable in order to handle count_x(“Hello!”), count_x is declared as a const char parameter.
In old-fashioned code, nullptr's function is usually replaced by 0 and NULL. However, using nullptr can avoid confusing integers (0 or NULL) with pointers (such as nullptr).
In count_ In the X () example, we do not use the initialization part for the for statement, so we can use a simpler while statement:
int count_x(const char* p, char x) //Count the number of occurrences of x in p [] //Suppose p points to a zero terminated array of characters (or nothing) { if(p==nullptr) return 0; int count = 0; while(*p){ if(*p == x) ++count; ++p; } return count; }
The while statement is executed repeatedly until its loop condition becomes false.
Checking a value (for example, while (* P) in count_x() is equivalent to comparing the value with 0 (for example, while (* P! = 0)). Checking the pointer value (such as if §) is equivalent to comparing the pointer value with nullptr (such as if (P! = nullptr)).
Empty reference does not exist. A reference must point to a legal object (which is also assumed by C + + implementations). There are clever but obscure ways to break this rule, but don't do it.