paulo1205
(usa Ubuntu)
Enviado em 19/10/2014 - 04:40h
c1c3ru escreveu:
ok! primeiramente gostaria de agradecer a todos que responderam ao tópico.
então deixa eu ver se entendi o que você está me explicando...
alocar de uma só vez eu faria assim:
m = |a,b,c,d|
|e,f,g,h|
|i,j,l,m|
Não. Supondo uma matriz 3×4, com elementos na forma
a b c d
e f g h
i j k l
, o primeiro modo de fazer a que me referi criaria um
vetor de 12 (3×4) posições, com elementos na forma de vetor mesmo, i.e.
a b c d e f g h i j k l
. Para obter o elemento na linha
l e coluna
c , você teria de calcular por conta própria, sem qualquer ajuda do compilador, a posição no vetor correspondente ao tal elemento. A fórmula para esse cálculo é
l*M+c (onde
M é o número de colunas da matriz).
vetor de linha:
[coluna]
[linha] [1][2][3][4] e esses mesmos índices seriam as minhas colunas,ok blz.
o trecho de ponteiro também que, o que ele ta fazendo é dizer que o endereço da matriz de ponteiro int**m;
vai recebe um vetor do tipo inteiro e de tamanho dinâmico(passado pelo usuário),int*[linha],correto?
Não entendi o que você quis dizer acima. Pode ser um pouco mais claro?
agora não entendo que fez isso com linhas,mas colunas ele criou um loop.Pergunto não poderia ter feito a mesma coisa com as colunas,sem a necessidade do loop "for"(essa é minha real duvida)?
Vamos esquecer a matriz por enquanto, e pensar primeiro na alocação dinâmica de vetor. Mas, antes disso, vamos olhar um pouquinho para os ponteiros isoladamente.
Observe a seguinte declaração.
int *pi;
Seguindo a antiga regra de “ler declarações em C de trás para frente”, geralmente se diz o seguinte: “
pi é um ponteiro (‘*’) para inteiro (‘int’)”.
Entretanto, quando aparece algo como “*pi” numa expressão, e não numa declaração, geralmente se lê, da esquerda para a direita mesmo, algo como “conteúdo de
pi ” ou “conteúdo da memória apontada por (ou referida por)
pi ”.
Às vezes eu penso que ajuda a entender melhor o papel de um ponteiro se lermos sempre da esquerda para a direita. Assim como uma declaração de variável não-ponteiro como
int i;
é lida “
i é (um valor) inteiro”, também algo como
int *pi;
poderia ser lido como “o conteúdo de
pi é (um valor) inteiro”. De fato, assim como eu posso atribuir a ou ler de
i qualquer valor inteiro, eu posso fazer exatamente o mesmo com
*pi -- com a única ressalva de que o valor de
pi (usado sem o operador “
* ”) seja o de um endereço de memória válido para o programa.
A ressalva acima é importante: não se pode mesmo esperar que o programa consiga ler ou escrever valores num endereço de memória que não lhe pertence, certo? Por isso, existem algumas maneiras pelas quais se pode fazer com que um ponteiro qualquer aponte para memória válida. São elas:
a) fazer com que o ponteiro aponte para uma o endereço correspondente a uma variável já existente, como no exemplo abaixo (preste atenção aos comentários);
int i; // i é uma variável inteira.
int ai[10]; // ai é um vetor de 10 elementos inteiros.
int *pi, *pmi; // pi e pmi são ponteiros para inteiros, ou
// “o conteúdo de pi” e “o conteúdo de pmi”
// são inteiros.
pi=&i; // lê-se “pi é igual ao endereço de i”.
*pi=5; // lê-se “o conteúdo de pi é igual a 5”.
/* Agora i também é igual a 5. */
ai[0]=1; // lê-se “o elemento com índice zero do array
// (ou vetor) ai é igual a 1”.
pmi=ai; // lê-se “pmi é igual a ai” (oh! ;)); lembre-se
// que quando o nome de um array do C ou C++ é
// usado sem o operador [] e um índice, essa ex-
// pressão tem o valor do endereço de seu primeiro
// elemento, logo a atribuição é perfeitamente
// válida, pois pmi espera ter o endereço
// (ponteiro) de um inteiro, e o endereço do
// primeiro elemento de ai é justamente o endereço
// de um inteiro.
*pmi+=5; // lê-se “o conteúdo de pmi é acrescido de 5”.
/* Agora ai[0] vale 6. */
b) pedir ao ambiente de execução a alocação de mais memória, e receber o valor do primeiro endereço da região alocada num ponteiro.
int *pi, *pmi;
pi=new int; // Cria espaço para um único valor inteiro
// e faz com que pi aponte para ele.
pmi=new int[10]; // Cria espaço para múltiplos (10, no caso)
// valores inteiros contíguos e faz com que
// pmi aponte para o primeiro deles.
/*
Note que as alocações acima apenas reservam espaços em
memória e colocam os endereços desses espaços nos respec-
tivos ponteiros. O conteúdo dessa memória, no entanto,
é inicialmente desconhecido (exceto no caso da alocação
de classes ou estruturas que possuam construtores).
*/
/*
Quando os conteúdos referidos por pi e pmi não forem
mais necessários, a memória alocada para eles deve ser
liberada.
*/
delete[] pmi; // delete[] libera blocos de valores.
delete pi; // delete puro libera espaço de um valor só.
Nos exemplos acima, tanto em (a) quanto em (b), você viu aparecer o operador de acesso com deslocamento (ou de indexação, ou de índice)
[] . Certamente você está acostumado a vê-lo associado a
arrays (ou vetores), mas a verdade é que ele é aplicável a qualquer relacionamento entre um endereço e um valor inteiro, denotando acesso ao elemento contido no endereço contido nesse deslocamento em relação ao endereço-base.
Como ponteiros são endereços, decorre que o operador
[] pode ser aplicado a ponteiros. De fato, existe uma relação de equivalência entre o operador
* (“conteúdo de”) e o operador de indexação
[] , que é a seguinte.
*pi==pi[0]
Analogamente, segue que:
pi==&pi[0]
(Os operadores
& (“endereço de”) e
* (“conteúdo de”) são como que inversos um do outro. A identidade acima é decorrência da aplicação do operador
& aos dois lados da identidade anterior, i.e. “
&*pi==&pi[0] ”, sendo que o pedaço “
&* ” pode ser cancelado dos pontos de vista matemático, lógico, semântico e sintático.)
Mais genericamente, pode-se dizer o seguinte.
*(pi+n)==pi[n] /* n é inteiro */
pi+n==&pi[n]
Tanto no acesso com deslocamento (operador
[] ) quanto no deslocamento puro (
endereço+inteiro ), o compilador leva em consideração o tipo de dado contido no ponteiro ou elemento do
array . Assim sendo, se, hipoteticamente falando, o endereço-base contido em
pi é o da posição 1500 da memória e o tipo de dados é um inteiro que ocupa quatro
bytes ,
pi+1 corresponderá ao endereço 1504, não 1501;
pi+2 será o endereço 1508, e assim por diante. Por outro lado, se o tipo de dado apontado pelo ponteiro ou contido no elemento do
array tiver tamanho um (tipo de dados
char , por exemplo), os deslocamentos serão de um em um (por exemplo: se
ac é um
array de
char disposto a partir da posição de memória 2000,
ac+1 (ou
&ac[1] ) será 2001,
ac+2 será 2002, e assim por diante).
Nos exemplos dados lá no começo (tanto no caso (a) quanto no caso (b)), eu mostro exemplos de ponteiros que apontam para um único dado (
pi ) ou para o primeiro de vários valores inteiros contíguos (
pmi ). Você, porém, deve ter notado que o tipo de dados dos dois ponteiros era exatamente o mesmo:
int * (ponteiro para inteiro). Teoricamente, você só deve aplicar deslocamentos no segundo caso (
pmi ), como sugerido pelos próprios comandos de alocação e desalocação (caso (b)), mas o compilador não vai se queixar se você aplicar deslocamentos também sobre o que alocou espaço apenas para um (
pi ). Cabe a você ser prudente. O compilador pega alguns erros crassos, mas ele em geral confia que você sabe o que está fazendo.
(Até agora, não sei se estou ajudando ou confundindo mais ainda. Mas continuo.)
C e C++ admitem ponteiros para qualquer tipo de dados -- existe até mesmo um “ponteiro para
void ” (que, em princípio, seria meio estranho, mas que tem o sentido de permitir guardar um endereço de qualquer tipo de valor ou objeto, sem se preocupar, durante a compilação, com que tipo de dado existe realmente ali -- de novo, é o compilador confiando em você). Em particular, é possível criar ponteiros de ponteiros, ponteiros de ponteiros de ponteiros, e assim sucessivamente.
Uma declaração como
int **ppi
pode ser lida de três maneiras, a saber:
*
ppi é um ponteiro para ponteiro para inteiro;
* o conteúdo do conteúdo de
ppi é um inteiro;
* o conteúdo de
ppi é um ponteiro para inteiro.
A primeira forma é a leitura clássica da direita para a esquerda. A segunda, a aplicação direta do que eu disse anteriormente sobre ler o operador
* como “conteúdo de” também no momento da declaração. Mas a terceira é um híbrido das duas, que penso que facilita ver o porquê da alocação dinâmica de matriz em duas fases (sendo a segunda fase em
loop ).
Aliás, acho que também ajudaria se eu mudasse o nome da variável e dissesse o seguinte.
int **pmpmi; // "pmpmi" é um ponteiro para
// múltiplos ponteiros para
// múltiplos inteiros.
Lembra do que eu disse lá em cima sobre a alocação não definir valores iniciais para o dado ou bloco de dados alocado? Isso também é útil agora.
Pensando de fora para dentro, no momento em que você aloca o ponteiro para múltiplos ponteiros de inteiro, você aloca um bloco onde cada elemento é um ponteiro para inteiro. O conteúdo desses elementos, como você deve lembrar é desconhecido. Cada elemento desses é um ponteiro para múltiplos inteiros, então você precisa fazer com que eles realmente apontem para esses múltiplos inteiros, e faz isso através da alocação sucessiva de cada um deles.
O código em C++ que faz isso é, obviamente, o já conhecido:
int **pmpmi;
pmpmi=new int *[n_linhas]; // entrega o endereço (do
// primeiro elemento) de um
// bloco com n_linhas elementos
// do tipo “int *” (note que
// “endereço de int *” é equiva-
// lente a “ponteiro para int *”,
// logo a atribuição é compatí-
// vel com o tipo de pmpmi).
for(int i=0; i<n_linhas; i++)
// Aqui é útil lembrar da analogia entre [] e *,
// bem como pensar nos termos híbridos de “conteúdo
// de pmpmi é ponteiro para (múltiplos) inteiro(s)”.
pmpmi=new int[n_colunas]; // entrega sucessivos
// endereços (dos primeiros
// elementos) de blocos com
// n_colunas elementos do
// tipo “int” (note que
// “endereço de int” é equi-
// valente a “ponteiro para
// int”, logo a atribuição é
// compatível com o tipo de
// *pmpmi e pmpmi[i].
---
Eu não consigo imaginar outra forma de fazer. Você perguntou sobre fazer sem [i]loop. Imaginou o quê? Algo como o que segue?
int **matriz;
matriz=new int[n_linhas][N_COLUNAS];
Se foi, isso não funcionaria porque os tipos de dados são incompatíveis: a alocação entrega um bloco com elementos do tipo “
array com
N_COLUNAS elementos inteiros”. Para fazer funcionar sem mexer na expressão de alocação, você teria de trocar a declaração de
matriz para o seguinte.
int (*)matriz[N_COLUNAS];
Mas isso mudaria radicalmente o sentido do programa. A quantidade de colunas teria de ser constante ao longo de todo o programa, e você alocaria dinamicamente apenas a quantidade de linhas, cada uma delas com tamanho fixo.
------
e alguém conseguiu fazer rodar esse código?.
Não tentei. A explicação que você pediu não dependia do restante do código, e eu realmente não me preocupei com ele.