C++ - scope, namespace e librerie
In questa pagina 4
Due temi legati dall'idea di nomi: dove un nome è valido (scope e namespace) e come si condividono le dichiarazioni tra file e librerie (header, include guard, linking statico e dinamico). Prima si sono visti la compilazione di un file in C++ - introduzione, compilazione e linkingC++ è un linguaggio compilato e standard che dà controllo totale sulla macchina (basso livello) e strumenti di astrazione (alto livello); un programma parte da main, usa gli stream (std::cout << ...) e le direttive del preprocessore (#include); il passaggio da sorgente a eseguibile ha tre fasi, preprocessore (espande #include e macro), compilatore (controlla sintassi e tipi e produce un file oggetto in codice macchina) e linker (unisce i file oggetto e le librerie risolvendo i nomi); gli errori di sintassi e di tipo sono del compilatore, i simboli dichiarati ma non definiti (undefined reference) sono del linker; si compila da riga di comando con g++ -o eseguibile sorgente.cpp -lLibreria.C++ - introduzione, compilazione e linking → e la differenza fra dichiarazione e definizione in 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 →; la durata delle variabili in memoria (diversa dallo scope) è in C++ - chiamate a funzione e layout della memoriauna chiamata a funzione crea un record di attivazione (RA) con una copia dei parametri e le variabili locali, impilato nello stack (LIFO) e tolto all'uscita; la dimensione dipende dalle variabili locali e il tempo per inserirlo è costante; il processo ha cinque regioni di memoria: text (istruzioni, spesso read-only), initialized data (globali e statiche inizializzate), uninitialized data o BSS (globali e statiche non inizializzate, azzerate dal sistema), stack (RA) e heap (allocazione dinamica con new e delete); la durata (storage duration) di una variabile è diversa dallo scope: le globali e le static sono statiche (esistono per tutto il programma, inizializzate una volta), le locali sono automatiche (create all'entrata nel blocco, distrutte in ordine inverso all'uscita); le globali sono inizializzate prima del main ma l'ordine fra file diversi non è definito.C++ - chiamate a funzione e layout della memoria →.
Lo scope
Definizione. Lo scope è una regione del testo di un programma. Ogni nome è dichiarato in uno scope ed è valido (in scope) dal punto della dichiarazione fino alla fine di quello scope.
Obiettivo: rendere i nomi locali ("Locality is good") per evitare interferenze (clash). Per questo i nomi sono tanto più descrittivi quanto più grande è il loro scope. Non va confuso con la durata (storage duration): lo scope dice dove nel testo il nome è accessibile, la durata dice per quanto tempo l'oggetto esiste in memoria. Una variabile static locale esce dallo scope a fine funzione ma resta in memoria (C++ - chiamate a funzione e layout della memoriauna chiamata a funzione crea un record di attivazione (RA) con una copia dei parametri e le variabili locali, impilato nello stack (LIFO) e tolto all'uscita; la dimensione dipende dalle variabili locali e il tempo per inserirlo è costante; il processo ha cinque regioni di memoria: text (istruzioni, spesso read-only), initialized data (globali e statiche inizializzate), uninitialized data o BSS (globali e statiche non inizializzate, azzerate dal sistema), stack (RA) e heap (allocazione dinamica con new e delete); la durata (storage duration) di una variabile è diversa dallo scope: le globali e le static sono statiche (esistono per tutto il programma, inizializzate una volta), le locali sono automatiche (create all'entrata nel blocco, distrutte in ordine inverso all'uscita); le globali sono inizializzate prima del main ma l'ordine fra file diversi non è definito.C++ - chiamate a funzione e layout della memoria →).
I tipi di scope
| scope | dove | note |
|---|---|---|
| globale (di file) | fuori da ogni altro scope | funzioni e tipi spesso; le variabili globali vanno usate con cautela |
| di classe | il testo dentro una classe | i membri sono visibili in tutta la classe, anche se dichiarati dopo |
| locale | dentro un blocco { } o nella lista degli argomenti di una funzione |
dal punto di dichiarazione alla } |
| di statement | dentro un'istruzione come il for |
la variabile del for non esiste dopo il ciclo |
| di namespace | uno scope con un nome, dentro lo scope globale o un altro namespace | strumento nuovo, vedi sotto |
Un unico programma con un esempio per tipo:
#include <iostream>
int global_counter = 100; // scope GLOBALE (di file): visibile ovunque sotto
namespace Geometria { // scope di NAMESPACE: uno scope con nome
constexpr double pi = 3.14159;
double area_cerchio(double r) { return pi * r * r; }
}
class Contatore { // scope di CLASSE: il testo dentro la classe
public:
void incrementa() { ++valore; } // valore e' visibile qui, anche se dichiarato dopo
int leggi() const { return valore; }
private:
int valore = 0;
};
int somma(int a, int b) // a e b: scope LOCALE (lista degli argomenti)
{
int risultato = a + b; // scope locale: dal punto di dichiarazione alla }
return risultato;
}
int main()
{
int x = 5; // locale a main
{ // un blocco crea uno scope annidato
int x_interno = x * 2; // esiste solo qui dentro
std::cout << "blocco: x_interno = " << x_interno << '\n';
}
// std::cout << x_interno; // errore: x_interno non e' piu' in scope
for (int i = 0; i < 3; ++i) // scope di STATEMENT: i vive solo nel for
global_counter += i;
// std::cout << i; // errore: i non esiste fuori dal for
Contatore c;
c.incrementa();
c.incrementa();
std::cout << "somma = " << somma(2, 3) << '\n';
std::cout << "global_counter = " << global_counter << '\n';
std::cout << "contatore = " << c.leggi() << '\n';
std::cout << "area = " << Geometria::area_cerchio(1.0) << '\n'; // nome qualificato
return 0;
}blocco: x_interno = 10
somma = 5
global_counter = 103
contatore = 2
area = 3.14159Le righe commentate come errori (x_interno, i fuori dal for) non compilerebbero: il nome è già uscito dallo scope.
Variabili globali
Le funzioni sono spesso dichiarate e definite nello scope globale. Per le variabili lo scope globale è problematico: la variabile è accessibile ovunque nel file (scope di file) e ogni funzione può modificarla, senza protezione né incapsulamento; il debug diventa quasi impossibile in programmi veri, e il codice è meno espressivo, perché i dati "passano" nascosti invece che come argomenti. Si usano per "semplificare" il passaggio dei dati: è una cattiva ragione. Meglio parametri e valori di ritorno (C++ - funzioni, reference e passaggio dei parametriuna funzione ha parametri formali (0 o più, input) e un valore di ritorno (0 o 1, output); in C++ gli argomenti si passano per valore, cioè il parametro è una variabile locale inizializzata con una copia dell'argomento, e modificarla non cambia l'originale; per oggetti grandi (vector, immagini) la copia costa, e si passa per reference (T&), un alias dell'oggetto del chiamante che permette lettura e scrittura, oppure per const reference (const T&), alias in sola lettura verificata dal compilatore, che accetta anche literal (oggetto temporaneo); regola: valore per oggetti piccoli, const T& per grandi da non modificare, T& solo se la funzione deve davvero modificare o produrre più di un output; il passaggio converte l'argomento nel tipo del parametro come in un'inizializzazione e un cast esplicito (static_cast) rende visibile l'intenzione.C++ - funzioni, reference e passaggio dei parametri →).
Scope annidati
Uno scope può stare dentro un altro. Casi, dal più utile al meno:
| annidamento | giudizio |
|---|---|
funzioni in classi (void C::f()) |
il caso più frequente e utile |
blocchi in blocchi (if dentro for), cicli annidati |
inevitabili, ma attenzione ai blocchi troppo complessi; l'indentazione è essenziale |
classi in classi (classe annidata, come Date::Invalid) |
raramente necessario |
| classi in funzioni (classi locali) | da evitare: se serve, la funzione è probabilmente troppo lunga |
| funzioni in funzioni | illegali in C++ |
Un nome interno può nascondere (shadow) un nome esterno uguale; l'operatore :: senza prefisso raggiunge il nome globale.
#include <iostream>
int x = 1; // globale
int main()
{
std::cout << "x globale = " << x << '\n';
int x = 2; // locale: NASCONDE la globale (shadowing)
std::cout << "x locale = " << x << '\n';
{
int x = 3; // ancora piu' interno: nasconde quella di main
std::cout << "x interna = " << x << '\n';
std::cout << "x globale con :: = " << ::x << '\n'; // operatore di scope senza nome = globale
}
std::cout << "di nuovo x locale = " << x << '\n';
return 0;
}x globale = 1
x locale = 2
x interna = 3
x globale con :: = 1
di nuovo x locale = 2Nascondere nomi è lecito ma pericoloso: un x in più nel blocco interno cambia silenziosamente quale variabile si usa.
Namespace
Funzioni e classi permettono di definire entità senza conflitti di nomi dentro di sé: le funzioni raggruppano righe in un blocco, le classi raggruppano funzioni, dati e tipi in un tipo. Il namespace è uno scope dedicato a organizzare classi, funzioni, dati e tipi dentro uno spazio di nomi senza definire un tipo. È utilissimo per nomi probabilmente usati da molte librerie: Text, Line, Matrix, Color, Shape.
#include <iostream>
#include <string>
namespace Graph_lib { // la libreria grafica...
struct Text { std::string s; };
struct Line { int x1, y1, x2, y2; };
}
namespace TextLib { // ...e una libreria di testo, con gli stessi nomi
class Text { public: std::string contenuto; };
class Line { public: std::string riga; };
}
int main()
{
Graph_lib::Text t1 {"etichetta"}; // fully qualified name: namespace::membro
TextLib::Text t2;
t2.contenuto = "paragrafo"; // nessun conflitto fra i due Text
std::cout << t1.s << ' ' << t2.contenuto << '\n';
using std::string; // using declaration: solo il nome string
using std::cout;
string nome = "Ada";
cout << "ciao " << nome << '\n';
using namespace Graph_lib; // using directive: tutti i nomi di Graph_lib
Line l {0, 0, 3, 4}; // Line di Graph_lib, senza prefisso
cout << "dx = " << l.x2 - l.x1 << '\n';
return 0;
}etichetta paragrafo
ciao Ada
dx = 3- Ora le classi si chiamano
Graph_lib::TexteTextLib::Text: la formanamespace::membroè il nome qualificato (fully qualified name). Non c'è conflitto perTexteLine. - Il prefisso appesantisce il codice (
std::string,std::cout). Due scorciatoie:
| costrutto | effetto | quando |
|---|---|---|
using declaration using std::string; |
porta nello scope un solo nome | comoda e sicura, anche in un blocco |
using directive using namespace std; |
porta tutti i nomi del namespace | solo per namespace molto noti (std) e mai negli header: chi include l'header si ritrova i nomi importati senza volerlo e può avere conflitti |
Perché non negli header? L'header viene incluso in molti .cpp; una using namespace lì dentro "contamina" tutti. Tutta la libreria standard sta nel namespace std.
In sintesi: lo scope dice dove un nome è valido, il namespace è uno degli strumenti (non l'unico) per gestire gli scope.
Librerie e header file
Molte funzioni che usiamo non le abbiamo scritte noi (sqrt(), << su cout): servono librerie, e in C++ le si importa attraverso i file header.
L'header visto da chi lo usa
Definizione. Un header file è un insieme di dichiarazioni (alcune delle quali definizioni) di entità, utilizzabili da chi lo include. Esempio:
math.hdichiaradouble sqrt(double x);.
Due forme di inclusione, che differiscono per il percorso di ricerca:
| forma | per | ricerca |
|---|---|---|
#include <iostream> |
header di sistema | nei percorsi di sistema |
#include "my_header.h" |
header definiti dall'utente | prima nel percorso locale, poi in quello di sistema |
Si può anche includere con un percorso: #include "opencv2/core.h". Il preprocessore copia il testo dell'header al posto della riga, quindi file1.cpp e file2.cpp che includono math.h conoscono entrambi la dichiarazione di sqrt.
L'header visto da chi lo scrive
Un progetto grande ha molte dichiarazioni e molte definizioni: conviene raggrupparle e spostarle in file diversi. Si ottiene più di un file sorgente e degli header. L'esempio della funzione f:
| contenuto | dove |
|---|---|
dichiarazione int f(int i); |
header my_func.h |
definizione int f(int i) { return i + 2; } |
file sorgente my_func.cpp |
chiamata i = f(i); |
main.cpp, che include my_func.h |
Il programma completo a tre file è in C++ - introduzione, compilazione e linkingC++ è un linguaggio compilato e standard che dà controllo totale sulla macchina (basso livello) e strumenti di astrazione (alto livello); un programma parte da main, usa gli stream (std::cout << ...) e le direttive del preprocessore (#include); il passaggio da sorgente a eseguibile ha tre fasi, preprocessore (espande #include e macro), compilatore (controlla sintassi e tipi e produce un file oggetto in codice macchina) e linker (unisce i file oggetto e le librerie risolvendo i nomi); gli errori di sintassi e di tipo sono del compilatore, i simboli dichiarati ma non definiti (undefined reference) sono del linker; si compila da riga di comando con g++ -o eseguibile sorgente.cpp -lLibreria.C++ - introduzione, compilazione e linking →. Un file sorgente può includere più header, e gli header possono includerne altri (book.h include date.h). Questo crea due problemi: l'inclusione doppia (se main.cpp include sia date.h sia book.h, date.h arriva due volte) e le inclusioni cicliche (a.h include b.h e viceversa).
Include guard
Ogni header deve essere protetto dalle include guard, direttive del preprocessore:
#ifndef RATIONAL_H // se RATIONAL_H non e' ancora definito...
#define RATIONAL_H // ...definiscilo e includi il contenuto
// ... dichiarazioni ...
#endif // RATIONAL_H // fineLa prima volta che il preprocessore incontra l'header, il nome non esiste: lo definisce e copia il contenuto. Le volte dopo il nome esiste, quindi salta tutto fino a #endif. Alcuni compilatori accettano anche #pragma once, ma non è standard. Senza guard, l'errore è nella compilazione, non nel linker:
t/g/s.h:1:8: error: redefinition of 'Punto'
t/g/main.cpp:1:10: note: 't/g/s.h' included multiple times, additional include site hereEsempio: il progetto seguente ha main.cpp che include date.h e anche book.h, che a sua volta include date.h; funziona solo grazie alle guard.
#ifndef DATE_H
#define DATE_H
struct Date {
int y, m, d;
};
int giorni_nel_mese(const Date& dt); // dichiarazione: la definizione e' in date.cpp
#endif // DATE_H#include "date.h"
int giorni_nel_mese(const Date& dt)
{
switch (dt.m) {
case 2: return (dt.y % 4 == 0 && (dt.y % 100 != 0 || dt.y % 400 == 0)) ? 29 : 28;
case 4: case 6: case 9: case 11: return 30;
default: return 31;
}
}#ifndef BOOK_H
#define BOOK_H
#include "date.h" // book.h dipende da date.h
struct Book {
const char* titolo;
Date uscita;
};
#endif // BOOK_H#include <iostream>
#include "date.h" // date.h e' incluso qui...
#include "book.h" // ...e di nuovo da book.h: le guard evitano il doppione
int main()
{
Book b {"Programmazione in C++", {2024, 2, 1}};
std::cout << b.titolo << ": il mese ha " << giorni_nel_mese(b.uscita) << " giorni\n";
return 0;
}Compilato con g++ -o prog main.cpp date.cpp (cioè linkando i due file oggetto), stampa:
Programmazione in C++: il mese ha 29 giorniDa sorgente a eseguibile con più file
Preprocessore, compilatore e linker agiscono ora su più file: ogni .cpp diventa un file oggetto da solo (con i suoi header copiati), e il linker unisce tutti gli oggetti nell'eseguibile.
main.cpp + my_func.h --> main.o --\
+--> linker --> eseguibile
my_func.cpp + my_func.h --> my_func.o --/Lo stesso meccanismo funziona quando la libreria e il codice che la usa appartengono a progetti diversi: spesso del codice di terzi non arriva il .cpp ma un file precompilato più l'header. Nel caso minimo (HelloWorld.cpp) la "libreria" è quella della standard library: l'header iostream dichiara, il linker aggiunge il codice precompilato della libreria standard (libstdc++).
Librerie statiche e dinamiche
Che cosa succede se molti programmi linkano la stessa libreria?
| linking statico (libreria statica) | linking dinamico (libreria dinamica) | |
|---|---|---|
| copie della libreria | una copia dentro ogni eseguibile | una sola copia sul sistema, condivisa a run-time |
| eseguibile | self-contained, ma più grande; non si può "spezzare" dopo | più piccolo, ma la libreria deve essere presente all'esecuzione |
| spazio su disco | molto (N eseguibili, N copie) | poco |
| aggiornare la libreria | ricompilare e rilinkare ogni eseguibile | si ricompila la libreria senza toccare gli eseguibili |
| nomi | .a (Linux), .lib (Windows) |
.so shared object (Linux), .dll dynamic linking library (Windows) |
| adatta a | distribuire software monolitico | librerie usate da molti programmi |
Attenzione al tranello classico dei quiz: le DLL sono di Windows e gli .so di Linux, non il contrario; le librerie dinamiche riducono lo spazio occupato e non producono eseguibili self-contained.
Domande d'esame
1. Che cos'è lo scope? Elencare i tipi di scope con un esempio per ciascuno.
Traccia. Regione di testo in cui un nome è valido, dalla dichiarazione alla fine dello scope. Globale (variabile fuori da tutto), di classe (membri di class Contatore), locale (blocco { } o parametri di funzione), di statement (variabile del for), di namespace (namespace Geometria { ... }); vedi il programma scope_tipi. Non confondere con la durata: una variabile static locale è fuori scope ma ancora in memoria.
2. Cosa fanno le include guard e perché servono? Sono lo stesso che #pragma once?
Traccia. Direttive del preprocessore (#ifndef, #define, #endif) che fanno copiare il contenuto dell'header una volta sola per unità di traduzione, evitando la doppia definizione di funzioni e classi (e le inclusioni cicliche). Non servono al linker: lavorano prima della compilazione. #pragma once ha lo stesso scopo ma non è standard.
3. Confrontare librerie statiche e dinamiche.
Traccia. Tabella sopra: statiche = copiate in ogni eseguibile, self-contained, eseguibili grandi; dinamiche = una sola copia condivisa, risparmio di disco, ricompilabili senza toccare gli eseguibili, devono essere presenti a run-time (.so Linux, .dll Windows).
4. Perché non scrivere using namespace std; in un header?
Traccia. L'header è incluso in molti file: importerebbe tutti i nomi di std ovunque, con rischio di conflitti con i nomi di chi lo include.
Versione ripasso
- Scope = regione di testo in cui un nome è valido, dalla dichiarazione alla fine dello scope. Obiettivo: nomi locali (nessun clash). Diverso dalla durata (quanto sta in memoria): una
staticlocale esce dallo scope ma resta in memoria. Rimando: C++ - chiamate a funzione e layout della memoriauna chiamata a funzione crea un record di attivazione (RA) con una copia dei parametri e le variabili locali, impilato nello stack (LIFO) e tolto all'uscita; la dimensione dipende dalle variabili locali e il tempo per inserirlo è costante; il processo ha cinque regioni di memoria: text (istruzioni, spesso read-only), initialized data (globali e statiche inizializzate), uninitialized data o BSS (globali e statiche non inizializzate, azzerate dal sistema), stack (RA) e heap (allocazione dinamica con new e delete); la durata (storage duration) di una variabile è diversa dallo scope: le globali e le static sono statiche (esistono per tutto il programma, inizializzate una volta), le locali sono automatiche (create all'entrata nel blocco, distrutte in ordine inverso all'uscita); le globali sono inizializzate prima del main ma l'ordine fra file diversi non è definito.C++ - chiamate a funzione e layout della memoria →. - Tipi di scope: globale (di file), di classe, locale (blocco
{}o argomenti di funzione), di statement (variabile delfor), di namespace.
int global_counter = 100; // globale
namespace Geometria { constexpr double pi = 3.14159; } // namespace
class Contatore { int valore = 0; public: void incrementa() { ++valore; } }; // classe
int somma(int a, int b) { int r = a + b; return r; } // locale (a, b, r)
int main() { for (int i = 0; i < 3; ++i) global_counter += i; // i: statement
{ int interno = 1; } /* interno non esiste piu' */ }- Variabili globali: accessibili e modificabili da ogni funzione, nessun incapsulamento, debug difficile: evitarle, passare i dati per argomento.
- Annidamenti: funzioni in classi (utile), blocchi (inevitabile), classi in classi (raro), classi in funzioni (da evitare), funzioni in funzioni: illegali. Un nome interno nasconde quello esterno;
::x= laxglobale (dal programma: globale 1, locale 2, interna 3,::x= 1). - Namespace = scope con nome, senza creare un tipo; evita conflitti (
Graph_lib::TexteTextLib::Text). Nome qualificato:namespace::membro. using declarationusing std::string;(un nome); using directiveusing namespace std;(tutti: solo perstd, mai negli header). - Header file = dichiarazioni utilizzabili da chi lo include.
#include <...>= di sistema;#include "..."= del progetto (prima percorso locale). Progetti grandi: dichiarazioni nel.h, definizioni nel.cpp, chiamata inmain.cpp. - Include guard (non
#pragma once, che non è standard):
#ifndef RATIONAL_H
#define RATIONAL_H
// dichiarazioni
#endif // RATIONAL_HEvitano la doppia inclusione e quella ciclica; senza di esse: error: redefinition of 'Punto'.
- Compilazione multi-file: ogni
.cpp.oda solo (header copiati), il linker unisce gli.oe le librerie;g++ -o prog main.cpp date.cpp. Librerie di terzi: header + file precompilato. - Statiche (
.a/.lib): copiate in ogni eseguibile, self-contained, eseguibili grandi, per cambiarle si rilinka tutto. Dinamiche (.soLinux,.dllWindows): una sola copia condivisa, risparmio di spazio, ricompilabili senza toccare gli eseguibili, devono essere presenti a run-time. - Esempio di progetto a più file:
date.h(structDate+ dichiarazionegiorni_nel_mese),date.cpp(definizione),book.h(includedate.h, structBook),main.cpp(includedate.hebook.h:date.harriva due volte, le guard lo rendono innocuo). Uscita:Programmazione in C++: il mese ha 29 giorni. - Namespace, esempio:
Graph_lib::TexteTextLib::Textconvivono;using std::string;porta un nome;using namespace Graph_lib;li porta tutti (Line l {0,0,3,4};). - Esempio di scope in un programma:
int global_counter = 100;(globale) modificato da unfor (int i = 0; i < 3; ++i) global_counter += i;103;class Contatoreconvaloreprivato (scope di classe),namespace Geometria { constexpr double pi; area_cerchio }chiamato comeGeometria::area_cerchio(1.0)3.14159;somma(int a, int b)conrisultatolocale. - Risposte d'esame: scope = regione di testo in cui un nome è valido; elencare i cinque tipi con un esempio ciascuno; include guard = direttive del preprocessore che fanno includere l'header una volta sola (non servono al linker); statiche vs dinamiche = tabella sopra;
using namespacenegli header vietato perché contamina chi li include. - Errori tipici: scambiare
.soe.dll; dire che le dinamiche sono self-contained; dire che le include guard servono al linker; mettereusing namespacein un header; usare una variabile fuori dal suo blocco; definire una funzione dentro un'altra; confondere scope e durata. - Domande d'esame su questa nota: tipi di scope con esempio: Esercizio - I tipi di scope in C++ (secondo appello 2021-22, teoria); scope e durata della memorizzazione: Esercizio - Scope e durata della memorizzazione (terzo appello 2022-23, teoria); quiz su include guard e librerie dinamiche: Esercizio - Quiz su include guard, librerie dinamiche, explicit e function object (primo appello 2021-22, quiz).
- Comandi e messaggi da ricordare: senza guard
error: redefinition of 'Punto'; compilazione a due passig++ -c main.cpp,g++ -c date.cpp,g++ -o prog main.o date.o; con più fileg++ -o prog main.cpp date.cpp.
Esercizi su questo argomento
- Esercizio - Hello world, variabili e funzioni (laboratorio 1)
- Esercizio - I tipi di scope in C++ (secondo appello 2021-22, teoria)
- Esercizio - Quiz su include guard, librerie dinamiche, explicit e function object (primo appello 2021-22, quiz)
- Esercizio - Quiz su inizializzazione, free store, polimorfismo e passaggio di argomenti (quarto appello 2022-23, quiz)
- Esercizio - Quiz su operatore =, dangling pointer, enumerazioni e at() (quarto appello 2021-22, quiz)
- Esercizio - Quiz su scope, riferimenti, conversioni e puntatori (secondo appello 2022-23, quiz)
- Esercizio - Scope e durata della memorizzazione (terzo appello 2022-23, teoria)