TStringList的addObject方法

13

我想知道这个方法调用是做什么的:

stringList.addObject(String,Object);

我也想知道这个属性是做什么用的:

stringList.Objects[i]

在添加时看起来是键值对,但在循环中检索时会检索到什么?

我还看到了items[i]的调用。

我对TStringList操作和TList操作感到困惑。


1
这真的是古老的非泛型 Delphi 的遗物。现在使用这种粗糙的无类型机制已经没有意义了。泛型的 TList<T> 取代了这种技术。 - David Heffernan
1
@RRUZ 没错。在使用泛型之前可以用它,但是一旦你超越了这个阶段,请停止使用它。 - David Heffernan
2
@Ken 当你下次在那个领域工作时,请重新编写它。但是将资源投入到这样的整理工作中很重要。 - David Heffernan
6
我非常不同意这种想法,如果代码能够正常工作并经过充分测试,那就没有必要为了重写而重写。在我看来,没有必要修补一些无需修补的小问题或功能性变化,用我爷爷的话说:“不要修理不坏的东西”。只有当需要彻底改变功能时,才值得冒着引入新错误和增加额外开销的风险去进行改写。 - Ken White
1
@Arnaud 这就是我所说的“当你下次在那个领域工作时,你可以重新编写它。” - David Heffernan
显示剩余4条评论
3个回答

19
它添加了一对项目:一个在 TStringList.Strings 列表中的条目,以及一个匹配的 TObjectTStringList.Objects 列表中。
这使您可以存储一组字符串列表,为项目提供名称,并且一个对象是包含匹配项的类。
type
  TPerson=class
    FFirstName, FLastName: string;
    FDOB: TDateTime;
    FID: Integer;
  private
    function GetDOBAsString: string;
    function GetFullName: string;
  published
    property FirstName: string read FFirstName write FFirstName;
    property LastName: string read FLastName write FLastName;
    property DOB: TDateTime read FDOB write FDOB;
    property DOBString: string read GetDOBAsString;
    property FullName: string read GetFullName;
    property ID: Integer read FID write FID;
  end;

implementation

{TPerson}
function TPerson.GetDOBAsString: string;
begin
  Result := 'Unknown';
  if FDOB <> 0 then
    Result := DateToStr(FDOB);
end;

function TPerson.GetFullName: string;
begin
  Result := FFirstName + ' ' + FLastName; // Or FLastName + ', ' + FFirstName
end;

var
  PersonList: TStringList;
  Person: TPerson;
  i: Integer;
begin
  PersonList := TStringList.Create;
  try
    for i := 0 to 9 do
    begin
      Person := TPerson.Create;
      Person.FirstName := 'John';
      Person.LastName := Format('Smith-%d', [i]); // Obviously, 'Smith-1' isn't a common last name.
      Person.DOB := Date() - RandRange(1500, 3000);  // Make up a date of birth
      Person.ID := i;
      PersonList.AddObject(Person.LastName, Person);
    end;

    // Find 'Smith-06'
    i := PersonList.IndexOf('Smith-06');
    if i > -1 then
    begin
      Person := TPerson(PersonList.Objects[i]);
      ShowMessage(Format('Full Name: %s, ID: %d, DOB: %s',
                         [Person.FullName, Person.ID, Person.DOBString]));
    end;
  finally
    for i := 0 to PersonList.Count - 1 do
      PersonList.Objects[i].Free;
    PersonList.Free;
  end;

这显然是一个刻意编造的例子,因为它并不是你真正会发现有用的东西。不过它演示了这个概念。

另一个方便的用途是将整数值与字符串一起存储(例如,在 TComboBoxTListBox 中显示项目列表以及用于数据库查询的相应 ID)。在这种情况下,您只需将整数(或任何其他 SizeOf(Pointer))强制转换为 Objects 数组即可。

// Assuming LBox is a TListBox on a form:
while not QryItems.Eof do
begin
  LBox.Items.AddObject(QryItem.Fields[0].AsString, TObject(QryItem.Fields[1[.AsInteger));
  QryItems.Next;
end;

// User makes selection from LBox
i := LBox.ItemIndex;
if i > -1 then
begin
  ID := Integer(LBox.Items.Objects[i]);
  QryDetails.ParamByName('ItemID').AsInteger := ID;
  // Open query and get info.
end;

如果存储的不是实际的 TObject,则无需释放其内容。由于它们不是真正的对象,除了 TStringList 本身之外,没有其他需要释放的东西。


3
较新版本的 Delphi 中有一个 TStringList 对象,其拥有一个类似于 TObjectList 的 OwnsObjects 属性(以及构造函数参数)。如果将其设置为 true,则在释放列表之前,您不需要且实际上不应该释放其中的实例。 - Marjan Venema
当我们执行 stringListB := stringListA 时,描述对象会发生什么将会很有帮助。stringListB.Objects 是否会复制指向原始对象的指针?这是否意味着当我们想要释放 stringListA 时,不应该逐个释放其分配的对象? - Vassilis
@VassilisGr:在上面的代码中,没有对象。它们是整数,所以没有什么需要释放的。使用stringListB := stringListA;不是复制;它将对stringListA的引用分配给变量stringListB。也就是说,它们是同一件事情,被两个变量引用。因为没有第二个字符串列表的副本,所以没有第二个Objects的副本。如果这些对象不是真正的对象,那么没有理由释放任何东西(正如我答案的最后一段明确说明的那样)。 - Ken White
@Ken White,我是在讨论实际对象的情况。当然,我同意你的观点。我现在暂时不考虑对象!这段代码为什么能正常工作?因为Items是一个TListBox的属性吗?在这种情况下,它如何处理赋值?slA:= TStringList.create; slA.add('A'); ListBox1.Items:= slA; slA.Free; ShowMessage(ListBox1.Items [0]);如果它只是一个引用,通过释放slAListBox1.Items不应该包含任何值(至少)。但是,如果我们使用另一个TStringList实例代替ListBox,则会像你说的那样导致代码崩溃。我的第一个问题是使用TListBox.Items发展出来的。 - Vassilis
@VassilisGr:不,你的代码是错误的。:) slA := TStringList.Create; slA.Add('A'); ListBox1.Items.Assign(slA); slA.Free; 是正确的方式(或者用 ListBox1.Items.AddStrings(slA); 替换 Assign)。问题在于 Items := slA; 只是得到了 slA 的第二个引用(它们是同一个对象);因此,slA.Free; 使得 ListBox1.Items 中包含的引用无效,因为它们是同一件事情。我在上一条评论中已经说过了。如果您需要更多关于这些内容如何工作的信息,请发布自己的新问题;这不适合在本问题的评论中讨论。 - Ken White
显示剩余3条评论

5
AddObject方法可以让你存储一个TObject地址 (指针),与存储在Item属性中的字符串相关联。而Objects属性用于访问存储的对象。
以下是一个简单示例,使用AddObject来存储与每个字符串关联的整数值。
var
 List : TStringList;
 I    : integer;
begin
  try
    List:=TStringList.Create;
    try
      List.AddObject('Item 1', TObject(332));
      List.AddObject('Item 2', TObject(345));
      List.AddObject('Item 3', TObject(644));
      List.AddObject('Item 4', TObject(894));

      for I := 0 to List.Count-1 do
        Writeln(Format('The item %d contains the string "%s" and the integer value %d',[I, List[I], Integer(List.Objects[i])]));
    finally
      List.Free;
    end;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  Readln;
end.

1
是的,TStringList.Objects[]可以用来存储值。但请记住,在XE2中指针大小并不总是等于整数大小! - Toon Krijthe
1
确实,但如果您使用x64,则指针的大小为8个字节,并且完全可以容纳始终为4个字节大小的整数。 - RRUZ
1
如果您使用的是在TStringList拥有OwnsObjects属性(和构造函数参数)之前的Delphi版本,并且您不仅仅存储值而是实例引用,那么您必须在释放StringList本身之前释放任何放置在这些对象引用中的实例(除非当然这些实例被其他东西所拥有)。 - Marjan Venema

2

TStringList不仅仅是一个字符串列表。

它可以用于名称值对:

stringlist.Values['apple'] := 'one';
stringlist.Values['banana'] := 'two';

但是它也可以用于将字符串与任何对象(或任何指针)关联起来。

stringlist.AddObject('apple', TFruit.Create);
stringlist.AddObject('banana', TFruit.Create);


i := stringlist.IndexOf('apple');
if i >= 0 then
  myfruit := stringlist.Objects[i] as TFruit;

TList是一个存储指针的列表,它们与字符串无关。


3
你需要在回答中提到任何分配的对象(TFruit.Create)必须被释放(例如 for i := 0 to stringlist.Count-1 do stringList.Objects[i].Free;OwnsObjects 属性),并且不要使用 TStringList 来存储刚创建的对象。你可以将其与具有 VCL 管理所有权或具有自己生命周期的对象一起使用。因为 OP 似乎没有经验,所以你的答案应该至少提到这一点(就像 Ken White 的回答)。 - Arnaud Bouchez

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接