Η έννοια του δείκτη σε δείκτη έχει ήδη συζητηθεί. Ο δείκτης σε δείκτη μπορεί να είναι οποιουδήποτε τύπου. Για παράδειγμα:
char ch; /* a character */
char *pch; /* a pointer to a character */
char **ppch; /* a pointer to a pointer to a character */
Οι δηλώσεις φαίνονται στο Σχήμα 1. Εκεί βλέπουμε οτι ο δείκτης **ppch περιέχει τη διεύθυνση του δείκτη *pch ο οποίος περιέχει τη διεύθυνση της μεταβλητής ch. Τι σημαίνει αυτό πρακτικά;
Σχήμα 1 Δείκτες σε Δείκτες
Θυμηθείτε οτι η δήλωση char * συνήθως δείχνει όχι μόνο σε έναν απλό χαρακτήρα αλλά σε μια συμβολοσειρά (πίνακας χαρακτήρων που τερματίζεται με NULL, \0). Έτσι, συνήθως δείκτης **ppch είναι δείκτης σε μια συμβολοσειρά *pch (Σχήμα 2)
Σχήμα 2 Δείκτης σε Συμβολοσειρά
Αν προχωρήσουμε ένα βήμα ακόμη μπορούμε να έχουμε δείκτη σε πίνακα συμβολοσειρών. To ppch δείχνει στο pch[0] το οποίο είναι η διεύθυνση της μηδενικής συμβολοσειράς, το ppch+1 δείχνει στο pch[1] το οποίο είναι η διεύθυνση της πρώτης συμβολοσειράς, ενώ το ppch+4 στο σχήμα μας είναι NULL, \0. (Σχήμα 3)
Σχήμα 3 Δείκτης σε Πίνακα Συμβολοσειρών.
Μπορούμε να αναφερθούμε στις ξεχωριστές συμβολοσειρές και ως ppch[0], ppch[1], ... Ισοδύναμα μπορούμε να δηλώσουμε τον πίνακα συμβολοσειρών ως char *ppch[].
Αυτή η δήλωση είναι συνηθισμένη στον χειρισμό της εισόδου από τη γραμμή εντολών.
Η C επιτρέπει την εισαγωγή ορισμάτων από τη γραμμή εντολών, τα
οποία μπορούν να χρησιμοποιηθούν από τα προγράμματά μας.
Τα ορίσματα εισάγονται στη γραμμή εντολών αμέσως μετά το όνομα του προγράμματος, πρίν πιέσουμε το πλήκτρο ENTER. Χωρίζονται μεταξύ τους με κενά, ακριβώς όπως τα ορίσματα των εντολών και σεναρίου φλοιού UNIX/Linux.
Για παράδειγμα στη γραμμή
gcc -o prog prog.c
το gcc είναι το όνομα του προγράμματος, ενώ τα -o prog prog.c είναι τα ορίσματα.
Για να μπορέσουμε να χρησιμοποιήσουμε τα ορίσματα πρέπει να δηλώσουμε τη συνάρτηση main() ως εξής:
main(int argc, char **argv) ή main(int argc, char *argv[])
Ένα παράδειγμα προγράμματος:
#include<stdio.h>
main (int argc, char **argv)
{ /* program to print arguments from command line */
int i;
printf("argc = %d\n\n",argc);
for (i=0; i<argc; ++i)
printf("argv[%d]: %s\n", i, argv[i]);
}
Έστω οτι το μεταγλωττίζουμε το παραπάνω πρόγραμμα ως args.
'Ετσι, αν γράψουμε στη γραμμή εντολών:
args f1 "f2" f3 4 stop!
The output would be:
argc = 6
Σημείωση:
argv[0] = args
argv[1] = f1
argv[2] = f2
argv[3] = f3
argv[4] = 4
argv[5] = stop!
#include <stdio.h>
#include <stdilb.h>
main(argc, argv)
int argc;
char **argv;
{
int c;
FILE *from, *to;
/*
* Check our arguments.
*/
if (argc != 3) {
fprintf(stderr, "Usage: %s from-file to-file\n", *argv);
exit(1);
}
/*
* Open the from-file for reading.
*/
if ((from = fopen(argv[1], "r")) == NULL) {
perror(argv[1]);
exit(1);
}
/*
* Open the to-file for appending. If to-file does
* not exist, fopen will create it.
*/
if ((to = fopen(argv[2], "a")) == NULL) {
perror(argv[2]);
exit(1);
}
/*
* Now read characters from from-file until we
* hit end-of-file, and put them onto to-file.
*/
while ((c = getc(from)) != EOF)
putc(c, to);
/*
* Now close the files.
*/
fclose(from);
fclose(to);
exit(0);
}
#include < stdio.h>Αν το πρόγραμμα λέγεται test μια τυπική γραμμή εντολών θα μπορούσε να είναι:
#include < stdlib.h>
main(int argc, char** argv)
{
/* Set defaults for all parameters: */
int a_value = 0;
float b_value = 0.0;
char* c_value = NULL;
int d1_value = 0, d2_value = 0;
int i;
/* Start at i = 1 to skip the command name. */
for (i = 1; i < argc; i++) {
/* Check for a switch (leading "-"). */
if (argv[i][0] == '-') {
/* Use the next character to decide what to do. */
switch (argv[i][1]) {
case 'a': a_value = atoi(argv[++i]);
break;
case 'b': b_value = atof(argv[++i]);
break;
case 'c': c_value = argv[++i];
break;
case 'd': d1_value = atoi(argv[++i]);
d2_value = atoi(argv[++i]);
break;
default: printf("Unknown switch %s\n", argv[i]);
}
}
}
printf("a = %d\n", a_value);
printf("b = %f\n", b_value);
if (c_value != NULL) printf("c = \"%s\"\n", c_value);
printf("d1 = %d, d2 = %d\n", d1_value, d2_value);
}
getopt()
καλείται σε ένα βρόχο. Όταν η getopt
επιστρέφει -1
, σημαίνει οτι δεν υπάρχουν ορίσματα και ο βρόχος τερματίζει. #include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int
main (int argc, char **argv)
{
int aflag = 0;
int bflag = 0;
char *cvalue = NULL;
int index;
int c;
opterr = 0;
while ((c = getopt (argc, argv, "abc:")) != -1)
switch (c)
{
case 'a':
aflag = 1;
break;
case 'b':
bflag = 1;
break;
case 'c':
cvalue = optarg;
break;
case '?':
if (optopt == 'c')
fprintf (stderr, "Option -%c requires an argument.\n", optopt);
else if (isprint (optopt))
fprintf (stderr, "Unknown option `-%c'.\n", optopt);
else
fprintf (stderr,"Unknown option character `\\x%x'.\n", optopt);
return 1;
default:
abort ();
}
printf ("aflag = %d, bflag = %d, cvalue = %s\n", aflag, bflag, cvalue);
for (index = optind; index < argc; index++)
printf ("Non-option argument %s\n", argv[index]);
return 0;
}
Ακολουθούν μερικά παραδείγματα εισόδου στη γραμμή εντολών, με όνομα προγράμματος testopt.
% testoptΑντίστοιχα υπάρχει και συνάρτηση getopt_long() που επιπλέον εξεσφαλίζει διαχείριση επιλογές πλήρων λέξεων (πχ --help).
aflag = 0, bflag = 0, cvalue = (null)
% testopt -a -b
aflag = 1, bflag = 1, cvalue = (null)
% testopt -ab
aflag = 1, bflag = 1, cvalue = (null)
% testopt -c foo
aflag = 0, bflag = 0, cvalue = foo
% testopt -cfoo
aflag = 0, bflag = 0, cvalue = foo
% testopt arg1
aflag = 0, bflag = 0, cvalue = (null)
Non-option argument arg1
% testopt -a arg1
aflag = 1, bflag = 0, cvalue = (null)
Non-option argument arg1
% testopt -c foo arg1
aflag = 0, bflag = 0, cvalue = foo
Non-option argument arg1
% testopt -a -- -b
aflag = 1, bflag = 0, cvalue = (null)
Non-option argument -b
% testopt -a -
aflag = 1, bflag = 0, cvalue = (null)
Non-option argument -
int main (int argc, char *argv[], char *envp[])
Ο δείκτης σε συνάρτηση είναι μια από τις πιο δύσκολες έννοιες στη C. Oυσιαστικά ο δείκτης κρατά τη διεύθυνση εισόδου (entry point) του εκτελέσιμου στιγμιότυπου της συνάρτησης. Η χρήση τους δεν είναι πολύ συνηθισμένη σε πρoγράμματα εφαρμογών, όμως είναι αρκετά κοινή σε βιβλιοθήκες λογισμικού. Εκεί συνήθως οι δείκτες σε συναρτήσεις εμφανίζονται ως παράμετροι σε κλήσεις άλλων συναρτήσεων.
Αυτή η τεχνική είναι ιδιαίτερα χρήσιμη όταν έχουμε εναλλακτικές συναρτήσεις που μπορεί να χρησιμοποιηθούν για να εκτελέσουν παρόμοιες εργασίες σε δεδομένα. Τότε μπορούμε να περάσουμε τα δεδομένα και τη συνάρτηση που θα χρησιμοποιήσουμε ως παραμέτρους στη κλήση μιας συνάρτησης ελέγχου(ή οδήγησης). Σύντομα θα δούμε δύο τέτοια παραδείγματα συναρτήσεων ταξινόμησης (qsort) και αναζήτησης (bsearch) . Εύκολα μπορείτε να ενσωματώσετε μια δική σας συνάρτηση μέσω της συνάρτησης ελέγχου.
Η δήλωση δείκτη σε συνάρτηση είναι ως εξής:
int (*pf) ();
Αυτή η δήλωση απλά κρατά μια θέση δείκτη *pf σε μια συνάρτηση που επιστρέφει ένα int. Ακόμη δεν δείχνεται κάποια συγκεκριμένη συνάρτηση.
Αν έχουμε μια συνάρτηση int f() απλά γράφουμε:
pf = &f;
Για την καλύτερη λειτουργία της προτυποποίησης έιναι καλό οι δηλώσεις να έχουν πιο συγκεκριμένη:
int f(int);
int (*pf) (int) = &f;
Τώρα η f() επιστρέφει έναν int και δέχεται ένα int ως παράμετρο.
Με αυτή τη δήλωση τα παρακάτω είναι ισοδύναμα:
ans = f(5);
ans = pf(5);
Ακολουθεί να πληρέστερο παράδειγμα δήλωσης και χρήσης δείκτη σε συνάρτηση.
#include <stdio.h>
void my_int_func(int x)
{
printf( "%d\n", x );
}
int main()
{
/* pointer to function declaration */
void (*foo)(int);
/* pointer to function initialisation */
foo = &my_int_func;
/* call my_int_func (note that you do not need to write (*foo)(2) ) */
foo( 2 );
/* but if you want to, you may */
(*foo)( 2 );
return 0;
}
Η πρότυπη συνάρτηση βιβλιοθήκης qsort() είναι πολύ χρήσιμη, γιατί είναι σχεδιασμένη να ταξινομεί ένα πίνακα βάσει ενός κλειδιού οποιουδήποτε τύπου αρκεί τα στοιχεία του πίνακα να είναι οποιουδήποτε (συγκεκριμένου, ενιαίου) τύπου.
Η συνάρτηση qsort() ορίζεται στη πρότυπη βιβλιοθήκη stdlib.h:
void qsort(void *base, size_t num_elements, size_t element_size, int (*compare)(void const *, void const *));
Η παράμετρος base δείχνει στον πίνακα προς ταξινόμηση. Το num_elements ορίζει το μέγεθος του πίνακα (σε bytes) --συνήθως το size_t προκαθορίζεται σε int. To element_size ορίζει το μέγεθος κάθε στοιχείου του πίνακα (σε bytes). Η τελική παράμετρος compare είναι δείκτης σε μια συνάρτηση, που δέχεται δύο παραμέτρους -- δείκτες στα κλειδιά με γενικό τύπο (void) και επιστρέφει ένα int.
H συνάρτηση qsort() καλεί τη συνάρτηση compare() η οποία πρέπει να οριστεί από το χρήστη, για να ορίσει το τρόπο σύγκρισης των στοιχείων του πίνακα. Σημειώστε οτι η qsort() διατηρεί την ανεξαρτησία της από τον τύπο δεδομένων του πίνακα αναθέτοντας την ευθύνη της τελικής σύγκρισης στο χρήστη. Ο int που επιστρέφει η compare() τυπικά ακολουθεί παρακάτω σύμβαση (για αύξουσα ταξινόμηση):
Με αυτό το τρόπο μπορούμε να ταξινομήσουμε αρκετά σύνθετες δομές δεδομένων. Για παράδειγμα, για να ταξινομήσουμε ένα πίνακα με στοιχεία δομής Record και κλειδί int:
typedef struct {
int key;
struct other_data;
} Record;
Μπορούμε να γράψουμε μια συνάρτηση σύγκρισης, recordcompare:
int recordcompare(void const *a, void const *b) {
return ( ((Record *)a)->key - ((Record *)b)->key );
}
Έστω οτι έχουμε ένα πίνακα array με στοιχεία Record και μέγεθος πίνακα arraylength. Μπορούμε να καλέσουμε τη συνάρτηση qsort() ως εξής:
qsort(array, arraylength, sizeof(Record), recordcompare);
Ακολουθεί ένα πληρέστερο παράδειγμα χρήσης της qsort() για ταξινόμηση πίνακα ακεραίων.
#include <stdlib.h>
int int_sorter( const void *first_arg, const void *second_arg )
{
int first = *(int*)first_arg;
int second = *(int*)second_arg;
if ( first < second )
{
return -1;
}
else if ( first == second )
{
return 0;
}
else
{
return 1;
}
}
int main()
{
int array[10];
int i;
/* fill array */
for ( i = 0; i < 10; ++i )
{
array[ i ] = 10 - i;
}
qsort( array, 10 , sizeof( int ), int_sorter );
for ( i = 0; i < 10; ++i )
{
printf ( "%d\n" ,array[ i ] );
}
}
Σε επόμενα κεφάλαια θα δούμε άλλες εφαρμογές των δεικτών σε συναρτήσεις.
Άσκηση 12476
Γράψτε ένα πρόγραμμα με όνομα last που εμφανίζει τις τελευταίες n γραμμές ενός αρχείου κειμένου που δίνεται ως όρισμα στη γραμμή εντολών, δηλαδή
last textfile
Ως προεπιλογή το n είναι 5, αλλά το πρόγραμμα επιτρέπει την αλλαγή του n μέσω μια επιλογής της μορφής
last -n 10 textfile
Άσκηση 12477
Γράψτε ένα πρόγραμμα που να ταξινομεί ένα πίνακα ακεραίων σε αύξουσα ή φθίνουσα σειρά, ανάλογα με την επιλογή που θα δεχτεί από τη γραμμή εντολών.
Άσκηση 12478
Γράψτε ένα πρόγραμμα που διαβάζει ένα πίνακα εγγραφών
typedef struct {
char keyword[10];
int other_data;
} Record;
και τον ταξινομεί με βάση το keyword με χρήση της qsort
Άσκηση 12479
Γράψτε μια συνάρτηση με όνομα insort() που να υλοποιεί τη ταξινόμηση εισαγωγής και να έχει ίδια προτυποοίηση όπως η qsort() και να επιτρέπει τη ταξινόμηση οποιουδήποτε τύπου δεδομένων.