#delphi #строки #delphi_xe
Function exchange(input: String): String; Var I: Integer; Begin Result:=input; I:=Length(Result); For I:=1 To I Do Begin If Result[I]='a' Then Result[I]:='b'; //[DCC Error] test.pas(13): E1047 Unsafe code 'String index to var param' If Result[I]='c' Then Result[I]:='d'; //[DCC Error] test.pas(14): E1047 Unsafe code 'String index to var param' If Result[I]='e' Then Result[I]:='f'; //[DCC Error] test.pas(15): E1047 Unsafe code 'String index to var param' End; End; Именно изменить, что можно отключить предупреждения я знаю, мне интересно как же пишется этот "надёжный" код и что именно ему тут не нравится?
Ответы
Ответ 1
Вот такой код будет корректным и безошибочным: Function exchange(input: String): String; Var I: Integer; sb: TStringBuilder; Begin sb := TStringBuilder.Create(input); try For I := 0 To sb.Length - 1 Do Begin if sb.Chars[I] = 'a' Then sb.Chars[I] := 'b'; if sb.Chars[I] = 'c' Then sb.Chars[I] := 'd'; if sb.Chars[I] = 'e' Then sb.Chars[I] := 'f'; End; Result := sb.ToString; finally sb.Free; end; End; А теперь объяснение: В современных версиях Делфы, при внедрении поддержки новых платформ (iOS, Android, x64 и т.п.) было принято решение уходить от строк индексируемых 1 .. N в пользу стандартных с индексацией 0 .. N-1. Данное изменение ломает практически весь существующий код. Поэтому его внедрение идет очень медленно и постепенно. В какой-то версии добавили предупреждения, в какой-то {$IFDEF ZEROBASEDSTRINGS}. Где-то эта директива включена по умолчанию, но в основном нет. О чем говорит данное предупреждение - о том, что обращаться к строкам по индексу небезопасно, т.к. индексация может быть заранее неизвестна (в зависимости от директивы и целевой платформы). Как быть? Самое простое решение, если вам нужна только Win32 - отключить предупреждения и спокойно жить дальше. Самое правильное, если вам нужны новые платформы - переписать код с использованием построителя строк (TStringBuilder), который сам разберется как именно строка индексируется, и предоставляет к ней единый интерфейс через свои методы. P.S. Как ни странно, For I:=1 To I Do является вполне корректным кодом, т.к. цикл сохраняет максимальное значение до начала итерирования.Ответ 2
Как было сказано выше, варнинг появляется из-за того, что современные версии Delphi стали поддерживать различные платформы, на которых нумерация строк сделана с 0, а не с 1, как это было раньше на Win32. И теперь, чтобы код работал на всех платформах, каноничный способ итерироваться по строкам заключается в использовании функций Low и High: function Exchange(const AInput: string): string; var I: Integer; begin Result := AInput; for I := Low(Result) to High(Result) do begin if Result[I] = 'a' then Result[I] := 'b' else if Result[I] = 'c' then Result[I] := 'd' else if Result[I] = 'e' then Result[I] := 'f'; end; end; Однако, данный способ сработает только в Delphi XE3 и выше, а вот в XE2 и ниже, будут ошибки: E2198 Low cannot be applied to a long string E2198 High cannot be applied to a long string И если вдруг хочется поддерживать старые версии Delphi, то придётся написать свои обёртки над этими функциями и использовать их: function StrLow(const S: string): Integer; inline; begin Result := {$IFDEF XE3UP} Low(S) {$ELSE} 1 {$ENDIF}; end; function StrHigh(const S: string): Integer; inline; begin Result := {$IFDEF XE3UP} High(S) {$ELSE} Length(S) {$ENDIF}; end; Однако, и здесь есть подводный камень - если вы вдруг в какой-то функции захотите временно включить/выключить поддержку новых строк через директиву ZEROBASEDSTRINGS и вызвать одну из этих функций, то вы получите ошибку, т.к. они будут выдавать результат в не зависимости от этой настройки (будут использоваться глобальные настройки этой директивы). Поэтому, просто не стоит управлять руками этой директивой внутри своих функций/юнитов. Подробное обсуждение вопроса о написании обратно-совместимого кода см. тут: How to work with 0-based strings in a backwards compatible way since Delphi XE5?
Комментариев нет:
Отправить комментарий