Este material cobre diversos aspectos relacionados com o conceito de variável, p.ex. declaração, atribuição, inicialização, tipo. A linguagem C possui várias categorias de variáveis e a linguagem C++ estendeu ainda mais estas categorias.
O material abaixo focaliza nas “variáveis locais simples” .
Em diversas linguagens de programação existe o conceito de variável. Uma variável corresponde a uma posição ou espaço de memória. Uma posição ou espaço de memória pode guardar um valor. Uma declaração de variável define seu tipo e o identificador (nome) e também outros atributos. Uma das coisas que podemos fazer com uma variável é usá-la como alvo de uma iniciação/inicialização ou ainda como alvo de uma operação de atribuição (assignment). Na linguagem C a Declaração pode ser distinta da Definição de uma variável. Mas não vamos explorar isso por agora (mas estes são dois conceitos distintos e importantes: definição & declaração).
A sintaxe simplificada para a declaração de uma variável é:
<tipo> <nomeDeVariavel>;
Esta forma apenas declara a variável; outra sintaxe:
<tipo>
<nomeDeVariavel>=<expressao>;
Esta segunda forma corresponde a declarar e iniciar/inicializar o valor da
posição de memória correspondente à variável.
Mais à frente na disciplina veremos que as variáveis podem ser definidas em duas situações (i)dentro de uma função ou (ii)fora de uma função. Além disso uma variável pode (ii) ter o modificador static ou não ter o modificador static. Neste material estaremos falando “basicamente” de uma variável definida dentro de uma função e sem o modificador static. Na definição deste tipo de variável sem “inicialização” o conteúdo inicial deste tipo de variável é indefinido ou comumente referido como “lixo” de memória.
O nome de uma variável <nomeDeVariavel> deve começar com uma letra ou o sublinhado (underscore), os demais caracteres devem ser letras, sublinhado ou um caractere numérico (0-9). Algumas palavras não podem ser usadas (são chamadas “palavras chave” (keywords) exemplos de keywords são int double return char. As letras maiúsculas são distintas das letras minúsculas. Há uma certa disciplina no uso de nomes iniciando com o caractere sublinhado portanto evite usá-los.
A sintaxe simplificada de um comando ou operação ou expressão de
atribuição é:
<nomeDeVariavel> =
<expressão>;
O operador =(igual) corresponde a atribuição e existe um “operador relacional
igual” que é denotado com dois destes caracteres (==), os demais operadores
relacionais (>, < >=, <=, !=) são discutidos no material sobre
expressões. Neste comando o valor correspondente à expressão é copiado para a
posição de memória correspondente à variável. Note que o antigo valor é perdido.
Só podemos atribuir um valor para uma variável que tenha sido declarada ou
definida. A sintaxe e semântica de uma <expressão>
serão definidas mais tarde; os programas abaixo apresentam expressões simples e
bem intuitivas. Um exemplo de tipo em C é o tipo int correspondente a um
subconjunto finito do conjunto dos inteiros da Matemática e outro exemplo é o
tipo double correspondente a um subconjunto finito do conjunto dos
números reais (ou mais especificamente dos números racionais) da Matemática.
A sintaxe de uma expressão válida será definida ao longo da disciplina. A expressão mais simples corresponde a um valor simples, as vezes chamado constante ou literal. O termo “literal” é utilizado para designar as contantes que podem ser expressas no texto do programa. Os literais são basicamente cadeias de caracteres e existem várias regras sobre como escrever “literais” válidos. A partir dos caracteres em um “literal” o compilador infere o “tipo” do literal, veremos à frente que 123 é um literal do tipo int e que 1.0E10 é um literal do tipo double. A linguagem C não tem um tipo “lógico” ou “booleano” com literais “verdadeiro” e “falso” ou, no caso, “true” e “false”. Os projetistas da linguagem convencionaram que “bits em zero/bits desligados” denota o valor “falso” e um valor onde existe um ou mais bits ligados (um ou mais bits em um) denota o valor “verdadeiro”. Os operadores relacionais denotam o valor verdadeiro como sendo o valor “1”. Em versões mais recentes da especificação da linguagem C foi considerada a definição de um tipo booleano... mas em nossa disciplina seguiremos uma linguagem C mais tradicional (ou clássica) onde não existe o tipo booleano.
Quando o fluxo de controle atinge o ponto correspondente a uma “sentença de declaração” de variável local a variável correspondente passa a ficar disponível para atribuições e acesso. Quando o fluxo de controle atinge uma atribuição a “expressão” à direita do operador de atribuição (=) é avaliada e o resultado da avaliação da expressão passa a ser o valor da variável que está à esquerda do operador de atribuição. Há uma certa similaridade sintática e semântica entre o comando de atribuição e a iniciação ou inicialização de uma variável, mas a inicialização só é feita quando uma variável é criada e a atribuição pode ser feita a qualquer momento na vida de uma variável. Mais à frente vamos ver o ciclo de vida de vários tipos de variáveis, algumas “vivem” durante toda a execução do programa, mas outras só “vivem” durante parte do tempo de execução do programa.
O trecho de programa a seguir ilustra algumas declarações de variáveis e
comandos de atribuição:
int v1;
double v2;
v1=123;
v2=3.1415;
No trecho acima são declaradas duas variáveis, a variável v1 é do tipo int e a
variável v2 é do tipo double. Vemos ainda dois comandos de atribuição usando
expressões simples.
O tipo restringe os valores que podem ser atribuidos a uma variável.
O tipo int quando tem 32 bits (o tipo int pode ter 16, 32 ou mesmo 64 bits
dependendo do ambiente da linguagem C, no Dev-C++ o tipo int tem 32 bits!)
permite a representação de valores de -2.15 bilhões até +2.15 bilhões, o tipo
long quando tem 64 bits (no Dev-C++ o tipo long
tem 32 bits! Se quisermos um tipo com 64 bits temos que usar o long long) permite a representação de
valores de -9.22 quintilhões ate' +9.22 quintilhões. A exigência de tipo na
definição de uma variável tem a intenção de impor restrições que forcem
disciplina por parte do programador com o objetivo de permite programas mais
eficientes, com menor probabilidade de erros ou programas que sejam de leitura
mais fácil. As regras com relação a tipos e os literais ou constantes variam de
linguagem para linguagem. O trecho a seguir infelizmente é permitido na
linguagem C:
int v1;
v1=3.1415; /* poderia/deveria ser erro! mas não é... */
Uma linguagem pode ter regras de misturas de tipos mais ou menos rigorosas, quando a linguagem tem um certo número convencional de regras razoavelmente rigorosas é dito que a linguagem é “fortemente tipada” (strongly typed). Outra maneira de caracterizar a “tipagem” verifica se a linguagem tem mais ou menos restrições explícitas ou implícitas para misturas de valores e variáveis de diferentes tipos. A linguagem C permite certas misturas de valores e variáveis que muitas outras linguagens proibem (o compilador aceita uma atribuição de double para char e vice versa! acredito que deveria haver alguma “disciplina” ao menos neste caso.).
A associação de um tipo a uma variável na Computação é similar à amarração de
uma variável a um domínio como ocorre na Matemática. No entanto deve ser
ressaltado que o conceito de variável na Computação e o conceito de variável
na Matemática são conceitos bastante distintos. Uma das distinções mais
notáveis é que uma variável na Matemática, usualmente, não tem um valor
específico. Considere a expressão da Matemática
y=x+3
A variável x não tem um valor associado a ela, esta expressão pode ser
interpretada como representando o lugar geométrico dos pontos de um plano
correspondentes a uma reta. A variável (da Matemática) x pode tomar qualquer
valor real. As vezes os autores cuidadosos explicitam que a variável x
"pode tomar valores em" ou "pertence ao" domínio dos
números Reais, outros autores assumem que o leitor irá deduzir isso. Na
Computação, uma variável, no escopo (trecho de programa) onde ela é válida,
sempre tem um determinado valor associado a ela, este valor possivelmente muda
ao longo do tempo de execução do programa, mas a cada momento o valor de uma
variável é único. Na Matemática, uma expressão envolvendo uma variável, deve
sempre considerar que aquela variável, em todo ponto que ela aparece, tem
sempre um mesmo valor. Na Computação, em particular na linguagem C, isto não é
verdade, considere o trecho de programa abaixo:
int x;
x=(x=((x=1)+1)+1);
Na expressão acima a variável x aparece três vezes. Em cada uma das vezes x tem
um valor distinto associado. (A expressão acima não
tem muito sentido (melhor escrever x=3;) mas, mais a frente
na disciplina, veremos o conceito de função ou método e teremos oportunidade de
construir expressões onde este aspecto faça mais sentido)
A linguagem C tem vários tipos
int, float, char, short (short int) , long (long int), double (double-precision float)
novos tipos podem ser construídos com arranjos, estruturas e uniões, na linguagem C, além das várias extensões que a linguagem C++ introduziu e serão vistas mais à frente na disciplina.
É interessante observar que não existe o mnemônico integer (usa-se int), ou real/rational (usa-se float ou double), afinal os computadores representam apenas um subconjunto finito dos domínios infinitos correspondentes encontrados na Matemática.
A existência de tipos, além de melhorar a legibilidade, é justificada por questões de eficiência. Com a tecnologia atual ainda não temos sistemas gerais que conseguem descobrir dinamicamente e de forma eficiente como representar os valores com um número variável de posições de memória para os diferentes domínios de problemas e usar sempre um grande número de posições de memória para cada variável não é considerada uma opção aceitável. O programador fica encarregado de definir e operar com tipos apropriados para o caso específico de programa que está sendo implementado.
---------------------------
As constantes ou os literais podem ser do tipo int, por exemplo 1234, podem ser do tipo longo colocando a letra L (maiúscula ou minuscula no final), por exemplo 1234L ou 1234l; podem ser escritas na base 8 (octal) colocando o dígito 0 no início por exemplo 012 equivale a 10, podem ser escritas na base 16 (hexadecimal) prefixando com 0x ou 0X por exemplo 0x12 equivale a 18. As contantes inteiras com um u ou U no final são consideradas do tipo sem sinal (unsigned) e 1234UL é uma contante do tipo long sem sinal.
Pratique a definição, atribuição e impressão de
variáveis (utilizando a função printf) fazendo programas como este:
#include <stdio.h>
#include
<stdlib.h>
int main(int argc,
char *argv[]){
int i;
i=10;
printf("%d\n", i);
float f;
f=1.2345;
printf("%f\n", f);
double d;
d=12345.6789E300;
printf("%e\n", d);
printf("i:%d\nf:%f\ne:%e\n", i,f,d);
system("PAUSE");
return 0;
}
--------------------
Um outro tipo de variável da linguagem C são as variáveis que armazenam endereços de memória, em particular endereços de memória de outras variáveis. Essas variáveis as vezes são chamadas simplesmente de “ponteiros” (pointers). (Como será visto à frente, na disciplina, uma das decisões de projeto da linguagem C foi que a passagem de parâmetro das funções seria apenas por valor e uma das razões da necessidade de ponteiros se relaciona com este aspecto). o Domínio “endereços de memória” é um domínio bem próprio da área de Computação e alguns autores gostam de classificar as linguagens em função do quão longe ou perto elas proporcionam abstrações para os endereços de memória! Como a linguagem C trata os endereços de memória como “cidadãos de primeira classe” C é considerada uma linguagem de “baixo nível” (ou mais especificamente uma linguagem de baixo nível de abstração).
No trecho de programa abaixo x é uma variável do tipo inteiro e p é uma variável do tipo apontador para inteiro. Os operadores * e & são discutidos mais abaixo. Observe que (1)“int *” é o nome de um tipo: endereço de int ou apontador de int (2) Assim como existe o operador menos unário (-10 menos dez) e o operador menos binário (20-10 vinte menos dez) existe o operador & unário (&abc endereço de abc) e o operador &(e/and) binário (a&b a e/and bit-a-bit b). O operador asterisco binário corresponde ao operador de multiplicação mas o operador asterisco unário é denominado operador de indireção (indirection or dereferencing operator)
int x=3;
int *p; // (*p) é um inteiro e
p é um endereço ou apontador para inteiro
p=&x;
printf(“%d\n”, x);
*p=5;// a variável
p é usada para modificar o conteúdo de x
printf(“%d\n”, x);
Uma das maneiras que podemos interpretar: x está em um endereço (digamos por exemplo endereço 890ABCDE de memória), considere que cada byte tenha um endereço e que a variável x ocupa 4 bytes, neste caso a variável x corresponde ao espaço de memória correspondente aos endereços [890ABCDE; 890ABCDF; 890ABCE0; 89ABCE1]. Podemos assumir que a variável p ocupa 4 bytes de memória (um espaço de endereçamento de 32 bits) e portanto ocupa, 4 endereços: [890ABCE2; 890ABCE3; 890ABCE4; 890ABCE5]. o conteúdo de x inicialmente é 3; o conteúdo de p é o endereço da variável x:
890ABCDE:03; 890ABCDF:00;
890ABCE0:00; 890ABCE1:00
890ABCE2:DE;
890ABCE3:BC; 890ABCE4:0A; 890ABCE5:89
Uma outra convenção de visualização para as variáveis x e p é dada
pela figura abaixo:

Usaremos este tipo de convenção na disciplina. Nesta figura a memória é representada por uma “fita” (duas linhas paralelas horizontais) e as variáveis são regiões delimitadas com um nome. O conteúdo de x é 3 e o conteúdo de p é uma “seta” que representa o endereço de memória que p “aponta”. No caso acima p aponta para a região de memória correspondente à variável x. Infelizmente a maioria dos textos (e este texto aqui também!) não são precisos com relação ao uso dos termos. Algumas vezes o termo “ponteiro”se refere à variavel p (o endereço de memória onde está p e o conteúdo de p) outras vezes o termo “ponteiro” se refere apenas ao conteúdo da variável p. Um ponteiro pode receber a atribuição de um valor especial denominado NULL (nulo). Um ponteiro contendo NULL significa que o ponteiro não aponta para um valor válido. A convenção que usaremos de desenho é colocar um “aterramento” uma linha que se inicia com a bola preta como acima mas que termina em três barras de tamanho grande/médio/pequeno. A variável q na figura acima esta “aterrada” ou q aponta para “NULL” ou ainda q não aponta para um elemento válido. Adeclaração de q poderia ser int q=NULL; A constante NULL normalmente corresponde a todos os bits em zero que normalmente corresponde ao valor zero tanto no caso de int´s quanto no caso de double´s.
O asterisco * é um operador unário e denota “indireção”. Quando o * é aplicado a uma expressão, se esta expressão obedece a certas regras e representa um endereço de memória válido então o resultado é o valor associado ao endereço correspondente a esta expressão. Na declaração int *p deve ser observado que p não é um ponteiro “genérico”, p é um ponteiro para posições de memória que armazenam um valor do tipo int. Na declaração double *q observe que q é um ponteiro para posições de memória que armazenam um valor do tipo double.
O e-comercial & é um operador unário e denota “endereço de”. Quando o & é aplicado a uma expressão, se esta expressão obedece a certas regras e representa um endereço de memória então o resultado é o endereço de memória correspondente a esta expressão ( O operador & só pode ser aplicado a variáveis simples e elementos de arranjos que serão vistos abaixo).
As sentenças envolvendo variável, endereço de variável e valor de uma variável são potencialmente confusas (para o aprendiz!) porque uma variável do lado esquerdo de uma atribuição corresponde a um endereço, uma variável do lado direito de uma atribuição corresponde a um valor, e os operadores * e & trazem novos significados.
Podemos colocar mais de um nível de indireção:
int x=3;
int *p;
int **q;
p=&x;
q=&p;
printf("%d\n", x);
**q=5;
printf("%d\n", x);
Exercício: Faça o desenho das variáveis x, p e q correspondentes ao trecho acima.
Mas não é razoável ou usual colocar mais de um nível de operador “endereço de” Tente entender porque o programa abaixo está errado:
int x=3;
int *p;
int **q;
q=&&x; // errado!!!!
printf("%d\n",
x);
**q=5;
printf("%d\n", x);
Se o compilador fosse “bastante inteligente” talvez ele conseguisse descobrir
onde está o endereço do endereço de x! O fato mais básico é que só podemos
aplicar o operador & (endereço de) em certas expressões: por agora somente
variáveis.
Os ponteiros dão ênfase ao fato de que uma variável tem várias propriedades ou características: (i) tem nome(identificador) (ii) tem tipo (iii) tem um endereço (iv) tem um valor, dentre outras características. Quando usamos um nome de variável o nome pode denotar o endereço da variável (conhecido como left-value) ou pode denotar o valor da variável (conhecido como right-value). Os ponteiros também podem denotar o valor ou endereço de uma variável:
No trecho:
int i=8;
int *p=&i
a expressão p denota o endereço da variável i e a expressão *p denota o valor da variável i (no caso 8); Observe que em um comando de atribuição nos podemos ter expressões dos dois lados da atribuição. O comando de atribuição depende para sua execução de determinar um L-value (valor esquerdo, left-value) para funcionar como endereço de memória, e ainda de um R-value (valor direito, right-value) para funcionar como valor de um tipo que também será determinado em função da sintaxe da expressão.
Observe a assimetria no tratamento do nome de uma variável nas expressões de atribuição abaixo:
int i=7;
int j=8;
int k=9;
i=j;// o valor de i
é ignorado, o endereço de i é utilizado; o valor de j é o objetivo!
k=i; // o valor de
k é ignorado, o endereço de k é utilizado; o valor de i é o objetivo!
A assimetria consiste no fato de que um nome de variável denota um ENDEREÇO
quando está no lado esquerdo de um operador de atribuição e este mesmo nome de
variável denota um VALOR quando está em outros lugares (em particular no lado
direito do operador de atribuição). Uma pessoa poderia querer que a sintaxe
fosse:
&i=j; //erro
Se tentarmos atribuir uma expressão a algo que não seja um “valor esquerdo”
válido o compilador GNU emite uma mensagem de erro:
10=20; //erro
invalid lvalue in
assignment
Ou seja… na linguagem C uma constante denota apenas um valor e não
denota um endereço de memória.
----------------
A linguagem C nos permite o seguinte tipo de declaração:
int d[9];
Neste caso d não é uma variável! d passa a denotar uma sequência de variáveis do tipo int. o número de variáveis corresponde ao valor da expressão entre colchetes. Uma declaração como acima tenta simular a seguinte declaração int d[0], d[1], ..., d[8]; onde cada d[i] é uma variável. As variáveis definidas utilizando arranjos têm índice sempre iniciando de 0 (zero). Os projetistas da linguagem C quiseram que o nome “d” funcionasse (sintaticamente) como um ponteiro/“pointer”. Mas lembre-se que d não é uma variável (mas pode haver confusão porque d denota o endereço inicial do arranjo que fica alocado sequencialmente em memória!!)!
Podemos trabalhar com cada uma das variáveis utilizando o operador “indexação” [] junto ao nome do arranjo.
O seguinte trecho de programa:
int cpf=123456789;
int d0, d1, d2, d3, d4, d5, d6, d7, d8;
int i;
d0=cpf%10;
d1=cpf/10%10; d2=cpf/100%10;
d3=cpf/1000%10; d4=cpf/10000%10;
d5=cpf/100000%10;
d6=cpf/1000000%10; d7=cpf/10000000%10; d8=cpf/100000000%10;
printf(“%d\n%d\n%d\n%d\n%d\n%d\n%d\n%d\n%d\n”,d0,d1,
d2, d3, d4, d5, d6, d7,d8);
pode ser colocado em correspondência com este trecho:
int
cpf=123456789;
int d[9];
int i;
int
aux=cpf;
for(i=0; i<9; i++){ d[i]=aux%10; aux/=10;}
for(i=0; i<9; i++) printf("%d\n", d[i]);
observe o uso do operador de indexação! O comando “for(exp1; exp2; exp3) será visto mais à frente na disciplina.
Na linguagem C o operador indexação [] é definido de forma a satisfazer a seguinte identidade:
exp1[exp2] é idêntico a *(exp1+exp2)
como exemplo d[0] corresponde a *d. Ou seja a linguagem C define uma espécie de “aritmética de ponteiros” que tornou-se parte inseparável da linguagem. Portanto observe que a declaração:
int a[10];
define um arranjo (chamado genericamente de “arranjo a”) de tamanho 10. Consiste em um bloco de 10 variáveis consecutivas: a[0], a[1], ... a[9]. A notação a[i] refere-se ao i-ésimo elemento do arranjo. A declaração:
int *pa;
define uma variável (de nome pa) do tipo “ponteiro para int”.
e a declaração
int (*pa)[10];
define uma variável (de nome pa) do tipo pointer para arranjo de 10
ints; e a declaração
int *pb[10];
define um arranjo (de nome pb) de ponteiros para int (com 10
ponteiros):
A atribuição
pa=&a[0];
atribui para pa o endereço do primeiro elemento do arranjo a. A
atribuição
*(pa+1)=3;
atribui a a[1] o valor 3! Ou seja a aritmética de ponteiros leva em
conta o tamanho em bytes de um tipo na memória e faz as operações aritméticas
de forma a preservar a equivalência entre exp1[exp2] e *(exp1+exp2).
A linguagem C permite a definição de “constantes do tipo enumeração”. E usando enumeração podemos, por exemplo, deixar mais bem documentados certas “convenções”. Considere por exemplo:
enum boolean {FALSO, VERDADEIRO};
Com a sentença acima o programador definiu uma etiqueta de enumeração (enumeration tag): boolean e deu nomes para valores do tipo int, ou, de modo inverso associou valores do tipo int para nomes. O primeiro nome em uma enumeração (p.ex. FALSO) tem o valor 0, o seguinte 1 e assim sucessivamente. Com a definição acima temos o nome FALSO como um sinônimo para 0; podemos atribuir zero para uma variável desta forma: int j=FALSO; No entanto os valores podem ser explicitados:
enum meses {JAN=1, FEV, MAR,ABR, MAI, JUN, JUL, AGO, SET, OUT, NOV, DEZ}; /* FEV=2 etc*/
As etiquetas de enumeração podem aparecer sem as listas e são tratadas de
forma similar a tipos:
enum boolean ChaveDesligada;
enum meses
mesDeNascimento;
No exemplo acima chaveDesligada é do tipo enum boolean e mesDeNascimento é do tipo enum meses. Entretanto o que pode parecer confuso é que a definição da linguagem C não distingue entre enumerações e o tipo int, ou seja quando definimos uma variável como sendo do tipo “enumeração” ela na verdade é do tipo int.
Podemos então fazer as seguintes atribuições:
chaveDesligada=FALSO;
mesDeNascimento=MAR;
Podemos usar variáveis do tipo enumeração em situações onde podemos usar
variáveis do tipo int:
if(chaveDesligada)
printf(“Chave esta desligada\n”);
Em outras ocasiões não existe interesse na etiqueta de enumeração, deseja-se
usar apenas o mecanismo de associar valores do tipo int para os iten de
enumeração:
enum {DERROTA, EMPATE,
VITORIA}; int resultado=EMPATE;
O ponto importante é que o uso de enumerações pode tornar o programa mais legível. Ao invés de usar convenções e comentários para certas codificações:
int resultado; //derrota=0;
empate=1; vitoria=2
...
if(resultado==2)
printf(“vitoria\n”);
Podemos usar as enumerações:
int resultado; enum {DERROTA,
EMPATE, VITORIA};
...
if(resultado==VITORIA)
printf(“vitoria\n”);
Nomes em diferentes enumerações devem ser distintos, valores nas enumerações
não necessitam ser distintos. A linguagem C oferece ainda o pré-processador com
a opção #define
para definir contantes. Enumerações são tratadas em tempo de compilação a
cláusula #define é tratada anteriormente à compilação pelo pré-processador C.
Mais à frente na disciplina veremos as variáveis do tipo “struct” e veremos
o tratamento especial que tem o tipo “arranjo de char”. A figura abaixo tenta
resumir algumas das relações entre tipos da linguagem C:
------------------
A figura acima de uma certa maneira simplifica a visão das possibilidades de
tipos na Linguagem C. Uma visão ainda mais elaborada do tipos é dada na figura
a seguir. Pode ser observado que o tipo “pointer” é tratado como sendo quase
uma “extrusão” de tipos primitivos, e pode ser observado o estranho tipo “void”.
Além disso existe um tipo função. Um ponteiro para char pode ser um ”ponteiro
para char” ou pode ser um ser um “string”, um “arranjo de char” pode ser um “arranjo
de char” ou pode ser um “string”.

Alocação dinâmica
O espaço em memória de uma variável pode ser alocado dinamicamente através da função malloc(). Veja os exemplos abaixo:
int a; a=10; equivale a
int a; int *pa=&a; *pa=10; que equivale a
int *pa; pa=(int *)malloc(sizeof(int)); *pa=10;
O termo sizeof(int) corresponde a uma das formas do operador sizeof(<nome-de-tipo>) e devolve o tamanho em bytes de um certo tipo.
double a; a=10.; equivale a
double a; double
*pa=&a; *pa=10.; equivale a
double *pa;
pa=(double *)malloc(sizeof(double)); *pa=10.;
int a[]={10,20}; equivale a
int (*pa)[2]; pa=(int (*)[2])malloc(sizeof(int)*2); (*pa)[0]=10; (*pa)[1]=20;
Há uma forma mais legível da chamada malloc(sizeof(int)*2), trata-se da função calloc: calloc(2, sizeof(int)). Portanto:
int a[]={10,20}; equivale a
int
(*pa)[2]; pa=(int (*)[2])calloc(2, sizeof(int)); (*pa)[0]=10; (*pa)[1]=20;
As funções malloc e calloc correspondem aos mecanismos de alocação dinâmica de memória. Ao longo da execução de um programa o programador pode ou deve indicar que não irá usar mais o espaço de memória reservado por estas funções, a função que proporciona esta semântica é free(void *p).
Não se sinta intimidado pela complexidade da sintaxe da linguagem C...Mesmo os projetistas da linguagem reconhecem “C is sometimes castigated for the syntax of its declarations” [K&R, 2ª. ed, p. 122] ao longo da disciplina iremos analisar algumas expressões e o segredo é fazer pequenos programas e testar se a interpretação das sentenças esta correta.
---------------
Mais à frente na disciplina iremos tratar de outros tipos de variáveis.
Literais na linguagem C:
A linguagem apresenta constantes do tipo int, constantes do tipo char, constants do tipo float e constantes do tipo enumeração. As constantes do tipo int são seqüências de digitos que são consideradas na base 16 caso os primeiros caracteres sejam 0x ou 0X (caractere zero seguido do caractere x maiúsculo ou minúsculo ):
0x10 corresponde a 16 na base 10; 0x21 corresponde a 33 na base 10.
A base é 8 caso o primeiro caractere seja zero: 010 corresponde a 8 na base 10; 021 corresponde a 17 na base 10.
A base é 10 caso o primeiro dígito não seja 0. A expressão 123 corresponde a um int, para denotar uma constante do tipo “longo”escreve-se um L maiúsculo ou minúsculo no final: 1234565789123L
Podemos também denotar constantes do tipo sem sinal (“unsigned”) colocando um U maiúsculo ou minúsculo ao final da constante: 12345U, 12345UL.
As constantes do tipo char consistem em um caractere entre apóstrofo, exemplos: ¢A¢, ¢1¢, ¢$¢. Em um computador existe uma ou mais“tabelas” que relacionam caracteres com valores (normalmente do tipo int). A tabela abaixo é uma das mais usadas, denominada tabela ASCII:

Usando a tabela acima podemos verificar que o caractere zero tem valor 48 (0x30). As constantes caracteres podem participar de expressões, por exemplo ¢B¢-¢A¢ corresponde ao valor 1. A linguagem C define “seqüências de escape” para certos caracteres que não tem um “glifo” distinto, por exemplo ¢\n¢ corresponde ao caractere “nova linha” (são dois caracteres representando um caractere!). A forma geral ¢\ooo¢, onde o é um caractere representando um dígito octal, representa um valor em 8 bits, outra forma é ¢\xhh¢ onde h é um caractere representando um dígito hexadecimal:
considere a variável: char
c; as atribuições abaixo, em termos de tabela ASCII, são
equivalentes:
c=¢A¢; c=65; c=¢\x41¢ c=¢\101¢;
Dentre as seqüências de escape da linguagem C temos:
\a campainha, \b backspace, \f formfeed, \n nova linha, \r retorno de carro, \t tabulação, \v tabulação vertical, \\ barra, \? interrogação, \¢ apostrofo, \″ aspas, \ooo [octal], \xhh [hexadecimal].
Este literal construído com quatro caracteres: ¢\0¢, representa o caractere com valor zero as vezes também chamado “caractere nulo” (null character).
--------------------
A linguagem C não tem um tipo “cadeia de caracteres” (String) para variáveis mas define uma constante(literal) do tipo String. Um literal(constante) cadeia de caracteres(String) consiste em uma seqüência de caracteres entre aspas, exemplo: ″abcd123″
A cadeia de caracteres vazia é representada por uma aspas seguida imediatamente da outra: ″″ Observe que na linguagem C não tem significado um apostrofo imediatamente seguido de outro.
Um string pode ser seguido de outro: ″ola″ ″ mundo″ equivale a ″ola mundo″, esta concatenação de strings é feita em tempo de compilação. Isto é útil para partir strings extensos. A linguagem C trata os strings como sendo um arranjo de caracteres (que serão tratados à frente na disciplina). A convenção na linguagem C é que ao ser construida a seqüência de caracteres será colocado o caractere ¢\0¢ no final da seqüência. O tamanho de uma sequência de caracteres é sempre 1 caractere a mais em função do caractere nulo que é colocado ao final. A função strlen(s) devolve o tamanho do string s sem considerar o caractere nulo.
As constantes do tipo enumeração serão vistas à frente.
As constantes de ponto flutuante têm um ponto decimal (por exemplo 12.34) ou um expoente (por exemplo 1e-2) ou ambos. As constantes são do tipo double por default, o sufixo f ou F indica uma constante float. o sufixo l ou L indicam um long double.
Assim como vimos um “mantra” de exibição de variáveis é interessante praticar “mantras” de leitura de valores de variáveis. A função scanf tem certa relação com a função printf.
o trecho
int i;
i=123;
printf("%d\n",
i);
declara, atribui e exibe o valor da variável i.
o trecho
int i;
scanf("%d",
&i);
printf("valor
lido:%d\n", i);
declara e lê a partir do teclado (mais especificamente da “entrada padrão” – será discutido à frente) um valor para i e depois exibe o valor lido. Observe que o identificador i é precedido pelo operador & (a linha scanf(“%d”, &i) é normalmente lida como “scan f percento d endereço de i” ou ainda “scan f percento d e-comercial i”. Conforme será discutido em outro material a função scanf deve receber o endereço de uma variável. Usando este endereço e a especificação “%d” a função scanf converte o que for digitado no teclado em um valor do tipo int e atribui para o endereço especificado.
---
Escopos (scope), Espaços de Nomes, Classe de Armazenamento, Ligação etc
A linguagem C tem diversas versões e diversas especificações, vamos continuar, de modo geral, a adotar o livro de K&R comtítulo The C Programming Language 2nd ed, vamos apenas esquematizar o assunto de maneira "tosca", pois existe um grande número de detalhes. Este material só é compreendido após o conhecimento de vários tópicos discutidos no material sobre funções.
Um identificador ou nome, pode referir (ou denotar!) os seguintes elementos:
-funções; Na definição: int identidadeInt(int x){return x;} e na chamada (invocação)
printf("%d\n", identidadeInt(123)); o nome identidadeInt denota uma função.
-estiquetas de estruturas; Na definição: struct ETIQ{int campo;} var1; e na declaração struct ETIQ var2; o nome ETIQ denota uma etiqueta.
-etiquetas de uniões; similar ao caso de estruturas;
-etiquetas de enumerações; similar ao caso de estruturas;
-membros de estruturas; Na definição: struct E{int campo;} var1; e na seleção var1.campo o nome campo denota um membro (ou campo) de estrutura.
-membros de uniões - similar
-contantes de enumerações; Na definição: enum E{ABC, DEF} var1; e na expressão: var1=ABC; o nome ABC denota uma constante de enumeração.
-nomes de "typedefs"; Na definição: typedef int meses[13]; e na declaração meses var1, var2; o nome meses denota um typedef.
-variáveis (K&R usaram "object" mas ressalvam "sometimes called a variable");Na definição int mes; e na expressão mes=1; o nome mes denota uma variável.
-rótulos [não são considerados usualmente como da mesma categoria acima]; no trecho while(1){goto ROT;} ROT: o nome ROT denota um rótulo
Características ou aspectos de uma variável :
classe de armazenamento (storage class) e tipo; A classe de armazenamento determina a vida (lifetime) [ou o tempo de vida] da área de memória associada à variável. O tipo determina a interpretação, o significado dos valores associados à variável.
Os nomes também são caracterizados pelo Escopo (Scope) que corresponde a regiões de um programa onde eles são conhecidos e são caracterizados também por Ligação (linkage) que determina se um nome em um certo escopo X refere-se a uma mesma variável ou função.
Existem duas classes de armazenamento: automático e estático (automatic & static). Algumas palavras chave e "contexto" especificam a classe de armazenamento.
Variáveis declaradas fora das funções são estáticas, variáveis declaradas com a palavra chave "static" são estáticas: quando o programa inicia a execução e o fluxo de controle chega até as definições elam passam a existir e só deixam de existir quando o programa termina.
Variáveis dentro das funções ou dentro de blocos são "automáticas": quando o fluxo de controle atinge os blocos, mais especificamente as definições as variáveis passam a existir e quando o fluxo de controle abandona o bloco correspondente elas deixam de existir.
A linguagem C tem 4 tipos de Espaços de nomes para os identificadores ou nomes. O significado disto é que em um mesmo escopo podemos usar um mesmo identificador sem colisão de significado.
1-nomes de rótulos (nomes do tipo "vá para" (goto)), rótulos de sentenças;
2-Etiquetas (struc/union/enum)
3-Membros (campos) de struct/union
4-Nome de funções, nomes de variáveis, nomes de contantes de enumeração, nomes de typedefs,
O trecho de programa abaixo ilustra este aspecto da linguagem C. o identificador ABC existe em 5 diferentes espaços de nomes sem colisões. Observe que cada struct/union define um espaço de nomes para membros/campos, portanto ABC de ABC é distinto de ABC de X.
int main(int argc, char *argv[]){
struct ABC{int ABC;} var1;
struct X{double ABC} var2;
int ABC;
goto ABC;
ABC: printf("programa ok!\n");
system("PAUSE");
return 0;
}
A linguagem C permite os seguintes tipos de escopos (regiões onde nomes são definidos ou utilizáveis):
-escopo global
-escopo de arquivo
-escopo de função
-escopo de bloco ou escopo local
-escopo de protótipo de função.
Os nomes de rótulos têm somente um escopo: função;
As estiquetas de struct/union/enum podem ter escopo global, arquivo, função, bloco e têm sentido restrito no escopo de protótipo de função;
os membros de struct e union têm escopo local próprio.
Os nomes de função têm escopo global ou de arquivo;
Os nomes de variáveis, contantes de enumeração e nomes de typedefs podem ter todos escopos;
Via de regra o aninhamento de escopo implica no ocultamento de um mesmo nome:
typedef int meses[13];
meses a1,a2;
int f(){ typedef
double meses; meses var; var=3.14;}
No trecho acima o nome "meses" foi usado em dois escopos (escopo global e escopo de função) e ilustra um mesmo nome denotando typedefs distintos.
A linguagem C provê ainda a palavra chave “extern”. Esta palavra chave
permite a ligação (linkage) de elementos entre diferentes escopos. Considere o
trecho de programa abaixo:
void f(){
extern int
abc;
printf(“%d\n”, abc);
}
O nome abc irá corresponder a uma variável definida fora do escopo da função f.
Quando a definição de abc é no mesmo arquivo o uso de “extern” é opcional.