Strings & String Routines

Long strings are reference-counted with copy-on-write: assigning is cheap, and the data is copied only when one of the variables is modified.

17.1 Basics

Long strings are reference-counted with copy-on-write: assigning is cheap, and the data is copied only when one of the variables is modified.

var
  S, T: string;
begin
  S := 'Hello';
  T := S + ', World';           // concatenation
  Writeln(Length(T));           // 12
  Writeln(T[1]);                // H (1-based)
  T[1] := 'J';                  // copy-on-write: S is unaffected
  Writeln(S, ' / ', T);         // Hello / Jello, World
  if S = 'Hello' then Writeln('equal');   // case-sensitive comparison
end.

17.2 Classic string routines

number of chars (code units) Length('abc') = 3 Copy(S, Index, Count) Copy('Pascal', 2, 3) = 'asc' first position, 0 if absent Pos('c', 'Pascal') = 4 Pos(Sub, S, Offset) / PosEx System / StrUtils search from an offset Delete(S, Index, Count) remove chars in place Insert(Sub, S, Index) insert in place SetLength(S, N) StringOfChar(C, N) StringOfChar('-', 5) UpperCase, LowerCase ASCII case conversion AnsiUpperCase, AnsiLowerCase locale-aware case conversion Trim, TrimLeft, TrimRight remove whitespace/control chars StringReplace(S, Old, New, Flags) replace ([rfReplaceAll, rfIgnoreCase]) CompareStr, CompareText, SameText case-sensitive / insensitive compare IntToStr, StrToInt, Format wrap in quotes, doubling inner quotes LeftStr, RightStr, MidStr ReverseString, DupeString reverse, repeat

StartsText, EndsText, ContainsText split into an array (Delphi; in FPC use S.Split)

program StringOps;
{$mode objfpc}{$H+}
uses SysUtils, StrUtils;
var
  S: string;
begin
  S := '  Free Pascal is fun  ';
  S := Trim(S);
  Writeln('[', S, ']');                                    // [Free Pascal is fun]
  Writeln(UpperCase(S));                                   // FREE PASCAL IS FUN
  Writeln(Pos('Pascal', S));                               // 6
  Writeln(Copy(S, 6, 6));                                  // Pascal
  Writeln(StringReplace(S, 'fun', 'fast', [rfReplaceAll]));
  Writeln(ReverseString('stressed'));                      // desserts
  Writeln(DupeString('ab', 3));                            // ababab
  Delete(S, 1, 5);
  Writeln(S);                                              // Pascal is fun
  Insert('Object ', S, 1);
  Writeln(S);                                              // Object Pascal is fun
end.

17.3 String helper methods

Delphi XE3+ and FPC 3.0+ (SysUtils) provide a record helper for string with method syntax and 0-based indexes:

Mixing 1-based Copy/Pos and 0-based helper methods is a common source of off-by-one bugs — pick one style per code base.

var
  S: string;
  Parts: TStringArray;     // FPC; Delphi: TArray<string>
  P: string;
begin
  S := 'red,green,blue';
  Writeln(S.ToUpper);              // RED,GREEN,BLUE
  Writeln(S.Contains('green'));    // TRUE
  Writeln(S.IndexOf('green'));     // 4 (0-based!)
  Writeln(S.Replace(',', ';'));
  Parts := S.Split([',']);
  for P in Parts do Writeln(P.Trim);
  Writeln(S.StartsWith('red'), ' ', S.Length);
end.

17.4 Splitting with TStringList

uses Classes;
var
  SL: TStringList;
  I: Integer;
begin
  SL := TStringList.Create;
  try
    SL.Delimiter := ';';
    SL.StrictDelimiter := True;          // do not also split on spaces
    SL.DelimitedText := 'alpha;beta gamma;delta';
    for I := 0 to SL.Count - 1 do
      Writeln(I, ': ', SL[I]);
    SL.CommaText := 'x=1,y=2';
    Writeln(SL.Values['y']);             // 2
  finally
    SL.Free;
  end;
end.

17.5 Building strings efficiently

Repeated S := S + X in a long loop reallocates memory; it is acceptable for small loops (the memory manager is good at it) but for large outputs use TStringBuilder (Delphi System.SysUtils; FPC 3.2 SysUtils) or a TStringList:

var
  SB: TStringBuilder;
  I: Integer;
begin
  SB := TStringBuilder.Create;
  try
    for I := 1 to 1000 do
      SB.Append(I).Append(',');
    Writeln(Length(SB.ToString));
  finally
    SB.Free;
  end;
end.

17.6 Unicode and encodings

Delphi string is UTF-16: Length counts UTF-16 code units; characters outside the BMP (emoji) take two (a surrogate pair).

Lazarus/FPC objfpc programs use UTF-8 in AnsiString: Length counts bytes. Use UTF8Length, UTF8Copy, UTF8UpperCase from the LazUTF8 unit for character-based operations.

Convert explicitly: UTF8Encode/UTF8Decode, TEncoding.UTF8.GetBytes, UTF8ToString (Delphi), or assignment between UnicodeString and UTF8String (the compiler converts).

Files: TStringList.SaveToFile(Name, TEncoding.UTF8); Delphi's TFile.ReadAllText(Name, TEncoding.UTF8).

17.7 ShortString and PChar

PChar(S) of an empty string returns a pointer to a #0 character, never nil, so it is always safe to pass to C APIs.

var
  SS: string[20];           // ShortString with max length 20
  P: PChar;
  Buf: array[0..63] of Char;
  S: string;
begin
  SS := 'Turbo Pascal';
  Writeln(Ord(SS[0]));      // 12 — the length byte
  S := 'null-terminated';
  P := PChar(S);            // pointer to the string's characters (valid while S lives)
  StrPCopy(Buf, 'copied');  // SysUtils: copy into a char array
  Writeln(StrLen(P));       // 15
end.