Go supports pointers. A pointer is a variable that stores the memory address of another variable where the data is stored, rather than storing a value itself. In this lesson, we will look at the use of pointers.
All variables in Go other than maps and slices are value types. What that means is that if you have a variable that you are passing to a function and you want to make changes to the variable outside of the function, you can't change the variable directly. A copy gets passed in whenever you send it to a function.
If you want to modify the variable, the general approach is to use a pointer to the memory address instead of trying to affect the variable itself. In other words, you can pass in a pointer to the address of the variable, rather than passing a copy of the value itself. When you pass in a pointer, both the original value and the value used in the function are pointing to the same part of memory. Because they're pointing to the same spot in memory, when you change one value, both variables are changed.
Pointers are also used to pass in larger variables. If you have a large struct, then your application would take time and memory to copy the struct and all of its fields over to a function when passed, whereas if you pass in a pointer, the function will just receive the memory address of the struct. The memory address is going to be much smaller than the struct, so the application will be more efficient.
The syntax used to define a pointer is:
var pointerName *dataType
The pointerName
is the name of the variable that will hold the pointer. The dataType
is the type that is associated with the value stored in the pointer. In Listing 10.1, you create two variables:
a
is a normal int
variable, to which you assign the value 20
.b
is an int
pointer, as designated by *int
.When you run the code, the output is:
20
<nil>
Nothing has been assigned to the b
variable, so it is empty. While Go does assign the default value of 0 to an int
variable, it does not assign any value at all to a pointer. As a result, you will see that it is shown to be equal to nil
.
Once you define a pointer, you can initialize it to point to the memory address of another variable by using the syntax:
pointerName = &variableName
In Listing 10.2, the pointer b
points to the memory address of variable a
.
Running the program now gives you this output:
20
0xc0000100b0
The second value is the hexadecimal address of the memory where the value for a
is stored. This value will likely be different each time you run the program, depending on available memory blocks.
You can also combine the definition and initialization of a pointer in one step. Listing 10.3 shows your pointer b
being declared and assigned the address of a
within one statement.
You again declare a
as an int
and store the value of 20
within it. You then declare b
as a pointer to an int
. Within the declaration, you then assign the address of a
to the pointer b
. When you run this listing, you see output similar to the previous listing. Again, the address printed in the second print statement will vary:
20
0xc0000aa058
You can also create a pointer variable using dynamic typing in the same way you can declare other types of variables. As you may recall, dynamic typing is when you create a variable and assign a value without specifying its type. Listing 10.4 illustrates dynamic typing, where variable b
is defined without a type. The compiler will infer the type based on the initial value.
This listing operates identically to the previous listings. The only difference is that the compiler will infer that b
is a pointer to an int
.
Up to this point, you've used a pointer to an int
. You can use pointers to any other data types in the exact manner, as illustrated in Listing 10.5.
This listing simply adds declarations to the previous listing to define variables of types float32
, string
, and uint
along with pointers to each. When you run this listing, you see that the values of the variables are printed, and each pointer variable has a unique address:
20
0xc000014098
-------------
10.3
0xc0000140b0
-------------
My string
0xc00003a230
-------------
42
0xc0000140b8
-------------
Looking at Listing 10.5, you can see that the type declared for each pointer matches the type being assigned. What happens if you try to assign a variable of one type to a pointer of a different type, such as trying to assign a variable of type float32
to a pointer to an int
? Modify Listing 10.5 to assign the value in the variable c
(a float32
value) to the pointer called h
, which is defined as a pointer to an uint
. What happens when you do this? Listing 10.6 shows this modification.
If you make the change and run the new listing, you will get an error:
# command-line-arguments
.Listing1006.go:16:9: cannot use &c (type *float32) as type *uint in assignment
While a pointer might hold an address, the value stored in the pointer needs to still match the defined type.
In order to access the value stored in the memory address stored in a pointer, you can use the *
operator (the dereferencing operator), as shown in Listing 10.7.
In this listing, a variable of type int
called myVar
is declared and set to the value of 20
. A second variable called b
is also declared and it is set to the address of myVar
. Because a type is not included when b
is declared, the compiler will dynamically type b
to be a pointer to type int
.
After your two variables are declared, two print statements are executed. You first print out the value of b
, which will be the memory address where myVar
is located. In the second print statement, you pass *b
instead of b
. Instead of the address being printed, the dereferencing operator provides the value stored in the variable called b
to be retrieved and printed, which is the value stored in myVar
. Based on this dereferencing, the output this time looks like this:
0xc0000100b0
20
You saw earlier that when we define a pointer without initializing it, it contains a nil
value by default. Listing 10.8 declares a pointer but does not initialize it.
The output for this program is:
<nil>
The nil
value means that the pointer itself is empty and contains no real value. In other words, it doesn't point to anything. What would happen if you added a print statement to dereference the value stored in b
?
fmt.Println(*b)
If you try to access the value in a nil
pointer, you will get an error similar to the following:
panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xc0000005 code=0x0 addr=0x0 pc=0x2ec5d5]
To avoid this error, you can use an if
statement to verify that a pointer doesn't equal nil. Listing 10.9 checks to see if the pointer called b
is nil
before trying to access its value.
You can see that to make sure b
has a value, it is simply compared to the Go keyword nil
. If b
is equal to nil
, then you know it doesn't point to anything, so you can avoid doing something that would cause an error.
Because the dereferencing operator (*
) assigns a variable's value to a pointer, you can also use it to assign new values to the variables themselves. Listing 10.10 illustrates this.
In this listing, you assign a value of 20
to the variable a
. You create a variable called b
as a pointer to a
. The listing prints a
, b
(the memory address), and *b
(the value stored at the address stored in b
, which is the value of a
).
After printing these values, the following line is executed:
*b = 30
This statement changes the value stored at the memory address in b
. As a result of using *
on the pointer variable b
, you change the value assigned to a
. You can confirm this by reviewing the output from the listing:
The value stored in a: 20
Memory address: 0xc0000ae058
Value stored in a (via pointer): 20
New value of a: 30
Again, the memory address will be different, but you can see that the value of a
was changed from 20
to 30
.
You can use the ==
operator to check if two pointers are equal. Pointers are equal if they point to the same storage location, which typically implies that they point to the same variable. In Listing 10.11, you create two pointers that refer to the variable a
, as well as a nil
pointer.
Both b
and c
are declared as pointers that contain the address of a
. You also declare a pointer called x
as a pointer to an int
. You do not assign x
a value, so it will contain nil
.
You then compare the pointers to each other. First, you compare c
to b
. You then compare x
to b
. The output is:
Is c == b? true
Is x == b? false
In the previous lesson, you learned about arrays. In Go, you can also create an array of pointers. Each element of such an array contains an address to a value stored in memory. You can use an array like this in the same way you use traditional arrays. Listing 10.12 illustrates the use of an array of pointers.
In this example, you create an int array called numbers
that contains three elements: 100
, 1000
, and 10000
. You then use a for
loop to print these values.
You then create a second array called numbersptr
that holds three pointers to variables of type int
:
var numbersptr [3]*int
You use a second for
loop to assign a pointer to each value in the original array. This assignment is done using the same index offset:
numbersptr[ctr] = &numbers[ctr]
Once these assignments are done, you print the new array, which displays the addresses of the pointers in your numbersptr
array.
Finally, you print the individual values the pointers in the array point to. You do this by using the *
operator. The output from the complete listing is:
100
1000
10000
[0xc00000a3c0 0xc00000a3c8 0xc00000a3d0]
100
1000
10000
You can manipulate the values of an array using an array of pointers. The code in Listing 10.13 takes the previous example and refactors it to use the pointer to update a value in the original array.
Most of this listing is the same as the previous one. However, instead of printing the individual elements of the array of pointers, you assign the value of 200
to the dereferenced pointer in the first element of the numbersptr
(position 0):
*numbersptr[0]=200
You can then see in the output that the value stored in the first element of the numbers
array has indeed changed from 100
to 200
:
100
1000
10000
[0xc00000a3c0 0xc00000a3c8 0xc00000a3d0]
[200 1000 10000]
You can pass pointers to functions. By passing a pointer to a function, you are able to access and use the value stored in the variable the pointer references. Listing 10.14 includes a function called isupper
that accepts a pointer to a string as input and returns true
if the string is in uppercase and false
otherwise.
Here, instead of passing the message
variable directly to the function, the isupper
function is set up to accept a pointer to that variable, as indicated by the *string
argument. Because the original string stored in message
is in all caps, the program returns true
.
As a side note, this listing imports strings
, which includes routines for working with and manipulating strings. In this case, the strings.ToUpper
method is used, which takes a string as a parameter and returns a copy of the string in all uppercase letters. For this listing, the copy of the string in all uppercase is then compared to the original string (*x
) to see if they are the same.
We mentioned before that variables are passed by value to a function, which means a copy of the variable is passed. You just saw that by passing a pointer to a function, you are able to access and use the value of a variable. The previous example retrieved a value stored in memory without changing that value. However, as you have seen, you can also use pointers to change the value stored in a variable.
Listing 10.15 converts a string to uppercase. The function (called upper
in this listing) is again defined to use a pointer as an argument rather than the variable's value itself. This time, however, the function replaces the variable's value with an uppercase version of the original string.
In this code, you initialize message
with a string in lowercase. You then use messageptr
in the upper
function to convert the string to uppercase. The final output (HELLO WORLD
) shows that the variable's value has been changed, rather than just presenting the original string in uppercase.
In this lesson you learned about pointers, which are variables that contain the memory address of another variable or nil
if the pointer is unassigned. Not only did you learn how to declare and initialize pointers, but you also saw how to use them to access and manipulate the values in other variables. You even went further and saw how to use pointers in arrays as well as how to use them with functions. You learned that by passing a pointer to a function, you are able to change values in variables outside of the function.
The following exercises are provided to allow you to experiment with the tools and concepts presented in this lesson. For each exercise, write a program that meets the specified requirements and verify that the program runs as expected. The exercises are:
Create a program that contains three variables: name
, age
, and gender
. Assign values to each. Make each of these variables a different data type. Create pointers that point to each variable. Use fmt.Println
statements to print the values from each of the three variables both by using the original variables and by dereferencing the pointers.
In a previous lesson, you prompted the user to enter values. You used the fmt.Scanln
function for this. Enter and run Listing 10.16, which prompts the user to enter a name, age, and gender:
After running the listing, change it to use pointers to each of the three variables instead of directly passing the address of each.
Enter and run the program in Listing 10.14 if you haven't already. Make the following changes to the listing and review what happens. Try to predict what will happen before making the changes.
message
to be "Hello World"
. What is the output?"HELLO 123 WORLD"
. What is the output?message
instead of messageptr
in the last line of code?fmt.Println(isupper(message))
Create a function that takes three parameters. The first should be a pointer to a string variable that will hold a full name. The second and third parameters should be strings that contain a first name and a last name, respectively. The function should first combine the first and last name strings with a space between to create a full name, which should be applied to the variable in the location of the pointer passed to the function.
In addition to writing the function, create a main program that creates the full name variable and a pointer to the full name. Pass these along with first and last name strings to the function. After calling the function, print the full name.
Take a look at Listing 10.17. Most of this code is similar to what you saw in this lesson. Without running the code, what do the last three statements in the listing do?
Expand the listing in Exercise 10.5 so that you use the pointers to change the order of the array. Using the pointers, reverse the values so that when the array is displayed, the results are:
[444 666 888 555 999 222 777 333 111 123]
Rewrite the listing in Exercise 10.6 so that you use the pointers to change the order of the array. This time, use the pointers and the values to which they point to sort the list from highest to lowest.