страничка tripsin'а


Главная | Статьи | Заметки | Файлы| Ссылки | я

String. То, чего ты мог и не знать.

Повторенье - Мать … !
(недописанная поговорка)



Ты наверное думаешь, что про строки уже и писать нечего. Тогда почитай, чего я для себя наконспектировал и наверняка что-нибудь новое для себя узнаешь. Думаю статейка будет полезна не только начинающим программистам.

Прежде всего давай четко определимся, что такое тип String в Delphi. В зависимости от директив компилятора тип String может интерпретироваться как ShortString или AnsiString.

- {$H+} или {$LongStrings On} String = AnsiString. По умолчанию.
- {$H-} или {$LongStrings Off} String = ShortString.
- Можно управлять из окна настроек проекта – “Compiler” -> “Huge strings”
- Если при определении типа String указана длина строки: String[32], то вне зависимости от установок компилятора это будет означать объявление ShortString соответствующего размера.

ShortString - короткая строка

Чтобы преобразовать ShortString в PChar надо ручками добавить в конец строки терминальный нуль #0 и вернуть адрес первого символа :
  function ShortStringToPChar (sstr: ShortString): PChar;
  begin
    sstr := sstr + #0;
    Result := @sstr[1];
  end;

AnsiString - длинная строка

AnsiString можно представить в виде записи:
  type // Это описательное определение,
  TAnsiString = record // не предназначенное для компиляции !!!
    RefCount: LongWord; //Счетчик ссылок
    Length: LongWord; // Длина строки
    Data: array[1..Length+1] of AnsiChar; //Массив символов, нумерация с единицы
  end;
     type
       PStrRec = ^StrRec;
       StrRec = packed record // Заголовок строки 8 байт
         refCnt: Longint; // 4 байта – счетчик ссылок
         length: Longint; // 4 байта – длина строки
       end;
В коде объяви строку и указатель на структуру заголовка:
var
  S: String;
  P: PStrRec;
Получи указатель на заголовок строки S:
P := Pointer(Integer(S) - 8); 
Здесь ты получил указатель на строку S и отступил от начала строки на 8 байт (длину заголовка StrRec). Теперь ты можешь смотреть значение счетчика, получать длину строки и даже менять их (но это не рекомендуется)

Memo1.Lines.Add('refCnt = ' + IntToStr(P.refCnt)); // Счетчик ссылок
Memo1.Lines.Add('length = ' + IntToStr(P.length)); // Длина строки
Delphi проверяет: попадает ли индекс в границы диапазона, как и с динамическими массивами (если включена проверка диапазона {$R+}). Но пустая длинная строка представлена нулевым указателем. Поэтому проверка границ пустой строки (при обращении к символу строки по индексу) приводит к ошибке доступа вместо ошибки выхода за границы диапазона.
 
По умолчанию проверка диапазона выключена ({$R-} или {$RangeChecks Off}), но лучше всегда ее включать, т.к. она помогает отловить многие ошибки, а в релизе сделает прогу менее чувствительной к BufferOverflow-атаке (т.н. строковое и массивное переполнение). По этой же причине всегда включай {$O+} или {$OverflowCheks On}. Выключай их только при серьезной проблеме с производительностью и только в критичных участках кода.
 
Пример. Вывод русского текста в консольное окно. Это почему-то у многих вызывает трудности.
  procedure WriteRussianText(Msg :String);
  // вывод строки в консольное окно в OEM кодировке
  begin
    UniqueString(Msg); // получим уникальный экземпляр строки
    Windows.CharToOem(PChar(Msg),PChar(Msg)); // преобразуем его
    Write(Msg); // выведем текст на консоль
  end;

PChar - нультерминальная строка

PWideChar

Многобайтовые строки - для сведения

type TMbcsByteType = (
  mbSingleByte,// Одиночный однобайтовый символ
  mbLeadByte, // Первый байт многобайтового символа
  mbTrailByte);// Второй байт многобайтового символа

function ByteType (const S: String; Index: Integer): TMbcsByteType;

На сладкое: resourcestring

         resourcestring
           MyResString = 'Hello World';
          resourcestring
            ResErrorMsg = 'Ошибка: невозможно сделать %0:s для %1:s , потому-что %2:s';
     type
       PResStringRec = ^TResStringRec;
       TResStringRec = packed record
         Module: ^Cardinal; // Модуль из которого загружен ресурс (чаще всего твой экзешник)
         Identifier: Integer; // Идентификатор строкового ресурса
       end;
      var ResID: Integer;
      ..
      ResID := PResStringRec(@MyResString).Indentifier;

Успехов! Орехов Роман aka tripsin


Приложение 1.

unit Unit1;
// Модуль для изучения AnsiString и его счетчика ссылок
interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
    // 1. Сначала попробуй процедуру без передачи параметра, потом
    // раскомментируй параметр везде и проверь, как он передается,
    // 2. при этом не забудь закомментировать строки обозначенные //***
    // 3. Напоследок попробуй передать параметр как (var S: String)
    procedure GetStringInfo{(S: String)};
  end;

var
  Form1: TForm1;
  S: String;
implementation

{$R *.dfm}
procedure TForm1.GetStringInfo{(S: String)};
type
  PStrRec = ^StrRec;
  StrRec = packed record // Заголовок строки 8 байт
    refCnt: Longint; // 4 байта
    length: Longint; // 4 байта
  end;
var
  S: String; //***
  P: PStrRec;
  pp: ^Byte;
begin
  S := 'AAAAAAAAAA'; // Так S будет ссылаться на литерал //***
//  S := S + 'a'; // Раскомментируешь - S будет в динамической памяти
  P := Pointer(Integer(s) - 8); // Смещаемся к началу заголовка

  // Проверяем размер переменной  S
  Memo1.Lines.Add('SizeOf(S) = ' + IntToStr(SizeOf(S)));
  // Проверяем размер заголовка
  Memo1.Lines.Add('Sizeof(StrRec) = ' + IntToStr(Sizeof(StrRec)));
  Memo1.Lines.Add('refCnt = ' + IntToStr(P.refCnt)); // Счетчик ссылок
  Memo1.Lines.Add('length = ' + IntToStr(P.length)); // Длина строки
  pp := Pointer(S); // Получаем указатель на строку
  inc(pp, P.length); // Передвигаемся в конец строки ...
  // ... и смотрим, что там
  Memo1.Lines.Add('Байт в конце строки = ' + IntToStr(Integer(pp^)));
  Memo1.Lines.Add('S = ' + s); // Выводим строку
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
 S := 'AAAAAAAAAAAAAAAA';
 GetStringInfo{( S)};
end;

end.

Опубликована на VR-online в 2006 г.

Hosted by uCoz