1. append(...): aggiungere testo
Quando si inizia a studiare le stringhe, sembra che servano più o meno per tre cose: chiedere il nome dell'utente, stampare «Ciao!», e al massimo concatenare firstName + " " + lastName. Ma nella vita reale capita spesso di ricevere testo "peggiorato": con caratteri in eccesso, commenti, separatori strani o nel formato “chiave=valore”.
Buona notizia: std::string è un oggetto modificabile. Non è una "tavoletta di pietra". Hai operazioni che fanno esattamente quello che promettono: aggiungono, rimuovono, sostituiscono. La cattiva notizia: come qualsiasi utensile, possono "tagliare troppo" se confondi indici e lunghezze. Quindi oggi lavoreremo come chirurghi: prima cerchiamo, poi tagliamo.
In questa lezione cominceremo a implementare un piccolo TextPrep — un frammento didattico di applicazione che prende una stringa di comando/config e la porta in una forma più comoda. L'analisi completa in token arriverà più avanti; per ora il compito è semplice: "trovare → correggere".
Esempio minimo: "costruire un messaggio" con append
Quando si scrive codice da principianti, la mano spesso tende a usare +: "Hello, " + name + "!". Va bene, ma nei compiti di elaborazione testo è spesso più comodo costruire la stringa passo dopo passo. Per questo è utile append(...) (e il suo parente +=).
#include <iostream>
#include <string>
int main() {
std::string msg;
msg.append("Hello, ");
msg.append("Bob");
msg.append("!\n");
std::cout << msg; // Hello, Bob!
}
Nota: append modifica la stringa in luogo. Non crea una nuova stringa e non ti costringe a indovinare come il compilatore concatenerà l'espressione.
append vs +=
Di solito s += "abc" si legge più conciso, mentre s.append("abc") è un po' più "formale" e più evidente nel codice. È utile conoscere entrambe le varianti. Nei nostri esempi useremo più spesso append, perché sottolinea chiaramente l'azione di "aggiungere alla fine".
#include <iostream>
#include <string>
int main() {
std::string s = "file";
s += "_2026";
s.append(".txt");
std::cout << s << '\n'; // file_2026.txt
}
Pattern "costruire il risultato separatamente"
A volte la logica è: "non voglio rovinare la stringa originale, voglio costruirne una nuova". Allora creiamo una std::string out e aggiungiamo solo ciò che serve. Questo sarà particolarmente utile nelle lezioni successive, quando normalizzeremo gli spazi e tokenizzeremo il testo, ma le basi (come costruire una stringa) le poniamo adesso.
#include <iostream>
#include <string>
int main() {
std::string raw = "milk";
std::string out;
out.append("[item=");
out.append(raw);
out.append("]");
std::cout << out << '\n'; // [item=milk]
}
2. erase(...): rimuovere parti della stringa
Se append è "accrescere il testo", allora erase è "togliere l'eccesso". E nell'elaborazione delle stringhe erase si trova continuamente: rimuovere il commento alla fine della riga, cancellare un delimitatore in eccesso, togliere ".tmp", eliminare una virgola accidentale, rimuovere un prefisso come "cmd:".
Useremo due forme principali:
- erase(pos) rimuove tutto dalla posizione pos fino alla fine.
- erase(pos, count) rimuove esattamente count caratteri a partire da pos.
Rimuovere la coda a partire da un marcatore: "tutto dopo # è commento"
Scenario tipico: una stringa tipo "value = 42 # comment". Cerchiamo '#' con find, e se lo troviamo tagliamo la coda.
#include <iostream>
#include <string>
int main() {
std::string line = "value = 42 # comment";
std::size_t hash = line.find('#');
if (hash != std::string::npos) {
line.erase(hash); // rimuove da '#' fino alla fine
}
std::cout << line << '\n'; // value = 42
}
Nota lo spazio finale — è rimasto perché abbiamo rimosso solo il commento, non abbiamo normalizzato gli spazi. È normale: ci occuperemo degli spazi più avanti; oggi ci alleniamo proprio a "tagliare la coda".
Rimuovere un pezzo fisso: eliminare un singolo separatore
A volte vuoi rimuovere esattamente un carattere, per esempio i due punti in "user:alice" (e sostituirlo con "=" è già compito di replace, vedi oltre). Per cancellare un singolo carattere è comodo erase(pos, 1).
#include <iostream>
#include <string>
int main() {
std::string s = "user:alice";
std::size_t colon = s.find(':');
if (colon != std::string::npos) {
s.erase(colon, 1); // rimosso ':'
}
std::cout << s << '\n'; // useralice
}
Sì, le parole si sono "attaccate" — ed è proprio per questo che spesso invece di erase si preferisce replace. Ma l'esempio mostra bene la meccanica.
Importante: dopo erase le posizioni "si spostano"
Se hai trovato una posizione pos, poi hai cancellato un pezzo prima di quella posizione, tutto ciò che era a destra si sposterà verso sinistra. I vecchi indici diventano sospetti. Perciò in una serie di modifiche spesso è più onesto fare un ciclo: "trovato → corretto → cercare di nuovo".
Non è una regola "assoluta", ma è una buona abitudine per i principianti: meno probabilità di bug "magici".
3. replace(pos, count, text): sostituire frammenti
Quando fai trasformazioni testuali, spesso non vuoi "cancellare e poi inserire separatamente". Vuoi dire in modo più naturale: "questo segmento sostituiscilo con questo". Per questo esiste replace.
La forma che useremo oggi:
replace(pos, count, text) — sostituisce un segmento di lunghezza count, a partire da pos, con la stringa text.
È molto importante non confondere il secondo argomento: è la lunghezza dell'area da sostituire, non "l'indice di fine".
Sostituire un singolo carattere: ':' → '='
Torniamo alla stringa "user:alice", ma facciamo le cose "da grandi": sostituiamo ':' con '='.
#include <iostream>
#include <string>
int main() {
std::string s = "user:alice";
std::size_t colon = s.find(':');
if (colon != std::string::npos) {
s.replace(colon, 1, "="); // sostituito un carattere
}
std::cout << s << '\n'; // user=alice
}
Questo è uno degli esempi più semplici di replace: sostituire "un carattere con un carattere", ma puoi sostituire anche con una stringa di lunghezza diversa.
Sostituire parola con parola: "on" → "off"
#include <iostream>
#include <string>
int main() {
std::string flags = "feature=on";
std::size_t pos = flags.find("on");
if (pos != std::string::npos) {
flags.replace(pos, 2, "off"); // "on" di lunghezza 2
}
std::cout << flags << '\n'; // feature=off
}
Se dopo volessimo sostituire qualcos'altro a destra, faremmo nuovamente find, perché la stringa ha cambiato lunghezza.
Sostituzione di "un pezzo": attenzione ai bordi
replace(pos, count, ...) non richiede che count "corrisponda esattamente a una parola". Sostituirà semplicemente count caratteri. Quindi, se sbagli di 1 carattere, puoi mangiarti troppo o lasciare troppo. È un errore classico dei principianti, e la cura è semplice: pronuncia ad alta voce cosa significa count.
4. Pattern «trovare → modificare»
Ora mettiamo insieme tutto ciò che sappiamo: find, il controllo su std::string::npos, poi erase o replace o append. È un pattern così comune che è utile vederlo come un mini-flusso.
Ecco lo schema:
flowchart TD
A[Esiste la stringa s] --> B[Trovare marcatore / frammento con find]
B --> C{pos != npos?}
C -- no --> D[Non fare nulla / altro scenario]
C -- si --> E[Modifica la stringa: erase / replace / append]
E --> F[Se necessario: cercare di nuovo, perché la lunghezza è cambiata]
Scenario: rimuovere il commento e sostituire il separatore
Immaginiamo di ricevere stringhe di mini-config nel formato: "user:alice # main user". Vogliamo ottenere: "user=alice". Lo facciamo in due passi: prima rimuoviamo il commento, poi sostituiamo ':' con '='.
#include <iostream>
#include <string>
int main() {
std::string line = "user:alice # main user";
std::size_t hash = line.find('#');
if (hash != std::string::npos) {
line.erase(hash);
}
std::size_t colon = line.find(':');
if (colon != std::string::npos) {
line.replace(colon, 1, "=");
}
std::cout << line << '\n'; // user=alice
}
Sì, lo spazio finale è rimasto (di solito prima di # c'è uno spazio). Va bene: gli spazi sono un argomento a parte per la prossima lezione sulla normalizzazione.
"Sostituire solo la prima occorrenza"
replace sostituisce esattamente l'area nella posizione che gli fornisci. Quindi, per impostazione predefinita, se trovi la prima occorrenza con find, cambi solo la prima.
#include <iostream>
#include <string>
int main() {
std::string s = "a=b=c";
std::size_t eq = s.find('=');
if (eq != std::string::npos) {
s.replace(eq, 1, ":");
}
std::cout << s << '\n'; // a:b=c
}
Se vuoi sostituire tutte le occorrenze, serve un ciclo "trovare → sostituire → cercare ancora", ma qui sorge la domanda: dove muovere la posizione dopo la sostituzione?
Mini-ciclo: sostituire tutti i punti con underscore
Facciamo un ciclo semplice: dopo la sostituzione spostiamo la posizione di 1. Qui è sicuro, perché sostituiamo 1 carattere con 1 carattere.
#include <iostream>
#include <string>
int main() {
std::string name = "report.2026.final.txt";
std::size_t pos = 0;
while ((pos = name.find('.', pos)) != std::string::npos) {
name.replace(pos, 1, "_");
pos += 1; // procediamo oltre
}
std::cout << name << '\n'; // report_2026_final_txt
}
Questo esempio è utile perché è "onesto": sostituzione di pari lunghezza, quindi la posizione dopo la sostituzione è prevedibile.
5. Due approcci: modificare in luogo o costruire una nuova stringa
Arriva il momento in cui molti principianti si chiedono: "Cosa è meglio — modificare la stringa originale o costruirne una nuova?". La risposta è noiosa ma pratica: dipende dal compito. Se fai un'operazione puntuale come "tagliare il commento" — è più semplice modificare in luogo (erase). Se filtri caratteri, ricostruisci il formato, salti o inserisci elementi — spesso è più semplice costruire una nuova stringa con append.
Inoltre c'è un fattore psicologico: "costruire una nuova stringa" generalmente porta meno errori con gli indici, perché procedi da sinistra a destra e aggiungi solo ciò che serve.
Costruire il risultato: rimuovere tutti i caratteri #
No, non stiamo parlando di "commenti", è solo una dimostrazione del pattern "copio solo ciò che serve". Non usiamo qui nulla di più complicato di un ciclo e append.
#include <iostream>
#include <string>
int main() {
std::string s = "a#b##c";
std::string out;
for (std::size_t i = 0; i < s.size(); ++i) {
if (s[i] != '#') out.append(1, s[i]); // aggiungi 1 carattere
}
std::cout << out << '\n'; // abc
}
C'è una piccola "magia": append(1, s[i]) significa "aggiungi il carattere una volta". È un modo legittimo per aggiungere un char con append, anche se spesso si usa push_back. Oggi però rimaniamo focalizzati sulla famiglia append.
Confronto degli approcci
| Compito | Di solito più semplice | Perché |
|---|---|---|
| "Tagliare la coda dopo un marcatore" | |
Una sola operazione, logica minima |
| "Sostituire un singolo separatore" | |
Non serve fare delete+insert manuale |
| "Costruire una stringa formattata" | in una nuova stringa |
Meno rischi con gli indici, struttura più chiara |
6. Mini-strumento "TextPrep": funzione prepareLine
Ora creiamo un frammento che assomiglia davvero a una parte di applicazione. Scriveremo la funzione prepareLine che prende una stringa e restituisce una versione più comoda.
Regole per oggi (senza spazi e tokenizzazione, questo verrà dopo):
- Prima rimuoviamo il commento da # fino alla fine.
- Poi sostituiamo : con = (se presente).
- Poi aggiungiamo il prefisso "OK: " solo per vedere come append aiuta a costruire il risultato.
Funzione prepareLine
#include <string>
std::string prepareLine(std::string line) {
std::size_t hash = line.find('#');
if (hash != std::string::npos) line.erase(hash);
std::size_t colon = line.find(':');
if (colon != std::string::npos) line.replace(colon, 1, "=");
std::string out;
out.append("OK: ");
out.append(line);
return out;
}
Nota un importante dettaglio di design: prendiamo line per valore (cioè una copia), la modifichiamo e ritorniamo la nuova stringa. Per un principiante questo è spesso più semplice: dall'esterno la funzione sembra sicura — "passi la stringa, ottieni il risultato". Parleremo più avanti delle sottigliezze sul costo della copia; ora l'obiettivo è chiarezza.
Mini-test in main
#include <iostream>
#include <string>
std::string prepareLine(std::string line);
int main() {
std::string raw = "user:alice # main user";
std::string ready = prepareLine(raw);
std::cout << ready << '\n'; // OK: user=alice
}
Se guardi lo spazio finale e pensi "eccolo, il mondo è ingiusto" — reazione normale. Correggeremo gli spazi nelle prossime lezioni, perché gli spazi sono un argomento a sé.
7. Errori tipici
Errore №1: usare erase/replace con la posizione std::string::npos.
std::string::npos è un valore speciale "non trovato" e non è un indice valido. Se fai line.erase(npos) o line.replace(npos, ...), ottieni comportamento che nel migliore dei casi si traduce in eccezione/errore a runtime, nel peggiore in un malfunzionamento misterioso. La cura è semplice: fai sempre prima if (pos != std::string::npos) e solo dopo applica le operazioni.
Errore №2: confondere "lunghezza" e "indice finale" nel secondo parametro di replace e erase.
In replace(pos, count, text) e erase(pos, count) il parametro count è il numero di caratteri, non la "posizione di fine". Se pensi mentalmente "sostituisco dal 3 al 7", nel codice devi scrivere replace(3, 7 - 3, ...). Questo errore è subdolo perché il codice compila e spesso "quasi funziona", ma a volte mangia un carattere in più.
Errore №3: fare più modifiche consecutive usando vecchie posizioni.
Non appena fai erase o replace, la stringa potrebbe cambiare lunghezza. Questo significa che un indice trovato "prima" può non puntare più allo stesso punto. I principianti spesso fanno così: trovano pos1, trovano pos2, poi cancellano qualcosa all'inizio — e all'improvviso pos2 è sbagliato. Quando la stringa cambia, è più sicuro cercare di nuovo dopo ogni modifica significativa, almeno finché non hai confidenza con gli indici.
Errore №4: aspettarsi che append/erase/replace restituiscano "una nuova stringa" invece di modificare quella corrente.
Questi metodi prima di tutto modificano l'oggetto. Sì, molti di loro ritornano un riferimento alla stringa (per permettere chaining), ma l'idea è "modifico questa stringa". Se scrivi line.erase(pos); aspettandoti che "da qualche parte appaia una nuova stringa", non succederà. O lavori sullo stesso oggetto, o costruisci intenzionalmente out e lo ritorni.
Errore №5: costruire stringhe con tanti + in espressioni lunghe e perdere leggibilità.
A volte un'espressione come a + b + c + d + e diventa una "zuppa di stringhe" difficile da comprendere. In quei casi append o una costruzione passo-passo risultano più leggibili: si vede cosa è prefisso, cosa sono i dati e cosa è il suffisso. Questo riduce il rischio di dimenticare uno spazio, due punti o un newline.
GO TO FULL VERSION