Docsity
Docsity

Prepara i tuoi esami
Prepara i tuoi esami

Studia grazie alle numerose risorse presenti su Docsity


Ottieni i punti per scaricare
Ottieni i punti per scaricare

Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium


Guide e consigli
Guide e consigli


Programmazione Procedurale - domande, Appunti di Programmazione Orientata agli Oggetti

Domande ed esercizi con soluzioni codice in C e C++

Tipologia: Appunti

2024/2025

In vendita dal 23/02/2026

silvia-isopo-1
silvia-isopo-1 🇮🇹

13 documenti

1 / 27

Toggle sidebar

Questa pagina non è visibile nell’anteprima

Non perderti parti importanti!

bg1
Programmazione
Procedurale
Domande-esercizi
pf3
pf4
pf5
pf8
pf9
pfa
pfd
pfe
pff
pf12
pf13
pf14
pf15
pf16
pf17
pf18
pf19
pf1a
pf1b

Anteprima parziale del testo

Scarica Programmazione Procedurale - domande e più Appunti in PDF di Programmazione Orientata agli Oggetti solo su Docsity!

Programmazione

Procedurale

Domande-esercizi

  1. Funzioni per la gestione della memoria dinamica in C Malloc (dim) → usata per prelevare dallo Heap una quantità di memoria (in byte) pari al valore specificato come parametro. Se ha successo restituisce l'indirizzo da cui posso inserire. Il suo prototipo è void *malloc(size_t_size); Calloc (n, dim) → usata per prelevare dallo Heap una quantità di memoria sufficiente a memorizzare un vettore di n oggetti ciascuno di dim byte. Il suo prototipo è void *calloc (size_t nmemb, size_t size); La memoria allocata con calloc viene inizializzata mettendo tutti i bit a 0, il che in certi casi è utile/necessario, ma comporta inevitabilmente un tempo di esecuzione maggiore rispetto a malloc, soprattutto se il blocco di memoria richiesto è grande. Free → Lo spazio allocato dinamicamente tramite malloc() e calloc() non viene restituito al sistema al termine delle funzione, bisogna usare free(). Il prototipo è void free(void *ptr);
  1. Funzioni principali per la gestione dei file in C ▫ Fopen() → si usa la funzione FILE *fopen(const char *filename, const char *mode); usata per effettuare operazione associate all’apertura di un file bufferizzato. Il primo parametro rappresenta il nome del file da aprire, il secondo la modalità di apertura. Ce ne sono diverse: ▫ Fclose() → quando un file non è più utilizzato è necessario chiuderlo, tramite una chiamata alla funzione int fclose(FILE fp); ▫ Fprint() e fwrite() → fprintf funziona come printf. L'unica differenza è che le informazioni vengono scritte nel file associato al puntatore fp (a partire dalla posizione corrente). Nel caso della fwrite vengono scritti nel file individuato da fp, n oggetti di dimensione el_size (nel_size byte) letti nel vettore specificato da a_ptr. ▫ Fscan() e fread() → Il comportamento della funzione fscanf è analogo a quello della funzione scanf. La differenza consiste nel fatto che le informazioni vengono lette dal file associato al File di testo File binari Lettura “r” “rb” Scrittura “w” “wb” Aggiunta in coda “a” “ab” int fprintf(FILE *fp,const char *format, ... ); size_t fwrite(const void *a_ptr, size_t el_size, size_t n, FILE *fp); int fscanf(FILE *fp,const char *format, ... ); size_t fread(void *a_ptr, size_t el_size, size_t n,FILE *fp)
  1. Cos'è il puntatore this? Si tratta di un puntatore implicitamente aggiunto come parametro a tutti i metodi di una classe (tranne quelli statici) e destinato a contenere l'indirizzo dell'oggetto istanza della classe su cui il metodo viene invocato. È il modo attraverso il quale il codice dei metodi (condiviso da tutti gli oggetti) riesce ad accedere alle variabili d'istanza (specifiche per ciascun oggetto).
  1. Che differenza c'è tra binding dinamico e statico? Il binding dinamico è supportato in C++? Se sì, in che modo? Per binding si intende il processo di associazione nome-di-procedura indirizzo. Il binding è detto dinamico se tale associazione viene fatta durante l'esecuzione (in contrapposizione al binding statico dove l'associazione avviene in compilazione). Con il binding dinamico solo al momento dell'esecuzione è noto l'indirizzo della procedura da eseguire a fronte di una data chiamata. Il C++ supporta il binding dinamico attraverso il meccanismo delle funzioni virtuali. Per ogni funzione dichiarata virtual viene riservato un puntatore destinato a contenere l'indirizzo preciso della funzione da eseguire a fronte della chiamata. A fronte della chiamata obj.f() il compilatore genera del codice che prevede un salto all'istruzione il cui indirizzo è contenuto in una posizione opportuna di una tabella contenente gli indirizzi di tutte le funzioni virtuali della classe di cui obj è istanza). L'oggetto memorizza al suo interno un solo puntatore indipendentemente dal numero delle funzioni virtuali esistenti nella classe.
  2. Quali regole adotta il linguaggio C per il passaggio dei parametri? L'unica regola adottata per il passaggio dei parametri è quella per copia o valore. I parametri attuali vengono valutati al momento della chiamata e i valori ottenuti copiati all'interno del record di attivazione della procedura. Nel caso dei vettori, viene copiato l'indirizzo base dello stesso (e non l'intero vettore). Lo stesso non accade per le strutture, delle quali viene effettuata una copia campo per campo. Si può simulare una call by reference utilizzando i puntatori, ovvero passando (per copia) l'indirizzo dell'oggetto a cui si vuole accedere.
  1. Quali regole adotta il linguaggio C per il passaggio dei parametri? Il C supporta unicamente la call by value , ovvero il passaggio per copia (o valore). All'atto della chiamata vengono valutate le espressioni in corrispondenza dei parametri formali e i valori ottenuti vengono copiati all'interno del record di attivazione della procedura. Unica eccezione è costituita dai parametri di tipo vettore, in questo caso non viene copiato l'intero vettore ma semplicemente passato l'indirizzo base del vettore.
  1. Quali sono i principali fattori di qualità del software che traggono vantaggio da un approccio orientato agli oggetti? Tra i principali fattori di qualità che traggono vantaggio dall'approccio OOP c’è:
    • Correttezza: comportarsi secondo le specifiche e i requisiti
    • Robustezza: capacità di funzionare anche in condizioni anomale
    • Estendibilità: facilità di adattamento del SW al cambiamento delle specifiche
    • Riutilizzo: possibilità di recuperare (porzioni di) un programma per nuove applicazioni
    • Compatibilità: facilità con cui programmi diversi interagiscono
  1. Illustrate i criteri utilizzati per la selezione del metodo da eseguire quando un oggetto riceve un messaggio che richiede l'esecuzione di un metodo con selettore p. Distinguete i due casi, ereditarietà semplice ed ereditarietà multipla. Un oggetto x che riceve un messaggio con selettore p ricerca innanzitutto nella propria classe d'appartenenza X se esiste un metodo con tale nome. Se così allora è tale metodo ad essere eseguito, altrimenti distinguiamo i due casi:
    • Ereditarietà semplice → L'albero di ereditarietà fornisce un ordine totale delle superclassi di X. Il metodo che viene eseguito è quello che si trova nella prima superclasse che contiene un metodo con nome p e che si trova sul percorso che parte da X e si dirige verso la radice.
    • Ereditarietà multipla → Il grafo di ereditarietà non definisce in generale un ordine totale delle superclassi di X. Possono sorgere ad esempio dei conitti tra due superclassi Y1 e Y2 di X che non sono confrontabili (ovvero nel grafo d'ereditarietà non esiste un cammino che parte da Y1 e arriva a Y2, o viceversa) e che contengono un metodo di nome p. Non esiste una strategia generale in grado di risolvere automaticamente i conitti. Nel caso del C++, i conitti vengono segnalati in compilazione e risolti (manualmente) attraverso l'uso dell'operatore di scope resolution (::).
  1. Cosa fa il preprocessore Il preprocessore è un componente che esegue trasformazioni sul codice sorgente prima della compilazione. Opera tramite direttive (iniziano con #), che non producono codice eseguibile ma influenzano la compilazione.
  1. Illustrate le principali direttive del preprocessore C. ▫ La direttiva include permettere di importare un file header in un file c (#include)
  1. Cosa sono le funzioni virtuali? Illustrate il modo in cui il linguaggio C++ le implementa. Ad ogni classe con almeno una funzione virtuale è associata una tabella contenente gli indirizzi di ciascuna funzione virtuale presente nella classe. Ogni oggetto p che è istanza di una classe X con almeno una funzioni e virtuale ha tra i campi un puntatore (nascosto) inizializzato con l'indirizzo base della tabella associata a X. L'invocazione di un metodo virtuale per p (anche se operato tramite puntatore) viene implementato attraverso un indirizzamento indiretto, un primo accesso a una certa posizione della tabella (il cui indirizzo è dato dalla somma tra l'indirizzo base della tabella - contenuto nel puntatore nascosto è un offset individuato dal nome del metodo invocato) seguito da un trasferimento del controllo all'indirizzo trovato in tabella.
  2. Quali sono le principali caratteristiche del linguaggio C++ che non sono presenti in Java? Le principali caratteristiche del C++ non presenti in Java sono:
    • i puntatori
    • l'ereditarietà multipla
    • l'overloading degli operatori
  1. Come si comporta il C++ nei confronti di tipizzazione e binding C++ è un linguaggio con tipizzazione statica, ovvero il tipo di un elemento sintattico è noto al momento della compilazione. Questo permette di effettuare dei controlli sulle espressioni al fine di intercettare in compilazione eventuali errori di tipo. Non è però possibile escludere il verificarsi di errori di tipo durante l’esecuzione (C++ non gode della type safety – si pensi ad un uso poco accorto delle conversioni di tipo). C++ supporta invece il binding dinamico.
  1. Elencate i diversi usi del valore 0 in C.
    • rappresenta il valore di verità falso
    • viene usato come valore per il puntatore nullo
    • viene usato come terminatore di stringa (carattere appeso in coda a ogni stringa)
    • è l’indice del primo elemento di un vettore
  1. Illustrate le quattro classi di memorizzazione associate alle parole riservate del linguaggio C auto, extern, register, static. Auto → La classe di memorizzazione automatica è quella di default per le variabili locali, ma può essere specificata esplicitamente con la parola riservata auto. Lo spazio per memorizzare una variabile automatica viene allocato all’interno del record di attivazione della funzione a cui appartiene la variabile. Di conseguenza, tale spazio rimane occupato solo per la durata dell’esecuzione della funzione, dopodiché viene rilasciato.

Register → La classe di memorizzazione registro, specificata con la parola riservata register, si applica a una variabile locale per suggerire al compilatore di memorizzare tale variabile in un registro del processore, invece che nel record di attivazione (nell’area di stack della memoria). Ciò può essere utile, ad esempio, per accelerare l’accesso a variabili che vengono lette e/o modificate di frequente, come ad esempio il contatore di un ciclo. Se il compilatore determina che i registri sono già tutti occupati, oppure se la dimensione della variabile non è compatibile con quella dei registri disponibili — i registri di un processore sono pochi e hanno dimensioni molto limitate — allora la variabile viene gestita come se fosse automatica, ovvero memorizzata nel record di attivazione. Extern → La classe di memorizzazione esterna, indicata con la parola riservata extern, si applica sia alle variabili che alle funzioni. Nel caso di una variabile globale o di un prototipo di funzione, la classe di memorizzazione esterna indica al compilatore che la definizione completa di tale variabile o funzione è situata in un altro file (ad esempio, potrebbe essere una definizione di libreria). Static → La classe di memorizzazione statica, indicata con la parola riservata static, può essere applicata alle variabili locali, oppure alle variabili globali e alle funzioni. Quando viene applicata a una variabile locale, la classe di memorizzazione statica indica che la variabile conserva il proprio valore tra una chiamata e l’altra della funzione in cui è definita. Nel caso delle variabili globali e delle funzioni, la parola riservata static funge da restrizione di visibilità: una variabile globale o funzione con l’attributo static è visibile solo all’interno del file in cui è definita (a partire dal punto della definizione), mentre non è possibile accedervi (tramite dichiarazioni extern) dagli eventuali altri file dello stesso programma.

  1. Il C permette di definire funzioni che hanno tra i parametri matrici? Quali convenzioni bisogna rispettare? Per poter definire funzioni che accettano come parametro matrici di dimensioni arbitrarie occorre rappresentare una matrice come vettore di puntatori alle righe. Ad esempio, dato un tipo base T per la matrice, una funzione che accetta in input una matrice di tipo T con m righe e n colonne e che restituisce l'elemento sull'ultima riga e sull’ultima colonna sarebbe definita come T f(T*,int m,int n){return T[m-1][n-1];} Si noti che tale funzione non opererebbe correttamente se chiamata fornendo in input una matrice definita come T mat[a][b] in quanto l'espressione T[m-1][n-1] corrisponde a ((T+m-1)+n-1),con il valore di T+m- 1 che corrisponde al valore di T incrementato del valore intero (m-1)k dove k è il numero di byte associati a un puntatore e non al tipo T.

In C++, è possibile ereditare una classe in tre modi: public , protected e private. Se una classe viene ereditata con public, i membri public e protected della classe base mantengono il loro livello di accesso nella classe derivata, mentre i membri private restano inaccessibili. Se l'ereditarietà è protected, i membri public della classe base diventano protected nella classe derivata, mentre i protected rimangono tali e i private restano inaccessibili. Con un'ereditarietà private, invece, sia i membri public che quelli protected della classe base diventano private nella classe derivata, impedendo a ulteriori sottoclassi di accedere direttamente a questi membri.

  1. Illustrate il layout del record di attivazione di procedura in C. Una volta che un record di attivazione è posto in cima allo stack l'accesso alle variabili locali avviene attraverso indirizzi della forma frame pointer + displacement. Dove displacement è calcolato durante la compilazione e indica lo spiazzamento rispetto ad un indirizzo base rappresentato da frame pointer.
  1. A cosa serve il distruttore di una classe e quando viene chiamato? Ogni classe deve avere un distruttore? Il distruttore viene utilizzato per gestire le operazioni che si rendono necessarie quando un oggetto muore, ovvero quando esce dallo scope. Se l’oggetto (tramite i metodi della classe) ha richiesto memoria dinamica, è questo il momento in cui deve essere restituita. Non è necessario che ogni classe sia dotata di distruttore.
  1. Cosa si intende per classe astratta Una classe astratta è una classe che contiene almeno un metodo costituito da una funzione virtuale pura e tipicamente non permette di fissare un’implementazione concreta per ogni metodo.

Parametro Parametro Area di salvataggio stato della memoria Variabili locali Variabili temporanee

  1. Parola friend nel C++ La parola riservata friend viene utilizzata per consentire a singole funzioni o a intere classi di acquisire gli stessi privilegi d’accesso che una classe stessa possiede nei riguardi dei propri campi e metodi. Nel caso in cui una classe A dichiarasse amica un’intera classe B, l’accesso sarebbe consentito a tutte le funzioni membro di B (se si vuole, si può dichiarare amica anche una sola funzione f di B scrivendo friend B::f() in A.
  1. Cosa si intende per conitto in una gerarchia di classi? Esiste una tecnica automatica per la risoluzione dei conitti? Come si comporta C++ al riguardo? Si osserva un conitto in presenza di nomi di metodi (o attributi) presenti in classi non confrontabili rispetto alla relazione di ereditarietà. Non esiste una tecnica generale per la risoluzione dei conitti e che rispetti i vincoli espressi tramite molteplicità e visione modulare. C++ si limita a segnalare il conitto lasciando al programmatore la sua soluzione (tramite l'uso dell'operatore di scope resolution ::).
  1. Cosa si intende per genericità? È presente in C++? Per genericità si intende la possibilità di usare tipi come variabili nella definizione di classi e funzioni. È una delle caratteristiche più interessanti dei linguaggi orientati agli oggetti in quanto favorisce il riutilizzo del software. È presente in C++ attraverso i Template Un template è un meccanismo che consente di definire classi e funzioni generiche, utilizzando tipi di dati come parametri. Può essere di due tipi: di funzione o di classe
  1. Cos'è la tabella delle funzioni virtuali? Dato un vettore di Figure geometriche A spiegate come viene calcolato l'indirizzo del metodo (virtuale) stampa da chiamare a fronte dell'invocazione A[i].stampa(). La tabella delle funzioni virtuali è una tabella contenente puntatori a funzione e che viene creata per ogni classe che possiede almeno un metodo dichiarato virtuale. In concreto, l'indirizzo della specifica versione di ogni funzione virtuale della classe viene inserito in tabella, in modo tale che se una classe ha k funzioni virtuali la tabella conterrà k puntatori. Ogni oggetto che è istanza di una classe con (almeno) una funzione virtuale ha tra i membri un puntatore contenente l'indirizzo della tabella associata alla classe. Così facendo, l'invocazione A[i].stampa() corrisponde ad andare ad eseguire la funzione il cui indirizzo è calcolato tramite due accessi in memoria. Si accede al puntatore presente nell'oggetto A[i], detto il suo valore, la funzione da eseguire si trova all'indirizzo contenuto all'indirizzo α+k- 1 (aritmetica dei puntatori) dove k è la posizione del metodo A[i].stampa() tra le funzioni virtuali della classe di cui A[i] è istanza ( k = 1 se la classe ha una sola funzione virtuale).
  1. Considerate le dichiarazioni int x; Complex y; float z; e l'istruzione x=f(y,z); e spiegate quale processo metto in atto il compilatore per identificare la funzione f da chiamare tra le numerose funzioni di nome f (overloading).

Esercizi Programmazione Procedurale

  1. Definite una MACRO Max(a,b,c) da utilizzare per ottenere il massimo dei valori di tre espressioni a, b e c. In quali casi la macro crea inevitabilmente dei problemi? #define Max(a,b,c){(a>b)? ((a>c)?(a):(c)): ((b>c)? (b):(c))} Seppur correttamente definita, la macro non ha il comportamento atteso nel caso in cui le espressioni contengano operatori con side-effect. Ad esempio, se a vale 6, b vale 4 e c vale 3, il valore di Max(--a,b,c) è 3 anziché 6.
  1. Definite una MACRO scambio(a,b) da utilizzare per scambiare il contenuto di due variabili a e b di tipo int. Per fare in modo che le istruzioni che realizzano lo scambio vengano considerate alla stregua di una singola istruzione semplice occorre racchiuderle tra parentesi graffe. #define scambio(a,b) { int tmp; tmp =a; a=b; b=tmp;}
  1. Date le definizioni int A[4][5]; int i,j; scrivete un'espressione C che risulti equivalente a A[i][j]=7 e che non faccia uso dell'operatore []. Spiegatene il significato. La matrice è vista come un array di array, di conseguenza l'identificatore A può essere utilizzato come se fosse un puntatore (costante) inizializzato con l'indirizzo del primo elemento (ovvero l'indirizzo in memoria della prima riga). Un'espressione equivalente a quella indicata è quindi ((A+i)+j)=7, dove *(A+i) rappresenta l'indirizzo del primo elemento della riga di indice i , al quale viene sommato j (aritmetica dei puntatori) per ottenere l'indirizzo &A[i][j].
  2. Scrivete il codice di una funzione MinString che accetta in ingresso un vettore di stringhe e una funzione da utilizzare per il confronto di stringhe, e che restituisce la stringa più piccola. char* MinString(char m,int n,int (min)(char,char)) {int i; char stringa=m[0]; for(i=1;i
  3. Data la definizione int Mat[10][20], scrivete un'espressione equivalente a Mat[i][j]=7 e che non usi l'operatore []. Il valore di pt+i è uguale all'intero ottenuto sommando al valore di pt l'intero isizeof(pt)), l'espressione equivalente è: (&Mat[0][0] + 20i + j)=7.
  1. Considerate la struttura struct nodo {char info; struct nodo* next;} per rappresentare i nodi di una lista di caratteri. Scrivete una procedura C HalfList che accetta in ingresso una lista di caratteri e la modifica eliminando quelli che occupano le posizioni pari.
  1. Considerate la struttura struct nodo {unsigned cifra; struct nodo* next;} per rappresentare i nodi di una lista. Scrivete una funzione C IntToList che accetta in ingresso un intero senza segno e restituisce una lista contenente le singole cifre (le unità all'ultimo posto).
  1. Definite una funzione Conta a che riceve in ingresso una stringa (vettore di caratteri) e restituisce il numero totale di vocali 'a' presenti in tutte le stringhe su cui è stata chiamata. void HalfList(struct nodo* l){ struct nodo* pt; int i; if (l==NULL || l->next==NULL) return; //lista nulla o di un solo elemento pt=l->next; // pt punta al secondo elemento while(pt!=NULL) {l->next=pt->next; //ora il nodo in posizione i(d) punta a quello i+ l=l->next;//avanza sul prossimo nodo dispari; free(pt); //libera il nodo pari alle spalle if(l!=NULL)pt=l->next; //avanza sul prossimo nodo pari else pt=l;} return; } typedef struct nodo nodo; nodo* IntToList(unsigned n) {nodo * first=(nodo)malloc(sizeof(nodo)); if (first==null) return first //memoria esaurita nodo * last=first; last->cifra=n%10; last->next=null; n=n/10; while(n!=0){ first=(nodo)malloc(sizeof(nodo)); if (first==null) return first //memoria esaurita first->cifra=n%10; first->next=last; last=first; n=n/10;} return first;}

_______________________________________________________________________________________

  1. Data la struttura struct nodo {short cifra; struct nodo* next;}, scrivete una funzione che riceve in ingresso un intero senza segno e restituisce una lista i cui nodi contengono le singole cifre, a partire dalle unità (ad esempio all'intero 123 viene fatta corrispondere la lista 3! 2! 1).
  1. Definite una funzione ContaVoc che riceve in ingresso una matrice di caratteri e restituisce il numero di vocali presenti (si supponga che i caratteri siano tutti minuscoli). Nota: la funzione deve operare su matrici di ordine arbitrario. La matrice deve essere necessariamente rappresentata tramite un vettore di puntatori a caratteri (le righe della matrice). In tal caso possiamo scrivere int NUnicoFiglio(struct nodo* t) {if(t==NULL) return 0; if(t->sx==NULL&&t->dx==NULL) return 0; if(t->sx==NULL) return NUnicoFiglio(t->dx)+1; if(t->dx==NULL) return NUnicoFiglio(t->sx)+1; return NUnicoFiglio(t->sx)+NUnicoFiglio(t->dx); } typedef struct nodo nodo; nodo* ToList(unsigned int n) {nodo * pt1=(nodo)malloc(sizeof(nodo)); nodo * pt2=pt1; while(n/10!=0){ pt2->next=(nodo)malloc(sizeof(nodo)); pt2->cifra=n%10; pt2=pt2->next; n=n/10;} pt2->next=null; pt2->cifra=n; return pt1; }

_______________________________________________________________________________________

  1. Scrivete una funzione mialloc che assegna un blocco di un KB di memoria dinamica al puntatore costituente l'unico parametro, e che restituisce un intero che rappresenta il numero di blocchi allocati fino a quel momento. La funzione deve permettere al massimo l'allocazione di 1000 blocchi (alla richiesta del 1001-esimo blocco il puntatore risulterà nullo).
  1. Considerate la struttura struct nodo{int info; struct nodo* next;} per rappresentare i nodi di una lista. Scrivete una procedura C Nto1 che accetta in ingresso una lista e la modifica cancellando tutti i nodi tranne il primo. int ContaVoc(char** mat, int nrighe, int ncol) {int i,j,count; count=0; for(i=0;inext; // pt punta al secondo nodo (se esiste) l->next=NULL; while(pt!=NULL){l=pt;pt=pt->next;free(l);} return; }
  1. Completate la classe in modo che per un oggetto s istanza della classe Stringa l'espressione - s abbia come effetto quello di rovesciare la stringa s. Si tratta di effettuare l'overloading dell'operatore unario - , definendolo amico della classe. In altre parole, basta aggiungere alla classe
  1. Dato il seguente codice C++ spiegate quale sarebbe l'effetto della sua esecuzione sostituendo alternativamente al posto del commento: vedi sotto pA=&d; pA->f(); pA->g(); → La classe D eredita in maniera pubblica da B, che a sua volta eredita pubblicamente da A. Di conseguenza, è possibile assegnare l'indirizzo di un oggetto di classe D ad un puntatore ad un oggetto di classe A dato che la conversione può sempre avvenire (a prescindere dal luogo in cui è richiesta). Poiché la funzione f() è definita virtuale in A, la chiamata pA->f() esegue la versione presente nella classe D (viene stampato "f() di D"). Similmente, la chiamata pA->g() esegue la versione presente nella classe C (viene stampato "g() di C"), da cui D eredita in maniera protetta. class Stringa{public: Stringa(char c); Stringa(char* s); int size(){return length;}; private: char* st; int length; } Stringa::Stringa(char c){st=new char[2];st[0]=c;st[1]='\0';length=1;} Stringa::Stringa(char* c){int count=0;char* ptr=c; if (c==NULL) {st=c; return;} while(*c!='\0'){count++;c++;} st=new char[count+1]; length=count; while(count>=0){st[count]=ptr[count];count--;} } friend Stringa& operator-(Stringa& s){ char c; int i=0; int j=s.size();j--; while (i pC=&d; pC->f(); → D non rispetta l'interfaccia di C dato che eredita in modalità protetta. La conversione di un puntatore alla classe base protetta può avvenire solo in una funzione amica o in un metodo di D o di una classe derivata da D. Si ha quindi un errore in compilazione sull'istruzione pC=&d. d.g(); → Dato che D eredita da C in maniera protetta, d.g() causa un errore in compilazione poiché g() risulta protetta in D (la versione presente in A non è più accessibile) e la chiamata avviene in una funzione che non _e amica di D o un metodo di D.
  1. Considerate il seguente codice e spiegate cosa avverrebbe eseguendolo ponendo (alternativamente) al posto dei puntini le seguenti istruzioni 1. pA=&d; pA->f(); pA->g(); 2. pC=&d; pC->f(); 3. d.g();
    1. La classe D eredita in maniera pubblica da B, che a sua volta eredita pubblicamente dalla classe base virtuale A. Di conseguenza, chiunque può convertire un puntatore a un oggetto di classe D ad un puntatore ad un oggetto di classe A (D rispetta l'interfaccia di A ed esiste un unico sotto-oggetto A in un oggetto di classe D). Dato che la funzione f() è definita virtuale in A, la chiamata pA->f() esegue la versione presente nella classe D (viene stampato "f() di D"). Similmente, la chiamata pA->g() esegue la versione presente nella classe C (viene stampato "g() di C"), da cui D eredita in maniera protetta.
    2. D non rispetta l'interfaccia di C dato che eredita in modalità protetta e la conversione del puntatore avviene in una funzione che non _e membro di una classe derivata da C (o di una classe dichiarata amica). Si ha quindi un errore in compilazione sull'istruzione pC=&d.
    3. Dato che D eredita da C in maniera protetta, d.g() causa un errore in compilazione poiché g() risulta protetta in D (la versione presente in A non è più accessibile). class A {public:virtual void f() {std::cout <<"f di A\n";}; virtual void g() {std::cout <<"g di A\n";};}; class B: virtual public A {public: virtual void f() {std::cout <<"f di B\n";};}; class C: virtual public A {public: virtual void g() {std::cout <<"g di C\n";};}; class D: public B, protected C {public: virtual void f() {std::cout <<"f di D\n";};}; void main(void){ A* pA;C* pC;A a;C c;D d; ... }