[DOMANDA] Ambito delle variabili, concetto di "scope", passaggio dei parametri per valore, right-value e left-value di una variabile

#1
Visto le mie difficoltà qualcuno puo' riassumermi alcuni concetti su questi argomenti:
"Ambito delle variabili, concetto di "scope", passaggio dei parametri per valore, right-value e left-value di una variabile"
Il mio libro non è molto ben fatto e la spiegazione è troppo veloce per me..
Grazie a tutti.
 
Ultima modifica:

_Achille

Utente Attivo
2,881
647
Hardware Utente
CPU
Intel i5-6600K @4.6 GHz
Dissipatore
Cryorig H5
Scheda Madre
ASRock Z170 Extreme 6
Hard Disk
WesternDigital 1TB & Crucial MX200 250GB
RAM
Corsair Ven 16GB DDR4 2133MHz
Scheda Video
Sapphire RX 580 Nitro+
Monitor
Dell S2418H
Alimentatore
RM550X
Case
NZXT S340
Periferiche
Cooler Master XT; Razer Abyssus
Sistema Operativo
Windows 10 Pro
#2
Sono cose che basta googlare.
Scope -> ambito di visibilità.
Passaggio per valore -> normale passaggio che viene effettuato a meno di reference, rvalue-reference o puntatori.
Left-value -> tutto ciò che può stare a sinistra (es. variabili non const, funzioni che ritornano un riferimento).
Right-value -> tutto ciò che sta solo a destra (es. costanti tipo numeri, stringhe, funzioni che ritornano un valore)
 
#4
Variabile = un contenitore di dati identificato da un tipo un nome. Esempio:
int a;
dichiara una variabile chiamata "a" il cui tipo è un intero, quindi contiene solo valori interi.
l'assegnazione
a = 10;
scrive (in binario naturalmente) nella cella di memoria di a il valore 10;
adesso scriviamo l'espressione
a = a + 5;
si distinguono 2 parti a seconda della posizione dell'=
  1. left-value (valore a sinistra) a: è la parte a sinistra dell'assegnazione; rappresenta il nome simbolico della variabile e ti dice "esiste in memoria una cella di memoria che simbolicamente identifichiamo con l'etichetta a (sotto-sotto tale etichetta è un indirizzo di memoria)
  2. right-value (valore a destra) a + 5; : è la parte a destra dell'assegnazione e contienre VALORI; la "a" a destra è 10, ossia è il CONTENUTO della cella di memoria la cui etichetta è "a".
  3. l'effetto dell'istruzione a = a + 5; è copiare in un registro della CPU il valore 10 (la a sulla destra dell'uguale), copiare in un altro registro il valore 5, eseguire la somma (eventualmente) in un altro registro ottenendo il valore 15. Infine copia il valore 15 come nuovo valore contenuto nella cella di memoria relativa alla variabile a (quella a sinistra).
In breve, il left-value è l'etichetta di un indirizzo, il right-value è il contenuto nella cella di memoria di quell'indirizzo.

Lo SCOPE (raggio/campo d'azione o visibilità) di una variabile è la porzione di codice (in questo caso C++) all'interno di cui essa è utilizzabile;
lo scope di una variabile inizia dalla sua dichiarazione, finisce nel blocco di codice in cui è dichiarata
C++:
int main() {
    int a=10;
    for(int i=0; i<a; i++){
        cout << " a = " << a << endl; // OK
        cout << " i = " << i << endl;
    }
    cout << " a = " << a << endl; // OK
    cout << " i = " << i << endl; // ERRORE, NON COMPILA: i visibile solo nel for
    return 0;
}
Le funzioni sono definite da un nome, un tipo di ritorno (eventualmente void), una lista di parametri ciascuno con un tipo;
tutto cià si chiama "firma" della funzione. Poi ovviamente segue il corpo della funzione ossia le istruzioni.
I parametri della firma si chiamano PARAMETRI FORMALI: significa che, quando si chiama la funzione su certi valori la funzione li identifica formalmente con i nomi di variabile definita nel suo codice. Esempio:
C++:
#include <iostream>
using namespace std;

int sommaInt(int a, int b) // FIRMA
{ // inizio corpo
    return a+b; // corpo:
} // fine corpo

int main() {
    int x, y;
    cout << "Inserisci x --> ";
    cin >> x;
    cout << "Inserisci y --> ";
    cin >> y;
    int somma = sommaInt(x,y);
    cout << x << " + " << y << " = " << somma << endl; // OK
    return 0;
}
In questo semplice programma definisco 2 variabili intere chiamate x ed y;
il programma le legge e le memorizza in celle di memoria etichettate simbolicamente x ed y;
l'istruzione
int somma = sommaInt(x,y);
  1. definisce una variabile intera somma e le riserva una cella di memoria
  2. chiama la funzione sommaInt sui VALORI di x ed y: i valori si chiamano PARAMETRI ATTUALI perché sono quelli che in questo momento verranno manipolati dalla funzione. La funzione INTERNAMENTE fa una corrispondenza tra i parametri che le vengono passati: esegue una COPIA di x e lo indica con a (parametro formale) all'interno del proprio codice, poi fa una COPIA di y e lo indica con b;. Quindi internamente alla funzione si calcola a+b cioè x+y, il risultato è ritornato come intero e copiato nella cella di memoria di somma.
Adesso immagina di modificare il main, usando a e b al posto di x ed y.
C++:
int main() {
    int a, b; // sono differenti da quelli di sommaInt
    cout << "Inserisci a --> ";
    cin >> a;
    cout << "Inserisci b --> ";
    cin >> b;
    int somma = sommaInt(a,b);
    cout << a << " + " << b << " = " << somma << endl; // OK
    return 0;
}
Non cambia nulla: a e b nel main quando sono passati alla funzione vengono ricopiati in 2 nuove celle di memoria che SIMBOLICAMENTE noi umani indichiamo ancora con a e b, ma il programma in realtà usa indirizzi binari differenti (non lettere) per cui non farà mai confusione.
 
Ultima modifica:
Mi Piace: Lux90
#6
te l'ho appena spiegato: passaggio per valore significa semplicemente che quando passi i parametri ad una funzione gli passi dei valori di cui la funzione fa una copia (per non modificare i valori originali che gli hai passato).
Il C++ supporta poi il passaggio per riferimento (passa l'indirizzo di una variabile) che può alterare il contenuto della variabile originale.
 
#7
Scusami ma questo che avevo fatto pochi giorni fa è sbagliato scritto cosi':
/*scrivere una funzione che, presi tre
parametri double B,b,h, stampa a video
l'area del trapezio*/


Codice:
#include <iostream>
#include <ctime>
#include<cstdlib>
#include <chrono>


using namespace std;
using namespace chrono;


double B;
double b;
double h;
double area;

void areatrapezio ()

{
cout<< "inserisci base maggiore  ";
cin>>B;
cout<< "inserisci base minore  ";
cin>>b;
cout<< "inserisci altezza  ";
cin>>h;
area=((B+b)*h)/2;
cout<<"l'area del trapezio e': " <<area<<endl;
}



int main()
{

    areatrapezio();

}
Post unito automaticamente:

Sarebbe meglio cosi' ?

Codice:
include <iostream>

using namespace std;



double areatrapezio (double b, double B, double h)
{
    return ((B+b)*h)/2;
}


    int main() {
   {

    double B,b,h;
    cout << "Inserisci b --> ";
    cin >> b;
    cout << "\nInserisci B --> ";
    cin >> B;
    cout << "Inserisci h --> ";
    cin >> h;
    double area = areatrapezio (b,B,h);
    cout << area<< endl;

    return 0;
}
    }
 
Ultima modifica:

_Achille

Utente Attivo
2,881
647
Hardware Utente
CPU
Intel i5-6600K @4.6 GHz
Dissipatore
Cryorig H5
Scheda Madre
ASRock Z170 Extreme 6
Hard Disk
WesternDigital 1TB & Crucial MX200 250GB
RAM
Corsair Ven 16GB DDR4 2133MHz
Scheda Video
Sapphire RX 580 Nitro+
Monitor
Dell S2418H
Alimentatore
RM550X
Case
NZXT S340
Periferiche
Cooler Master XT; Razer Abyssus
Sistema Operativo
Windows 10 Pro
#8
Scusami ma questo che avevo fatto pochi giorni fa è sbagliato scritto cosi':
/*scrivere una funzione che, presi tre
parametri double B,b,h, stampa a video
l'area del trapezio*/


Codice:
#include <iostream>
#include <ctime>
#include<cstdlib>
#include <chrono>


using namespace std;
using namespace chrono;


double B;
double b;
double h;
double area;

void areatrapezio ()

{
cout<< "inserisci base maggiore  ";
cin>>B;
cout<< "inserisci base minore  ";
cin>>b;
cout<< "inserisci altezza  ";
cin>>h;
area=((B+b)*h)/2;
cout<<"l'area del trapezio e': " <<area<<endl;
}



int main()
{

    areatrapezio();

}
Post unito automaticamente:

Sarebbe meglio cosi' ?

Codice:
include <iostream>

using namespace std;



double areatrapezio (double b, double B, double h)
{
    return ((B+b)*h)/2;
}


    int main() {
   {

    double B,b,h;
    cout << "Inserisci b --> ";
    cin >> b;
    cout << "\nInserisci B --> ";
    cin >> B;
    cout << "Inserisci h --> ";
    cin >> h;
    double area = areatrapezio (b,B,h);
    cout << area<< endl;

    return 0;
}
    }
Andrebbe fatto così a meno di saper usare i reference.
 
397
254
Hardware Utente
CPU
Intel i7 6700HQ, 2.60Ghz, 4 core 8 threads
Scheda Madre
Asustek
Hard Disk
Hitachi 7200 rpm, 1TB
RAM
16GB DDR4 (2 slot su 4)
Scheda Video
Nvidia Geforce GTX 960M, 4GB
Scheda Audio
Realtek
Sistema Operativo
Windows 10 64bit
#10
Non so cosa siano i reference..
comunque va bene il secondo postato giusto?
Post unito automaticamente:
I reference li vedrete tra poco.

Si, va bene. Ma devi capirne la logica, poiché in un caso un po' differente devi giungere comunque ad una soluzione.
 
#11
Non so cosa siano i reference..
Se vuoi capire cosa sono i reference, che poi è l'opposto del passaggio per valore, cosa faresti se io t'imponessi di scrivere la funzione areatrapezio così?

C++:
void areatrapezio (double b, double B, double h)
Considera che a questo punto non hai modo di ritornare il valore dell'area tramite return. L'unica possibilità, senza usare variabili globali, è questa

C++:
void areatrapezio (double b, double B, double h, double &a)

{

    a = ((B+b)*h)/2;

}
Notato quello strano simbolo vicino al nome della variabile a? Quello indica un passaggio per riferimento ( reference, l'opposto del passaggio per valore ) e passa alla funzione non un numero ma l'indirizzo della variabile

C++:
double area;
areatrapezio (b,B,h,area);
Con questo giochino la funzione areatrapezio può andare a scrivere nella variabile area che è definita nel main. Cioè accede ad una variabile fuori dal suo scope.

Ovvero quando assegna il risultato alla variabile a, sta scrivendo in realtà in area. La variabile a funziona come un alias della variabile area.
 

gronag

Utente Attivo
16,110
5,178
Hardware Utente
#12
Visto le mie difficoltà qualcuno puo' riassumermi alcuni concetti su questi argomenti:
"Ambito delle variabili, concetto di "scope", passaggio dei parametri per valore, right-value e left-value di una variabile"
Il mio libro non è molto ben fatto e la spiegazione è troppo veloce per me..
Grazie a tutti.
Farò riferimento al Pascal ma il concetto è generale ;)
Puoi pensare ad un programma come ad una gerarchia di procedure: al livello più alto ci sono quelle da affinare, al livello più basso quelle completamente affinate :sisi:
Ok … quindi la struttura di un programma consiste in una serie di sottoprogrammi (eventualmente annidati) :sisi:
Ora, di solito gli oggetti che vengono dichiarati all'interno del sottoprogramma hanno significato solo nell'ambito del sottoprogramma stesso.
Negli altri casi le dichiarazioni vengono effettuate nella parte dichiarativa del programma stesso.
Dunque nell'ambito di un sottoprogramma possono esistere due tipi di oggetti: quelli LOCALI e quelli GLOBALI (qualcuno li definisce anche NON LOCALI).
Gli oggetti locali possono essere costanti, variabili, ecc., definiti nella parte dichiarativa del sottoprogramma.
Gli oggetti globali vengono definiti nell'ambiente della procedura.
In particolare gli oggetti globali vengono definiti nel MAIN del programma mentre gli oggetti non locali sono definiti nei sottoprogrammi di livello superiore.
Tieni conto che questa distinzione che ho voluto puntualizzare non viene più usata, per cui si parla indifferentemente di oggetti globali o non locali.
Riassumendo, ciascun identificatore ha un suo SCOPE, ossia un CAMPO DI VALIDITA' in cui, per quanto possa essere ristretto, è riconosciuto e usato.
Possiamo dire che lo "scope" è quella subroutine in cui l'oggetto è stato dichiarato, nel caso però di subroutine annidate la faccenda si complica :asd:
Quello che dobbiamo fare in questo caso è definire delle regole precise per determinare lo scope di oggetti locali, non locali e globali del programma :sisi:
Spero di aver contribuito a chiarire meglio i termini della questione posta :look:
A presto ;)
Post unito automaticamente:

Caspita ma sei un professore? Complimenti per la spiegazione!!
Ho solo un dubbio sulla definizione "passaggio dei parametri per valore".
Come sai, uno dei motivi per cui le subroutine sono largamente utilizzate è quello di evitare di riscrivere intere parti di programma :sisi:
Ora, soprattutto nel caso di algoritmi complessi, non è raro trovare parti di programma in più punti assolutamente uguali nella logica, che operano su dati di valore diverso.
Ovviamente questo implica alcuni inconvenienti: errori di copiatura in primo luogo, operazioni di testing su tutte le "occorrenze" delle istruzioni in secondo luogo.
Per risolvere il problema ci viene in aiuto una "nuova" struttura: i parametri :asd:
I parametri sono oggetti che possiedono un identificatore, un tipo e un valore che verrà definito successivamente.
In modo del tutto intuitivo, possiamo dire che si scrivono le istruzioni della subroutine facendole "lavorare" su oggetti "fittizi": i cosiddetti PARAMETRI FORMALI.
Al momento della chiamata della subroutine si forniscono le indicazioni per effettuare le "corrispondenze" tra gli oggetti reali, cioè i PARAMETRI ATTUALI, e i parametri formali (che sono fittizi, come abbiamo detto).
In base a queste corrispondenze, la CPU attua la cosiddetta TRASMISSIONE DEI PARAMETRI, ossia sostituisce i parametri formali con quelli attuali :asd:
Si tratta di un passaggio completamente automatico e trasparente per il programmatore.
L'utilizzo dei parametri comporta evidenti vantaggi nella programmazione ma occorre innanzitutto individuare questi parametri, gli oggetti locali, non locali e globali, poi occorre anche dichiarare sia i parametri formali che quelli attuali e infine dare le indicazioni sulle corrispondenze per la sostituzione.
Sicuramente la sostituzione dei parametri costituisce, se vogliamo, il problema più impegnativo: dobbiamo trovare un modo più o meno semplice per dare alla CPU tutte le informazioni necessarie per la corretta attuazione della sostituzione dei parametri.
Possiamo considerare la sostituzione per nome (o per "valore") come quella di "default" per quanto riguarda il passaggio dei parametri.
Al momento della chiamata della subroutine la CPU riserva una zona di memoria per i parametri formali, poi questi parametri formali vengono inizializzati coi valori dei parametri attuali.
Da questo momento in poi i parametri attuali non vengono più considerati, la CPU lavora solo sui parametri formali, ossia sull'area di memoria ad essi riservata.
Ovviamente al rientro dalla subroutine l'area di memoria dedicata viene rilasciata.
In sintesi, il passaggio dei parametri per nome garantisce che il valore del parametro attuale non venga in alcun modo modificato dalla subroutine, in quanto la CPU lo utilizza soltanto per inizializzare l'area di memoria.
Ecco ... questo sistema è valido se il parametro è un argomento e non un risultato poiché in quest'ultimo caso andrebbe perso.
Se il parametro è un risultato della subroutine dovremo ricorrere alla trasmissione dei parametri per "referenza" :sisi:
A presto ;)

P.S. Faccio sempre riferimento al Pascal ma il concetto è generale :)
 
Ultima modifica: