美文网首页
Chapter 6 Pointer

Chapter 6 Pointer

作者: linceln | 来源:发表于2018-08-01 15:10 被阅读24次

Introductions to pointers

A pointer is a variable that holds a memory address as its value.

It can be dereferenced using the dereference operator(*) to retrieve the value at the address they are holding.

What good are pointers?

It turns out that pointers are useful in many different cases:

  1. Arrays are implemented using pointers. Pointers can be used to iterate through an array.
  2. Pointers are the only way you can dynamically allocate memory in C++.
  3. Pointers can be used to pass a large amount of data to a function in a way that doesn't involve copying the data, which is inefficient.
  4. Pointers can be used to pass a function as a parameter to another function.
  5. Pointers can be used to achieve polymorphism when dealing with inheritance.
  6. Pointers can be used to have one struct/class point at another struct/class, to form a chain. This is useful in some more advanced data structures, such as linked list and trees.

The address-of operator(&)

The address-of operator(&) allows us to see what memory address is assigned to a variable..

#include <iostream>

int main() 
{
    int x = 5;
    // print the memory address of variable x
    std::cout << &x << '\n';
}

Note: the address-of operator looks like the bitwise-and operator, the difference between them is the address-of operator is unary, but the bitwise-and operator is binary.

The dereference operator(*)

Getting the address of a variable isn't very useful by itself.

The dereference operator(*) allows us to access the value at a particular address.

Note: the dereference operator looks like the multiplication operator, the difference between them is dereference operator is unary, but the multiplication operator is binary.

Declaring a pointer

Pointer variables are declared just like normal variables, only with an asterisk between the data type and the variable name.

int *iPtr; // a pointer to an integer value
double *dPtr; // a pointer to a double value

int* iPtr2; // also valid syntax(acceptable, but not favored)
int * iPtr3; // also vallid syntax(but don't do this)

Note: remember the asterisk between data type and variable name is not a dereference operator, it is part of the pointer declaration syntax.

Why recommend attaching the asterisk to the variable name instead of data type?

Here is an example of declaring multiple pointer variables:

int* iPtr6, iPtr7;

It's easy to forget to include asterisk with each variable, as a result, iPtr6 is a pointer to an int, but iPtr7 is just a plain int.

However, when returning a pointer from a function, it's clearer to put the asterisk next to the return type:

int* doSomething();

Best practice

  1. When declaring a pointer variable, put the asterisk next to the variable name.
  2. When declaring a function, put the asterisk of a pointer return value next to the type.

Assigning a value to a pointer

Since pointers only hold address, when we assign a value to a pointer, that value has to be an address.

One of the most common things to do with pointers is have them hold the address of a different variable.

To get the address of a variable, we use the address-of operator:

int value = 5;
int *ptr = &value;

C++ will not allow you to directly assign literal memory adresses to a pointer:

double *p = 0x0012FF7C; // not okey, treated as assigning an integer literal

The address-of operator returns a pointer

The address-of operator(&) doesn't return the address of its operand as a literal. Instead, it returns a pointer containing the address of the operand, whose type is derived from the argument.

#include <iostream>
#include <typeinfo>

int main()
{
    int x(4);
    std::cout << typeid(&x).name();
    
    return 0;
}

On Xcode, this prints

Pi

which means pointer of int.

Dereferencing pointers

Once we have a pointer variable pointing at something, the other common thing to do with it is dereference()* the pointer to get the value of what it's pointing at.

int value = 5;
std::cout << &value; // prints address of value
std::cout << value; // prints contents of value
 
int *ptr = &value; // ptr points to value
std::cout << ptr; // prints address held in ptr, which is &value
std::cout << *ptr; // dereference ptr (get the value that ptr is pointing to)

When the address of variable value is assigned to pointer ptr, the following are true:

  • ptr is the same as &value
  • *ptr is treated the same as value

Dereferencing invalid pointers

Pointers in C++ are inherently unsafe.

The size of pointers

The size of a pointer is dependent upon the architecture the executable is compiled for:

  • A pointer on a 32-bit machine is 32 bits(4 bytes).
  • A pointer on a 64-bit machine is 64 bits(8 bytes).

Note: this is true regardless of what is being pointed to.

Null pointers

Besides memory addresses, there is one additional value that a pointer can hold: a null value.

A null value is a special value that means the pointer is not pointing at anything. A pointer holding a null value is called a null pointer.

In C++, we can assign a pointer a null value by initializing or assigning it the literal 0:

float *ptr {0}; // ptr is now a null pointer

float *ptr1; // ptr1 is uninitialized
ptr1 = 0; // ptr1 is now a null pointer

Best Practice
Initialeze your pointers to a null value if you are not giving them another value.

Dereferencing null pointers

Dereferencing a garbage pointer would lead to undefined results, dereferencing a null pointer also results in undefined behavior. In most case, it will crash your application.

The NULL mocro

C(but not C++) defines a special preprocessor macro called NULL that is #define as the value 0.

double *ptr { NULL }; // assign address 0 to ptr

Best practice
Because NULL is a preprocessor macro and because it's technically not a part of C++, best practice in C++ is to avoid using it.

nullptr in C++11

Note that the value of 0 is not a pointer type, so assigning 0 to a pointer to denote that the pointer is a null pointer is a little inconsistent.

In rare cases, it can cause problems because the compileer can't tell whether we mean a null pointer or the integer 0:

// is 0 an integer argument or a null pointer argument? (It will assume integer)
doSomething(0);

Starting with C++11, this should be favored instead of 0 when we want a null pointer:

int *ptr { nullptr };

C++ will implicitly convert nullptr to any pointer type. So in the above example, nullptr is implicitly converted to an integer pointer, and then the value of nullptr assigned to ptr.

std::nullptr_t in C++11

std::nullptr_t is a new type, it can only hold one value: nullptr.

If we want to write a function that accepts a nullptr argument, std::nullptr is the type we want.

#include <iostream>
#include <cstddef> // for std::nullptr_t
 
void doSomething(std::nullptr_t ptr)
{
    std::cout << "in doSomething()\n";
}
 
int main()
{
    doSomething(nullptr); // call doSomething with an argument of type std::nullptr_t
    return 0;
}

Pointers and arrays

Pointers and arrays are instinsically related in C++.

Similarities between pointers and fixed arrays

The array variable contains the address of the first element of the array, as if it were a pointer.

#include <iostream>

int main()
{
    int array[5] = { 9, 7, 5, 3, 1 };
    // print the value of the array avariable, first element's address of array
    std::cout << array << '\n';
}

Note: it's a common fallacy in C++ believe an array and a pointer to the array are identical. They're not. Although both point to the first element of array, they have different type imformation. In the above case, array is of type int[5], whereas a pointer to the array would be of type int *.

#include <iostream>

int main()
{
    int array[5] = { 9, 7, 5, 3, 1 };
    // print 9
    std::cout << *array << '\n';
}

Note that we are not actually dereferencing the array itself. The array(of type int[5])gets implicitly converted into a pointer(of type int *), and we deferencing the pointer to get the value at the memory address the pointer is holding, which is the value of the first element of the array.

We can also assign a pointer to point at the array:

#include <iostream>

int main()
{
    int array[5] = { 9, 7, 5, 3, 1 };
    // the array decays(implicitly converted) into a pointer of type int *
    int *ptr = array;
}

Difference between pointers and fixed arrays

  1. When using the sizeof() operator

    • When used on a fixed array, sizeof() returns the size of the entire array(array length * element size).
    • When used on a pointer, sizeof() returns the size of a memory address(in bytes)
  2. When using the address-of operator(&)

    • Taking the address of a pointer yields the memory address of the pointer variable
    • Taking the address of a array returns a pointer to the the entire array, this pointer also pointers to the first element of the array, but the type information is different

Note: A fixed array knows how long the array it is pointing to is. A pointer to the array does not.

Passing fixed array to functions

When passing an array as an argument to a function, a fixed array decays into a pointer, and the pointer is passed to the function.

#include <iostream>

void printSize(int *array)
{
    // print 4(size of pointer)
    std::cout << sizeof(array) << '\n';
}

int main()
{
    int array[] = {1, 2, 3};
    
    // print 12(array length * element size)
    std::cout << sizeof(array) << '\n';
    
    printSize(array);
    
    return 0;
}

Note: this happen even if the parameter is declared as a fixed array int the function printSize(int array[]).

Recommendation: Favor the pointer syntax over the array syntax for array function parameters. It makes it clear that the parameter is being treated as a pointer, not a fixed array.

An intro to pass by address

When printSize(int *ptr) is called, array decays into a pointer, and the value of that pointer is copied into the ptr parameter. Although the value in ptr is a copy of the address, ptr still points at the actual array. Consequently, when ptr is dereferenced, the actual array is dereferenced.

Arrays in structs and classes don't decay

Pointer arithmetic and array indexing

Pointer arithmetic

The C++ language allows you to perform integer addition or subtraction operations on pointers.

If a pointer ptr pointes to an integer, ptr + 1 is the address of the next integer in memory after ptr.

If ptr points to an integer(assuming 4 bytes), ptr + 3 means 3 integers(12 bytes) after ptr. If ptr points to a char, which is always 1 byte, ptr + 3 means 3 chars(3 bytes) after ptr.

When calculating the result of a pointer arithmetic expressions, the compiler always multiplies the integer operand by the size of the object being pointed to.
This is called scaling.

Arrays are laid out sequentially in memory

By using the address-of operator(&), we can determine that arrays are laid out swquentially in memory. That is, elements 1, 2, 3, 4, ... are all adjacent to each other, in order.

Pointer arithmetic, arrays, and the magic behind indexing

  1. Arrays are laid out in memory sequentially
  2. A fixed array can decay into a pointer that points to the first element of the array
  3. Adding 1 to a pointer returns the memory address of the next object of that type in memory

From above, we can conclude that adding 1 to an array should point to the second element

#include <iostream>

int main()
{
    int array[5] = {1, 2, 3, 4, 5};
    
    int first = *array
    int second = *(array + 1);
}

When the compiler sees the subscript(indexing) operator([]), it actually translates that into a pointer addition and dereference. array[n] is the same as *(array + n).

Using a pointer to iterate through an array

We can use a pointer and pointer arithmetic to loop through an array.

#include <iostream>
 
int main()
{
    const int arrayLength = 7;
    char name[arrayLength] = "Mollie";
    int numVowels(0);
    for (char *ptr = name; ptr < name + arrayLength; ++ptr)
    {
        switch (*ptr)
        {
            case 'A':
            case 'a':
            case 'E':
            case 'e':
            case 'I':
            case 'i':
            case 'O':
            case 'o':
            case 'U':
            case 'u':
                ++numVowels;
        }
    }
 
    cout << name << " has " << numVowels << " vowels.\n";
 
    return 0;
}

C-style string symbolic constants

#include <iostream>

int main()
{
    // case 1
    char name[] = "Alex";
    // case 2
    const char *name = "Alex";
}

case 1:
The program allocates memory for a fixed array of lenth 5, and initializes that memory with the string "Alex\0". As memory has been specifically allocated for the array, you're free to alter the contents of the array. The array itself is treated as a normal local variable, so when the array goes out of scope, the memory used by the array is freed up for other uses.

case 2:
The compiler places the string "Alex\n" into read-only memory somewhere, and then sets the pointer to point to it. Because this memory my be read-only, best practice is to make sure the string is const.

To summarize, use a non-const char array when you need a string variable that you can modify later, use a pointer to a const string literal when you need a read-only string literal.

Rule: Feel free to use C-style string symbolic constants if you need read-only strings in your program, but always make them const.

Dynamic memory allocation with new and delete

The need for dynamic memory allocation

C++ supports three basic types of memory allocation:

  • Static memory allocation happens for static and global variables. Memory for these types of variables is allocated once when your program is run and persites throughout the life of your program.
  • Automatic memory allocation happens for function parameters and local variables. Memory for these types of variables is allocated when the relevant block is entered, and freed when the block is exited, as many times as necessary.
  • Dynamic memory allocation

Both static and automatic allocation have two things in common:

  1. The size of the variable/array must be known at compile time
  2. Memory allocations deallocation happens automatically(when the variable is instantiated and destroyed)

Dynamic memory allocation is a way for running programs to request memory from the operating system when needed. This memorty does not com from the program's limited stack memory, but from the heap.

Dynamically allocating single variables

int *ptr = new int;

We are requesting an integer's worth of memory from the operating system. The new operator creates the object using that memory, and then returns a pointer containing the address of the memory that has been allocated.

We can then dereference the pointer to access the memory:

*ptr = 7;

How does dynamic memory allocation work

Your computer has memory (probably lots of it) that is available for applications to use. When you run an application, your operating system loads the application into some of that memory. This memory used by your application is divided into different areas, each of which serves a different purpose. One area contains your code. Another area is used for normal operations (keeping track of which functions were called, creating and destroying global and local variables, etc…). We’ll talk more about those later. However, much of the memory available just sits there, waiting to be handed out to programs that request it.

When you dynamically allocate memory, you’re asking the operating system to reserve some of that memory for your program’s use. If it can fulfill this request, it will return the address of that memory to your application. From that point forward, your application can use this memory as it wishes. When your application is done with the memory, it can return the memory back to the operating system to be given to another program.

Unlike static or automatic memory, the program itself is responsible for requesting and disposing of dynamically allocated memory.

Initializing a dynamically allocated variable

int *ptr = new int (5);
int *ptr11 = new int {5};// in C++11

Deleting single variables

delete ptr;
ptr = 0; // set ptr to be a null pointer
ptr = nullptr; // in C++11

What does it mean to delete memory?

The delete operator does not actually delete anything. It simply returns the memory being pointed to back to the operating system.

And we are not deleting a variable either. The pointer variable still has the same scope as before, and can be assigned a new value just like other variable.

Note: deteting a pointer that is not pointing to dynamically allocated memory may cause bad things to hanppen.

Dangling pointers

A pointer that is pointing to deallocated memory is called a dangling pointer. Dereferencing or deleting a dangling pointer will lead to undefined behavior.

int *ptr = new int;
*ptr = 7;
delete ptr; // ptr now is a dangling pointer now

std::cout << *ptr; // dereferencing a dangling pointer will cause undefined behavior

delete ptr; // trying to deallocate the memory again will also lead to undefined behavior

Deallocating memory may create multiple dangling pointers

int *ptr = new int;
int *otherPtr = ptr;

delete ptr; // ptr and otherPtr are now dangling pointers
ptr = 0; // ptr is now a null pointer

// leaves otherPtr a dangling pointer

return 0;

Best practice:

  1. Try to avoid having multiple pointers point at the same piece of dynamic memory.
  2. When deleting a pointer, if that pointer is not going out of scope immeidately afterward, set the pointer to null pointer.

new operator can fail

When requesting memory from the operating system, in rare cases, the operating system may not have any memory to grant the request with.

// value will be set to a null pointer if the integer allocation fails
int *value = new (std::nothrow)int;

Null pointers and dynamic memory allocation

// if ptr isn't already allocated, allocate it
if (!ptr) {
    ptr = new int;
}

// deleting a null pointer has no effect
delete ptr;

Memory leaks

Dynamically allocated memory effectively has no scope, however, the pointers used to hold dynamically allocated memory addresses follow the scoping rules of normal variables.

Memory leaks happen when your program loses the address of some bit of dynamically allocated memory before giving it back to the operating system.

void doSomething() {
    int *ptr = new int;
}

The function above allocates an integer dynamically, but never free it using delete operation.

Because pointers follow all of the same rules as normal variables, when the function ends, ptr will go out of scope. As ptr is the only variable holding the address of the dynamically allocated integer, when variable ptr is destroyed there are no more references to the dynamically allocated memory.

There are other ways of memory leaks:

// case 1
int value = 5;
int *ptr = new int; // allocate memory
ptr = &value; // old address lost, results in memory leak

// case 2
int *ptr = new int;
ptr = new int; // old address lost, results in memory leak

To fixed:

// case 1
int value = 5;
int *ptr = new int;
delete ptr;
ptr = &value;

// case 2
int *ptr = new int;
delete ptr;
ptr = new int;

Dynamically allocating arrays

new[] and delete[]

Unlike a fixed array, where the array size must be fixed at compile time, dynamically allocating an array allow us to choose an array length at runtime.

To allocate an array dynamically, we use the array form of new[] and delete[].

int *array = new int[length];

delete[] array;

Note: this memory is allocated from a different place than the memory used for fixed arrays, the size of the array can be quite large.

When deleting a dynamically allcated array, we have to use the array version of delete, which is delete[].

This tells the CPU that it needs to clean up multiple variables instead of a single variable.

The new[] keeps track of how mush memory was allocated to a variable, so that delete[] can delete the proper amount. Unfortunately, this size isn't accessible to the programmer.

Initializing

int *array = new int[length]();

Prior to C++11, there was no easy way to initialize a dynamic array to a non-zero value

// Prior to C++11
int fixedArray[5] = { 9, 7, 5, 3, 1 };
int *array = new int[5];
array[0] = 9;
array[1] = 7;
array[2] = 5;
array[3] = 3;
array[4] = 1;

// C++11
int fixedArray[5] { 9, 7, 5, 3, 1 };
int *array = new int[5] {9, 7, 5, 3, 1};

In C++11, you can not initialize a dynamically allocated char array from a C-style string:

char *array = new char[14] {"Hello, world"}; // doesn't work in C++11, use std::string instead or strcpy it

Dynamic arrays must be declared with an explicit length:

int fixedArray[] {1, 2, 3}; // okay: implicit array size for fixed arrays
 
int *dynamicArray1 = new int[] {1, 2, 3}; // not okay: implicit size for dynamic arrays
 
int *dynamicArray2 = new int[3] {1, 2, 3}; // okay: explicit size for dynamic arrays

Resizing arrays

Dynamically allocating an array allows you to set the array length at the time of allocation. However, C++ does not provide a built-in way to resize an array that has already been allocated.

To work around this limitation, you can copy allocate a new array, copying the elements over, and deleting the old array.

But avoid doing this yourself, std::vector can do this.

Pointers and const

  • A non-const pointer can be redirected to point to other addresses.
  • A const pointer always points to the same address, and this address can not be changed.
  • A pointer to a non-const value can change the value it is pointing to. These can not point to a const value.
  • A pointer to a const value treats the value as const (even if it is not), and thus can not change the value it is pointing to.

Reference variables

So far, we've discussed two basic variable types:

  • Normal variables, which hold values directly.
  • Pointers, which hold the address of another value(or null) and can be dereferenced to retrieve the value at the address they point to.

References are the third basic type of variable that C++ supports. A reference is a type of C++ variable that acts as an alias to another object or value.

C++ supports three kinds of references:

  1. Reference to non-const values.
  2. Reference to const values.
  3. C++11 added r-value reference.

Reference to non-const values

int value = 5; // normal integer
int &ref = value; // reference to variable value

In this context, the ampersand & does not mean "address of", it means "reference to".

Reference as aliases

References generally act identically to the values they're referencing.

int value = 5;
int &ref = value;

value = 6;
ref = 7;

std::cout << value; // prints 7

++ref;

std::cout << value; // prints 8

std::cout << &value; // prints 0012FF7C
std::count << &ref; // prints 0012FF7C

Review of l-values and r-values

L-values are objects thar have a defined memory address(such as variables), and persist beyond a single expression. R-values are temporary values that do not have a defined memory address, and only have expression scope. R-values include both the results of expressions and literals.

Reference must be initialized

int value = 5;
int &ref = value; // valid

int &invalidRef; // invalid

Unlike pointers, which can hold a null value, there is no such thing as a null reference.

References to non-const values can only be initialized with non-const l-values. They can not be initialzed with const l-values or r-values.

int x = 5;
int &ref1 = x;

const int y = 7;
int &ref2 = y; // not okay, y is a const  l-value

int &ref3 = 6; // not okay, 6 is an r-value

References can not be reassigned

Once initialized, a reference can not be changed to reference another variable.

int value1 = 5;
int value2 = 6;

int &ref = value1;
ref = value2; // assign value2 to value1  does not change the reference.

value1 = 6
value2 = 6

Reference as function parameters

Reference are most often used as function parameters. In this context, the reference parameter acts as an alias for the argument, and no copy of the argument is made into the parameter. This can lead to better performance if the argument is large or expensive to copy.

#include <iostream>

// ref is a reference to the argument passed in, not a copy
void change(int &ref)
{
    ref = 6;
}

int main ()
{
    int n = 5;
    change(n);
    
    std::cout << n << std::endl;
    
    return 0
}

Best practice:
Pass arguments by non-const reference when the argument needs to be modified by the functioin.

The primary downsize of using non-const references as function parameters is that the argument must be a non-const l-value. This can be restrictive.

Using reference to pass C-style arrasys to functions

If a C-style array is passed by reference, it does not decay to pointers.

void printElements(int (&array)[4]) {
    
    int length {sizeof(array) / sizeof(array[0]};
    
    for (int i{0}; i < length; ++i) {
        std::cout << array[i] << std::endl;
    }
}

int main() {

    int arr[]{99, 20, 14, 80};
    
    printElements(arr);
    
    return 0;
}

Note: you need to explicitly define the array size in the parameter.

References as shortcuts

A secondary use of references is to provide easier access to nested data.

struct Something {
    int value1;
    float value2;
}

struct other {
    Something something;
    int otherValue;
}

Other other;

int &ref = other.something.value1;

other.something.value1 = 5;
ref = 5;

References vs pointers

A reference acts like a pointer that is inplicitly dereferenced when accessed(references are usually implemented internally by the compiler using pointers).

int value = 5;
int *const ptr = &value;
int &ref = value;

*ptr = 5;
ref = 5;

Reference must be initialized to valid objects(cannot be null) and cannot be changed once set.

References and const

const int value = 5;
const int &ref = value; // ref is a reference to const value

Unlike refereces to non-const values, which can only be initialized with non-const l-values, references to const values can be initialized with non-const l-value, const l-value and r-values.

int x = 5;
const int &ref1 = x;

const int y = 7;
const int &ref2 = y;

const int &ref3 = 6;

ref3 = 7; // illegal -- ref3 is const

Member selection with pointers and references

struct Person {
    int age;
    double weight;
}

Person person;
person.age = 5;

Person &ref = person;
ref.age = 5;

Person *ptr = &person;
(*ptr).age = 5;
ptr->age = 5;

Rule: When using a pointer to access the value of a member, use operator -> intstead of operator ..

For-each loops

for (element_declaration : array)
    statement;

For each loops and the auto keyword

int array[5] = { 9, 7, 5, 3, 1 };

for (auto element: array) // element will be a copy of the current array element
        std::cout << element << ' ';
     
for (auto &element: array) // element will be a referance to the actual array element, preventing a copy from being mode
        std::cout << element << ' ';   

Rule: Use references or const references for your element declaratioin in for-each loops for performance reasons.

For-each loops and non-arrays

For each loops privide a flexible and generic way to iterate through more than just arrays, there are vectors, linked l ists, trees and maps.

For-each doesn't work with pointers to an array

In order to iterate through the array, for-each needs to know how big the array is, which means knowing the array size. Because arrays that have decayed into a pointer do not know their size, for-each loops will not work with them.

Note: For-each was added in C++11

Void pointers

The void pointer, also known as the generic pointer, is a special type of pointer that can be pointed at objects of any data type.

void *ptr;// ptr is a void pointer
int nValue;
float fValue;

struct Something {

    int n;
    float f;
}

Something sValue;
void *ptr;
ptr = &nValue;
ptr = &fValue;
ptr = &sValue;

Because the void pointer does not know what type of object it is pointing to, it cannot be dereferenced directly. It must first be explicitly cast ot another pointer type before it is dereferenced.

int value = 5;
void *voidPtr = &value;

std::cout << *voidPtr << std::endl; // illegal: cannot dereference a void pointer

int *intPtr = static_cast<int*>(voidPtr);

std::cout << *intPtr << std::endl;

How to know what to cast a void pointer to? Ultimately, that is up to you to keep track of.

Void pointer miscellany

Void pointer can be set to a null value;

void *ptr = 0; // null pointer

It's not possible to do pointer arithmetic on a void pointer. Because pointer arithmetic requires the pointer to know what size object it is pointing to, so it can increment or decrement the pointer.

Note that there is no void reference, because a void reference would be of type void &, and would not know what type of value it referenced.

Pointers to pointers and dynamic multidimensional arrays

A pointer to a pointer is a pointer that holds the address of another pointer.

int *ptr; // pointer to an int
int **ptrptr; // pointer to a pointer to an int

An introduction to std::array

Note: std::array is introduced in C++11, which provides the functionality of C++'s built-in fixed arrays in a safer and more usable form.

Downsides of fixed and dynamic arrays:

  1. Fixed arrays decay into pointers, losing the array length information
  2. Dynamic arrays have messy deallocation issues and are chanlleging to resize without error

std::array provides fixed array functionality that won't decay when passed into a function, std::array is defined in the array header, inside the std namespace.

#include <array>

std::array<int, 3> myArray {9,7,5};
std::array<int, > myArray {9,7,5}; // illegal

myArray.at(0) = 6;

myArray.at(9) = 7; // will throw error

at() is slower(but safer) than operator [].

std::array will clean up after itself when it goes out of scope, so there's no need to do any kind of cleanup.

Size and sorting

std::array<double, 5> myArray { 9.0, 7.2, 5.4, 3.6, 1.8 };
std::cout << "length: " << myArray.size();


#include <iostream>
#include <array>
 
void printLength(const std::array<double, 5> &myArray)
{
    std::cout << "length: " << myArray.size();
}
 
int main()
{
    std::array<double, 5> myArray { 9.0, 7.2, 5.4, 3.6, 1.8 };
 
    printLength(myArray);
 
    return 0;
}

Rule: Always poss std::array by reference or const reference

Sorting

#include <iostream>
#include <algorithm> // for std::sort
#include <array> // for std::array

int main(int argc, const char * argv[]) {
    
    std::array<int, 6> myArray {11, 10, 90, 20, 11, 23};
    std::sort(myArray.begin(), myArray.end());
    
    for (auto element : myArray) {
        std::cout << element << std::endl;
    }
    return 0;
}

An introduction to std::vector

std::vector provides functionality that makes working with dynamic arrays safer and easier.

#include <vector>

std::vector<int> array;
std::vector<int> array2 = {9,7,5,3,1};

std::vector<int> array3 {9,7,5,3,1}; // C++11

Self-cleanup prevents memory leaks

When a vector variable goes out of scope, it automatically deallocates the memory it controls(if necessary).

Vectors remember their length

Unlike built-in dynamic arrays, which don't know the length of the array they are pointing to, std::vector keeps track of its length, we can ask for the vector's length via the size() function.

std::vector<int> array {9,7};
array.size();// prints 2

Resize an array

相关文章

网友评论

      本文标题:Chapter 6 Pointer

      本文链接:https://www.haomeiwen.com/subject/cdiamftx.html