Salta al contenuto
Note per Studenti Esercizio - Quiz su scope, riferimenti, conversioni e puntatori (secondo appello 2022-23, quiz)

Esercizio - Quiz su scope, riferimenti, conversioni e puntatori (secondo appello 2022-23, quiz)

Esame

Quiz del secondo appello del corso di Laboratorio di Programmazione, Ing. Informatica UniPD, a.a. 2022-23 (90 minuti): dieci domande a quattro affermazioni vero/falso (giusta +1+1, sbagliata −0.25-0.25, non data 00). Gli altri esercizi dell'appello: Esercizio - Polimorfismo statico e dinamico, bounded e unbounded (secondo appello 2022-23, teoria), Esercizio - Design pattern, elementi e classificazione (secondo appello 2022-23, teoria), Esercizio - Passaggio per copia e per riferimento con schema dello stack (secondo appello 2022-23, teoria), Esercizio - La classe template DynamicStorage (secondo appello 2022-23, programmazione).

1) Lo scope in C++:

affermazione V/F motivo
Definisce una regione del testo di un programma V è la definizione
Non può essere definito uno scope di funzione all'interno di uno scope di classe F le funzioni membro sono proprio funzioni nello scope di classe (il caso più frequente di scope annidato)
Può essere definito all'interno di un blocco { }, in questo caso si parla di scope locale V
In un file header, è definito delle include guards; in questo caso si parla di scope globale F le include guard sono direttive del preprocessore e non definiscono nessuno scope. Vedi C++ - scope, namespace e librerielo scope è la regione di testo in cui un nome è valido, dal punto di dichiarazione alla fine dello scope; i tipi di scope sono globale (di file), di classe, locale (blocco o lista argomenti di una funzione), di statement (for) e di namespace; funzioni dentro funzioni sono illegali, le classi locali da evitare; i namespace raggruppano nomi senza creare un tipo e si accedono col nome qualificato namespace::membro oppure con using (la using directive va evitata negli header); un header file è un insieme di dichiarazioni che chi lo include può usare (<> per i file di sistema, "" per quelli del progetto), dev'essere protetto dalle include guard (#ifndef/#define/#endif) per non includere due volte le stesse definizioni; il linker unisce i file oggetto con le librerie, statiche (copiate in ogni eseguibile, self-contained) o dinamiche (una sola copia condivisa, .so su Linux, .dll su Windows, ricompilabili senza toccare gli eseguibili).C++ - scope, namespace e librerie →

2) Il passaggio per riferimento di una variabile a una funzione:

3) Le conversioni fra variabili di tipo diverso in C++11:

affermazione V/F motivo
Se sicure, preservano il valore delle variabili V verso un tipo con maggiore capacità
Se non sicure, prevengono il problema del narrowing F le non sicure sono il narrowing: perdono informazione
La notazione { } dà un errore in compilazione nel caso di conversione non sicura V int y {x}; con double x
La notazione ( ) dà un errore in compilazione nel caso di conversione non sicura F le parentesi convertono senza controllo. Vedi C++ - tipi, variabili, espressioni e dichiarazioniun tipo definisce contenuto, intervallo di valori e operazioni; un oggetto è una regione di memoria con un tipo, il valore è la configurazione di bit letta in base al tipo, una variabile è un oggetto con un nome; inizializzazione (dare il primo valore) e assegnamento (cambiare il valore) sono diversi; le conversioni verso un tipo più capiente sono sicure, quelle verso un tipo meno capiente (narrowing, double->int, int->char) non lo sono e il compilatore le segnala di rado, ma la notazione con graffe {} le vieta; un'espressione calcola un valore da operandi e operatori, un lvalue è ciò che può stare a sinistra di =, constexpr vuole un valore noto in compilazione mentre const solo un valore che non cambia; gli operatori con side effect (++, --, +=) non vanno mischiati con altri usi della stessa variabile nella stessa espressione perché l'ordine di valutazione non è definito; una dichiarazione introduce un nome e un tipo, una definizione fornisce anche l'entità (memoria per una variabile, corpo per una funzione).C++ - tipi, variabili, espressioni e dichiarazioni →

4) Il record di attivazione:

5) Il layout della memoria di un programma in esecuzione:

affermazione V/F motivo
L'uninitialized data segment contiene variabili globali e statiche non inizializzate V il BSS
L'heap viene gestito dal programmatore tramite allocazione dinamica della memoria V new/delete
L'initialized data segment è solo read-only F lettura/scrittura (può avere una parte read-only)
Il text segment contiene unicamente il nome dell'eseguibile F contiene le istruzioni eseguibili

6) La funzione STL std::find():

affermazione V/F motivo
È in grado di effettuare una ricerca su container diversi perché ne esistono molte versioni in overloading F ne esiste una sola, template
È in grado di effettuare una ricerca su container diversi perché è una funzione template V opera su iteratori generici: template<typename In, typename T> In find(In first, In last, const T& val)
Ritorna sempre un iteratore, anche se il dato cercato non è stato trovato V se non trova ritorna last, cioè end()
È in grado di operare su container di oggetti di tipo T, indipendentemente dalla definizione della classe T F T deve poter essere confrontato con == (o !=). Vedi STL - algoritmi, predicati e lambdaLa STL ha circa 80 algoritmi generici (header e ) che lavorano su sequenze [first, last) di qualunque contenitore tramite iteratori: find/find_if, count/count_if, sort, copy/copy_if/unique_copy, merge, equal_range, equal, accumulate, inner_product. find cerca un valore e restituisce l'iteratore alla prima occorrenza o end(); find_if verifica un predicato (funzione che restituisce bool) passato come puntatore a funzione, function object (classe con operator()) o lambda expression. Una variabile globale per passare la soglia al predicato è da evitare; il function object la memorizza in un dato membro. Una lambda cattura -> tipo { corpo } definisce la funzione sul posto e può catturare le variabili locali per valore ([=], [x]) o per riferimento ([&], [&x]). sort(b, e) ordina con < e sort(b, e, cmp) con un criterio, solo su iteratori random access; copy richiede spazio a destinazione.STL - algoritmi, predicati e lambda →

7) Il costruttore e l'assegnamento di spostamento di una classe:

affermazione V/F motivo
Sono definiti implicitamente dal compilatore, se non sono previsti dal programmatore V purché la classe non dichiari copia o distruttore (altrimenti non sono generati)
Normalmente causano un overhead ridotto rispetto a costruttore e assegnamento di copia V cedono i dati invece di duplicarli
Hanno come argomento una const reference a un oggetto della stessa classe F hanno una rvalue reference non const (T&&)
Invalidano l'oggetto fornito come argomento V lo lasciano valido ma vuoto. Vedi C++ - costruttori, inizializzazione, copia e spostamentouna buona interfaccia è completa e minimale, ha i costruttori giusti, supporta o proibisce la copia, usa tipi adeguati, dichiara const ciò che non modifica e libera le risorse nel distruttore; il costruttore di default (senza argomenti) esiste quando c'è un valore iniziale sensato, si ottiene con valori di default nei membri (in-class initializer) o argomenti di default, e explicit vieta la conversione implicita da un argomento; initializer_list permette la lista {a, b, c} e con le graffe ha la precedenza sul costruttore a un intero (vector{3} è un elemento, vector(3) sono tre); la copia di default è membro a membro (shallow copy) e con un puntatore a un buffer duplica il puntatore invece del contenuto (due oggetti che condividono i dati e una doppia delete), quindi servono il costruttore di copia e l'assegnamento di copia che fanno la deep copy (l'assegnamento prima alloca e copia, poi libera i vecchi dati, e deve tollerare l'auto-assegnamento); this è il puntatore all'oggetto corrente, parametro implicito delle funzioni membro; il costruttore e l'assegnamento di spostamento (T&&, rvalue reference) rubano il buffer della sorgente lasciandola vuota ma valida, il compilatore li sceglie da solo (con la copy elision a volte non servono nemmeno); i cinque (distruttore, copia, spostamento) vanno definiti insieme.C++ - costruttori, inizializzazione, copia e spostamento →

8) Oggetto b di una classe base B e oggetto d di una sua derivata D:

affermazione V/F motivo
Effettuare un override di una funzione definita in B significa definire una funzione con lo stesso nome in D, anche se numero e tipo degli argomenti sono diversi F per l'override servono stesso nome, stessi tipi e stessa constness; altrimenti si nasconde
È corretta l'istruzione: D* pd = &b; F b non è un D: un downcast implicito non è lecito (servirebbe dynamic_cast)
Dato uno std::vector<B*>, ciascun puntatore dentro al vettore può puntare indifferentemente a un oggetto di classe B o a un oggetto di classe D V un B* può puntare a B o D
Dato uno std::vector<D>, si verifica lo slicing se si tenta di inserire nel vettore un oggetto di classe B F non compila (un B non è un D); lo slicing è l'inserimento di un D in un vector<B>. Vedi C++ - ereditarietà, funzioni virtuali e layout degli oggettiL'ereditarietà pubblica (class D : public B) esprime la relazione "is a": D eredita da B i membri public e protected e li estende; la composizione esprime invece "has a". Una classe base di una gerarchia disabilita copia e assegnamento (=delete) perché copiando una derivata in una base si ha lo slicing. Una funzione virtuale può essere ridefinita nelle derivate (override: stesso nome, tipi e constness) e la funzione chiamata si decide a run-time in base al tipo dell'oggetto, non del puntatore o del riferimento; override esplicito con la keyword override; una funzione virtuale pura (= 0) obbliga le derivate a ridefinirla e rende astratta la classe; il distruttore di una classe base polimorfa è virtuale. Il meccanismo è realizzato con una vtbl per classe (tabella di puntatori alle funzioni) e un vptr in ogni oggetto; i dati della derivata seguono quelli della base.C++ - ereditarietà, funzioni virtuali e layout degli oggetti →

9) La funzione template template<typename T> bool AreEqual(const T& a, const T& b) { return a == b; }:

affermazione V/F motivo
È replicata dal compilatore per ogni tipo per cui è specializzata V
Non impone vincoli su come è definito il tipo passato come parametro template F T deve avere operator==
Non compila se il costruttore di copia di T è disabilitato F i parametri sono const T&: nessuna copia
Compila solo per tipi built-in F va bene per ogni tipo con operator==. Vedi C++ - template e programmazione genericaun template permette di usare un tipo (o un intero noto in compilazione) come parametro di una classe o di una funzione; il compilatore genera il codice per ogni tipo per cui il template è specializzato (template instantiation, a tempo di compilazione): il codice è efficiente ma gli errori sono lunghi e oscuri, e il compilatore controlla sia il template sia il suo argomento; la notazione è template (o class T, equivalenti) davanti alla classe o alla funzione, e le funzioni membro fuori dalla classe si scrivono template tipo Classe::nome(...); nelle function template il compilatore deduce gli argomenti dai parametri della chiamata (myMax(i, j) invece di myMax(i, j)), ma due argomenti di tipo diverso per lo stesso T sono un errore; una specializzazione esplicita (template<> ...) fornisce il codice per un tipo particolare; i parametri interi (template<typename T, int N> struct array) danno array di dimensione fissa senza free store, più efficienti e che esprimono concetti come la tripletta RGB; poiché il compilatore deve vedere la definizione completa al punto d'uso, le definizioni si mettono nell'header (spesso in un file .hpp incluso in coda al .h).C++ - template e programmazione generica →

10) Il codice int x{3}, y{8}; int *p = &x; int *q = &y; p = &y; *p = *q; (righe 1-5):

affermazione V/F motivo
La riga 4 scrive il contenuto di y in x F la riga 4 sposta il puntatore: p ora punta a y
Alla fine, p == q è true V entrambi puntano a y
Alla fine, x == y è true F x resta 3; la riga 5 fa y = y, nessuna modifica (y resta 8)
È possibile effettuare l'operazione alla riga 4 usando le reference al posto dei puntatori F una reference non si può spostare dopo l'inizializzazione: r = y copierebbe il valore. Vedi C++ - puntatori, array e castun puntatore è una variabile che contiene un indirizzo di memoria e sa il tipo dell'oggetto puntato (T*); si ottiene l'indirizzo con & e si accede al dato con * (dereference, un lvalue), nullptr indica nessun oggetto e due puntatori a tipi diversi sono tipi diversi; void* è un puntatore a memoria grezza (accetta qualsiasi puntatore ma non si dereferenzia) e i cast (static_cast, reinterpret_cast, const_cast) servono per interfacciarsi con hardware e codice non modificabile; il puntatore, a differenza della reference, si può spostare, può essere nullptr e va dereferenziato, e assegnarlo copia l'indirizzo (shallow copy) mentre assegnare una reference copia l'oggetto; un array C è una sequenza contigua di elementi dello stesso tipo, con dimensione costante, senza controllo dei limiti, che passato a una funzione decade in un puntatore al primo elemento; l'aritmetica dei puntatori somma o sottrae interi spostando di n elementi (non di n byte) e la differenza fra puntatori è in elementi; gli errori classici sono nullptr, puntatori non inizializzati, accessi fuori dai limiti e puntatori a oggetti usciti dallo scope.C++ - puntatori, array e cast →

Versione ripasso

Teoria collegata