Απ' ό,τι καταλαβαίνω, το string
είναι μέλος του χώρου ονομάτων std
, οπότε γιατί συμβαίνει το εξής;
#include <iostream>
int main()
{
using namespace std;
string myString = "Press ENTER to quit program!";
cout << "Come up and C++ me some time." << endl;
printf("Follow this command: %s", myString);
cin.get();
return 0;
}
Κάθε φορά που εκτελείται το πρόγραμμα, το myString
εκτυπώνει μια φαινομενικά τυχαία συμβολοσειρά 3 χαρακτήρων, όπως στην παραπάνω έξοδο.
Μεταγλωττίζεται επειδή η printf
δεν είναι ασφαλής τύπου, αφού χρησιμοποιεί μεταβλητά ορίσματα με την έννοια της C1. Η printf
δεν έχει επιλογή για std::string
, μόνο για ένα string τύπου C. Χρησιμοποιώντας κάτι άλλο στη θέση αυτού που περιμένει, σίγουρα δεν θα'δώσετε τα αποτελέσματα που θέλετε. Στην πραγματικότητα πρόκειται για απροσδιόριστη συμπεριφορά, οπότε θα μπορούσε να συμβεί οτιδήποτε.
Ο ευκολότερος τρόπος για να το διορθώσετε αυτό, αφού'χρησιμοποιείτε C++, είναι να το εκτυπώσετε κανονικά με το std::cout
, αφού το std::string
το υποστηρίζει αυτό μέσω της υπερφόρτωσης τελεστών:
std::cout << "Follow this command: " << myString;
Αν, για κάποιο λόγο, πρέπει να εξάγετε το αλφαριθμητικό τύπου C, μπορείτε να χρησιμοποιήσετε τη μέθοδο c_str()
της std::string
για να πάρετε ένα const char *
που έχει μηδενικό τερματισμό. Χρησιμοποιώντας το παράδειγμά σας:
#include <iostream>
#include <string>
#include <stdio.h>
int main()
{
using namespace std;
string myString = "Press ENTER to quit program!";
cout << "Come up and C++ me some time." << endl;
printf("Follow this command: %s", myString.c_str()); //note the use of c_str
cin.get();
return 0;
}
Αν θέλετε μια συνάρτηση που είναι σαν την printf
, αλλά με ασφάλεια τύπου, κοιτάξτε τα variadic templates (C++11, υποστηρίζεται από όλους τους μεγάλους μεταγλωττιστές από το MSVC12). Μπορείτε να βρείτε ένα παράδειγμα εδώ. Δεν'υπάρχει κάτι που γνωρίζω να έχει υλοποιηθεί έτσι στην τυπική βιβλιοθήκη, αλλά ίσως υπάρχει στην Boost, συγκεκριμένα στην boost::format
.
[1]: Αυτό σημαίνει ότι μπορείτε να περάσετε οποιονδήποτε αριθμό ορίων, αλλά η συνάρτηση βασίζεται σε εσάς για να της πείτε τον αριθμό και τους τύπους αυτών των ορίων. Στην περίπτωση της printf
, αυτό σημαίνει ένα αλφαριθμητικό με κωδικοποιημένες πληροφορίες τύπου όπως %d
που σημαίνει int
. Αν πείτε ψέματα για τον τύπο ή τον αριθμό, η συνάρτηση δεν έχει κανέναν τυπικό τρόπο να το μάθει, αν και ορισμένοι μεταγλωττιστές έχουν τη δυνατότητα να ελέγχουν και να δίνουν προειδοποιήσεις όταν λέτε ψέματα.
Παρακαλούμε μην χρησιμοποιείτε το printf("%s", your_string.c_str());
Χρησιμοποιήστε cout << your_string;
αντί αυτού. Σύντομο, απλό και ασφαλές για τους τύπους. Στην πραγματικότητα, όταν γράφετε C++, γενικά θέλετε να αποφύγετε εντελώς το printf
-- είναι ένα απομεινάρι της C που σπάνια χρειάζεται ή είναι χρήσιμο στη C++.
Όσον αφορά το γιατί θα πρέπει να χρησιμοποιήσετε το cout
αντί του printf
, οι λόγοι είναι πολλοί. Ακολουθεί ένα δείγμα μερικών από τους πιο προφανείς:
Όπως δείχνει η ερώτηση, η printf
δεν είναι ασφαλής ως προς τον τύπο. Αν ο τύπος που περνάτε διαφέρει από αυτόν που δίνεται στον προσδιοριστή μετατροπής, η printf
θα προσπαθήσει να χρησιμοποιήσει ό,τι βρει στη στοίβα σαν να ήταν ο καθορισμένος τύπος, δίνοντας απροσδιόριστη συμπεριφορά. Μερικοί μεταγλωττιστές μπορούν να προειδοποιήσουν γι' αυτό υπό ορισμένες συνθήκες, αλλά μερικοί μεταγλωττιστές δεν μπορούν/δεν θέλουν καθόλου, και κανένας δεν μπορεί υπό όλες τις συνθήκες.
Η printf
δεν είναι επεκτάσιμη. Μπορείτε να της περάσετε μόνο πρωτόγονους τύπους. Το σύνολο των προσδιοριστών μετατροπής που καταλαβαίνει είναι σκληρά κωδικοποιημένο στην υλοποίησή του, και δεν υπάρχει τρόπος να προσθέσετε περισσότερους/άλλους. Οι περισσότερες καλά γραμμένες C++ θα πρέπει να χρησιμοποιούν αυτούς τους τύπους κυρίως για την υλοποίηση τύπων προσανατολισμένων προς το πρόβλημα που επιλύεται.
Κάνει την αξιοπρεπή μορφοποίηση πολύ πιο δύσκολη. Για ένα προφανές παράδειγμα, όταν'εκτυπώνετε αριθμούς για να τους διαβάσουν οι άνθρωποι, συνήθως θέλετε να εισάγετε διαχωριστικά χιλιάδων κάθε λίγα ψηφία. Ο ακριβής αριθμός των ψηφίων και οι χαρακτήρες που χρησιμοποιούνται ως διαχωριστικά ποικίλλει, αλλά το cout
το καλύπτει και αυτό. Για παράδειγμα:
std::locale loc(""),
std::cout.imbue(loc),
std::cout << 123456.78,
Η ανώνυμη locale (το "") επιλέγει μια locale με βάση τις ρυθμίσεις του χρήστη'. Επομένως, στο δικό μου μηχάνημα (ρυθμισμένο για τα αγγλικά των ΗΠΑ) αυτό εκτυπώνεται ως 123,456.78
. Για κάποιον που ο υπολογιστής του έχει ρυθμιστεί για (ας πούμε) Γερμανία, θα εκτυπωθεί κάτι σαν 123.456,78
. Για κάποιον που τον έχει ρυθμίσει για την Ινδία, θα εκτυπωθεί ως 1,23,456.78
(και φυσικά υπάρχουν πολλές άλλες). Με την printf
παίρνω ακριβώς ένα αποτέλεσμα: 123456,78
. Είναι συνεπές, αλλά είναι σταθερά λάθος για όλους και παντού. Ουσιαστικά ο μόνος τρόπος για να το παρακάμψετε είναι να κάνετε τη μορφοποίηση ξεχωριστά και μετά να περάσετε το αποτέλεσμα ως συμβολοσειρά στην printf
, γιατί η printf
από μόνη της απλά δεν κάνει σωστά τη δουλειά.
Αν και είναι αρκετά συμπαγείς, οι συμβολοσειρές μορφοποίησης printf
μπορεί να είναι αρκετά δυσανάγνωστες. Ακόμα και μεταξύ των προγραμματιστών της C που χρησιμοποιούν την printf
σχεδόν καθημερινά, υποθέτω ότι τουλάχιστον το 99% θα πρέπει να ψάξει τα πράγματα για να είναι σίγουρος τι σημαίνει το #
στο %#x
και πώς αυτό διαφέρει από το τι σημαίνει το #
στο %#f
(και ναι, σημαίνουν εντελώς διαφορετικά πράγματα).
χρησιμοποιήστε την myString.c_str()
αν θέλετε μια συμβολοσειρά τύπου c (const char*
) για χρήση με την printf
ευχαριστώ