Previous Up Next
Programação de Computadores em C

Capítulo 5   Arranjos

Abordamos a seguir, nos Capítulos 5 a 7, a definição e o uso de estruturas de dados, que são valores compostos por valores mais simples: arranjos (capítulo 5), ponteiros (capítulo 6) e registros (ou, como são chamados em C, estruturas, capítulo 7). Uma introdução à definição e uso de estruturas de dados encadeadas, formadas com o uso de registros com ponteiros, é apresentada na seção 7.

Arranjos são estruturas de dados homogêneas, devido ao fato de que os componentes têm que ser todos de um mesmo tipo, enquanto estruturas de dados encadeadas e registros em geral são estruturas de dados heterogêneas, que podem envolver componentes de vários tipos, diferentes entre si. Como veremos na seção 5.5, cadeias de caracteres são, na linguagem C, arranjos terminados com um caractere especial (o caractere \0’).

Arranjos são estruturas de dados muito usadas em programas. Um arranjo é uma forma de representar uma função finita (função de domínio finito) — que em geral é vista como uma tabela ou uma seqüência finita de valores — com a característica de que o acesso aos seus componentes podem ser feitos de modo eficiente. Esta seção aborda a definição e uso de arranjos na linguagem C.

Um arranjo é uma estrutura de dados formada por um certo número finito de componentes (também chamados de posições do arranjo) de um mesmo tipo, sendo cada componente identificado por um índice.

Um arranjo tem um tamanho, que é o número de componentes do arranjo. A uma variável de tipo arranjo de um tipo T e tamanho n correspondem n variáveis de tipo T.

Em C, os índices de um arranjo são sempre inteiros que variam de 0 a n-1, onde n é o tamanho do arranjo.

Se v é uma expressão que representa um arranjo de tamanho n, e i é uma expressão de tipo int com valor entre 0 e n-1, então v[i] representa um componente do arranjo v.

Nota sobre uso de índices fora do limite em C: A linguagem C não especifica que, em uma operação de indexação (uso de um valor como índice) de um arranjo, deva existir uma verificação de que esse índice é um índice válido. A linguagem simplesmente deixa a responsabilidade para o programador. Se o índice estiver fora dos limites válidos em uma indexação, uma área de memória distinta da área alocada para o arranjo será usada, ou o programa é interrompido, com uma mensagem de que um erro ocorreu devido a um acesso ilegal a uma área de memória que não pode ser usada pelo processo corrente. Se o índice for inválido mas estiver dentro da área reservada ao processo corrente, nenhum erro em tempo de execução será detectado. O erro devido a um acesso ilegal a uma área de memória não reservada ao processo corrente provoca a emissão da mensagem segmentation fault, e a interrupção da execução do processo corrente. O motivo de não existir verificação de que um índice de um arranjo está ou não entre os limites desse arranjo é, obviamente, eficiência (i.e. evitar gasto de tempo de execução). O programador deve estar ciente disso e atento de modo a evitar erros (i.e. evitar o uso de índices fora dos limites válidos em indexações de arranjos).

A eficiência que existe no acesso a componentes de um arranjo se deve ao fato de que arranjos são geralmente armazenados em posições contíguas da memória de um computador, e o acesso à i-ésima posição é feito diretamente, sem necessidade de acesso a outras posições. Isso espelha o funcionamento da memória de computadores, para a qual o tempo de acesso a qualquer endereço de memória é o mesmo, ou seja, independe do valor desse endereço. Um arranjo é, por isso, chamado de uma estrutura de dados de acesso direto (ou acesso indexado). Ao contrário, em uma estrutura de dados de acesso sequencial, o acesso ao i-ésimo componente requer o acesso aos componentes de índice inferior a i.

5.1  Declaração e Criação de Arranjos

Em C, arranjos (valores de tipo arranjo) podem ser criados no instante da declaração de variáveis do tipo arranjo. Note, no entanto, que uma variável que representa um arranjo dinâmico em C armazena não um valor de tipo arranjo mas o endereço do primeiro componente do arranjo.

A versão C-99 da linguagem permite a declaração de arranjos dentro de funções com tamanho que é conhecido apenas dinamicamente.

É comum que uma variável de tipo arranjo tenha um tamanho conhecido apenas dinamicamente (em tempo de execução), como por exemplo quando é parâmetro de uma função. O tamanho, nesse caso, deve ser especificado como um parâmetro adicional na chamada à função (a função deve ter um parâmetro adicional que representa o tamanho).

O tamanho de um arranjo não faz parte do seu tipo (em geral, esse tamanho não é conhecido estaticamente), mas não pode ser modificado. Considere os seguintes exemplos:

int ai[3]; char ac[4];

As declarações acima criam as variáveis ai e ac. A primeira é um arranjo de 3 inteiros e a segunda um arranjo de 4 caracteres.

A declaração dessas variáveis de tipo arranjo envolvem também a criação de um valor de tipo arranjo. Na declaração de ai, é criada uma área de memória com tamanho igual ao de 3 variáveis de tipo int. Similarmente, na declaração de ac, é criada uma área de memória com tamanho igual ao de 4 variáveis de tipo char. Os valores contidos nessas áreas de memória não são conhecidos: são usados os valores que estão já armazenados nessas áreas de memória.

5.2  Arranjos criados dinamicamente

A função malloc, definida na biblioteca stdlib, aloca dinamicamente uma porção de memória de um certo tamanho, passado como argumento da função, e retorna o endereço da área de memória alocada. Esse endereço é retornado com o valor de um ponteiro para o primeiro componente do arranjo. Ponteiros são abordados mais detalhadamente na seção 6. Por enquanto, considere apenas que malloc aloca uma área de memória — na área de memória dinâmica do processo corrente, chamada em inglês de heap — que será usada tipicamente por meio da operação de indexação do arranjo. Considere os seguintes comandos:

int* pi; char* pc; pi = malloc(3 * sizeof(int)); pc = malloc(4 * sizeof(char));

A chamada malloc(3*sizeof(int)) aloca uma área de memória de tamanho 3× x, se x é o tamanho da unidade de memória ocupada por valores de tipo int. Similarmente, a chamada malloc(4 * sizeof(char)) alloca uma área de memória de tamanho 4*(sizeof(char)), sendo sizeof(char) o tamanho de uma área de memória ocupada por um valor de tipo char.

Após a atribuição à variável pi acima, pi contém o endereço da primeira posição de um arranjo com 3 componentes de tipo int. Analogamente, após a atribuição à variável pc acima, ac contém o endereço da primeira posição de um arranjo com 4 componentes de tipo char.

No caso da declaração de variáveis com um tipo que é explicitamente indicado como sendo um tipo arranjo, para o qual o tamanho é indicado explicitamente (como no exemplo anterior das variáveis pi e pc), o arranjo não é alocado na área dinâmica mas na área de pilha da função na qual a declaração ocorre (no caso, na área de memória alocada quando a execução da função main é iniciada).

O tamanho de uma área de memória alocada para variáveis de um tipo pode ser obtido em C por meio do uso da função predefinida sizeof. A palavra reservada sizeof pode ser seguida de um nome de tipo (como nos exemplos acima) ou por uma expressão. O resultado retornado pela avaliação de sizeof é o tamanho em bytes da área alocada para variáveis do tipo ou expressão usada como “argumento”. No caso de uso de um tipo, ele deve ser colocado entre parênteses, mas quando uma expressão é usada, ela pode seguir sizeof sem necessidade de parênteses (respeitando-se a precedência de operadores e chamadas de funções).

Por exemplo, depois da atribuição acima, sizeof pi retorna o mesmo que sizeof(3 * sizeof(int)).

A linguagem C usa colchetes em declaração de arranjos após o nome da variável; por exemplo, a declaração:

int a[5];

declara uma variável a de tipo arranjo, mas o tipo int ocorre antes e a indicação de que esse tipo é um arranjo de componentes de tipo int ocorre após o nome da variável.

Isso é feito com o intuito de permitir declarações um pouco mais sucintas, como a seguinte, que cria uma variável b de tipo int e um arranjo a com componentes de tipo int:

int b, a[5];

5.3  Exemplo de Uso de Arranjo Criado Dinamicamente

Como exemplo do uso de arranjo criados dinamicamente, vamos considerar o problema de ler um inteiro não-negativo n, em seguida n valores inteiros e imprimir os n inteiros na ordem inversa à que foram lidos.

Por exemplo, se forem lidos o inteiro 3, em seguida três inteiros i1, i2 e i3, o programa deve imprimir i3, i2, i1, nesta ordem. Uma solução é mostrada na Figura 5.1.


/*************************************************************** * Lê n, depois n inteiros do dispositivo de entrada padrão * * e imprime os n inteiros lidos em ordem inversa. * ***************************************************************/ #include stdio.h #include stdlib.h int main() { int *arr, i=0, n; scanf("%d", &n); arr = malloc(n * sizeof(int)); for (i=0; i<n; i++) scanf("%d", &arr[i]); for (i--; i>=0; i--) printf("%d ", arr[i]); }
Figura 5.1: Exemplo de uso de comando for para percorrer arranjo


O programa da Figura 5.1 ilustra a operação básica de “percorrer” um arranjo para realização de alguma operação sobre os valores armazenados no arranjo.

O programa declara o tipo da variável arr não como um arranjo de componentes de tipo int, mas como um ponteiro para variáveis do tipo int. Isso ocorre porque o tamanho do arranjo só é conhecido dinamicamente.

A alocacão de memória feita pela função malloc ocorre na área de memória dinâmica. A função retorna o endereço inicial da área de memória alocada.

A operação de indexação de arranjos funciona no caso de variáveis ou valores de tipo ponteiro exatamente como ocorre no caso de variáveis e valores de tipo arranjo. Mais detalhes sobre a relação entre arranjos e ponteiros em C estão na seção 6.

Outra observação importante é referente à necessidade de se especificar o tamanho do arranjo, ou seja, o número de valores inteiros a ser digitado, antes da leitura dos valores inteiros. Para evitar isso, há duas opções: i) não usar um arranjo, mas uma estrutura de dados encadeada, como descrito na seção 7, ou ii) adotar um certo valor como máximo para o número de valores a serem digitados (de modo a alocar um arranjo com tamanho igual a esse número máximo). Essa opção tem as desvantagens de que um número máximo pode não ser conhecido estaticamente ou ser difícil de ser determinado, e o uso de um valor máximo pode levar a alocação desnecessária de área significativa de memória, que não será usada (ou seja, o máximo pode ser um valor muito maior do que o de fato necessário). No caso de uso de um arranjo, é possível testar se o número máximo foi alcançado e realocar um arranjo maior caso isso ocorra, mas essa é uma operação dispendiosa em termos de esforço de programação e tempo de execução (requer não só testes para verificar se o número máximo foi alcançado mas, também, nesse caso, eventual necessidade de cópia de todo o arranjo para outra área de memória).

Para percorrer um arranjo, é usado tipicamente um comando for, pois o formato desse comando é apropriado para o uso de uma variável que controla a tarefa de percorrer um arranjo: iniciação do valor da variável usada para indexar o arranjo, teste para verificar se seu valor é ainda um índice válido do arranjo, e atualização do valor armazenado na variável.

5.4  Operações Comuns em Arranjos

A Figura 5.2 apresenta exemplos de funções que realizam operações básicas bastante comuns sobre arranjos de componentes de um tipo específico, int. As funções são: i) preencher todos os componentes de um arranjo, passado como argumento, com um valor, também passado como argumento, e ii) testar igualdade de arranjos, passados como argumentos da função.

A primeira operação é uma função com “efeito colateral”. Isso significa que ela não é uma função que tem como domínio e contra-domínio os tipos anotados na definição da função, mas precisa, para poder ser considerada como função, que o domínio e contra-domínio abranjam (além dos parâmetros explicitamente indicados na definição da função) também uma forma de representação do estado da computação. O estado da computação pode ser representado na forma de uma função que associa variáveis a valores armazenados nessas variáveis.


void preenche(int arr[], int tam, int valor) { // Preenche todas as posicoes de arr com valor int i; for (i=0; i<tam; i++) arr[i] = valor; } int iguais(int arr1[], int tam1, int arr2[], int tam2) { // Retorna verdadeiro sse arr1 = arr2, componente a componente. int i; if (tam1 == tam2) { for (i=0; i<tam1; i++) if (arr1[i] != arr2[i]) return 0; return 1; } else return 0; }
Figura 5.2: Operações comuns em arranjos


Nas duas funções acima, preenche e iguais, o tamanho do arranjo (número de posições alocadas) é um parâmetro da função. Isso é feito para evitar o uso da função predefinida sizeof de C com argumento que é um arranjo alocado dinamicamente: esta função, anteriormente à definição do padrão C-99, só podia ser usada quando o tamanho do arranjo era conhecido em tempo de compilação.

Note que o uso de == não é adequado para comparar igualdade de dois arranjos em C (i.e. comparar se dois arranjos têm o mesmo número de componentes e os conteúdos dos componentes em cada índice dos dois arranjos são iguais). O teste de igualdade de valores de tipo arranjo com == se refere a comparação apenas de ponteiros (i.e. comparação entre se os endereços da primeira posição do primeiro e do segundo arranjo são iguais).

O programa da Figura 5.3 ilustra o uso da função iguais definida acima, escrevendo um programa que lê, nesta ordem, um valor inteiro positivo n, 2 × n valores inteiros, e em seguida imprime o resultado de testar se os n primeiros dos 2 × n valores lidos são iguais aos n últimos (ou seja, se o primeiro é igual ao n-ésimo mais um, o segundo é igual ao n-ésimo mais dois, etc.).


#include <stdio.h> #include <stdlib.h> int main() { int *arr1, *arr2, n1, n2, i; printf("Digite inteiro positivo n1, n1 valores inteiros, "); printf("inteiro positivo n2, n2 valores inteiros\n"); scanf("%d", &n1); arr1 = malloc(n1 * sizeof(int)); for (i=0; i<n1; i++) scanf("%d", &(arr1[i])); scanf("%d", &n2); arr2 = malloc(n2 * sizeof(int)); for (i=0; i<n2; i++) scanf("%d", &(arr2[$i$])); printf("Sequencia 1 de valores inteiros e' \%s sequencia 2 de valores inteiros.", iguais(arr1,n1,arr2,n2) ? "igual a" : "diferente da"); return 0; }
Figura 5.3: Exemplo de uso de função que testa igualdade entre arranjos


5.5  Cadeias de caracteres

Em C, uma cadeia de caracteres — em inglês, um string — é um arranjo de caracteres que segue a convenção de que o último componente é o caractere '\0' (chamado de caractere nulo). O caractere '\0' é inserido automaticamente em literais de tipo cadeia de caracteres, escritos entre aspas duplas.

Por exemplo:

char str[] = "1234";

é o mesmo que:

char str[5] = "1234";

Note que são 5 componentes no arranjo str: o último componente, de índice 4, contém o caractere '\0', e é inserido automaticamentre no literal de tipo cadeia de caracteres, e o tamanho do arranjo deve ser igual ao número de caracteres no literal mais 1.

O uso de cadeias de caracteres em C, e em particular a operação de ler uma cadeia de caracteres, deve levar em conta de que toda variável que é uma cadeia de caracteres deve ter um tamanho fixo. Para ler uma cadeia de caracteres, é necessário primeiro alocar espaço — um tamanho máximo — para armazenar os caracteres que vão ser lidos. No entanto, no caso de passagem de parâmetros para a função main (veja seção 3.9), o sistema aloca, automaticamente, cadeias de caracteres de tamanho suficiente para armazenar os caracteres passados como parâmetros para a função main).

O uso de cadeias de caracteres em C envolve muitas vezes o uso de funções definidas na biblioteca string, dentre as quais destacamos as seguintes:

 
AssinaturaSignificado
int strlen (char *s) Retorna tamanho da cadeia s, excluindo caractere nulo no final de s
charstrcpy (char *destconst char *fonte) Copia cadeia apontada por fonte, incluindo caractere '\0' que indica terminação da cadeia, para dest; retorna fonte
charstrcat (char *destconst char *fonte) Insere cadeia apontada por fonte, incluindo caractere '\0' que indica terminação da cadeia, no final da cadeia apontada por dest, sobrepondo primeiro caractere de fonte com caractere nulo que termina dest; retorna cadeia dest atualizada
int strcmp (const char *s1const char *s2) Comparação lexicográfica entre cadeias de caracteres, sendo retornado 0 se as cadeias são iguais, caractere a caractere, valor negativo se s1<s2, lexicograficamente, e positivo caso contrário.

O uso do atributo const na declaração de parâmetros em assinaturas de funções acima significa que o parâmetro não é modificado no corpo da função, e é usado por questão de legibilidade.

A comparação do conteúdo de duas cadeias de caracteres não pode ser feita com o operador ==, pois esse operador, se aplicado a valores de tipo char*, ou char[], testa igualdade de ponteiros, e não do conteúdo apontado pelos ponteiros.

Em C, podem ocorrer erros difíceis de serem detectados se não for seguida a convenção de que uma cadeia de caracteres termina com o caractere nulo.

As funções acima não devem ser usadas se houver sobreposição de cadeias fonte e destino, sendo o comportamento dessas funções não especificado nesses casos.

A comparação lexicográfica entre duas cadeias s1 e s2 termina assim que ocorre uma diferença, e resulta em que s1 < s2 se o tamanho de s1 é menor que o de s2 ou se o caractere diferente de s1 é menor do que o s2. A comparação entre caracteres é feita pelo valor da representação no código ASCII. Por exemplo: "ab" é menor que "abc" e que "ac", e maior que "aa" e "a".

5.5.1  Conversão de cadeia de caracteres para valor numérico

As seguintes funções da biblioteca stdlib podem ser usadas para converter uma cadeia de caracteres em um valor numérico:

 
AssinaturaSignificado
int atoi (char *)Converte cadeia de caracteres para valor de tipo int
double atof (char *)Converte cadeia de caracteres para valor de tipo double
long atol (char *)Converte cadeia de caracteres para valor de tipo long

Os significados são ilustrados em comentários no seguinte exemplo:

char *s1 = "1234"; char *s2 = "12.34"; char *s3 = " 1234"; char *s4 = "123quatro"; char *s5 = "xpto1234"; int i; float f; i = atoi(s1); // i = 1234 f = atof(s2); // f = 12.34 i = atoi(s3); // i = 1234 i = atoi(s4); // i = 123 i = atoi(s5); // i = 0

Note que:

5.5.2  Conversão para cadeia de caracteres

Para conversão de um valor numérico em uma cadeia de caracteres, a função sprintf, similar a printf, pode ser usada. Um uso de sprintf tem o seguinte formato:

sprintf(str, formato, v1, …, vn )

onde formato é um literal de tipo cadeia de caracteres de controle da operação de escrita na cadeia de caracteres str e v1,…,vn são argumentos (valores a serem escritos).

As especificações de controle em formato são feitas como no caso de printf, usando o caractere % seguido de outro caractere indicador do tipo de conversão a ser realizada.

A função sprintf tem um comportamento semelhante ao de printf, com a diferença de que os valores são escritos na cadeia de caracteres str, em vez de no dispositivo de saída padrão.

O tamanho da cadeia str deve ser suficiente para conter todos os resultados da conversão dos valores para uma cadeia de caracteres.

sprintf não pode ser usada quando se deseja o valor da cadeia de caracteres correspondente a um inteiro (pois sprintf é um comando, não retorna nenhum valor). Quando um valor é desejado, uma função pode ser definida pelo programador, ou pode ser usada a função itoa, disponível em grande parte das implementações da biblioteca stdlib, embora não seja definida na linguagem C padrão (ANSI C). A função itoa tem a seguinte assinatura:

char* itoa (int valor, char* str, int base)

itoa converte valor para uma cadeia de caracteres, terminada com o caractere NULL, usando a base base, armazena essa cadeia em str, e retorna str.

Se a base for 10 e valor for negativo, a cadeia resultante é precedida do sinal ’-’. Em qualquer outro caso, simplesmente se supõe que o valor é positivo.

str deve ser um arranjo com um tamanho grande o suficiente para conter a cadeia.

5.5.3  Passando valores para a função main

A função main pode receber como argumento várias cadeias de caracteres, separadas entre si por espaços ou outros caracteres delimitadores de palavras (como tab, i.e. '\t'). O interpretador da linha de comandos do sistema operacional, quando chamado com um nome de um programa seguido de uma cadeia de caracteres, percorre essa cadeia de caracteres colocando cada sequência de caracteres, separada da seguinte por um ou mais delimitadores, em uma posição de um arranjo que é passado como argumento para a função main, precedido do número de valores contidos neste arranjo.

Por exemplo, para um programa com nome prog, o comando:

prog abc xy 123 

faz com que o interpretador da linha de comandos percorra a cadeia de caracteres "abc xy 123" e coloque cada sequência de caracteres que está separada da seguinte por um ou mais delimitadores em uma posição de um arranjo, e passe como argumento para o método main do programa (prog) o valor 3 — que é igual ao tamanho do arranjo (número de cadeias de caracteres) — e esse arranjo. Assim, o arranjo passado contém "abc" na variável de índice 0, "xy" na variável de índice 1 e "123" na variável de índice 2.

5.5.4  Leitura de cadeias de caracteres

Em princípio, se poderia pensar que uma desvantagem de usar scanf para leitura de cadeias de caracteres é que não se pode ler cadeias de caracteres que contenham espaços (e caracteres de terminação de linha, \n’). No entanto, é possível ler espaços e e quebras de linhas usando scanf. O formato de controle da leitura usado no scanf pode ser usado para especificar tanto os caracteres válidos que podem ou não podem fazer parte da cadeia de caracteres lido. Por exemplo:

char str[100]; scanf("%[A-Z]s", str);

O formato %[A-Z]s especifica que apenas letras maiúsculas (de ’A’ a ’Z’) podem ser armazenadas em str. Ou seja, a leitura de caracteres realizada em uma chamada a scanf irá terminar quando um caractere que não seja uma letra maiúscula for encontrado.

Por exemplo, ao digitar-se "XPto", apenas "XP" vai ser armazenado em str, após o comando scanf("%[A-Z]s"str); acima. A leitura termina ao encontrar a letra minúscula t.

É possível também inserir o caractere \n’ como entrada válida a ser lida e armazenada na cadeia de caracteres; por exemplo:

char str[100]; scanf("%[A-Z a-z 0-9\n]s", str);

faz com que letras maiúsculas ou minúsculas, dígitos de ’0’ a ’9’ e o caratere de terminação de linha \n’ possam ser lidos e armazenados em str. Note: o uso da tecla ENTER (ou RETURN) não irá encerrar a leitura; a leitura será terminada apenas se for digitado algum caractere que não seja uma letra (maiúscula ou minúscula), um dígito ou o caractere de terminação de linha (com entrada feita com as teclas ENTER ou RETURN).

Os caracteres que podem ser lidos e armazenados na cadeia de caracteres podem também ser especificados como todos aqueles que precedem, na tabela ASCII, um determinado caractere, que deve suceder o caractere '^'; por exemplo:

char str[100]; scanf("%[^\n]s", str);

O comando scanf("%[^\n]s"str); acima faz com que os os caracteres lidos, com código na tabela ASCII menor que '\n', sejam armazenados em str (até que um caractere com código na tabela ASCII igual ou superior a '\n' seja lido).

Resumindo: o caractere '[' indica que a cadeia de caracteres lida pode conter todos os caracteres que estejam na tabela ASCII entre '[' e ']'. Se o primeiro caractere depois de '[' for '^', no entanto, então a cadeia de caracteres lida pode conter todos os caracteres que estejam na tabela ASCII até (e não incluindo) o primeiro caractere que segue o '^'.

Por exemplo, "%[^]ABC]s" especifica que todos os caracteres anteriores a ']' e mais ’A’, ’B’ e ’C’ podem fazer parte da cadeia lida. Para ser incluído, um hífen deve ser o último caractere da cadeia de caracteres que segue o '[' (i.e. o caractere anterior ao ']'. Por exemplo, "%[ABC-]s" indica que podem ser lidos os caracteres ’A’, ’B’, ’C’ e '-'. Se ocorrer entre dois caracteres dentro do formato que inicia com o caractere '[', o hífen indica a faixa entre esses dois caracteres. Por exemplo: "%[A-Za-z0-9]s\" indica letras maiúsculas (de ’A’ a ’Z’), letras minúsculas (de ’a’ a ’z’) e dígitos (de ’0’ a ’9’).

A cadeia de caracteres lida por scanf sempre termina com o caractere '\0'.

5.5.5  Exercícios Resolvidos

  1. Escreva um programa que leia um valor inteiro positivo n, em seguida uma cadeia de caracteres de tamanho menor que n e imprima a frequência de todos os caracteres que ocorrem nesta cadeia.

    Por exemplo, para a entrada:

    10
    112223a

    a saída deve ser:

    1 aparece 2 vezes
    2 aparece 3 vezes
    3 aparece 1 vez
    a aparece 1 vez

    A ordem de impressão dos caracteres e sua frequência não é importante, mas cada caractere deve aparecer, com sua frequência de ocorrência, apenas uma vez na saída, e somente se essa frequência for diferente de zero.

    Solução: Cada caractere é representado no código ASCII por um valor inteiro compreendido entre 0 e 127. Para armazenar a informação sobre o número de ocorrências de cada um desses caracteres, podemos usar um arranjo de valores inteiros com 128 componentes, fazendo corresponder a cada caractere representado pelo número inteiro i a posição i desse arranjo. Tal arranjo pode ser declarado da seguinte forma:

    int max = 128;\\ int *contchar = malloc(max * sizeof(char));

    Um trecho de programa que armazena em contchar[i] o número de ocorrências de cada caractere de uma cadeia de caracteres str — onde o fim da cadeia str é indicado pela existência do caractere '\0' — pode ser escrito como a seguir:

    void contFreqChar (const char str[], int contChar[]) { int i; for (i=0; str[i] != '\0'; i++) contChar[str[i]]++; }

    O uso do atributo const na declaração do parâmetro str da função contFreqChar é usado apenas por questão de legibilidade, para indicar que a cadeia de caracteres str não é modificada no corpo da função.

    Um exemplo de definição de uma função main que usa a função contFreqChar é mostrada a seguir. Os valores de entrada são lidos, a função contFreqChar é chamada e a frequência de cada caractere que ocorre na cadeia de caracteres especificada na entrada é impressa.

    int main() { int tam; scanf("%d", &tam); char *s = malloc (tam * sizeof(char)); scanf("%s",s); printf("Na cadeia de caracteres %s\n", s); int max = 128, *contChar = malloc (max * sizeof(int)), i, freq; for (i=0; i<max; i++) contChar[i] = 0; contFreqChar(s, contChar); for (i=0; i<max; i++) if (contChar[i] != 0) { freq = contChar[i]; printf("%c aparece %d %s\n",(char)i, freq, freq==1?"vez":"vezes"); } }
  2. Escreva um programa para determinar a letra ou algarismo que ocorre com maior freqüência em uma cadeia de caracteres dada, com um tamanho máximo previamente fornecido, e imprimir esse algarismo no dispositivo de saída padrão.

    Solução: Um caractere alfanumérico é um caractere que pode ser um algarismo ou uma letra. A solução consiste em, inicialmente, determinar a freqüência de ocorrência de cada caractere alfanumérico, de maneira análoga à do exercício anterior. Ou seja, a solução armazena cria arranjos com componentes correspondentes a cada caractere alfanumérico. Cada componente contém a frequência de ocorrência do caractere alfanumérico correspondente. Em seguida, o índice do componente de maior valor (isto é, maior frequência) desse arranjo é determinado e o caractere alfanumérico correspondente a esse índice é impresso. Vamos usar três arranjos, um arranjo para dígitos — com índices de 0 a 9 —, e os outros para letras minúsculas e maiúsculas. O índice do arranjo de dígitos correspondente a um dígito d é obtido por d - ’0’, usando o fato de que a representação dos dígitos são crescentes, a partir do valor da representação do caractere ’0’. Analogamente para letras minúsculas e maiúsculas, que têm valores de representação crescentes a partir, respectivamente, dos valores das representações dos caractere ’a’ e ’A’.

    #include <stdio.h> #include <stdlib.h> #include <ctype.h> // define isdigit int letraMinusc(char c) { return (c >= 'a' && c <= 'z'); } int letraMaiusc(char c) { return (c >= 'A' && c <= 'Z'); } void contFreqAlfaNums (const char s[], int contDigLets[]) { int i, tamDigs = 10, tamLets = 26; char c; for (i=0; s[i] != '\0'; i++) { c = s[i]; if (isdigit(c)) contDigLets[c - '0']++; else if (letraMinusc(c)) contDigLets[tamDigs + (c - 'a')]++; else if (letraMaiusc(c)) contDigLets[tamDigs + tamLets + (c - 'A')]++; } } int maisFreq (const int freq[], int tam) { int i, maxi, maxFreq = 0; for (i=0; i<tam; i++) if (freq[i] > maxFreq) { maxi = i; maxFreq = freq[maxi]; } return maxi; } char alfaNumMaisFreq (const char s[]) { int tamDigs = 10, tamLets = 26, tam = tamDigs + 2*tamLets, *contAlfaNums = malloc (tam * sizeof(int)), i; for (i=0; i<tam; i++) contAlfaNums[i] = 0; contFreqAlfaNums (s, contAlfaNums); i = maisFreq (contAlfaNums, tam); return (i<tamDigs ? i + '0' : i<tamDigs + tamLet ? i + 'a' : i + 'A'); } int main() { int tamstr; scanf("%d", &tamstr); char *str = malloc (tamstr * sizeof(char)); scanf("%s", str); printf("Caractere alfanumerico mais frequente em %s e': %c\n", str, alfaNumMaisFreq (str, tamstr)); }

    A função maisFreq recebe como argumento uma cadeia de caracteres, em que cada caractere representa um algarismo, e retorna o algarismo mais freqüente nessa cadeia. Quando existir mais de um algarismo com a maior freqüência de ocorrência, o método retorna, dentre esses, aquele que ocorre primeiro na cadeia. Por exemplo, maisFreq("005552") retorna ’5’, e maisFreq("110022") retorna ’1’.

  3. Escreva um programa para resolver o exercício 15, considerando a condição de que inteiros podem ter até 1000 dígitos decimais.

    Solução: Usamos uma cadeia de caracteres de até 1001 dígitos para armazenar inteiros que podem ter até 1000 dígitos, e mais o caractere '\0' para indicar terminação da cadeia. A soma dos dígitos de um inteiro de até 1000 dígitos sempre pode ser armazenada em um valor de tipo int.

    #include <stdio.h> #include <stdlib.h> int somaDigsInt (int n) { return somaDigsInt1(n,0); } int somaDigsInt1 (int n, int soma) { return (n==0 ? : soma : somaDigsInt (n/10, n%10 + soma); } int somaDigs (char* digs, int i) { return (digs[i] == '\0' ? 0 : (digs[i] - '0') + somaDigs(digs,i+1); } int grau9Int (int n) { if (n <= 9) return (n == 9 ? 1 : 0); else { int grau = grau9Int (somaDigsInt(n)); return (grau == 0 ? grau : 1 + grau); } } int grau9 (char *digs) { return grau9Int (somaDigs (digs,0)); } int main() { const int numDigs = 1001; char v[numDigs]; while (1) { scanf("%s", &v); int i=0; while (i < numDigs && v[i] == '0') i++; if (i < numDigs && v[i]=='\0') break; // Termina se inteiro lido igual a zero int grau9v = grau9(v); printf("%s is%s a multiple of 9", v, grau9v==0 ? " not" : "");\\ printf (grau9v==0 ? "\n" : " and has 9-degree \%d$\backslash$n",{\it grau9v\/});\\ \hspace*{.2cm} \}\\ \hspace*{.2cm} return 0;\\ }

5.5.6  Exercícios

  1. Escreva uma função inverte que estenda o Exercício 5 da seção 3.12 para qualquer cadeia de caracteres s. A função inverte tem como parâmetro adicional (além da cadeia de caracteres) o tamanho n da cadeia passada como argumento. O programa que chama a função inverte deve ler, antes de cada cadeia de caracteres, um valor que, deve-se supor, é maior que o tamanho da cadeia.
  2. Defina uma função decPraBin que receba um número inteiro não-negativo como argumento e retorne uma cadeia de caracteres que é igual à representação desse número em notação binária.

    Por exemplo, ao receber o número inteiro 8, a função deve retornar "1000".

    Para calcular o tamanho da cadeia de caracteres a ser alocada, defina e use uma função numDiv2 que, ao receber um número inteiro positivo n como argumento, retorne m+1 tal que 2mn < 2m+1. Esse número (m+1) é igual ao número de vezes que n pode ser dividido por 2 até que o quociente da divisão seja zero. Por exemplo, 23 ≤ 8 < 24, e 4 é o número de caracteres da cadeia "1000", necessários para representação de 8 na base 2.

    Note que, para cada n, deve ser alocada uma cadeia de caracteres de tamanho tam+2, onde tam é o resultado de numDiv2(n), para conter, além dos caracteres necessários para representação de n na base 2, o caractere '\0' (usado em C para terminação de cadeias de caracteres).

    Escreva um programa que leia vários números inteiros positivos do dispositivo de entrada padrão e imprima, para cada inteiro lido, a sua representação em notação binária, usando a função definida no item anterior (o programa que contém a função main deve conter também a definição das funções definidas acima).

    A execução deve terminar quando um inteiro negativo ou zero for lido.

  3. Defina uma função que receba como argumento um número inteiro não-negativo b, em notação binária, e retorne o valor inteiro (de tipo int) correspondente, em notação decimal.

    Por exemplo, ao receber o número inteiro 1000, a função deve retornar o valor 8.

    Seu programa pode ler b como um valor inteiro — e supor que o valor lido pode ser armazenado como um valor de tipo int — ou como uma cadeia de caracteres — e supor que o tamanho máximo da cadeia é de 30 dígitos binários.

    No caso de leitura como um valor de tipo int, cada dígito deve ser obtido como resto de divisão por 10. Por exemplo, para obter cada dígito de 101, obtenha o 1 mais à direita como resto da divisão de 101 por 10; depois obtenha o quociente da divisão de 101 por 10 (que é igual a 10) e repita o processo.

    Escreva um programa que leia, do dispositivo de entrada padrão, várias cadeias de caracteres que representam números inteiros positivos em notação binária, e imprima, para cada valor lido, a sua representação em notação decimal, usando a função definida acima (o programa que contém a função main deve conter também a definição da função definida acima).

    A execução deve terminar com o fim dos dados de entrada.

  4. Escreva um programa que leia, do dispositivo de entrada padrão, um valor inteiro positivo t, em seguida uma cadeia de caracteres s de tamanho menor que t e, em seguida, várias cadeias de caracteres s1, …, sn, também com tamanho menor que t, e imprima, para cada cadeia si, para i entre 1 e n, uma mensagem que indica se s contém si ou não. A entrada deve terminar com o fim dos dados de entrada, isto é, quando fim-de-arquivo for detectado; em entrada interativa, quando scanf retornar -1, devido ao fato de o usuário digitar Control-d (no Unix) ou Control-z (no Windows).

    Por exemplo, se a entrada for:

    1000
    abcdefghijklmnopqrstuvwxyz123456789
    nopqr
    789
    xya
    abc

    a saída deve ser uma mensagem como a seguir:

    nopqr Sim
    789 Sim
    xya Nao
    abc Sim
  5. Escreva um programa que leia um inteiro positivo n, em seguida vários pares s1, s2 de cadeias de caracteres, de tamanho menor que n, e imprima, para cada par lido, uma cadeia de caracteres que é a concatenação de s1 e s2 (a concatenação de s1 com s2 é a cadeia formada pelos caracteres de s1 seguidos pelos caracteres de s2).

    Não se esqueça de considerar que, em C, cadeias de caracteres são armazenadas de modo a terminar com o caractere '\0'.

  6. Escreva um programa que leia um valor n, duas cadeias de caracteres s1 e s2, ambas com tamanho menor que n, e imprima o resultado de remover de s2 todos os caracteres que aparecem em s1.

    Por exemplo, para a entrada:

    100
    abci adefghiabaf

    A saída deve ser:

    defghf

5.6  Arranjo de arranjos

Considere o trecho de programa a seguir:

int **a, n=4, m=3; a = malloc(n * sizeof(int*)); int i; for (i=0; i<n; i++) a[i] = malloc(m * sizeof(int));

Após a execução desse trecho de programa, a representa um arranjo com quatro componentes, sendo cada componente um arranjo com três componentes. Tal arranjo é algumas vezes chamado de uma matriz, no caso uma matriz 4 por 3. Um arranjo de arranjos é também chamado de arranjo multidimensional.

Um arranjo pode ter como componentes arranjos de tamanhos diferentes, alocados dinamicamente, como ilustra o exemplo a seguir.

int **a; a = malloc(2 * sizeof(int*)); … a[0] = malloc(10 * sizeof(int)); … a[1] = malloc(40 * sizeof(int)); …

O arranjo a é um arranjo, alocado dinamicamente, de tamanho 2, contendo dois arranjos alocados dinamicamente, sendo que o primeiro deles, a[0], tem 10 componentes, enquanto o segundo, a[1], tem 40 componentes.

5.6.1  Passagem de Arranjos Multidimensionais como Parâmetros

O modo mais simples, mas não muito flexível, de passar um arranjo multidimensional como parâmetro é usar tipos estáticos na declaração do parâmetro e do argumento. Por exemplo, podemos declarar:

int f (int arr[2][3]) { \\ … } int a[2][3]; … f(a) …

A definição da função f acima só funciona para argumentos que são arranjos formados por 2 arranjos de 3 inteiros. Se quisermos usar argumentos cujo tipo são arranjos de tamanhos estáticos, só podemos deixar a primeira dimensão (o número de linhas) não especificado, em C; veja o exemplo a seguir:

int f (int (*arr)[3], int tam) { … } int a[2][3]; … f(a) …

Ao definir um parâmetro para f de tipo que é um ponteiro para ponteiros de valores de tipo int, não podemos usar a, de tipo int [2][3], como argumento (porque a não é um arranjo de ponteiros; cada componente de a não é um ponteiro, mas um arranjo de 3 inteiros). Uma maior flexibilidade para o uso de f requer que o tipo do parâmetro seja um ponteiro para ponteiros, e o argumento seja um arranjo dinâmico, como mostrado a seguir:

int f (int **arr, int numLin, int numCol) { … } int** a; int nlins = 2, ncols = 3; a = malloc(nlins * sizeof(int*)); … a[0] = malloc(ncols * sizeof(int)); … a[1] = malloc(ncols * sizeof(int)); f(a) …

5.7  Inicialização de Arranjos

Variáveis devem em geral ser inicializadas na sua declaração. Do contrário, o valor armazenado será definido pelo valor que estiver na memória, durante a execução do programa, quando a variável é criada. Isso pode provocar a ocorrência de erros em outras partes do programa, que podem ser difíceis de detectar.

Para variáveis de tipo arranjo, existe uma notação especial em C, que infelizmente só pode ser usada em declarações de variáveis, que consiste em enumerar, entre os caracteres e , todos os valores componentes do arranjo, separados por vírgulas.

Por exemplo, pode-se escrever:

char* diasDaSemana[] = { "Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sab" };

para declarar uma variável diasDaSemana que é um arranjo de 7 componentes, sendo cada componente uma cadeia de caracteres. O tamanho do arranho não precisa ser especificado, sendo determinado automaticamente de acordo com o número de componentes especificado no valor usado para inicialização do arranjo. Ou seja, a declaração acima é equivalente pode ser feita como a seguir:

char* diasDaSemana[7] = { "Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sab" };

No entanto, se o tamanho for explicitamente especificado, como acima, a variável de tipo arranjo não poderá posteriormente conter um arranjo com tamanho diferente de 7, ou seja, a variável só poderá ser indexada com valores entre 0 e 6.

Em casos em que não se pode inicializar um arranjo no instante de sua declaração (pois o valor inicial ou o tamanho de um arranjo ainda não são conhecidos), é em geral conveniente inicializar a variável de tipo arranjo com o valor NULL (ponteiro nulo).

5.8  Exercícios Resolvidos

  1. Escreva um programa que leia notas de alunos obtidas em tarefas de uma certa disciplina, e imprima a nota total de cada aluno e a média das notas dos alunos nessa disciplina. A entrada dos dados contém, primeiramente, o número n de alunos da turma, depois o número k de tarefas da disciplina, e em seguida as k notas de cada aluno i, para i de 1 a n. Cada valor é separado do seguinte por um ou mais espaços ou linhas.

    Solução: A solução define uma função calcNotas que recebe um arranjo com as notas de cada aluno em cada tarefa, e retorna um arranjo com as notas finais de cada aluno. Em C, o arranjo passado como argumento, alocado dinamicamente, é um ponteiro para ponteiros-para-inteiros.

    Outra função, chamada media, recebe um arranjo de inteiros (em C, um ponteiro para inteiros) e o tamanho do arranjo, e retorna um valor de tipo float que é igual à média dos inteiros contidos no arranjo.

    Na função main, um arranjo de notas de cada aluno em cada avaliação é preenchido com valores lidos, o método calcNotas é chamado para cálculo das notas finais, e as notas finais calculadas, assim como a média, calculada por chamada à função media, são impressas.

    As notas de cada aluno são representadas em C como ponteiros para ponteiros-para-inteiros. Para i de 1 a n, as notas do aluno i são armazenadas em um arranjo notas[i-1] (pois o índice de arranjos na linguagem C começa com zero), e para cada tarefa j de 1 a k a nota do aluno i na tarefa j é armazenada em notas[i-1][j-1].

    #include <stdio.h> #include <stdlib.h> int* calcNotas (int **notas, int numAlunos, int numAvaliacoes) { int i, j, *notasFinais; notasFinais = malloc(numAlunos * sizeof(int)); // Inicializa notas finais de cada aluno com 0 for (i=0; i < numAlunos; i++) notasFinais[i] = 0; for (i=0; i < numAlunos; i++) for (j=0; j < numAvaliacoes; j++) notasFinais[i] += notas[i][j]; return notasFinais; } float media(int vs[], int n) { int i, soma=0; for (i=0; i<n; i++) soma += vs[i]; return ((float)soma)/n; } int main() { int n,k; scanf("%d", &n); scanf("%d", &k); int **notas = malloc(n*sizeof(int*)); int i,j; for (i=0; i<n; i++) { notas[i] = malloc(k*sizeof(int)); for (j=0; j<k; j++) scanf("%d",&(notas[i][j])); } int *notasFinais = calcNotas(notas,n,k); for (i=0; i<n; i++) printf("Nota final do aluno %d = %d\n", i+1, notasFinais[i]); printf("Media da turma = %f", media(notasFinais,n)); }

5.9  Exercícios

  1. Para tentar descobrir se um dado era viciado, os resultados de lançar o dado foram armazenados. Escreva um programa que leia um valor inteiro positivo n, que denota o número de faces do dado, em seguida os resultados de lançar o dado — valores inteiros de 1 a n —, e imprima o número de ocorrências de cada face do dado (cada número separado do seguinte por um espaço). A leitura deve ser feita do dispositivo de entrada padrão e deve terminar com o fim dos dados de entrada (indicado por EOF).
  2. Considere que uma empresa comercial, que tem n lojas especializadas de certo tipo de material, te contratou para fazer o seguinte programa em C.

    A empresa tem dados armazenados sobre o número de vendas realizadas em cada loja. Não importa qual tipo de material, a empresa está interessada apenas no número de unidades vendidas.

    A empresa quer um programa que leia, do dispositivo de entrada padrão, o valor de n, em seguida n valores v1, …, vn que correspondem ao número de unidades vendidas em um mês nas lojas de 1 a n, respectivamente, e imprima, no dispositivo de saída padrão, quais foram as lojas de 1 a n nas quais o número de unidades vendidas foi maior ou igual à média de unidades vendidas em suas lojas.

  3. Escreva um programa que leia um texto qualquer, caractere a caractere, e imprima:
  4. Escreva um programa que leia um valor inteiro positivo n, em seguida uma matriz quadrada n × n de valores inteiros, e imprima uma mensagem indicando se a matriz quadrada é ou não um quadrado mágico.

    Um quadrado mágico é uma matriz quadrada na qual a soma dos números em cada linha, coluna e diagonal é igual.

  5. Os votos de uma eleição são representados de modo que 0 significa voto em branco, 1 a n significam votos para os candidatos de números 1 a n, respectivamente, e qualquer valor diferente desses significa voto nulo.

    A eleição tem um vencedor se o número de votos em branco mais o número de votos nulos é menor do que 50% do total de votos, sendo vencedores, nesse caso, todos os candidatos com número de votos igual ao maior número de votos.

    Escreva um programa que leia um número positivo n, que indica o número de candidatos de uma eleição, em seguida leia cada um dos votos de uma eleição, e determine se há um vencedor e, em caso positivo, determine o número de vencedores da eleição, quais são esses vencedores e o número de votos dos mesmos.

  6. Escreva um programa que leia um valor inteiro positivo n, em seguida um valor inteiro positivo k, depois uma sequência s de k valores inteiros diferentes de zero, cada valor lido separado do seguinte por um ou mais espaços ou linhas, e imprima n linhas tais que: a 1a linha contém os valores de s divisíveis por n, a 2a linha contém os valores de s para os quais o resto da divisão por n é 1, etc., até a n-ésima linha, que contém os valores de s para os quais o resto da divisão por n é n−1.

    Dica: use um arranjo de n posições com elementos que são arranjos de k valores inteiros, sendo um valor igual a zero indicativo de ausência de valor naquela posição.

    Exemplo: Considere a entrada:

    4 5 12 13 15 16 17

    Para essa entrada, a saída deve ser como a seguir:

    Valores divisiveis por 4: 12 16
    Valores com resto da divisao por 4 igual a 1: 13 17
    Valores com resto da divisao por 4 igual a 2:
    Valores com resto da divisao por 4 igual a 3: 15
  7. Reescreva o programa referente a impressão do Triângulo de Pascall (exercício 13 do capítulo anterior), usando um arranjo e o fato de que cada valor em uma posição j (diferente da primeira, que é 1) de qualquer linha (diferente da primeira) do Triângulo de Pascal pode ser obtido somando-se os valores nas posições j e j−1 da linha anterior.

    Por exemplo, o valor contido na terceira coluna da linha correspondente a n=5 no Triângulo de Pascal é 10. Ele é igual a 6+4: 6 é o valor na mesma coluna da linha anterior (correspondente a n=4), e 4 o valor anterior a 6 nesta mesma linha (correspondente a n=4).

  8. Escreva um programa que leia um número inteiro positivo n, em seguida uma seqüência com n números inteiros x0, …, xn−1, e imprima a soma dos números da sub-sequência dessa sequência que tem soma máxima e o índice do primeiro valor nessa sub-sequência. O primeiro índice da sequência é 0 e o último n−1.

    Por exemplo, para a entrada:

    11 2 -2 -7 3 14 10 -3 9 -6 4 1

    a saída deve ser 33 3.

    Isso porque a sub-sequência que tem soma máxima é 3 14 10 -3 9.

  9. Escreva um programa que leia, repetidamente, do dispositivo de entrada padrão, os seguintes valores, nesta ordem:
    1. um número inteiro positivo n,
    2. n inteiros positivos v1, …, vn,
    3. 3 inteiros positivos i, k, m.

    O programa deve imprimir, para cada n lido, uma linha com os valores vi, vi+k, vi+2× k, …, vi+p× k tais que i+p × km. Os valores devem ser separados por espaços e impressos no dispositivo de saída padrão.

    Por exemplo, para a entrada:

    10 11 25 33 40 50 69 73 85 96 101 3 2 7 0

    a saída deve ser:

    33 50 73

    Isso ocorre porque o primeiro valor impresso é v3 (i=3), o seguinte é v5 (k=2, 5 = i+k), e o seguinte e último é v7 (m = 7 = i+2 × k).

  10. Escreva um programa que leia um texto qualquer, caractere a caractere, e imprima a frequência de cada letra, maiúscula e minúscula, que ocorre no texto.
  11. Escreva um programa que leia, repetidamente, um valor inteiro n, em seguida, se o valor for positivo, uma sequência de caracteres de tamanho máximo n em uma linha da entrada padrão, e imprima, para cada linha lida, uma linha com os mesmos caracteres mas com as letras minúsculas transformadas em letras maiúsculas. O programa deve terminar quando o valor n lido for menor ou igual a zero.

    Escreva o seu programa usando a função capitaliza, definida abaixo para você. Essa função recebe um valor de tipo char e, se o valor for uma letra minúscula, retorna a letra maiúscula correspondente, senão o próprio valor recebido. A função capitaliza usa uma função minusc, que você deve definir, que recebe um valor de tipo char e retorna se esse valor é ou não uma letra minúscula, ou seja, se é ou não um valor maior ou igual a ’a’ e menor ou igual a ’z’.

    char capitaliza (char c) {
      return (minusc(c) ? c - ’a’ + ’A’ : c);
    }

    Por exemplo, se a entrada for:

    10
    ab1
    10 ab*cd!
    0

    a saída deve ser:

    AB1
    AB*CD!
  12. Escreva um programa que leia, do dispositivo de entrada padrão, um texto qualquer, caractere a caractere, e imprima, no dispositivo de saída padrão, i) o número de palavras que têm um gênero, masculino ou feminino, ii) o número de palavras do gênero masculino, e iii) o número de palavras do gênero feminino.

    Você pode considerar, para simplificar o problema, que:

    Dica: Use a função getchar para ler um caractere, e use o valor retornado por getChar para verificar fim dos dados de entrada: getChar retorna o valor EOF (igual a -1) para indicar fim dos dados.

  13. Escreva um programa que leia, caractere a caractere, do dispositivo de entrada padrão, um texto qualquer, e imprima, no dispositivo de saída padrão, o número de palavras do texto.

    Dica: para contar o número de palavras, use uma variável booleana para indicar se a posição corrente de leitura corresponde a uma posição interna a uma palavra ou externa. Uma posição fora de uma palavra é uma posição correspondente a um delimitador.

    Lembre-se que, em C, uma variável booleana é uma variável inteira: o valor 0 representa falso e qualquer valor diferente de zero verdadeiro

  14. Estenda o exercício 12 para imprimir também o número total de palavras do texto.
  15. Um armazém trabalha com n mercadorias diferentes, identificadas por números de 1 a n.

    Cada funcionário do armazém tem salário mensal estipulado como 20% do total da receita que ele consegue vender, mais um bônus igual a 10% da receita obtida com a mercadoria que teve a maior receita com as vendas.

    Escreva um programa em C para calcular o valor do salário de um funcionário em um determinado mês; o programa deve ler o valor n que indica o número de mercadorias, em seguida uma sequência de pares i seguido de vi, sendo i um número de mercadoria (de 1 a n) e vi uma receita obtida no mês pelo funcionário por uma operação de venda da mercadoria i, e imprimir o salário desse funcionário, nesse mês.

    Por exemplo, se a entrada for:

    3
    1 10.0
    2 20.0
    3 30.0
    1 30.0

    a saída deve ser: 22.0

    Isso ocorre porque o total das vendas foi 90.0 e a maior receita por mercadoria, obtida com a mercadoria 1, foi 40.0 (30.0 + 10.0), e 90.0 * 0.2 (vinte por cento do total de vendas) somado com 0.1 * 40.0 (dez por cento da total de vendas com a mercadoria mais vendida) é igual a 22.0.

5.10  Notas Bibliográficas

Existe uma vasta literatura sobre algoritmos e estruturas de dados em computação, que estendem o que foi abordado neste capítulo principalmente com o estudo mais detalhado de algoritmos para busca, inserção, remoção e ordenação de valores nessas estruturas de dados. Além de aspectos de implementação de estruturas de dados e de operações para manipulação das mesmas, essa literatura aborda em geral diferentes aplicações dos algoritmos e discute também aspectos de eficiência (ou complexidade, como é usual dizer em computação) de algoritmos.

Livros didáticos dedicados a esses temas incluem [27, 22, 15, 19], os dois primeiros já traduzidos para a língua portuguesa, o quarto escrito em português e o terceiro merece ser destacado. [9] é exemplo de um livro que adota a abordagem de programação funcional.


Previous Up Next