Processamento de Strings

Lição 19: Processamento de Strings

🎯 Analogia da Vida

Imagine que você é um bibliotecário. Todos os dias você lida com uma grande quantidade de trabalho com texto:

O processamento de strings é o "trabalho com texto" nos programas — quase todo programa precisa lidar com texto.


Conceitos Centrais

Go fornece várias bibliotecas padrão para manipulação de strings:

Pacote Propósito Funções Comuns
strings Pesquisa, substituição, divisão, junção de strings, etc. Contains, Replace, Split, Join, Trim
strconv Conversão entre strings e outros tipos Atoi, Itoa, ParseBool, FormatFloat
unicode/utf8 Operações relacionadas à codificação UTF-8 RuneCountInString, ValidString
strings.Builder Concatenação eficiente de muitas strings WriteString, String

Pontos-chave:

  1. Strings em Go são imutáveis — qualquer modificação cria uma nova string
  2. Strings em Go são sequências de bytes codificadas em UTF-8 internamente
  3. len(str) retorna o número de bytes, não de caracteres
  4. rune é o tipo do Go para representar pontos de código Unicode (essencialmente int32)

Sintaxe Básica e Uso

1. Pacote strings

GO
package main

import (
    "fmt"
    "strings"
)

func main() {
    str := "Hello, Go语言!"

    // Pesquisar
    fmt.Println(strings.Contains(str, "Go"))       // true
    fmt.Println(strings.HasPrefix(str, "Hello"))    // true
    fmt.Println(strings.HasSuffix(str, "!"))        // true
    fmt.Println(strings.Index(str, "Go"))           // 7

    // Substituir
    result := strings.Replace(str, "Go", "Golang", 1)
    fmt.Println(result) // "Hello, Golang语言!"

    // Substituir todas as ocorrências
    s := "aabbcc"
    fmt.Println(strings.ReplaceAll(s, "a", "x")) // "xxbbcc"

    // Dividir e juntar
    csv := "apple,banana,cherry"
    fruits := strings.Split(csv, ",")
    fmt.Println(fruits) // [apple banana cherry]

    joined := strings.Join(fruits, " | ")
    fmt.Println(joined) // "apple | banana | cherry"

    // Recortar
    padded := "  Hello World  "
    fmt.Println(strings.TrimSpace(padded))         // "Hello World"
    fmt.Println(strings.Trim("##Hello##", "#"))     // "Hello"
    fmt.Println(strings.TrimLeft("##Hello##", "#"))  // "Hello##"

    // Conversão de maiúsculas/minúsculas
    fmt.Println(strings.ToUpper("hello")) // "HELLO"
    fmt.Println(strings.ToLower("HELLO")) // "hello"

    // Repetir
    fmt.Println(strings.Repeat("Go", 3)) // "GoGoGo"

    // Contar
    fmt.Println(strings.Count("banana", "an")) // 2
}
💡 Dica: Quando o separador de strings.Split é uma string vazia, a string é dividida em um slice de caracteres individuais.

2. Pacote strconv

GO
package main

import (
    "fmt"
    "strconv"
)

func main() {
    // String → Inteiro
    num, err := strconv.Atoi("42")
    if err != nil {
        fmt.Println("Conversão falhou:", err)
    }
    fmt.Println(num) // 42

    // Inteiro → String
    str := strconv.Itoa(42)
    fmt.Println(str) // "42"

    // String → Booleano
    b, err := strconv.ParseBool("true")
    fmt.Println(b, err) // true <nil>

    // String → Float
    f, err := strconv.ParseFloat("3.14", 64)
    fmt.Println(f, err) // 3.14 <nil>

    // Float → String
    // 'f' significa formato normal, -1 significa dígitos mínimos, 64 significa float64
    s := strconv.FormatFloat(3.14, 'f', -1, 64)
    fmt.Println(s) // "3.14"

    // Saída formatada (semelhante ao sprintf do C)
    formatted := strconv.FormatInt(255, 16) // Hexadecimal
    fmt.Println(formatted) // "ff"
}
💡 Dica: strconv.Atoi é equivalente a strconv.ParseInt(s, 10, 0), retornando o tipo int dependente da plataforma.

3. Pacote unicode/utf8

GO
package main

import (
    "fmt"
    "unicode/utf8"
)

func main() {
    str := "Go语言编程"

    // len() retorna o número de bytes
    fmt.Println(len(str)) // 14 (cada caractere chinês ocupa 3 bytes)

    // utf8.RuneCountInString() retorna o número de caracteres
    fmt.Println(utf8.RuneCountInString(str)) // 7

    // Verificar se é um UTF-8 válido
    fmt.Println(utf8.ValidString(str))  // true
    fmt.Println(utf8.ValidString("abc")) // true

    // Iterar sobre cada rune na string
    for i, r := range str {
        fmt.Printf("Índice:%d Caractere:%c Unicode:%U\n", i, r, r)
    }
}
💡 Dica: Ao usar range para iterar sobre uma string, Go itera automaticamente por rune (caractere Unicode), não por byte.

4. strings.Builder (Concatenação Eficiente)

GO
package main

import (
    "fmt"
    "strings"
)

func main() {
    // ❌ Ineficiente: cria uma nova string a cada concatenação
    // result := ""
    // for i := 0; i < 1000; i++ {
    //     result += "a"  // aloca nova memória a cada vez
    // }

    // ✅ Eficiente: use strings.Builder
    var builder strings.Builder
    for i := 0; i < 1000; i++ {
        builder.WriteString("a")
    }
    result := builder.String()
    fmt.Println(len(result)) // 1000

    // Pré-alocar capacidade para desempenho ainda melhor
    var builder2 strings.Builder
    builder2.Grow(1000) // Pré-alocar 1000 bytes
    for i := 0; i < 1000; i++ {
        builder2.WriteString("b")
    }
    fmt.Println(builder2.Len()) // 1000
}
💡 Dica: strings.Builder internamente usa um slice []byte, evitando alocações frequentes de memória causadas pela imutabilidade das strings.


Código de Exemplo

Exemplo: Estatísticas e Análise de Strings (Dificuldade ⭐)

GO
package main

import (
    "fmt"
    "strings"
    "unicode"
)

// Analisa as contagens de diferentes tipos de caracteres em uma string
func analyzeString(s string) (letters, digits, spaces, others int) {
    for _, r := range s {
        switch {
        case unicode.IsLetter(r):
            letters++
        case unicode.IsDigit(r):
            digits++
        case unicode.IsSpace(r):
            spaces++
        default:
            others++
        }
    }
    return
}

func main() {
    text := "Hello, Go语言! 2024年 Version 2.0"

    letters, digits, spaces, others := analyzeString(text)
    fmt.Printf("Texto: %q\n", text)
    fmt.Printf("Letras: %d\n", letters)
    fmt.Printf("Dígitos: %d\n", digits)
    fmt.Printf("Espaços: %d\n", spaces)
    fmt.Printf("Outros: %d\n", others)

    // Contar frequência de palavras
    words := strings.Fields("the go the language the world")
    freq := make(map[string]int)
    for _, w := range words {
        freq[strings.ToLower(w)]++
    }
    fmt.Println("\nFrequência de palavras:", freq)
}
▶ Experimente

Saída:

TEXT
Texto: "Hello, Go语言! 2024年 Version 2.0"
Letras: 17
Dígitos: 6
Espaços: 5
Outros: 3

Frequência de palavras: map[go:1 language:1 the:3 world:1]

Exemplo: Analisador CSV (Dificuldade ⭐⭐)

GO
package main

import (
    "fmt"
    "strings"
)

// Analisador simples de linhas CSV com suporte a campos entre aspas
func parseCSVLine(line string) []string {
    var fields []string
    var current strings.Builder
    inQuotes := false

    for _, r := range line {
        switch {
        case r == '"' && !inQuotes:
            // Entrar na região entre aspas
            inQuotes = true
        case r == '"' && inQuotes:
            // Sair da região entre aspas
            inQuotes = false
        case r == ',' && !inQuotes:
            // Encontrou delimitador, salvar campo atual
            fields = append(fields, current.String())
            current.Reset()
        default:
            // Caractere normal
            current.WriteRune(r)
        }
    }
    // Salvar o último campo
    fields = append(fields, current.String())

    return fields
}

// Limpar e formatar campos
func cleanFields(fields []string) []string {
    cleaned := make([]string, len(fields))
    for i, f := range fields {
        cleaned[i] = strings.TrimSpace(f)
    }
    return cleaned
}

func main() {
    // Dados CSV simulados
    csvData := []string{
        `Alice,28,"Beijing, China"`,
        `Bob,35,"New York, USA"`,
        `Charlie,42,"London, UK"`,
    }

    fmt.Println("=== Resultados da Análise CSV ===")
    for _, line := range csvData {
        fields := parseCSVLine(line)
        fields = cleanFields(fields)
        fmt.Printf("Nome: %-10s Idade: %-4s Local: %s\n",
            fields[0], fields[1], fields[2])
    }

    // Operação inversa: juntar um slice em uma linha CSV
    record := []string{"David", "30", "Shanghai, China"}
    csvLine := strings.Join(record, ",")
    fmt.Println("\nLinha CSV gerada:", csvLine)
}
▶ Experimente

Saída:

TEXT
=== Resultados da Análise CSV ===
Nome: Alice      Idade: 28   Local: Beijing, China
Nome: Bob        Idade: 35   Local: New York, USA
Nome: Charlie    Idade: 42   Local: London, UK

Linha CSV gerada: David,30,Shanghai, China

Exemplo: Motor de Templates (Dificuldade ⭐⭐⭐)

GO
package main

import (
    "fmt"
    "strconv"
    "strings"
)

// Motor de template simples: substitui {{key}} pelos valores correspondentes
func renderTemplate(template string, data map[string]string) string {
    var result strings.Builder
    result.Grow(len(template) * 2) // Estimar capacidade

    i := 0
    for i < len(template) {
        // Encontrar "{{"
        if i+1 < len(template) && template[i] == '{' && template[i+1] == '{' {
            // Encontrar o "}}" correspondente
            end := strings.Index(template[i+2:], "}}")
            if end != -1 {
                key := strings.TrimSpace(template[i+2 : i+2+end])
                if value, ok := data[key]; ok {
                    result.WriteString(value)
                } else {
                    // Chave não encontrada, manter como está
                    result.WriteString("{{" + key + "}}")
                }
                i += end + 4 // Pular "}}"
                continue
            }
        }
        result.WriteByte(template[i])
        i++
    }

    return result.String()
}

// Formatar saída de tabela
func formatTable(headers []string, rows [][]string) string {
    // Calcular largura máxima para cada coluna
    colWidths := make([]int, len(headers))
    for i, h := range headers {
        colWidths[i] = len(h)
    }
    for _, row := range rows {
        for i, cell := range row {
            if i < len(colWidths) && len(cell) > colWidths[i] {
                colWidths[i] = len(cell)
            }
        }
    }

    var b strings.Builder

    // Escrever cabeçalho
    for i, h := range headers {
        b.WriteString(fmt.Sprintf("%-*s | ", colWidths[i], h))
    }
    b.WriteString("\n")

    // Escrever linha separadora
    for i := range headers {
        b.WriteString(strings.Repeat("-", colWidths[i]) + "-+-")
    }
    b.WriteString("\n")

    // Escrever linhas de dados
    for _, row := range rows {
        for i, cell := range row {
            if i < len(colWidths) {
                b.WriteString(fmt.Sprintf("%-*s | ", colWidths[i], cell))
            }
        }
        b.WriteString("\n")
    }

    return b.String()
}

// Converter uma string numérica para diferentes representações de base
func toBases(numStr string) (map[string]string, error) {
    num, err := strconv.ParseInt(numStr, 10, 64)
    if err != nil {
        return nil, err
    }

    return map[string]string{
        "decimal":     strconv.FormatInt(num, 10),
        "binary":      strconv.FormatInt(num, 2),
        "octal":       strconv.FormatInt(num, 8),
        "hexadecimal": strconv.FormatInt(num, 16),
    }, nil
}

func main() {
    // 1. Renderização de template
    fmt.Println("=== Renderização de Template ===")
    template := "Olá, {{name}}! Bem-vindo a {{city}}. Você tem {{count}} novas mensagens."
    data := map[string]string{
        "name":  "Alice",
        "city":  "Beijing",
        "count": "5",
    }
    fmt.Println(renderTemplate(template, data))

    // 2. Formatação de tabela
    fmt.Println("\n=== Formatação de Tabela ===")
    headers := []string{"Nome", "Idade", "Cidade"}
    rows := [][]string{
        {"Alice", "28", "Beijing"},
        {"Bob", "35", "New York"},
        {"Charlie", "42", "London"},
    }
    fmt.Print(formatTable(headers, rows))

    // 3. Conversão de base
    fmt.Println("\n=== Conversão de Base ===")
    bases, _ := toBases("255")
    for name, value := range bases {
        fmt.Printf("%-12s: %s\n", name, value)
    }
}
▶ Experimente

Saída:

TEXT
=== Renderização de Template ===
Olá, Alice! Bem-vindo a Beijing. Você tem 5 novas mensagens.

=== Formatação de Tabela ===
Nome    | Idade | Cidade    | 
--------+-------+-----------+-
Alice   | 28    | Beijing   | 
Bob     | 35    | New York  | 
Charlie | 42    | London    | 

=== Conversão de Base ===
decimal     : 255
binary      : 11111111
octal       : 377
hexadecimal : ff

Cenários de Aplicação Prática

Cenário 1: Analisador de Logs

GO
package main

import (
    "fmt"
    "strconv"
    "strings"
    "time"
)

// Estrutura de entrada de log
type LogEntry struct {
    Timestamp string
    Level     string
    Message   string
    Source    string
}

// Analisar uma linha de log
// Formato: [2024-01-15 10:30:00] [ERROR] Database connection failed (db-service)
func parseLogLine(line string) (*LogEntry, error) {
    entry := &LogEntry{}

    // Extrair timestamp
    if start := strings.Index(line, "["); start != -1 {
        if end := strings.Index(line, "]"); end != -1 {
            entry.Timestamp = line[start+1 : end]
            line = line[end+1:]
        }
    }

    // Extrair nível de log
    line = strings.TrimSpace(line)
    if start := strings.Index(line, "["); start != -1 {
        if end := strings.Index(line, "]"); end != -1 {
            entry.Level = line[start+1 : end]
            line = line[end+1:]
        }
    }

    // Extrair mensagem e fonte
    line = strings.TrimSpace(line)
    if parenStart := strings.LastIndex(line, "("); parenStart != -1 {
        if parenEnd := strings.LastIndex(line, ")"); parenEnd != -1 {
            entry.Source = line[parenStart+1 : parenEnd]
            entry.Message = strings.TrimSpace(line[:parenStart])
        }
    } else {
        entry.Message = line
    }

    return entry, nil
}

// Analisar níveis de log
func analyzeLogs(entries []LogEntry) map[string]int {
    stats := make(map[string]int)
    for _, e := range entries {
        stats[strings.ToUpper(e.Level)]++
    }
    return stats
}

// Filtrar logs contendo uma palavra-chave
func filterLogs(entries []LogEntry, keyword string) []LogEntry {
    var filtered []LogEntry
    keyword = strings.ToLower(keyword)
    for _, e := range entries {
        if strings.Contains(strings.ToLower(e.Message), keyword) {
            filtered = append(filtered, e)
        }
    }
    return filtered
}

func main() {
    // Dados de log simulados
    logLines := []string{
        "[2024-01-15 10:30:00] [INFO] Application started (main-service)",
        "[2024-01-15 10:30:05] [INFO] Connected to database (db-service)",
        "[2024-01-15 10:31:00] [WARN] High memory usage detected (monitor)",
        "[2024-01-15 10:32:00] [ERROR] Database connection timeout (db-service)",
        "[2024-01-15 10:32:01] [ERROR] Retry failed, switching to backup (db-service)",
        "[2024-01-15 10:33:00] [INFO] Backup database connected (db-service)",
        "[2024-01-15 10:35:00] [DEBUG] Cache cleared (cache-service)",
    }

    // Analisar todos os logs
    var entries []LogEntry
    for _, line := range logLines {
        entry, err := parseLogLine(line)
        if err == nil {
            entries = append(entries, *entry)
        }
    }

    // Estatísticas de nível de log
    fmt.Println("=== Estatísticas de Nível de Log ===")
    stats := analyzeLogs(entries)
    for level, count := range stats {
        fmt.Printf("  %s: %d entradas\n", level, count)
    }

    // Filtrar logs de erro
    fmt.Println("\n=== Logs de Erro ===")
    for _, e := range entries {
        if strings.ToUpper(e.Level) == "ERROR" {
            fmt.Printf("  %s | %s | %s\n", e.Timestamp, e.Message, e.Source)
        }
    }

    // Pesquisar por palavra-chave
    fmt.Println("\n=== Logs contendo 'database' ===")
    filtered := filterLogs(entries, "database")
    for _, e := range filtered {
        fmt.Printf("  [%s] %s\n", e.Level, e.Message)
    }
}

Saída:

TEXT
=== Estatísticas de Nível de Log ===
  INFO: 3 entradas
  WARN: 1 entradas
  ERROR: 2 entradas
  DEBUG: 1 entradas

=== Logs de Erro ===
  2024-01-15 10:32:00 | Database connection timeout | db-service
  2024-01-15 10:32:01 | Retry failed, switching to backup | db-service

=== Logs contendo 'database' ===
  [INFO] Connected to database
  [ERROR] Database connection timeout
  [INFO] Backup database connected

Cenário 2: Validação e Limpeza de Entrada do Usuário

GO
package main

import (
    "fmt"
    "regexp"
    "strconv"
    "strings"
    "unicode"
)

// Limpar entrada do usuário para nome de usuário
func sanitizeUsername(name string) (string, error) {
    // Remover espaços do início e do final
    name = strings.TrimSpace(name)

    // Verificar comprimento
    if len(name) < 3 {
        return "", fmt.Errorf("nome de usuário muito curto (mínimo 3 caracteres)")
    }
    if len(name) > 20 {
        return "", fmt.Errorf("nome de usuário muito longo (máximo 20 caracteres)")
    }

    // Permitir apenas letras, dígitos e sublinhados
    var cleaned strings.Builder
    for _, r := range name {
        if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_' {
            cleaned.WriteRune(r)
        }
    }

    result := cleaned.String()
    if len(result) < 3 {
        return "", fmt.Errorf("poucos caracteres válidos")
    }

    return strings.ToLower(result), nil
}

// Validar e analisar número de telefone
func parsePhone(phone string) (string, error) {
    // Remover todos os espaços e hífens
    phone = strings.ReplaceAll(phone, " ", "")
    phone = strings.ReplaceAll(phone, "-", "")

    // Verificar se começa com +55 (Brasil) ou código similar
    if strings.HasPrefix(phone, "+55") {
        phone = phone[3:]
    } else if strings.HasPrefix(phone, "55") {
        phone = phone[2:]
    }

    // Validar comprimento
    if len(phone) < 10 || len(phone) > 11 {
        return "", fmt.Errorf("comprimento incorreto do número de telefone: %d dígitos", len(phone))
    }

    // Validar se todos são dígitos
    for _, r := range phone {
        if !unicode.IsDigit(r) {
            return "", fmt.Errorf("número de telefone contém caractere não numérico: %c", r)
        }
    }

    return phone, nil
}

// Analisar string de tamanho com unidades
func parseSize(sizeStr string) (int64, error) {
    sizeStr = strings.TrimSpace(strings.ToUpper(sizeStr))

    // Extrair partes numérica e de unidade
    var numPart strings.Builder
    var unitPart strings.Builder

    for _, r := range sizeStr {
        if unicode.IsDigit(r) || r == '.' {
            numPart.WriteRune(r)
        } else if unicode.IsLetter(r) {
            unitPart.WriteRune(r)
        }
    }

    num, err := strconv.ParseFloat(numPart.String(), 64)
    if err != nil {
        return 0, fmt.Errorf("número inválido: %s", numPart.String())
    }

    // Converter para bytes com base na unidade
    unit := unitPart.String()
    multipliers := map[string]int64{
        "B":  1,
        "KB": 1024,
        "MB": 1024 * 1024,
        "GB": 1024 * 1024 * 1024,
        "TB": 1024 * 1024 * 1024 * 1024,
    }

    multiplier, ok := multipliers[unit]
    if !ok {
        return 0, fmt.Errorf("unidade desconhecida: %s", unit)
    }

    return int64(num * float64(multiplier)), nil
}

func main() {
    // Testes de limpeza de nome de usuário
    fmt.Println("=== Validação de Nome de Usuário ===")
    usernames := []string{"  Alice_123  ", "ab", "A!@#B", "GoDeveloper2024"}
    for _, u := range usernames {
        result, err := sanitizeUsername(u)
        if err != nil {
            fmt.Printf("  %q → Erro: %v\n", u, err)
        } else {
            fmt.Printf("  %q → %q\n", u, result)
        }
    }

    // Testes de análise de número de telefone
    fmt.Println("\n=== Análise de Número de Telefone ===")
    phones := []string{"11 98765 4321", "+55-11-98765-4321", "12345", "987654321012"}
    for _, p := range phones {
        result, err := parsePhone(p)
        if err != nil {
            fmt.Printf("  %q → Erro: %v\n", p, err)
        } else {
            fmt.Printf("  %q → %s\n", p, result)
        }
    }

    // Testes de análise de tamanho de arquivo
    fmt.Println("\n=== Análise de Tamanho de Arquivo ===")
    sizes := []string{"1.5GB", "512MB", "1024KB", "100B", "2TB"}
    for _, s := range sizes {
        bytes, err := parseSize(s)
        if err != nil {
            fmt.Printf("  %s → Erro: %v\n", s, err)
        } else {
            fmt.Printf("  %s → %d bytes\n", s, bytes)
        }
    }
}

Saída:

TEXT
=== Validação de Nome de Usuário ===
"  Alice_123  " → "alice_123"
"ab" → Erro: nome de usuário muito curto (mínimo 3 caracteres)
"A!@#B" → Erro: poucos caracteres válidos
"GoDeveloper2024" → "godeveloper2024"

=== Análise de Número de Telefone ===
"11 98765 4321" → 11987654321
"+55-11-98765-4321" → 11987654321
"12345" → Erro: comprimento incorreto do número de telefone: 5 dígitos
"987654321012" → Erro: comprimento incorreto do número de telefone: 12 dígitos

=== Análise de Tamanho de Arquivo ===
1.5GB → 1610612736 bytes
512MB → 536870912 bytes
1024KB → 1048576 bytes
100B → 100 bytes
2TB → 2199023255552 bytes

❓ Perguntas Frequentes

P1: Por que len("Go语言") retorna 8 em vez de 4?

GO
package main

import (
    "fmt"
    "unicode/utf8"
)

func main() {
    s := "Go语言"

    // len() retorna bytes, não caracteres
    fmt.Println("len():", len(s)) // 8

    // Caracteres chineses ocupam 3 bytes cada em UTF-8
    // G(1) + o(1) + 语(3) + 言(3) = 8

    // Maneira correta de obter a contagem de caracteres
    fmt.Println("RuneCountInString():", utf8.RuneCountInString(s)) // 4

    // Ou use range para contar
    count := 0
    for range s {
        count++
    }
    fmt.Println("contagem com range:", count) // 4
}

Ponto-chave: Ao lidar com caracteres multibyte como chinês, sempre use utf8.RuneCountInString() ou range para obter a contagem real de caracteres.

P2: Concatenação de strings — usar + ou strings.Builder?

GO
package main

import (
    "fmt"
    "strings"
)

func main() {
    // Algumas concatenações: use + (o compilador otimiza)
    s := "Hello" + " " + "World"
    fmt.Println(s)

    // Muitas concatenações: use strings.Builder
    var builder strings.Builder
    for i := 0; i < 10000; i++ {
        builder.WriteString("a")
    }
    fmt.Println("Comprimento:", builder.Len())

    // Pré-alocação melhora ainda mais o desempenho
    var builder2 strings.Builder
    builder2.Grow(10000) // Pré-alocar 10000 bytes
    for i := 0; i < 10000; i++ {
        builder2.WriteString("b")
    }
    fmt.Println("Comprimento:", builder2.Len())
}

Regras Práticas:

Cenário Abordagem Recomendada
2-3 concatenações de strings + ou fmt.Sprintf
Concatenação em loop (contagem conhecida) strings.Builder + Grow()
Concatenação em loop (contagem desconhecida) strings.Builder

P3: Como verificar se uma string contém apenas caracteres específicos?

GO
package main

import (
    "fmt"
    "strings"
    "unicode"
)

func main() {
    s := "Hello123"

    // Verificar se contém apenas letras e dígitos
    isAlphanumeric := true
    for _, r := range s {
        if !unicode.IsLetter(r) && !unicode.IsDigit(r) {
            isAlphanumeric = false
            break
        }
    }
    fmt.Println("Apenas alfanumérico:", isAlphanumeric)

    // Verificar se contém apenas letras ASCII
    isASCII := true
    for _, r := range s {
        if r > 127 {
            isASCII = false
            break
        }
    }
    fmt.Println("Apenas ASCII:", isASCII)

    // Verificar se contém apenas um conjunto específico de caracteres
    allowed := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
    containsOnlyAllowed := true
    for _, r := range s {
        if !strings.ContainsRune(allowed, r) {
            containsOnlyAllowed = false
            break
        }
    }
    fmt.Println("Dentro do intervalo permitido:", containsOnlyAllowed)
}

P4: Quando usar strings.Contains vs expressões regulares?

GO
package main

import (
    "fmt"
    "regexp"
    "strings"
)

func main() {
    text := "Meu email é test@example.com, telefone é 11987654321"

    // Pesquisa simples → use o pacote strings (mais rápido)
    fmt.Println(strings.Contains(text, "example.com")) // true

    // Correspondência de padrões → use expressões regulares
    emailRegex := regexp.MustCompile(`[\w.]+@[\w.]+\.\w+`)
    fmt.Println("Email:", emailRegex.FindString(text))

    phoneRegex := regexp.MustCompile(`1[3-9]\d{9}`)
    fmt.Println("Telefone:", phoneRegex.FindString(text))
}

Diretrizes:


📖 Resumo

Tópico Conteúdo Principal
Pacote strings Contains, HasPrefix, HasSuffix, Index, Replace, Split, Join, Trim, ToUpper, ToLower, Count, Repeat, Fields
Pacote strconv Atoi, Itoa, ParseBool, ParseFloat, FormatInt, FormatFloat
unicode/utf8 RuneCountInString, ValidString, unicode.IsLetter/IsDigit/IsSpace
strings.Builder WriteString, WriteRune, WriteByte, Grow, String, Len
Princípios Fundamentais Strings são imutáveis, len retorna bytes, range itera por rune, use Builder para muitas concatenações

📝 Exercícios

Exercício 1: Básico — Inversão de String

Escreva uma função reverseString(s string) string que inverte uma string. Ela deve lidar corretamente com caracteres chineses.

Dica: Você não pode simplesmente converter a string para []byte e invertê-la, porque caracteres chineses ocupam múltiplos bytes.

GO
// Resultados esperados
reverseString("Hello")   // "olleH"
reverseString("Go语言")  // "言语oG"

Exercício 2: Intermediário — Conversão CamelCase e Snake_case

Escreva duas funções:

Dica: Use unicode.IsUpper para detectar posições de letras maiúsculas.

GO
// Resultados esperados
camelToSnake("helloWorld")     // "hello_world"
camelToSnake("HTTPResponse")   // "http_response"
snakeToCamel("hello_world")    // "helloWorld"
snakeToCamel("http_response")  // "httpResponse"

Exercício 3: Desafio — Extrator Simples de Cabeçalhos Markdown

Escreva uma função extractHeadings(md string) []string que extrai todos os cabeçalhos de um documento Markdown.

Dica: Cabeçalhos começam com #, e o número de símbolos # indica o nível do cabeçalho.

GO
// Entrada
md := `# Heading 1
Este é o texto do corpo
## Heading 2
### Heading 3
## Outro Heading 2`

// Saída esperada
// ["# Heading 1", "## Heading 2", "### Heading 3", "## Outro Heading 2"]

Próxima Lição

Parabéns por completar o processamento de strings! Na próxima lição, aprenderemos sobre Operações de E/S de Arquivos — como ler e escrever arquivos, manipular diretórios e usar E/S em buffer para melhor desempenho.

👉 Lição 20: E/S de Arquivos

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%