Strings

Programação Estruturada

Victor S. Portella

UFABC

Questões logísticas

Primeira prova (P1) - 27 de outubro

  • A prova será feita no papel, com papel e caneta;
  • Vou reservar uma sala de aula convencional para a aplicação da prova;
  • A matéria será todo o conteúdo visto desde o início do curso até a aula de hoje, incluindo:
    • matrizes;
    • strings;
    • recursão.
  • Apesar de precisar escrever código, foco vai ser em compreeensão de código e de ideias fundamentais.

Arquivos .h no EP1

Como um arquivo .c conhece outro?

Cada arquivo .c é compilado separadamente.

Em desenho.c, uma função é implementada:

void linha_horizontal(
    unsigned char img[][MAX_DIM],
    int x, int y,
    int comprimento,
    unsigned char cor) {
    // implementação
}

Mas fractal.c quer usá-la:

void cantor(/* ... */) {
    // ...
    linha_horizontal(/* ... */);
}

Como o compilador saberá que essa função existe?

desenho.h declara/avisa o que desenho.c oferece para os outros arquivos.

Incluindo esse aviso

Os arquivos que precisam dessa informação incluem o mesmo cabeçalho:

desenho.h

void linha_horizontal(
    unsigned char img[][MAX_DIM],
    int x, int y,
    int comprimento,
    unsigned char cor);

desenho.c e fractal.c

#include "desenho.h"


O comando #include torna as declarações em .h disponíveis antes da compilação do arquivo .c.

Detalhes sobre diretivas de pré-processador

Podemos pensar nas linhas que começam com # são diretivas do pré-processador, processadas antes da compilação propriamente dita.

  • #include inclui um cabeçalho
  • #define define uma constante/macro
  • #ifndef e #endif processam um trecho apenas sob determinada condição.

No EP1, a combinação de #ifndef, #define e #endif evita que o mesmo .h seja incluído mais de uma vez.


Os arquivos .h não são compilados separadamente: compilamos os .c que os incluem.

Strings

Strings

  • Não há o tipo String em C;
  • Para representar uma String em C, usamos um vetor de char.
#include <stdio.h>

int main() {
    char nome[50] = "Hello World";
    printf("%s\n", nome);

    return 0;
}

Representação de uma String

char texto[] = "Hello World";

Na memória, string é um vetor de char:

0 1 2 3 4 5 6 7 8 9 10 11
H e l l o ␣ W o r l d \0

Toda string em C termina com o caractere nulo '\0'.

Criando uma string caractere a caractere

#include<stdio.h>

int main() {
    char universidade[5];
    universidade[0] = 'U';
    universidade[1] = 'F';
    universidade[2] = 'A';
    universidade[3] = 'B';
    universidade[4] = 'C';

    printf("%s\n", universidade);

    return 0;
}

Veja que usamos aspa simples para representar caracteres!

O que será impresso?

Vamos criar uma string caractere a caractere

Resultado (em alguns compiladores)

UFABCw▌\▒a■   ║k1w¡l1w└@


Faltou colocarmos o caractere \0 indicando o final da String!

Muitas vezes a impressão não vai sair quebrada como acima. Usar a flag -fsanitize=address vai apontar acesso em local inválido da memória.

Lendo Strings

Para ler Strings sem espaços (␣, \t, \n,), passamos o vetor para o scanf:

int main() {
    char universidade[50];
    scanf("%s", universidade);
    printf("%s\n", universidade);
    return 0;
}

Apesar de ser o scanf, observe que aqui não usamos o “&”.

Identificador do vetor sem os colchetes representa o endereço do primeiro elemento.

Lendo Strings

Como universidade == &universidade[0], o seguinte código funciona:


int main() {
    char universidade[50];

    scanf("%s", &universidade[0]);
    printf("%s\n", universidade);

    return 0;
}

gets e puts

Funções para ler e imprimir strings

  • gets: lê uma string até \n ou EOF (End Of File) (substituidos por \0);
  • puts: imprime uma string e quebra a linha.
#include <stdio.h>

int main() {
    char texto[20];

    gets(texto); // Le texto
    puts(texto); // Imprime texto

    return 0;
}

fgets

O fgets é parecido com gets, mas limita a quantiade de caracteres lida

#include <stdio.h>

int main() {

    printf("Digite uma frase: ");

    char frase[6];
    fgets(frase, 6, stdin);
    puts(frase);

    return 0;
}
Digite uma frase: UFABC
UFABC

Além do limite de fgets

Digite uma frase: Universidade
Unive

Pegou apenas os 5 primeiros caracteres!


É recomendável utilizar o fgets ao invés do gets! O gets é insegura, pode sobrescrever pedaços arbitrários da memória.

O fgets lê até o primeiro \n ou EOF

fgets e puts

  • fgets: lê uma string com um limite de caracteres;
  • puts: imprime uma string e quebra a linha.
#include <stdio.h>

int main() {
    char texto[20];

    fgets(texto, 20, stdin);
    puts(texto);

    return 0;
}

Qual a diferença para o scanf e printf?

Há alguma diferença entre esses dois programas?

#include <stdio.h>

int main() {
    char texto[20];

    scanf("%s", texto);
    printf("%s\n", texto);

    return 0;
}
#include <stdio.h>

int main() {
    char texto[20];

    fgets(texto, 20, stdin);
    puts(texto);

    return 0;
}
  • scanf("%s", texto) ignora espaços em branco iniciais e para de ler quando encontra o próximo espaço em branco.
  • fgets(texto, n, stdin) lê no máximo n - 1 caracteres, parando antes disso se encontrar \n ou EOF. Se encontrar \n, ele é armazenado na string.

Biblioteca string.h

A biblioteca string.h tem várias funções úteis:

  • strlen devolve o tamanho da string
  • strcmp compara duas strings já que não podemos usar <, <=, >, >=, == e !=
  • strcpy copia uma string
  • strcat concatena duas strings

… e várias outras. Veja o manual para a documentação (ex: man strlen)

Não confunda com a biblioteca strings.h

Cifra de César

Na cifra de César, cada letra é substituída pela letra que está n posições à frente no alfabeto.

Para n = 2:

A → C    B → D    ...    X → Z    Y → A    Z → B

Assim, a mensagem

O vento hoje estava muito forte

torna-se:

Q xgpvq jqlg guvcxc owkvq hqtvg

Espaços e outros caracteres que não são letras permanecem inalterados.

Tabela ASCII e p tipo char

Em C, um char armazena um pequeno valor inteiro. A tabela ASCII (American Standard Code for Information Interchange) associa um número a cada caractere.

Caractere ASCII Caractere ASCII
'A' 65 'a' 97
'B' 66 'b' 98
... ... ... ...
'Z' 90 'z' 122

ASCII possui 128 códigos, de 0 a 127. Letras acentuadas, como á e ç, não fazem parte da tabela ASCII original. Número negativos em char são usados em outras tabelas.

Aritmética com caracteres

O mesmo valor pode ser interpretado como um caractere ou como um número:

char letra = 'O';

printf("%c\n", letra);  // O
printf("%d\n", letra);  // 79

No ASCII, as letras maiúsculas e minúsculas formam duas sequências. Assim, podemos fazer operações aritméticas com elas:

char cifrada = letra + 2;
printf("%c\n", cifrada);  // Q

E se tivéssemos letra = Z?

Da letra para uma posição

Subtrair 'a' transforma uma letra minúscula em uma posição entre 0 e 25:

'a' - 'a' = 0
'b' - 'a' = 1
       ...
'z' - 'a' = 25

Depois, deslocamos essa posição e voltamos a somar 'a':

int posicao = letra - 'a';
int nova_posicao = (posicao + n) % 26;
char cifrada = 'a' + nova_posicao;

O operador % 26 faz a contagem voltar ao começo do alfabeto para valores >= 26.

Cifrando e decifrando a crifra de Cesar

Para cada caractere da string:

  1. se estiver entre 'a' e 'z', use 'a' como base;
  2. se estiver entre 'A' e 'Z', use 'A' como base;
  3. caso contrário, mantenha o caractere como está.

Para cifrar, avançamos n posições:

nova_posicao = (posicao + n) % 26;

Para decifrar, fazemos o caminho inverso:

nova_posicao = (posicao - n + 26) % 26;

Exercício 1 – Cifra de César

  • Faça uma função para criptografar uma frase com a cifra de César sobre uma string somente com letras minusculas entre a e z;
  • Faça também uma função para reverter o processo.

Como seria o protótipo dessas funções?

void encrypt(char texto_secreto[], int n);
void decrypt(char texto_protegido[], int n);

Exercício 2 – remover a segunda palavra de uma frase

Faça uma função que receba uma frase e remova a segunda palavra de uma frase.

Frase legal de teste -> Frase de teste

Ideia de assinatura:

void remove_segunda_palavra(char frase[], int comp);

Podemos fazer esta função sem o parâmetro de comprimento?

Sim, nesse caso, teríamos que encontrar o caractere '\0' primeiro (ele indica o fim da string).

Exercício 3 – remover a palavra de índice i de uma frase

Faça uma função que receba uma frase e remova a palavra de índice i de uma frase (considere que a primeira palavra tem índice 0).

void remove_palavra(char frase[], int i, int comp);

Referência

Slides originais: Prof. Paulo Henrique Pisani, Strings e Matrizes, Programação Estruturada, UFABC, setembro/2021.