![]() |
Mòdul
5
![]() |
Fonaments de
Programació. Llenguatge C/C++![]() |
Pràctica
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() |
![]() |
Pràctica
d'ampliació ![]() ![]() |
Resum Teòric
Introducció als punters Quan es declara una variable, es reserva espai a la memòria per contenir el valor d’aquesta variable. El nom de la variable queda associat a l'adreça de memòria on comença aquest espai reservat. La quantitat d’espai reservat depèn del tipus de variable, per exemple, en la següent declaració:
es tindrà el següent esquema a la memòria de l’ordinador (les posicions concretes de memòria són orientatives): Un punter és una variable que conté l'adreça d’una altra variable. Es diu que la variable punter o el punter apunta a aquesta segona variable.. Els punters proporcionen una gran potència als llenguatges C i C++ i marquen la diferència entre aquests i altres llenguatges de programació. Els punters ens permeten aproximar-nos al tipus de treball que fa l’ordinador. Els programes que utilitzen punters són normalment més eficients, encara que els punters són un element perillós en el sentit que un punter sense valor inicial o incontrolat pot provocar un mal funcionament del sistema i provocar errors de difícil localització. La importància dels punters està principalment en aquests tres punts:
El fet de treballar en sistemes de 32 bits fa que els punters puguin directament apuntar a "qualsevol" lloc de la memòria. No és necessari l’ús de segments i desplaçaments com necessitaven els sistemes de 16 bits, no obstant això, els sistemes operatius Windows (NT, 95,98 i 2000) no permeten que els punters apuntin fora de la memòria reservada per a l'execució del programa. Declaració d'un punter Les variables punters s'han de declarar com qualsevol altra variable en C/C++. El format general de la declaració d'un punter és: tipus_basic *nom_de_la_variable on tipus_basic és qualsevol dels tipus bàsic de dades i defineix el tipus de dades que trobarem en el lloc on apunta el punter. L'asterisc es pot llegir de moment com "punter a tipus_basic". El nom de la variable punter és un identificador de variable normal i, com a tal, és correcte qualsevol identificador. Per exemple:
Aquesta sentència declara dues variables, una variable tipus char anomenada carac, i una altra de tipus char * ( punter a char ) anomenada ptrcarac. Si fem les següents assignacions:
tindrem el següent esquema a la memòria: Els números de la columna de l'esquerra representen, en format hexagesimal, l'adreça de cada posició de memòria (aquestes adreces són orientatives). Dintre dels quadres es representa el contingut de la memòria: La variable carac està assignada a la posició 006515A1 i el seu contingut és 'A', o bé el número 65. Aquesta variable ocupa un únic octec. La variable ptrcarac està assignada a la posició 006515A2 i el seu contingut és la posició de la variable carac, és a dir, la posició 006515A1. En Visual C, les variables punters ocupen 4 octets, independentment del tipus de variable a la qual apunten.
Els operadors de manipulació de punters: & i * L'operador & (operador d'adreça) és un operador unari que retorna l'adreça de memòria del seu operant. El seu operant pot ser qualsevol tipus de variable, incloent-hi les variables punters. A l'exemple anterior, l'assignació:
fa que a la variable ptrcarac s'emmagatzemi l'adreça de la variable carac. En aquest moment, la variable ptrcarac apuntarà a la variable carac. L'operador * (operador d'indirecció) és un altre operador unari que retorna el valor de la variable on està apuntant l'operant (que serà un punter). Per exemple, la sentència:
assignarà a la variable val (declarada prèviament com a char) el valor 65. L'operador & actua sobre qualsevol variable. L'operador * actua sobre variables punters. No s'ha de confondre l'operador unari * amb l'operador binari * que representa el producte de dos nombres. En expressions complicades en les quals pugui haver confusions, es pot posar parèntesis per evitar aquestes confusions.
Assignacions a punters Com qualsevol variable, es pot fer servir un punter a la part dreta d'una sentència per assignar el valor del punter a un altre punter, per exemple:
El punter py apuntarà a la variable a, per tant, *py serà igual a 100. El codi de format per mostrar adreces de memòria en hexagesimal amb la funció printf() és %p.
Aritmètica de punters En C es pot fer servir els operadors ++,--, + i - sobre punters. Una expressió com: p++; sobre un punter p fa que apunti a la següent posició de memòria, entenent com a següent posició la que s'obté de sumar el nombre d'octets que ocupa el tipus base del punter. Per exemple, si la variable punter p, declarada com un punter a enter (int * p), conté l'adreça 0065A510, la sentència p++ fa que aquest punter contingui ara l'adreça 0065A514, ja que, una variable enter ocupa 4 octets. Els llenguatges C/C++ no es limiten només als increments i decrements, també es pot sumar i restar enters als punters. Per exemple, si a la variable p definida al paràgraf anterior, sumem 5 amb la sentència: p=p+5; el valor actual serà 0065A514+5*(mida d'un enter)=0065A528 (Si no heu entès aquesta suma, recordeu que les adreces s'expressen normalment en hexagesimal, de fet, s'ha sumat 20 posicions de memòria).
Inicialització de punters Si una variable local no s'inicialitza, el seu valor és indeterminat. Si aquest fet pot ser perillós en el cas d’una variable normal, és especialment perillós en el cas dels punters. Sempre cal inicialitzar els punters abans de fer-los servir. Per treballar amb punters buits (que temporalment no apuntin enlloc), es poden inicialitzar a un valor especial anomenat NULL (punter nul). NULL és una constant simbòlica que està definida en alguns arxius de capçalera estàndards com stdio.h o ioscrean.h i de fet és un valor 0, el valor fals en les condicions. Si un punter sempre apunta a una mateixa variable és una bona pràctica inicialitzar-la en la declaració, per exemple:
Pas d’arguments per valor i per referència En C/C++, quan cridem a una funció amb un argument (una variable), es passa una còpia del contingut d’aquesta variable. Es diu que l’argument s’ha passat per valor. La funció no pot modificar el contingut de la variable original. La principal restricció del mètode de crida per valor és que la funció només pot tornar un únic valor. Una altra possibilitat és passar arguments per referència, és a dir, passar l’adreça de la variable en lloc del seu valor. Això fa que la funció no té la necessitat de crear una còpia d’aquesta variable i, a més, les modificacions que faci la funció afectaran al valor de la variable una vegada acabada la funció. D’aquesta forma una funció pot modificar més d’un valor. En C/C++ es pot crear una crida per referència utilitzant un punter com argument. A la primera pràctica podreu entendre la diferència entre aquests dos tipus de pas d'arguments.
Punters del tipus void * C/C++ incorporen la possibilitat de declarar un punter com void *, això permet que el punter apunti a qualsevol tipus de dades. Això pot ser útil en molts casos. Podem pensar, per exemple, en la funció estàndard d'entrada C: scanf(), que admet com arguments punters a qualsevol tipus de dades. Quan es vol manipular una variable punter void *, primer s'ha de fer una conversió explícita a qualsevol tipus de dada vàlida C/C++. A la pràctica 3 es tracta un exemple d'aquesta característica.
Vectors o variables indexades Els vectors (també coneguts com variables indexades, arrays, arreglos, formacions, matrius, etc.) són un conjunt de variables del mateix tipus i amb el mateix nom. Per referir-nos a un element concret d’un vector es fa servir un o més índexs tancats entre claudàtors. El nombre de claudàtors representarà la dimensió del vector. En el cas que la dimensió sigui superior a 1 se sol anomenar matriu. Per declarar un vector o matriu d'una o diverses dimensions, s'ha d'escriure el tipus i el nom seguit d’uns claudàtors amb un nombre d'elements per a cada dimensió. Per exemple:
Per referir-nos a una de les 10 variables int del vector x es fa servir un índex que pot ser qualsevol expressió que torni un enter entre 0 i n-1, essent n el nombre que s’ha utilitzat en la definició. A les matrius de dues dimensions, els elements es van emmagatzemant per variació dels índexs de més a la dreta cap als índexs de més a l’esquerra, per exemple:
s’emmagatzemarà a la memòria en el següent ordre: m[0][0], m[0][1], m[0][2], m[0][3], m[1][0], m[1][1], m[1][2], m[1][3], m[2][0], m[2][1], m[2][2], m[2][3] Una cosa molt important a tenir en compte és que C/C++ no fa comprovació de límits, això vol dir que si es declara un vector de dimensió n, pot passar que s’utilitzi un índex amb un valor més gran que n. Aquesta circumstància no és controlada pel compilador i pot provocar la caiguda del sistema. Inicialització de vectors Com tota variable, un vector pot prendre valors inicials després de la seva declaració. El format general és: tipus identificador_variable [grandaria] = { llista_de_valors }; La llista de valors és una llista separada per comes " , ", de constants que són del mateix tipus que el tipus base del vector. Exemple:
En aquest exemple la primera constant de valor 0 l'emmagatzemarà a la primera posició del vector, la segona constant de valor 1 a la segona posició del vector, i així successivament fins completar les cinc constants. El compilador les situarà en posicions contigües de memòria. No és necessari posar valor inicial a tot el vector, en aquest cas el compilador posarà zeros a tots aquells elements que no li hem assignat cap valor. Per exemple:
En aquest exemple el compilador assignarà zeros als dos últims elements del vector. També és possible al posar els valors inicials al vector, no declarar la seva grandària. El compilador la determina calculant el nombre de valors enumerats. Així, l’assignació:
fa que la dimensió de la
variable num sigui 5.
Per posar valors inicials als vectors
multidimensionals ho farem d'una forma semblant a la feta pels vectors
unidimensionals. Són exemples de posar valors inicials les següents
declaracions d'assignació:
equivalent a:
i també equivalent a:
En aquest exemple declarem una matriu d'enters de dues files per cinc columnes. Així, com en els vectors unidimensionals, el posar valor inicial ho podíem fer en forma parcial, ara també és possible, a condició de començar pel principi de cada índex del vector. No obstant és necessari anar en compte perquè en les declaracions incompletes les confusions són fàcils com mostra el següent exemple:
Relació entre vectors i punters Existeix
una estreta relació entre vectors i punters. De fet, el nom d'un vector és
un punter a l'adreça de memòria que conté el primer element del vector, és
a dir, si definim el vector:
L'identificador vect és equivalent a &vect[0]. En
general, vect+i serà un punter que apuntarà a vect[i]. De
fet, als punters se'ls poden posar índexs i, si s'ha definit un punter com:
és equivalent p+i que p[i]. En
el cas dels vectors multidimensionals o matrius, la relació entre aquests i
els punters és una mica més sofisticat. En el cas d'una matriu bidimensional,
el nom de la matriu és un punter al primer element d'un vector de punters,
per exemple, si definim:
mat és un punter al primer element del vector de punters mat[]. Per tant, mat és equivalent a &mat[0] i mat[0] és equivalent a &mat[0][0], de la mateixa forma que mat[1] és equivalent a &mat[1][0]. Pas de vectors com arguments d'una funció Per considerar el pas de vectors com arguments d'una funció, és necessari entendre la relació entre aquests i els punters. Si passem a una funció un element d'un vector, estem passant el valor d'aquest element. En aquest cas, l'argument de la funció s'ha de declarar del tipus de dada que es passa. Per exemple:
Podem passar directament tot el vector. En aquest cas la crida es farà amb el nom del vector que, com ja se sap, és un punter. Es pot fer de dues formes:
En el cas de matrius multidimensionals, és
necessari donar les dimensions de la matriu que se passa com argument excepte la
primera. Per exemple:
A la segona versió, la de la dreta, el parèntesi de int (*v)[10] és necessari degut a la major prioritat de l'operador [] sobre l'operador * Punters a funcions Les funcions, a l'igual que les variables, tenen la seva pròpia adreça de memòria. Una característica molt interessant, al mateix temps que confusa, és la de punter a funció. Aquest punter correspondrà a l'adreça inicial del codi de la funció. Els punters a funcions permeten referenciar de forma indirecta una funció, i també permeten que una funció pugui ser passada com argument a una altra funció. Com passa amb els vectors, el nom d'una funció sense els seus parèntesi s'interpreta com un punter a la funció. Per declarar un punter a una funció es fa normalment de dues formes: La primera forma és declarar directament una variable punter a funció de la següent forma:
És necessari posar parèntesi al voltant del nom de la funció (*nom_punter_funció) degut a que l’operador * té menor prioritat que el parèntesi que l’encercla. La segona forma és declarar un punter tipus void, que posteriorment serà assignat a una funció. Aquesta assignació es pot fer posant el nom d'una funció, sense parèntesi, al costat dret d'una sentència d'assignació:
|
|||||||||||||||||||||||||
![]() |
![]() |