[Powered by Google Translate] [Seção 4 - Mais Confortável]
[Rob Bowden - Harvard University]
[Esta é CS50. - CS50.TV]
Temos um teste amanhã, caso vocês não sabiam disso.
É basicamente sobre tudo o que você poderia ter visto em aula ou deveria ter visto em sala de aula.
Isso inclui ponteiros, apesar de serem um tema muito recente.
Você deve pelo menos entender os altos níveis de los.
Qualquer coisa que se foi em classe, você deve entender para o quiz.
Então se você tem perguntas sobre eles, você pode perguntar-lhes agora.
Mas esta vai ser uma sessão muito liderado por estudantes, onde vocês perguntas,
por isso espero que as pessoas têm perguntas.
Alguém tem dúvidas?
Sim. >> [Aluno] pode ir mais de ponteiros de novo?
Eu vou passar por cima de ponteiros.
Todas as suas variáveis, necessariamente, viver na memória,
mas geralmente você não se preocupe com isso e você acabou de dizer x + 2 e + 3 y
eo compilador irá descobrir onde as coisas estão a viver para você.
Uma vez que você está lidando com ponteiros, agora você está explicitamente usando os endereços de memória.
Assim, uma única variável só vai viver em um único endereço a qualquer momento.
Se queremos declarar um ponteiro, que é o tipo vai parecer?
>> Quero declarar um ponteiro p. O que faz o tipo de aparência?
[Estudante] int * p. Sim >>. Assim, int * p.
E como faço para torná-lo apontar para x? >> [Aluno] Ampersand.
[Bowden] Então comercial é chamado literalmente o endereço do operador.
Portanto, quando digo & x está ficando o endereço de memória da variável x.
Então agora eu tenho o ponteiro p, e em qualquer lugar no meu código eu posso usar p *
ou eu poderia usar x e vai ser exatamente a mesma coisa.
(* P). O que se está fazendo? O que significa que a estrela significa?
[Estudante] Significa um valor nesse ponto. Sim >>.
Então, se olharmos para ele, pode ser muito útil para tirar os diagramas
onde esta é uma pequena caixa de memória para x, que acontece de ter o valor 4,
então temos uma pequena caixa de memória para p,
e assim p aponta para x, para que desenhe uma seta de p para x.
Assim, quando dizemos p * estamos dizendo ir para a caixa que é p.
Star é seguir a seta e, em seguida, fazer o que quiser com essa caixa aí.
Então eu posso dizer * p = 7, e que irá para o caixa que é x e mudança que a 7.
Ou eu poderia dizer int z = * p * 2; Isso é confuso porque é estrela estrela.
A estrela é uma dereferencing p, a outra estrela está se multiplicando por 2.
Repare que eu poderia ter tão bem substituiu o p * com x.
Você pode usá-los da mesma forma.
E mais tarde eu posso ter ponto p para uma coisa completamente nova.
Só posso dizer p = &z;
Então, agora p sem pontos mais longos para x, que aponta para z.
E a qualquer momento que eu faço p * é o mesmo que fazer z.
Então a única coisa útil sobre isso é quando começamos a entrar em funções.
>> É uma espécie de inútil para declarar um ponteiro que aponta para algo
e então você está apenas dereferencing-lo
quando você poderia ter usado a variável original para começar.
Mas quando você entra em funções - então vamos dizer que temos alguma função, int foo,
que leva um ponteiro e só faz * p = 6;
Como vimos antes, com swap, você não pode fazer um swap de efetivo e uma função separada
por apenas passando inteiros porque tudo na C está sempre passando por valor.
Mesmo quando você está passando ponteiros você está passando por valor.
O que acontece é que esses valores são endereços de memória.
Portanto, quando digo foo (p), eu estou passando o ponteiro para a função foo
e, em seguida, faz foo * p = 6;
Assim, dentro dessa função, * p é ainda equivalente a x,
mas eu não posso usar x dentro dessa função, porque não é escopo dentro dessa função.
Então * p = 6 é a única maneira que eu posso acessar uma variável local de outra função.
Ou, bem, os ponteiros são a única maneira que eu posso acessar uma variável local de outra função.
[Aluno] Digamos que você queria voltar para um ponteiro. Como exatamente você faz isso?
[Bowden] Retornar um ponteiro como em algo como int y = 3; retorno & y? >> [Estudante] Yeah.
[Bowden] Okay. Você nunca deve fazer isso. Isso é ruim.
Eu acho que vi nestes slides das aulas que começaram a ver neste diagrama toda a memória
onde até aqui você tem endereço de memória 0
e aqui você tem de endereço de memória 4 GB ou 2 elevado à 32.
Então você tem algumas coisas e algumas coisas e então você tem a sua pilha
e você tem a sua pilha, que você só começou a aprender sobre, crescendo.
[Aluno] não é a pilha acima da pilha?
>> Sim. A pilha é em cima, não é? >> [Aluno] Bem, ele colocou 0 em cima.
[Estudante] Oh, ele colocou 0 em cima. >> [Aluno] Ah, ok.
Disclaimer: Qualquer lugar com CS50 você vai vê-lo desta maneira. >> [Aluno] Okay.
É que quando você está vendo primeiro pilhas,
como quando você pensar em uma pilha você acha de empilhar as coisas em cima da outra.
Então, temos a tendência de virar esta em torno de modo a pilha está crescendo como uma pilha normalmente
em vez da pilha pendurada para baixo. >> [Aluno] não montes tecnicamente crescer muito, embora?
Depende do que você entende por crescer.
A pilha e heap crescer sempre em direções opostas.
Uma pilha é sempre crescendo no sentido de que ele está crescendo
para endereços de memória mais altas, ea pilha está crescendo para baixo
em que é crescente para endereços menores de memória.
Assim, o topo é 0 e no fundo é endereços de memória de alta.
Ambos estão crescendo, apenas em direções opostas.
[Estudante] Eu só quis dizer que porque você disse que colocar pilha na parte inferior
porque parece mais intuitivo porque, para a pilha para começar no topo de uma pilha,
pilha é em cima de si também, então isso é - >> Yeah.
Você também acha da pilha como o crescimento e maior, mas a pilha mais.
Assim, a pilha é o que nós meio que queremos mostrar crescendo.
Mas em todo lugar que você olha de outra forma vai mostrar o endereço 0 no topo
eo endereço de memória mais alto na parte inferior, por isso esta é a sua visão usual de memória.
>> Você tem uma pergunta?
[Aluno] Você pode nos dizer mais sobre o monte?
Sim. Eu vou chegar a isso em um segundo.
Primeiro, por que voltar para devolver & y é uma coisa ruim,
na pilha você tem um monte de quadros de pilha que representam todas as funções
que foram chamados.
Então, ignorando as coisas anteriores, o topo da sua pilha é sempre vai ser a função principal
já que é a primeira função que está sendo chamado.
E então quando você chamar uma outra função, a pilha vai crescer para baixo.
Então, se eu chamar alguma função, foo, e torna-se sua estrutura própria pilha,
ele pode chamar alguma função bar,, que recebe o seu quadro de pilha própria.
E bar pode ser recursiva e que poderia chamar-se,
e para que segunda chamada para a barra vai ficar sua estrutura própria pilha.
E assim o que se passa nestes quadros de pilha são todas as variáveis ​​locais
e todos os argumentos da função que -
Todas as coisas que estão no local delimitado para esta função vai nesses quadros de pilha.
Então isso significa que quando eu disse algo como bar é uma função,
Eu só vou declarar um inteiro e, em seguida, retornar um ponteiro para esse inteiro.
Então onde é que y viver?
[Estudante] y vive no bar. >> [Bowden] Yeah.
Em algum lugar neste pequeno quadrado de memória é uma praça que tem littler y nele.
Quando eu voltar & y, estou retornando um ponteiro para esse pequeno bloco de memória.
Mas então, quando a função retorna, o seu quadro de pilha é pego na pilha.
E é por isso que ele é chamado de pilha.
É como a estrutura de dados de pilha, se você sabe o que é.
Ou até mesmo como uma pilha de bandejas é sempre o exemplo,
principal está a ir na parte inferior, em seguida, a função de primeiro ligar está a ir em cima do que,
e você não pode voltar ao principal até retornar de todas as funções que foram chamados
que foram colocados na parte superior do mesmo.
>> [Aluno] Então, se você realmente fez retornar a Y &, esse valor está sujeito a alteração sem aviso prévio.
Sim, é - >> [aluno] Poderia ser substituído. Sim >>.
É completamente - Se você tentar -
Esta seria também uma barra * int porque ele está retornando um ponteiro,
assim, seu tipo de retorno é int *.
Se você tentar usar o valor de retorno dessa função, é um comportamento indefinido
porque esse ponteiro aponta para a memória ruim. >> [Aluno] Okay.
Então, o que se, por exemplo, você declarou int * y = malloc (sizeof (int))?
Assim é melhor. Sim.
[Aluno] Nós conversamos sobre como quando arrastar as coisas para a nossa lixeira
eles não estão realmente apagado, nós só perdem seus ponteiros.
Portanto, neste caso nós realmente apagar o valor ou é ainda existe na memória?
Para a maior parte, ele vai estar lá.
Mas vamos dizer que acontecer para chamar alguma outra função, Baz.
Baz vai ficar sua estrutura própria pilha aqui.
Vai ser a substituição de todas essas coisas,
e então se você tentar mais tarde e usar o ponteiro que você tem antes,
ele não vai ser o mesmo valor.
Vai ter mudado só porque você chamou o baz função.
[Aluno] Mas se não tivéssemos, estaríamos ainda obter 3?
[Bowden] Em toda a probabilidade, você o faria.
Mas você não pode contar com isso. C apenas diz comportamento indefinido.
>> [Estudante] Oh, é verdade. Okay.
Então, quando você quiser retornar um ponteiro, este é o lugar onde malloc vem em uso.
Eu estou escrevendo na verdade, apenas retornar malloc (3 * sizeof (int)).
Nós falaremos sobre malloc mais em um segundo, mas a idéia de malloc é tudo de suas variáveis ​​locais
sempre vão para a pilha.
Qualquer coisa que é malloced vai na pilha, e será para sempre e sempre na pilha
até que você explicitamente libertá-lo.
Então isso significa que quando você malloc alguma coisa, ele vai sobreviver após o retorno da função.
[Aluno] Será que vai sobreviver depois que o programa pára de correr? Não. >>
Ok, então ele vai estar lá até que o programa é todo o caminho feito em execução. Sim >>.
Podemos ir mais detalhes do que acontece quando o programa pára de funcionar.
Você pode precisar para me lembrar, mas que é uma coisa separada inteiramente.
[Estudante] Então malloc cria um ponteiro? Sim >>.
Malloc - >> [estudante] Eu acho que malloc designa um bloco de memória que um ponteiro pode usar.
[Bowden] Eu quero que diagrama novamente. >> [Aluno] Então, essa função funciona, embora?
[Aluno] Sim, malloc designa um bloco de memória que você pode usar,
e, em seguida, retorna o endereço do primeiro bloco de memória que.
>> [Bowden] Yeah. Então, quando malloc você, você está pegando um bloco de memória
que está atualmente no heap.
Se a pilha é muito pequeno, então a pilha só vai crescer, e cresce nessa direção.
Então, digamos que a pilha é muito pequeno.
Então, ele está prestes a crescer um pouco e retornar um ponteiro para este bloco que só cresceu.
Quando o material livre, você está fazendo mais espaço no heap,
Então uma chamada posterior para malloc pode reutilizar que a memória que você já tinha libertado.
A coisa importante sobre malloc e free é que lhe dá o controle completo
sobre a vida destes blocos de memória.
As variáveis ​​globais são sempre vivo.
As variáveis ​​locais estão vivos dentro de seu alcance.
Assim que você passar uma chaveta, as variáveis ​​locais estão mortos.
Memória Malloced está vivo quando você quiser estar vivo
e depois é liberado quando você diga a ele para ser lançado.
Esses são realmente os únicos três tipos de memória, realmente.
Há gerenciamento automático de memória, que é a pilha.
As coisas acontecem automaticamente para você.
Quando você diz int x, memória é alocada para int x.
Quando x sai do escopo, a memória é recuperado para x.
Depois, há o gerenciamento de memória dinâmica, que é o que malloc é,
que é quando você tem controle.
Você decidir dinamicamente quando a memória deve e não deve ser alocado.
E depois há o estático, que apenas significa que ele vive para sempre,
que é o que as variáveis ​​globais são.
Eles são apenas sempre na memória.
>> Perguntas?
[Estudante] Você pode definir um bloco de apenas usando chaves
mas não ter que ter um? se declaração ou uma declaração de tempo ou algo assim
Você pode definir um bloco como em uma função, mas que tem chaves também.
[Aluno] Então, você não pode apenas ter como um par aleatório de chaves no seu código
que têm variáveis ​​locais? >> Sim, você pode.
Dentro do bar int poderíamos ter {int y = 3;}.
Isso deveria estar aqui.
Mas que define completamente o escopo de int y.
Depois que chaveta segundo, y não pode ser mais usado.
Você quase nunca fazem isso, no entanto.
Voltando ao que acontece quando um programa termina,
há uma espécie de mentira equívoco / meia que damos a fim de apenas fazer as coisas mais fáceis.
Nós dizemos a vocês que quando você alocar memória
você está alocando algum pedaço de memória RAM para essa variável.
Mas você não está realmente tocar directamente RAM sempre em seus programas.
Se você pensar sobre isso, como eu desenhei -
E, na verdade, se você passar no GDB você vai ver a mesma coisa.
Independentemente do número de vezes que você executar o programa ou o programa que está em execução,
a pilha sempre vai começar -
você sempre vai ver variáveis ​​em torno de algo oxbffff endereço.
É geralmente em algum lugar na região.
Mas como pode, eventualmente, ter dois programas de ponteiros para a memória mesmo?
[Aluno] Há alguma designação arbitrária de onde oxbfff é suposto ser na RAM
que pode realmente estar em lugares diferentes, dependendo de quando a função foi chamada.
Sim. O termo é a memória virtual.
A idéia é que cada processo único, cada programa que está em execução no computador
tem o seu próprio - vamos supor que 32 bits - espaço de endereçamento completamente independente.
Este é o espaço de endereço.
Ele tem suas próprias completamente independentes 4 gigabytes de usar.
>> Então, se você executar dois programas simultaneamente, este programa vê quatro gigabytes para si,
este programa vê quatro gigabytes para si,
e é impossível para este programa dereference um ponteiro
e acabar com a memória a partir deste programa.
E o que a memória virtual é um mapeamento de um espaço de endereço de processos
a coisas reais na RAM.
Portanto, cabe ao seu sistema operacional para saber que,
hey, quando esse cara oxbfff ponteiro dereferences, que realmente significa
que ele quer RAM byte 1000,
enquanto que se este programa oxbfff dereferences, ele realmente quer RAM byte 10000.
Eles podem ser arbitrariamente afastadas.
Isto é mesmo verdade das coisas dentro de um espaço único de processos de endereço.
Então, como se vê todos os 4 gigabytes para si mesmo, mas vamos dizer -
[Aluno] Será que cada processo -
Vamos dizer que você tem um computador com apenas 4 gigabytes de memória RAM.
A cada processo ver toda a 4 gigabytes? Sim >>.
Mas os 4 gigabytes que vê é uma mentira.
É só ele acha que tem toda a memória isso porque não conhece nenhum outro processo existe.
É só usar a memória, tanto quanto ele realmente precisa.
O sistema operacional não vai dar RAM para este processo
se não for usar qualquer memória em toda essa região.
Não vai dar-lhe a memória para essa região.
Mas a idéia é que - eu estou tentando pensar - Eu não consigo pensar em uma analogia.
Analogias são difíceis.
Uma das questões de memória virtual ou uma das coisas que ele está resolvendo
é a de que os processos devem ser completamente alheios um ao outro.
E assim você pode escrever qualquer programa que apenas dereferences qualquer ponteiro,
como apenas escrever um programa que diz * (ox1234),
e isso é o endereço de memória dereferencing 1234.
>> Mas cabe ao sistema operacional para depois traduzir o que significa 1234.
Assim, se 1234 acontece de ser um endereço de memória válido para este processo,
como é na pilha ou algo assim, então isso vai devolver o valor do endereço de memória
na medida em que o processo sabe.
Mas se 1234 não é um endereço válido, como acontece à terra
em algum pedaço de memória aqui que está além da pilha e além da pilha
e você realmente não tenho usado que, em seguida, que é quando você fazer as coisas como segfaults
porque você está tocando memória que você não deve tocar.
Isso também é verdadeiro -
Um sistema de 32 bits, 32 bits significa que você tem 32 bits para definir um endereço de memória.
É por isso que os ponteiros são 8 bytes, porque 32 bits são 8 bytes - ou 4 bytes.
Os ponteiros são 4 bytes.
Então, quando você vê um ponteiro como oxbfffff, que é -
Dentro de um determinado programa você pode apenas construir qualquer ponteiro arbitrária,
em qualquer lugar de ox0 para boi 8 f's - ffffffff.
[Aluno] Você não disse que eles são 4 bytes? Sim >>.
[Aluno] Em seguida, cada byte terá - >> [Bowden] Hexadecimal.
Hexadecimal - 5, 6, 7, 8. Então ponteiros você vai sempre ver em hexadecimal.
É exatamente como nós classificamos ponteiros.
A cada 2 dígitos do hexadecimal é um byte.
Então lá vai ser de 8 dígitos hexadecimais de 4 bytes.
Assim, cada ponteiro único em um sistema de 32 bits vai ser 4 bytes,
o que significa que, em seu processo, você pode construir qualquer arbitrárias 4 bytes
e fazer um ponteiro de fora,
o que significa que, tanto quanto é consciente, pode dirigir um inteiro 2 para os 32 bytes de memória.
Mesmo que ele realmente não tem acesso a isso,
mesmo se o seu computador tem apenas 512 megabytes, ele acha que tem essa quantidade de memória.
E o sistema operacional é inteligente o suficiente para que ele só irá alocar o que você realmente precisa.
Não basta ir, oh, um novo processo: quatro shows.
>> Sim. >> [Aluno] O que é que o boi significa? Por que você escreve?
É apenas o símbolo para hexadecimal.
Quando você vê um número com início boi, as coisas são sucessivas hexadecimal.
[Aluno] Você estava explicando sobre o que acontece quando um programa termina. Sim >>.
O que acontece quando um programa termina é o sistema operacional
apenas apaga os mapeamentos que tem para esses endereços, e é isso.
O sistema operacional pode agora dar apenas que a memória para outro programa para usar.
[Aluno] Okay.
Então, quando você aloca algo na pilha ou as variáveis ​​de pilha ou global ou qualquer coisa,
todos eles simplesmente desaparecer assim que o programa termina
porque o sistema operacional é agora livre para dar essa memória para qualquer outro processo.
[Aluno] Mesmo que provavelmente há ainda valores escritos em? Sim >>.
Os valores são provavelmente ainda está lá.
É só isso que vai ser difícil chegar a eles.
É muito mais difícil para chegar a eles que é para chegar a um arquivo excluído
porque o tipo de arquivo que foi excluído fica lá por um longo tempo eo disco rígido é muito maior.
Por isso, vai para substituir partes diferentes de memória
antes que aconteça para substituir o pedaço de memória que o arquivo utilizado para estar.
Mas a memória principal, memória RAM, você percorrer muito mais rápido,
por isso vai muito rapidamente ser substituído.
Dúvidas sobre esta ou qualquer outra coisa?
[Aluno] Tenho dúvidas sobre um tópico diferente. Ok >>.
Alguém tem dúvidas sobre isso?
>> Okay. Tópico diferente. >> [Aluno] Okay.
Eu estava passando por alguns dos testes práticos,
e em uma delas ele estava falando sobre o sizeof
eo valor que ele retorna ou diferentes tipos de variáveis. Sim >>.
E disse que tanto int e longo retorno tanto 4, então ambos são 4 bytes.
Existe alguma diferença entre um int e um longo, ou é a mesma coisa?
Sim, há uma diferença.
O padrão C -
Provavelmente, vou estragar.
O padrão C é como o que é C, a documentação oficial do C.
Isto é o que diz.
Assim, o padrão C apenas diz que um char para sempre e ser sempre um byte.
Tudo o que depois - a um curto é sempre apenas definida como sendo maior do que ou igual a um char.
Isso pode ser estritamente maior que, mas não positiva.
Um int é apenas definida como sendo maior do que ou igual a uma curta distância.
E um comprimento é apenas definida como sendo maior do que ou igual a um inteiro.
E um longo tempo é maior do que ou igual a um comprimento.
Então, a única coisa que o padrão C define é a ordenação relativa de tudo.
A quantidade real de memória que as coisas levam-se geralmente até a sua implementação,
Mas é muito bem definida neste momento. >> [Aluno] Okay.
Então shorts são quase sempre vai ser de 2 bytes.
Ints são quase sempre vai ser de 4 bytes.
Longs longos são quase sempre vai ser de 8 bytes.
E deseja, isso depende se você estiver usando um de 32 bits ou um sistema de 64 bits.
Assim, um tempo que vai corresponder ao tipo de sistema.
Se você estiver usando um sistema de 32 bits, como o aparelho, que vai ser de 4 bytes.
Se você estiver usando uma de 64 bits como um monte de computadores mais recentes, que vai ser de 8 bytes.
>> Ints são quase sempre 4 bytes neste momento.
Longs longos são quase sempre de 8 bytes.
No passado, o ints usado para ser apenas 2 bytes.
Mas note que este satisfaz completamente todas essas relações de maior e igual a.
Contanto é perfeitamente permitido ter o mesmo tamanho como um inteiro,
e está também autorizado a ser do mesmo tamanho que um comprimento longo.
E isso só acontece de ser que em 99,999% dos sistemas, que vai ser igual a
ou um int ou um longo tempo. Depende apenas de 32-bit ou 64-bit. >> [Aluno] Okay.
Em carros alegóricos, como é o ponto decimal designada em termos de bits?
Como como binário? Sim >>.
Você não precisa saber que para CS50.
Você não precisa nem saber que em 61.
Você não aprende que realmente em qualquer curso.
É apenas uma representação.
Eu esqueço os loteamentos bit exatas.
A idéia de ponto flutuante é que você atribuir um número específico de bits para representar -
Basicamente, tudo está em notação científica.
Então você atribuir um número específico de bits para representar o número em si, como 1,2345.
Eu nunca pode representar um número com mais dígitos do que 5.
Então também atribuir um número específico de bits de modo que tende a ser semelhante
você só pode ir até um certo número, que esse é o maior expoente você pode ter,
e você só pode ir até um certo expoente,
assim é o menor expoente você pode ter.
>> Eu não me lembro os bits maneira exata são atribuídos a todos esses valores,
mas um certo número de bits são dedicados a 1,2345,
outro determinado número de bits são dedicados para o expoente,
e só é possível para representar um expoente de um determinado tamanho.
[Estudante] e um duplo? É que, como um flutuador extra longa? Sim >>.
É a mesma coisa que um carro alegórico só que agora você está usando 8 bytes em vez de 4 bytes.
Agora você vai ser capaz de usar 9 dígitos ou 10 dígitos,
e isso vai ser capaz de ir até 300 em vez de 100. >> [Aluno] Okay.
E flutua também são 4 bytes. Sim >>.
Bem, mais uma vez, provavelmente depende global sobre a aplicação geral,
mas flutuadores são 4 bytes, duplos são 8.
Duplas são chamados de casal, porque eles são o dobro do tamanho dos carros alegóricos.
[Aluno] Okay. E há dupla dobra? >> Não há.
Eu acho - >> [estudante] Como longs longas? Sim >>. Acho que não. Sim.
[Estudante] Em teste no ano passado, houve uma pergunta sobre a principal função
ter que fazer parte do seu programa.
A resposta foi que ele não tem que ser parte de seu programa.
Em que situação? Isso é o que eu vi.
[Bowden] Parece - >> [aluno] Que situação?
Você tem o problema? >> [Aluno] Sim, eu definitivamente posso puxá-lo para cima.
Ele não tem que ser, tecnicamente, mas basicamente vai ser.
[Estudante] Eu vi um em um ano diferente.
Era como Verdadeiro ou Falso: Um válido - >> Ah, um arquivo c.?
. [Aluno] Qualquer arquivo deve ter c - [ambos falando de uma vez - ininteligível]
Okay. Então, isso é separado.
>> Uma. Arquivo c só precisa conter funções.
Você pode compilar um arquivo em código de máquina, binário, qualquer que seja,
sem que seja executável ainda.
Um executável válido deve ter uma função principal.
Você pode escrever 100 funções em um arquivo, mas não principais
e depois compilar que até binário,
então você escrever outro arquivo que tem apenas principal, mas chama um monte dessas funções
neste arquivo binário por aqui.
E assim, quando você está fazendo o executável, que é o que faz o linker
é ele combina estes dois arquivos binários em um arquivo executável.
Assim, um. Arquivo c não precisa ter uma função principal de todo.
E em grandes bases de código você verá milhares de arquivos. C e 1 arquivo principal.
Mais perguntas?
[Aluno] Havia outra questão.
Ele disse que fazer é um compilador. Verdadeiro ou Falso?
E a resposta era falsa, e eu entendi por que ele não é como Clang.
Mas o que nós chamamos de fazer, se não é?
Fazer é, basicamente, apenas - Eu posso ver exatamente o que ele chama.
Mas isso só executa comandos.
Fazer.
Posso puxar isso. Sim.
Ah, sim. Faça também faz isso.
Este diz que o propósito do utilitário make é determinar automaticamente
que partes de um grande programa precisam ser recompilados
e emitir os comandos para recompilá-los.
Você pode fazer fazer arquivos que são absolutamente enorme.
Faça olha as marcas de tempo de arquivos e, como dissemos antes,
você pode compilar arquivos individuais para baixo, e não é até chegar ao linker
que eles estão juntos em um executável.
Então, se você tem 10 arquivos diferentes e você faz uma alteração em um deles,
então o que fizer vai fazer é apenas recompilar que um arquivo
e, depois, religar tudo juntos.
Mas é muito mais burro do que isso.
É até você para definir completamente que isso é o que deveria estar fazendo.
É por padrão tem a capacidade de reconhecer essas coisas carimbo de tempo,
mas você pode escrever um arquivo de make para fazer qualquer coisa.
Você pode escrever um arquivo para fazer que quando você digita torná-lo apenas cd para outro diretório.
Eu estava ficando frustrado, porque eu tudo rumo dentro da minha Appliance
e depois eu ver o PDF do Mac.
>> Então eu vou para Finder e que eu possa fazer Vai, Conectar ao Servidor,
eo servidor de eu ligar para o meu aparelho e, em seguida, eu abrir o PDF
que é compilado pelo LATEX.
Mas eu estava ficando frustrado, porque cada vez que eu precisava atualizar o PDF,
Eu tinha que copiá-lo para um diretório específico que poderia acessar
e foi ficando chato.
Então, em vez escrevi um arquivo make, que você tem que definir a forma como ele faz as coisas.
Como você faz isso é em PDF LaTeX.
Assim como qualquer outro arquivo de outra marca - ou eu acho que você ainda não viu os arquivos de fazer,
mas temos no aparelho um arquivo de marca global que apenas diz:
se você está compilando um arquivo C, utilize Clang.
E aqui no meu arquivo make que eu faço eu digo,
este arquivo que você vai querer compilar com LaTeX PDF.
E por isso é LaTeX PDF que está fazendo a compilação.
Fazer não é compilar. É só executar esses comandos na seqüência eu especifiquei.
Por isso, corre LaTeX PDF, ele copia para o diretório que eu quero que ele seja copiado para,
que cd para o diretório e faz outras coisas,
mas tudo que faz é reconhecer quando um arquivo é modificado,
e se muda, então ele vai executar os comandos que é suposto para executar
quando as alterações do arquivo. >> [Aluno] Okay.
Eu não sei de onde os arquivos fazem globais são para eu dar uma olhada.
Outras perguntas? Qualquer coisa do passado quizzes? Quaisquer coisas ponteiro?
Há coisas sutis com ponteiros como -
Eu não vou ser capaz de encontrar a uma pergunta sobre ele -
mas como este tipo de coisa.
Certifique-se de entender que quando eu digo int * x * y -
Este não é exatamente nada aqui, eu acho.
Mas, como * x * y, aqueles são 2 variáveis ​​que estão na pilha.
Quando eu digo que x = malloc (sizeof (int)), x ainda é uma variável na pilha,
malloc é uma quadra no heap, e nós estamos tendo ponto x no heap.
>> Então, alguma coisa sobre os pontos de pilha para o heap.
Sempre que você malloc nada, você está inevitavelmente armazená-lo dentro de um ponteiro.
Assim que o ponteiro estiver na pilha, o bloco é malloced na pilha.
Muitas pessoas se confundem e dizem int * x = malloc; x é na pilha.
Não. O que é x aponta para a pilha.
x si é na pilha, a não ser que, por qualquer razão, você x ser uma variável global,
caso em que passa a ser em outra região de memória.
Então, manter o controle, estes diagramas de caixa e flecha são bastante comuns para o quiz.
Ou se não é no teste 0, ele estará no questionário 1.
Você deve saber tudo isso, os passos na compilação
desde que você teve que responder a perguntas sobre aqueles. Sim.
[Aluno] Podemos ir mais dessas etapas - >> Claro.
Antes de passos e compilação temos o pré-processamento,
compilação, montagem e ligação.
Pré-processamento. O que isso faz?
É o passo mais fácil em - bem, não como -
isso não significa que deveria ser óbvio, mas é o passo mais fácil.
Vocês poderiam implementá-lo vocês mesmos. Sim.
[Aluno] Pegue o que você tem em seu inclui como este e ele copia e depois também define.
Ele procura por coisas como # include e # define,
e apenas copia e cola o que aqueles realmente significam.
Então, quando você diz # include cs50.h, o pré-processador está copiando e colando cs50.h
em que a linha.
Quando você diz # define x para ser 4, o pré-processador passa por todo o programa
e substitui todas as ocorrências de x por 4.
Assim, o pré-processador tem um arquivo C válido e gera um arquivo válido C
onde as coisas têm sido copiado e colado.
Então agora compilar. O que isso faz?
[Aluno] Ele vai de C para binário.
>> [Bowden] Não percorrer todo o caminho para binário.
[Estudante] para código de máquina, então? >> Não é código de máquina.
[Aluno] Assembléia? >> Assembleia.
Ele vai para a Assembléia antes que vá todo o caminho para o código C,
ea maioria das línguas fazer algo assim.
Escolha qualquer linguagem de alto nível, e se você estiver indo para compilá-lo,
é provável que compilar em etapas.
Primeiro ele vai compilar o Python para C, então ele vai para compilar C a Assembléia,
e Assembléia vai ficar traduzido para binário.
Então compilação vai trazê-lo de C para Assembly.
A palavra compilando normalmente significa trazê-lo a partir de um nível mais elevado
a um menor nível de linguagem de programação.
Portanto, esta é a única etapa na compilação onde você começa com uma linguagem de alto nível
e acabam em uma linguagem de baixo nível, e é por isso que a etapa é chamado de compilação.
[Aluno] Durante a compilação, vamos dizer que você fez # include cs50.h.
Será que o compilador recompilar o cs50.h, como as funções que estão lá,
e traduzir isso em código Assembléia, bem como,
ou vai copiar e colar algo que tem sido pré-montagem?
cs50.h vai praticamente nunca acabar na Assembleia.
Coisas como protótipos de função e as coisas são apenas para você ter cuidado.
Ele garante que o compilador pode verificar as coisas que você está chamando funções
com os tipos de retorno de direita e os argumentos certos e outras coisas.
>> Então cs50.h serão pré-processados ​​no arquivo, e então quando ele está compilando
é basicamente jogado fora depois que ele garante que tudo está sendo chamado corretamente.
Mas as funções definidas na biblioteca CS50, que são separados cs50.h,
aqueles que não será compilado separadamente.
Que realmente vai vir para baixo na etapa de ligação, assim que nós vamos chegar a isso em um segundo.
Mas, primeiro, o que é montagem?
[Estudante] Assembleia para binário? Sim >>.
Montagem.
Nós não chamá-lo de compilação porque Assembléia é praticamente uma tradução pura de binário.
Há uma lógica muito pouco em ir de Assembléia para binário.
É como olhar para cima na tabela, oh, nós temos essa instrução;
que corresponde ao binário 01110.
E assim os arquivos que a montagem geralmente saídas são. Arquivos o.
E arquivos. O são o que estávamos dizendo antes,
como um arquivo não precisa ter uma função principal.
Qualquer arquivo pode ser compilado para baixo para um arquivo o. Enquanto ele é um arquivo C válido.
Ele pode ser compilado para baixo. O.
Agora, ligando é o que realmente traz um monte de. O arquivos e os leva para um executável.
E então o que faz é que liga você pode pensar a biblioteca como um arquivo CS50 o..
É um arquivo binário já compilado.
E assim, quando você compilar seu arquivo, seu hello.c, que chama GetString,
hello.c é compilado até hello.o,
hello.o está agora em binário.
Ele usa GetString, por isso precisa ir para cs50.o,
eo vinculador smooshes-los juntos e copia GetString para este arquivo
e sai com um executável que tem todas as funções que necessita.
Então cs50.o não é realmente um arquivo de O, mas está perto o suficiente para que não há diferença fundamental.
Então, ligando apenas traz um monte de arquivos juntos
que separadamente conter todas as funções que preciso utilizar
e cria o executável que vai realmente funcionar.
>> E por isso é também o que dizíamos antes
onde você pode ter 1000. arquivos C, compilá-los todos. arquivos Ô,
que provavelmente vai demorar um pouco, então você alterar uma. arquivo c.
Você só precisa recompilar que 1. Arquivo c e depois ligar novamente tudo o mais,
vincular tudo de volta juntos.
[Aluno] Quando estamos ligando escrevemos lcs50?
Sim, assim lcs50. Que os sinais de bandeira para o vinculador que você deve estar ligando nessa biblioteca.
Perguntas?
Temos ido mais binário diferente que 5 segundos em primeira palestra?
Acho que não.
Você deve saber tudo do SO grande que já sabemos,
e você deve ser capaz de, se lhe deu uma função,
você deve ser capaz de dizer que é grande O, aproximadamente. Ou bem grande, ó é áspera.
Então, se você ver aninhados loops looping sobre o mesmo número de coisas,
como int i, i <n; int j, j > [aluno] n ao quadrado. >> Tende a ser n ao quadrado.
Se você triplo aninhados, ele tende a ser n cubos.
Então, que tipo de coisa você deve ser capaz de apontar imediatamente.
Você precisa saber o tipo de inserção e bubble sort e merge sort e de todos aqueles.
É mais fácil entender por que eles são aqueles n ao quadrado e n log n e tudo isso
porque eu acho que houve um quiz em um ano em que, basicamente, deu-lhe
uma implementação do bubble sort, e disse: "O que é o tempo de execução desta função?"
Então, se você reconhecê-lo como uma espécie de bolha, então você pode dizer imediatamente n ao quadrado.
Mas se você olhar para ele, você não precisa mesmo de perceber que é uma espécie de bolha;
você pode apenas dizer que este está fazendo isso e isso. Este é n ao quadrado.
[Aluno] Há exemplos difíceis que você pode vir acima com,
como uma idéia semelhante de descobrir?
>> Eu não acho que iria dar-lhe alguns exemplos difíceis.
O bubble sort é tão resistente quanto íamos nós,
e mesmo que, desde que você entenda que você está interagindo sobre a matriz
para cada elemento da matriz, o que vai ser algo que n ao quadrado.
Há questões gerais, como aqui nós temos - Ah.
Ainda no outro dia, Doug afirmou: "Eu inventei um algoritmo que pode classificar um array
"De n números em O (log n) o tempo!"
Assim como sabemos que isso é impossível?
[Resposta do aluno inaudível] >> Yeah.
No mínimo, você tem que tocar cada elemento na matriz,
por isso é impossível de classificar uma matriz de -
Se tudo estiver em ordem não separado, então você vai estar tocando tudo na matriz,
por isso é impossível fazê-lo em menos de O de n.
[Aluno] Você nos mostrou que o exemplo de ser capaz de fazê-lo em O de n
se você usar um monte de memória. Sim >>.
E isso é - eu esqueço o que isso é - É contando tipo?
Hmm. Que é um algoritmo de ordenação inteiro.
Eu estava olhando para o nome especial para isso que eu não conseguia me lembrar, na semana passada.
Sim. Estes são os tipos de tipos que podem realizar as coisas em grande O de n.
Mas há limitações, como você só pode usar números inteiros até um determinado número.
Além disso, se você está tentando classificar Sim. Aí algo -
Se a sua matriz é 012, -12, 151, 4 milhões,
em seguida, o elemento único vai arruinar completamente a triagem inteiro.
>> Perguntas?
[Aluno] Se você tem uma função recursiva e isso só faz as chamadas recursivas
dentro de uma instrução de retorno, que é rabo recursiva,
e assim que não iria usar mais memória durante o tempo de execução
ou seria, pelo menos, usar a memória comparável como solução iterativa?
[Bowden] Sim.
Provavelmente seria um pouco mais lento, mas não realmente.
Cauda recursiva é muito bom.
Olhando novamente para quadros de pilha, vamos dizer que temos principal
e temos barra int (int x) ou algo assim.
Esta não é uma função recursiva perfeito, mas barra de retorno (x - 1).
Então, obviamente, este é falho. Você precisa casos base e outras coisas.
Mas a idéia aqui é que esta é a cauda recursiva,
o que significa que quando a barra de chamadas principal que vai ter o seu quadro de pilha.
Neste quadro de pilha lá vai ser um pequeno bloco de memória
que corresponde ao seu argumento x.
E assim vamos dizer principal acontece a chamar bar (100);
Então x vai começar como 100.
Se o compilador reconhece que esta é uma função recursiva cauda,
em seguida, quando a barra faz a sua chamada recursiva para barrar,
em vez de fazer um novo quadro de pilha, que é onde a pilha começa a crescer em grande parte,
eventualmente, ele será executado na pilha e então você começa segfaults
porque a memória começa a colidir.
>> Então, em vez de fazer o seu próprio quadro de pilha, pode realizar,
Ei, eu nunca realmente precisa para voltar a este quadro de pilha,
assim, em vez vou substituir esse argumento com 99 e, em seguida, começar a barra de todo.
E então ele vai fazer isso de novo e vai chegar barra de retorno (x - 1),
e em vez de fazer um novo quadro de pilha, ele vai apenas substituir seu argumento atual com 98
e depois saltar de volta para o início do bar.
Essas operações, substituindo esse valor 1 na pilha e saltar de volta para o início,
são bastante eficientes.
Portanto, não é apenas o uso de memória mesmo que uma função própria, que é iterativo
porque você está usando apenas um quadro de pilha, mas você não está sofrendo as desvantagens
de ter que chamar funções.
Funções de chamada pode ser um pouco caro porque tem que fazer toda essa configuração
e desmontagem e todas essas coisas.
Portanto, este recursão de cauda é bom.
[Aluno] Por que não criar novos passos?
Porque ele percebe que não precisa.
A chamada para a barra está apenas retornando a chamada recursiva.
Por isso, não precisa de fazer qualquer coisa com o valor de retorno.
Ele só vai imediatamente devolvê-lo.
Por isso, só vai substituir o seu próprio argumento e começar de novo.
E também, se você não tem a versão cauda recursiva,
então você obter todos estes bares onde, quando esta barra retorna
ele tem que retornar o seu valor para este, em seguida, que a barra retorna imediatamente
e retorna o seu valor para este, em seguida, é só ir para retornar imediatamente
e retornar o seu valor a este.
Então você está salvando esta aparecendo todas essas coisas fora da pilha
uma vez que o valor de retorno é apenas vai ser passado por todo o caminho de volta de qualquer maneira.
Então por que não substituir o nosso argumento com o argumento atualizado e começar de novo?
Se a função não é recursiva cauda, ​​se você faz algo como -
[Aluno] se bar (x + 1). Sim >>.
>> Então, se você colocá-lo em condições, então você está fazendo algo com o valor de retorno.
Ou mesmo se você acabou de fazer return 2 * bar (x - 1).
Então bar agora (x - 1) precisa voltar para que ele para calcular 2 vezes esse valor,
então agora é necessário seu quadro de pilha separada,
e agora, não importa o quão duro você tente, você vai precisar de -
Esta não é a cauda recursiva.
[Aluno] Será que eu tento trazer uma recursão a apontar para uma recursão de cauda -
[Bowden] Em um mundo ideal, mas na CS50 você não precisa fazer isso.
A fim de obter recursão de cauda, ​​geralmente, você configura um argumento adicional
onde bar terá int x em y
e y corresponde à última coisa que você quer voltar.
Então esta você vai estar retornando bar (x - 1), 2 * y.
Então, isso é apenas um alto nível como você transformar as coisas para ser cauda recursiva.
Mas o argumento extra -
E então, no final, quando você chegar ao seu caso base, você acabou de voltar y
porque você foi acumulando o tempo todo o valor de retorno que você deseja.
Você meio que tem feito de forma iterativa, mas através de chamadas recursivas.
Perguntas?
[Estudante] Talvez sobre a aritmética de ponteiro, como quando se usa cordas. >> Claro.
Aritmética de ponteiro.
Ao utilizar cordas é fácil porque as cordas são estrelas char,
chars são para sempre e sempre um único byte,
e assim a aritmética de ponteiro é equivalente a aritmética regular, quando você está lidando com cordas.
Vamos apenas dizer que char * s = "Olá".
Portanto, temos um bloco na memória.
Ele precisa de 6 bytes, porque você sempre precisa do terminador nulo.
E char * s vai para apontar para o início desta matriz.
Assim s aponte lá.
Agora, isso é basicamente como qualquer matriz funciona,
independentemente de ele era um retorno por malloc ou se é a pilha.
Qualquer matriz é, basicamente, um ponteiro para o início da matriz,
e depois de qualquer operação de matriz, qualquer indexação, é só ir em que a matriz de um determinado deslocamento.
>> Portanto, quando digo algo como s [3], o que vai s e contando 3 caracteres dentro
Assim, s [3], temos 0, 1, 2, 3, de modo s [3] vai para se referir a este l.
[Aluno] E nós poderíamos chegar ao mesmo valor, fazendo s + 3 e estrela parênteses?
Sim.
Isto é equivalente a * (s + 3);
e que é para sempre e sempre equivalentes, não importa o que você faz.
Você nunca precisa usar a sintaxe de suporte.
Você sempre pode usar o * (s + 3) sintaxe.
As pessoas tendem a gostar da sintaxe suporte, no entanto.
[Aluno] Então, todas as matrizes são na verdade apenas ponteiros.
Há uma ligeira distinção quando digo int x [4]; >> [aluno] Será que criar a memória?
[Bowden] Isso vai criar 4 ints na pilha, de modo geral 16 bytes.
Vai criar 16 bytes na pilha.
x não é armazenada em qualquer lugar.
É apenas um símbolo referente ao início da coisa.
Porque você declarou a matriz dentro da função,
o que o compilador vai fazer é substituir todas as ocorrências da variável x
com o local onde aconteceu a escolha de colocar estes 16 bytes.
Ele não pode fazer isso com char * s porque s é um ponteiro real.
Ela é livre para, em seguida, apontar para outras coisas.
x é uma constante. Você não pode se apontar para uma matriz diferente. >> [Aluno] Okay.
Mas esta idéia, esta indexação, é o mesmo, independentemente de se tratar de uma matriz tradicional
ou se é um ponteiro para alguma coisa ou se é um ponteiro para uma matriz malloced.
E, na verdade, é assim equivalente que essa é também a mesma coisa.
Ele realmente apenas traduz o que está dentro dos colchetes eo que sobrou dos suportes,
adiciona-los juntos, e dereferences.
Portanto, esta é tão válida quanto * (s + 3) ou s [3].
[Estudante] Você pode ter ponteiros apontam para 2-dimensional arrays?
>> É mais difícil. Tradicionalmente, no.
Uma matriz de 2 dimensões é apenas uma matriz 1-dimensional com alguma sintaxe conveniente
porque quando eu digo int x [3] [3], isso é realmente apenas uma matriz com 9 valores.
E então quando eu índice, o compilador sabe o que quero dizer.
Se eu disser x [1] [2], ele sabe que eu quero ir para a segunda linha, por isso vai pular o primeiro 3,
e então ele quer que a segunda coisa em que, por isso vai receber um presente.
Mas ainda é apenas uma matriz unidimensional.
E assim se eu queria atribuir um ponteiro para essa matriz,
Eu diria int * p = x;
O tipo de x é apenas -
É tipo dizendo aproximada de x, uma vez que é apenas um símbolo e não é uma variável real,
mas é apenas um int *.
x é apenas um ponteiro para o início desta. >> [Aluno] Okay.
E por isso não vou ser capaz de acessar [1] [2].
Eu acho que existe uma sintaxe especial para declarar um ponteiro,
algo ridículo como int (* p [-. algo absolutamente ridículo que eu não sei mesmo.
Mas há uma sintaxe para declarar ponteiros como com parênteses e coisas.
Pode até não deixar você fazer isso.
Eu poderia olhar para trás, algo que me diga a verdade.
Vou procurá-lo mais tarde, se houver uma sintaxe para ponto. Mas você nunca vai vê-lo.
E mesmo a sintaxe é tão arcaico que se você usá-lo, as pessoas vão se perplexo.
Matrizes multidimensionais são muito raros como ela é.
Está muito bonito -
Bem, se você está fazendo as coisas da matriz não vai ser raro,
mas em C você raramente vai usar matrizes multidimensionais.
Sim. >> [Aluno] Digamos que você tem uma matriz muito longo.
>> Assim, na memória virtual parece ser todos consecutivos,
como os elementos ao lado uns dos outros,
mas na memória física, seria possível para que isso se separaram? Sim >>.
Como a memória funciona virtuais é apenas separa -
A unidade de distribuição é uma página, que tende a ser de 4 kilobytes,
e assim, quando um processo diz, hey, eu quero usar essa memória,
o sistema operacional vai atribuir-lhe 4 kilobytes para esse pequeno bloco de memória.
Mesmo se você usar apenas um único byte pouco em todo o bloco de memória,
o sistema operacional vai dar o total de 4 kilobytes.
Então, o que isto significa é que eu poderia ter - digamos que esta é a minha pilha.
Esta pilha podiam ser separados. Minha pilha poderia ser megabytes e megabytes.
Minha pilha pode ser enorme.
Mas a pilha em si tem de ser dividido em páginas individuais,
que, se olharmos para cá, digamos que esta é a nossa memória RAM,
se eu tiver 2 gigabytes de memória RAM, este é o endereço real 0 como o byte 0 da minha memória RAM,
e este é de 2 gigabytes todo o caminho até aqui.
Portanto, esta página pode corresponder a este bloco aqui.
Esta página pode corresponder a este bloco aqui.
Este pode corresponder a este aqui.
Assim, o sistema operacional é livre para atribuir memória física
para qualquer página individual arbitrariamente.
E isso significa que, se isso acontece fronteira para straddle uma matriz,
uma matriz passa a ser deixada deste e direita da ordem de uma página,
em seguida, que a matriz vai ser dividida em memória física.
E então quando você sair do programa, ao final do processo,
esses mapeamentos obter apagados e então é livre para usar estes pequenos blocos para outras coisas.
Mais perguntas?
[Aluno] aritmética O ponteiro. >> Oh yeah.
Cordas foram mais fáceis, mas olhando para algo como ints,
Então, de volta para int x [4];
Se isto é uma matriz ou se é um ponteiro para uma matriz malloced de 4 números inteiros,
ele vai ser tratado da mesma maneira.
[Estudante] Então matrizes são na pilha?
[Bowden] Matrizes não são na pilha. >> [Aluno] Oh.
>> [Bowden] Este tipo de arranjo tende a ser na pilha
a menos que você declarou na - ignorando variáveis ​​globais. Não use variáveis ​​globais.
Dentro de uma função que eu digo int x [4];
Vai criar um bloco de 4 inteiro na pilha para esta matriz.
Mas este malloc (4 * sizeof (int)); está a ir na pilha.
Mas após este ponto eu posso usar x e p em praticamente da mesma forma,
Para além das excepções que eu disse antes sobre você pode reatribuir p.
Tecnicamente, as suas dimensões são um pouco diferentes, mas isso é completamente irrelevante.
Você nunca realmente usar seus tamanhos.
O que eu poderia dizer p p [3] = 2; ou x [3] = 2;
Você pode usá-los em exatamente da mesma maneira.
Assim, a aritmética de ponteiro agora - Sim.
[Aluno] Você não tem que fazer p * se você tem os suportes?
Os suportes são uma dereference implícita. Ok >>.
Na verdade, também o que você está dizendo com o que você pode obter matrizes multidimensionais
com ponteiros, o que você pode fazer é algo como, digamos, int ** pp = malloc (sizeof (int *) * 5);
Eu só vou escrever tudo primeiro.
Eu não queria isso.
Okay.
O que eu fiz aqui é - Isso deve ser pp [i].
Assim, pp é um apontador para um apontador.
Você está mallocing pp, para apontar para uma matriz de 5 estrelas int.
Assim, em memória que você tem sobre a pilha pp
Vai apontar para uma matriz de 5 blocos que são todos próprios ponteiros.
E então quando eu malloc aqui, malloc eu que cada um desses indicadores individuais
deve apontar para um bloco separado de 4 bytes na pilha.
Portanto, este aponta para 4 bytes.
E isso aponta para uma diferente 4 bytes.
>> E todos eles apontam para suas próprias 4 bytes.
Isto dá-me uma maneira de fazer as coisas multidimensionais.
Eu poderia dizer pp [3] [4], mas agora isso não é a mesma coisa como matrizes multidimensionais
porque as matrizes multidimensionais é traduzido [3] [4] em um único deslocamento para a matriz x.
Este p dereferences, acessa o terceiro índice, em seguida, que dereferences
e acessos - 4 seria inválida - o segundo índice.
Considerando que, quando tivemos a int x [3] [4] antes como uma matriz multidimensional
e quando você dá um duplo suporte é realmente apenas dereference uma única
você está seguindo um único ponteiro e, em seguida, um deslocamento,
isso é realmente referências 2D.
Você segue dois ponteiros separados.
Portanto, esta também tecnicamente permite que você tenha matrizes multidimensionais
onde cada arranjo indivíduo é tamanhos diferentes.
Então eu acho irregulares matrizes multidimensionais é o que é chamado
já que realmente a primeira coisa que poderia apontar para algo que tem 10 elementos,
a segunda coisa que poderia apontar para algo que tem 100 elementos.
[Aluno] Existe algum limite para o número de ponteiros que você pode ter
apontando para outros ponteiros? Não. >>
Você pode ter int ***** p.
Voltar para a aritmética de ponteiro - >> [aluno] Oh. Sim >>.
[Aluno] Se eu tiver int p *** e então eu faço um dereferencing e eu digo p * é igual a este valor,
é que só vai fazer um nível de dereferencing? Sim >>.
Então, se eu quiser acessar a coisa que o último ponteiro está apontando para -
Então você faz p ***. Ok >>.
Portanto, este é p aponta para um bloco, aponta para um outro bloco, aponta para um outro bloco.
Então, se você faz * p = outra coisa, então você está mudando esta
agora a apontar para um bloco diferente. Ok >>.
>> [Bowden] E se estes foram malloced, então você já vazou memória
a menos que você acontecer ter referências diferentes destas
desde que você não pode voltar para aquelas que você só jogou fora.
Aritmética de ponteiro.
int x [4]; vai alocar uma matriz de 4 inteiros
onde x vai para apontar para o início da matriz.
Portanto, quando digo algo como x [1], eu quero que ela signifique ir para o segundo inteiro na matriz,
o que seria um presente.
Mas, realmente, que é 4 bytes na matriz desde que este número inteiro ocupa 4 bytes.
Então, um deslocamento de 1 realmente significa um deslocamento de uma
vezes o tamanho de qualquer que seja o tipo de matriz é.
Este é um array de inteiros, para que ele saiba fazer uma vezes o tamanho de int quando quer compensar.
A sintaxe outro. Lembre-se que isso é equivalente a * (x + 1);
Quando eu digo ponteiro + 1, o que que retorna é o endereço que o ponteiro está armazenando
mais 1 vezes o tamanho do tipo de ponteiro.
Então, se x = ox100, então x + 1 = ox104.
E você pode abusar desse e dizer algo como char * c = (char *) x;
e agora c vai ser o mesmo endereço x.
c vai ser igual a ox100,
mas c + 1 vai ser igual a ox101
desde a aritmética de ponteiro depende do tipo do ponteiro que você está adicionando.
Então c + 1, ele olha para c, é um ponteiro char, por isso vai adicionar uma vezes o tamanho do char,
que é sempre vai ser um, para que você obtenha 101,
enquanto que se o fizer x, o qual é também ainda 100, x + 1 vai ser 104.
[Estudante] Você pode usar C + + para avançar o ponteiro por um?
Sim, você pode.
Você não pode fazer isso com x porque x é apenas um símbolo, é uma constante, você não pode mudar x.
>> Mas c acontece de ser apenas um ponteiro, então c + + é perfeitamente válido e será incrementado em 1.
Se c fosse apenas um int *, então c + + seria 104.
+ + Aritmética de ponteiro faz exatamente como c + 1 teria feito aritmética de ponteiro.
Este é, na verdade, como um monte de coisas como merge sort -
Em vez de criar cópias de coisas, você pode passar em vez -
Como se eu queria passar esta metade da matriz - vamos apagar um pouco isso.
Vamos dizer que eu queria passar deste lado da matriz em uma função.
O que eu iria passar para essa função?
Se eu passar x, eu estou passando este endereço.
Mas eu quero passar esse endereço particular. Então o que devo passar?
[Estudante] Ponteiro + 2?
[Bowden] Então x + 2. Sim.
Isso vai ser este endereço.
Você também vai muito freqüentemente vê-lo como x [2] e, em seguida, o endereço do.
Então, você precisa ter o endereço dela, porque o suporte é um dereference implícita.
x [2] refere-se ao valor que está na caixa, e então você quer o endereço dessa caixa,
então você diz & x [2].
Então é assim que algo em merge sort onde você quer passar metade da lista para algo
você realmente só passam & x [2], e agora até a chamada recursiva está em causa,
minha nova matriz começa aí.
Última perguntas minutos.
[Aluno] Se não colocar um comercial ou um - o que é que chama? >> Star?
[Aluno] Star. >> Tecnicamente, operador dereference, mas - >> [aluno] Referência no.
>> Se não colocar uma estrela ou um comercial, o que acontece se eu apenas dizer y = x e x é um ponteiro?
Qual o tipo de y? >> [Aluno] Eu só vou dizer que é ponteiro 2.
Então, se você acabou de dizer y = x, agora x e y ponto para a mesma coisa. >> [Estudante] Ponto para a mesma coisa.
E, se x é um ponteiro int? Seria >> reclamar, porque você não pode atribuir ponteiros.
[Aluno] Okay.
Lembre-se que os ponteiros, apesar de atraí-los como flechas,
realmente loja todos eles - int * x - realmente todo x está armazenando é algo como ox100,
que acontecer para representar como apontando para o bloco armazenado a 100.
Portanto, quando digo int * y = x, eu só estou copiando ox100 em y,
que está indo só para representar como y, apontando também para ox100.
E se eu disser int i = (int) x; então eu vai armazenar qualquer que seja o valor de ox100 é
dentro dele, mas agora ele vai ser interpretado como um inteiro, em vez de um ponteiro.
Mas você precisa do elenco ou então ele vai reclamar.
[Estudante] Então você quer dizer para lançar -
Será que vai estar lançando int de x ou de y int elenco?
[Bowden] O que?
[Aluno] Okay. Após estes parênteses está lá vai ser um x ou ay lá?
>> [Bowden] De qualquer. x e y são equivalentes. >> [Aluno] Okay.
Porque eles são os dois ponteiros. Sim >>.
[Estudante] Então seria armazenar os 100 hexadecimal na forma inteiro? >> [Bowden] Yeah.
Mas não o valor de tudo o que ele aponta.
[Bowden] Yeah. >> [Aluno] Então, apenas o endereço em forma de número inteiro. Okay.
[Bowden] Se você quisesse, por alguma razão bizarra,
você poderia exclusivamente lidar com ponteiros e nunca lidar com números inteiros
e apenas ser como int * x = 0.
Então, você vai ficar muito confuso, uma vez aritmética de ponteiro começa a acontecer.
Assim, os números que eles armazenam são sem sentido.
É exatamente como você acaba interpretá-las.
Então, eu estou livre para copiar ox100 de um * int para um int,
e eu sou livre para atribuir - você é, provavelmente, vai ser repreendidos por não lançar -
Eu sou livre para atribuir algo como (int *) ox1234 neste int * arbitrária.
Então ox123 é tão um endereço válido de memória como é e y.
& Y acontece para retornar algo que é muito bonito ox123.
[Aluno] Teria que ser uma forma muito legal para ir de hexadecimal para a forma decimal,
Como se você tiver um ponteiro e lançá-lo como um int?
[Bowden] Você pode realmente apenas imprimir usando como printf.
Vamos dizer que eu tenho int y = 100.
Então printf% (d \ n - como você já deve saber - que imprimir como um inteiro x,%.
Nós apenas imprimi-lo como hexadecimal.
Assim, um apontador não é armazenado como hexadecimal,
e um número inteiro não é armazenado como decimal.
Tudo é armazenado como binário.
É que nós tendem a mostrar ponteiros como hexadecimal
porque pensamos de coisas nesses blocos de 4 bytes,
e endereços de memória tendem a ser familiar.
Nós somos como, se ele começa com bf, então ele passa a ser na pilha.
Então é só nossa interpretação de ponteiros como hexadecimal.
Okay. Qualquer dúvida últimos?
>> Eu estarei aqui para um pouco depois, se você tem qualquer outra coisa.
E esse é o fim de tudo.
>> [Aluno] Yay! [Aplausos]
>> [CS50.TV]