Les structures étant toujours passées par valeur, on doit utiliser un pointeur quand on veut les passer par référence, que ce soit pour rendre plusieurs résultats ou pour modifier une structure sans devoir la copier, par souci d'efficacité. On procède de façon analogue aux types simples, en remplaçant le paramètre z par *z dans la définition de la fonction, et en passant l'adresse de la structure en argument à la fonction. Voici par exemple une procédure d'addition de deux complexes, avec utilisation des pointeurs :
void add3(complexe *z1, complexe *z2, complexe *z) {
(*z).re = (*z1).re + (*z2).re,
(*z).im = (*z1).im + (*z2).im;
}
Il est indispensable de placer des parenthèses autour de *z, etc, sans quoi *z.re serait analysé comme *(z.re), ce qui est incorrect comme type, car z.re est un double, pas un pointeur. On l'appellera ainsi :
complexe un = {3,0};
complexe I = {0,1};
complexe z;
add3(&un, &I, &z);
En fait, le troisième argument doit être passé par référence pour servir de résultat ; mais les deux premiers ne sont passés par référence que pour éviter qu'ils ne soient copiés, par simple souci d'efficacité. Pour éviter que z1 et z2 ne soient modifiés -- par erreur --, on peut spécifier dans la définition de la fonction qu'ils doivent être traités comme des constantes :
void add3(const complexe *z1, const complexe *z2, complexe *z);
En outre, le groupement (*z).re est tellement usuel (et ce placement obligatoire des parenthèses tellement agaçant) qu'il existe une abréviation : on écrit z->re au lieu de (*z).re. On écrira donc plutôt cette fonction :
void add3(const complexe *z1, const complexe *z2, complexe *z) {
z->re = z1->re + z2->re,
z->im = z1->im + z2->im;
}