[Powered by Google Translate] [Sezione 4 - più confortevole] [Rob Bowden - Harvard University] [Questo è CS50. - CS50.TV] Abbiamo un quiz domani, nel caso in cui voi non lo sapeva. E 'fondamentalmente su tutto ciò che poteva essere visto in classe o dovuto vedere in classe. Questo include puntatori, anche se sono un argomento molto recente. Si dovrebbe almeno capire gli alti livelli di loro. Tutto ciò che è stato superato in classe è necessario comprendere per il quiz. Quindi, se avete domande su di loro, si può chiedere loro ora. Ma questa sarà una sessione molto studente-led dove voi ragazzi fare domande, quindi speriamo che le persone hanno domande. Qualcuno ha domande? Sì. >> [Studente] Si può andare oltre puntatori di nuovo? Vado oltre puntatori. Tutte le variabili vivono necessariamente in memoria, ma di solito non ti preoccupare di questo e hai appena detto x + 2 e y + 3 e il compilatore di capire dove le cose vivono per voi. Una volta che hai a che fare con i puntatori, ora si sta esplicitamente l'utilizzo di tali indirizzi di memoria. Quindi una singola variabile sarà sempre e solo vivere in un unico indirizzo in un dato momento. Se vogliamo dichiarare un puntatore, qual è il tipo di andare a guardare come? Voglio dichiarare un puntatore p. Che cosa significa il tipo di aspetto? [Studente] int * p. Sì >>. Così int * p. E come faccio a farlo puntare a x? >> [Studente] Ampersand. [Bowden] Così commerciale è letteralmente chiamato l'indirizzo dell'operatore. Quindi, quando dico e x si sta facendo l'indirizzo di memoria della variabile x. Così ora ho il puntatore p, e ovunque nel mio codice posso usare * p o potrei usare x e sarà esattamente la stessa cosa. (* P). Cosa sta facendo questo? Che cosa significa quella stella? [Studente] Significa un valore in quel punto. Sì >>. Quindi, se lo guardiamo, può essere molto utile per far emergere gli schemi in cui si tratta di una piccola scatola di memoria per x, che succede ad avere il valore 4, allora abbiamo una piccola scatola di memoria per p, e così p punta a x, in modo da disegnare una freccia da p a x. Così, quando diciamo p * stiamo dicendo andare alla casella che si trova p. Star è seguire la freccia e poi fare quello che vuoi con quella scatola lì. Quindi posso dire * p = 7, e che andrà al box che è x e cambiamento che a 7. Oppure potrei dire int z = * p * 2; Questo è confuso perché si tratta di stelle, stelle. La stella è dereferenziazione p, l'altra stella è moltiplicare per 2. Notate avrei potuto altrettanto bene sostituito il p * con x. È possibile utilizzare nello stesso modo. E poi, più tardi posso avere il punto p di una completamente nuova cosa. Posso solo dire p = &z; Così ora p non punti più a x, ma punta a z. E ogni volta che lo faccio p * è lo stesso come fare z. Quindi la cosa utile di questo è una volta che iniziare a ricevere in funzioni. È un po 'inutile per dichiarare un puntatore che punta a qualcosa e poi siete solo dereferenziazione quando avrebbe potuto usare la variabile originale per cominciare. Ma quando si entra in funzioni - quindi diciamo che abbiamo una funzione, int foo, che prende un puntatore e non solo * p = 6; Come abbiamo visto prima con swap, non si può fare uno scambio efficace e una funzione separata da solo di passaggio interi perché tutto in C è sempre di passaggio per valore. Anche quando si sta passando i puntatori si sta passando per valore. Si dà il caso che questi valori sono indirizzi di memoria. Quindi, quando dico foo (p), io sto passando il puntatore nella funzione foo e poi pippo sta facendo * p = 6; Quindi all'interno di tale funzione, * p è ancora equivalente a x, ma non posso usare x all'interno di tale funzione, perché non è contenuto all'interno di tale funzione. Così * p = 6 è l'unico modo per accedere a una variabile locale da un'altra funzione. O, beh, i puntatori sono l'unico modo per accedere a una variabile locale da un'altra funzione. [Studente] Diciamo che voleva restituire un puntatore. Esattamente come si fa a farlo? [Bowden] Ritorna un puntatore come in qualcosa di simile int y = 3; ritorno & y? >> [Studente] Si '. [Bowden] Ok. Non si dovrebbe mai fare questo. Questo è male. Credo di aver visto in queste slides delle lezioni è iniziato a vedere questo schema tutto di memoria dove qui hai l'indirizzo di memoria 0 e qui si dispone di indirizzo di memoria 4 GB o da 2 a 32. Allora hai un po 'di roba e un po' di roba e poi avete il vostro stack di e hai la tua heap, che hai appena iniziato a conoscere, crescere. [Studente] Non è il mucchio sopra la pila? Gia '. L'heap è in cima, non è vero? >> [Studente] Beh, mettere 0 in cima. [Studente] Oh, ha messo 0 in alto. >> [Studente] Oh, va bene. Disclaimer: Ovunque con CS50 si sta andando a vedere in questo modo. >> [Studente] Ok. E 'solo che quando si è prima vedere pile, come quando si pensa di una pila si pensa di impilare le cose su uno sopra l'altro. Così si tende a capovolgere questo giro così lo stack sta crescendo come una pila normalmente invece della pila penzoloni. >> [Studente] Non cumuli tecnicamente crescere troppo, però? Dipende da cosa si intende per crescere. Lo stack e heap sempre crescere in direzioni opposte. Una pila è sempre in crescita nel senso che sta crescendo verso indirizzi di memoria più elevate, e il mucchio è in crescita verso il basso nel senso che sta crescendo verso indirizzi di memoria più bassi. Così la parte superiore è 0 e il fondo è indirizzi di memoria alta. Sono entrambi in crescita, solo in direzioni opposte. [Studente] Volevo solo dire che, perché hai detto che hai messo pila sul fondo perché sembra più intuitiva perché per la pila a partire dall'alto di un cumulo, heap è su se stesso anche, in modo that's - >> Si '. Si pensa anche al mucchio come cresce e più grande, ma lo stack di più. Quindi lo stack è quello che abbiamo tipo vogliamo mostrare crescita. Ma ovunque si guardi in caso contrario è intenzione di mostrare l'indirizzo 0 in alto e l'indirizzo di memoria più alto in fondo, quindi questo è il tuo campo visivo abituale di memoria. Hai una domanda? [Studente] Puoi dirci di più sul mucchio? Gia '. Prendo a che in un secondo. In primo luogo, tornando al motivo per cui tornare & y è una brutta cosa, sullo stack si ha un po 'di stack frame che rappresentano tutte le funzioni che sono stati chiamati. Quindi, ignorando le cose precedenti, la parte superiore del tuo stack sta andando sempre essere la funzione principale dato che è la prima funzione che viene chiamato. E poi quando si chiama un'altra funzione, lo stack è destinato a crescere verso il basso. Quindi, se io chiamo una funzione, foo, e prende il proprio quadro dello stack, si può chiamare alcuni, barra delle funzioni, ma prende il proprio quadro stack. E bar potrebbe essere ricorsiva e si poteva chiamare, e in modo che seconda convocazione, per bar sta per arrivare la sua cornice proprio stack. E così ciò che accade in questi stack frame sono tutte le variabili locali e tutti gli argomenti della funzione che - Tutte le cose che sono a livello locale come ambito di questa funzione andare in questi stack frame. Questo significa che quando ho detto qualcosa come bar è una funzione, Sto solo andando a dichiarare un intero e poi tornare un puntatore a tale numero intero. Così dove y vive? [Studente] y vive in bar. >> [Bowden] Si '. Da qualche parte in questa piccola piazza della memoria è una piazza che ha littler y in esso. Quando tornerò & y, sto restituendo un puntatore a questo piccolo blocco di memoria. Ma poi, quando di una funzione, il suo stack frame viene estratto dallo stack. Ed è per questo che si chiama stack. E 'come la struttura dati stack, se si sa di cosa si tratta. O anche come una pila di vassoi è sempre l'esempio, si sta per andare sul fondo, poi la prima funzione si chiama sta per andare in cima a quella, e non è possibile tornare alla pagina principale fino a tornare da tutte le funzioni che sono stati chiamati che sono stati posti su di esso. [Studente] Quindi, se avete fatto fare restituire il & y, tale valore è soggetto a modifiche senza preavviso. Sì, E'- >> [studente] Potrebbe essere sovrascritti. Sì >>. È completamente - Se si tenta di - Questo sarebbe anche un bar * int perché è restituire un puntatore, per cui il suo tipo di ritorno è int *. Se si tenta di utilizzare il valore di ritorno di questa funzione, è un comportamento indefinito perché tale puntatore punta a brutto ricordo. >> [Studente] Ok. Che importa se, per esempio, hai dichiarato int * y = malloc (sizeof (int))? Così va meglio. Sì. [Studente] Abbiamo parlato di come quando ci trascina le cose al nostro cestino non sta effettivamente cancellato, abbiamo appena perso i puntatori. Quindi, in questo caso possiamo davvero cancellare il valore o è ancora lì in memoria? Per la maggior parte, sarà ancora lì. Ma diciamo che ci capita di chiamare qualche altra funzione, baz. Baz sta per arrivare il proprio quadro di stack qui. Sta andando essere sovrascrivere tutte queste cose, e poi se in seguito si cerca di utilizzare il puntatore che avete ottenuto in precedenza, non sarà lo stesso valore. Sta andando essere cambiato solo perché si chiama baz funzione. [Studente] Ma se non avessimo, saremmo ancora ottenere 3? [Bowden] Con ogni probabilità, si farebbe. Ma non si può fare affidamento su questo. C dice solo un comportamento indefinito. [Studente] Oh, lo fa. Va bene. Quindi, quando si desidera restituire un puntatore, è qui che entra in uso malloc. Sto scrivendo in realtà solo tornare malloc (3 * sizeof (int)). Andremo oltre malloc più in un secondo, ma l'idea di malloc è tutte le variabili locali sempre messe in pila. Tutto ciò che è malloced va sul mucchio, e sarà per sempre e sempre sul mucchio fino a quando non esplicitamente liberare. Quindi questo significa che quando si malloc qualcosa, sta andando a sopravvivere dopo la funzione restituisce. [Studente] Sarà sopravvivere dopo il programma smette di funzionare? No. >> Ok, che sta per essere lì fino a quando il programma è tutto il percorso fatto in esecuzione. Sì >>. Siamo in grado di andare oltre i dettagli di ciò che accade quando il programma si ferma. Potrebbe essere necessario ricordare a me, ma che è una cosa del tutto separata. [Studente] Così malloc crea un puntatore? Sì >>. Malloc - >> [studente] Penso che malloc indica un blocco di memoria che può utilizzare un puntatore. [Bowden] Voglio quel diagramma di nuovo. >> [Studente] Quindi, questa funzione è attiva, anche se? [Studente] Sì, malloc designa un blocco di memoria che è possibile utilizzare, e poi lo restituisce l'indirizzo del primo blocco di memoria che. [Bowden] Si '. Quindi, quando si malloc, si sta prendendo un po 'di blocco di memoria che è attualmente nel mucchio. Se il cumulo è troppo piccolo, il mucchio è solo destinato a crescere, e cresce in questa direzione. Quindi diciamo che il cumulo è troppo piccolo. Poi si tratta di far crescere un po 'e restituire un puntatore a questo blocco che solo cresciuta. Quando si roba gratis, si sta facendo più spazio nel mucchio, così poi una successiva chiamata a malloc può riutilizzare quella memoria che si era precedentemente liberato. La cosa importante malloc e free è che ti dà il controllo completo per la durata di questi blocchi di memoria. Le variabili globali sono sempre vive. Le variabili locali sono vivi nel loro campo di applicazione. Non appena si supera una parentesi graffa, le variabili locali sono morti. Malloced memoria è viva quando si vuole che sia viva e poi viene rilasciato quando si dicono per essere rilasciato. Questi sono in realtà i soli 3 tipi di memoria, davvero. C'è gestione automatica della memoria, che è la pila. Le cose accadono in modo automatico. Quando si dice int x, memoria viene allocata per x int. Quando x esce dall'area di validità, memoria viene recuperata per x. Poi c'è la gestione della memoria dinamica, che è ciò che è malloc, che è quando si ha il controllo. È dinamicamente decidere quando la memoria deve e non deve essere allocata. E poi c'è statico, il che significa solo che vive per sempre, che è quello che le variabili globali sono. Sono solo sempre in memoria. Domande? [Studente] Si può definire un blocco semplicemente utilizzando le parentesi graffe ma non dover avere un'istruzione if o while o qualcosa di simile? È possibile definire un blocco come in una funzione, ma che ha anche le parentesi graffe. [Studente] Così non si può semplicemente come un paio casuale di parentesi graffe nel codice che hanno variabili locali? >> Sì, è possibile. All'interno della barra int avremmo potuto {int y = 3;}. Che dovrebbe essere proprio qui. Ma che definisce completamente il campo di applicazione int y. Dopo di che coppia secondo riccio, y non può più essere utilizzato. È quasi mai farlo, però. Tornando a ciò che accade quando un programma termina, c'è una specie di malinteso / mezza bugia che diamo al fine di fare solo le cose più facili. Vi diciamo che quando si alloca la memoria si sta assegnando un certo pezzo di RAM per quella variabile. Ma tu non sei veramente a contatto diretto con RAM mai nei vostri programmi. Se si pensa che, come ho disegnato - E in realtà, se si passa attraverso in GDB vedrete la stessa cosa. Indipendentemente dal numero di volte che si esegue il programma o quale programma è in esecuzione, lo stack è sempre sta per iniziare - si sta sempre andando a vedere le variabili intorno qualcosa indirizzo oxbffff. Di solito è da qualche parte in quella regione. Ma come si può eventualmente avere 2 programmi di puntatori a la stessa memoria? [Studente] C'è qualche designazione arbitraria di dove oxbfff dovrebbe essere sulla RAM che può effettivamente essere in luoghi diversi a seconda di quando la funzione è stata chiamata. Gia '. Il termine è la memoria virtuale. L'idea è che ogni singolo processo, ogni singolo programma che è in esecuzione sul computer ha il suo - supponiamo - 32 bit spazio di indirizzamento completamente indipendente. Questo è lo spazio degli indirizzi. Ha le sue proprie completamente indipendenti 4 GB da utilizzare. Quindi, se si esegue 2 programmi contemporaneamente, il programma vede 4 GB a se stesso, questo programma vede 4 GB a se stesso, ed è impossibile per questo programma per risolvere il riferimento un puntatore e finiscono con memoria da questo programma. E ciò che la memoria virtuale è una mappatura da uno spazio di indirizzamento dei processi di cose reali su RAM. Quindi tocca al sistema operativo per sapere che, ehi, quando questo tipo puntatore dereferenziazioni oxbfff, che in realtà significa che vuole RAM byte 1000, mentre se tale oxbfff programma dereferenziazioni, ha tanta voglia di RAM byte 10000. Essi possono essere arbitrariamente distanti. Questo vale anche per le cose all'interno di un unico spazio di indirizzamento dei processi. Così come si vede tutti i 4 gigabyte a se stesso, ma diciamo - [Studente] Fa ogni singolo processo - Diciamo che avete un computer con solo 4 gigabyte di RAM. Fa ogni singolo processo vedere le intere 4 gigabyte? Sì >>. Ma i 4 gigabyte si vede è una bugia. E 'appena ritenga di disporre di tutta questa memoria perché non conosco nessun altro processo esiste. Esso utilizza solo memoria tanto quanto effettivamente necessario. Il sistema operativo non sta per dare RAM a questo processo se non utilizza alcuna memoria in questa regione. Non è intenzione di dare memoria per quella regione. Ma l'idea è che - sto cercando di pensare - non riesco a pensare di un'analogia. Le analogie sono difficili. Uno dei problemi di memoria virtuale o di una delle cose che è la risoluzione è che i processi dovrebbe essere completamente all'oscuro uno all'altro. E così si può scrivere un programma che solo dereferenzia qualsiasi puntatore, come basta scrivere un programma che dice * (ox1234), e questo è l'indirizzo di memoria dereferenziazione 1234. Ma tocca al sistema operativo di tradurre poi cosa significa 1234. Quindi, se 1234 sembra essere un indirizzo di memoria valido per questo processo, come se fosse in pila o qualcosa del genere, allora questo verrà restituito il valore di tale indirizzo di memoria per quanto riguarda il processo sa. Ma se 1234 non è un indirizzo valido, come accade a terra in qualche piccolo pezzo di memoria qui che è al di là e oltre lo stack heap e non è stato realmente utilizzato, allora in quel momento che si ottiene cose come segfault perché si sta toccando la memoria che non si deve toccare. Questo è anche vero - Un sistema a 32 bit, 32 bit significa avere 32 bit per definire un indirizzo di memoria. È per questo che i puntatori sono 8 byte perché 32 bit sono 8 byte - o 4 byte. I puntatori sono di 4 byte. Quindi quando vedete un puntatore come oxbfffff, che è - All'interno di un determinato programma si può semplicemente costruire qualsiasi puntatore arbitraria, ovunque da ox0 di bue 8 f's - ffffffff. [Studente] Non hai detto che sono 4 byte? Sì >>. [Studente] Poi ogni byte avrà - >> [Bowden] esadecimale. Esadecimale - 5, 6, 7, 8. Quindi puntatori si sta andando a vedere sempre in formato esadecimale. E 'solo il modo in cui classificare i puntatori. Ogni 2 cifre esadecimali è di 1 byte. Quindi ci sarà 8 cifre esadecimali per 4 byte. Così ogni singolo puntatore su un sistema a 32 bit sta per essere di 4 byte, il che significa che nel processo si può costruire qualsiasi arbitrarie 4 byte e fare un puntatore su di esso, il che significa che, per quanto è a conoscenza, può indirizzare un intero 2 ai 32 byte di memoria. Anche se in realtà non ha accesso a che, anche se il computer dispone solo di 512 megabyte, che pensa che ha molta memoria. E il sistema operativo è abbastanza intelligente che sarà solo allocare cosa hai veramente bisogno. Non basta andare, oh, un nuovo processo: 4 concerti. Gia '. >> [Studente] Che cosa significa il bue significa? Perché si scrive? E 'solo il simbolo per esadecimale. Quando si vede un inizio numero con bue, le cose successive sono esadecimali. [Studente] Eri spiegando cosa succede quando un programma termina. Sì >>. Cosa succede quando un programma termina è il sistema operativo solo cancella le mappature che ha per questi indirizzi, e questo è tutto. Il sistema operativo può solo dare che la memoria a un altro programma da usare. [Studente] Ok. Quindi, quando si assegna qualcosa nel mucchio o le variabili dello stack o globali o altro, tutti scompaiono non appena il programma termina perché il sistema operativo è ora libero di dare che la memoria di qualsiasi altro processo. [Studente] Anche se ci sono probabilmente ancora valori scritti in? Sì >>. I valori sono probabilmente ancora lì. E 'solo che sarà difficile arrivare a loro. E 'molto più difficile arrivare a loro di quanto non lo è quello di arrivare a un file cancellato perché il tipo di file cancellato si trova lì per un lungo periodo di tempo e il disco rigido è molto più grande. Così sta andando a sovrascrivere diverse parti della memoria prima che accada per sovrascrivere il blocco di memoria che quel file usato per essere. Ma la memoria principale, memoria RAM, di scorrere molto più velocemente, quindi sarà molto rapidamente sovrascritto. Domande su questo o qualsiasi altra cosa? [Studente] Ho delle domande su un argomento diverso. Va bene >>. Qualcuno ha domande su questo? Va bene. Argomento diverso. >> [Studente] Ok. Stavo attraversando alcune delle prove pratiche, e in uno di essi si stava parlando del sizeof e il valore che restituisce o diversi tipi di variabili. Sì >>. E si dice che sia int e long sia di ritorno 4, quindi sono entrambi 4 byte. C'è qualche differenza tra un int e un lungo, o è la stessa cosa? Sì, c'è una differenza. Il C standard - Probabilmente sto andando a rovinare. Lo standard C è proprio come quello che è C, la documentazione ufficiale di C. Questo è quello che dice. Quindi lo standard C dice solo che un char sarà per sempre e sempre 1 byte. Tutto dopo che - a breve è sempre appena definito come maggiore o uguale a un char. Questo potrebbe essere strettamente maggiore di, ma non positivo. Un int è appena definito come maggiore o uguale ad una breve. E lunga è appena definito come maggiore o uguale a int. E un lungo tempo è maggiore o uguale a un lungo. Quindi l'unica cosa che il C standard definisce è l'ordinamento relativo di tutto. La quantità effettiva di memoria che le cose occupano in genere fino alla realizzazione, ma è piuttosto ben definito, a questo punto. >> [Studente] Ok. Così pantaloncini sono quasi sempre sarà 2 byte. Ints sono quasi sempre sarà 4 byte. Lunghi lunghi sono quasi sempre sarà 8 byte. E sospira, dipende dal fatto che si sta utilizzando una versione a 32 bit oa 64-bit del sistema. Così una lunga sta per corrispondere al tipo di sistema. Se si utilizza un sistema a 32 bit come il Appliance, che sta per essere di 4 byte. Se si utilizza una versione a 64-bit come un sacco di computer recenti, sarà 8 byte. Ints sono quasi sempre 4 byte a questo punto. Lunghi lunghi sono quasi sempre 8 byte. In passato, int usato per essere solo 2 byte. Ma notare che questo soddisfa pienamente tutti questi rapporti di maggiore e uguale a. Finché è perfettamente consentito di avere la stessa dimensione come un intero, ed è anche permesso di essere la stessa dimensione di un lungo tempo. E così succede essere che in 99,999% dei sistemi, che sta per essere uguale a sia un int o un long long. Dipende solo da 32-bit o 64-bit. >> [Studente] Ok. In galleggianti, come è il punto decimale designato in termini di bit? Così come binario? Sì >>. Non hai bisogno di sapere che per CS50. Non hai nemmeno sapere che nel 61. Non si impara che in realtà, in ogni corso. E 'solo una rappresentazione. Ho dimenticato le assegnazioni bit esatti. L'idea di virgola mobile è che si alloca un numero specifico di bit per rappresentare - In sostanza, tutto è in notazione scientifica. Quindi si assegna un certo numero di bit per rappresentare il numero stesso, come 1,2345. Non ho mai può rappresentare un numero con più di 5 cifre. Allora anche assegnare un numero specifico di bit in modo che tende ad essere come si può solo andare fino a un certo numero, come questo è il più grande esponente si può avere, e si può solo andare fino a un certo esponente, piace questo è il più piccolo esponente si può avere. Non mi ricordo i bit modo esatto sono assegnati a tutti questi valori, ma un certo numero di bit sono dedicati a 1,2345, un certo numero di bit sono dedicati all'esponente, ed è solo possibile rappresentare un esponente di una certa dimensione. [Studente] E una doppia? E 'come un float extra lungo? Sì >>. E 'la stessa cosa di un galleggiante ma ora si sta utilizzando 8 byte invece di 4 byte. Ora sarete in grado di utilizzare 9 cifre o 10 cifre, e questo potrà andare fino a 300 invece di 100. >> [Studente] Ok. E galleggianti sono anche 4 byte. Sì >>. Bene, ancora una volta, dipende probabilmente complessiva sull'attuazione generale, ma galleggianti sono di 4 byte, doppie sono 8. Doppio sono chiamati doppio perché sono il doppio della dimensione dei carri allegorici. [Studente] Ok. E sono lì raddoppia doppio? Non ci sono >>. Penso - >> [studente] Ti piace lunghi lunghi? Sì >>. Non ci penso. Sì. [Studente] In prova dello scorso anno c'è stata una domanda circa la funzione principale dover essere parte del vostro programma. La risposta è che non deve essere parte del vostro programma. In quale situazione? Questo è quello che ho visto. [Bowden] Sembra - >> [studente] Quale situazione? Avete il problema? >> [Studente] Si ', posso sicuramente tirare su. Non deve essere, tecnicamente, ma in fondo che sta per essere. [Studente] ho visto uno su un altro, l'anno. E 'stato come Vero o Falso: Un valido - >> Oh, un file c.? . [Studente] Tutti i file c deve avere - [sia parlando in una sola volta - incomprensibile] Va bene. Così che è separato. Un file. C ha solo bisogno di contenere le funzioni. È possibile compilare un file in codice macchina, binario, a prescindere, senza che sia eseguibile ancora. Un file eseguibile valido deve avere una funzione principale. È possibile scrivere 100 funzioni in 1 file ma non principali e quindi compilare che fino al binario, allora si scrive un altro file che ha solo principale, ma lo chiama un po 'di queste funzioni in questo file binario qui. E così, quando si sta facendo l'eseguibile, questo è quello che fa il linker si unisce questi 2 file binari in un file eseguibile. Così un file. C non ha bisogno di avere una funzione principale affatto. E su grandi basi di codice vedrete migliaia di file. C e 1 file principale. Altre domande? [Studente] Ci fu un'altra domanda. Ha detto che fanno è un compilatore. Vero o Falso? E la risposta era falsa, e ho capito perché non è come Clang. Ma ciò che chiamiamo fare se non è? Fai è fondamentalmente solo - posso vedere esattamente ciò che lo chiama. Ma funziona solo comandi. Marca. Posso tirare questo. Gia '. Oh, si '. Fare fa anche questo. Questo dice lo scopo della utility make è quello di determinare automaticamente che pezzi di un programma di grandi dimensioni devono essere ricompilati ed eseguire i comandi di ricompilare loro. Si può fare rendere i file che sono assolutamente enorme. Fai guarda i timestamp di file e, come abbiamo detto prima, è possibile compilare i singoli file verso il basso, e non è fino ad arrivare al linker che vengono messe insieme in un file eseguibile. Quindi, se si dispone di 10 file differenti e si apporta una modifica a 1 di loro, allora ciò che marca sta per fare è solo ricompilare che 1 file e poi ricollegare il tutto. Ma è molto più stupido di quello. E 'a voi per definire completamente che questo è quello che dovrebbe fare. E 'di default ha la capacità di riconoscere questa roba data e ora, ma è possibile scrivere un file di marca a fare qualsiasi cosa. È possibile scrivere un make file in modo che quando si digita farlo proprio cd in un'altra directory. Ero frustrato perché ho tutto virare all'interno del mio Appliance e poi visualizzare il PDF del Mac. Così vado a Finder e posso andare, Connetti al server, e il server mi connetto è la mia macchina, e quindi apro il PDF che viene compilato da LaTeX. Ma ero frustrato perché ogni volta che avevo bisogno di aggiornare il file PDF, Ho dovuto copiarlo in una directory specifica che potrebbe accedere e stava diventando fastidioso. Così, invece ho scritto un file di marca, che si deve definire come rende le cose. Come fare in questo è PDF LaTeX. Proprio come qualsiasi altro file altra marca - o credo che non avete visto i file make, ma abbiamo in Appliance un file globale marca che dice basta, se si compila un file C, utilizzare Clang. Ed ecco nel mio file marca che faccio io dico, il file che si sta andando a voler compilare con PDF LaTeX. E così è LaTeX PDF che sta facendo la compilazione. Fare non sta compilando. E 'solo l'esecuzione di questi comandi nella sequenza ho specificato. Così funziona LaTeX PDF, lo copia nella directory voglio che essere copiati, la cd alla directory e fa altre cose, ma tutto ciò che fa è riconoscere quando un file viene modificato, e se cambia, allora verrà eseguito i comandi che si suppone l'esecuzione quando cambia il file. >> [Studente] Ok. Non so dove trovare i file make globali sono per me di check it out. Altre domande? Qualsiasi cosa, da passato quiz? Tutte le cose puntatore? Ci sono cose sottili con puntatori come - Non ho intenzione di essere in grado di trovare una domanda quiz su di esso - ma proprio come questo genere di cose. Assicuratevi di aver capito che quando dico int * x * y - Questo non è esattamente nulla qui, credo. Ma come * x * y, quelli sono 2 variabili che sono in pila. Quando dico x = malloc (sizeof (int)), x è ancora una variabile sullo stack, malloc è qualche blocco più nel mucchio, e stiamo avendo punto x al mucchio. Quindi, qualcosa sui punti dello stack al mucchio. Ogni volta che si malloc niente, si sta inevitabilmente la memorizzazione all'interno di un puntatore. In modo che il puntatore è in pila, il blocco malloced è sul mucchio. Un sacco di persone si confondono e dicono int * x = malloc, x è sul mucchio. No. Cosa x indica è sul mucchio. x sé è in pila, a meno che per qualche motivo si è x essere una variabile globale, nel qual caso sembra essere in un'altra regione di memoria. Quindi tenere traccia, questi diagrammi scatola e la freccia sono abbastanza comuni per il quiz. Oppure, se non è il quiz 0, sarà il quiz 1. Si deve sapere tutto questo, i passaggi nella compilazione dal momento che ha dovuto rispondere alle domande su quelle. Sì. [Studente] Potremmo andare oltre quei passi - >> Certo. Prima di passi e la compilazione che abbiamo pre-elaborazione, compilazione, il montaggio e il collegamento. Pre-elaborazione. Che cosa fare? E 'il passo più semplice in - be', non come - ciò non significa che dovrebbe essere ovvio, ma è il passo più semplice. Voi ragazzi potrebbe implementare voi stessi. Gia '. [Studente] Prendete quello che avete nel vostro comprende come questo e lo copia e poi definisce anche. Sembra per cose come # include e # define, e solo copie e paste di quelli effettivamente cosa dire. Quindi, quando si dice # include cs50.h, il preprocessore è copiare e incollare cs50.h in quella riga. Quando si dice # define x essere 4, il preprocessore attraversa l'intero programma e sostituisce tutte le istanze di x con 4. Così il preprocessore prende un file valido C e produce un file valido C dove le cose sono stati copiati e incollati. Così ora la compilazione. Che cosa fare? [Studente] E va da C a binario. [Bowden] Non andare fino in binario. [Studente] in codice macchina, allora? >> Non e 'codice macchina. [Studente] Assemblea? Assemblea >>. Va a Assemblea prima che arriva fino al codice C, e la maggior parte dei linguaggi di fare qualcosa del genere. Scegli qualsiasi linguaggio di alto livello, e se avete intenzione di compilarlo, è probabile che per compilare in passi. In primo luogo si sta andando a compilare Python a C, allora sta andando a compilare C all'Assemblea, e poi Assemblea sta per essere tradotto in binario. Quindi la compilazione sta per portarlo da C a Assembly. La parola compilazione solito significa portandolo da un livello superiore di un linguaggio di basso livello di programmazione. Quindi questa è la soluzione soltanto in presenza di compilazione in cui si inizia con un linguaggio di alto livello e finire in un linguaggio di basso livello, ed è per questo che il passo si chiama compilazione. [Studente] Durante la compilazione, diciamo che hai fatto # include cs50.h. Sarà il compilatore ricompilare il cs50.h, come le funzioni che sono in là, e tradurlo in codice Assembly pure, o intende copiare e incollare qualcosa che è stato pre-montaggio? cs50.h sarà praticamente mai molto a finire in Assemblea. Cose come prototipi di funzione e tutte le cose sono solo per voi di stare attenti. Garantisce che il compilatore può controllare le cose come si sta chiamando le funzioni con i tipi restituiti giusti e gli argomenti giusti e roba. Così cs50.h saranno pre-elaborato nel file, e poi quando si sta compilando è praticamente buttato via dopo che si assicura che tutto venga richiamato correttamente. Ma le funzioni definite nella libreria CS50, che sono separati da cs50.h, coloro che non saranno separatamente compilato. Che effettivamente venire giù nella fase di collegamento, quindi dovremo arrivare a questo in un secondo. Ma in primo luogo, che cosa è il montaggio? [Studente] Assemblea a binario? Sì >>. Assemblaggio. Noi non chiamiamo la compilazione perché Assemblea è praticamente una pura traduzione di binario. C'è molto poco in logica che va da Assemblea binario. E 'come guardare in una tabella, oh, noi abbiamo questa istruzione; che corrisponde al binario 01.110. E così i file che l'assemblaggio in generale uscite sono. File o. E i file. O sono ciò che noi dicevamo prima, come un file non ha bisogno di avere una funzione principale. Ogni file può essere compilato fino a un file. O fino a quando si tratta di un file valido C. Esso può essere compilato fino a. O. Ora, il collegamento è quello che porta in realtà un gruppo di. File o e li porta a un file eseguibile. E allora cosa fa Il collegamento è che si può pensare della biblioteca CS50 come file. O. Si tratta di un file binario già compilato. E così, quando si compila il file, il vostro hello.c, che chiama GetString, hello.c viene compilato fino a hello.o, hello.o è ora in binario. Esso utilizza GetString, quindi ha bisogno di andare oltre a cs50.o, e il linker li smooshes insieme e copia GetString in questo file e se ne esce con un eseguibile che ha tutte le funzioni di cui ha bisogno. Quindi cs50.o non è in realtà un file di O, ma è abbastanza vicino che non vi è alcuna differenza fondamentale. Quindi, collegando solo porta un gruppo di file insieme separatamente che contengono tutte le funzioni ho bisogno di usare e crea il file eseguibile che funziona anche. E così questo è anche quello che stavamo dicendo prima dove si può avere 1000. file c, si compila tutti a. file o, che probabilmente prendere un po ', poi si cambia 1. file c. Hai solo bisogno di ricompilare che 1. File c e poi tutto il resto ricollegare, collegare tutto insieme. [Studente] Quando siamo collegamento scriviamo lcs50? Si ', so-lcs50. Che i segnali di bandiera per il linker che si dovrebbe essere di collegamento in quella biblioteca. Domande? Abbiamo superato il binario diverso da quello che 5 secondi nella prima lezione? Non ci penso. Si deve sapere tutte le grandi uscite che abbiamo superato, e si dovrebbe essere in grado di, se vi abbiamo dato una funzione, si dovrebbe essere in grado di dire che è O grande, più o meno. Or bene, grande O è ruvida. Quindi, se vedete cicli for nidificati looping sopra lo stesso numero di cose, come int i, i > [studente] n quadrato. >> Tende essere n squared. Se è stato triplo nidificato, tende ad essere n cubo. Quindi, quel genere di cosa che si dovrebbe essere in grado di rilevare immediatamente. È necessario conoscere insertion sort e bubble sort e merge sort e tutti quelli. E 'più facile capire perché sono quelli n quadrato e n log n e tutto il resto perché penso che ci fosse un quiz su un anno quando abbiamo praticamente ti ha dato un'implementazione di bubble sort e disse: "Che cosa è il tempo di esecuzione di questa funzione?" Quindi, se lo si riconosce come bubble sort, allora si può subito dire n quadrato. Ma se si guarda, non avrete nemmeno bisogno di realizzare il suo bubble sort; si può solo dire che questo sta facendo questo e questo. Questo è n quadrato. [Studente] Vi sono esempi più difficili si può venire con, come una simile idea di capire? Non credo che ci vuoi dare qualche esempio difficili. La cosa bubble sort è quanto di più difficile come saremmo andati, e anche che, fino a quando si capisce che si sta scorrendo la matrice per ogni elemento della matrice, che sta per essere qualcosa che sta al quadrato n. Ci sono domande di carattere generale, come qui abbiamo - Oh. Proprio l'altro giorno, Doug ha affermato: "Ho inventato un algoritmo in grado di ordinare un array "Di n numeri in O (log n) tempo!" Quindi, come facciamo a sapere che è impossibile? [Risposta degli studenti incomprensibile] >> Si '. Per lo meno, si deve toccare ogni elemento dell'array, quindi è impossibile ordinare un array di - Se tutto è in ordine indifferenziati, allora si sta andando ad essere in contatto con tutto ciò che nella matrice, quindi è impossibile farlo in meno di O di n. [Studente] Lei ci ha mostrato l'esempio di essere in grado di farlo in O di n se si utilizza un sacco di memoria. Sì >>. E that's - mi ricordo cosa that's - E 'sorta di conteggio? Hmm. Che è un algoritmo di ordinamento intero. Stavo cercando il nome speciale per questo che non riuscivo a ricordare la settimana scorsa. Gia '. Questi sono i tipi di tipi che possono realizzare le cose in grande O di n. Ma ci sono delle limitazioni, come è possibile utilizzare solo numeri interi fino a un certo numero. Inoltre, se si sta cercando di ordinare that's qualcosa - Se l'array è 012, -12, 151, 4 milioni di euro, allora che singolo elemento sta per rovinare completamente l'intero ordinamento. Domande? [Studente] Se si dispone di una funzione ricorsiva e fa solo le chiamate ricorsive all'interno di una istruzione return, che è la coda ricorsiva, e quindi non che utilizzano più memoria in fase di esecuzione o sarebbe almeno comparabile utilizzare la memoria come una soluzione iterativa? [Bowden] Sì. Sarebbe probabilmente un po 'più lento, ma non proprio. Ricorsiva di coda è piuttosto buona. Guardando di nuovo stack frame, diciamo che abbiamo principale e abbiamo bar int (int x) o qualcosa del genere. Questa non è una funzione ricorsiva perfetto, ma bar ritorno (x - 1). Così, ovviamente, questo è difettoso. Hai bisogno di casi di base e roba del genere. Ma l'idea è che si tratta di coda ricorsiva, che significa che quando chiamate bar principale che sta per ottenere il suo stack frame. In questo stack frame ci sarà un piccolo blocco di memoria che corrisponde al suo argomento x. E così diciamo principale succede a chiamare bar (100); Quindi x sta per iniziare come 100. Se il compilatore riconosce che si tratta di una funzione ricorsiva di coda, poi quando fa il suo bar chiamata ricorsiva alla battuta, invece di fare un nuovo frame dello stack, che è dove la pila inizia a crescere in gran parte, alla fine verrà eseguito nel mucchio e poi si arriva segfault perché la memoria inizia a collisione. Così, invece di fare il proprio quadro di stack, può realizzare, hey, non ho mai bisogno di tornare in questo stack frame, così invece mi limiterò a sostituire questo argomento con 99 e quindi avviare bar tutto. E poi lo farò più e raggiungerà bar ritorno (x - 1), e invece di fare un nuovo frame dello stack, sarà solo sostituire il suo argomento attuale con 98 e poi tornare al proprio all'inizio del bar. Tali operazioni, in sostituzione di quello 1 valore sullo stack e saltare di nuovo all'inizio, sono abbastanza efficienti. Quindi non solo è questo l'utilizzo della memoria come una funzione separata che è iterativo perché si sta utilizzando solo uno stack frame, ma non siete affetti lati negativi di dover chiamare le funzioni. Funzioni di chiamata può essere un po 'costoso perché ha a che fare tutto questo setup e teardown e tutta questa roba. Quindi questo ricorsione in coda è buono. [Studente] Perché non creare nuovi passi? Perché realizza non necessario. La chiamata al bar è appena rientrato la chiamata ricorsiva. Quindi non ha bisogno di fare qualsiasi cosa con il valore di ritorno. E 'solo andando a rinviarlo immediatamente. Quindi è solo andando a sostituire propria tesi e ricominciare da capo. E inoltre, se non si dispone della versione ricorsiva di coda, poi si arriva tutti questi bar dove, quando questa barra restituisce deve restituire il suo valore a questo, poi quella barra ritorna immediatamente e restituisce il suo valore a questo, allora è solo andando a restituire immediatamente e restituisce il suo valore a questa. Quindi stai salvando questo schioccare tutte queste cose fuori della pila in quanto il valore di ritorno è solo andare a essere passato tutta la strada lo stesso. Allora perché non sostituire il nostro argomento con l'argomento aggiornato e ricominciare da capo? Se la funzione non è tail ricorsiva, se fai una cosa del genere - [Studente] se bar (x + 1). Sì >>. Quindi, se lo metti in condizione, allora si sta facendo qualcosa con il valore di ritorno. O anche se lo farete ritorno 2 * bar (x - 1). Così ora bar (x - 1) deve restituire in modo per consentire di calcolare 2 volte tale valore, così ora ha bisogno di una sua struttura propria pila separata, e ora, non importa quanto duramente si tenta, si sta andando ad avere bisogno di - Questa non è la coda ricorsiva. [Studente] Dovrei cercare di portare un ricorsione per puntare ad una ricorsione in coda - [Bowden] In un mondo ideale, ma in CS50 non è necessario. Al fine di ottenere la ricorsione in coda, in genere, si imposta un ulteriore argomento bar dove prenderà int x in y e y corrisponde l'ultima cosa che si desidera tornare. Allora questo si sta andando di tornare bar (x - 1), 2 * y. Ecco, questo è solo un alto livello come trasformare le cose di essere coda ricorsiva. Ma l'argomento extra - E poi, alla fine, quando si raggiunge il caso base, basta tornare y perché hai accumulato per tutto il tempo il valore di ritorno che si desidera. Si tipo di lo hanno fatto iterativamente ma usando chiamate ricorsive. Domande? [Studente] Forse circa l'aritmetica dei puntatori, come quando si usano le stringhe. Certo >>. Puntatore aritmetica. Quando si utilizzano le stringhe è facile perché le stringhe sono stelle char, caratteri sono sempre e sempre un singolo byte, e così l'aritmetica dei puntatori è equivalente a aritmetica regolare quando hai a che fare con le stringhe. Diciamo solo che char * s = "ciao". Così abbiamo un blocco in memoria. Ha bisogno di 6 byte, perché è sempre necessario il terminatore null. E char * s sta per indicare l'inizio di questo array. Quindi s punti lì. Ora, questo è fondamentalmente come qualsiasi array funziona, indipendentemente dal fatto che si trattava di un ritorno da malloc o se è in pila. Qualsiasi array è fondamentalmente un puntatore all'inizio della matrice, e quindi ogni operazione di matrice, qualsiasi indicizzazione, è solo andare in tale matrice un determinato offset. Quindi, quando dico qualcosa come s [3], si tratta di andare a s e il conteggio 3 caratteri trovi Quindi s [3], abbiamo 0, 1, 2, 3, così s [3] sta per fare riferimento a questa l. [Studente] E si potrebbe raggiungere lo stesso valore facendo s + 3 e poi stelle parentesi? Sì. Ciò è equivalente a * (s + 3); e che è sempre e sempre equivalente, non importa quello che fai. Non hai mai bisogno di usare la sintassi staffa. È sempre possibile utilizzare il * (s + 3) la sintassi. Le persone tendono a come la sintassi staffa, però. [Studente] Così tutte le matrici sono in realtà solo puntatori. Vi è una distinzione sottile quando dico int x [4]; >> [studente] Ritiene che creano la memoria? [Bowden] Che sta per creare 4 int in pila, quindi 16 byte generale. E 'intenzione di creare 16 byte nello stack. x non vengono memorizzati da nessuna parte. È solo un simbolo relativo all'inizio della cosa. Perché hai dichiarato l'array all'interno di questa funzione, ciò che il compilatore sta per fare è sostituire tutte le istanze della variabile x con cui è successo di scegliere di mettere questi 16 byte. Non può farlo con char * s, perché s è un puntatore reale. E 'gratuito per puntare poi ad altre cose. x è una costante. Non si può puntare a un array diverso. >> [Studente] Ok. Ma questa idea, questo indice, è la stessa indipendentemente dal fatto che si tratta di un array tradizionale o se si tratta di un puntatore a qualcosa o se si tratta di un puntatore a un array malloced. E in effetti, è così equivalente che è anche la stessa cosa. In realtà si traduce solo quello che c'è dentro le staffe e ciò che è rimasto delle staffe, li somma e dereferenziazioni. Quindi questo è altrettanto valide * (s + 3) o s [3]. [Studente] Si può avere puntatori che puntano a 2-dimensionali? E 'più difficile. Tradizionalmente, no. A 2-dimensionale array è solo un 1-dimensionale array con un po 'di sintassi conveniente perché quando dico int x [3] [3], questo è davvero solo 1 campo con 9 valori. E così quando ho indice, il compilatore sa cosa voglio dire. Se dico x [1] [2], si sa che io voglio andare in seconda fila, quindi sta andando a saltare i primi 3, e poi vuole la seconda cosa, in quanto, così sta andando a ottenere questo. Ma è ancora solo un array monodimensionale. E quindi se volessi assegnare un puntatore a tale array, Direi int * p = x; Il tipo di x è solo - E 'ruvida tipo detto di x in quanto è solo un simbolo e non è una variabile reale, ma è solo un int *. x è solo un puntatore all'inizio di questo. >> [Studente] Ok. E quindi non sarà in grado di accedere [1] [2]. Penso che ci sia una speciale sintassi per la dichiarazione di un puntatore, qualcosa di ridicolo come int (* p [-. qualcosa di assolutamente ridicola che non so nemmeno. Ma c'è una sintassi per la dichiarazione di puntatori come tra parentesi e le cose. Si può anche non lasciartelo fare. Ho potuto guardare indietro a qualcosa che mi avrebbe detto la verità. Cercherò in un secondo momento, se vi è una sintassi per il punto. Ma non è mai lo vedrà. E anche la sintassi è così arcaica che se lo si utilizza, la gente sarà sconcertato. Gli array multidimensionali sono piuttosto rari in quanto è. È più o meno - Beh, se si sta facendo le cose di matrice non sarà raro, ma in C si sta raramente intenzione di utilizzare array multidimensionali. Gia '. >> [Studente] Diciamo che dispone di una vasta molto lungo. Così in memoria virtuale sembrerebbe essere tutti consecutivi, come gli elementi a destra accanto all'altro, ma nella memoria fisica, sarebbe possibile per quella da dividere? Sì >>. Come funziona di memoria virtuale è lo separa solo - L'unità di allocazione è una pagina, che tende ad essere 4 kilobyte, e così quando un processo dice: ehi, voglio usare questa memoria, il sistema operativo sta per allocare 4 kilobyte per quel piccolo blocco di memoria. Anche se si usa solo un singolo byte po 'in tutto il blocco di memoria, il sistema operativo è intenzione di dare la piena 4 kilobyte. Che cosa questo significa è che ho potuto - diciamo che questo è il mio stack. Questa pila potrebbe essere separati. Il mio stack potrebbe essere megabyte e megabyte. Il mio stack potrebbe essere enorme. Ma la stessa pila deve essere suddiviso in singole pagine, che, se guardiamo da questa parte diciamo che questa è la nostra RAM, se ho 2 gigabyte di RAM, questo è 0 vero indirizzo come il byte zero della mia RAM, e questo è di 2 gigabyte tutta fin qui. Quindi, questa pagina potrebbe corrispondere a questo blocco qui. Questa pagina potrebbe corrispondere a questo blocco qui. Questo potrebbe corrispondere a questo qui. Quindi il sistema operativo è libero di assegnare memoria fisica ad ogni singola pagina arbitrariamente. E questo significa che se questo confine accade a cavallo di un array, un array avviene da sinistra di questo e destra di questo ordine di una pagina, allora tale matrice sarà suddiviso in memoria fisica. E poi, quando si esce dal programma, al termine del processo, queste mappature vengono cancellati e poi è libero di utilizzare questi piccoli blocchi per altre cose. Altre domande? [Studente] Il puntatore aritmetica. >> Oh yeah. Strings erano più facili, ma guardando qualcosa di interi, Ma torniamo al int x [4]; Se questo è un array o se si tratta di un puntatore a un array di 4 interi malloced, che sta per essere trattati allo stesso modo. [Studente] Così gli array sono nel mucchio? Bowden [] array non sono sul mucchio. >> [Studente] Oh. [Bowden] Questo tipo di matrice tende ad essere in pila meno che non venga dichiarata a - ignorando le variabili globali. Non utilizzare le variabili globali. All'interno di una funzione che dico int x [4]; E 'intenzione di creare una struttura a 4 intero blocco sullo stack per questa matrice. Ma questo malloc (4 * sizeof (int)); sta per andare sul mucchio. Ma dopo questo punto posso usare x e p in più o meno con le stesse modalità, A parte le eccezioni che ho detto prima in merito è possibile riassegnare p. Tecnicamente, le loro dimensioni sono un po 'diversa, ma questo è del tutto irrilevante. Non si è mai effettivamente utilizzare le loro dimensioni. Il p Potrei dire p [3] = 2, oppure x [3] = 2; Si possono usare in esattamente gli stessi metodi. Quindi l'aritmetica dei puntatori ora - Sì. [Studente] Non si deve fare * p se hai le staffe? Le staffe sono un dereference implicita. Va bene >>. A dire il vero, anche quello che stai dicendo con la si può ottenere array multidimensionali con i puntatori, cosa si può fare è qualcosa di simile, diciamo, int ** pp = malloc (sizeof (int *) * 5); Mi limiterò a scrivere tutto per primo. Non volevo che uno. Va bene. Quello che ho fatto qui è - Questo dovrebbe essere pp [i]. Così pp è un puntatore a un puntatore. Stai mallocing pp per puntare a un array di 5 stelle int. Così in memoria che si hanno sul pp pila E 'intenzione di puntare a un array di 5 blocchi che sono tutti loro puntatori. E poi quando ho malloc qui, ho malloc che ciascuno di questi puntatori singoli dovrebbe puntare a un blocco separato di 4 byte sul mucchio. Quindi questo punti a 4 byte. E questo punti a un altro 4 byte. E tutti indicano i propri 4 byte. Questo mi dà un modo di fare le cose multidimensionali. Potrei dire pp [3] [4], ma ora questo non è la stessa cosa come array multidimensionali perché gli array multidimensionali tradotto [3] [4] in un unico spostamento nella matrice x. Questo p dereferenzia, accede al terzo indice, quindi dereferenziazioni che e accessi - 4 non sarebbero valide - il secondo indice. Considerando che, quando abbiamo avuto la int x [3] [4] prima come un array multidimensionale e quando si fa doppio supporto è davvero solo un dereference singolo, stai seguendo un unico puntatore e poi un offset, questo è davvero riferimenti 2D. Segui 2 puntatori separati. Quindi, anche questo consente tecnicamente di avere array multidimensionali dove ogni individuo è matrice dimensioni diverse. Quindi penso che frastagliati array multidimensionali è quello che si chiama poiché in realtà la prima cosa che potrebbe puntare a qualcosa che ha 10 elementi, la seconda cosa potrebbe puntare a qualcosa che ha 100 elementi. [Studente] C'è un limite al numero di puntatori che si possono avere indicando altri puntatori? No. >> Si può avere int ***** p. Torna l'aritmetica dei puntatori - >> [studente] Oh. Sì >>. [Studente] Se ho int p *** e poi faccio un dereferenziazione e dico * p è uguale a questo valore, sta solo andando a fare 1 livello di dereferenziazione? Sì >>. Quindi, se voglio accedere la cosa che l'ultimo puntatore punta a - Poi fare p ***. Va bene >>. Quindi questo è p punta a 1 isolato, punta a un altro blocco, punta a un altro blocco. Poi se si fa * p = qualcos'altro, allora si sta modificando questo per puntare ora in un altro blocco. Va bene >>. [Bowden] E se questi sono stati malloced, quindi ora avete fatto trapelare memoria a meno che non vi capita di avere riferimenti diversi di questi dal momento che non può tornare a quelli che hai appena buttato via. Puntatore aritmetica. int x [4], sta per allocare un array di 4 interi dove x sta per puntare all'inizio della matrice. Quindi, quando dico una cosa del genere x [1], voglio che significa andare al secondo intero nella matrice, che sarebbe questo. Ma in realtà, questo è 4 byte nella matrice da questo intero occupa 4 byte. Quindi, un offset di 1 significa veramente un offset di 1 volte la dimensione di qualsiasi tipo di matrice è. Si tratta di un array di interi, in modo da sapere a che fare 1 volte dimensione di int quando vuole compensare. L'altra sintassi. Ricordate che questo è equivalente a * (x + 1); Quando dico puntatore + 1, cosa che ritorna è l'indirizzo che il puntatore sta memorizzando plus 1 volte la dimensione del tipo di puntatore. Quindi, se x = ox100, allora x + 1 = ox104. E si può abusare di questo e dire qualcosa del tipo char * c = (char *) x; e ora c sta per essere lo stesso indirizzo di x. c sarà uguale a ox100, ma c + 1 sarà pari a ox101 poiché l'aritmetica dei puntatori dipende dal tipo del puntatore che si sta aggiungendo a. Quindi c + 1, sembra in c, è un puntatore char, quindi è intenzione di aggiungere 1 volte dimensione del carattere, che sta andando sempre essere 1, in modo da ottenere 101, mentre se lo faccio x, che è anche ancora al 100, x + 1 sta per essere 104. [Studente] Si può usare c + +, al fine di avanzare il puntatore di 1? Sì, è possibile. Non si può fare che con x perché x è solo un simbolo, è una costante, non si può cambiare x. Ma c sembra essere solo un puntatore, in modo da c + + è perfettamente valido e questo valore viene aumentato di 1. Se c erano solo un int *, allora c + + sarebbe 104. + + Non l'aritmetica dei puntatori come c + 1 avrebbe fatto l'aritmetica dei puntatori. Questo è in realtà come un sacco di cose come merge sort - Invece di creare copie di cose, si può invece passare - Come se avessi voluto passare questa metà della matrice - Facciamo cancellare una parte di questo. Diciamo che ho voluto passare questo lato della matrice in una funzione. Cosa dovrei passare a tale funzione? Se mi passa x, sto passando questo indirizzo. Ma voglio passare questo indirizzo particolare. Che cosa devo passare? [Studente] Puntatore + 2? [Bowden] Quindi x + 2. Sì. Che sarà questo indirizzo. Potrai anche molto spesso lo vedono come x [2] e quindi l'indirizzo di questo. Quindi è necessario prendere l'indirizzo di essa, perché la staffa è un dereference implicita. x [2] si riferisce al valore che si trova in questa casella, e quindi si desidera che l'indirizzo di quella scatola, così si dice & x [2]. È così che qualcosa in merge sort in cui si desidera passare la metà della lista a qualcosa davvero basta passare & x [2], e ora per quanto riguarda la chiamata ricorsiva è interessato, il mio nuovo array inizia lì. Domande last minute. [Studente] Se non mettiamo una e commerciale o - cosa che ha chiamato? >> Star? [Studente] Star. >> Tecnicamente, operatore dereferenziare, ma - >> [studente] risoluzione del riferimento. Se non mettiamo una stella o di una e commerciale, che cosa succede se solo dire y = x e x è un puntatore? Qual è il tipo di y? >> [Studente] mi limiterò a dire che il puntatore 2. Quindi, se hai appena detto y = x, x ora e il punto y per la stessa cosa. >> [Studente] Punto alla stessa cosa. E se x è un puntatore a int? Sarebbe >> lamentano perché non è possibile assegnare i puntatori. [Studente] Ok. Ricordate che i puntatori, anche se li disegnare come frecce, in realtà tutto ciò che negozio - int * x - davvero tutto x è la memorizzazione è qualcosa di simile ox100, che ci capita di rappresentare come punta al blocco conservato a 100. Quindi, quando dico int * y = x, sto solo copiando ox100 in y, che stiamo solo andando a rappresentare come y, anche indicando ox100. E se dico int i = (int) x, quindi mi sta per memorizzare qualunque sia il valore di ox100 è all'interno di esso, ma ora che sta per essere interpretato come un numero intero al posto di un puntatore. Ma è necessario il cast altrimenti si lamenterà. [Studente] Quindi hai intenzione di lanciare - E 'intenzione di essere colata int x int o colata di y? [Bowden] Cosa? [Studente] Ok. Dopo queste parentesi è lì sarà un x aa o là? [Bowden] In entrambi i casi. x ed y sono equivalenti. >> [Studente] Ok. Perché sono entrambi puntatori. Sì >>. [Studente] Quindi sarebbe memorizzare il 100 in forma esadecimale intero? >> [Bowden] Si '. Ma non il valore di ciò a cui punta. [Bowden] Si '. >> [Studente] Quindi, solo l'indirizzo in formato intero. Va bene. [Bowden] Se si voleva per qualche strana ragione, si potrebbe trattare esclusivamente con puntatori e mai trattare con i numeri interi e proprio essere come int * x = 0. Poi si sta andando ad ottenere davvero confuso una volta l'aritmetica dei puntatori comincia ad accadere. Quindi i numeri che memorizzano sono prive di significato. E 'proprio come si finisce per interpretarli. Quindi sono liberi di copiare ox100 da un * int a un int, e io sono libero di assegnare - tu sei probabilmente andando a sgridato per non fusione - Sono libero di assegnare qualcosa di simile (int *) ox1234 in questo * int arbitrario. Quindi ox123 è altrettanto valido è un indirizzo di memoria come & y. & Y succede a restituire qualcosa che è praticamente ox123. [Studente] Vorrei che essere un modo davvero bello per passare da esadecimale in forma decimale, come se si dispone di un puntatore e la lanci come un int? [Bowden] È possibile in realtà solo stampare utilizzando come printf. Diciamo che ho int y = 100. Così printf (% d \ n - come si dovrebbe già sapere - che la stampa come un intero, x%. Dobbiamo solo stamparlo come esadecimale. Quindi un puntatore non viene memorizzato come esadecimale, e un intero non viene memorizzato come decimale. Il tutto viene memorizzato in formato binario. E 'solo che si tende a mostrare i puntatori come esadecimale perché pensiamo delle cose in questi blocchi di 4 byte, e gli indirizzi di memoria tendono ad essere familiare. Siamo come, se inizia con bf, poi capita di essere in pila. Quindi è solo la nostra interpretazione di puntatori come esadecimale. Va bene. Le ultime domande? Sarò qui per un po 'dopo se si dispone di qualsiasi altra cosa. E questa è la fine di tutto questo. [Studente] Yay! [Applausi] [CS50.TV]