Näpunäited C-s Selgitatud - nad pole nii rasked kui arvate

Osutajad on C-le vaieldamatult kõige raskemini mõistetav omadus. Kuid need on üks omadusi, mis teevad C-st suurepärase keele.

Selles artiklis läheme viidete põhitõdedest nende kasutamiseni massiivide, funktsioonide ja struktuuriga.

Nii et lõõgastuge, haarake kohvi ja valmistuge õppima kõike näpunäiteid.

Teemad

A. Põhialused
  1. Mis on näpunäited täpsemalt?
  2. Definitsioon ja tähistus
  3. Mõned erilised näpunäited
  4. Kursori aritmeetika
B. Massiivid ja stringid
  1. Miks osutid ja massiivid?
  2. 1-D massiivid
  3. 2-D massiivid
  4. Keelpillid
  5. Näidikute massiiv
  6. Märkija massiivile
C. Funktsioonid
  1. Kõne väärtusega v / s Kõne viite järgi
  2. Kursorid kui funktsiooni argumendid
  3. Kursorid funktsioonina naasevad
  4. Funktsioonide osutaja
  5. Funktsioonidele osutavate märkide massiiv
  6. Funktsioonina osutaja kui argument
D. Struktuur
  1. Struktuuri osuti
  2. Struktuuri massiiv
  3. Kursor struktuurile kui argument
E. Pointer pointerile
F. Järeldus

A. Definitsioon, tähistus, tüübid ja aritmeetika

1. Mis on näpunäited täpsemalt?

Enne kui osutame näpunäidete määratlusele, mõistkem, mis juhtub järgmise koodi kirjutamisel:

int digit = 42; 

intVäärtuse hoidmiseks reserveerib kompilaator mäluploki . Selle ploki nimi on digitja selles plokis salvestatud väärtus on 42.

Nüüd, ploki mäletamiseks, määratakse sellele aadress või asukohanumber (näiteks 24650).

Asukohanumbri väärtus pole meie jaoks oluline, kuna see on juhuslik väärtus. Kuid sellele aadressile pääseme juurde operaatori &(ampersand) või aadressi abil .

printf("The address of digit = %d.",&digit); /* prints "The address of digit = 24650. */ 

Muutuja väärtuse saame digitselle aadressilt, kasutades teist operaatorit *(tärni), mida nimetatakse operaatoriks indirection või dereferencing või value at address .

printf("The value of digit = %d.", *(&digit); /* prints "The value of digit = 42. */ 

2. Definitsioon ja tähistamine

Muutuja aadressi saab salvestada teise muutujana, mida nimetatakse osuti muutujaks. Muutuja aadressi kursorisse salvestamise süntaks on järgmine:

dataType *pointerVariableName = &variableName; 

Meie digitmuutuja jaoks saab seda kirjutada järgmiselt:

int *addressOfDigit = &digit; 

või selline:

int *addressOfDigit; addressOfDigit= &digit; 

Deklaratsioon ja määratlus

Seda võib lugeda järgmiselt: muutuja salvestab kursor int(täisarv) .addressOfDigitaddress of(&)digit

Mõni punkt mõistmiseks:

dataType- Peame arvutile ütlema, mis on muutuja andmetüüp, mille aadressi me salvestame. Siin intoli andmetüüp digit.

See ei tähenda, et addressOfDigitsee salvestaks tüübi väärtuse int. Täisarvukursor (nagu addressOfDigit) saab salvestada ainult täisarvutüüpi muutujate aadressi.

int variable1; int variable2; char variable3; int *addressOfVariables; 

*- Pointer muutuja on eriline muutuja selles mõttes, et seda kasutatakse salvestada aadress teise muutuja. Selle eristamiseks muudest muutujatest, mis aadressi ei salvesta, kasutame *deklaratsioonis sümbolina.

Siin saame omistada aadressi variable1ja variable2täisarvukursori, addressOfVariableskuid mitte sellele, variable3kuna see on tüüp char. Selle aadressi salvestamiseks vajame tähemärkide muutujat.

Saame kasutada addressOfDigitkursori muutujat aadressi ja väärtuse printimiseks digitjärgmiselt:

printf("The address of digit = %d.", addressOfDigit); /* prints "The address of digit = 24650." */ printf("The value of digit = %d.", *addressOfDigit); /*prints "The value of digit = 42. */ 

Siin *addressOfDigitsaab lugeda väärtuseks aadressil salvestatud aadressil addressOfDigit.

Teatis me kasutasime %dkui formaadi tunnuseks eest addressOfDigit. Noh, see pole täiesti õige. Õige identifikaator oleks %p.

Kasutades %pkuvatakse aadress kuueteistkümnendsüsteemis. Kuid mäluaadressi saab kuvada nii täisarvude kui ka kaheksandväärtustena. Siiski, kuna see pole täiesti õige viis, kuvatakse hoiatus.

int num = 5; int *p = # printf("Address using %%p = %p",p); printf("Address using %%d = %d",p); printf("Address using %%o = %o",p); 

Kasutatava kompilaatori väljund on järgmine:

Address using %p = 000000000061FE00 Address using %d = 6422016 Address using %o = 30377000 
See on hoiatus, mida näidatakse, kui kasutate% d - "hoiatus: formaat '% d' ootab argumenti tüüpi 'int', kuid argumendil 2 on tüüp 'int *'".

3. Mõned erilised näpunäited

Metsik Pointer

char *alphabetAddress; /* uninitialised or wild pointer */ char alphabet = "a"; alphabetAddress = &alphabet; /* now, not a wild pointer */ 

Kui määratlesime oma tähemärgi alphabetAddress, ei lähtestanud me seda.

Selliseid osutajaid nimetatakse metsikuteks osutajateks . Nad salvestavad baidi prügiväärtuse (see tähendab mäluaadress), mida me ei tea, et see on reserveeritud või mitte (pidage meeles int digit = 42;, et me deklareerisime mäluaadressi).

Oletame, et me eristame metsikut kursorit ja määrame väärtuse mälu aadressile, millele see osutab. See toob kaasa ootamatu käitumise, kuna kirjutame mäluplokki andmed, mis võivad olla vabad või reserveeritud.

Nullpointer

Veendumaks, et meil pole metsikut kursorit, saame initsialiseerida kursori NULLväärtusega, muutes selle nullkursoriks .

char *alphabetAddress = NULL /* Null pointer */ 

Nullkursor ei osuta millelegi või mäluaadressile, millele kasutajad ei pääse.

Tühine osuti

Void kursorit saab kasutada punktis muutuva tahes andmete liiki. Seda saab taaskasutada, et osutada igale soovitud andmetüübile. See on kuulutatud järgmiselt:

void *pointerVariableName = NULL; 

Kuna need on oma olemuselt väga üldised , on nad tuntud ka kui üldised näpunäited .

Oma paindlikkusega toovad tühised näpunäited ka teatud piiranguid. Tühiseid näpunäiteid ei saa eristada nagu ühegi teise osuti puhul. Vajalik on asjakohane tüübiprotseduur .

void *pointer = NULL; int number = 54; char alphabet = "z"; pointer = &number; printf("The value of number = ", *pointer); /* Compilation Error */ /* Correct Method */ printf("The value of number = ", *(int *)pointer); /* prints "The value at number = 54" */ pointer = &alphabet; printf("The value of alphabet = ", *pointer); /* Compilation Error */ printf("The value of alphabet = ", *(char *)pointer); /* prints "The value at alphabet = z */ 

Samamoodi tuleb tühjad osutajad aritmeetiliste toimingute tegemiseks ette näha.

Tühjad näpunäited on C. Raamatukogu funktsioonides väga kasulikud malloc()ja calloc()need eraldavad mälu dünaamiliselt tühiseid näpunäiteid. qsort()C-s sisseehitatud sortimisfunktsioonil on argumendina funktsioon, mis ise võtab argumendiks tühised näpunäited.

Rippuv osuti

Rippuv osuti osutab mäluaadressile, mis varem kasutas muutujat. Kuna aadressi, millele ta osutab, pole enam reserveeritud, toob selle kasutamine ootamatuid tulemusi.

main(){ int *ptr; ptr = (int *)malloc(sizeof(int)); *ptr = 1; printf("%d",*ptr); /* prints 1 */ free(ptr); /* deallocation */ *ptr = 5; printf("%d",*ptr); /* may or may not print 5 */ } 

Ehkki mälu on lahendatud free(ptr), osutab terviku arvu osundaja ptrsellele reserveerimata mäluaadressile.

4. Kursori aritmeetika

Nüüdseks teame, et osutid pole nagu ükski teine ​​muutuja. Nad ei salvesta muud väärtust kui mäluplokide aadressi.

Seega peaks olema üsna selge, et kõik aritmeetilised toimingud ei kehti nende puhul. Kas kahe näpunäite korrutamisel või jagamisel ( millel on aadressid ) oleks mõtet?

Osutajatel on vähe, kuid tohutult kasulikke kehtivaid toiminguid:

  1. Ühe osuti väärtuse saate teisele omistada ainult siis, kui see on sama tüüpi (välja arvatud juhul, kui see on tüüpvärviga või üks neist on void *).
int ManU = 1; int *addressOfManU = &ManU; int *anotherAddressOfManU = NULL; anotherAddressOfManU = addressOfManU; /* Valid */ double *wrongAddressOfManU = addressOfManU; /* Invalid */ 

2. Kursoreid saate lisada või lahutada ainult täisarvudest .

int myArray = {3,6,9,12,15}; int *pointerToMyArray = &myArray[0]; pointerToMyArray += 3; /* Valid */ pointerToMyArray *= 3; /* Invalid */ 

Kui lisate (või lahutate) osuti täisarvu (ütleme n), ei lisata (ega lahutata) kursori väärtusele n baidi. Tegelikult lisate (või lahutate) n- kordse baitidena muutuja andmetüübi suuruse .

int number = 5; /* Suppose the address of number is 100 */ int *ptr = &number; int newAddress = ptr + 3; /* Same as ptr + 3 * sizeof(int) */ 

Salvestatud väärtus newAddressei ole pigem 103 112.

3.   Näitajate lahutamine ja võrdlemine kehtib ainult siis, kui mõlemad kuuluvad samasse massiivi. Kursorite lahutamine annab neid eraldavate elementide arvu.

int myArray = {3,6,9,12,15}; int sixthMultiple = 18; int *pointer1 = &myArray[0]; int *pointer2 = &myArray[1]; int *pointer6 = &sixthMuliple; /* Valid Expressions */ if(pointer1 == pointer2) pointer2 - pointer1; /* Invalid Expressions if(pointer1 == pointer6) pointer2 - pointer6 

4. Saate osuti määrata või sellega võrrelda NULL.

Ainus erand ülaltoodud reeglitest on see, et massiivi viimase elemendi järel oleva esimese mäluploki aadress järgib kursori aritmeetikat.

Kursor ja massiivid eksisteerivad koos. Need kursoritega kehtivad manipulatsioonid on tohutult kasulikud massiividega, mida käsitletakse järgmises osas.

B. Massiivid ja stringid

1. Miks osutid ja massiivid?

C-s on osutitel ja massiividel üsna tugev suhe.

The reason they should be discussed together is because what you can achieve with array notation (arrayName[index]) can also be achieved with pointers, but generally faster.

2. 1-D Arrays

Let us look at what happens when we write int myArray[5];.

Five consecutive blocks of memory starting from myArray[0] to myArray[4] are created with garbage values in them. Each of the blocks is of size 4 bytes.

Thus, if the address of myArray[0] is 100 (say), the address of the rest of the blocks would be 104, 108, 112, and 116.

Have a look at the following code:

int prime[5] = {2,3,5,7,11}; printf("Result using &prime = %d\n",&prime); printf("Result using prime = %d\n",prime); printf("Result using &prime[0] = %d\n",&prime[0]); /* Output */ Result using &prime = 6422016 Result using prime = 6422016 Result using &prime[0] = 6422016 

So, &prime, prime, and &prime[0] all give the same address, right? Well, wait and read because you are in for a surprise (and maybe some confusion).

Let's try to increment each of &prime, prime, and &prime[0] by 1.

printf("Result using &prime = %d\n",&prime + 1); printf("Result using prime = %d\n",prime + 1); printf("Result using &prime[0] = %d\n",&prime[0] + 1); /* Output */ Result using &prime = 6422036 Result using prime = 6422020 Result using &prime[0] = 6422020 

Wait! How come &prime + 1 results in something different than the other two? And why are prime + 1 and &prime[0] + 1 still equal? Let's answer these questions.

prime and &prime[0] both point to the 0th element of the array prime. Thus, the name of an array is itself a pointer to the 0th element of the array.

Here, both point to the first element of size 4 bytes. When you add 1 to them, they now point to the 1st element in the array. Therefore this results in an increase in the address by 4.

&prime, on the other hand, is a pointer to an int array of size 5. It stores the base address of the array prime[5], which is equal to the address of the first element. However, an increase by 1 to it results in an address with an increase of 5 x 4 = 20 bytes.

In short, arrayName and &arrayName[0] point to the 0th element whereas &arrayName points to the whole array.

1-D massiiv

We can access the array elements using subscripted variables like this:

int prime[5] = {2,3,5,7,11}; for( int i = 0; i < 5; i++) { printf("index = %d, address = %d, value = %d\n", i, &prime[i], prime[i]); } 

We can do the same using pointers which are always faster than using subscripts.

int prime[5] = {2,3,5,7,11}; for( int i = 0; i < 5; i++) { printf("index = %d, address = %d, value = %d\n", i, prime + i, *(prime + i)); } 

Both methods give the output:

index = 0, address = 6422016, value = 2 index = 1, address = 6422020, value = 3 index = 2, address = 6422024, value = 5 index = 3, address = 6422028, value = 7 index = 4, address = 6422032, value = 11 

Thus, &arrayName[i] and arrayName[i] are the same as arrayName + i and  *(arrayName + i), respectively.

3. 2-D Arrays

Two-dimensional arrays are an array of arrays.

int marks[5][3] = { { 98, 76, 89}, { 81, 96, 79}, { 88, 86, 89}, { 97, 94, 99}, { 92, 81, 59} }; 

Here, marks can be thought of as an array of 5 elements, each of which is a one-dimensional array containing 3 integers. Let us work through a series of programs to understand different subscripted expressions.

printf("Address of whole 2-D array = %d\n", &marks); printf("Addition of 1 results in %d\n", &marks +1); /* Output */ Address of whole 2-D array = 6421984 Addition of 1 results in 6422044 

Like 1-D arrays, &marks points to the whole 2-D array, marks[5][3]. Thus, incrementing to it by 1 ( = 5 arrays X 3 integers each X 4 bytes = 60) results in an increment by 60 bytes.

printf("Address of 0th array = %d\n", marks); printf("Addition of 1 results in %d\n", marks +1); printf("Address of 0th array =%d\n", &marks[0]); printf("Addition of 1 results in %d\n", &marks[0] + 1); /* Output */ Address of 0th array = 6421984 Addition of 1 results in 6421996 Address of 0th array = 6421984 Addition of 1 results in 6421996 

If marks was a 1-D array, marks and &marks[0] would have pointed to the 0th element. For a 2-D array, elements are now 1-D arrays. Hence, marks and &marks[0] point to the 0th array (element), and the addition of 1 point to the 1st array.

printf("Address of 0th element of 0th array = %d\n", marks[0]); printf("Addition of 1 results in %d\n", marks[0] + 1); printf("Address of 0th element of 1st array = %d\n", marks[1]); printf("Addition of 1 results in %d\n", marks[1] + 1); /* Output */ Address of 0th element of 0th array = 6421984 Addition of 1 results in 6421988 Address of 0th element of 1st array = 6421996 Addition of 1 results in 6422000 

And now comes the difference. For a 1-D array, marks[0] would give the value of the 0th element. An increment by 1 would increase the value by 1.

But, in a 2-D array, marks[0] points to the 0th element of the 0th array. Similarly, marks[1] points to the 0th element of the 1st array. An increment by 1 would point to the 1st element in the 1st array.

printf("Value of 0th element of 0th array = %d\n", marks[0][0]); printf("Addition of 1 results in %d", marks[0][0] + 1); /* Output */ Value of 0th element of 0th array = 98 Addition of 1 results in 99 

This is the new part. marks[i][j] gives the value of the jth element of the ith array. An increment to it changes the value stored at marks[i][j]. Now, let us try to write marks[i][j] in terms of pointers.

We know marks[i] + j would point to the ith element of the jth array from our previous discussion. Dereferencing it would mean the value at that address. Thus, marks[i][j] is the same as  *(marks[i] + j).

From our discussion on 1-D arrays, marks[i] is the same as *(marks + i). Thus, marks[i][j] can be written as *(*(marks + i) + j) in terms of pointers.

Here is a summary of notations comparing 1-D and 2-D arrays.

Expression 1-D Array 2-D Array
&arrayName points to the address of whole array

adding 1 increases the address by 1 x sizeof(arrayName)

points to the address of whole array

adding 1 increases the address by 1 x sizeof(arrayName)

arrayName points to the 0th element

adding 1 increases the address to 1st element

points to the 0th element (array)

adding 1 increases the address to 1st element (array)

&arrayName[i] points to the the ith element

adding 1 increases the address to (i+1)th element

points to the ith element (array)

adding 1 increases the address to the (i+1)th element (array)

arrayName[i] gives the value of the ith element

adding 1 increases the value of the ith element

points to the 0th element of the ith array

adding 1 increases the address to 1st element of the ith array

arrayName[i][j] Nothing gives the value of the jth element of the ith array

adding 1 increases the value of the jth element of the ith array

Pointer Expression To Access The Elements *( arrayName + i) *( *( arrayName + i) + j)

4. Strings

A string is a one-dimensional array of characters terminated by a null(\0). When we write char name[] = "Srijan";, each character occupies one byte of memory with the last one always being \0.

Similar to the arrays we have seen, name and &name[0] points to the 0th character in the string, while &name points to the whole string. Also, name[i] can be written as *(name + i).

/* String */ char champions[] = "Liverpool"; printf("Pointer to whole string = %d\n", &champions); printf("Addition of 1 results in %d\n", &champions + 1); /* Output */ Address of whole string = 6421974 Addition of 1 results in 6421984 printf("Pointer to 0th character = %d\n", &champions[0]); printf("Addition of 1 results in %d\n", &champions[0] + 1); /* Output */ Address of 0th character = 6421974 Addition of 1 results in a pointer to 1st character 6421975 printf("Pointer to 0th character = %d\n", champions); printf("Addition of 1 results in a pointer to 1st character %d\n", champions + 1); /* Output */ Address of 0th character = 6421974 Addition of 1 results in 6421975 printf("Value of 4th character = %c\n", champions[4]); printf("Value of 4th character using pointers = %c\n", *(champions + 4)); /* Output */ Value of 4th character = r Value of 4th character using pointers = r 

A two-dimensional array of characters or an array of strings can also be accessed and manipulated as discussed before.

/* Array of Strings */ char top[6][15] = { "Liverpool", "Man City", "Man United", "Chelsea", "Leicester", "Tottenham" }; printf("Pointer to 2-D array = %d\n", &top); printf("Addition of 1 results in %d\n", &top + 1); /* Output */ Pointer to 2-D array = 6421952 Addition of 1 results in 6422042 printf("Pointer to 0th string = %d\n", &top[0]); printf("Addition of 1 results in %d\n", &top[0] + 1); /* Output */ Pointer to 0th string = 6421952 Addition of 1 results in 6421967 printf("Pointer to 0th string = %d\n", top); printf("Addition of 1 results in %d\n", top + 1); /* Output */ Pointer to 0th string = 6421952 Addition of 1 results in 6421967 printf("Pointer to 0th element of 4th string = %d\n", top[4]); printf("Pointer to 1st element of 4th string = %c\n", top[4] + 1); /* Output */ Pointer to 0th element of 4th string = 6422012 Pointer to 1st element of 4th string = 6422013 printf("Value of 1st character in 3rd string = %c\n", top[3][1]); printf("Same using pointers = %c\n", *(*(top + 3) + 1)); /* Output */ Value of 1st character in 3rd string = h Same using pointers = h 

5. Array of Pointers

Like an array of ints and an array of chars, there is an array of pointers as well. Such an array would simply be a collection of addresses. Those addresses could point to individual variables or another array as well.

The syntax for declaring a pointer array is the following:

dataType *variableName[size]; /* Examples */ int *example1[5]; char *example2[8]; 

Following the operators precedence, the first example can be read as -  example1 is an array([]) of 5 pointers to int. Similarly, example2 is an array of 8 pointers to char.

We can store the two-dimensional array to string top using a pointer array and save memory as well.

char *top[] = { "Liverpool", "Man City", "Man United", "Chelsea", "Leicester", "Tottenham" }; 

top will contain the base addresses of all the respective names. The base address of "Liverpool" will be stored in top[0], "Man City" in top[1], and so on.

In the earlier declaration, we required 90 bytes to store the names. Here, we only require ( 58 (sum of bytes of names) + 12 ( bytes required to store the address in the array) ) 70 bytes.

The manipulation of strings or integers becomes a lot easier when using an array of pointers.

If we try to put "Leicester" ahead of "Chelsea", we just need to switch the values of top[3] and top[4] like below:

char *temporary; temporary = top[3]; top[3] = top[4]; top[4] = temporary; 

Without pointers, we would have to exchange every character of the strings, which would have taken more time. That's why strings are generally declared using pointers.

6. Pointer to Array

Like "pointer to int" or "pointer to char", we have pointer to array as well. This pointer points to whole array rather than its elements.

Remember we discussed how &arrayName points to the whole array? Well, it is a pointer to array.

A pointer to array can be declared like this:

dataType (*variableName)[size]; /* Examples */ int (*ptr1)[5]; char (*ptr2)[15]; 

Notice the parentheses. Without them, these would be an array of pointers. The first example can be read as - ptr1 is a pointer to an array of 5 int(integers).

int goals[] = { 85,102,66,69,67}; int (*pointerToGoals)[5] = &goals; printf("Address stored in pointerToGoals %d\n", pointerToGoals); printf("Dereferncing it, we get %d\n",*pointerToGoals); /* Output */ Address stored in pointerToGoals 6422016 Dereferencing it, we get 6422016 

When we dereference a pointer, it gives the value at that address. Similarly, by dereferencing a pointer to array, we get the array and the name of the array points to the base address. We can confirm that *pointerToGoals gives the array goals if we find its size.

printf("Size of goals[5] = %d, *pointerToGoals); /* Output */ Size of goals[5] = 20 

If we dereference it again, we will get the value stored in that address. We can print all the elements using pointerToGoals.

for(int i = 0; i < 5; i++) printf("%d ", *(*pointerToGoals + i)); /* Output */ 85 102 66 69 67 

Pointers and pointer to arrays are quite useful when paired up with functions. Coming up in the next section!

C. Functions

1. Call by Value vs Call by Reference

Have a look at the program below:

#include  int multiply(int x, int y){ int z; z = x * y; return z; } main(){ int x = 3, y = 5; int product = multiply(x,y); printf("Product = %d\n", product); /* prints "Product = 15" */ } 

The function multiply() takes two int arguments and returns their product as int.

In the function call multiply(x,y), we passed the value of x and y ( of main()), which are actual arguments, to multiply().

The values of the actual arguments are passed or copied to the formal argumentsx and y ( of multiply()). The x and y of multiply() are different from those of main(). This can be verified by printing their addresses.

#include  int multiply(int x, int y){ printf("Address of x in multiply() = %d\n", &x); printf("Address of y in multiply() = %d\n", &y); int z; z = x * y; return z; } main(){ int x = 3, y = 5; printf("Address of x in main() = %d\n", &x); printf("Address of y in main() = %d\n", &y); int product = multiply(x,y); printf("Product = %d\n", product); } /* Output */ Address of x in main() = 6422040 Address of y in main() = 6422036 Address of x in multiply() = 6422000 Address of y in multiply() = 6422008 Product = 15 

Since we created stored values in a new location, it costs us memory. Wouldn't it be better if we could perform the same task without wasting space?

Call by reference helps us achieve this. We pass the address or reference of the variables to the function which does not create a copy. Using the dereferencing operator *, we can access the value stored at those addresses.

We can rewrite the above program using call by reference as well.

#include  int multiply(int *x, int *y){ int z; z = (*x) * (*y); return z; } main(){ int x = 3, y = 5; int product = multiply(&x,&y); printf("Product = %d\n", product); /* prints "Product = 15" */ } 

2. Pointers as Function Arguments

In this section, we will look at various programs where we give int, char, arrays and strings as arguments using pointers.

#include  void add(float *a, float *b){ float c = *a + *b; printf("Addition gives %.2f\n",c); } void subtract(float *a, float *b){ float c = *a - *b; printf("Subtraction gives %.2f\n",c); } void multiply(float *a, float *b){ float c = *a * *b; printf("Multiplication gives %.2f\n",c); } void divide(float *a, float *b){ float c = *a / *b; printf("Division gives %.2f\n",c); } main(){ printf("Enter two numbers :\n"); float a,b; scanf("%f %f",&a,&b); printf("What do you want to do with the numbers?\nAdd : a\nSubtract : s\nMultiply : m\nDivide : d\n"); char operation = '0'; scanf(" %c",&operation); printf("\nOperating...\n\n"); switch (operation) { case 'a': add(&a,&b); break; case 's': subtract(&a,&b); break; case 'm': multiply(&a,&b); break; case 'd': divide(&a,&b); break; default: printf("Invalid input!!!\n"); } } 

We created four functions, add(), subtract(), multiply() and divide() to perform arithmetic operations on the two numbers a and b.

The address of a and b was passed to the functions. Inside the function using * we accessed the values and printed the result.

Similarly, we can give arrays as arguments using a pointer to its first element.

#include  void greatestOfAll( int *p){ int max = *p; for(int i=0; i  max) max = *(p+i); } printf("The largest element is %d\n",max); } main(){ int myNumbers[5] = { 34, 65, -456, 0, 3455}; greatestOfAll(myNumbers); /* Prints :The largest element is 3455" */ } 

Since the name of an array itself is a pointer to the first element, we send that as an argument to the function greatestOfAll(). In the function, we traverse through the array using loop and pointer.

#include  #include  void wish(char *p){ printf("Have a nice day, %s",p); } main(){ printf("Enter your name : \n"); char name[20]; gets(name); wish(name); } 

Here, we pass in the string name to wish() using a pointer and print the message.

3. Pointers as Function Return

#include  int* multiply(int *a, int *b){ int c = *a * *b; return &c; } main(){ int a= 3, b = 5; int *c = multiply (&a,&b); printf("Product = %d",*c); } 

The function multiply() takes two pointers to int. It returns a pointer to int as well which stores the address where the product is stored.

It is very easy to think that the output would be 15. But it is not!

When multiply() is called, the execution of main() pauses and memory is now allocated for the execution of multiply(). After its execution is completed, the memory allocated to multiply() is deallocated.

Therefore, though c ( local to main()) stores the address of the product, the data there is not guaranteed since that memory has been deallocated.

So does that mean pointers cannot be returned by a function? No!

We can do two things. Either store the address in the heap or global section or declare the variable to be static so that their values persist.

Static variables can simply be created by using the keywordstatic before data type while declaring the variable.

To store addresses in heap, we can use library functions malloc() and calloc() which allocate memory dynamically.

The following programs will explain both the methods. Both methods return the output as 15.

#include  #include  /* Using malloc() */ int* multiply(int *a, int *b){ int *c = malloc(sizeof(int)); *c = *a * *b; return c; } main(){ int a= 3, b = 5; int *c = multiply (&a,&b); printf("Product = %d",*c); } /* Using static keyword */ #include  int* multiply(int *a, int *b){ static int c; c = *a * *b; return &c; } main(){ int a= 3, b = 5; int *c = multiply (&a,&b); printf("Product = %d",*c); } 

4. Pointer to Function

Like pointer to different data types, we also have a pointer to function as well.

A pointer to function or function pointer stores the address of the function. Though it doesn't point to any data. It points to the first instruction in the function.

The syntax for declaring a pointer to function is:

 /* Declaring a function */ returnType functionName(parameterType1, pparameterType2, ...); /* Declaring a pointer to function */ returnType (*pointerName)(parameterType1, parameterType2, ...); pointerName = &functionName; /* or pointerName = functionName; */ 

The below example will make it clearer.

int* multiply(int *a, int *b) { int *c = malloc(sizeof(int)); *c = *a * *b; return c; } main() { int a=3,b=5; int* (*p)(int*, int*) = &multiply; /* or int* (*p)(int*, int*) = multiply; */ int *c = (*p)(&a,&b); /* or int *c = p(&a,&b); */ printf("Product = %d",*c); } 

The declaration for the pointer p to function multiply() can be read as ( following operator precedence) - p is a pointer to function with two integer pointers ( or two pointers to int) as parameters and returning a pointer to int.

Since the name of the function is also a pointer to the function, the use of & is not necessary. Also removing * from the function call doesn't affect the program.

5. Array of Pointers to Functions

We have already seen how to create an array of pointers to int, char, and so on. Similarly, we can create an array of pointers to function.

In this array, every element will store an address of a function, where all the functions are of the same type. That is, they have the same type and number of parameters and return types.

We will modify a program discussed earlier in this section. We will store the addresses of add(), subtract(), multiply() and divide() in an array make a function call through subscript.

#include  void add(float *a, float *b){ float c = *a + *b; printf("Addition gives %.2f\n",c); } void subtract(float *a, float *b){ float c = *a - *b; printf("Subtraction gives %.2f\n",c); } void multiply(float *a, float *b){ float c = *a * *b; printf("Multiplication gives %.2f\n",c); } void divide(float *a, float *b){ float c = *a / *b; printf("Division gives %.2f\n",c); } main(){ printf("Enter two numbers :\n"); float a,b; scanf("%f %f",&a,&b); printf("What do you want to do with the numbers?\nAdd : a\nSubtract : s\nMultiply : m\nDivide : d\n"); char operation = '0'; scanf(" %c",&operation); void (*p[])(float* , float*) = {add,subtract,multiply,divide}; printf("\nOperating...\n\n"); switch (operation) { case 'a': p[0](&a,&b); break; case 's': p[1](&a,&b); break; case 'm': p[2](&a,&b); break; case 'd': p[3](&a,&b); break; default: printf("Invalid input!!!\n"); } } 

The declaration here can be read as - p is an array of pointer to functions with two float pointers as parameters and returning void.

6. Pointer to Function as an Argument

Like any other pointer, function pointers can also be passed to another function, therefore known as a callback function or called function. The function to which it is passed is known as a calling function.

A better way to understand would be to look at qsort(), which is an inbuilt function in C. It is used to sort an array of integers, strings, structures, and so on. The declaration for qsort() is:

void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void *)); 

qsort() takes four arguments:

  1. a void pointer to the start of an array
  2. number of elements
  3. size of each element
  4. a function pointer that takes in two void pointers as arguments and returns an int

The function pointer points to a comparison function that returns an integer that is greater than, equal to, or less than zero if the first argument is respectively greater than, equal to, or less than the second argument.

The following program showcases its usage:

#include  #include  int compareIntegers(const void *a, const void *b) { const int *x = a; const int *y = b; return *x - *y; } main(){ int myArray[] = {97,59,2,83,19,97}; int numberOfElements = sizeof(myArray) / sizeof(int); printf("Before sorting - \n"); for(int i = 0; i < numberOfElements; i++) printf("%d ", *(myArray + i)); qsort(myArray, numberOfElements, sizeof(int), compareIntegers); printf("\n\nAfter sorting - \n"); for(int i = 0; i < numberOfElements; i++) printf("%d ", *(myArray + i)); } /* Output */ Before sorting - 97 59 2 83 19 97 After sorting - 2 19 59 83 97 97 

Since a function name is itself a pointer, we can write compareIntegers as the fourth argument.

D. Structure

1. Pointer to Structure

Like integer pointers, array pointers, and function pointers, we have pointer to structures or structure pointers as well.

struct records { char name[20]; int roll; int marks[5]; char gender; }; struct records student = {"Alex", 43, {76, 98, 68, 87, 93}, 'M'}; struct records *ptrStudent = &student; 

Here, we have declared a pointer ptrStudent of type struct records. We have assigned the address of student to ptrStudent.

ptrStudent stores the base address of student, which is the base address of the first member of the structure. Incrementing by 1 would increase the address by sizeof(student) bytes.

printf("Address of structure = %d\n", ptrStudent); printf("Adress of member `name` = %d\n", &student.name); printf("Increment by 1 results in %d\n", ptrStudent + 1); /* Output */ Address of structure = 6421984 Adress of member `name` = 6421984 Increment by 1 results in 6422032 

We can access the members of student using ptrStudent in two ways. Using our old friend * or using -> ( infix or arrow operator).

With *, we will continue to use the .( dot operator) whereas with -> we won't need the dot operator.

printf("Name w.o using ptrStudent : %s\n", student.name); printf("Name using ptrStudent and * : %s\n", ( *ptrStudent).name); printf("Name using ptrStudent and -> : %s\n", ptrStudent->name); /* Output */ Name without using ptrStudent: Alex Name using ptrStudent and *: Alex Name using ptrStudent and ->: Alex 

Similarly, we can access and modify other members as well. Note that the brackets are necessary while using * since the dot operator(.) has higher precedence over *.

2. Array Of Structure

We can create an array of type struct records and use a pointer to access the elements and their members.

struct records students[10]; /* Pointer to the first element ( structure) of the array */ struct records *ptrStudents1 = &students; /* Pointer to an array of 10 struct records */ struct records (*ptrStudents2)[10] = &students; 

Note that ptrStudent1 is a pointer to student[0] whereas ptrStudent2 is a pointer to the whole array of  10 struct records. Adding 1 to ptrStudent1 would point to student[1].

We can use ptrStudent1 with a loop to traverse through the elements and their members.

 for( int i = 0; i name, ( ptrStudents1 + i)->roll); 

3. Pointer to Structure as an Argument

We can also pass the address of a structure variable to a function.

#include  struct records { char name[20]; int roll; int marks[5]; char gender; }; main(){ struct records students = {"Alex", 43, {76, 98, 68, 87, 93}, 'M'}; printRecords(&students); } void printRecords( struct records *ptr){ printf("Name: %s\n", ptr->name); printf("Roll: %d\n", ptr->roll); printf("Gender: %c\n", ptr->gender); for( int i = 0; i marks[i]); } /* Output */ Name: Alex Roll: 43 Gender: M Marks in 0th subject: 76 Marks in 1th subject: 98 Marks in 2th subject: 68 Marks in 3th subject: 87 Marks in 4th subject: 93 

Note that the structure struct records is declared outside main(). This is to ensure that it is available globally and printRecords() can use it.

If the structure is defined inside main(), its scope will be limited to main(). Also structure must be declared before the function declaration as well.

Nagu struktuuridel, on meil ka ametiühingutele viiteid ja liikmetele pääseb juurde nooleoperaatori ( ->) abil.

E. Pointer pointerile

Siiani oleme vaadanud kurssi erinevatele primitiivsetele andmetüüpidele, massiividele, stringidele, funktsioonidele, struktuuridele ja unioonidele.

Automaatne küsimus, mis pähe tuleb, on - kuidas on pointer pointeriga?

Noh, hea uudis teile! Ka nemad on olemas.

int var = 6; int *ptr_var = &var; printf("Address of var = %d\n", ptr_var); printf("Address of ptr_var = %d\n", &ptr_var); /* Output */ Address of var = 6422036 Address of ptr_var = 6422024 

intMuutuja aadressi salvestamiseks on varmeil kursor intptr_var. Aadressi salvestamiseks vajame veel ühte kursorit ptr_var.

Kuna see ptr_varon tüüpi int *, peaksime selle aadressi salvestamiseks looma kursori int *. Allolev kood näitab, kuidas seda saab teha.

int * *ptr_ptrvar = &ptr_var; /* or int* *ppvar or int **ppvar */ 

Saame kasutada varile juurdepääsuks ptr_ptrvaraadressi ptr_varja topeltkaalutlust.

printf("Address of ptr_var = %d\n", ptr_ptrvar); printf("Address of var = %d\n", *ptr_ptrvar); printf("Value at var = %d\n", *(*ptr_ptrvar)); /* Output */ Address of ptr_var = 6422024 Address of var = 6422036 Value at var = 6 

It is not required to use brackets when dereferencing ptr_ptrvar. But it is a good practice to use them. We can create another pointer ptr_ptrptrvar, which will store the address of ptr_ptrvar.

Since ptr_ptrvar is of type int**, the declaration for ptr_ptrptrvar will be

int** *ptr_ptrptrvar = &ptr_ptrvar; 

We can again access ptr_ptrvar, ptr_var and var using ptr_ptrptrvar.

printf("Address of ptr_ptrvar = %d\n", ptr_ptrptrvar); printf("Value at ptr_ptrvar = %d\n",*ptr_ptrptrvar); printf("Address of ptr_var = %d\n", *ptr_ptrptrvar); printf("Value at ptr_var = %d\n", *(*ptr_ptrptrvar)); printf("Address of var = %d\n", *(*ptr_ptrptrvar)); printf("Value at var = %d\n", *(*(*ptr_ptrptrvar))); /* Output */ Address of ptr_ptrvar = 6422016 Value at ptr_ptrvar = 6422024 Address of ptr_var = 6422024 Value at ptr_var = 6422036 Address of var = 6422036 Value at var = 6 

Pointer pointerile

If we change the value at any of the pointer(s) using ptr_ptrptrvar or ptr_ptrvar, the pointer(s) will stop pointing to the variable.

Conclusion

Phew! Yeah, we're finished. We started from pointers and ended with pointers (in a way). Don't they say that the curve of learning is a circle!

Try to recap all the sub-topics that you read. If you can recollect them, well done! Read the ones you can't remember again.

See artikkel on valmis, kuid näpunäidetega ei tohiks seda teha. Mängi nendega. Järgmisena saate viidete paremaks tundmaõppimiseks uurida dünaamilise mälu jaotust .

Jää koju, ole turvaline.