C ++ succintamente tipi

Tipi fondamentali

C ++ contiene le stesse parole chiave familiari (ad es., Int) che riconosci da C #. Questo non sorprende dato che entrambi sono linguaggi simili a C. C'è, tuttavia, una potenziale mina terrestre che può metterti nei guai. Mentre C # definisce esplicitamente le dimensioni dei tipi fondamentali (un corto è un numero intero a 16 bit, un int è un numero intero a 32 bit, un numero intero a 64 bit un intero, un doppio è un IEEE 754 a doppia precisione a 64 bit flottante numero di punto, ecc.), il C ++ non offre tali garanzie.

La più piccola unità fondamentale in C ++ è char, che deve essere almeno abbastanza grande da contenere i 96 caratteri di base specificati dallo standard C ++, più eventuali altri caratteri nel set di caratteri di base dell'implementazione. In teoria, alcune implementazioni di C ++ potrebbero definire un char come 7 bit o 16 bit ... quasi tutto è possibile. Ma in pratica non è necessario preoccuparsi troppo di un char essendo qualcosa di diverso da 8 bit (l'equivalente del tipo byte o sbyte in C #), che è la sua dimensione in Visual C++.

In C ++, char, signed char e unsigned char sono tre tipi distinti. Tutti e tre sono tenuti a prendere la stessa quantità di memoria in memoria. Quindi un char in pratica è firmato o non firmato. Se è firmato o non firmato è definito l'implementazione (vedi la barra laterale). In Visual C ++ il tipo di carattere è, per impostazione predefinita, firmato. Ma puoi usare un interruttore del compilatore per farlo trattare invece come non firmato. In GCC, indipendentemente dal fatto che sia firmato o non firmato, dipende da quale architettura della CPU si sta prendendo di mira.

I tipi di interi con segno, in ordine di grandezza dal più piccolo al più grande, sono:

  1. Carattere firmato
  2. breve int
  3. int
  4. int lungo
  5. lungo int lungo

L'unica garanzia della dimensione di ciascuno di questi tipi interi è che ognuno è grande almeno quanto il successivo tipo di intero più piccolo. In Visual C ++, un int e un long int sono entrambi numeri interi a 32 bit. È solo il long long int che è un numero intero a 64 bit.

Nota: Puoi semplicemente scrivere lungo o lungo; non è necessario scrivere long int o long long int, rispettivamente. Lo stesso vale anche per l'abbreviato int (ad esempio, puoi scrivere brevemente). Il tipo breve è un numero intero con segno a 16 bit in Visual C++.

Ciascuno dei tipi interi ha un corrispondente numero intero senza segno. Basta mettere la parola chiave senza segno davanti per ottenere la versione senza firma (eccetto per il carattere firmato, che si trasforma in carattere senza segno).

Se è necessario assicurarsi di utilizzare dimensioni specifiche, è possibile includere il file di intestazione della libreria standard C ++ cstdint (per esempio., #includere ), che definisce, tra l'altro, i tipi:

  1. int8_t
  2. int16_t
  3. int32_t
  4. int64_t
  5. uint8_t
  6. uint16_t
  7. uint32_t
  8. uint64_t

Questi tipi hanno il loro uso, ma scoprirai che la maggior parte delle API non li usa; invece, usano direttamente i tipi fondamentali. Ciò può rendere confusa la tua programmazione, poiché devi costantemente controllare il tipo fondamentale sottostante per assicurarti di non finire con il troncamento o l'espansione involontari.

Questi tipi potrebbero essere più utilizzati, quindi consiglio di verificare il loro utilizzo nelle principali librerie e API di volta in volta e di adattare il codice di conseguenza se diventano ampiamente adottati. Ovviamente, se è assolutamente necessario che una variabile sia, ad esempio, un numero intero a 32 bit senza segno, è consigliabile utilizzare uint32_t e apportare eventuali modifiche per le chiamate API e la portabilità necessarie.

I numeri in virgola mobile sono gli stessi delle regole per gli ordini di dimensioni. Passano dal galleggiante al doppio al doppio lungo. In Visual C ++, float è un numero a virgola mobile a 32 bit e il doppio e il doppio lungo sono entrambi numeri a virgola mobile a 64 bit (il doppio lungo non è più grande del doppio, in altre parole).

Il C ++ non ha alcun tipo nativo che sia paragonabile al tipo decimale di C #. Tuttavia, una delle cose carine su C ++ è che in genere ci sono un gran numero di librerie gratuite o poco costose che è possibile acquistare in licenza. Ad esempio, sono presenti la libreria decNumber, la libreria Math virgola mobile decimale Intel e la libreria aritmetica di precisione multipla GNU. Nessuno è esattamente compatibile con il tipo decimale di C #, ma se si sta scrivendo solo per sistemi Windows, è possibile utilizzare il tipo di dati DECIMAL per ottenere tale compatibilità, se necessario, insieme alle Funzioni aritmetiche decimali e alle funzioni di conversione del tipo di dati..

Esiste anche un tipo booleano, bool, che può essere vero o falso. In Visual C ++, un bool richiede un byte. A differenza di C #, un bool può essere trasformato in un tipo intero. Quando è falso, ha un valore intero equivalente di 0 e, quando è true, ha un valore di 1. Quindi l'istruzione bool result = true == 1; verrà compilato e risultato verrà valutato su true quando l'istruzione è stata eseguita.

Poi c'è il tipo wchar_t, che contiene un carattere ampio. Le dimensioni di un personaggio ampio variano in base alla piattaforma. Sulle piattaforme Windows, è un carattere a 16 bit. È l'equivalente del tipo di carattere di C #. Viene spesso utilizzato per costruire stringhe. Discuteremo le stringhe in un altro capitolo poiché molte varianti possono essere utilizzate per le stringhe.

Infine, c'è il tipo void, che viene usato allo stesso modo in C #. E c'è un tipo std :: nullptr_t, che è disordinato per spiegare correttamente, ma fondamentalmente c'è il tipo di nullptr literal, che è quello che dovresti usare al posto di NULL o un letterale 0 (zero) per verificare null valori.

enumerazioni

Le enumerazioni sono abbastanza simili tra loro in C ++ e C #. C ++ ha due tipi di enumerazioni: con scope e senza ambito.

Una enumerazione dell'ambito è definita come una classe enum o una struttura enum. Non c'è differenza tra i due. Un'enumerazione senza ambito è definita come enum semplice. Diamo un'occhiata ad un campione:

Esempio: EnumSample \ EnumSample.cpp

#includere  #includere  #includere  #include "... /pchar.h" enum class Colore rosso, arancione, giallo, blu, indaco, viola; // È possibile specificare qualsiasi tipo di integrale sottostante che si desidera, purché sia ​​adatto. enum Sapore: breve int unsigned Vanilla, Chocolate, Strawberry, Mint,; int _pmain (int / * argc * /, _pchar * / * argv * / []) Flavor f = Vanilla; f = menta; // Questo è legale poiché l'enume di Flavio è un enum non-scoped. Colore c = Colore :: Arancio; // c = Arancione; // Questo è illegale poiché l'enum di colore è un enume con scope. std: sapore wstring; std: colore wstring; switch (c) case Color :: Red: color = L "Red"; rompere; caso Colore :: Arancio: colore = L "Arancione"; rompere; caso Colore :: Giallo: colore = L "Giallo"; rompere; caso Colore :: Blu: colore = L "Blu"; rompere; caso Colore :: Indaco: colore = L "Indaco"; rompere; caso Colore :: Viola: colore = L "Viola"; rompere; default: color = L "Unknown"; rompere;  switch (f) case Vanilla: flavor = L "Vanilla"; rompere; case Chocolate: flavor = L "Chocolate"; rompere; caso fragola: flavor = L "fragola"; rompere; caso Menta: flavor = L "Mint"; rompere; default: break;  std :: wcout << L"Flavor is " << flavor.c_str() << L" (" << f << L"). Color is " << color.c_str() << L" (" << static_cast(C) << L")." << std::endl << L"The size of Flavor is " << sizeof(Flavor) << L"." << std::endl << L"The size of Color is " << sizeof(Color) << L"." << std::endl; return 0; 

Questo codice darà il seguente risultato:

Il sapore è menta (3). Il colore è arancione (1). La dimensione di Flavor è 2. La dimensione di Color è 4.

Come puoi vedere nell'esempio, l'enumerazione del colore dell'ambito richiede che tu acceda ai suoi membri allo stesso modo di C # anteponendo il membro di enumerazione al nome dell'enumerazione e all'operatore di risoluzione dell'ambito. Al contrario, l'enumerazione di Flavon senza ambito consente semplicemente di specificare i membri senza alcun prefisso. Per questo motivo, penso che sia preferibile preferire le enumerazioni mirate: minimizzi i rischi di nominare le collisioni e ridurre l'inquinamento dello spazio dei nomi.

Si noti che c'è un'altra differenza con le enumerazioni di ambito: quando si desiderava emettere il valore numerico dell'enumerazione di ambito, dovevamo usare l'operatore static_cast per convertirlo in un int, mentre non avevamo bisogno di fare alcun casting per l'ONU enumerazione basata sul sapore.

Per l'enumerazione di Flavor, abbiamo specificato il tipo sottostante come int breve senza segno. È inoltre possibile specificare il tipo sottostante per le enumerazioni con ambito. La specifica del tipo sottostante è facoltativa, ma è obbligatoria se si desidera utilizzare la dichiarazione diretta con un'enumerazione senza ambito. La dichiarazione diretta è un modo per accelerare i tempi di compilazione del programma dicendo al compilatore solo ciò che è necessario sapere su un tipo piuttosto che forzarlo a compilare l'intero file di intestazione del tipo definito in.

Lo vedremo più avanti. Per ora, è sufficiente ricordare che un'enumerazione senza ambito deve avere il suo tipo sottostante esplicitamente specificato per poter utilizzare una dichiarazione forward di esso; l'enumerazione dell'ambito non richiede la specifica del suo tipo sottostante per utilizzare una dichiarazione forward (il tipo sottostante sarà int se nessuno è specificato).

Puoi fare la stessa cosa con le enumerazioni in C ++ che puoi in C # in termini di assegnazione esplicita di valori ai membri e in termini di creazione di enumerazioni di flag. Lo fai allo stesso modo, tranne che non devi applicare nulla come FlagAttribute in C ++ per creare enumerazioni di flag; basta assegnare i valori corretti e procedere da lì.

std :: wcout, std :: wcerr, std :: wcin

The std :: wcout << L”Flavor… code outputs wide character data to the standard output stream. In the case of a console program such as this, the standard output is the console window. There is also a std::wcerr output stream, which will output wide character data to the standard error output stream. This is also the console window, but you can redirect std::wcout output to one file and std::wcerr output to another file. There is also a std::wcin for inputting data from the console. We won't explore this, nor will we explore their byte counterparts: std::cout, std::cerr, and std::cin.

Solo per farti vedere come appare l'input, ecco un esempio.

Esempio: ConsoleSample \ ConsoleSample.cpp

#includere  #includere  #includere  #include "... /pchar.h" struct Color float ARGB [4]; void A (valore float) ARGB [0] = valore;  float A (void) const return ARGB [0];  void R (valore float) ARGB [1] = valore;  float R (void) const return ARGB [1];  void G (float value) ARGB [2] = valore;  float G (void) const return ARGB [2];  void B (float value) ARGB [3] = valore;  float B (void) const return ARGB [3]; ; // Questa è una funzione autonoma, che capita di essere un operatore binario // per il << operator when used with a wostream on // the left and a Color instance on the right. std::wostream& operator<<(std::wostream& stream, const Color& c)  stream << L"ARGB: " << c.A() << L"f, " << c.R() << L"f, " << c.G() << L"f, " << c.B() << L"f "; return stream;  int _pmain(int /*argc*/, _pchar* /*argv*/[])  std::wcout << L"Please input an integer and then press Enter: "; int a; std::wcin >> a; std :: wcout << L"You entered '" << a << L"'." << std::endl; std::wcout << std::endl << L"Please enter a noun (one word, no spaces) " << L"and then press Enter: "; std::wstring noun; // wcin breaks up input using white space, so if you include a space or // a tab, then it would just put the first word into noun and there // would still be a second word waiting in the input buffer. std::wcin >> nome; std :: wcerr << L"The " << noun << L" is on fire! Oh no!" << std::endl; Color c =   100.0f/255.0f, 149.0f/255.0f, 237.0f/255.0f, 1.0f  ; // This uses our custom operator from above. Come back to this sample // later when we've covered operator overloading and this should make // much more sense. std::wcout << std::endl << L"Cornflower Blue is " << c << L"." << std::endl; return 0; 

Il codice precedente è una demo abbastanza semplice. Ad esempio, non ha alcun controllo degli errori. Quindi, se inserisci un valore errato per il numero intero, verrà eseguito fino alla fine con std :: wcin che ritorna istantaneamente senza alcun dato (è ciò che fa a meno che e finché non si risolve l'errore).

Se sei interessato alla programmazione di iostream, incluso l'utilizzo di cose come std :: wofstream per l'output di dati in un file e std :: wifstream per leggere i dati da un file (funzionano come std :: wcout e std :: wcin, solo con funzionalità aggiuntive per gestire il fatto che funzionano con i file), vedere le pagine di programmazione iostream MSDN. Imparare tutti i dettagli dei flussi potrebbe facilmente riempire un libro solo per conto suo.

Un'ultima cosa però. Hai senza dubbio notato che la funzionalità di streaming sembra un po 'strana con gli operatori di spostamento dei bit << and >>. Questo perché questi operatori sono stati sovraccaricati. Mentre ci si aspetterebbe che gli operatori di spostamento dei bit agiscano in un certo modo sui numeri interi, non vi è alcuna aspettativa specifica che si possa avere su come dovrebbero funzionare se applicati a un flusso di output o ad un flusso di input, rispettivamente. Pertanto, gli stream della libreria standard C ++ hanno cooptato questi operatori per utilizzarli per l'immissione e l'emissione di dati nei flussi. Quando vogliamo la capacità di leggere o scrivere un tipo personalizzato che abbiamo creato (come la precedente struttura Color), abbiamo semplicemente bisogno di creare un appropriato overload dell'operatore. Impareremo di più sull'overloading degli operatori più avanti nel libro, quindi non preoccuparti se è un po 'di confusione al momento


Classi e strutture

La differenza tra una classe e una struttura in C ++ è semplicemente che i membri di una struttura sono predefiniti come pubblici mentre i membri di una classe sono predefiniti come privati. Questo è tutto. Sono altrimenti uguali. Non c'è distinzione tra valore e tipo di riferimento, come in C #.

Detto questo, in genere vedrai i programmatori utilizzare classi per tipi elaborati (combinazioni di dati e funzioni) e strutture per semplici tipi di soli dati. Normalmente, questa è una scelta stilistica che rappresenta le origini non orientate agli oggetti della struttura in C, facilitando la differenziazione rapida tra un semplice contenitore di dati rispetto a un oggetto in piena regola, cercando di vedere se si tratta di una struttura o di una classe. Raccomando di seguire questo stile.

Nota: Un'eccezione a questo stile è dove un programmatore sta scrivendo un codice che deve essere usato sia in C che in C ++. Poiché C non ha un tipo di classe, il tipo di struttura potrebbe invece essere usato in modo simile a come usereste una classe in C ++. In questo libro non coprirò la scrittura di C ++ compatibile con C. Per fare ciò, è necessario avere familiarità con il linguaggio C e le differenze tra esso e C ++. Invece, ci stiamo concentrando sulla scrittura di codice C ++ pulito e moderno.

Nella programmazione di Windows Runtime ("WinRT"), una struttura pubblica può avere solo membri di dati (senza proprietà o funzioni). Questi membri dei dati possono essere costituiti solo da tipi di dati fondamentali e altre strutture pubbliche - che, ovviamente, hanno le stesse restrizioni solo per i dati, fondamentali e pubbliche. Tienilo a mente se stai lavorando su qualsiasi app in stile Metro per Windows 8 usando C++.

A volte vedrai la parola chiave friend usata all'interno di una definizione di classe. È seguito da un nome di classe o da una dichiarazione di funzione. Ciò che questo costrutto di codice fa è dare a quella classe o funzione l'accesso ai dati e alle funzioni dei membri non pubblici della classe. In genere, è necessario evitare ciò poiché la classe dovrebbe normalmente esporre tutto ciò che si desidera esporre tramite la sua interfaccia pubblica. Ma in quei rari casi in cui non desideri esporre pubblicamente determinati membri dei dati o funzioni dei membri, ma vuoi che una o più classi o funzioni abbiano accesso ad essa, puoi usare la parola chiave friend per realizzare questo.

Poiché le classi sono una parte molto importante della programmazione in C ++, le esamineremo in modo molto più dettagliato più avanti nel libro.

sindacati

Il tipo di unione è un po 'strano, ma ha i suoi usi. Lo incontrerai di tanto in tanto. Un sindacato è una struttura di dati che sembra contenere molti membri dei dati, ma consente solo all'utente di utilizzare uno dei suoi membri dati in qualsiasi momento. Il risultato finale è una struttura dati che offre molti possibili usi senza spreco di memoria. La dimensione del sindacato deve essere abbastanza grande da contenere solo il membro più grande del sindacato. In pratica, ciò significa che i membri dei dati si sovrappongono l'un l'altro in memoria (quindi, è possibile utilizzarne solo uno alla volta). Questo significa anche che non hai modo di sapere quale sia il membro attivo di un'unione a meno che tu non ne tenga traccia in qualche modo. Ci sono molti modi in cui puoi farlo, ma mettere un'unione e un enume in una struttura è un modo buono, semplice e ordinato di farlo. Ecco un esempio.

Esempio: UnionSample \ UnionSample.cpp

#includere  #includere  #include "... /pchar.h" enum class SomeValueDataType Int = 0, Float = 1, Double = 2; struct SomeData SomeValueDataType Type; union int iData; float fData; doppio dData;  Valore; SomeData (void) SomeData (0);  SomeData (int i) Type = SomeValueDataType :: Int; Value.iData = i;  SomeData (float f) Type = SomeValueDataType :: Float; Valore.fData = f;  SomeData (double d) Type = SomeValueDataType :: Double; Value.dData = d; ; int _pmain (int / * argc * /, _pchar * / * argv * / []) SomeData data = SomeData (2.3F); std :: wcout << L"Size of SomeData::Value is " << sizeof(data.Value) << L" bytes." << std::endl; switch (data.Type)  case SomeValueDataType::Int: std::wcout << L"Int data is " << data.Value.iData << L"." << std::endl; break; case SomeValueDataType::Float: std::wcout << L"Float data is " << data.Value.fData << L"F." << std::endl; break; case SomeValueDataType::Double: std::wcout << L"Double data is " << data.Value.dData << L"." << std::endl; break; default: std::wcout << L"Data type is unknown." << std::endl; break;  return 0; 

Come puoi vedere, definiamo un enum che ha membri rappresentanti ciascuno dei tipi di membri dell'unione. Quindi definiamo una struttura che include sia una variabile del tipo di enum che un'unione anonima. Questo ci fornisce tutte le informazioni di cui abbiamo bisogno per determinare quale tipo il sindacato è attualmente in possesso di un pacchetto incapsulato.

Se si desidera che l'unione sia utilizzabile in più strutture, è possibile dichiararla al di fuori della struttura e dargli un nome (ad es. Unione SomeValue ...;). Potresti quindi usarlo all'interno della struttura come, ad esempio, SomeValue Value ;. Di solito è meglio tenerlo come un'unione anonima, dato che non devi preoccuparti degli effetti collaterali di fare un cambiamento se non all'interno delle strutture in cui è definito.

I sindacati possono avere costruttori, distruttori e funzioni membro. Ma dal momento che possono avere solo un membro attivo di dati, raramente ha senso scrivere funzioni membro per un sindacato. Li vedrai raramente, forse mai.

typedef

La prima cosa da capire su typedef è che, nonostante le implicazioni del suo nome, typedef non crea nuovi tipi. È un meccanismo di aliasing che può essere utilizzato per molte cose.

È usato molto per implementare la libreria standard C ++ e altri codici basati su template. Questo è, probabilmente, il suo uso più importante. Lo esploreremo di più nel capitolo sui modelli.

Può salvarti da un sacco di digitazione (anche se questo argomento ha perso parte della sua forza con il riproposizione della parola chiave auto per la deduzione del tipo in C ++ 11). Se hai un tipo di dati particolarmente complicato, creare un typedef significa che devi solo scriverlo una volta. Se lo scopo del tuo tipo di dati complicato non è chiaro, assegnargli un nome più semanticamente significativo con un typedef può aiutarti a rendere il tuo programma più facile da capire.

A volte viene usato come un'astrazione dagli sviluppatori per modificare facilmente un tipo di supporto (ad esempio, da un vettore std :: a un elenco std ::) o il tipo di un parametro (ad esempio, da un int a un long). Per il tuo codice di uso interno, questo dovrebbe essere disapprovato. Se stai sviluppando un codice che altri useranno, come una libreria, non dovresti mai provare a usare un typedef in questo modo. Tutto quello che stai facendo è diminuire la rilevabilità di modifiche alla tua API se cambi un typedef. Usali per aggiungere il contesto semantico, certo, ma non usarli per modificare un tipo sottostante nel codice su cui altri si affidano.

Se è necessario modificare il tipo di qualcosa, ricordare che qualsiasi modifica ai parametri di una funzione è una modifica di interruzione, come lo è un cambiamento nel tipo di ritorno o l'aggiunta di un argomento predefinito. Il modo corretto per gestire la possibilità di un cambio di tipo futuro è con classi astratte o con modelli (a seconda di quale sia il più adatto o quello che preferisci, se entrambi serviranno). In questo modo l'interfaccia pubblica per il tuo codice non cambierà, solo l'implementazione lo farà. L'idioma Pimpl è un altro buon modo per mantenere un'API stabile mantenendo la libertà di modificare i dettagli di implementazione. Esploreremo l'idioma Pimpl, abbreviazione di "puntatore all'implementazione", in un capitolo successivo.

Ecco un piccolo blocco di codice che illustra la sintassi per typedef.

class ExistingType; typedef ExistingType AliasForExistingType;

E il seguente è un breve esempio che mostra come potrebbe essere usato typedef. Lo scopo di questo esempio è di illustrare un uso semplificato ma realistico di un typedef. In pratica, un typedef come questo andrebbe in uno spazio dei nomi e sarebbe quindi incluso in un file di intestazione. Dal momento che non abbiamo coperto nulla di ciò, questo esempio è stato tenuto semplicemente intenzionalmente.

Esempio: TypedefSample \ TypedefSample.cpp

#includere  #includere  #includere  #includere  #include "... /pchar.h" // Questo rende WidgetIdVector un alias per std :: vector, che ha // più significato di std :: vector avrebbe, dato che ora sappiamo che // qualsiasi cosa usi questo alias si aspetta un vettore di ID di widget // piuttosto che un vettore di numeri interi. typedef std :: vector WidgetIdVector; bool ContainsWidgetId (WidgetIdVector idVector, int id) return (std :: end (idVector)! = std :: find (std :: begin (idVector), std :: end (idVector), id));  int _pmain (int / * argc * /, _pchar * / * argv * / []) WidgetIdVector idVector; // Aggiungi alcuni numeri ID al vettore. idVector.push_back (5); idVector.push_back (8); // Emette un risultato che ci fa sapere se l'id si trova in // WidgetIdVector. std :: wcout << L"Contains 8: " << (ContainsWidgetId(idVector, 8) ? L"true." : L"false.") << std::endl; return 0; 

Conclusione

Ora dovresti avere una chiara comprensione dei tipi disponibili in C ++. Nel prossimo articolo, daremo un'occhiata più da vicino agli spazi dei nomi in C++.

Questa lezione rappresenta un capitolo di C ++, un eBook gratuito del team di Syncfusion.