Capítulo– 5
Introdução à programação C
Introdução
'C' é uma das linguagens de computador mais populares no mundo dos computadores de hoje. A linguagem de programação 'C' foi projetada e desenvolvida por Brian Kernighan e Dennis Ritchie no The Bell Research Labs em 1972.
'C' é uma linguagem criada especificamente para permitir ao programador acesso a quase todos os componentes internos da máquina - registradores, slots de E/S e endereços absolutos. Ao mesmo tempo, 'C' permite o máximo de manipulação de dados e modularização de texto programado, conforme necessário para permitir que projetos multiprogramadores muito complexos sejam construídos de maneira organizada e oportuna.
Embora essa linguagem tenha sido originalmente planejada para ser executada no UNIX, houve um grande interesse em executá-la no sistema operacional MS-DOS no IBM PC e compatíveis. É uma linguagem excelente para esse ambiente devido à simplicidade de expressão, à compacidade do código e à ampla faixa de aplicabilidade.
Além disso, devido à simplicidade e facilidade de escrever um compilador C, geralmente é a primeira linguagem de alto nível disponível em qualquer computador novo, incluindo microcomputadores, minicomputadores e mainframes.
Por que usar C na Programação de Recuperação de Dados
No mundo atual da programação de computadores, existem muitas linguagens de alto nível disponíveis. Essas linguagens são boas com muitos recursos adequados para a maioria das tarefas de programação. No entanto, existem várias razões pelas quais C é a primeira escolha dos programadores que estão dispostos a fazer programação para recuperação de dados, programação de sistemas, programação de dispositivos ou programação de hardware:
- C é uma linguagem popular preferida por programadores profissionais. Como resultado, uma grande variedade de compiladores C e acessórios úteis estão disponíveis.
- C é uma linguagem portátil. Um programa em C escrito para um sistema de computador pode ser compilado e executado em outro sistema com pouca ou nenhuma modificação. A portabilidade é aprimorada pelo padrão ANSI para C, o conjunto de regras para compiladores C.
- C permite um amplo uso de módulos na programação. O código C pode ser escrito em rotinas chamadas funções. Essas funções podem ser reutilizadas em outros aplicativos ou programas. Você não precisa fazer esforços extras na programação de um novo aplicativo para criar o mesmo módulo que você desenvolveu em outra programação de aplicativo anteriormente.
Você pode usar esta função em um novo programa sem nenhuma alteração ou algumas pequenas alterações. No caso de programação de recuperação de dados, você encontrará essa qualidade muito útil quando precisar executar as mesmas funções várias vezes em diferentes aplicativos de programas diferentes.
- C é uma linguagem poderosa e flexível. Esta é a razão pela qual C é usado para projetos tão diversos como sistemas operacionais, processadores de texto, gráficos, planilhas e até compiladores para outras linguagens.
- C é uma linguagem de poucas palavras, contendo apenas um punhado de termos, chamados palavras-chave, que servem como base sobre a qual a funcionalidade da linguagem é construída. Essas palavras-chave, também chamadas de palavras reservadas, o tornam mais poderoso e dão a ampla área de programação e fazem com que um programador sinta-se a fazer qualquer tipo de programação em C.
Deixe-me supor que você não sabe nada em C
Presumo que você não saiba nada sobre programação em C e também não tenha nenhuma ideia de programação. Vou começar com os conceitos mais básicos de C e levá-lo até o alto nível de programação em C, incluindo os conceitos geralmente intimidantes de ponteiros, estruturas e alocação dinâmica.
Para entender completamente esses conceitos, levará um bom tempo e trabalho de sua parte, porque eles não são particularmente fáceis de entender, mas são ferramentas muito poderosas.
Programar em C é um grande trunfo nas áreas em que você pode precisar usar a linguagem Assembly, mas prefere mantê-la como um programa simples de escrever e fácil de manter. O tempo economizado na codificação de C pode ser enorme nesses casos.
Embora a linguagem C tenha um bom registro quando os programas são transportados de uma implementação para outra, existem diferenças nos compiladores que você encontrará sempre que tentar usar outro compilador.
A maioria das diferenças se torna aparente quando você usa extensões não padrão, como chamadas para o BIOS do DOS ao usar o MS-DOS, mas mesmo essas diferenças podem ser minimizadas pela escolha cuidadosa de construções de programação.
Quando ficou evidente que a linguagem de programação C estava se tornando uma linguagem muito popular disponível em uma ampla variedade de computadores, um grupo de indivíduos preocupados se reuniu para propor um conjunto padrão de regras para o uso da linguagem de programação C.
O grupo representou todos os setores da indústria de software e depois de muitas reuniões e muitos rascunhos, eles finalmente escreveram um padrão aceitável para a linguagem C. Foi aceito pelo American National Standards Institute (ANSI), e pela International Standards Organization (ISO).
Não é imposto a nenhum grupo ou usuário, mas como é amplamente aceito, seria um suicídio econômico para qualquer compilador se recusar a se adequar ao padrão.
Os programas escritos neste livro são principalmente para uso em um IBM-PC ou computador compatível, mas podem ser usados com qualquer compilador padrão ANSI, pois está em conformidade com o padrão ANSI.
Vamos começar
Antes de poder fazer qualquer coisa em qualquer linguagem e começar a programar, você deve saber como nomear um identificador. Um identificador é usado para qualquer variável, função, definição de dados, etc. Na linguagem de programação C, um identificador é uma combinação de caracteres alfanuméricos, sendo o primeiro uma letra do alfabeto ou um sublinhado e o restante sendo qualquer letra do alfabeto, qualquer dígito numérico ou o sublinhado.
Duas regras devem ser lembradas ao nomear identificadores.
- O caso de caracteres alfabéticos é significativo. C é uma linguagem sensível a maiúsculas e minúsculas. Isso significa que Recovery é diferente de recovery e rEcOveRY é diferente de ambos mencionados anteriormente.
- De acordo com o padrão ANSI-C, pelo menos 31 caracteres significativos podem ser usados e serão considerados significativos por um compilador ANSI-C em conformidade. Se mais de 31 forem usados, todos os caracteres além do 31º poderão ser ignorados por qualquer compilador.
Palavras-chave
Existem 32 palavras definidas como palavras-chave em C. Elas têm usos predefinidos e não podem ser usadas para nenhum outro propósito em um programa em C. Eles são usados pelo compilador como uma ajuda para compilar o programa. Eles são sempre escritos em letras minúsculas. Segue uma lista completa:
auto |
break |
case |
char |
const |
continue |
default |
do |
double |
else |
enum |
extern |
float |
for |
goto |
if |
int |
long |
register |
return |
short |
signed |
sizeof |
static |
struct |
switch |
typedef |
union |
unsigned |
void |
volatile |
while |
Aqui vemos a magia do C. A maravilhosa coleção de apenas 32 palavras-chave dá um amplo uso em diferentes aplicações. Qualquer programa de computador tem duas entidades a serem consideradas, os dados e o programa. Eles são altamente dependentes um do outro e o planejamento cuidadoso de ambos leva a um programa bem planejado e bem escrito.
Vamos começar com um programa simples em C:
/* First Program to learn C */
#include <stdio.h>
void main()
{
printf("This is a C program\n"); // printing a message
}
Embora o programa seja muito simples, alguns pontos são dignos de nota. Vamos examinar o programa acima. Tudo o que estiver dentro de /* e */ é considerado um comentário e será ignorado pelo compilador. Você não deve incluir comentários em outros comentários, portanto, algo assim não é permitido:
/* este é um /* comentário */ dentro de um comentário, o que está errado */
Há também uma forma de documentação que funciona dentro de uma linha. Usando // podemos adicionar uma pequena documentação dentro dessa linha.
Todo programa C contém uma função chamada main. Este é o ponto de partida do programa. Toda função deve retornar um valor. Neste programa, a função main não retorna nenhum valor de retorno, portanto, escrevemos void main. Também poderíamos escrever este programa como:
/* Primeiro programa para aprender C */
#include <stdio.h>
main()
{
printf("This is a C program\n"); // printing a message
return 0;
}
Ambos os programas são iguais e executam a mesma tarefa. O resultado de ambos os programas imprimirá a seguinte saída na tela:
Este é um programa em C
#include<stdio.h> permite que o programa interaja com a tela, teclado e sistema de arquivos do seu computador. Você o encontrará no início de quase todos os programas em C.
main() declara o início da função, enquanto os dois colchetes mostram o início e o fim da função. Chaves em C são usadas para agrupar instruções como em uma função ou no corpo de um loop. Esse agrupamento é conhecido como uma instrução composta ou um bloco.
printf("This is a C program\n"); imprime as palavras na tela. O texto a ser impresso está entre aspas duplas. O \n no final do texto diz ao programa para imprimir uma nova linha como parte da saída. A função printf() é usada para exibição do monitor da saída.
A maioria dos programas em C está em letras minúsculas. Você normalmente encontrará letras maiúsculas usadas em definições de pré-processador que serão discutidas mais tarde, ou entre aspas como partes de cadeias de caracteres.
Compilando o programa
Deixe o nome do nosso programa é CPROG.C. Para inserir e compilar o programa C, siga estas etapas:
- Faça o diretório ativo de seus programas C e inicie seu editor. Para isso, qualquer editor de texto pode ser usado, mas a maioria dos compiladores C, como o Turbo C++ da Borland, possui um ambiente de desenvolvimento integrado (IDE) que permite inserir, compilar e vincular seus programas em uma configuração conveniente.
- Escreva e salve o código-fonte. Você deve nomear o arquivo CPROG.C.
- Compile e vincule CPROG.C. Execute o comando apropriado especificado pelos manuais do seu compilador. Você deve receber uma mensagem informando que não houve erros ou avisos.
- Verifique as mensagens do compilador. Se você não receber nenhum erro ou aviso, tudo deve ficar bem. Se houver algum erro na digitação do programa, o compilador o detectará e exibirá uma mensagem de erro. Corrija o erro, exibido na mensagem de erro.
- Seu primeiro programa em C deve estar compilado e pronto para ser executado. Se você exibir uma listagem de diretórios de todos os arquivos chamados CPROG vocêobterá os quatro arquivos com extensões diferentes descritos da seguinte forma:
- CPROG.C, o arquivo de código-fonte
- CPROG.BAK, o arquivo de backup do arquivo de origem que você criou com o editor
- CPROG.OBJ, contém o código do objeto para CPROG.C
- CPROG.EXE, o programa executável criado quando você compilou e vinculou CPROG.C
- Para executar ou executar o CPROG.EXE, basta digitar cprog. A mensagem Este é um programa C é exibida na tela.
Agora vamos examinar o seguinte programa:
/* First Program to learn C */ // 1
// 2
#include <stdio.h> // 3
// 4
main() // 5
{
// 6
printf("This is a C program\n"); // 7
// 8
return 0; // 9
} // 10
Quando você compila este programa, o compilador exibe uma mensagem semelhante à seguinte:
cprog.c(8): Erro: `;' esperado
vamos dividir esta mensagem de erro em partes. cprog.c é o nome do arquivo onde o erro foi encontrado. (8) é o número da linha onde o erro foi encontrado. Erro: `;' esperado é Uma descrição do erro.
Esta mensagem é bastante informativa e informa que na linha 8 do CPROG.C o compilador esperava encontrar um ponto e vírgula, mas não encontrou. No entanto, você sabe que o ponto e vírgula foi omitido na linha 7, portanto, há uma discrepância.
Por que o compilador relata um erro na linha 8 quando, na verdade, um ponto e vírgula foi omitido na linha 7. A resposta está no fato de que C não se importa com coisas como quebras entre linhas. O ponto e vírgula que pertence após a instrução printf() poderia ter sido colocado na próxima linha, embora isso fosse uma programação ruim na prática.
Somente após encontrar o próximo comando (return) na linha 8, o compilador tem certeza de que o ponto e vírgula está faltando. Portanto, o compilador informa que o erro está na linha 8.
Pode haver várias possibilidades de diferentes tipos de erros. Vamos discutir a vinculação de mensagens de erro. Os erros do vinculador são relativamente raros e geralmente resultam de erros ortográficos no nome de uma função da biblioteca C. Nesse caso, você recebe um erro: símbolos indefinidos: mensagem de erro, seguido pelo nome incorreto. Depois de corrigir a ortografia, o problema deve desaparecer.
Imprimir números
Vejamos o seguinte exemplo:
// Como imprimir os números //
#include<stdio.h>
void main()
{
int num = 10;
printf(“ The Number Is %d”, num);
}
A saída do programa será exibida na tela da seguinte forma:
O número é 10
O sinal % é usado para sinalizar a saída de muitos tipos diferentes de variáveis. O caractere após o sinal % é um d, que sinaliza a rotina de saída para obter um valor decimal e emiti-lo.
Usando variáveis
Em C, uma variável deve ser declarada antes de poder ser usada. As variáveis podem ser declaradas no início de qualquer bloco de código, mas a maioria é encontrada no início de cada função. A maioria das variáveis locais são criadas quando a função é chamada e são destruídas no retorno dessa função.
Para usar variáveis em seus programas C, você deve conhecer as seguintes regras ao nomear variáveis em C:
- O nome pode conter letras, dígitos e o caractere sublinhado (_).
- O primeiro caractere do nome deve ser uma letra. O sublinhado também é um primeiro caractere legal, mas seu uso não é recomendado.
- C diferencia maiúsculas de minúsculas, portanto, o nome da variável num é diferente de Num.
- Palavras-chave C não podem ser usadas como nomes de variáveis. Uma palavra-chave é uma palavra que faz parte da linguagem C.
A lista a seguir contém alguns exemplos de nomes de variáveis C legais e ilegais:
Variable Name |
Legal or Not |
Num |
Legal |
Ttpt2_t2p |
Legal |
Tt pt |
Illegal: Space is not allowed |
_1990_tax |
Legal but not advised |
Jack_phone# |
Illegal: Contains the illegal character # |
Case |
Illegal: Is a C keyword |
1book |
Illegal: First character is a digit |
A primeira novidade que se destaca é a primeira linha do corpo de main():
int num = 10;
Esta linha define uma variável chamada 'num' do tipo int e a inicializa com o valor 10. Isso também pode ter sido escrito como:
int num; /* define uninitialized variable 'num' */
/* and after all variable definitions: */
num = 10; /* assigns value 10 to variable 'num' */
As variáveis podem ser definidas no início de um bloco (entre as chaves {e}), geralmente no início de um corpo de função, mas também pode ser no início de outro tipo de bloco.
As variáveis que são definidas no início de um bloco são padronizadas para o status 'auto'. Isso significa que eles só existem durante a execução do bloco. Quando a execução da função começar, as variáveis serão criadas, mas seu conteúdo será indefinido. Quando a função retornar, as variáveis serão destruídas. A definição também poderia ter sido escrita como:
auto int num = 10;
Como a definição com ou sem a palavra-chave auto é completamente equivalente, a palavra-chave auto é obviamente bastante redundante.
No entanto, às vezes não é isso que você quer. Suponha que você queira que uma função mantenha a contagem de quantas vezes ela é chamada. Se a variável fosse destruída toda vez que a função retornasse, isso não seria possível.
Portanto, é possível dar à variável o que se chama de duração estática, o que significa que ela permanecerá intacta durante toda a execução do programa. Por exemplo:
static int num = 10;
Isto inicializa a variável num para 10 no início da execução do programa. A partir de então o valor permanecerá inalterado; a variável não será reinicializada se a função for chamada várias vezes.
Às vezes não é suficiente que a variável seja acessível apenas de uma função ou pode não ser conveniente passar o valor por meio de um parâmetro para todas as outras funções que precisam dele.
Mas se você precisar acessar a variável de todas as funções em todo o arquivo de origem, isso também pode ser feito com a palavra-chave static, mas colocando a definição fora de todas as funções. Por exemplo:
#include <stdio.h>
static int num = 10; /* will be accessible from entire source file */
int main(void)
{
printf("The Number Is: %d\n", num);
return 0;
}
E também há casos em que uma variável precisa ser acessível a partir de todo o programa, que pode consistir em vários arquivos de origem. Isso é chamado de variável global e deve ser evitado quando não for necessário.
Isso também é feito colocando a definição fora de todas as funções, mas sem usar a palavra-chave estática:
#include <stdio.h>
int num = 10; /* will be accessible from entire program! */
int main(void)
{
printf("The Number Is: %d\n", num);
return 0;
}
Há também a palavra-chave extern, que é usada para acessar variáveis globais em outros módulos. Há também alguns qualificadores que você pode adicionar às definições de variáveis. O mais importante deles é const. Uma variável definida como const não pode ser modificada.
Existem mais dois modificadores que são menos usados. O modificador volátil e de registro. O modificador volátil requer que o compilador realmente acesse a variável toda vez que ela for lida. Pode não otimizar a variável colocando-a em um registro ou algo assim. Isso é usado principalmente para fins de multithreading e processamento de interrupção, etc.
O modificador de registro solicita que o compilador otimize a variável em um registro. Isso só é possível com variáveis automáticas e em muitos casos o compilador pode selecionar melhor as variáveis para otimizar em registradores, então esta palavra-chave é obsoleta. A única consequência direta de fazer um registro de variável é que seu endereço não pode ser obtido.
A tabela de variáveis, fornecida na próxima página, descreve a classe de armazenamento de cinco tipos de classes de armazenamento.
Na tabela vemos que a palavra-chave extern é colocada em duas linhas. A palavra-chave extern é usada em funções para declarar uma variável externa estática definida em outro lugar.
Tipos de variáveis numéricas
C fornece vários tipos diferentes de variáveis numéricas porque valores numéricos diferentes têm requisitos de armazenamento de memória variados. Esses tipos numéricos diferem na facilidade com que certas operações matemáticas podem ser executadas neles.
Inteiros pequenos requerem menos memória para armazenar, e seu computador pode realizar operações matemáticas com esses números muito rapidamente. Inteiros grandes e valores de ponto flutuante requerem mais espaço de armazenamento e mais tempo para operações matemáticas. Ao usar os tipos de variáveis apropriados, você garante que seu programa seja executado da maneira mais eficiente possível.
As variáveis numéricas de C se enquadram nas duas categorias principais a seguir:
- Variáveis inteiras
- Variáveis de ponto flutuante
Dentro de cada uma dessas categorias há dois ou mais tipos de variáveis específicas. A tabela a seguir mostra a quantidade de memória, em bytes, necessária para manter uma única variável de cada tipo.
O tipo char pode ser equivalente a char assinado ou char não assinado, mas é sempre um tipo separado de qualquer um deles.
Em C não há diferença entre armazenar caracteres ou seus valores numéricos correspondentes em uma variável, portanto também não há necessidade de uma função converter entre um caractere e seu valor numérico ou vice-versa. Para os outros tipos inteiros, se você omitir assinado ou não assinado, o padrão será assinado, por exemplo, int e int assinado são equivalentes.
O tipo int deve ser maior ou igual ao tipo short e menor ou igual ao tipo long. Se você simplesmente precisa armazenar alguns valores que não são muito grandes, geralmente é uma boa idéia usar o tipo int; geralmente é o tamanho com o qual o processador pode lidar com mais facilidade e, portanto, o mais rápido.
Com vários compiladores, double e long double são equivalentes. Isso combinado com o fato de que a maioria das funções matemáticas padrão trabalham com o tipo double, é uma boa razão para sempre usar o tipo double se você tiver que trabalhar com números fracionários.
A tabela a seguir descreve melhor os tipos de variáveis:
Tipos para fins especiais comumente usados:
Variable Type |
Description |
size_t |
unsigned type used for storing the sizes of objects in bytes |
time_t |
used to store results of the time() function |
clock_t |
used to store results of the clock() function |
FILE |
used for accessing a stream (usually a file or device) |
ptrdiff_t |
signed type of the difference between 2 pointers |
div_t |
used to store results of the div() function |
ldiv_t |
used to store results of ldiv() function |
fpos_t |
used to hold file position information |
va_list |
used in variable argument handling |
wchar_t |
wide character type (used for extended character sets) |
sig_atomic_t |
used in signal handlers |
Jmp_buf |
used for non-local jumps |
Para entender melhor essas variáveis vamos dar um exemplo:
/* Programa para informar o intervalo e tamanho em bytes da variável C */
#include <stdio.h>
int main()
{
int a; /* simple integer type */
long int b; /* long integer type */
short int c; /* short integer type */
unsigned int d; /* unsigned integer type */
char e; /* character type */
float f; /* floating point type */
double g; /* double precision floating point */
a = 1023;
b = 2222;
c = 123;
d = 1234;
e = 'X';
f = 3.14159;
g = 3.1415926535898;
printf( "\nA char is %d bytes", sizeof( char ));
printf( "\nAn int is %d bytes", sizeof( int ));
printf( "\nA short is %d bytes", sizeof( short ));
printf( "\nA long is %d bytes", sizeof( long ));
printf( "\nAn unsigned char is %d bytes",
sizeof( unsigned char ));
printf( "\nAn unsigned int is %d bytes",
sizeof( unsigned int ));
printf( "\nAn unsigned short is %d bytes",
sizeof( unsigned short ));
printf( "\nAn unsigned long is %d bytes",
sizeof( unsigned long ));
printf( "\nA float is %d bytes", sizeof( float ));
printf( "\nA double is %d bytes\n", sizeof( double ));
printf("a = %d\n", a); /* decimal output */
printf("a = %o\n", a); /* octal output */
printf("a = %x\n", a); /* hexadecimal output */
printf("b = %ld\n", b); /* decimal long output */
printf("c = %d\n", c); /* decimal short output */
printf("d = %u\n", d); /* unsigned output */
printf("e = %c\n", e); /* character output */
printf("f = %f\n", f); /* floating output */
printf("g = %f\n", g); /* double float output */
printf("\n");
printf("a = %d\n", a); /* simple int output */
printf("a = %7d\n", a); /* use a field width of 7 */
printf("a = %-7d\n", a); /* left justify in
field of 7 */
c = 5;
d = 8;
printf("a = %*d\n", c, a); /* use a field width of 5*/
printf("a = %*d\n", d, a); /* use a field width of 8 */
printf("\n");
printf("f = %f\n", f); /* simple float output */
printf("f = %12f\n", f); /* use field width of 12 */
printf("f = %12.3f\n", f); /* use 3 decimal places */
printf("f = %12.5f\n", f); /* use 5 decimal places */
printf("f = %-12.5f\n", f); /* left justify in field */
return 0;
}
O Resultado do programa após a execução será exibido como:
A char is 1 bytes
An int is 2 bytes
A short is 2 bytes
A long is 4 bytes
An unsigned char is 1 bytes
An unsigned int is 2 bytes
An unsigned short is 2 bytes
An unsigned long is 4 bytes
A float is 4 bytes
A double is 8 bytes
a = 1023
a = 1777
a = 3ff
b = 2222
c = 123
d = 1234
e = X
f = 3.141590
g = 3.141593
a = 1023
a = 1023
a = 1023
a = 1023
a = 1023
f = 3.141590
f = 3.141590
f = 3.142
f = 3.14159
f = 3.14159 |
Antes de seu uso, uma variável em um programa C, deve ser declarada. Uma declaração de variável informa ao compilador o nome e o tipo de uma variável e, opcionalmente, inicializa a variável com um valor específico.
Se seu programa tenta usar uma variável que não foi declarada, o compilador gera uma mensagem de erro. Uma declaração de variável tem o seguinte formato:
typename varname;
typename especifica o tipo de variável e deve ser uma das palavras-chave. varname é o nome da variável. Você pode declarar várias variáveis do mesmo tipo em uma linha separando os nomes das variáveis com vírgulas:
int count, number, start; /* three integer variables */
float percent, total; /* two float variables */
A palavra-chave typedef
A palavra-chave typedef é usada para criar um novo nome para um tipo de dados existente. Na verdade, typedef cria um sinônimo. Por exemplo, a declaração
typedef int inteiro;
aqui vemos que typedef cria integer como sinônimo de int. Você então pode usar integer para definir variáveis do tipo int, como neste exemplo:
contagem de inteiros;
Portanto, typedef não cria um novo tipo de dados, apenas permite que você use um nome diferente para um tipo de dados predefinido.
Inicializando variáveis numéricas
Quando qualquer variável é declarada, o compilador é instruído a reservar espaço de armazenamento para a variável. No entanto, o valor armazenado nesse espaço, o valor da variável, não está definido. Pode ser zero ou pode ser algum "lixo" valor. Antes de usar uma variável, você deve sempre inicializá-la com um valor conhecido. Tomemos este exemplo:
int count; /* Set aside storage space for count */
count = 0; /* Store 0 in count */
Essa instrução usa o sinal de igual (=), que é o operador de atribuição de C. Você também pode inicializar uma variável quando ela é declarada. Para isso, siga o nome da variável na declaração de declaração com um sinal de igual e o valor inicial desejado:
int count = 0;
double rate = 0.01, complexity = 28.5;
Tenha cuidado para não inicializar uma variável com um valor fora do intervalo permitido. Aqui estão dois exemplos de inicializações fora do intervalo:
int amount = 100000;
unsigned int length = -2500;
O compilador C não detecta esses erros. Seu programa pode compilar e vincular, mas você pode obter resultados inesperados quando o programa é executado.
Vamos usar o seguinte exemplo para calcular o número total de setores em um disco:
// Programa modelo para calcular setores em um disco //
#include<stdio.h>
#define SECTOR_PER_SIDE 63
#define SIDE_PER_CYLINDER 254
void main()
{
int cylinder=0;
clrscr();
printf("Enter The No. of Cylinders in the Disk \n\n\t");
scanf("%d",&cylinder); // Get the value from the user //
printf("\n\n\t Total Number of Sectors in the disk = %ld", (long)SECTOR_PER_SIDE*SIDE_PER_CYLINDER* cylinder);
getch();
}
A saída do programa é a seguinte:
Enter The No. of Cylinders in the Disk
1024
Total Number of Sectors in the disk = 16386048
Neste exemplo, vemos três coisas novas para aprender. #define é usado para usar constantes simbólicas no programa ou em alguns casos para economizar tempo definindo palavras longas em pequenos símbolos.
Aqui definimos o número de setores por lado que é 63 como SECTOR_PER_SIDE para facilitar a compreensão do programa. O mesmo caso é verdadeiro para #define SIDE_PER_CYLINDER 254. scanf() é usado para obter a entrada do usuário.
Aqui estamos pegando o número de cilindros como entrada do usuário. * é usado para multiplicar dois ou mais valores, conforme mostrado no exemplo.
A função getch() basicamente obtém uma entrada de caractere único do teclado. Digitando getch(); aqui paramos a tela até que qualquer tecla seja pressionada no teclado.
Operadores
Um operador é um símbolo que instrui C a realizar alguma operação ou ação em um ou mais operandos. Um operando é algo sobre o qual um operador atua. Em C, todos os operandos são expressões. Os operadores C são das seguintes quatro categorias:
- O operador de atribuição
- Operadores matemáticos
- Operadores relacionais
- Operadores lógicos
Operador de atribuição
O operador de atribuição é o sinal de igual (=). O uso do sinal de igual na programação é diferente do seu uso em relações algébricas matemáticas regulares. Se você escrever
x = y;
Em um programa C, isso não significa "x é igual a y" Em vez disso, significa "atribuir o valor de y a x". Em uma instrução de atribuição C, o lado direito pode ser qualquer expressão e o lado esquerdo deve ser um nome de variável. Assim, o formulário é o seguinte:
variável = expressão;
Durante a execução, a expressão é avaliada e o valor resultante é atribuído à variável.
MOperadores atemáticos
Os operadores matemáticos de C realizam operações matemáticas como adição e subtração. C tem dois operadores matemáticos unários e cinco operadores matemáticos binários. Os operadores matemáticos unários são assim chamados porque recebem um único operando. C tem dois operadores matemáticos unários.
Os operadores de incremento e decremento podem ser usados somente com variáveis, não com constantes. A operação realizada é adicionar um ou subtrair um do operando. Em outras palavras, as instruções ++x; e --y; são os equivalentes destas declarações:
x = x + 1;
y = y - 1;
operadores matemáticos binários recebem dois operandos. Os primeiros quatro operadores binários, que incluem as operações matemáticas comuns encontradas em uma calculadora (+, -, *, /), são familiares para você. O quinto operador Modulus retorna o resto quando o primeiro operando é dividido pelo segundo operando. Por exemplo, 11 módulo 4 é igual a 3 (11 é dividido por 4, duas vezes e 3 sobrando).
Operadores Relacionais
Os operadores relacionais de C são usados para comparar expressões. Uma expressão contendo um operador relacional é avaliada como verdadeira (1) ou falsa (0). C tem seis operadores relacionais.
Operadores lógicos
Os operadores lógicos de C permitem combinar duas ou mais expressões relacionais em uma única expressão que é avaliada como verdadeira ou falsa. Os operadores lógicos são avaliados como verdadeiro ou falso, dependendo do valor verdadeiro ou falso de seus operandos.
Se x for uma variável inteira, expressões usando operadores lógicos podem ser escritas das seguintes maneiras:
(x > 1) && (x < 5)
(x >= 2) && (x <= 4)
Operator |
Symbol |
Description |
Example |
Assignment operators |
equal |
= |
assign the value of y to x |
x = y |
Mathematical operators |
Increment |
++ |
Increments the operand by one |
++x, x++ |
Decrement |
-- |
Decrements the operand by one |
--x, x-- |
Addition |
+ |
Adds two operands |
x + y |
Subtraction |
- |
Subtracts the second operand from the first |
x - y |
Multiplication |
* |
Multiplies two operands |
x * y |
Division |
/ |
Divides the first operand by the second operand |
x / y |
Modulus |
% |
Gives the remainder when the first operand is divided by the second operand |
x % y |
Relational operators |
Equal |
= = |
Equality |
x = = y |
Greater than |
> |
Greater than |
x > y |
Less than |
< |
Less than |
x < y |
Greater than or equal to |
>= |
Greater than or equal to |
x >= y |
Less than or equal to |
<= |
Less than or equal to |
x <= y |
Not equal |
!= |
Not equal to |
x != y |
Logical operators |
AND |
&& |
True (1) only if both exp1 and exp2 are true; false (0) otherwise |
exp1 && exp2 |
OR |
|| |
True (1) if either exp1 or exp2 is true; false (0) only if both are false |
exp1 || exp2 |
NOT |
! |
False (0) if exp1 is true; true (1) if exp1 is false |
!exp1 |
Things to remember about logical expressions
x * = y |
is same as |
x = x * y |
y - = z + 1 |
is same as |
y = y - z + 1 |
a / = b |
is same as |
a = a / b |
x + = y / 8 |
is same as |
x = x + y / 8 |
y % = 3 |
is same as |
y = y % 3 |
O operador de vírgula
A vírgula é frequentemente usada em C como um simples sinal de pontuação, para separar declarações de variáveis, argumentos de funções, etc. Em certas situações, a vírgula atua como um operador.
Você pode formar uma expressão separando duas subexpressões com uma vírgula. O resultado é o seguinte:
- Ambas as expressões são avaliadas, com a expressão da esquerda sendo avaliada primeiro.
- A expressão inteira é avaliada como o valor da expressão correta.
Por exemplo, a instrução a seguir atribui o valor de b a x, então incrementa a e então incrementa b:
x = (a++, b++);
Precedência do operador C (Resumo dos operadores C)
Rank and Associativity |
Operators |
1(left to right) |
() [] -> . |
2(right to left) |
! ~ ++ -- * (indirection) & (address-of) (type)
sizeof + (unary) - (unary) |
3(left to right) |
* (multiplication) / % |
4(left to right) |
+ - |
5(left to right) |
<< >> |
6(left to right) |
< <= > >= |
7(left to right) |
= = != |
8(left to right) |
& (bitwise AND) |
9(left to right) |
^ |
10(left to right) |
| |
11(left to right) |
&& |
12(left to right) |
|| |
13(right to left) |
?: |
14(right to left) |
= += -= *= /= %= &= ^= |= <<= >>= |
15(left to right) |
, |
() is the function operator; [] is the array operator. |
|
Vamos dar um exemplo de uso de operadores:
/* Uso de operadores */
int main()
{
int x = 0, y = 2, z = 1025;
float a = 0.0, b = 3.14159, c = -37.234;
/* incrementing */
x = x + 1; /* This increments x */
x++; /* This increments x */
++x; /* This increments x */
z = y++; /* z = 2, y = 3 */
z = ++y; /* z = 4, y = 4 */
/* decrementing */
y = y - 1; /* This decrements y */
y--; /* This decrements y */
--y; /* This decrements y */
y = 3;
z = y--; /* z = 3, y = 2 */
z = --y; /* z = 1, y = 1 */
/* arithmetic op */
a = a + 12; /* This adds 12 to a */
a += 12; /* This adds 12 more to a */
a *= 3.2; /* This multiplies a by 3.2 */
a -= b; /* This subtracts b from a */
a /= 10.0; /* This divides a by 10.0 */
/* conditional expression */
a = (b >= 3.0 ? 2.0 : 10.5 ); /* This expression */
if (b >= 3.0) /* And this expression */
a = 2.0; /* are identical, both */
else /* will cause the same */
a = 10.5; /* result. */
c = (a > b ? a : b); /* c will have the max of a or b */
c = (a > b ? b : a); /* c will have the min of a or b */
printf("x=%d, y=%d, z= %d\n", x, y, z);
printf("a=%f, b=%f, c= %f", a, b, c);
return 0;
}
e o resultado deste programa será exibido na tela como:
x=3, y=1, z=1
a=2.000000, b=3.141590, c=2.000000
Something more about printf() and Scanf()
Consider the following two printf statements
printf(“\t %d\n”, num);
printf(“%5.2f”, fract);
na primeira instrução printf \t solicita o deslocamento de tabulação na tela, o argumento %d informa ao compilador que o valor de num deve ser impresso como inteiro decimal. \n faz com que a nova saída comece a partir de uma nova linha.
Na segunda instrução printf %5.2f informa ao compilador que a saída deve estar em ponto flutuante, com cinco casas no total e duas casas à direita do ponto decimal. Mais sobre o caractere de barra invertida foi mostrado na tabela a seguir:
Constant |
Meaning |
‘\a’ |
Audible alert (bell) |
‘\b’ |
Backspace |
‘\f’ |
Form feed |
‘\n’ |
New line |
‘\r’ |
Carriage return |
‘\t’ |
Horizontal tab |
‘\v’ |
Vertical tab |
‘\’’ |
Single quote |
‘\”’ |
Double quote |
‘\?’ |
Question mark |
‘\\’ |
Backslash |
‘\0’ |
Null |
Vamos considerar a seguinte declaração scanf
scanf(“%d”, &num);
Os dados do teclado são recebidos pela função scanf. No formato acima, o & (e comercial) antes de cada nome de variável é um operador que especifica o endereço do nome da variável.
Ao fazer isso, a execução para e espera que o valor da variável num seja digitado. Quando o valor inteiro é inserido e a tecla de retorno é pressionada, o computador prossegue para a próxima instrução. Os códigos de formato scanf e printf estão listados na tabela a seguir:
Code |
Reads... |
%c |
Single character |
%d |
Decimal integer |
%e |
Floating point value |
%f |
Floating point value |
%g |
Floating point value |
%h |
Short integer |
%i |
Decimal, hexadecimal or octal integer |
%o |
Octal integer |
%s |
String |
%u |
Unsigned decimal integer |
%x |
Hexadecimal integer |
Declarações de controle
Um programa consiste em um número de instruções que normalmente são executadas em sequência. Os programas podem ser muito mais poderosos se pudermos controlar a ordem em que as instruções são executadas.
As declarações se dividem em três tipos gerais:
- Atribuição, onde os valores, geralmente os resultados de cálculos, são armazenados em variáveis.
- Entrada / Saída, os dados são lidos ou impressos.
- Controle, o programa toma uma decisão sobre o que fazer em seguida.
Esta seção discutirá o uso de instruções de controle em C. Mostraremos como elas podem ser usadas para escrever programas poderosos por;
- Repetindo seções importantes do programa.
- Seleção entre seções opcionais de um programa.
A instrução if else
Isso é usado para decidir se deve fazer algo em um ponto especial ou decidir entre dois cursos de ação.
O teste a seguir decide se um aluno foi aprovado em um exame com uma nota de aprovação de 45
if (result >= 45)
printf("Pass\n");
else
printf("Fail\n");
It is possible to use the if part without the else.
if (temperature < 0)
print("Frozen\n");
Cada versão consiste em um teste, na declaração entre colchetes após o if. Se o teste for verdadeiro, então a próxima afirmação é obedecida. Se for falso, a instrução que segue o else é obedecida, se presente. Depois disso, o resto do programa continua normalmente.
Se desejarmos ter mais de uma instrução após o if ou o else, eles devem ser agrupados entre colchetes. Esse agrupamento é chamado de instrução composta ou bloco.
if (result >= 45)
{ printf("Passed\n");
printf("Congratulations\n");
}
else
{ printf("Failed\n");
printf("Better Luck Next Time\n");
}
Às vezes, desejamos tomar uma decisão multifacetada com base em várias condições. A maneira mais geral de fazer isso é usando a variante else if na instrução if.
Isso funciona com várias comparações em cascata. Assim que um deles fornece um resultado verdadeiro, a instrução ou bloco a seguir é executado e nenhuma outra comparação é realizada. No exemplo a seguir, estamos atribuindo notas dependendo do resultado do exame.
if (result <=100 && result >= 75)
printf("Passed: Grade A\n");
else if (result >= 60)
printf("Passed: Grade B\n");
else if (result >= 45)
printf("Passed: Grade C\n");
else
printf("Failed\n");
Neste exemplo, todas as comparações testam uma única variável chamada resultado. Em outros casos, cada teste pode envolver uma variável diferente ou alguma combinação de testes. O mesmo padrão pode ser usado com mais ou menos if's, e apenas o else final pode ser deixado de fora.
Cabe ao programador conceber a estrutura correta para cada problema de programação. Para entender melhor o uso de if else vamos ver o exemplo
#include <stdio.h>
int main()
{
int num;
for(num = 0 ; num < 10 ; num = num + 1)
{
if (num == 2)
printf("num is now equal to %d\n", num);
if (num < 5)
printf("num is now %d, which is less than 5\n", num);
else
printf("num is now %d, which is greater than 4\n", num);
} /* end of for loop */
return 0;
}
Resultado do programa
num is now 0, which is less than 5
num is now 1, which is less than 5
num is now equal to 2
num is now 2, which is less than 5
num is now 3, which is less than 5
num is now 4, which is less than 5
num is now 5, which is greater than 4
num is now 6, which is greater than 4
num is now 7, which is greater than 4
num is now 8, which is greater than 4
num is now 9, which is greater than 4
A instrução switch
Esta é outra forma de decisão multifacetada. Está bem estruturado, mas só pode ser usado em certos casos em que;
- Apenas uma variável é testada, todas as ramificações devem depender do valor dessa variável. A variável deve ser do tipo integral. (int, long, short ou char).
- Cada valor possível da variável pode controlar uma única ramificação. Uma ramificação padrão final, catch all, pode ser usada opcionalmente para interceptar todos os casos não especificados.
O exemplo abaixo esclarecerá as coisas. Esta é uma função que converte um inteiro em uma descrição vaga. É útil quando estamos preocupados apenas em medir uma quantidade quando ela é muito pequena.
estimate(number)
int number;
/* Estimate a number as none, one, two, several, many */
{ switch(number) {
case 0 :
printf("None\n");
break;
case 1 :
printf("One\n");
break;
case 2 :
printf("Two\n");
break;
case 3 :
case 4 :
case 5 :
printf("Several\n");
break;
default :
printf("Many\n");
break;
}
}
Cada caso interessante é listado com uma ação correspondente. A instrução break impede que quaisquer outras instruções sejam executadas ao sair do switch. Como o caso 3 e o caso 4 não têm quebra de sequência, eles continuam permitindo a mesma ação para vários valores de número.
As construções if e switch permitem que o programador faça uma seleção de várias ações possíveis. Vejamos um exemplo:
#include <stdio.h>
int main()
{
int num;
for (num = 3 ; num < 13 ; num = num + 1)
{
switch (num)
{
case 3 :
printf("The value is three\n");
break;
case 4 :
printf("The value is four\n");
break;
case 5 :
case 6 :
case 7 :
case 8 :
printf("The value is between 5 and 8\n");
break;
case 11 :
printf("The value is eleven\n");
break;
default :
printf("It is one of the undefined values\n");
break;
} /* end of switch */
} /* end of for loop */
return 0;
}
A saída do programa será
The value is three
The value is four
The value is between 5 and 8
The value is between 5 and 8
The value is between 5 and 8
The value is between 5 and 8
It is one of the undefined values
It is one of the undefined values
The value is eleven
It is one of the undefined values
A instrução break
Já encontramos break na discussão da instrução switch. É usado para sair de um loop ou switch, controle passando para a primeira instrução além do loop ou switch.
Com loops, break pode ser usado para forçar uma saída antecipada do loop ou para implementar um loop com um teste para sair no meio do corpo do loop. Uma quebra dentro de um loop deve sempre ser protegida dentro de uma instrução if que fornece o teste para controlar a condição de saída.
A declaração continue
Isso é semelhante ao break, mas é encontrado com menos frequência. Ele só funciona dentro de loops onde seu efeito é forçar um salto imediato para a instrução de controle de loop.
- Em um loop while, pule para a instrução de teste.
- Em um loop do while, vá para a instrução test.
- Em um loop for, pule para o teste e execute a iteração.
Como uma pausa, continue deve ser protegido por uma instrução if. É improvável que você o use com muita frequência. Para entender melhor o uso de break e continue, vamos examinar o seguinte programa:
#include <stdio.h>
int main()
{
int value;
for(value = 5 ; value < 15 ; value = value + 1)
{
if (value == 8)
break;
printf("In the break loop, value is now %d\n", value);
}
for(value = 5 ; value < 15 ; value = value + 1)
{
if (value == 8)
continue;
printf("In the continue loop, value is now %d\n", value);
}
return 0;
}
A saída do programa será a seguinte:
In the break loop, value is now 5
In the break loop, value is now 6
In the break loop, value is now 7
In the continue loop, value is now 5
In the continue loop, value is now 6
In the continue loop, value is now 7
In the continue loop, value is now 9
In the continue loop, value is now 10
In the continue loop, value is now 11
In the continue loop, value is now 12
In the continue loop, value is now 13
In the continue loop, value is now 14
Loops
O outro tipo principal de instrução de controle é o loop. Os loops permitem que uma instrução ou bloco de instruções seja repetido. Os computadores são muito bons em repetir tarefas simples muitas vezes. O loop é a maneira de C conseguir isso.
C oferece três tipos de loop: while, do-while e for.
- O loop while continua repetindo uma ação até que um teste associado retorne falso. Isso é útil quando o programador não sabe de antemão quantas vezes o loop será percorrido.
- Os loops do while são semelhantes, mas o teste ocorre após a execução do corpo do loop. Isso garante que o corpo do loop seja executado pelo menos uma vez.
- O loop for é frequentemente usado, geralmente onde o loop será percorrido um número fixo de vezes. É muito flexível, e programadores iniciantes devem tomar cuidado para não abusar do poder que ele oferece.
O loop while
O loop while repete uma instrução até que o teste no topo seja falso. Como exemplo, aqui está uma função para retornar o comprimento de uma string. Lembre-se de que a string é representada como uma matriz de caracteres terminada por um caractere nulo '\0'.
int string_length(char string[])
{ int i = 0;
while (string[i] != '\0')
i++;
return(i);
}
A string é passada para a função como um argumento. O tamanho do array não é especificado, a função funcionará para uma string de qualquer tamanho.
O loop while é usado para examinar os caracteres na string, um de cada vez, até que o caractere nulo seja encontrado. Em seguida, o loop é encerrado e o índice do nulo é retornado.
Enquanto o caractere não é nulo, o índice é incrementado e o teste é repetido. Vamos aprofundar os arrays mais tarde. Vejamos um exemplo de loop while:
#include <stdio.h>
int main()
{
int count;
count = 0;
while (count < 6)
{
printf("The value of count is %d\n", count);
count = count + 1;
}
return 0;
}
e o resultado é exibido da seguinte forma:
The value of count is 0
The value of count is 1
The value of count is 2
The value of count is 3
The value of count is 4
The value of count is 5
O loop do while
Isso é muito semelhante ao loop while, exceto que o teste ocorre no final do corpo do loop. Isso garante que o loop seja executado pelo menos uma vez antes de continuar.
Essa configuração é frequentemente usada onde os dados devem ser lidos. O teste então verifica os dados e faz um loop de volta para ler novamente se for inaceitável.
do
{
printf("Enter 1 for yes, 0 for no :");
scanf("%d", &input_value);
} while (input_value != 1 && input_value != 0)
Para entender melhor o loop do while vamos ver o seguinte exemplo:
#include <stdio.h>
int main()
{
int i;
i = 0;
do
{
printf("The value of i is now %d\n", i);
i = i + 1;
} while (i < 5);
return 0;
}
O resultado do programa é exibido da seguinte forma:
The value of i is now 0
The value of i is now 1
The value of i is now 2
The value of i is now 3
The value of i is now 4
O loop for
O loop for funciona bem onde o número de iterações do loop é conhecido antes que o loop seja inserido. A cabeça do loop consiste em três partes separadas por ponto e vírgula.
- O primeiro é executado antes que o loop seja inserido. Isso geralmente é a inicialização da variável de loop.
- O segundo é um teste, o loop é encerrado quando retorna falso.
- A terceira é uma instrução a ser executada toda vez que o corpo do loop for concluído. Isso geralmente é um incremento do contador de loops.
O exemplo é uma função que calcula a média dos números armazenados em um array. A função recebe o array e o número de elementos como argumentos.
float average(float array[], int count)
{
float total = 0.0;
int i;
for(i = 0; i < count; i++)
total += array[i];
return(total / count);
}
O loop for garante que o número correto de elementos da matriz seja somado antes de calcular a média.
As três instruções no início de um loop for geralmente fazem apenas uma coisa cada, porém qualquer uma delas pode ser deixada em branco. Uma primeira ou última instrução em branco significará nenhuma inicialização ou incremento em execução. Uma declaração de comparação em branco sempre será tratada como verdadeira. Isso fará com que o loop seja executado indefinidamente, a menos que seja interrompido por algum outro meio. Pode ser uma instrução return ou break.
Também é possível espremer várias declarações na primeira ou terceira posição, separando-as com vírgulas. Isso permite um loop com mais de uma variável de controle. O exemplo abaixo ilustra a definição de tal loop, com as variáveis hi e lo começando em 100 e 0 respectivamente e convergindo.
O loop for fornece uma variedade de atalhos para serem usados nele. Observe a seguinte expressão, nesta expressão o loop único contém dois loops for nele. Aqui hi-- é o mesmo que hi = hi - 1 e lo++ é o mesmo que lo = lo + 1,
for(hi = 100, lo = 0; hi >= lo; hi--, lo++)
O loop for é extremamente flexível e permite que muitos tipos de comportamento do programa sejam especificados de forma simples e rápida. Vamos ver um exemplo de loop for
#include <stdio.h>
int main()
{
int index;
for(index = 0 ; index < 6 ; index = index + 1)
printf("The value of the index is %d\n", index);
return 0;
}
O resultado do programa é exibido da seguinte forma:
The value of the index is 0
The value of the index is 1
The value of the index is 2
The value of the index is 3
The value of the index is 4
The value of the index is 5
A declaração goto
C tem um comando goto que permite saltos não estruturados. Para usar uma instrução goto, basta usar a palavra reservada goto seguida do nome simbólico para o qual você deseja pular. O nome é então colocado em qualquer lugar no programa seguido por dois pontos. Você pode pular praticamente em qualquer lugar dentro de uma função, mas não tem permissão para pular em um loop, embora tenha permissão para pular fora de um loop.
Este programa em particular é realmente uma bagunça, mas é um bom exemplo de por que os criadores de software estão tentando eliminar o uso da instrução goto o máximo possível. O único lugar neste programa onde é razoável usar o goto é onde o programa salta dos três loops aninhados em um salto. Nesse caso, seria bastante confuso configurar uma variável e pular sucessivamente para fora de cada um dos três loops aninhados, mas uma instrução goto tira você de todos os três de uma maneira muito concisa.
Algumas pessoas dizem que a instrução goto nunca deve ser usada em nenhuma circunstância, mas isso é um pensamento tacanho. Se houver um lugar onde um goto claramente fará um fluxo de controle mais organizado do que alguma outra construção, sinta-se à vontade para usá-lo, no entanto, como está no resto do programa em seu monitor. Vejamos o exemplo:
#include <stdio.h>
int main()
{
int dog, cat, pig;
goto real_start;
some_where:
printf("This is another line of the mess.\n");
goto stop_it;
/* the following section is the only section with a useable goto */
real_start:
for(dog = 1 ; dog < 6 ; dog = dog + 1)
{
for(cat = 1 ; cat < 6 ; cat = cat + 1)
{
for(pig = 1 ; pig < 4 ; pig = pig + 1)
{
printf("Dog = %d Cat = %d Pig = %d\n", dog, cat, pig);
if ((dog + cat + pig) > 8 ) goto enough;
}
}
}
enough: printf("Those are enough animals for now.\n");
/* this is the end of the section with a useable goto statement */
printf("\nThis is the first line of the code.\n");
goto there;
where:
printf("This is the third line of the code.\n");
goto some_where;
there:
printf("This is the second line of the code.\n");
goto where;
stop_it:
printf("This is the last line of this mess.\n");
return 0;
}
Vamos ver os resultados exibidos
Dog = 1 Cat = 1 Pig = 1
Dog = 1 Cat = 1 Pig = 2
Dog = 1 Cat = 1 Pig = 3
Dog = 1 Cat = 2 Pig = 1
Dog = 1 Cat = 2 Pig = 2
Dog = 1 Cat = 2 Pig = 3
Dog = 1 Cat = 3 Pig = 1
Dog = 1 Cat = 3 Pig = 2
Dog = 1 Cat = 3 Pig = 3
Dog = 1 Cat = 4 Pig = 1
Dog = 1 Cat = 4 Pig = 2
Dog = 1 Cat = 4 Pig = 3
Dog = 1 Cat = 5 Pig = 1
Dog = 1 Cat = 5 Pig = 2
Dog = 1 Cat = 5 Pig = 3
Those are enough animals for now.
This is the first line of the code.
This is the second line of the code.
This is the third line of the code.
This is another line of the mess.
This is the last line of this mess.
Ponteiros
Às vezes, queremos saber onde uma variável reside na memória. Um ponteiro contém o endereço de uma variável que possui um valor específico. Ao declarar um ponteiro, um asterisco é colocado imediatamente antes do nome do ponteiro .
O endereço do local de memória onde a variável está armazenada pode ser encontrado colocando um e comercial na frente do nome da variável.
int num; /* Normal integer variable */
int *numPtr; /* Pointer to an integer variable */
O exemplo a seguir imprime o valor da variável e o endereço na memória dessa variável.
printf("O valor %d está armazenado no endereço %X\n", num, &num);
Para atribuir o endereço da variável num ao ponteiro numPtr, você atribui o endereço da variável, num, como no exemplo dado a seguir:
numPtr = #
Para descobrir o que está armazenado no endereço apontado por numPtr, a variável precisa ser desreferenciada. A desreferenciação é feita com o asterisco com o qual o ponteiro foi declarado.
printf("O valor %d está armazenado no endereço %X\n", *numPtr, numPtr);
Todas as variáveis em um programa residem na memória. As instruções fornecidas a seguir solicitam que o compilador reserve 4 bytes de memória em um computador de 32 bits para a variável de ponto flutuante x e, em seguida, coloque o valor 6,5 nela.
float x;
x = 6.5;
Como a localização do endereço na memória de qualquer variável é obtida colocando-se o operador & antes de seu nome, portanto, &x é o endereço de x. C nos permite ir um estágio adiante e definir uma variável, chamada de ponteiro que contém o endereço de outras variáveis. Em vez disso, podemos dizer que o ponteiro aponta para outra variável. Por exemplo:
float x;
float* px;
x = 6.5;
px = &x;
define px como um ponteiro para objetos do tipo float e o define igual ao endereço de x. Assim, *px se refere ao valor de x:
Examinemos as seguintes afirmações:
int var_x;
int* ptrX;
var_x = 6;
ptrX = &var_x;
*ptrX = 12;
printf("value of x : %d", var_x);
A primeira linha faz com que o compilador reserve um espaço na memória para um inteiro. A segunda linha diz ao compilador para reservar espaço para armazenar um ponteiro.
Um ponteiro é um local de armazenamento para um endereço. A terceira linha deve lembrá-lo das instruções scanf. O endereço "&" O operador diz ao compilador para ir ao local onde armazenou var_x e, em seguida, fornecer o endereço do local de armazenamento para ptrX.
O asterisco * na frente de uma variável diz ao compilador para desreferenciar o ponteiro e ir para a memória. Em seguida, você pode fazer atribuições à variável armazenada nesse local. Você pode fazer referência a uma variável e acessar seus dados por meio de um ponteiro. Vejamos um exemplo de ponteiros:
/* illustration of pointer use */
#include <stdio.h>
int main()
{
int index, *pt1, *pt2;
index = 39; /* any numerical value */
pt1 = &index; /* the address of index */
pt2 = pt1;
printf("The value is %d %d %d\n", index, *pt1, *pt2);
*pt1 = 13; /* this changes the value of index */
printf("The value is %d %d %d\n", index, *pt1, *pt2);
return 0;
}
A saída do programa será exibida da seguinte forma:
The value is 39 39 39
The value is 13 13 13
Vejamos outro exemplo para entender melhor o uso de ponteiros:
#include <stdio.h>
#include <string.h>
int main()
{
char strg[40], *there, one, two;
int *pt, list[100], index;
strcpy(strg, "This is a character string.");
/* the function strcpy() is to copy one string to another. we’ll read about strcpy() function in String Section later */
one = strg[0]; /* one and two are identical */
two = *strg;
printf("The first output is %c %c\n", one, two);
one = strg[8]; /* one and two are identical */
two = *(strg+8);
printf("The second output is %c %c\n", one, two);
there = strg+10; /* strg+10 is identical to &strg[10] */
printf("The third output is %c\n", strg[10]);
printf("The fourth output is %c\n", *there);
for (index = 0 ; index < 100 ; index++)
list[index] = index + 100;
pt = list + 27;
printf("The fifth output is %d\n", list[27]);
printf("The sixth output is %d\n", *pt);
return 0;
}
A saída do programa será assim:
The first output is T T
The second output is a a
The third output is c
The fourth output is c
The fifth output is 127
The sixth output is 127
Matrizes
Um array é uma coleção de variáveis do mesmo tipo. Os elementos individuais do array são identificados por um índice inteiro. Em C, o índice começa em zero e é sempre escrito entre colchetes.
Já encontramos arrays unidimensionais que são declarados assim
resultados int[20];
Os arrays podem ter mais dimensões, caso em que podem ser declarados como
int results_2d[20][5];
int results_3d[20][5][3];
Cada índice tem seu próprio conjunto de colchetes. Uma matriz é declarada na função principal, geralmente possui detalhes de dimensões incluídos. É possível usar outro tipo chamado ponteiro no lugar de uma matriz. Isso significa que as dimensões não são fixadas imediatamente, mas o espaço pode ser alocado conforme necessário. Esta é uma técnica avançada que só é necessária em determinados programas especializados.
Como exemplo, aqui está uma função simples para somar todos os inteiros em uma matriz de dimensão única.
int add_array(int array[], int size)
{
int i;
int total = 0;
for(i = 0; i < size; i++)
total += array[i];
return(total);
}
O programa dado a seguir fará uma string, acessará alguns dados nela, imprimirá. Acesse-o novamente usando ponteiros e imprima a string. Deve imprimir "Oi!" e “012345678” em linhas diferentes. Vejamos a codificação do programa:
#include <stdio.h>
#define STR_LENGTH 10
void main()
{
char Str[STR_LENGTH];
char* pStr;
int i;
Str[0] = 'H';
Str[1] = 'i';
Str[2] = '!';
Str[3] = '\0'; // special end string character NULL
printf("The string in Str is : %s\n", Str);
pStr = &Str[0];
for (i = 0; i < STR_LENGTH; i++)
{
*pStr = '0'+i;
pStr++;
}
Str[STR_LENGTH-1] = '\0';
printf("The string in Str is : %s\n", Str);
}
[] (colchetes) são usados para declarar a matriz. A linha do programa char Str[STR_LENGTH]; declara um array de dez caracteres. Estes são dez caracteres individuais, que são todos reunidos na memória no mesmo lugar. Todos eles podem ser acessados através de nosso nome de variável Str junto com um [n] onde n é o número do elemento.
Deve-se sempre ter em mente quando se fala em array que quando C declara um array de dez, os elementos que você pode acessar são numerados de 0 a 9. Acessar o primeiro elemento corresponde a acessar o elemento 0. Portanto, no caso de Arrays, sempre conte de 0 ao tamanho do array - 1.
Em seguida, observe que colocamos as letras "Oi!" na matriz, mas depois colocamos um '\0', você provavelmente está se perguntando o que é isso. "\0" significa NULL e representa o fim da string. Todas as cadeias de caracteres precisam terminar com este caractere especial '\0'. Se não, e então alguém chama printf na string, então printf começaria no local de memória da sua string e continuaria imprimindo dizendo que encontra '\0' e, assim, você acabará com um monte de lixo no final de sua corda. Portanto, certifique-se de encerrar suas strings corretamente.
Matrizes de caracteres
Uma constante de string , como
"Sou uma string"
é uma matriz de caracteres. Ele é representado internamente em C pelos caracteres ASCII na string, ou seja, “I”, em branco, “a”, “m”,… ou a string acima, e terminada pelo caractere nulo especial “\0” para que os programas possam encontre o final da string.
As constantes de string são frequentemente usadas para tornar a saída do código inteligível usando printf:
printf("Hello, world\n");
printf("The value of a is: %f\n", a);
Constantes de string podem ser associadas a variáveis. C fornece a variável de tipo de caractere, que pode conter um caractere (1 byte) por vez. Uma cadeia de caracteres é armazenada em uma matriz de tipo de caractere, um caractere ASCII por local.
Nunca esqueça que, como as strings são terminadas convencionalmente pelo caractere nulo “\0”, exigimos um local de armazenamento extra na matriz.
C não fornece nenhum operador que manipule strings inteiras de uma só vez. Strings são manipuladas por meio de ponteiros ou rotinas especiais disponíveis na biblioteca de strings padrão string.h.
Usar ponteiros de caracteres é relativamente fácil, pois o nome de um array é apenas um ponteiro para seu primeiro elemento. Considere o programa dado a seguir:
#include<stdio.h>
void main()
{
char text_1[100], text_2[100], text_3[100];
char *ta, *tb;
int i;
/* set message to be an arrray */
/* of characters; initialize it */
/* to the constant string "..." */
/* let the compiler decide on */
/* its size by using [] */
char message[] = "Hello, I am a string; what are
you?";
printf("Original message: %s\n", message);
/* copy the message to text_1 */
i=0;
while ( (text_1[i] = message[i]) != '\0' )
i++;
printf("Text_1: %s\n", text_1);
/* use explicit pointer arithmetic */
ta=message;
tb=text_2;
while ( ( *tb++ = *ta++ ) != '\0' )
;
printf("Text_2: %s\n", text_2);
}
A saída do programa será a seguinte:
Original message: Hello, I am a string; what are you?
Text_1: Hello, I am a string; what are you?
Text_2: Hello, I am a string; what are you?
A biblioteca padrão “string” contém muitas funções úteis para manipular strings, que aprenderemos na seção string mais tarde.
Acessando os elementos
Para acessar um elemento individual no array, o número do índice segue o nome da variável entre colchetes. A variável pode então ser tratada como qualquer outra variável em C. O exemplo a seguir atribui um valor ao primeiro elemento na matriz.
x[0] = 16;
O exemplo a seguir imprime o valor do terceiro elemento em uma matriz.
printf("%d\n", x[2]);
O exemplo a seguir usa a função scanf para ler um valor do teclado no último elemento de uma matriz com dez elementos.
scanf("%d", &x[9]);
Inicializando elementos da matriz
Arrays podem ser inicializados como qualquer outra variável por atribuição. Como uma matriz contém mais de um valor, os valores individuais são colocados entre chaves e separados por vírgulas. O exemplo a seguir inicializa uma matriz de dez dimensões com os dez primeiros valores da tabela de três vezes.
int x[10] = {3, 6, 9, 12, 15, 18, 21, 24, 27, 30};
Isso evita atribuir os valores individualmente, como no exemplo a seguir.
int x[10];
x[0] = 3;
x[1] = 6;
x[2] = 9;
x[3] = 12;
x[4] = 15;
x[5] = 18;
x[6] = 21;
x[7] = 24;
x[8] = 27;
x[9] = 30;
Fazendo um loop em uma matriz
Como o array é indexado sequencialmente, podemos usar o loop for para exibir todos os valores de um array. O exemplo a seguir exibe todos os valores de uma matriz:
#include <stdio.h>
int main()
{
int x[10];
int counter;
/* Randomise the random number generator */
srand((unsigned)time(NULL));
/* Assign random values to the variable */
for (counter=0; counter<10; counter++)
x[counter] = rand();
/* Display the contents of the array */
for (counter=0; counter<10; counter++)
printf("element %d has the value %d\n", counter, x[counter]);
return 0;
}
embora a saída imprima os diferentes valores todas as vezes, o resultado será exibido mais ou menos assim:
element 0 has the value 17132
element 1 has the value 24904
element 2 has the value 13466
element 3 has the value 3147
element 4 has the value 22006
element 5 has the value 10397
element 6 has the value 28114
element 7 has the value 19817
element 8 has the value 27430
element 9 has the value 22136
Matrizes multidimensionais
Um array pode ter mais de uma dimensão. Ao permitir que o array tenha mais de uma dimensão, proporciona maior flexibilidade. Por exemplo, planilhas são construídas em uma matriz bidimensional; uma matriz para as linhas e uma matriz para as colunas.
O exemplo a seguir usa uma matriz bidimensional com duas linhas, cada uma contendo cinco colunas:
#include <stdio.h>
int main()
{
/* Declare a 2 x 5 multidimensional array */
int x[2][5] = { {1, 2, 3, 4, 5},
{2, 4, 6, 8, 10} };
int row, column;
/* Display the rows */
for (row=0; row<2; row++)
{
/* Display the columns */
for (column=0; column<5; column++)
printf("%d\t", x[row][column]);
putchar('\n');
}
return 0;
}
A saída deste programa será exibida da seguinte forma:
1 2 3 4 5
2 4 6 8 10
Cadeias
Uma string é um grupo de caracteres, geralmente letras do alfabeto, para formatar sua exibição de impressão de forma que fique bonita, tenha nomes e títulos significativos e seja esteticamente agradável para você e as pessoas que usam o saída do seu programa.
Na verdade, você já usou strings nos exemplos dos tópicos anteriores. Mas não é a introdução completa das cordas. Existem muitos casos possíveis na programação, onde o uso de strings formatadas ajuda o programador a evitar muitas complicações no programa e muitos bugs, é claro.
Uma definição completa de uma string é uma série de dados de tipo de caractere terminados por um caractere nulo ('\0').
Quando C vai usar uma string de dados de alguma forma, seja para compará-la com outra string, produzi-la, copiá-la para outra string ou qualquer outra coisa, as funções são configuradas para fazer o que são chamadas a fazer até que um nulo seja detectado.
Não existe um tipo de dado básico para uma string em C Instead; strings em C são implementadas como uma matriz de caracteres. Por exemplo, para armazenar um nome, você pode declarar uma matriz de caracteres grande o suficiente para armazenar o nome e, em seguida, usar as funções de biblioteca apropriadas para manipular o nome.
O exemplo a seguir exibe a string na tela, inserida pelo usuário:
#include <stdio.h>
int main()
{
char name[80]; /* Create a character array
called name */
printf("Enter your name: ");
gets(name);
printf("The name you entered was %s\n", name);
return 0;
}
A execução do programa será:
Enter your name: Tarun Tyagi
The name you entered was Tarun Tyagi
Algumas funções comuns de string
A biblioteca padrão string.h contém muitas funções úteis para manipular strings. Algumas das funções mais úteis foram exemplificadas aqui.
A função strlen
A função strlen é usada para determinar o comprimento de uma string. Vamos aprender o uso de strlen com exemplo:
#include <stdio.h>
#include <string.h>
int main()
{
char name[80];
int length;
printf("Enter your name: ");
gets(name);
length = strlen(name);
printf("Your name has %d characters\n", length);
return 0;
}
E a execução do programa será a seguinte:
Enter your name: Tarun Subhash Tyagi
Your name has 19 characters
Enter your name: Preeti Tarun
Your name has 12 characters
A função strcpy
A função strcpy é usada para copiar uma string para outra. Vamos aprender o uso desta função com o exemplo:
#include <stdio.h>
#include <string.h>
int main()
{
char first[80];
char second[80];
printf("Enter first string: ");
gets(first);
printf("Enter second string: ");
gets(second);
printf("first: %s, and second: %s Before strcpy()\n "
, first, second);
strcpy(second, first);
printf("first: %s, and second: %s After strcpy()\n",
first, second);
return 0;
}
e a saída do programa será como:
Enter first string: Tarun
Enter second string: Tyagi
first: Tarun, and second: Tyagi Before strcpy()
first: Tarun, and second: Tarun After strcpy()
A função strcmp
A função strcmp é usada para comparar duas strings. O nome da variável de um array aponta para o endereço base desse array. Portanto, se tentarmos comparar duas strings usando o seguinte, estaríamos comparando dois endereços, o que obviamente nunca seria o mesmo, pois não é possível armazenar dois valores no mesmo local.
if (primeiro == segundo) /* Nunca pode ser feito para comparar strings */
O exemplo a seguir usa a função strcmp para comparar duas strings:
#include <string.h>
int main()
{
char first[80], second[80];
int t;
for(t=1;t<=2;t++)
{
printf("\nEnter a string: ");
gets(first);
printf("Enter another string: ");
gets(second);
if (strcmp(first, second) == 0)
puts("The two strings are equal");
else
puts("The two strings are not equal");
}
return 0;
}
E a execução do programa será a seguinte:
Enter a string: Tarun
Enter another string: tarun
The two strings are not equal
Enter a string: Tarun
Enter another string: Tarun
The two strings are equal
A função strcat
A função strcat é usada para unir uma string a outra. Vamos ver como? Com a ajuda do exemplo:
#include <string.h>
int main()
{
char first[80], second[80];
printf("Enter a string: ");
gets(first);
printf("Enter another string: ");
gets(second);
strcat(first, second);
printf("The two strings joined together: %s\n",
first);
return 0;
}
E a execução do programa será a seguinte:
Enter a string: Data
Enter another string: Recovery
The two strings joined together: DataRecovery
A função strtok
A função strtok é usada para localizar o próximo token em uma string. O token é especificado por uma lista de delimitadores possíveis.
O exemplo a seguir lê uma linha de texto de um arquivo e determina uma palavra usando os delimitadores, espaço, tabulação e nova linha. Cada palavra é então exibida em uma linha separada:
#include <stdio.h>
#include <string.h>
int main()
{
FILE *in;
char line[80];
char *delimiters = " \t\n";
char *token;
if ((in = fopen("C:\\text.txt", "r")) == NULL)
{
puts("Unable to open the input file");
return 0;
}
/* Read each line one at a time */
while(!feof(in))
{
/* Get one line */
fgets(line, 80, in);
if (!feof(in))
{
/* Break the line up into words */
token = strtok(line, delimiters);
while (token != NULL)
{
puts(token);
/* Get the next word */
token = strtok(NULL, delimiters);
}
}
}
fclose(in);
return 0;
}
O programa acima, em = fopen("C:\\text.txt", "r"), abre e o arquivo existente C:\\text.txt. Se o não existir no caminho especificado ou por qualquer motivo, o arquivo não puder ser aberto, uma mensagem de erro será exibida na tela.
Considere o exemplo a seguir, que usa algumas dessas funções:
#include <stdio.h>
#include <string.h>
void main()
{
char line[100], *sub_text;
/* initialize string */
strcpy(line,"hello, I am a string;");
printf("Line: %s\n", line);
/* add to end of string */
strcat(line," what are you?");
printf("Line: %s\n", line);
/* find length of string */
/* strlen brings back */
/* length as type size_t */
printf("Length of line: %d\n", (int)strlen(line));
/* find occurence of substrings */
if ( (sub_text = strchr ( line, 'W' ) )!= NULL )
printf("String starting with \"W\" ->%s\n",
sub_text);
if ( ( sub_text = strchr ( line, 'w' ) )!= NULL )
printf("String starting with \"w\" ->%s\n",
sub_text);
if ( ( sub_text = strchr ( sub_text, 'u' ) )!= NULL )
printf("String starting with \"w\" ->%s\n",
sub_text);
}
A saída do programa será exibida da seguinte forma:
Line: hello, I am a string;
Line: hello, I am a string; what are you?
Length of line: 35
String starting with "w" ->what are you?
String starting with "w" ->u?
Funções
A melhor maneira de desenvolver e manter um programa grande é construí-lo a partir de partes menores, cada uma das quais mais fáceis de gerenciar (uma técnica às vezes chamada de Dividir e Conquistar). As funções permitem que o programador modularize o programa.
As funções permitem que programas complicados sejam divididos em pequenos blocos, cada um dos quais é mais fácil de escrever, ler e manter. Já encontramos a função main e usamos printf da biblioteca padrão. É claro que podemos criar nossas próprias funções e arquivos de cabeçalho. Uma função tem o seguinte layout:
return-type function-name ( argument list if necessary )
{
local-declarations;
statements ;
return return-value;
}
Se o tipo de retorno for omitido, o padrão C será int. O valor de retorno deve ser do tipo declarado. Todas as variáveis declaradas dentro de funções são chamadas de variáveis locais, pois são conhecidas apenas na função para a qual foram definidas.
Algumas funções têm uma lista de parâmetros que fornece um método de comunicação entre a função e o módulo que chamou a função. Os parâmetros também são variáveis locais, pois não estão disponíveis fora da função. Todos os programas abordados até agora têm main, que é uma função.
Uma função pode simplesmente executar uma tarefa sem retornar nenhum valor, nesse caso ela tem o seguinte layout:
void function-name ( argument list if necessary )
{
local-declarations ;
statements;
}
Os argumentos são sempre passados por valor em chamadas de função C. Isso significa que cópias locais dos valores dos argumentos são passadas para as rotinas. Qualquer alteração feita nos argumentos internamente na função é feita apenas nas cópias locais dos argumentos.
Para alterar ou definir um argumento na lista de argumentos, este argumento deve ser passado como um endereço. Você usa variáveis regulares se a função não alterar os valores desses argumentos. Você DEVE usar ponteiros se a função alterar os valores desses argumentos.
Vamos aprender com exemplos:
#include <stdio.h>
void exchange ( int *a, int *b )
{
int temp;
temp = *a;
*a = *b;
*b = temp;
printf(" From function exchange: ");
printf("a = %d, b = %d\n", *a, *b);
}
void main()
{
int a, b;
a = 5;
b = 7;
printf("From main: a = %d, b = %d\n", a, b);
exchange(&a, &b);
printf("Back in main: ");
printf("a = %d, b = %d\n", a, b);
}
E a saída deste programa será exibida da seguinte forma:
From main: a = 5, b = 7
From function exchange: a = 7, b = 5
Back in main: a = 7, b = 5
Vejamos outro exemplo. O exemplo a seguir usa uma função chamada quadrado que escreve o quadrado dos números entre 1 e 10.
#include <stdio.h>
int square(int x); /* Function prototype */
int main()
{
int counter;
for (counter=1; counter<=10; counter++)
printf("Square of %d is %d\n", counter, square(counter));
return 0;
}
/* Define the function 'square' */
int square(int x)
{
return x * x;
}
A saída deste programa será exibida da seguinte forma:
Square of 1 is 1
Square of 2 is 4
Square of 3 is 9
Square of 4 is 16
Square of 5 is 25
Square of 6 is 36
Square of 7 is 49
Square of 8 is 64
Square of 9 is 81
Square of 10 is 100
A função protótipo quadrado declara uma função que recebe um parâmetro inteiro e retorna um inteiro. Quando o compilador alcança a chamada de função para quadrado no programa principal, ele é capaz de verificar a chamada de função em relação à definição da função.
Quando o programa atinge a linha que chama a função square, o programa salta para a função e executa essa função antes de retomar seu caminho pelo programa principal. Programas que não possuem um tipo de retorno devem ser declarados usando void. Assim, os parâmetros da função podem ser Passar por valor ou Passar por referência.
Uma função recursiva é uma função que chama a si mesma. E esse processo é chamado de recursão.
Funções de passagem por valor
Os parâmetros da função square no exemplo anterior são passados por valor. Isso significa que apenas uma cópia da variável foi passada para a função. Quaisquer alterações no valor não serão refletidas de volta para a função de chamada.
O exemplo a seguir usa passagem por valor e altera o valor do parâmetro passado, que não tem efeito na função de chamada. A função count_down foi declarada como nula, pois não há tipo de retorno.
#include <stdio.h>
void count_down(int x);
int main()
{
int counter;
for (counter=1; counter<=10; counter++)
count_down(counter);
return 0;
}
void count_down(int x)
{
int counter;
for (counter = x; counter > 0; counter--)
{
printf("%d ", x);
x--;
}
putchar('\n');
}
A saída do programa será exibida da seguinte forma:
1
2 1
3 2 1
4 3 2 1
5 4 3 2 1
6 5 4 3 2 1
7 6 5 4 3 2 1
8 7 6 5 4 3 2 1
9 8 7 6 5 4 3 2 1
10 9 8 7 6 5 4 3 2 1
Vejamos outro Exemplo de C Pass By Value para melhor entendê-lo. O exemplo a seguir converte um número entre 1 e 30.000 digitado pelo usuário em palavras.
#include <stdio.h>
void do_units(int num);
void do_tens(int num);
void do_teens(int num);
int main()
{
int num, residue;
do
{
printf("Enter a number between 1 and 30,000: ");
scanf("%d", &num);
} while (num < 1 || num > 30000);
residue = num;
printf("%d in words = ", num);
do_tens(residue/1000);
if (num >= 1000)
printf("thousand ");
residue %= 1000;
do_units(residue/100);
if (residue >= 100)
{
printf("hundred ");
}
if (num > 100 && num%100 > 0)
printf("and ");
residue %=100;
do_tens(residue);
putchar('\n');
return 0;
}
void do_units(int num)
{
switch(num)
{
case 1:
printf("one ");
break;
case 2:
printf("two ");
break;
case 3:
printf("three ");
break;
case 4:
printf("four ");
break;
case 5:
printf("five ");
break;
case 6:
printf("six ");
break;
case 7:
printf("seven ");
break;
case 8:
printf("eight ");
break;
case 9:
printf("nine ");
}
}
void do_tens(int num)
{
switch(num/10)
{
case 1:
do_teens(num);
break;
case 2:
printf("twenty ");
break;
case 3:
printf("thirty ");
break;
case 4:
printf("forty ");
break;
case 5:
printf("fifty ");
break;
case 6:
printf("sixty ");
break;
case 7:
printf("seventy ");
break;
case 8:
printf("eighty ");
break;
case 9:
printf("ninety ");
}
if (num/10 != 1)
do_units(num%10);
}
void do_teens(int num)
{
switch(num)
{
case 10:
printf("ten ");
break;
case 11:
printf("eleven ");
break;
case 12:
printf("twelve ");
break;
case 13:
printf("thirteen ");
break;
case 14:
printf("fourteen ");
break;
case 15:
printf("fifteen ");
break;
case 16:
printf("sixteen ");
break;
case 17:
printf("seventeen ");
break;
case 18:
printf("eighteen ");
break;
case 19:
printf("nineteen ");
}
}
e a saída do programa será a seguinte:
Enter a number between 1 and 30,000: 12345
12345 in words = twelve thousand three hundred and forty five
Chamada por referência
Para fazer uma função chamada por referência, ao invés de passar a variável em si, passe o endereço da variável. O endereço da variável pode ser obtido usando o & operador. O seguinte chama uma função de troca passando o endereço das variáveis em vez dos valores reais.
swap(&x, &y);
Desreferenciamento
O problema que temos agora é que a função swap recebeu o endereço em vez da variável, então precisamos desreferenciar as variáveis para que estejamos olhando para os valores reais em vez dos endereços das variáveis para trocar eles.
A desreferenciação é obtida em C usando a notação de ponteiro (*). Em termos simples, isso significa colocar um * antes de cada variável antes de usá-la para que ela se refira ao valor da variável em vez de seu endereço. O programa a seguir ilustra a passagem por referência para trocar dois valores.
#include <stdio.h>
void swap(int *x, int *y);
int main()
{
int x=6, y=10;
printf("Before the function swap, x = %d and y =
%d\n\n", x, y);
swap(&x, &y);
printf("After the function swap, x = %d and y =
%d\n\n", x, y);
return 0;
}
void swap(int *x, int *y)
{
int temp = *x;
*x = *y;
*y = temp;
}
Vejamos a saída do programa:
Before the function swap, x = 6 and y = 10
After the function swap, x = 10 and y = 6
As funções podem ser recursivas, ou seja, uma função pode chamar a si mesma. Cada chamada para si mesma requer que o estado atual da função seja enviado para a pilha. É importante lembrar esse fato, pois é fácil criar um estouro de pilha, ou seja, a pilha ficou sem espaço para colocar mais dados.
O exemplo a seguir calcula o fatorial de um número usando recursão. Um fatorial é um número multiplicado por todos os outros inteiros abaixo de si mesmo, até 1. Por exemplo, o fatorial do número 6 é:
Fatorial 6 = 6 * 5 * 4 * 3 * 2 * 1
Portanto, o fatorial de 6 é 720. Pode-se ver no exemplo acima que fatorial 6 = 6 * fatorial 5. Da mesma forma, fatorial 5 = 5 * fatorial 4 e assim por diante.
A seguir está a regra geral para calcular números fatoriais.
fatorial(n) = n * fatorial(n-1)
A regra acima termina quando n = 1, pois o fatorial de 1 é 1. Vamos tentar entender melhor com a ajuda do exemplo:
#include <stdio.h>
long int factorial(int num);
int main()
{
int num;
long int f;
printf("Enter a number: ");
scanf("%d", &num);
f = factorial(num);
printf("factorial of %d is %ld\n", num, f);
return 0;
}
long int factorial(int num)
{
if (num == 1)
return 1;
else
return num * factorial(num-1);
}
Vejamos a saída da execução deste programa:
Enter a number: 7
factorial of 7 is 5040
Alocação de memória em C
O compilador C possui uma biblioteca de alocação de memória, definida em malloc.h. A memória é reservada usando a função malloc e retorna um ponteiro para o endereço. Leva um parâmetro, o tamanho da memória necessária em bytes.
O exemplo a seguir aloca espaço para a string "hello world".
ptr = (char *)malloc(strlen("Olá mundo") + 1);
O byte extra é necessário para levar em consideração o caractere de terminação da string, '\0'. O (char *) é chamado de cast e força o tipo de retorno a ser char *.
Como os tipos de dados têm tamanhos diferentes e malloc retorna o espaço em bytes, é uma boa prática por motivos de portabilidade usar o operador sizeof ao especificar um tamanho para alocar.
O exemplo a seguir lê uma string no buffer da matriz de caracteres e, em seguida, aloca a quantidade exata de memória necessária e a copia para uma variável chamada "ptr".
#include <string.h>
#include <malloc.h>
int main()
{
char *ptr, buffer[80];
printf("Enter a string: ");
gets(buffer);
ptr = (char *)malloc((strlen(buffer) + 1) *
sizeof(char));
strcpy(ptr, buffer);
printf("You entered: %s\n", ptr);
return 0;
}
A saída do programa será a seguinte:
Enter a string: India is the best
You entered: India is the best
Realocação de memória
Muitas vezes, enquanto você está programando, é possível realocar memória. Isso é feito com a função realloc. A função realloc recebe dois parâmetros, o endereço base da memória que você deseja redimensionar e a quantidade de espaço que deseja reservar e retorna um ponteiro para o endereço base.
Suponha que reservamos espaço para um ponteiro chamado msg e queremos realocar espaço para a quantidade de espaço que já ocupa, mais o comprimento de outra string, então podemos usar o seguinte.
msg = (char *)realloc(msg, (strlen(msg) + strlen(buffer) + 1)*sizeof(char));
O programa a seguir ilustra o uso de malloc, realloc e free. O usuário insere uma série de strings que são unidas. O programa para de ler strings quando uma string vazia é inserida.
#include <string.h>
#include <malloc.h>
int main()
{
char buffer[80], *msg;
int firstTime=0;
do
{
printf("\nEnter a sentence: ");
gets(buffer);
if (!firstTime)
{
msg = (char *)malloc((strlen(buffer) + 1) *
sizeof(char));
strcpy(msg, buffer);
firstTime = 1;
}
else
{
msg = (char *)realloc(msg, (strlen(msg) +
strlen(buffer) + 1) * sizeof(char));
strcat(msg, buffer);
}
puts(msg);
} while(strcmp(buffer, ""));
free(msg);
return 0;
}
A saída do programa será a seguinte:
Enter a sentence: Once upon a time
Once upon a time
Enter a sentence: there was a king
Once upon a timethere was a king
Enter a sentence: the king was
Once upon a timethere was a kingthe king was
Enter a sentence:
Once upon a timethere was a kingthe king was
Liberando memória
Quando você terminar com a memória que foi alocada, nunca se esqueça de liberar a memória, pois isso liberará recursos e aumentará a velocidade. Para liberar a memória alocada, use a função free.
grátis(ptr);
Estruturas
Além dos tipos de dados básicos, C possui um mecanismo de estrutura que permite agrupar itens de dados relacionados entre si sob um nome comum. Isso é comumente chamado de tipo definido pelo usuário.
A palavra-chave struct inicia a definição da estrutura e uma tag dá o nome exclusivo à estrutura. Os tipos de dados e nomes de variáveis adicionados à estrutura são membros da estrutura. O resultado é um modelo de estrutura que pode ser usado como especificador de tipo. O seguinte é uma estrutura com uma tag de mês.
struct month
{
char name[10];
char abbrev[4];
int days;
};
Um tipo de estrutura geralmente é definido próximo ao início de um arquivo usando uma instrução typedef. typedef define e nomeia um novo tipo, permitindo seu uso em todo o programa. typedef geralmente ocorre logo após as instruções #define e #include em um arquivo.
A palavra-chave typedef pode ser usada para definir uma palavra para se referir à estrutura em vez de especificar a palavra-chave struct com o nome da estrutura. É comum nomear o typedef em letras maiúsculas. Aqui estão os exemplos de definição de estrutura.
typedef struct {
char name[64];
char course[128];
int age;
int year;
} student;
Isso define um novo tipo de estudante As variáveis do tipo estudante podem ser declaradas da seguinte forma.
aluno st_rec;
Observe como isso é semelhante a declarar um int ou float. A variável nome é st_rec, possui membros chamados name, course, age e year. Similarmente,
typedef struct element
{
char data;
struct element *next;
} STACKELEMENT;
A variable of the user defined type struct element may now be declared as follows.
STACKELEMENT *stack;
Considere a seguinte estrutura:
struct student
{
char *name;
int grade;
};
Um ponteiro para struct student pode ser definido como segue.
struct student *hnc;
When accessing a pointer to a structure, the member pointer operator, -> is used instead of the dot operator. To add a grade to a structure,
s.grade = 50;
Você pode atribuir uma nota à estrutura da seguinte maneira.
s->nota = 50;
Assim como acontece com os tipos de dados básicos, se você deseja que as alterações feitas em uma função nos parâmetros passados sejam persistentes, você deve passar por referência (passar o endereço). O mecanismo é exatamente o mesmo que os tipos de dados básicos. Passe o endereço e consulte a variável usando a notação de ponteiro.
Depois de definir a estrutura, você pode declarar uma instância dela e atribuir valores aos membros usando a notação de ponto. O exemplo a seguir ilustra o uso da estrutura do mês.
#include <stdio.h>
#include <string.h>
struct month
{
char name[10];
char abbreviation[4];
int days;
};
int main()
{
struct month m;
strcpy(m.name, "January");
strcpy(m.abbreviation, "Jan");
m.days = 31;
printf("%s is abbreviated as %s and has %d days\n", m.name, m.abbreviation, m.days);
return 0;
}
A saída do programa será a seguinte:
January is abbreviated as Jan and has 31 days
Todos os compiladores ANSI C permitem que você atribua uma estrutura a outra, executando uma cópia de membro. Se tivéssemos estruturas de mês chamadas m1 e m2, poderíamos atribuir os valores de m1 a m2 com o seguinte:
- Estrutura com membros ponteiro.
- A estrutura inicializa.
- Passando uma estrutura para uma função.
- Ponteiros e estruturas.
Estruturas com membros ponteiro em C
Manter strings em um array de tamanho fixo é um uso ineficiente da memória. Uma abordagem mais eficiente seria usar ponteiros. Ponteiros são usados em estruturas exatamente da mesma forma que são usados em definições de ponteiros normais. Vejamos um exemplo:
#include <string.h>
#include <malloc.h>
struct month
{
char *name;
char *abbreviation;
int days;
};
int main()
{
struct month m;
m.name = (char *)malloc((strlen("January")+1) *
sizeof(char));
strcpy(m.name, "January");
m.abbreviation = (char *)malloc((strlen("Jan")+1) *
sizeof(char));
strcpy(m.abbreviation, "Jan");
m.days = 31;
printf("%s is abbreviated as %s and has %d days\n",
m.name, m.abbreviation, m.days);
return 0;
}
A saída do programa será a seguinte:
Janeiro é abreviado como Jan e tem 31 dias
Inicializadores de estrutura em C
Para fornecer um conjunto de valores iniciais para a estrutura, inicializadores podem ser adicionados à declaração de declaração. Como os meses começam em 1, mas os arrays começam em zero em C, um elemento extra na posição zero chamado lixo foi usado no exemplo a seguir.
#include <stdio.h>
#include <string.h>
struct month
{
char *name;
char *abbreviation;
int days;
} month_details[] =
{
"Junk", "Junk", 0,
"January", "Jan", 31,
"February", "Feb", 28,
"March", "Mar", 31,
"April", "Apr", 30,
"May", "May", 31,
"June", "Jun", 30,
"July", "Jul", 31,
"August", "Aug", 31,
"September", "Sep", 30,
"October", "Oct", 31,
"November", "Nov", 30,
"December", "Dec", 31
};
int main()
{
int counter;
for (counter=1; counter<=12; counter++)
printf("%s is abbreviated as %s and has %d days\n",
month_details[counter].name,
month_details[counter].abbreviation,
month_details[counter].days);
return 0;
}
E a saída será exibida da seguinte forma:
January is abbreviated as Jan and has 31 days
February is abbreviated as Feb and has 28 days
March is abbreviated as Mar and has 31 days
April is abbreviated as Apr and has 30 days
May is abbreviated as May and has 31 days
June is abbreviated as Jun and has 30 days
July is abbreviated as Jul and has 31 days
August is abbreviated as Aug and has 31 days
September is abbreviated as Sep and has 30 days
October is abbreviated as Oct and has 31 days
November is abbreviated as Nov and has 30 days
December is abbreviated as Dec and has 31 days
Passando estruturas para funções em C
Estruturas podem ser passadas como um parâmetro para uma função, assim como qualquer um dos tipos de dados básicos. O exemplo a seguir usa uma estrutura chamada date que é passada para uma função isLeapYear para determinar se o ano é bissexto.
Normalmente você passaria apenas o valor do dia, mas toda a estrutura é passada para ilustrar a passagem de estruturas para funções.
#include <stdio.h>
#include <string.h>
struct month
{
char *name;
char *abbreviation;
int days;
} month_details[] =
{
"Junk", "Junk", 0,
"January", "Jan", 31,
"February", "Feb", 28,
"March", "Mar", 31,
"April", "Apr", 30,
"May", "May", 31,
"June", "Jun", 30,
"July", "Jul", 31,
"August", "Aug", 31,
"September", "Sep", 30,
"October", "Oct", 31,
"November", "Nov", 30,
"December", "Dec", 31
};
struct date
{
int day;
int month;
int year;
};
int isLeapYear(struct date d);
int main()
{
struct date d;
printf("Enter the date (eg: 11/11/1980): ");
scanf("%d/%d/%d", &d.day, &d.month, &d.year);
printf("The date %d %s %d is ", d.day,
month_details[d.month].name, d.year);
if (isLeapYear(d) == 0)
printf("not ");
puts("a leap year");
return 0;
}
int isLeapYear(struct date d)
{
if ((d.year % 4 == 0 && d.year % 100 != 0) ||
d.year % 400 == 0)
return 1;
return 0;
}
E a Execução do programa será a seguinte:
Enter the date (eg: 11/11/1980): 9/12/1980
The date 9 December 1980 is a leap year
O exemplo a seguir aloca dinamicamente uma matriz de estruturas para armazenar os nomes e notas dos alunos. As notas são então exibidas de volta ao usuário em ordem crescente.
#include <string.h>
#include <malloc.h>
struct student
{
char *name;
int grade;
};
void swap(struct student *x, struct student *y);
int main()
{
struct student *group;
char buffer[80];
int spurious;
int inner, outer;
int counter, numStudents;
printf("How many students are there in the group: ");
scanf("%d", &numStudents);
group = (struct student *)malloc(numStudents *
sizeof(struct student));
for (counter=0; counter<numStudents; counter++)
{
spurious = getchar();
printf("Enter the name of the student: ");
gets(buffer);
group[counter].name = (char *)malloc((strlen(buffer)+1) * sizeof(char));
strcpy(group[counter].name, buffer);
printf("Enter grade: ");
scanf("%d", &group[counter].grade);
}
for (outer=0; outer<numStudents; outer++)
for (inner=0; inner<outer; inner++)
if (group[outer].grade <
group[inner].grade)
swap(&group[outer], &group[inner]);
puts("The group in ascending order of grades ...");
for (counter=0; counter<numStudents; counter++)
printf("%s achieved Grade %d \n”,
group[counter].name,
group[counter].grade);
return 0;
}
void swap(struct student *x, struct student *y)
{
struct student temp;
temp.name = (char *)malloc((strlen(x->name)+1) *
sizeof(char));
strcpy(temp.name, x->name);
temp.grade = x->grade;
x->grade = y->grade;
x->name = (char *)malloc((strlen(y->name)+1) *
sizeof(char));
strcpy(x->name, y->name);
y->grade = temp.grade;
y->name = (char *)malloc((strlen(temp.name)+1) *
sizeof(char));
strcpy(y->name, temp.name);
}
A execução da saída será a seguinte:
How many students are there in the group: 4
Enter the name of the student: Anuraaj
Enter grade: 7
Enter the name of the student: Honey
Enter grade: 2
Enter the name of the student: Meetushi
Enter grade: 1
Enter the name of the student: Deepti
Enter grade: 4
The group in ascending order of grades ...
Meetushi achieved Grade 1
Honey achieved Grade 2
Deepti achieved Grade 4
Anuraaj achieved Grade 7
União
Uma união permite que você veja os mesmos dados com tipos diferentes ou use os mesmos dados com nomes diferentes. Os sindicatos são semelhantes às estruturas. Uma união é declarada e usada da mesma forma que uma estrutura.
Uma união difere de uma estrutura em que apenas um de seus membros pode ser usado por vez. A razão para isso é simples. Todos os membros de um sindicato ocupam a mesma área de memória. Eles são colocados um sobre o outro.
As uniões são definidas e declaradas da mesma forma que as estruturas. A única diferença nas declarações é que a palavra-chave union é usada em vez de struct. Para definir uma união simples de uma variável char e uma variável integer, você escreveria o seguinte:
union shared {
char c;
int i;
};
Essa união, compartilhada, pode ser usada para criar instâncias de uma união que pode conter um valor de caractere c ou um valor inteiro i. Esta é uma condição OR. Ao contrário de uma estrutura que contém os dois valores, a união pode conter apenas um valor por vez.
Uma união pode ser inicializada em sua declaração. Porque apenas um membro pode ser usado por vez e apenas um pode ser inicializado. Para evitar confusão, apenas o primeiro membro da união pode ser inicializado. O código a seguir mostra uma instância da união compartilhada sendo declarada e inicializada:
união compartilhada generic_variable = {`@'};
Observe que a união generic_variable foi inicializada da mesma forma que o primeiro membro de uma estrutura seria inicializado.
Os membros individuais do sindicato podem ser usados da mesma forma que os membros da estrutura podem ser usados usando o operador de membro (.). No entanto, há uma diferença importante no acesso aos membros do sindicato.
Apenas um membro do sindicato deve ser acessado por vez. Como um sindicato armazena seus membros uns sobre os outros, é importante acessar apenas um membro por vez.
A palavra-chave união
union tag {
union_member(s);
/* additional statements may go here */
}instance;
A palavra-chave union é usada para declarar uniões. Uma união é uma coleção de uma ou mais variáveis (union_members) que foram agrupadas sob um único nome. Além disso, cada um desses membros do sindicato ocupa a mesma área de memória.
A palavra-chave union identifica o início de uma definição de união. É seguido por uma tag que é o nome dado ao sindicato. Após a tag estão os membros do sindicato entre chaves.
Uma instância, a declaração real de uma união, também pode ser definida. Se você definir a estrutura sem a instância, é apenas um modelo que pode ser usado posteriormente em um programa para declarar estruturas. O seguinte é o formato de um modelo:
union tag {
union_member(s);
/* additional statements may go here */
};
Para usar o modelo, você usaria o seguinte formato:
instância de tag de união;
Para usar este formato, você deve ter declarado previamente uma união com a tag fornecida.
/* Declare a union template called tag */
union tag {
int num;
char alps;
}
/* Use the union template */
union tag mixed_variable;
/* Declare a union and instance together */
union generic_type_tag {
char c;
int i;
float f;
double d;
} generic;
/* Initialize a union. */
union date_tag {
char full_date[9];
struct part_date_tag {
char month[2];
char break_value1;
char day[2];
char break_value2;
char year[2];
} part_date;
}date = {"09/12/80"};
Vamos entender melhor com a ajuda de exemplos:
#include <stdio.h>
int main()
{
union
{
int value; /* This is the first part of the union */
struct
{
char first; /* These two values are the second part of it */
char second;
} half;
} number;
long index;
for (index = 12 ; index < 300000L ; index += 35231L)
{
number.value = index;
printf("%8x %6x %6x\n", number.value,
number.half.first,
number.half.second);
}
return 0;
}
E a saída do programa será exibida da seguinte forma:
c c 0
89ab ffab ff89
134a 4a 13
9ce9 ffe9 ff9c
2688 ff88 26
b027 27 ffb0
39c6 ffc6 39
c365 65 ffc3
4d04 4 4d
Um uso prático de uma união na recuperação de dados
Agora vamos ver um uso prático da união na programação de recuperação de dados. Tomemos um pequeno exemplo. O programa a seguir é o pequeno modelo de programa de varredura de setor defeituoso para uma unidade de disquete (a: ), mas não é o modelo completo de software de varredura de setor defeituoso.
Vamos examinar o programa:
#include<dos.h>
#include<conio.h>
int main()
{
int rp, head, track, sector, status;
char *buf;
union REGS in, out;
struct SREGS s;
clrscr();
/* Reset the disk system to initialize to disk */
printf("\n Resetting the disk system....");
for(rp=0;rp<=2;rp++)
{
in.h.ah = 0;
in.h.dl = 0x00;
int86(0x13,&in,&out);
}
printf("\n\n\n Now Testing the Disk for Bad Sectors....");
/* scan for bad sectors */
for(track=0;track<=79;track++)
{
for(head=0;head<=1;head++)
{
for(sector=1;sector<=18;sector++)
{
in.h.ah = 0x04;
in.h.al = 1;
in.h.dl = 0x00;
in.h.ch = track;
in.h.dh = head;
in.h.cl = sector;
in.x.bx = FP_OFF(buf);
s.es = FP_SEG(buf);
int86x(0x13,&in,&out,&s);
if(out.x.cflag)
{
status=out.h.ah;
printf("\n track:%d Head:%d Sector:%d Status ==0x%X",track,head,sector,status);
}
}
}
}
printf("\n\n\nDone");
return 0;
}
Agora vamos ver como será sua saída se houver um setor defeituoso no disquete:
Redefinindo o sistema de disco....
Agora testando o disco para setores defeituosos....
track:0 Head:0 Sector:4 Status ==0xA
track:0 Head:0 Sector:5 Status ==0xA
track:1 Head:0 Sector:4 Status ==0xA
track:1 Head:0 Sector:5 Status ==0xA
track:1 Head:0 Sector:6 Status ==0xA
track:1 Head:0 Sector:7 Status ==0xA
track:1 Head:0 Sector:8 Status ==0xA
track:1 Head:0 Sector:11 Status ==0xA
track:1 Head:0 Sector:12 Status ==0xA
track:1 Head:0 Sector:13 Status ==0xA
track:1 Head:0 Sector:14 Status ==0xA
track:1 Head:0 Sector:15 Status ==0xA
track:1 Head:0 Sector:16 Status ==0xA
track:1 Head:0 Sector:17 Status ==0xA
track:1 Head:0 Sector:18 Status ==0xA
track:1 Head:1 Sector:5 Status ==0xA
track:1 Head:1 Sector:6 Status ==0xA
track:1 Head:1 Sector:7 Status ==0xA
track:1 Head:1 Sector:8 Status ==0xA
track:1 Head:1 Sector:9 Status ==0xA
track:1 Head:1 Sector:10 Status ==0xA
track:1 Head:1 Sector:11 Status ==0xA
track:1 Head:1 Sector:12 Status ==0xA
track:1 Head:1 Sector:13 Status ==0xA
track:1 Head:1 Sector:14 Status ==0xA
track:1 Head:1 Sector:15 Status ==0xA
track:1 Head:1 Sector:16 Status ==0xA
track:1 Head:1 Sector:17 Status ==0xA
track:1 Head:1 Sector:18 Status ==0xA
track:2 Head:0 Sector:4 Status ==0xA
track:2 Head:0 Sector:5 Status ==0xA
track:14 Head:0 Sector:6 Status ==0xA
Done
Pode ser um pouco difícil entender as funções e interrupções usadas neste programa para verificar o disco quanto a setores defeituosos e redefinir o sistema de disco etc. mas você não precisa se preocupar, vamos aprender todas essas coisas no BIOS e interromper a programação seções mais adiante nos próximos capítulos.
Manuseio de arquivos em C
O acesso a arquivos em C é obtido associando um fluxo a um arquivo. C se comunica com arquivos usando um novo tipo de dados chamado ponteiro de arquivo. Esse tipo é definido em stdio.h e escrito como FILE *. Um ponteiro de arquivo chamado output_file é declarado em uma instrução como
ARQUIVO *arquivo_saída;
Os modos de arquivo da função fopen
Seu programa deve abrir um arquivo antes de poder acessá-lo. Isso é feito usando a função fopen, que retorna o ponteiro de arquivo necessário. Se o arquivo não puder ser aberto por qualquer motivo, o valor NULL será retornado. Você normalmente usará o fopen da seguinte forma
if ((output_file = fopen("output_file", "w")) == NULL)
fprintf(stderr, "Cannot open %s\n",
"output_file");
fopen recebe dois argumentos, ambos são strings, o primeiro é o nome do arquivo a ser aberto, o segundo é um caractere de acesso, que geralmente é r, a ou w etc. Os arquivos podem ser abertos em vários modos, como mostrado na tabela a seguir.
File Modes |
r |
Open a text file for reading. |
w |
Create a text file for writing. If the file exists, it is overwritten. |
a |
Open a text file in append mode. Text is added to the end of the file. |
rb |
Open a binary file for reading. |
wb |
Create a binary file for writing. If the file exists, it is overwritten. |
ab |
Open a binary file in append mode. Data is added to the end of the file. |
r+ |
Open a text file for reading and writing. |
w+ |
Create a text file for reading and writing. If the file exists, it is overwritten. |
a+ |
Open a text file for reading and writing at the end. |
r+b or rb+ |
Open binary file for reading and writing. |
w+b or wb+ |
Create a binary file for reading and writing. If the file exists, it is overwritten. |
a+b or ab+ |
Open a text file for reading and writing at the end. |
Os modos de atualização são usados com as funções fseek, fsetpos e rewind. A função fopen retorna um ponteiro de arquivo ou NULL se ocorrer um erro.
O exemplo a seguir abre um arquivo, tarun.txt no modo somente leitura. É uma boa prática de programação testar se o arquivo existe.
if ((in = fopen("tarun.txt", "r")) == NULL)
{
puts("Unable to open the file");
return 0;
}
Fechando arquivos
Os arquivos são fechados usando a função fclose. A sintaxe é a seguinte:
fclose(in);
Ler arquivos
A função feof é usada para testar o final do arquivo. As funções fgetc, fscanf e fgets são usadas para ler os dados do arquivo.
O exemplo a seguir lista o conteúdo de um arquivo na tela, usando fgetc para ler o arquivo um caractere por vez.
#include <stdio.h>
int main()
{
FILE *in;
int key;
if ((in = fopen("tarun.txt", "r")) == NULL)
{
puts("Unable to open the file");
return 0;
}
while (!feof(in))
{
key = fgetc(in);
/* The last character read is the end of file marker so don't print it */
if (!feof(in))
putchar(key);
}
fclose(in);
return 0;
}
A função fscanf pode ser usada para ler diferentes tipos de dados do arquivo como no exemplo a seguir, desde que os dados no arquivo estejam no formato da string de formato usada com fscanf.
fscanf(in, "%d/%d/%d", &dia, &mês, &ano);
A função fgets é usada para ler vários caracteres de um arquivo. stdin é o fluxo de arquivo de entrada padrão, e a função fgets pode ser usada para controlar a entrada.
Escrevendo em arquivos
Os dados podem ser gravados no arquivo usando fputc e fprintf. O exemplo a seguir usa as funções fgetc e fputc para fazer uma cópia de um arquivo de texto.
#include <stdio.h>
int main()
{
FILE *in, *out;
int key;
if ((in = fopen("tarun.txt", "r")) == NULL)
{
puts("Unable to open the file");
return 0;
}
out = fopen("copy.txt", "w");
while (!feof(in))
{
key = fgetc(in);
if (!feof(in))
fputc(key, out);
}
fclose(in);
fclose(out);
return 0;
}
A função fprintf pode ser usada para gravar dados formatados em um arquivo.
fprintf(out, "Date: %02d/%02d/%02d\n",
day, month, year);
Argumentos de linha de comando com C
A definição ANSI C para declarar a função main( ) é:
int main() ou int main(int argc, char **argv)
A segunda versão permite que os argumentos sejam passados a partir da linha de comando. O parâmetro argc é um contador de argumentos e contém o número de parâmetros passados da linha de comando. O parâmetro argv é o vetor de argumentos que é um array de ponteiros para strings que representam os parâmetros reais passados.
O exemplo a seguir permite que qualquer número de argumentos seja passado da linha de comando e os imprima. argv[0] é o programa real. O programa deve ser executado a partir de um prompt de comando.
#include <stdio.h>
int main(int argc, char **argv)
{
int counter;
puts("The arguments to the program are:");
for (counter=0; counter<argc; counter++)
puts(argv[counter]);
return 0;
}
If the program name was count.c, it could be called as follows from the command line.
count 3
or
count 7
or
count 192 etc.
O próximo exemplo usa as rotinas de manipulação de arquivos para copiar um arquivo de texto para um novo arquivo. Por exemplo, o argumento da linha de comando pode ser chamado como:
txtcpy um.txt dois.txt
#include <stdio.h>
int main(int argc, char **argv)
{
FILE *in, *out;
int key;
if (argc < 3)
{
puts("Usage: txtcpy source destination\n");
puts("The source must be an existing file");
puts("If the destination file exists, it will be
overwritten");
return 0;
}
if ((in = fopen(argv[1], "r")) == NULL)
{
puts("Unable to open the file to be copied");
return 0;
}
if ((out = fopen(argv[2], "w")) == NULL)
{
puts("Unable to open the output file");
return 0;
}
while (!feof(in))
{
key = fgetc(in);
if (!feof(in))
fputc(key, out);
}
fclose(in);
fclose(out);
return 0;
}
Manipuladores de bits
No nível de hardware, os dados são representados como números binários. A representação binária do número 59 é 111011. O bit 0 é o bit menos significativo e, neste caso, o bit 5 é o bit mais significativo.
Cada conjunto de bits é calculado como 2 elevado à potência do conjunto de bits. Os operadores bit a bit permitem manipular variáveis inteiras em nível de bit. O seguinte mostra a representação binária do número 59.
binary representation of the number 59 |
bit 5 4 3 2 1 0 |
2 power n 32 16 8 4 2 1 |
set 1 1 1 0 1 1 |
Com três bits, é possível representar os números de 0 a 7. A tabela a seguir mostra os números de 0 a 7 em sua forma binária.
Binary Digits |
000 |
0 |
001 |
1 |
010 |
2 |
011 |
3 |
100 |
4 |
101 |
5 |
110 |
6 |
111 |
7 |
A tabela a seguir lista os operadores bit a bit que podem ser usados para manipular números binários.
Binary Digits |
& |
Bitwise AND |
| |
Bitwise OR |
^ |
Bitwise Exclusive OR |
~ |
Bitwise Complement |
<< |
Bitwise Shift Left |
>> |
Bitwise Shift Right |
Bit a bit E
O AND bit a bit é True somente se ambos os bits estiverem definidos. O exemplo a seguir mostra o resultado de um AND bit a bit nos números 23 e 12.
10111 (23)
01100 (12) AND
____________________
00100 (result = 4) |
Você pode usar um valor de máscara para verificar se determinados bits foram definidos. Se quiséssemos verificar se os bits 1 e 3 foram definidos, poderíamos mascarar o número com 10 (o valor dos bits 1 e 3) e testar o resultado em relação à máscara.
#include <stdio.h>
int main()
{
int num, mask = 10;
printf("Enter a number: ");
scanf("%d", &num);
if ((num & mask) == mask)
puts("Bits 1 and 3 are set");
else
puts("Bits 1 and 3 are not set");
return 0;
}
Bit a bit OU
O OR bit a bit é verdadeiro se um dos bits estiver definido. O seguinte mostra o resultado de um OR bit a bit nos números 23 e 12.
10111 (23)
01100 (12) OR
______________________
11111 (result = 31) |
Você pode usar uma máscara para garantir que um ou mais bits tenham sido definidos. O exemplo a seguir garante que o bit 2 esteja definido.
#include <stdio.h>
int main()
{
int num, mask = 4;
printf("Enter a number: ");
scanf("%d", &num);
num |= mask;
printf("After ensuring bit 2 is set: %d\n", num);
return 0;
}
Exclusivo bit a bit OU
O OR exclusivo bit a bit é True se um dos bits estiver definido, mas não ambos. O seguinte mostra o resultado de um exclusivo OR bit a bit nos números 23 e 12.
10111 (23)
01100 (12) Exclusive OR (XOR)
_____________________________
11011 (result = 27) |
O OR Exclusivo tem algumas propriedades interessantes. Se você exclui OR um número sozinho, ele se define como zero, pois os zeros permanecerão zero e os dois não podem ser definidos, portanto, são definidos como zero.
Como resultado disso, se você Exclusive OR um número com outro número, então Exclusive OR o resultado com o outro número novamente, o resultado é o número original. Você pode tentar isso com os números usados no exemplo acima.
23 XOR 12 = 27
27 XOR 12 = 23
27 XOR 23 = 12
Este recurso pode ser usado para criptografia. O programa a seguir usa uma chave de criptografia de 23 para ilustrar a propriedade em um número digitado pelo usuário.
#include <stdio.h>
int main()
{
int num, key = 23;
printf("Enter a number: ");
scanf("%d", &num);
num ^= key;
printf("Exclusive OR with %d gives %d\n", key, num);
num ^= key;
printf("Exclusive OR with %d gives %d\n", key, num);
return 0;
}
Elogios bit a bit
O Compliment bit a bit é um operador de complemento de um que ativa ou desativa o bit. Se for 1, será definido como 0, se for 0, será definido como 1.
#include <stdio.h>
int main()
{
int num = 0xFFFF;
printf("The compliment of %X is %X\n", num, ~num);
return 0;
}
Deslocamento bit a bit para a esquerda
O operador Bitwise Shift Left desloca o número para a esquerda. Os bits mais significativos são perdidos à medida que o número se move para a esquerda e os bits menos significativos desocupados são zero. Veja a seguir a representação binária de 43.
0101011 (43 decimal)
Ao deslocar os bits para a esquerda, perdemos o bit mais significativo (neste caso, um zero), e o número é preenchido com um zero no bit menos significativo. O seguinte é o número resultante.
1010110 (decimal 86)
Deslocamento bit a bit para a direita
O operador Bitwise Shift Right desloca o número para a direita. Zero é introduzido nos bits mais significativos desocupados e os bits menos significativos desocupados são perdidos. Veja a seguir a representação binária do número 43.
0101011 (43 decimal)
Ao deslocar os bits para a direita, perdemos o bit menos significativo (neste caso, um um), e o número é preenchido com um zero no bit mais significativo. O seguinte é o número resultante.
0010101 (21 decimal)
O programa a seguir usa o Bitwise Shift Right e Bitwise AND para exibir um número como um número binário de 16 bits. O número é deslocado para a direita sucessivamente de 16 para zero e bit a bit ANDed com 1 para ver se o bit está definido. Um método alternativo seria usar máscaras sucessivas com o operador Bitwise OR.
#include <stdio.h>
int main()
{
int counter, num;
printf("Enter a number: ");
scanf("%d", &num);
printf("%d is binary: ", num);
for (counter=15; counter>=0; counter--)
printf("%d", (num >> counter) & 1);
putchar('\n');
return 0;
}
Funções para Binário – Conversões Decimais
As duas funções fornecidas a seguir são para conversão de Binário para Decimal e Decimal para Binário. A função dada a seguir para converter um número decimal para o número binário correspondente suporta até 32 – Bit número binário. Você pode usar este ou o programa fornecido antes para conversão de acordo com seus requisitos.
Função para conversão de Decimal para Binário:
void Decimal_to_Binary(void)
{
int input =0;
int i;
int count = 0;
int binary [32]; /* 32 Bit, MAXIMUM 32 elements */
printf ("Enter Decimal number to convert into
Binary :");
scanf ("%d", &input);
do
{
i = input%2; /* MOD 2 to get 1 or a 0*/
binary[count] = i; /* Load Elements into the Binary Array */
input = input/2; /* Divide input by 2 to decrement via binary */
count++; /* Count how many elements are needed*/
}while (input > 0);
/* Reverse and output binary digits */
printf ("Binary representation is: ");
do
{
printf ("%d", binary[count - 1]);
count--;
} while (count > 0);
printf ("\n");
}
Função para conversão de binário para decimal:
A função a seguir é converter qualquer número Binário em seu número Decimal correspondente:
void Binary_to_Decimal(void)
{
char binaryhold[512];
char *binary;
int i=0;
int dec = 0;
int z;
printf ("Please enter the Binary Digits.\n");
printf ("Binary digits are either 0 or 1 Only ");
printf ("Binary Entry : ");
binary = gets(binaryhold);
i=strlen(binary);
for (z=0; z<i; ++z)
{
dec=dec*2+(binary[z]=='1'? 1:0); /* if Binary[z] is
equal to 1,
then 1 else 0 */
}
printf ("\n");
printf ("Decimal value of %s is %d",
binary, dec);
printf ("\n");
}
Depuração e teste
Erros de sintaxe
Sintaxe refere-se à gramática, estrutura e ordem dos elementos em uma instrução. Um erro de sintaxe ocorre quando quebramos as regras, como esquecer de terminar uma instrução com um ponto e vírgula. Quando você compila o programa, o compilador produzirá uma lista de todos os erros de sintaxe que ele pode encontrar.
Um bom compilador produzirá a lista com uma descrição do erro e poderá fornecer uma possível solução. A correção dos erros pode resultar na exibição de mais erros durante a recompilação. A razão para isso é que os erros anteriores mudaram a estrutura do programa, significando que outros erros foram suprimidos durante a compilação original.
Da mesma forma, um único erro pode resultar em vários erros. Tente colocar um ponto e vírgula no final da função main de um programa que compila e executa corretamente. Ao recompilá-lo, você obterá uma lista enorme de erros e, no entanto, é apenas um ponto e vírgula mal colocado.
Além dos erros de sintaxe, os compiladores também podem emitir avisos. Um aviso não é um erro, mas pode causar problemas durante a execução do programa. Por exemplo, atribuir um número de ponto flutuante de precisão dupla a um número de ponto flutuante de precisão simples pode resultar em perda de precisão. Não é um erro de sintaxe, mas pode levar a problemas. Neste exemplo específico, você pode mostrar a intenção convertendo a variável no tipo de dados apropriado.
Considere o exemplo a seguir, onde x é um número de ponto flutuante de precisão simples e y é um número de ponto flutuante de precisão dupla. y é explicitamente convertido em um float durante a atribuição, o que eliminaria quaisquer avisos do compilador.
x = (float)y;
Erros de lógica
Erros de lógica ocorrem quando há um erro na lógica. Por exemplo, você pode testar se um número é menor que 4 e maior que 8. Isso não pode ser verdade, mas se estiver sintaticamente correto o programa irá compilar com sucesso. Considere o seguinte exemplo:
if (x < 4 && x > 8)
puts("Will never happen!");
A sintaxe está correta, então o programa compilará, mas a instrução puts nunca será impressa, pois o valor de x não pode ser menor que quatro e maior que oito ao mesmo tempo.
A maioria dos erros de lógica é descoberta por meio do teste inicial do programa. Quando não se comporta como você esperava, você inspeciona as declarações lógicas mais de perto e as corrige. Isso só é verdade para erros lógicos óbvios. Quanto maior o programa, mais caminhos haverá através dele, mais difícil será verificar se o programa se comporta conforme o esperado.
Teste
No processo de desenvolvimento de software, erros podem ser injetados em qualquer estágio durante o desenvolvimento. Isso ocorre porque os métodos de verificação das fases anteriores de desenvolvimento de software são manuais. Portanto, o código desenvolvido durante a atividade de codificação provavelmente terá alguns erros de requisitos e erros de design, além de erros introduzidos durante a atividade de codificação. Durante o teste, o programa a ser testado é executado com um conjunto de casos de teste e a saída do programa para os casos de teste é avaliada para determinar se a programação está sendo executada.
Assim, o teste é o processo de análise de um item de software para detectar a diferença entre as condições existentes e necessárias (ou seja, bugs) e para avaliar os recursos dos itens de software. Portanto, o teste é o processo de análise de um programa com a intenção de encontrar erros.
Alguns princípios de teste
- O teste não pode mostrar a ausência de defeitos, apenas sua presença.
- Quanto mais cedo um erro for cometido, mais caro será.
- Quanto mais tarde um erro for detectado, mais caro ele será.
Agora vamos discutir algumas técnicas de teste:
Teste de caixa branca
O teste de caixa branca é uma técnica pela qual todos os caminhos através do programa são testados com todos os valores possíveis. Essa abordagem requer algum conhecimento de como o programa deve se comportar. Por exemplo, se o seu programa aceitasse um valor inteiro entre 1 e 50, um teste de caixa branca testaria o programa com todos os 50 valores para garantir que estava correto para cada um e, em seguida, testaria todos os outros valores possíveis que um inteiro pode assumir e testaria isso se comportou como esperado. Considerando o número de itens de dados que um programa típico pode ter, as possíveis permutações tornam o teste de caixa branca extremamente difícil para programas grandes.
O teste de caixa branca pode ser aplicado a funções críticas de segurança de um grande programa, e grande parte do restante testado usando o teste de caixa preta, discutido abaixo. Por causa do número de permutações, o teste de caixa branca geralmente é executado usando um equipamento de teste, onde intervalos de valores são alimentados ao programa rapidamente por meio de um programa especial, registrando exceções ao comportamento esperado. O teste de caixa branca às vezes é chamado de teste estrutural, claro ou de caixa aberta.
Teste de caixa preta
O teste de caixa preta é semelhante ao teste de caixa branca, exceto que, em vez de testar todos os valores possíveis, os valores selecionados são testados. Nesse tipo de teste, o testador conhece as entradas e quais devem ser os resultados esperados, mas não necessariamente como o programa chegou a eles. O teste de caixa preta às vezes é chamado de teste funcional.
Os casos de teste para testes de caixa preta são normalmente elaborados assim que as especificações do programa são concluídas. Os casos de teste são baseados em classes de equivalência.
Classes de equivalência
Para cada entrada, uma classe de equivalência identifica os estados válidos e inválidos. Geralmente, há três cenários para planejar ao definir classes de equivalência.
Se a entrada especificar um intervalo ou um valor específico, haverá um estado válido e dois estados inválidos definidos. Por exemplo, se um número deve estar entre 1 e 20, o estado válido está entre 1 e 20, haverá um estado inválido para menos de 1 e um estado inválido maior que 20.
Se a entrada excluir um intervalo ou valor específico, haverá dois estados válidos e um estado inválido definido. Por exemplo, se um número não deve estar entre 1 e 20, os estados válidos são menores que um e maiores que 20, e o estado inválido é entre 1 e 20.
Se a entrada especificar um valor booleano, haverá apenas dois estados, um válido e outro inválido.
Análise de valor de limite
A análise do valor limite considera apenas os valores no limite das entradas. Por exemplo, no caso de um número estar entre 1 e 20, os casos de teste podem ser 1, 20, 0 e 21. O pensamento por trás disso é que se o programa funcionar como esperado com esses valores, os outros valores também funcione como esperado.
A tabela a seguir fornece uma visão geral dos limites típicos que você pode identificar.
Testing Ranges |
Input type |
Test Values |
Range |
- x[lower_bound]-1
- x[lower_bound]
- x[upper_bound]
- x[upper_bound]+1
|
Boolean |
|
Elaboração de um plano de teste
Identifique as classes de equivalência e, para cada classe, identifique os limites. Tendo identificado os limites da classe, escreva uma lista de valores válidos e inválidos no limite e qual deve ser o comportamento esperado. O testador pode então executar o programa com os valores de limite e indicar o que aconteceu quando o valor de limite foi testado em relação ao resultado necessário.
O seguinte pode ser um plano de teste típico usado para verificar uma idade inserida em que os valores aceitáveis estão no intervalo de 10 a 110.
Equivalence Class |
Valid |
Invalid |
Between 10 and 110 |
> 110 |
|
< 10 |
Tendo definido nossa classe de equivalência, podemos agora elaborar um plano de teste para idade.
Test Plan |
Value |
State |
Expected Result |
Actual Result |
10 |
Valid |
Continue execution to get name |
|
110 |
Valid |
Continue execution to get name |
|
9 |
Invalid |
Ask for age again |
|
111 |
Invalid |
Ask for age again |
|
O "Resultado real" coluna é deixada em branco, pois será preenchida durante o teste. Se o resultado for o esperado, a coluna será marcada. Se não, um comentário indicando o que ocorreu deve ser inserido.
Página modificada em: 07/01/2022