Issuu on Google+

Curs 2007-2008 • DAI 1A • Crèdit 5: Programació estructurada i modular • UD3_NA2_Punters i Cadenes

UD3-NA2: Punters i cadenes 1. Punters 1.1. 1.2. 1.3. 1.4. 1.5.

Adreces de memòria Concepte de punter Importància dels punters Declaració d'un punter Operacions sobre punters

1.5.1. Els operadors de manipulació de punters: & i * 1.5.2. Assignació a punters 1.5.3. Aritmètica de punters 1.5.4. Comparació

1.6. Els punters a punter 1.7. El punter NULL 1.8. Errors habituals amb els punters

2. Ús dels punters 2.1. Pas d’arguments per referència 2.2. Relació entre arrays i punters 2.2.1. Vectors i punters 2.2.2. Matrius i punters 2.2.3. Pas d'arrays com arguments d'una funció

3. Cadenes 3.1. 3.2. 3.3. 3.4.

Concepte de cadena Declaració de variables cadena Inicialització de variables cadena Llegir i escriure una cadena de caràcters

3.4.1. Funcions scanf() i printf() 3.4.2. Funció getchar() 3.4.3. Funció gets() 3.4.4. Funció puts() 3.4.5. Neteja del buffer. Funció fflush()

3.5. Treballar amb cadenes de caràcters

4. Funcions de biblioteca 4.1. La biblioteca string.h 4.2. La biblioteca ctype.h 4.3. La biblioteca stdlib.h

Apèndix: Biblioteca de problemes

Professor : Quique Lorente

Pàgina 1


Curs 2007-2008 • DAI 1A • Crèdit 5: Programació estructurada i modular • UD3_NA2_Punters i Cadenes

UD3-NA2 : Punters i cadenes 1. Punters Un punter és una variable que conté adreces d'altres variables. Totes les variables vistes fins aquest moment contenen valors de dades; pel contrari els punters contenen valors que són adreces de memòria on s'emmagatzemen dades. 1.1. Adreces de memòria 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ó: char carac, carac2; int comptador; float x; int i;

es tindrà el següent esquema a la memòria de l’ordinador (les posicions concretes de memòria són orientatives): carac2 -> carac -> comptador -> x ->

i ->

01FF 0200 0201 0202 0203 0204 0205 0206 0207 0208 0209 020A

char char int

float

int

Al valor d'una variable s'accedeix per mitjà del seu nom. Per exemple es pot imprimir el valor de n amb la sentència: printf("%d", n);

A l'adreça de la variable s'accedeix per mitjà de l'operador d'adreça &. Per exemple, es pot imprimir l'adreça de n amb la sentència: printf("%p", &n);

L'operador d'adreça & s'aplica sobre el nom de la variable per obtenir la seva adreça. Exemple: Obtenir el valor i l'adreça d'una variable. #include <stdio.h> void main() { int n=50; printf("Valor de la variable: n = %d\n", n); printf("Adreça de la variable: &n = %p\n", &n); } Professor : Quique Lorente

Pàgina 2


Curs 2007-2008 • DAI 1A • Crèdit 5: Programació estructurada i modular • UD3_NA2_Punters i Cadenes

1.2. Concepte de punter Cada vegada que es declara una variable C, el compilador estableix una àrea de memòria per emmagatzemar el contingut de la variable. Quan es declara una variable int, per exemple, el compilador assigna dos bytes de memòria (per ser exactes, això depèn del compilador). L'espai per a aquesta variable se situa en una posició específica de la memòria, coneguda com adreça de memòria. Quan es referencia (s'utilitza) el valor de la variable, el compilador de C accedeix automàticament a l'adreça de memòria on s'emmagatzema l'enter. Cada variable que es declara en C té una adreça de memòria associada amb ella. Un punter és una variable que conté una adreça de memòria. El concepte de punter té correspondència en la vida diària. Quan s'envia una carta per correu, la seva informació es lliura basada en un punter que és l'adreça de la carta. Quan es truca a una persona, s'utilitza un punter, el número de telèfon que es marca. Així doncs, una adreça de correus i un número de telèfon tenen en comú que ambdós indiquen on trobar quelcom. Són punters a edificis i telèfons, respectivament. Un punter en C també indica on trobar quelcom: on trobar les dades que estan associades amb una variable. Un punter és una variable que conté l'adreça d'una dada emmagatzemada en memòria. Com conté l'adreça d'una dada podem accedir a la dada a través del punter, tant per llegir-la, com per modificar-la. La dada situada en memòria en l'adreça continguda pel punter ha de pertànyer a un tipus de dada, raó per la qual en definir un punter, haurem de definir també el tipus de dades del que s'emmagatzemaran adreces, per això tindrem punters a int, punters a char, etc., en general tindrem punters a qualsevol tipus de dades definit en C. Els punters reben el seu nom del fet que quan contenen l'adreça d'una altra variable, es diu que apunten a aquesta variable. 1.3. Importància dels punters Els punters proporcionen una gran potència al llenguatge C i marquen la diferència entre aquest 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: • •

Proporcionen el pas d'arguments per referència. Permeten l’assignació dinàmica de memòria. Això vol dir que amb l’ajuda dels punters es pot reservar la memòria en temps d’execució en lloc de en temps de compilació, el que significa que l’espai reservat per les dades pot ser determinat per l’usuari en lloc de pel programador. Poden substituir als vectors o variables indexades per tal d’incrementar l'eficàcia del programa. 1.4. Declaració d'un punter

La declaració d'un punter es fa indicant el tipus de la variable apuntada, seguit d'un * (asterisc), com indicador de punter, abans d'escriure el nom de la variable: tipus *nom_de_la_variable

Professor : Quique Lorente

Pàgina 3


Curs 2007-2008 • DAI 1A • Crèdit 5: Programació estructurada i modular • UD3_NA2_Punters i Cadenes

on tipus 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". El nom de la variable punter és un identificador de variable normal i, com a tal, és correcte qualsevol identificador. Per exemple: char c, *pc;

Aquesta sentència declara dues variables, una variable tipus char anomenada c, i una altra de tipus char * ( punter a char ) anomenada pc. Si fem les següents assignacions: c = 'A'; pc = &c;

tindrem el següent esquema a la memòria: 15A1

'A'

c

15A2 15A1

pc

15A3 15A4 15A5

Els números de la columna de l'esquerra representen, en format hexadecimal, 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 c està assignada a la posició 15A1 i el seu contingut és 'A'. Aquesta variable ocupa un únic byte. La variable pc està assignada a la posició 15A2 i el seu contingut és la posició de la variable c, és a dir, la posició 15A1. En Turbo C per a MS-DOS, les variables punters ocupen 2 bytes, independentment del tipus de variable a la qual apunten. En un compilador sota Windows ocupen 4 bytes. 1.5. Operacions sobre punters Sobre els punters només es poden utilitzar uns pocs operadors, ja que algunes operacions, com per exemple el producte de punters, no tenen sentit. A continuació es veuran els operadors més comunament usats. 1.5.1. 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 de la variable a què s'aplica. A l'exemple anterior, l'assignació: pc=&c;

fa que a la variable punter pc s'emmagatzemi l'adreça de la variable c. En aquest moment, el punter pc apuntarà a la variable c. L'operador * és, sens dubte, el més característic dels operadors que podem usar amb punters, ja que no només s'usa per declarar-los, sinó també per accedir a la dada apuntada. L'operador * també és un operador unari, i només s'aplica a variables punter. Aquest operador retorna la dada on està

Professor : Quique Lorente

Pàgina 4


Curs 2007-2008 • DAI 1A • Crèdit 5: Programació estructurada i modular • UD3_NA2_Punters i Cadenes

apuntant el punter, que serà del tipus de dades que s'ha definit per al punter. Per exemple, la sentència: val=*pc;

assignarà a la variable val (declarada prèviament com a char) el valor 'A'. 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 poden posar parèntesis per evitar aquestes confusions. 1.5.2. Assignació a punters Donada la declaració, int *px, *py;

la instrucció py=px copia l'adreça continguda en px sobre py, de manera que py contindrà la mateixa adreça que px, i per tant apuntarà sobre el mateix objecte apuntat per px. No s'ha de confondre amb la instrucció *py=*px, que copia l'objecte de tipus int apuntat per px a l'espai de memòria de tipus int apuntat per py. Quan hem vist l'operador &, s'ha dit que aquest operador obté l'adreça d'una variable, de forma que aquesta adreça pot ser assignada a una variable punter. int x=5; int *px, *py; px=&x;

En escriure px=&x, px passa a contenir l'adreça de x (px apunta a x). Si ara escrivim *px=10, aleshores x passarà a valer 10. P1

Exemple:

X

int x; int *p1,*p2; x=45; p1=&x; p2=p1;

45

P2

En aquest exemple, tant p1 com p2 passen a apuntar a x Exemple: #include <stdio.h> void main() { int a, z, *punt; a=23; punt=&a; z=*punt; printf("En l'adreça %p hi ha guardat el nombre %d", punt, z); }

Professor : Quique Lorente

Pàgina 5


Curs 2007-2008 • DAI 1A • Crèdit 5: Programació estructurada i modular • UD3_NA2_Punters i Cadenes

En aquest programa, a és una variable entera de valor 23; punt és un punter, que designa l'adreça de l'enter a; i z és el valor que es troba en l'adreça que apunta la variable punt. Quan s'executi el programa, s'obtindrà: En l'adreça ... hi ha guardat el nombre 23

però tenint present que en lloc dels punts hi sortirà l'adreça en hexadecimal de la posició de memòria on està guardat el 23. 1.5.3. Aritmètica de punters Es pot fer servir els operadors ++ i -- sobre punters. Una expressió com: p++;

sobre un punter p fa que aquest s'incrementi en una quantitat suficient de bytes per a què apunti a l'element següent al que apuntava inicialment. El llenguatge C no es limita només als increments i decrements, també es pot sumar i restar enters als punters. Per exemple, si al punter p li sumem 5 amb la sentència: p=p+5;

passarà a apuntar al cinquè element que hi ha a continuació. Aquest tipus d'operacions se solen usar per al maneig de vectors, on se sap que hi ha diverses dades del mateix tipus i totes juntes en memòria. 1.5.4. Comparació La comparació entre punters és perfectament vàlida, per tant, es poden usar els operadors ==, !=, <, <=, > i <= de la forma habitual. En el cas de la igualtat, una avaluació a cert ens indicarà que dos punters apunten al mateix espai de memòria. 1.6. Els punters a punter Un punter és una variable, per tant ocupa una posició en memòria i podem conèixer la seva adreça usant l'operador &. La variable que emmagatzemi l'adreça d'una variable punter, serà un punter a punter, i haurà de ser declarat amb dos asteriscs en lloc d'un. int x; int *px; int **ppx; px=&x; // *px conté el mateix valor que x ppx=&px; // **ppx conté el mateix valor que *px i que x

En aquest exemple declarem un enter x, un punter a enter px i un punter a punters que apunten a enter anomenat ppx. El punter a enter px emmagatzemarà adreces d'enters com la de x (px=&x;). Per la seva part ppx emmagatzemarà l'adreça d'un punter que apunti a enters com px (ppx=&px;). 1.7. El punter NULL A vegades interessarà saber si un punter no apunta enlloc, o provocar aquesta situació. Per a això se'ls assigna un valor especial anomenat NULL. Aleshores es diu que són punters buits o nuls, i

Professor : Quique Lorente

Pàgina 6


Curs 2007-2008 • DAI 1A • Crèdit 5: Programació estructurada i modular • UD3_NA2_Punters i Cadenes

temporalment no apunten enlloc. NULL és una constant predefinida i de fet és un valor 0, el valor fals en les condicions. El valor NULL el solen tornar determinades funcions per a indicar que ha hagut un error. 1.8. Errors habituals amb els punters Quan treballem amb punters s'ha d'anar molt en compte, ja que podem incórrer fàcilment en errors. Alguns dels més habituals són els següents: •

Problemes de tipus: els punters sempre han d'apuntar al tipus de dades correcte. Vegem un exemple: #include <stdio.h> void main() { float x, y; int *p; x=123.456; p=&x; // Error y=*p; printf("%f %f ",x, y); }

Aquest programa no assigna el valor de x a y, ja que *p és un enter. •

Punter no inicialitzat: int x,*p; x=5; *p=x; // Error

A quina posició de memòria apunta p? El valor 5 s'ha escrit en algun lloc no determinat. •

Assignar un valor a un punter, en lloc d'una adreça: int x, *p; x=5; p=x; // Error

En p es col·loca com a valor un 5, que s'interpreta com una adreça de memòria

Professor : Quique Lorente

Pàgina 7


Curs 2007-2008 • DAI 1A • Crèdit 5: Programació estructurada i modular • UD3_NA2_Punters i Cadenes

2. Ús dels punters 2.1. Pas d’arguments per referència En 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 tingui 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ó, ja que treballarà directament sobre el valor contingut en memòria. D’aquesta forma una funció pot modificar més d’un valor. En C es pot crear una crida per referència utilitzant un punter com argument. 2.2. Relació entre arrays i punters 2.2.1. Vectors i punters Existeix una estreta relació entre vectors i punters. De fet, el nom d'un vector és un punter constant a l'adreça de memòria que conté el primer element del vector, és a dir, si definim el vector: int vect[5]

L'identificador vect és equivalent a &vect[0]. El C considera vect com un punter constant a int. 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: int *p;

és equivalent *(p+i) que p[i]. Com els punters poden apuntar a diferents dades, es pot utilitzar un punter per a recórrer tot un vector. Exemple: escriure els valors d'un vector; versió sense i amb punters. /* Sense punters */ #include <stdio.h>

/* Amb punters */ #include <stdio.h>

void main() { int i,vector[4]={10,20,30 40}; for (i=0; i<4; i++) printf("%d ", v[i]); }

void main() { int *p,vector[4]={10,20,30,40}; for (p=vector; p<vector+4; p++) printf("%d ", *p)); }

Professor : Quique Lorente

Pàgina 8


Curs 2007-2008 • DAI 1A • Crèdit 5: Programació estructurada i modular • UD3_NA2_Punters i Cadenes

2.2.2. Matrius i punters En el cas dels arrays bidimensionals o matrius, el nom de la matriu apunta a la primera posició de memòria de les dades de la matriu. Però si declarem la següent matriu: int mat[5][3];

L'identificador mat no és equivalent a &mat[0][0]. El C considera mat com un punter constant a un vector de 3 enters, per tant no es pot utilitzar el nom de la matriu per a assignar una adreça a un punter a int, ja que no són del mateix tipus. Per a recórrer la matriu amb un punter s'inicialitzarà a &mat[0][0] o mat[0]. Exemple: escriure els valors d'una matriu; versió sense punters i dues versions amb punters. /* Sense punters */ #include <stdio.h> void main() { int i, j, matriu[2][3] = {10, 20, 30, 40, 50, 60}; for (i=0; i<2; i++) for (j=0; j<3; j++) printf("%d ", matriu[i][j])); } /* Amb punters A */ #include <stdio.h> void main() { int *p, i, j, matriu[2][3] = {10, 20, 30, 40, 50, 60}; p=&matriu[0][0]; for (i=0; i<2; i++) for (j=0; j<3; j++) { printf("%d ", *p); p++; } } /* Amb punters B */ #include <stdio.h> void main() { int *p, matriu[2][3] = {10, 20, 30, 40, 50, 60}; for (p=&matriu[0][0]; p<&matriu[0][0]+2*3; p++) printf("%d ", *p); }

Professor : Quique Lorente

Pàgina 9


Curs 2007-2008 • DAI 1A • Crèdit 5: Programació estructurada i modular • UD3_NA2_Punters i Cadenes

2.2.3. Pas d'arrays 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. En C tots els vectors es passen per referència. La crida es fa amb el nom del vector que, com ja se sap, és un punter. Es pot fer de dues formes: void funcio(int v[]); void main(){ int vect[10]; ...... funcio(vect); ...... }

void funcio(int *v); void main(){ int vect[10]; ...... funcio(vect); ...... }

void funcio(int v[]){ .......

void funcio(int *v){ .......

En el cas de matrius multidimensionals, és necessari donar les dimensions de la matriu que es passa com argument excepte la primera. Per exemple: void funcio(int m[][10]); void main(){ int mat[10][10]; ...... funcio(mat); ...... }

void funcio(int (*m)[10]); void main(){ int mat[10][10]; ...... funcio(mat); ...... }

void funcio(int m[][10]){ .......

void funcio(int (*m)[10]){ .......

A la segona versió, la de la dreta, el parèntesi de int (*m)[10] és necessari degut a la major prioritat de l'operador [] sobre l'operador *.

Professor : Quique Lorente

Pàgina 10


Curs 2007-2008 • DAI 1A • Crèdit 5: Programació estructurada i modular • UD3_NA2_Punters i Cadenes

3. Cadenes 3.1. Concepte de cadena Una cadena és un tipus de dades composat, un vector de caràcters acabat amb un caràcter nul ('\0', NULL). La cadena constant "Prova" ocupa 6 posicions de memòria, 5 per als caràcters i una més per a emmagatzemar el caràcter nul. 3.2. Declaració de variables cadena Les cadenes es declaren com els restants tipus de vectors. L'operador postfix [] conté la longitud màxima de l'objecte. El tipus de dades és char: char nomCadena [grandària màxima]

Exemple: char text [81];

/* cadena que pot emmagatzemar 80 caràcters, més un espai per al caràcter NULL ('\0') */

Observeu que la longitud de la cadena ha d'incloure el caràcter '\0'. En conseqüència, per a definir una variable cadena que contingui la cadena "ABCDEF", s'ha d'escriure: char cadena [7];

3.3. Inicialització de variables cadena Tots els tipus d'arrays admeten una inicialització que consisteix en una llista de valors separats per comes i tancats entre claus. Exemple: char char char char

text [81] = "Açò és una cadena"; cad1 [5] = {'a', 'b', 'c', 'd', '\0'}; cad2 [5] = {97, 98, 99, 100, 0}; cadenatest [] = "Quina és la longitud d'aquesta cadena?";

Les cadenes text i cad1 poden contenir 80 i 4 caràcters respectivament més el caràcter nul. Les cadenes cad1 i cad2 són exactament iguals, ja que a cad2 se li han assignat els mateixos caràcters, mitjançant la seva equivalència ASCII. La quarta cadena, cadenatest, es declara amb una especificació de tipus incompleta i es completa sols amb l'inicialitzador. Donat que en la frase hi ha 38 caràcters i el compilador afegeix el caràcter "\0", un total de 39 caràcters s'assignaran a cadenatest.

Exemple: Les cadenes acaben amb el caràcter NULL. Així, en el següent programa es mostra que el caràcter NULL ('\0') s'afegeix a la cadena: void main() { int i; char s[] = "ABCD"; for(i=0; i<5; i++) printf("s[%d] = %c \n", i, s[i]); }

Professor : Quique Lorente

Execució: s[0] = A s[1] = B s[2] = C s[3] = D s[4] =

Pàgina 11


Curs 2007-2008 • DAI 1A • Crèdit 5: Programació estructurada i modular • UD3_NA2_Punters i Cadenes

3.4. Llegir i escriure una cadena de caràcters 3.4.1. Funcions scanf() i printf() Una forma de llegir una cadena de caràcters és utilitzar la funció scanf() amb l'especificador de format %s. Per exemple, si voleu llegir un nom de 40 caràcters de longitud màxima, haureu de declarar la cadena i desprès llegir-la de la següent manera: char nom[41]; scanf("%s", nom); printf("%s\n", nom);

En aquest cas, la variable nom no s'ha de precedir per l'operador &, perquè com ja s'ha dit anteriorment, l'identificador d'un array és l'adreça de començament del array. En aquest cas, nom és l'adreça del començament de la cadena de caràcters. Ara bé, si executeu les sentències anteriors i realitzeu una entrada com la següent, Maria Teresa

no us sorprengueu quan al visualitzar la cadena únicament és visualitzi Maria. Recordeu que la funció scanf() llegeix les dades delimitades per espais en blanc. La solució més senzilla per a aquest problema és utilitzar la funció gets() de la biblioteca de C i que veurem un poc més endavant. 3.4.2. Funció getchar() Una forma de llegir un caràcter és utilitzant la funció getchar(). Aleshores, es pot llegir una cadena de caràcters invocant repetides vegades la funció getchar() i emmagatzemant cada caràcter llegit en la següent posició lliure d'un array de caràcters, tenint la precaució de finalitzar la cadena amb el caràcter '\0'. Per exemple: #include<stdio.h> #define LONG_CAD 41 void main() { char cadena[LONG_CAD]; //vector de LONG_CAD caràcters int i=0, car; printf("Introdueix un text: "); while(i<LONG_CAD-1 && (car = getchar())!='\n') { cadena[i]=car; i++; } cadena[i]=NULL; //Finalitza la cadena amb NULL o '\0' printf("Text introduït: %s \n", cadena); printf("Longitud del text: %d\n", i); }

L'exemple anterior defineix la variable cadena com un array de caràcters de grandària 41. Després estableix un bucle per a llegir els caràcters que es teclegin fins que es premi la tecla Enter. Cada

Professor : Quique Lorente

Pàgina 12


Curs 2007-2008 • DAI 1A • Crèdit 5: Programació estructurada i modular • UD3_NA2_Punters i Cadenes

caràcter llegit s'emmagatzema en la següent posició lliure del vector cadena. Finalment, s'escriu el contingut de cadena i el nombre de caràcters emmagatzemats. Si en lloc de llegir una línia volguéssim llegir un text format per diverses línies hauríem d'actuar així: while(i<LONG_CAD-1 && (car = getchar())!=EOF)

L'entrada des del teclat es tracta com si fos un fitxer. Amb aquesta instrucció es van llegint caràcters fins arribar al final del fitxer. Se sap que s'ha arribat al final del fitxer quan es llegeix la marca de fi de fitxer, EOF. Per a introduir-la des del teclat s'ha de fer amb la combinació Ctrl+z. Com aquesta marca està definida com a un enter, la variable car ha de ser entera, tot i que després la tractem com a caràcter. En vegada d'escriure la cadena com un únic element de dades, podríem també escriure-la caràcter a caràcter. Per exemple, el següent codi escriu cada caràcter de la cadena junt amb el seu valor ASCII: for(i=0; cadena[i]!=NULL; i++) printf("Caràcter %c, valor ASCII %d\n", cadena[i], cadena[i]);

Observeu el bucle utilitzat per a escriure la cadena: per a i=0 accedeix al primer element de la matriu, per a i=1 al segon, i així fins arribar al caràcter nul ('\0') que indica el final de la cadena. La solució que s'obtindria després de realitzar aquesta modificació seria similar a la següent: Introdueix un text: hola Caràcter h, valor ASCII 104 Caràcter o, valor ASCII 111 Caràcter l, valor ASCII 108 Caràcter a, valor ASCII 97

Aquest últim exemple demostra que un caràcter es pot tractar indistintament com un valor enter (valor ASCII del caràcter) o com un caràcter (símbol entre cometes simples). 3.4.3. Funció gets() Una altra forma de llegir una cadena de caràcters és utilitzant la funció gets(). La seva sintaxi és la següent: char *gets(char *var);

La variable var representa la cadena que contindrà tots els caràcters teclejats excepte el caràcter \n, que serà automàticament reemplaçat pel caràcter \0 amb què C finalitza tota cadena de caràcters. Per exemple, les següents línies de codi llegeixen i visualitzen la cadena de caràcters nom: char nom[41]; gets(nom); printf("%s\n",nom);

Observeu que el paràmetre var està definit com un punter a un char; és a dir, una adreça que fa referència al lloc on està emmagatzemat un caràcter. Açò és així, perquè com ja s'ha dit, el nom d'un array és l'adreça de començament del array. Per al cas d'una cadena de caràcters, aquesta Professor : Quique Lorente

Pàgina 13


Curs 2007-2008 • DAI 1A • Crèdit 5: Programació estructurada i modular • UD3_NA2_Punters i Cadenes

adreça coincideix amb l'adreça del primer caràcter; i el fi de la cadena està marcat per un caràcter nul. La funció gets() torna un punter a la cadena de caràcters llegida; és a dir, torna la cadena de caràcters llegida. Un valor nul per a aquest punter, indica un error o una condició de fi de fitxer (EOF). Un punter nul està definit en stdio.h per la constant NULL. El següent exemple llegeix cadenes de caràcters fins que s'introdueixi la marca de fi de fitxer. #include<stdio.h> void main() { char text[80]; printf("Introdueix línies de text:\n"); printf("Per a finalitzar introdueix la marca EOF\n\n"); // Llegeix la primera línia de text while(gets(text)!=NULL) { //Operacions amb la línia de text llegida // ... } }

Comparant les funcions scanf() i getchar() amb la funció gets(), podeu observar que aquesta última proporciona una forma més còmoda de llegir cadenes de caràcters i també permet l'entrada d'una cadena de caràcters formada per varies paraules separades per espais en blanc, sense cap tipus de format. 3.4.4. Funció puts() Analògicament, una altra forma d'escriure una cadena de caràcters és utilitzant la funció puts(). La seva sintaxi és la següent: int puts(const chat *var);

La funció puts() de la biblioteca de C escriu una cadena de caràcters en la sortida estàndard, i canvia el caràcter \0 d'acabament de la cadena pel caràcter \n, és a dir, després d'escriure la cadena, s'avança automàticament a la següent línia. Aquesta funció retorna un valor positiu si s'executa satisfactòriament i el valor EOF en cas contrari. Per exemple, les següents línies de codi llegeixen i visualitzen la cadena de caràcters nom: char nom[41]; gets(nom); puts(nom);

Aquest altre exemple que es mostra a continuació, utilitza el valor retornat per la funció gets() per a visualitzar la cadena llegida: #include<stdio.h> void main() { char *c=NULL; // per emmagatzemar el valor retornat per gets char text[80]; printf("Introdueix una línia de text:\n"); Professor : Quique Lorente

Pàgina 14


Curs 2007-2008 • DAI 1A • Crèdit 5: Programació estructurada i modular • UD3_NA2_Punters i Cadenes

c=gets(text); printf("\nEl text introduït és:\n"); puts(text); // equival a: printf("%s\n",text); puts("\nS'escriu per segona vegada:"); puts(c); }

3.4.5. Neteja del buffer. Funció fflush() Ja s'ha comentat l'efecte que produeix el caràcter '\n' que quedava en el buffer d'entrada després d'executar la funció scanf() o getchar(), si a continuació s'executava una altra vegada qualsevol d'elles amb la intenció de llegir caràcters. El que succeïa era que en ser '\n' un caràcter vàlid per a aquestes funcions, era llegit i no s'obtenia l'entrada que l'usuari esperava. Això mateix passarà amb la funció gets(), si quan s'executi hi ha un caràcter '\n' en el buffer d'entrada, perquè prèviament s'ha executat alguna de les funcions citades. La solució és netejar el buffer després d'haver invocat a les funcions scanf() o getchar(). 3.5. Treballar amb cadenes de caràcters El següent exemple és una funció que converteix els caràcters en minúscules d'una cadena, a majúscules. Si observeu la taula ASCII, comprovareu que els caràcters 'A', ..., 'Z', 'a', ..., 'z' són consecutius i en l'ordre ascendent del seu codi (valors 65 a 122). Aleshores, passar un caràcter de minúscules a majúscules suposa restar al valor enter (codi ASCII) associat amb el caràcter, la diferència entre els codis d'aquest caràcter en minúscula i el mateix en majúscula. Per exemple, la diferència 'a'-'A' és 97-65=32, i és la mateixa que 'b'-'B', que 'c'-'C', etc. Com podeu observar en el codi mostrat a continuació, la funció rep la cadena que es desitja passar a majúscules. A continuació, accedeix al primer element de la cadena i comprova si es tracta d'una minúscula; en aquest cas canvia el valor ASCII emmagatzemat en aquest element pel valor ASCII corresponent a la majúscula. Açò és: void MinusculesMajuscules(char cad[]) { int i, desp='a'-'A'; for(i=0; cad[i]!=NULL; i++) if(cad[i]>='a' && cad[i]<='z') cad[i]=cad[i]-desp; }

La solució que s'ha donat al problema plantejat no contempla els caràcters típics de la nostra llengua com la ç o les vocals accentuades.

Professor : Quique Lorente

Pàgina 15


Curs 2007-2008 • DAI 1A • Crèdit 5: Programació estructurada i modular • UD3_NA2_Punters i Cadenes

4. Funcions de biblioteca 4.1. La biblioteca string.h Les funcions estàndards més importants que permeten treballar amb cadenes de caràcters i que estan definides a la biblioteca string.h són les que a continuació es descriuen: int strlen(const char *cadena)

Torna el nombre de caràcters de la cadena, sense comptar el caràcter '\0'

char *strcpy( char *cadesti, const char *cadfont );

Copia el contingut de la cadena cadfont en la cadena cadesti. En aquesta funció s'ha de tenir cura que la cadena cadfont no sigui més gran que la cadena cadesti. Aquesta funció elimina el contingut previ de la cadena cadesti.

char *strncpy( char *cadesti, const char *cadfont, int n);

Copia el contingut dels n primers caràcters de cadfont en cadesti. Si n és més petit o igual que la longitud de la cadena cadfont, no s'afegeix cap caràcter NULL de fi de cadena. Si n és més gran que la longitud de la cadena cadfont, la cadena cadesti, s'omple de caràcters NULL fins a la longitud n. Aquesta funció, com l'anterior, no fa comprovació de longitud i, per tant, el comportament pot ser desastrós si la longitud de la cadena cadfont i n són més grans que la longitud de la cadena cadesti.

char *strcat( char *cadesti, const char *cadfont );

Afegeix la cadena cadfont al final de la cadena cadesti. La cadena cadfont no canvia el seu contingut. El primer caràcter afegit substitueix al caràcter fi de cadena anterior.

int strcmp( char *cad1, char *cad2 );

Aquesta funció compara les cadenes cad1 i cad2. Torna un 0 si coincideixen, un nombre negatiu si cad1 és alfabèticament anterior a cad2, i un nombre positiu si és al revés.

int strncmp( char *cad1, char *cad2, int n );

Compara els n primers caràcters de les cadenes cad1 i cad2. La comparació es fa de la mateixa forma que a la funció anterior.

int strcmpi( char *cad1, char *cad2, int n );

Compara les cadenes cad1 i cad2. No distingeix entre majúscules i minúscules.

char *strchr( const char *cad, int c );

Aquesta funció retorna un punter a la primera ocurrència del caràcter c en la cadena cad. Si aquest caràcter no hi és torna un NULL.

char *strstr( const char *cad, const char *subcad );

Aquesta funció retorna un punter a la primera ocurrència de la subcadena subcad en la cadena cad o bé un punter a NULL si no es troba.

char *strpbrk( const char *cad, const char *subcad );

Aquesta funció retorna un punter a la primera ocurrència de qualsevol dels caràcters de subcad en la cadena cad o bé un punter a NULL.

Les funcions de cadena declarades a string.h inclouen la paraula reservada const. L'avantatge d'aquesta paraula reservada és que es pot veure ràpidament la diferència entre els paràmetres d'entrada i sortida. Per exemple, el segon paràmetre de strcpy() representa la cadena font; i Professor : Quique Lorente

Pàgina 16


Curs 2007-2008 • DAI 1A • Crèdit 5: Programació estructurada i modular • UD3_NA2_Punters i Cadenes

s'utilitza només per a copiar caràcters d'ella, de manera que aquesta cadena no es modificarà. La paraula reservada const s'utilitza per a aquesta tasca. Es considera un paràmetre d'entrada, ja que la funció rep dades a través d'ella. En contrast, el primer paràmetre de strcpy() és la cadena destinació, la qual se sobreescriurà i, en conseqüència, no s'ha d'utilitzar const. En aquest cas, el paràmetre corresponent s'anomena paràmetre de sortida, ja que les dades s'escriuen en aquest paràmetre. 4.2. La biblioteca ctype.h A més d'aquestes funcions, n'hi ha altres que poden ser útils per al tractament de cadenes. Les declaracions d'aquestes funcions estan a la biblioteca ctype.h: int int int int int int int

isalpha(int isupper(int islower(int toupper(int tolower(int isdigit(int isalnum(int

c) c) c) c) c) c) c)

int isspace(int c)

Torna un valor diferent de 0 si c és una lletra i 0 si no ho és. Torna un valor diferent de 0 si c és una lletra majúscula i 0 si no ho és. Torna un valor diferent de 0 si c és una lletra minúscula i 0 si no ho és. Converteix c a majúscula. Si c no és una lletra, torna c Converteix c a minúscula. Si c no és una lletra, torna c Torna un valor diferent de 0 si c és un dígit i 0 si no ho és. Torna un valor diferent de 0 si c és una lletra o un dígit i 0 si no ho és. Torna un valor diferent de 0 si c és un espai, un tabulador o el caràcter de nova línia i 0 si no és cap d'aquests caràcters.

4.3. La biblioteca stdlib.h És molt freqüent haver de convertir números emmagatzemats en cadenes de caràcters a tipus de dades numèrics. C proporciona les funcions atoi(), atof() i atol() que realitzen aquestes conversions. Aquestes tres funcions s'inclouen a la biblioteca stdlib.h, i converteixen a int, double i long respectivament.

Professor : Quique Lorente

Pàgina 17


Curs 2007-2008 • DAI 1A • Crèdit 5: Programació estructurada i modular • UD3_NA2_Punters i Cadenes

Apèndix: Biblioteca de problemes Punters 1. Quina sortida produeix els següent programa? #include <stdio.h> void main() { int x, y; int *px, *py, *pz; x=5; y=2; px=&x; printf("%d", py=&y; printf("%d", *px=*py; printf("%d", *px=5; px=py; printf("%d", printf("%d",

*px); *py); x);

x); *px);

}

2. Elimina les línies incorrectes i esbrina quina sortida produeix el següent programa: #include <stdio.h> void main() { int a,b,c,*p,*q,**pp; a=2;b=3;c=5; p=&a; q=&b; pp=&c; q=&pp; q=*a; p=*q; *q=7; *pp=8; *q=*p; p=q; pp=&&a; **pp=17; pp=&q; **pp=23; &p=&q; &b=p; *p=b; *q=**pp; } Professor : Quique Lorente

Pàgina 18


Curs 2007-2008 • DAI 1A • Crèdit 5: Programació estructurada i modular • UD3_NA2_Punters i Cadenes

3. Escriviu un programa que generi de manera aleatòria un vector d'enters entre 0 i 9, de dimensió especificada per l'usuari. El programa haurà de mostrar el vector generat per pantalla. Realitzeu l'exercici de manera modular i utilitzant la memòria dinàmica.

Cadenes. Exercicis bàsics 4. Escriviu un programa que llegeixi una paraula i calculi la seva longitud. 5. Escriviu un programa que llegeixi una frase (formada per diverses paraules) i calculi la seva longitud. 6. Escriviu un programa que llegeixi un text (format per diverses línies) i calculi la seva longitud. 7. Realitzeu un programa que llegeixi una frase i transformi les lletres majúscules a minúscules i viceversa. 8. Realitzeu un programa que demani una frase i l'escrigui al revés. 9. Realitzeu un programa que demani frases fins que s'introdueixi la frase "fi". 10. Realitzeu un programa que demani tres frases i les concateni per a formar una quarta. 11. Realitzeu un programa que demani frases fins que s'introdueixi "." i emmagatzemi en un vector de quatre elements enters el nombre de cadenes amb longitud: • • • •

menor que cinc entre cinc i deu entre onze i quinze major que quinze

Al finalitzar s'imprimirà el percentatge total que representa cada compte. 12. Realitzeu un programa que demani noms i notes de deu alumnes. Al finalitzar, mostrarà el nom de l'alumne amb major nota. 13. El document d'identitat consta d'un número de 8 xifres i d'una lletra. La lletra s'obté calculant el residu de dividir el número del DNI entre 23. Segons aquest valor se selecciona la lletra associada en la següent taula: 0

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

T

R

W

A

G

M

Y

F

P

D

X

B

N

J

Z

S

Q

V

H

L

C

K

E

Feu un programa que proporcioni dues opcions: • Donat un número de DNI, calcular la lletra que li correspongui. • Donat un DNI amb lletra, comprovar si és correcte. 14. Escriviu un programa que llegeixi una frase i converteixi la primera lletra de cada paraula en majúscula.

Cadenes. Exercicis avançats. 15. La biblioteca string.h disposa de les funcions strlen(), strcpy() i strcat(). Escriviu unes funcions equivalents a aquestes, sense utilitzar aquesta biblioteca. 16. Feu un programa que calculi el nombre de pulsacions per minut que es té escrivint amb el teclat. Professor : Quique Lorente

Pàgina 19


Curs 2007-2008 • DAI 1A • Crèdit 5: Programació estructurada i modular • UD3_NA2_Punters i Cadenes

El programa ha de demanar la introducció d'un text. El programa detectarà la primera pulsació i començarà a comptar el temps des de llavors. Per acabar el text, l'usuari haurà de prémer CTRL-z. Llavors, el programa haurà d'indicar-nos: • • •

El nombre total de pulsacions. El temps en segons emprat. La velocitat expressada en pulsacions per minut.

Per poder comptar el temps tenim la funció time() la declaració de la qual es troba a l'arxiu de capçalera time.h. Cridant-la amb el paràmetre NULL torna el nombre de segons transcorreguts. 17. Realitzeu un programa que calculi el nombre de línies, paraules i caràcters d'un text introduït per teclat. 18. Feu un programa que compti el nombre de vegades que apareix cada caràcter dins d'un text. No considereu accents ni els caràcters 'ç' i 'ñ'. 19. Escriviu un programa per reconèixer palíndroms. Un palíndrom és una frase que es llegeix igual de dreta a esquerra que d'esquerra a dreta. No es té en compte els espais. En aquest exercici no cal tampoc que considereu els accents o dièresis. Exemples de palíndroms: • A flacs ell escalfa. • Un avi salta l'atlas, i va nu. A la pàgina http://www.fut.es/~mgine/palidi.htm#catala podeu trobar molts exemples de palíndroms en molts idiomes. 20. Feu una funció que elimini una subcadena especificada. 21. Feu una funció que insereixi una cadena dins d'una altra en una posició especificada. 22. Feu una funció que reemplaci una subcadena per una altra. Les dues seran de la mateixa longitud.

Punters i Cadenes. Exercicis d'ampliació. 23. Escriviu un programa que demani una matriu de reals, de dimensió especificada per l'usuari. El programa haurà de mostrar la matriu per pantalla. Realitzeu l'exercici de manera modular i utilitzant la memòria dinàmica. 24. Escriviu un programa que llegeixi una frase i escrigui en pantalla tantes línies com paraules té la frase; cada línia que escriu, a partir de la primera, sense l'última paraula de la línia anterior. Exemple: Introdueix una cadena: Açò és una prova Açò és una prova Açò és una Açò és Açò

25. Realitzeu un programa que utilitzi una funció per a passar un nombre en base binària a base decimal.

Professor : Quique Lorente

Pàgina 20


Curs 2007-2008 • DAI 1A • Crèdit 5: Programació estructurada i modular • UD3_NA2_Punters i Cadenes

26. Realitzeu un programa que utilitzi una funció per a passar un nombre en base decimal a una base qualsevol. 27. Feu un programa que compti el nombre de vegades que apareix cada caràcter dins d'un text. Considereu també els caràcters 'ç' i 'ñ'. Feu que compatibilitzi els caràcters {a, á, à, A, Á, À} junts, etc. 28. Torneu a implementar l'exercici dels palíndroms però considerant també els accents o dièresis, de forma que agafi com a palíndroms exemples com els següents: • •

A Cornellà, Tània i Aina tallen roca. Tip, el pastor ara farà rots a ple pit.

29. Escriviu un programa que llegeixi una cadena de fins a deu caràcters que representa a un número en numeració romana i mostri el seu equivalent en numeració aràbiga. Els caràcters romans i la seva equivalència són: M

1000

C

100

D 500 L 50 Comproveu el programa per a les següents dades:

X

10

V

5

I

1

LXXXVI (86), CCCXIX (319), MCCLIV (1254), CMXCIV (999) 30. Escriviu una funció que compti el nombre de cops que una cadena està continguda dins d'una altra. 31. Feu una funció que reemplaci una subcadena per una altra. Poden ser de longitud diferent.

Professor : Quique Lorente

Pàgina 21


C5 programacio