Skip to content

5. Ονόματα (names), προσδέσεις (bindings) και εμβέλειες (scopes)

  • Σημασιολογικά θέματα μεταβλητών
  • Χαρακτηριστικά μεταβλητών: τύπος, διεύθυνση, τιμή, ψευδώνυμα
  • Πρόσδεση, χρόνος πρόσδεσης
  • Κανόνες εμβέλειας ονομάτων (στατικής, δυναμικής) και περιβάλλον αναφοράς πρότασης
  • Επώνυμες σταθερές
  • Αρχικοποίηση μεταβλητών

5.1 Εισαγωγή

Η αφηρημένη έννοια για τα κελιά μνήμης είναι η μεταβλητή.

Μια μεταβλητή μπορεί να προσδιοριστεί από μια συλλογή χαρακτηριστικών (όνομα, διεύθυνση, τιμή, τύπος, διάρκεια ζωής, εμβέλεια).

5.2 Ονόματα

Τα ονόματα αποδίδονται σε μεταβλητές, υποπρογράμματα και άλλες δομές προγραμμάτων.

5.2.1 Θέματα σχεδιασμού

Βασικά θέματα σχεδιασμού σε ότι αφορά τα ονόματα είναι το εάν υπάρχει διάκριση πεζών κεφαλαίων και το εάν οι ειδικές λέξεις της γλώσσας είναι δεσμευμένες ή λέξεις κλειδιά.

5.2.2 Μορφές ονομάτων

Οι πρώτες γλώσσες προγραμματισμού χρησιμοποιούσαν μεταβλητές με ονόματα ενός μόνο χαρακτήρα (επιρροή από τα μαθηματικά).

Διαδεδομένες μορφές ονομάτων

  • camelCase
  • snake_case

Ειδικές περιπτώσεις κανόνων ονομάτων

  • PHP
  • Τα ονόματα ξεκινούν με $
  • Perl
  • Τα ονόματα ξεκινούν με $, @ ή % και με αυτό τον τρόπο δηλώνεται το είδος τους (βαθμωτός, διάνυσμα, λεξικό)
  • Ruby
  • Το @ στην αρχή ονόματος υποδηλώνει μεταβλητή στιγμιοτύπου ενώ το @@ υποδηλώνει μεταβλητή κλάσης

Γλώσσες με διάκριση πεζών-κεφαλαίων: C, C++, C#, Java, Ruby, Python, ...

Γλώσσες χωρίς διάκριση πεζών-κεφαλαίων: BASIC, FORTRAN, SQL, Pascal, ...

5.2.3 Ειδικές λέξεις (special words)

Οι ειδικές λέξεις χρησιμοποιούνται για το διαχωρισμό των συντακτικών τμημάτων των προτάσεων. Στις περισσότερες γλώσσες προγραμματισμού οι ειδικές λέξεις είναι και δεσμευμένες λέξεις δηλαδή δεν μπορούν να χρησιμοποιηθούν ως ονόματα. Ωστόσο, υπάρχουν γλώσσες όπως η Fortran στην οποία οι ειδικές λέξεις δεν είναι δεσμευμένες αλλά είναι μόνο λέξεις κλειδιά, και μπορούν να χρησιμοποιηθούν ως ονόματα.

5.3 Μεταβλητές

Τα 6 χαρακτηριστικά των μεταβλητών: όνομα, διεύθυνση, τιμή, τύπος, διάρκεια ζωής και εμβέλεια

5.3.1 Όνομα

Δεν έχουν όλες οι μεταβλητές ονόματα (π.χ. ανώνυμες μεταβλητές).

Παράδειγμα με Python και χρήση ανώνυμης μεταβλητής:

for _ in range(5):
    print("Hello!")

5.3.2 Διεύθυνση

Η διεύθυνση μιας μεταβλητής είναι η διεύθυνση μνήμης με την οποία συσχετίζεται η μεταβλητή.

5.3.3 Τύπος

Καθορίζει το εύρος τιμών που μπορεί να αποθηκεύσει η μεταβλητή και το σύνολο των πράξεων που ορίζονται για τιμές του συγκεκριμένου τύπου.

Στην Java ο τύπος int καθορίζει εύρος τιμών από -2147483648 μέχρι και 2147483647 και υποστηρίζει τις πράξεις πρόσθεση, αφαίρεση, πολλαπλασιασμό, διαίρεση και ακέραιο υπόλοιπο.

5.3.4 Τιμή

Η τιμή μιας μεταβλητής είναι τα περιεχόμενα της θέσης μνήμης που σχετίζεται με τη μεταβλητή.

  • Η διεύθυνση μιας μεταβλητής καλείται L-τιμή (L=Left) της μεταβλητής
  • Η τιμή μιας μεταβλητής καλείται R-τιμή της (R=Right) μεταβλητής

5.4 Η έννοια της πρόσδεσης (binding)

Πρόσδεση είναι η συσχέτιση ενός χαρακτηριστικού με μια οντότητα (π.χ. μεταβλητή και τύπος της, μεταβλητή και τιμή της).

Χρόνος πρόσδεσης είναι η χρονική στιγμή κατά την οποία συμβαίνει η πρόσδεση. Για παράδειγμα στην ακόλουθη εντολή της C++:

count = count + 5;
  • Ο τύπος της μεταβλητής count προσδένεται κατά την μεταγλώττιση
  • Το σύνολο πιθανών τιμών της count προσδένεται κατά τη σχεδίαση του μεταγλωττιστή
  • Η σημασία του τελεστή + προσδένεται κατά την μεταγλώττιση όταν είναι γνωστοί οι τύποι των τελεστέων του
  • Η εσωτερική αναπαράσταση της ακέραιας τιμής 5 προσδένεται κατά το σχεδιασμό του μεταγλωττιστή
  • Η τιμή της count προσδένεται κατά την εκτέλεση της εντολής

5.4.1 Πρόσδεση χαρακτηριστικών με μεταβλητές

Στατική πρόσδεση: Συμβαίνει κατά τη μεταγλώττιση και δεν μπορεί να αλλάξει κατά την εκτέλεση του προγράμματος.

Δυναμική πρόσδεση: Μπορεί να αλλάξει κατά την εκτέλεση προγράμματος.

5.4.2 Προσδέσεις τύπων

5.4.2.1 Στατική πρόσδεση τύπων (static binding ή early binding)

Γλώσσες με στατική πρόσδεση τύπων: C, C++, Java, Fortran, ...

Η στατική πρόσδεση τύπων μπορεί να γίνει είτε με άμεση δήλωση είτε με έμμεση δήλωση.

Η άμεση δήλωση είναι μια εντολή προγράμματος για τη δήλωση του τύπου μιας μεταβλητής. Για παράδειγμα στη C:

int x;

Η έμμεση δήλωση έχει να κάνει με την πρώτη εμφάνιση του ονόματος μιας μεταβλητής σε ένα πρόγραμμα. Για παράδειγμα η ακόλουθη εντολή στην Perl, προκαλεί στατική πρόσδεση μιας βαθμωτής τιμής για τη μεταβλητή x.

$x = 5;

Μιας μορφής έμμεση δήλωση τύπου είναι και η υπονοούμενη δήλωση που συναντάται σε γλώσσες όπως η C#. Για παράδειγμα:

var sum = 0;
var total = 0.0;
var name = "Fred";

δηλώνουν με στατική πρόσδεση ότι η τύποι των μεταβλητών είναι int, float και string αντίστοιχα.

Ομοίως και στη C++ μπορεί να υπάρξει υπονοούμενη δήλωση τύπων με τη λέξη κλειδί auto. Για παράδειγμα:

vector<int> v;
...
auto itr = v.iterator();

δηλώνουν με στατική πρόσδεση ότι o τύπος της μεταβλητής itr είναι

vector<int>::iterator

5.4.2.2 Δυναμική πρόσδεση τύπων (dynamic binding ή late binding)

Στη δυναμική πρόσδεση μνήμης ο τύπος μιας μεταβλητής δεν καθορίζεται από μια εντολή δήλωσης τύπου ούτε από το όνομα της μεταβλητής. Ο τύπος προσδένεται όταν ανατίθεται μιας τιμή σε μια πρόταση εκχώρησης στη μεταβλητή.

  • Οποιαδήποτε μεταβλητή μπορεί να λάβει οποιαδήποτε τιμή τύπου.
  • Ο τύπος μιας μεταβλητής μπορεί να αλλάζει κατά την εκτέλεση του προγράμματος.

Γλώσσες με δυναμική πρόσδεση τύπων: Python, Ruby, JavaScript, PHP, ...

Η C# υποστηρίζει και δυναμική πρόσδεση τύπων με τη δεσμευμένη λέξη dynamic.

5.4.3 Προσδέσεις χώρου αποθήκευσης και διάρκεια ζωής

Κατανομή/ανακατανομή κελιών μνήμης σε μεταβλητές.

Η διάρκεια ζωής μιας μεταβλητής είναι ο χρόνος που η μεταβλητή είναι προσδεδεμένη σε ένα συγκεκριμένο κελί δεδομένων. Με βάση τη διάρκεια ζωής τους οι μεταβλητές χωρίζονται σε 4 κατηγορίες:

  • Στατικές
  • Μεταβλητές με δυναμική δέσμευση στοίβας
  • Μεταβλητές με δυναμική άμεση δέσμευση σωρού
  • Μεταβλητές με δυναμική έμμεση δέσμευση σωρού

5.4.3.1 Στατικές μεταβλητές

Οι στατικές μεταβλητές προσδένονται με κελιά μνήμης πριν ξεκινήσει η εκτέλεση του προγράμματος και παραμένουν μέχρι το τέλος εκτέλεσής του.

5.4.3.2 Μεταβλητές με δυναμική δέσμευση στοίβας

Δημιουργούνται όταν υποβάλλονται σε επεξεργασία οι προτάσεις δήλωσής τους.

Στη C++, C# και Java οι μεταβλητές που ορίζονται σε μεθόδους είναι εξ ορισμού με δυναμική δέσμευση στοίβας.

5.4.3.3 Μεταβλητές με δυναμική άμεση δέσμευση σωρού

Πρόκειται για κελιά μνήμης των οποίων η κατανομή και η κατάργηση της κατανομής γίνεται με σαφείς εντολές που γράφει ο προγραμματιστής. Η αναφορές σε αυτά τα κελιά μνήμης γίνονται μέσω δεικτών ή αναφορών.

Για παράδειγμα στη C++

int *intnode;
intnode = new int;
...
delete intnode;

5.4.3.4 Μεταβλητές με δυναμική έμμεση δέσμευση σωρού

Οι μεταβλητές με έμμεση δυναμική δέσμευση σωρού προσδένονται σε χώρο αποθήκευσης σωρού μόνο όταν τους εκχωρούνται τιμές.

Για παράδειγμα στην JavaScript, στον ακόλουθο κώδικα η μεταβλητή highs άσχετα με τιμές που είχε πριν εκτελεστεί αυτή η εντολή, μετά την εκτέλεσή της είναι μια διάταξη με 5 αριθμητικές τιμές.

highs = [74, 84, 86, 90, 71];

5.5 Εμβέλεια

Εμβέλεια (scope) μιας μεταβλητής είναι το εύρος των εντολών τους προγράμματος στις οποίες είναι ορατή η μεταβλητή, δηλαδή οι θέσεις στο πρόγραμμα στις οποίες μπορεί να ληφθεί ή τιμή της μεταβλητής ή να αλλάξει η τιμή της μεταβλητής.

Τοπικές και μη τοπικές μεταβλητές (π.χ. καθολικές μεταβλητές) μιας ενότητας προγράμματος (π.χ. συνάρτησης).

5.5.1 Στατική εμβέλεια

Η στατική εμβέλεια ονομάζεται έτσι διότι μπορεί να προσδιοριστεί με στατικό τρόπο, δηλαδή πριν την εκτέλεση, εξετάζοντας τον πηγαίο κώδικα.

Ορισμένες γλώσσες επιτρέπουν ένθετες δηλώσεις υποπρογραμμάτων όπως η JavaScript και η Python.

Για παράδειγμα στον ακόλουθο κώδικα σε JavaScript:

sebesta_5_5_1.js
function big(){
    function sub1() {
        var x = 7;
        sub2();
    }
    function sub2() {
        var y = x;
        console.log(y)
    }
    var x = 3;
    sub1();
}

big() // εμφανίζει 3 διότι η JavaScript χρησιμοποιεί στατική εμβέλεια

Η αναφορά στο x στη συνάρτηση sub2 αφορά το x που ορίζεται στον στατικό πρόγονο της sub2 που είναι η big.

5.5.2 Ενότητες

Μια ενότητα κώδικα μπορεί να έχει τις δικές μεταβλητές. Δείτε το ακόλουθο παράδειγμα σε C.

scope1.c
#include <stdio.h>

int main(void) {
  int x = 1;
  {
    int x = 2;
    int y = 3;
    printf("x=%d at line %d\n", x, __LINE__);
    printf("y=%d at line %d\n", y, __LINE__);
  }
  printf("x=%d at line %d\n", x, __LINE__);
}
Ένα παράδειγμα μεταγλώττισης και εκτέλεσης:
$ gcc scope1.c
./a.out
x=2 at line 8
y=3 at line 9
x=1 at line 11

5.5.3 Σειρά δήλωσης

Στη C89 όλες οι δηλώσεις μεταβλητών μιας συνάρτησης πρέπει να γίνονται στην αρχή της συνάρτησης. Αυτό δεν ισχύει στην C99.

declaration_order_c89.c
1
2
3
4
5
6
7
8
#include <stdio.h>

int main(void) {
  int sum = 0;
  for (int i = 0; i < 10; i++) {
    sum += i;
  }
}
Ένα παράδειγμα μεταγλώττισης:
$ gcc declaration_order_c89.c -std=c89

error: 'for' loop initial declarations are only allowed in C99 or C11 mode

5.5.4 Καθολική εμβέλεια

Γλώσσες όπως η C, C++ και η Python επιτρέπουν τη συγγραφή προγραμμάτων που αποτελούνται από μια σειρά ορισμών συναρτήσεων. Επιτρέπουν επίσης τις δηλώσεις μεταβλητών έξω από τις δηλώσεις των συναρτήσεων.

Παράδειγμα στη C με global μεταβλητή και extern μεταβλητή

extern1.c
#include <stdio.h>

int g = 100;

void fun1() {
  extern int e;
  printf("%d e=%d\n", __LINE__, e);
  printf("%d g=%d\n", __LINE__, g);
}

int e = 3;

int main(void) {
  int a = 1;
  printf("%d a=%d\n", __LINE__, a);
  printf("%d e=%d\n", __LINE__, e);
  printf("%d g=%d\n", __LINE__, g);
  fun1();
}

Ένα παράδειγμα μεταγλώττισης και εκτέλεσης:

$ gcc extern1.c
$ ./a.out
15 a=1
16 e=3
17 g=100
7 e=3
8 g=100

Παράδειγμα στη C++ με global μεταβλητή

global.cpp
#include <iostream>
using namespace std;

int g = 100; // global μεταβλητή

int main() {
  int g = 1;         // τοπική μεταβλητή
  cout << g << endl; // αναφορά στην τοπική μεταβλητή
  cout << ::g << endl; // αναφορά στην global μεταβλητή
}

Ένα παράδειγμα μεταγλώττισης και εκτέλεσης:

$ g++ global.cpp
$ ./a.out
1
100

Παραδείγματα στην Python με local και global μεταβλητές

scope1a.py
1
2
3
4
5
6
7
8
day = "Monday"


def tester():
    print(f"The day is {day}")  # πρόσβαση στην global μεταβλητή day


tester()

Ένα παράδειγμα εκτέλεσης:

$ python scope1a.py
The day is Monday

scope1b.py
day = "Monday"


def tester():
    print(
        f"The day is {day}"
    )  # πρόσβαση στην global μεταβλητή day (UnboundLocalError: local variable 'day' referenced before assignment )
    day = "Tuesday"
    print(f"The day is {day}")  # πρόσβαση στην global μεταβλητή day


tester()

Ένα παράδειγμα εκτέλεσης (που εμφανίζει UnboundLocalError):

$ python scope1b.py
Traceback (most recent call last):
  File "scope1b.py", line 12, in <module>
    tester()
  File "scope1b.py", line 6, in tester
    f"The day is {day}"
UnboundLocalError: local variable 'day' referenced before assignment

scope1c.py
day = "Monday"


def tester():
    global day
    print(f"The day is {day}")  # πρόσβαση στην global μεταβλητή day
    day = "Tuesday"
    print(f"The day is {day}")  # πρόσβαση στην global μεταβλητή day


tester()

Ένα παράδειγμα εκτέλεσης:

$ python scope1c.py
The day is Monday
The day is Tuesday

LEGB (Local Enclosing Global Builitins) στην Python

LEGB notebook

scope2a.py
# Sebesta page 223

g = 3


def sub1():
    a = 5
    b = 7
    print(f"checkpoint1:  g={g}, a={a}, b={b}")

    def sub2():
        global g
        c = 9
        print(f"checkpoint2:  g={g}, c={c}")

        def sub3():
            nonlocal c
            g = 11
            print(f"checkpoint3:  g={g}, c={c}")

        sub3()

    sub2()


sub1()

Ένα παράδειγμα εκτέλεσης:

$ python scope2a.py
checkpoint1:  g=3, a=5, b=7
checkpoint2:  g=3, c=9
checkpoint3:  g=11, c=9

scope2b.py
g = 3

def sub1():
    a = 5
    b = 7
    print(f"checkpoint1: g={g}, a={a}, b={b}")
    def sub2():
        global g
        nonlocal a
        g = 9
        a = 9
        b = 9
        print(f"checkpoint2: g={g}, a={a}, b={b}")
    sub2()
    print(f"checkpoint3: g={g}, a={a}, b={b}")

sub1()
print(f"checkpoint4: g={g}")

Ένα παράδειγμα εκτέλεσης:

$ python scope2b.py
checkpoint1: g=3, a=5, b=7
checkpoint2: g=9, a=9, b=9
checkpoint3: g=9, a=9, b=7
checkpoint4: g=9

5.5.5 Αποτίμηση της στατικής εμβέλειας

Η στατική εμβέλεια με διάφορες παραλλαγές έχει επικρατήσει στις γλώσσες προγραμματισμού σήμερα. Ωστόσο, έχει μειονεκτήματα όπως η υπερβολική χρήση global μεταβλητών ειδικά μετά από αλλαγές σε υπάρχοντες κώδικες.

5.5.6 Δυναμική εμβέλεια

Η δυναμική εμβέλεια βασίζεται στην αλληλουχία των κλήσεων υποπρογραμμάτων και όχι στη σχέση τους στο χώρο και μπορεί να προσδιοριστεί μόνο κατά την εκτέλεση.

Γλώσσες που χρησιμοποιούν δυναμική εμβέλεια: APL, SNOBOL4, αρχικές εκδόσεις της Lisp.

Αν υποθέσουμε ότι ο ακόλουθος κώδικας εκτελείται με κανόνες δυναμικής εμβέλειας τότε η τιμή που θα λάβει η μεταβλητή y θα είναι 7.

function big(){
    function sub1() {
        var x = 7;
        sub2();
    }
    function sub2() {
        var y = x;
    }
    var x = 3;
    sub1();
}

5.5.7 Αποτίμηση της δυναμικής εμβέλειας

Με τη δυναμική εμβέλεια προκύπτουν λιγότερα αξιόπιστα προγράμματα από ότι με τη στατική εμβέλεια. Επιπλέον τα προγράμματα είναι δυσκολότερα στην ανάγνωση και εκτελούνται βραδύτερα.

5.6 Εμβέλεια και διάρκεια ζωής

Η εμβέλεια και η διάρκεια ζωής μιας μεταβλητής αν και σχετικές έννοιες δεν είναι ταυτόσημες. H μεν εμβέλεια έχει να κάνει με το χώρο ενώ η διάρκεια ζωής έχει να κάνει με τον χρόνο.

Μια static μεταβλητή σε μια συνάρτηση της C προσδένεται με στατικό τρόπο με την εμβέλεια της συνάρτησης, αλλά η μεταβλητή υπάρχει για όλη τη διάρκεια εκτέλεσης του προγράμματος.

static1.cpp
#include <iostream>

using namespace std;

void fun() {
  static int x = 0;
  x++;
  cout << x << endl;
}

int main() {
  for (int i = 0; i < 10; i++) {
    fun();
  }
}

Ένα παράδειγμα μεταγλώττισης και εκτέλεσης:

$ g++ static1.cpp
$ ./a.out
1
2
3
4
5
6
7
8
9
10

static2cpp
#include <iostream>

using namespace std;

int adder(int list[], int listlen) {
  static int sum = 0; // στατική τοπική μεταβλητή
  int count; // τοπική μεταβλητή δυναμική-στοίβας
  for (count = 0; count < listlen; count++) {
    sum += list[count];
  }
  return sum;
}

int main() {
  int a[] = {1, 2, 3};    // -> 6
  int b[] = {4, 5, 6, 7}; // -> 22
  cout << adder(a, 3) << endl;
  cout << adder(b, 4) << endl;
}

Ένα παράδειγμα μεταγλώττισης και εκτέλεσης:

$ g++ static2.cpp
$ ./a.out
6
28

5.7 Περιβάλλοντα αναφοράς

Το περιβάλλον αναφοράς μιας εντολής είναι η συλλογή όλων των ονομάτων που είναι ορατά σε αυτή την εντολή.

5.8 Επώνυμες σταθερές

Οι επώνυμες σταθερές είναι μεταβλητές που προσδένονται μια μόνο φορά σε τιμή. Διευκολύνουν την αναγνωσιμότητα.

const1.cpp
#include <iostream>
using namespace std;

int main() {
  int width;
  cout << "Input width: ";
  cin >> width;

  const int result =
      2 * width + 1; // δυναμική πρόσδεση τιμών σε επώνυμη σταθερά

  cout << result << endl;
}

Ένα παράδειγμα μεταγλώττισης και εκτέλεσης:

$ g++ const1.cpp
$ ./a.out
Input width: 20   
41