Pointeurs en Go
Go conserve les pointeurs, garantissant dans une certaine mesure les performances, tout en limitant leur utilisation pour un meilleur GC et pour des raisons de sécurité.
Création
Il y a deux opérateurs couramment utilisés pour les pointeurs : l'opérateur d'adresse & et l'opérateur de déréférencement *. Prendre l'adresse d'une variable retournera un pointeur du type correspondant. Par exemple :
func main() {
num := 2
p := &num
fmt.Println(p)
}Le pointeur stocke l'adresse de la variable num :
0xc00001c088L'opérateur de déréférencement a deux utilisations : la première est d'accéder à l'élément pointé par le pointeur, c'est-à-dire le déréférencement. Par exemple :
func main() {
num := 2
p := &num
rawNum := *p
fmt.Println(rawNum)
}p est un pointeur, et le déréférencement du type pointeur permet d'accéder à l'élément pointé par le pointeur. L'autre utilisation est de déclarer un pointeur. Par exemple :
func main() {
var numPtr *int
fmt.Println(numPtr)
}<nil>*int signifie que le type de la variable est un pointeur vers un int. Cependant, un pointeur ne peut pas être simplement déclaré, il doit être initialisé. Il est nécessaire de lui allouer de la mémoire, sinon c'est un pointeur nul qui ne peut pas être utilisé normalement. Soit on utilise l'opérateur d'adresse pour assigner l'adresse d'une autre variable à ce pointeur, soit on utilise la fonction intégrée new pour allouer manuellement. Par exemple :
func main() {
var numPtr *int
numPtr = new(int)
fmt.Println(numPtr)
}On utilise plus souvent la variable courte :
func main() {
numPtr := new(int)
fmt.Println(numPtr)
}La fonction new n'a qu'un seul paramètre, le type, et retourne un pointeur du type correspondant. La fonction allouera de la mémoire pour ce pointeur, et le pointeur pointera vers la valeur zéro du type correspondant. Par exemple :
func main() {
fmt.Println(*new(string))
fmt.Println(*new(int))
fmt.Println(*new([5]int))
fmt.Println(*new([]float64))
}
0
[0 0 0 0 0]
[]Interdiction des opérations sur les pointeurs
En Go, les opérations sur les pointeurs ne sont pas prises en charge. Autrement dit, un pointeur ne peut pas être décalé. Commençons par un code C++ :
int main() {
int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
int *p = &arr[0];
cout << &arr << endl
<< p << endl
<< p + 1 << endl
<< &arr[1] << endl;
}0x31d99ff880
0x31d99ff880
0x31d99ff884
0x31d99ff884On peut voir que l'adresse du tableau est la même que l'adresse du premier élément du tableau, et qu'après une opération d'addition de 1 sur le pointeur, il pointe vers le deuxième élément du tableau. Les tableaux en Go fonctionnent de la même manière, sauf que les pointeurs ne peuvent pas être décalés. Par exemple :
func main() {
arr := [5]int{0, 1, 2, 3, 4}
p := &arr
println(&arr[0])
println(p)
// Tenter une opération sur le pointeur
p++
fmt.Println(p)
}Un tel programme ne passera pas la compilation. L'erreur est la suivante :
main.go:10:2: invalid operation: p++ (non-numeric type *[5]int)TIP
La bibliothèque standard unsafe fournit de nombreuses opérations pour la programmation de bas niveau, y compris les opérations sur les pointeurs. Consultez Bibliothèque standard - unsafe pour plus de détails.
new et make
Dans les sections précédentes, nous avons mentionné à plusieurs reprises les fonctions intégrées new et make. Elles sont similaires, mais différentes. Révisons-les.
func new(Type) *Type- La valeur de retour est un pointeur de type
- Le paramètre reçu est un type
- Dédié à l'allocation de mémoire pour les pointeurs
func make(t Type, size ...IntegerType) Type- La valeur de retour est une valeur, pas un pointeur
- Le premier paramètre reçu est un type, les paramètres de longueur variable varient selon le type transmis
- Dédié à l'allocation de mémoire pour les tranches, les maps et les canaux
Voici quelques exemples :
new(int) // pointeur int
new(string) // pointeur string
new([]int) // pointeur de tranche d'entiers
make([]int, 10, 100) // tranche d'entiers de longueur 10, capacité 100
make(map[string]int, 10) // map de capacité 10
make(chan int, 10) // canal avec tampon de taille 10