Introdução

Introdução

Chegamos a um ponto crucial em nossa série de artigos sobre o desenvolvimento C. É também, não por coincidência, que parte de C que dá muitas dores de cabeça para iniciantes. É aqui que entramos, e o objetivo deste artigo (um deles, de qualquer maneira), é desmascarar os mitos sobre ponteiros e sobre C como um idioma difícil/impossível de aprender e ler. No entanto, recomendamos maior atenção e um pouco de paciência e você verá que os ponteiros não são tão impressionantes quanto as lendas dizem.

Definições e avisos

Parece senso natural e comum que devemos começar com os avisos, e recomendamos que você se lembre delas: enquanto os indicadores facilitam sua vida como desenvolvedor de C, eles também pode Introduzir bugs difíceis de encontrar e código incompreensível. Você verá, se você continuar lendo, do que estamos falando e a seriedade desses insetos, mas o resultado final é, como dito antes, tenha cuidado extra.

Uma definição simples de ponteiro seria "uma variável cujo valor é o endereço de outra variável". Você provavelmente sabe que os sistemas operacionais lidam com endereços ao armazenar valores, assim como você rotularia as coisas dentro de um armazém, para ter uma maneira fácil de encontrá -los quando necessário. Por outro lado, uma matriz pode ser definida como uma coleção de itens identificados por índices. Você verá mais tarde por que os ponteiros e matrizes geralmente são apresentados juntos, e como se tornar eficiente em C usando -os. Se você tem um histórico em outros idiomas de nível superior, está familiarizado com o tipo de dados da string. Em C, as matrizes são o equivalente a variáveis ​​tipo strings, e argumenta-se que essa abordagem é mais eficiente.



Ponteiros

Você viu a definição de ponteiro, agora vamos começar com algumas explicações detalhadas e, é claro, exemplos. Uma primeira pergunta que você pode fazer é “por que devo usar ponteiros?”. Embora eu possa ser chamado por essa comparação, vou me arriscar: você usa symblinks em seu sistema Linux? Mesmo que você não tenha criado um pouco, seu sistema os agrava e torna o trabalho mais eficiente. Eu ouvi algumas histórias de horror sobre desenvolvedores de C que jura. Além disso, há situações em que você terá que usar ponteiros, para que eles não sejam tratados como opcionais, porque não estão. Como antes, acredito em aprender pelo exemplo, então aqui vai:

int x, y, z; x = 1; y = 2; int *ptoi; /* PTOI é e significa Ponteiro para Inteiro*/ ptoi = & x; / * PTOI aponta para x */ z = *ptoi; / * z agora é 1, valor de x, para o qual PTOI Points */ ptoi = & y; / *PTOI agora aponta para y */

Se você está coçando a cabeça em confusão, não fuja: só dói a primeira vez, você sabe. Vamos fazer linha por linha e ver o que fizemos aqui. Primeiro declaramos três números inteiros, que são x, y e z, e deu os valores x e y 1 e 2, respectivamente. Esta é a parte simples. O novo elemento vem junto com a declaração da variável ptoi, que é um ponteiro para um inteiro, Então isso pontos em direção a um número inteiro. Isso é realizado usando o asterisco antes do nome da variável e diz ser um operador de redirecionamento. A linha 'ptoi = & x;' significa “PTOI agora aponta para x, o que deve ser um número inteiro, conforme a declaração de PTOI acima”. Agora você pode trabalhar com PTOI como faria com X (bem, quase). Sabendo disso, a próxima linha é o equivalente a 'z = x;'. Em seguida nós desreferência PTOI, o que significa que dizemos “Pare de apontar para X e comece a apontar para Y”. Uma observação importante é necessária aqui: o Operador só pode ser usado em objetos residentes com memória, sendo aqueles variáveis ​​(exceto registro [1]) e elementos de matriz.

[1] As variáveis ​​do tipo registro são um dos elementos de C que existem, mas a maioria dos programadores os evita. Uma variável com essa palavra -chave anexada sugere ao compilador que ela será usada com frequência e deve ser armazenada em um registro de processador para acesso mais rápido. A maioria dos compiladores modernos ignora essa dica e decide por si mesmos de qualquer maneira, então se você não tem certeza de que precisa se registrar, você não.

Dissemos que a PTOI deve apontar para um número inteiro. Como devemos proceder se quiséssemos um ponteiro genérico, para que não tenhamos que nos preocupar com os tipos de dados? Digite o ponteiro para anular. Isso é tudo o que vamos lhe contar, e a primeira tarefa é descobrir quais usos o ponteiro pode ter e quais são as suas limitações.



Matrizes

Você verá neste sub-capítulo por que insistimos em apresentar indicadores e matrizes em um artigo, apesar do risco de sobrecarregar o cérebro do leitor. É bom saber que, ao trabalhar com matrizes, você não precisa usar ponteiros, mas é bom fazê -lo, porque as operações serão mais rápidas, com a desvantagem de código menos compreensível. Uma declaração de matriz tem o resultado da declaração de vários elementos consecutivos disponíveis através de índices, assim:

int a [5]; int x; a [2] = 2; x = a [2];

A é uma matriz de 5 elementos, com o terceiro elemento sendo 2 (a numeração do índice começa com zero!) e x é definido como também 2. Muitos insetos e erros quando lidam com as matrizes é que se esquece do problema de 0 índice. Quando dissemos "elementos consecutivos", queremos dizer que é garantido que os elementos da matriz tenham locais consecutivos na memória, não que se a [2] seja 2, então a [3] é 3. Existe uma estrutura de dados em C chamada um enumeração que faz isso, mas ainda não lidaremos com isso. Encontrei um programa antigo que escrevi enquanto aprendi C, com alguma ajuda do meu amigo Google, que reverte os personagens em uma string. Aqui está:

#include #include int main () char atrevido [30]; int i; char c; printf ("Digite A String .\ n "); fgets (sedento, 30, stdin); printf (" \ n "); para(i = 0; i < strlen(stringy); i++) printf("%c", stringy[i]); printf("\n"); para(i = strlen (curativo); i> = 0; i--) printf ("%c", curativo [i]); printf ("\ n"); retornar 0;  

Esta é uma maneira de fazer isso sem usar ponteiros. Tem falhas em muitos aspectos, mas ilustra a relação entre strings e matrizes. Pedy é uma matriz de 30 caracteres que será usada para manter a entrada do usuário, eu serei o índice de matriz e C será o personagem individual para ser trabalhado. Por isso, pedimos uma string, salvamos na matriz usando fgets, imprime a string original começando a partir de [0] e continuando, usando um loop de forma incremental, até que a string termine. A operação reversa fornece o resultado desejado: nós novamente obtemos o comprimento da string com strlen () e iniciamos uma contagem regressiva 'Til Zero e então imprimimos o caractere da string por caractere. Outro aspecto importante é que qualquer matriz de caracteres em C termina com o caractere nulo, representado graficamente por '\ 0'.

Como faríamos tudo isso usando ponteiros? Não fique tentado a substituir a matriz por um ponteiro para char, que não funcionará. Em vez disso, use a ferramenta certa para o trabalho. Para programas interativos como o acima, use matrizes de caracteres de comprimento fixo, combinados com funções seguras como fgets (), para que você não seja mordido por transbordamentos de buffer. Para constantes de string, porém, você pode usar

char * myname = "David";

E então, usando as funções fornecidas a você em string.H, manipule dados como você achar adequado. Falando nisso, que função você escolheria adicionar myname às cordas que abordam o usuário? Por exemplo, em vez de "digite um número", você deve ter "David, digite um número".



Ponteiros e matrizes

Você pode e é incentivado a usar matrizes em conjunto com ponteiros, embora a princípio você possa se assustar por causa da sintaxe. De um modo geral, você pode fazer qualquer coisa relacionada a matriz com dicas, com a vantagem da velocidade ao seu lado. Você pode pensar que com o hardware de hoje, usando ponteiros com matrizes apenas para ganhar alguma velocidade. No entanto, à medida que seus programas crescem em tamanho e complexidade, a diferença começará a ser mais óbvia e, se você pensar em portar seu aplicativo para alguma plataforma incorporada, você se parabenizará. Na verdade, se você entendeu o que foi dito até este ponto, você não terá motivos para se assustar. Digamos que temos uma variedade de números inteiros e queremos declarar um ponteiro para um dos elementos da matriz. O código seria assim:

int myarray [10]; int *myptr; int x; myptr = & myarray [0]; x = *myptr;

Então, temos uma matriz chamada MyArray, composta por dez números inteiros, um ponteiro para um número inteiro, que recebe o endereço do primeiro elemento da matriz, e X, que obtém o valor desse primeiro elemento através da um ponteiro. Agora você pode fazer todos os tipos de truques bacanas para se movimentar pela matriz, como

*(myptr + 1);

O que apontará para o próximo elemento de Myarray, a saber, MyArray [1].

Uma coisa importante a saber, e ao mesmo tempo que ilustra perfeitamente a relação entre ponteiros e matrizes, é que o valor de um objeto do tipo Array é o endereço do seu primeiro elemento (zero), por isso, se myptr = & MyArray [ 0], então myptr = myarray. Como um exercício, convidamos você a estudar um pouco esse relacionamento e codificar algumas situações em que você acha que será útil/pode ser útil. Isso é o que você encontrará como aritmético ponteiro.

Considerações sobre strings em C e chamadas

Antes de termos visto que você pode fazer

char *mystring; mystring = "Esta é uma string."

ou você pode fazer o mesmo usando

char mystring [] = "Esta é uma string.";

No segundo caso, como você deve ter deduzido, Mystring é uma matriz grande o suficiente para manter os dados atribuídos a ele. A diferença é que, usando matrizes, você pode operar em caracteres individuais dentro da string, enquanto usando a abordagem do ponteiro, você não pode. É uma questão muito importante lembrar que o salvará do compilador tendo homens grandes chegando à sua casa e fazer coisas terríveis para sua avó. Indo um pouco mais longe, outro problema que você deve estar ciente é que, se você esquecer as dicas, as chamadas em C são feitas por valor. Então, quando uma função precisa de algo de uma variável, uma cópia local é feita e o trabalho é feito sobre isso. Mas se a função alterar a variável, as alterações não são refletidas, porque o original permanece intacto. Usando ponteiros, você pode usar o chamado por referência, Como você verá no nosso exemplo abaixo. Além disso, a chamada por valor pode se tornar intensiva em recursos se os objetos que estão sendo trabalhados forem grandes. Tecnicamente, há também uma chamada por ponteiro, mas vamos simplificar por enquanto.

Digamos que queremos escrever uma função que tome um número inteiro como um argumento e o incrementa com algum valor. Você provavelmente ficará tentado a escrever algo assim:

void inc (int a) a+= 20; 

Agora, se você tentar isso, verá que o número inteiro não será incrementado, porque apenas a cópia local será. Se você tivesse escrito

vazio incr (int & a) a+= 20; 

Seu argumento inteiro será incrementado com vinte, que é o que você quer. Então, se você ainda tinha algumas dúvidas sobre a utilidade dos ponteiros, aqui está um exemplo simples, mas significativo.



Tópicos um tanto avançados

Pensamos em colocar esses tópicos em uma seção especial, porque eles são um pouco mais difíceis de entender para iniciantes, mas são úteis, devem saber partes da programação C. Então…

Ponteiros para ponteiros

Sim, as dicas são variáveis ​​como qualquer outra, para que possam ter outras variáveis ​​apontando para eles. Enquanto as dicas simples, como vistas acima têm um nível de "apontando", os ponteiros para os ponteiros têm dois, então uma variável aponta para outra que aponta para outro. Você acha que isso é enlouquecedor? Você pode ter ponteiros para ponteiros para ponteiros para ponteiros para… .ad infinitum, mas você já cruzou o limiar de sanidade e utilidade se tivesse essas declarações. Recomendamos o uso do CDECL, que é um pequeno programa geralmente disponível na maioria das distritos Linux que "traduz" entre C e C ++ e o inglês e o contrário. Então, um ponteiro para um ponteiro pode ser declarado como

int ** ptrtoptr;

Agora, de acordo com a maneira como os ponteiros de nível múltiplo são utilizados, há situações quando você tem funções, como a comparação acima, e você deseja obter um ponteiro deles como valor de retorno. Você também pode querer uma variedade de cordas, que é um recurso muito útil, como você verá em um capricho.

Matrizes multidimensionais

As matrizes que você viu até agora são unidimensionais, mas isso não significa que você está limitado a isso. Por exemplo, uma matriz bidimensional pode ser imaginada em sua mente como sendo uma variedade de matrizes. Meu conselho seria usar matrizes multidimensionais se você sentir a necessidade, mas se você é bom com um simples e bom 'unidimensional, use isso para que sua vida como codificador seja mais simples. Para declarar uma matriz bidimensional (usamos duas dimensões aqui, mas você não está limitado a esse número), você fará

 int bidimarray [4] [2];

que terá o efeito de declarar uma matriz inteira 4 por 2. Para acessar o segundo elemento verticalmente (pense em um quebra -cabeça cruzado se isso ajudar!) e o primeiro horizontalmente, você pode fazer

bidimarray [2] [1];

Lembre -se de que essas dimensões são apenas para nossos olhos: o compilador aloca memória e trabalha com a matriz da mesma maneira; portanto, se você não vir a utilidade disso, não a use. Ergo, nossa matriz acima pode ser declarada como

int bidimarray [8]; / * 4 por 2, como dito */


Argumentos da linha de comando

Em nossa parte anterior da série, falamos sobre Main e como ela pode ser usada com ou sem argumentos. Quando o seu programa precisa e você tem argumentos, eles são Char Argc e Char *argv []. Agora que você sabe o que são matrizes e ponteiros, as coisas começam a fazer muito mais sentido. No entanto, pensamos em entrar em detalhes aqui. char *argv [] pode ser escrito como char ** argv também. Como um alimento para pensar, por que você acha que isso é possível? Lembre -se de que o ARGV significa "Vetor de argumento" e é uma variedade de strings. Sempre você pode confiar no fato de que o argv [0] é o nome do próprio programa, enquanto o argv [1] é o primeiro argumento e assim por diante. Portanto, um programa curto para ver o nome dele e os argumentos se pareceria assim:

#include #include int main (int argc, char ** argv)  enquanto(argc--) printf ("%s \ n", *argv ++); retornar 0; 

Conclusão

Escolhemos as partes que pareciam mais essenciais para a compreensão de ponteiros e matrizes, e intencionalmente deixou de fora alguns assuntos como ponteiros para funções. No entanto, se você trabalhar com as informações apresentadas aqui e resolver os exercícios, você terá um bom começo nessa parte de C, considerada a principal fonte de código complicado e incompreensível.

Aqui está uma excelente referência sobre os ponteiros C ++. Embora não seja C, os idiomas estão relacionados, então o artigo o ajudará a entender melhor os ponteiros.

Aqui está o que você pode esperar a seguir:

  • EU. C Desenvolvimento no Linux - Introdução
  • Ii. Comparação entre C e outras linguagens de programação
  • Iii. Tipos, operadores, variáveis
  • 4. Controle de fluxo
  • V. Funções
  • Vi. Ponteiros e matrizes
  • Vii. Estruturas
  • Viii. E/S básico
  • Ix. Estilo de codificação e recomendações
  • X. Construindo um programa
  • XI. Embalagem para Debian e Fedora
  • Xii. Obtendo um pacote nos repositórios oficiais do Debian

Tutoriais do Linux relacionados:

  • Uma introdução à automação, ferramentas e técnicas do Linux
  • Coisas para instalar no Ubuntu 20.04
  • Mastering Bash Script Loops
  • Loops aninhados em scripts de basquete
  • Mint 20: Melhor que o Ubuntu e o Microsoft Windows?
  • Manipulação de big data para diversão e lucro Parte 1
  • Coisas para fazer depois de instalar o Ubuntu 20.04 fossa focal linux
  • Tutorial de depuração do GDB para iniciantes
  • Loging e auditoria avançados no Linux
  • Com que frequência você tem que reiniciar seu servidor Linux?