AnsiString

Adam Boduch

AnsiString - typ łańcuchowy języka Delphi.

Typ AnsiString należy do tzw. długich łańcuchów, gdyż jego maksymalna długość to ~2^31 znaków, co przekłada się od 4 bajtów do 2 GB zajmowanej pamięci. Typ AnsiString obsługuje kodowanie 8bitowe (ANSI) oraz MBCS (Multibyte Character Set).

Przypisując wartość do zmiennej, tekst należy wziąć w apostrofy (') - przykład:

Zmienna := 'Wartość zmiennej';

W Delphi dla Win32, typ String wskazywał na typ AnsiString; w Delphi dla .NET, typ String wskazuje na WideString

Na platformie Win32 przełącznik kompilatora {$H-} powodował, iż typ String był utożsamiany przez kompilator jako typ ShortString.

Typ AnsiString jest tablicą znaków, tzn. do każdego elementu łańcucha (znaku) można odwoływać się podobnie jak do tablic:

program Foo;

{$APPTYPE CONSOLE}

uses
  SysUtils;

const
  Bar : AnsiString = 'Hello World';
var
  i : Integer;
begin
  for I := 1 to Length(Bar) do
    Writeln(I, ': ', Bar[i]);

  Readln;
end.

Na podstawie tego, co powiedzieliśmy, do dowolnego znaku można odwołać się poprzez Foo[n] gdzie n to numer znaku (1 oznacza pierwszy znak w łańcuchu).

const
  Bar : AnsiString = 'Hello World';
var
  S : AnsiChar;
begin
  S := Bar[1];
end.

AnsiString to tablica (Char'ów) od 1 do n ([1..n]). N jest w tym przypadku liczbą znaków (długością), jaki i idneksem ostatniego elementu. Możemy się do niego w łatwy sposób odwołać za pomocą Length.

var
  S: AnsiString = 'Ala ma kota';
begin
  S[1] := 'U';
  S[Length(S)] := 'y';
end.

Łańcuch AnsiString jest zawsze zakończony znakiem #0 (niejawnie - nie jest wliczany do długości ciągu), dlatego można w łatwy sposób rzutować go na PChar. W przeciwieństwie jednak do tego drugiego, może zawierać znaki #0 (dla PChar znak #0 oznacza koniec łańcucha).

W przeciwieństwie do ShortString, w AnsiString nie ma możliwości manipulowania długością ciągu poprzez jego zerowy element. Po odwołanie się do niego wywołany zostanie błąd kompilatora [Error] Unit1.pas(32): Element 0 inaccessible - use 'Length' or 'SetLength'.

Pojedynczy znak AnsiString wskazuje na typ AnsiChar. Mozna powiedzieć, że AnsiString to zmodyfikowany PChar. Alokacja pamięci jest jednak dokonywana niejawnie przez kompilator. Dodatkowo zawiera on informacje o długości, zajmowanej pamięci i tzw. licznik.

Struktura AnsiString

-12 bajtów (słowo) Wielkości przydzielonej pamięci | -8 bajtów (słowo) Licznik odwołań | -4 bajty (słowo) Długość | Łańcuch | Terminator (1 bajt)

Z licznikiem odwołań związana jest pewna osobliwość. Otóż gdy zadeklaruje się a := b to obie wartości będą fizycznie wskazywały na ten sam napis (ich liczniki odwołań będą takie same). Gdy licznik odwołań spada do zera to pamięć jest zwalniana.

var
  s1,s2: String;  //Wszystkie liczniki to 0 (nie jest przydzielona pamięć)
begin
  s1 := 'Napis';  //Liznik odwołań s1 to 1 (przydzielenie pamięci), s2 to 0 (nie jest przydzielona pamięć)
  s2 := s1; //Licznik odwołań s1 i s2 to 2
  s2 := s2 + ' i jeszcze troche dłuższy napis'; //Licznik odwołań s1 to 1, a s2 to 2
end.  //Wszystkie liczniki to 0, zwalnianie pamięci

Z AnsString związana jest skomplikowana gospodarka pamięci, która dzieje się bez wiedzy programisty. Powoduje to m.in., że lokalne zmienne typu AnsiString (w funkcjach czy procedurach) są inicjowane (otrzymują wartość '', czyli ?pusty ciąg znaków?) przy wejściu i zwalniane (o ile ich licznik odwołań nie zostanie zwiększony przez przypisanie do innej zmiennej globalnej) przy wyjściu z funkcji/procedury.

Z podobieństwa, AnsiString i PChar (terminator) wynika, że łańcuchy AnsiString można z powodzeniem użyć w funkcjach WinAPI. W przypadku, gdy AnsiString używany będzie jako stała wystarczy jedynie prosta konwersja PChar(AnsiString). Sprawa komplikuje się, gdy chcemy użyć AnsiString jako bufor (np. w funkcji GetWindowsDirectory). Wtedy należy postępować podobnie jak w poniższym wydruku.

const
  a : AnsiString = '\Temp\';
var
  s : AnsiChar;
begin
  SetLength(s, 260);  //przedłużenie s tak by mogło przechować ścieżkę Windows będąc w postaci PChar
  GetWindowsDirectory(PChar(s), 260);
  SetLength(s, StrLen(PChar(s)));  //zmiana długości z 260 znaków do ich rzeczywistej liczby

  {
   Zamiast funkcji SetLength można wykorzystać mechanizmy kompilatora.
   Wystarczy rzutować zmienną AnsiString na PChar a następnie przypisać ją do zmiennej typu AnsiString:

   s := PChar(s);
  }

  s := s + a;  //teraz można z powodzeniem dokonać konkatenacji
end.

Najpierw przedłużamy łańcuch z zera do długości 260 znaków. Wtedy do funkcji przekazany zostanie PChar o długości 260 znaków. Jeżeli krok ten zostanie pominięty do funkcji trafi PChar o długości 0 znaków. Wartość nie będzie mogła przechować ścieżki Windows (nie będzie wystarczającej ilości pamięci na jej zapisanie).
Na typie, AnsiString nie można dokonać konkatenacji, gdy rzeczywista długości różni się od tej, która jest zadeklarowana. To znaczy, gdy AnsiString będzie miał długość, np. 10 znaków, a przechowywał będzie łańcuch np. Michał. Wynika to z ukrytej gospodarki zarządzania pamięcią związaną z AnsiString. W powyższym przykładzie wartość s ma długość 260 znaków, podczas, gdy w większości przypadków będzie przechowywać ciąg C:\Windows. Trzeba wtedy uaktualnić informację o długości. Robimy to przy pomocy SetLength (patrz wydruk).

Rzutowanie w odwrotną stronę, czyli z PChar na AnsiString, jest równierz bardzo proste. Wystarczy przypisać zmienną typu PChar do zmiennej typu AnsiString.

Zobacz też:

2 komentarzy

Nie AnsiChar tylko AnsiString lub zwykły String!
Bo typ AnsiChar może zawierać TYLKO JEDEN ZNAK!!! Tak samo z Char.

Procedure Procedura;
var
exec : String;
AnsiExec : AnsiChar;
begin
 SetLength(AnsiExec, 5000);

Błąd:
[DCC Error] FtpUploader.pas(120): E2197 Constant object cannot be passed as var parameter

Ma ktoś jakiś pomysł jak to poprawić??