Консультация № 144768
23.09.2008, 15:33
0.00 руб.
0 22 3
hi...

Есть ли в delphi хороший и удобный способ создания динамического массива?
Перед мной стала задача создать динамический массив компонентов TEdit.
Полистав немного справку, на меня уставилась функция SetLength() и вот что из этого вышло:

// очистка всех полей редактирования переданной формы от текста
procedure clear_ed(form: TForm; eds_number: integer);
var
eds: array of TEdit;
i: integer;
begin
SetLength(eds, eds_number);
for i := 0 to form.Componentcount-1 do
begin
if(form.components[i] is TEdit) then
begin
eds[i]:= TEdit(form.components[i]);
eds[i].Clear;
end;
end;
end;

Однако, при повторном вызове этой функции, например кнопкой какой-нибудь - вылетает деббагер (
Еще немного полистав справку, нашелся GetMem(), от которого толку еще меньше...
Неужели нет в delphi такой юзабильной функции как например в С++(new) или в С(malloc, calloc) ?
Или что можно сделать с кодом выше?

спасибо...

Обсуждение

Неизвестный
23.09.2008, 15:45
общий
это ответ
Здравствуйте, Maksim Trofimov!


а точно у вас она ругается только при повторном вызове ? посмотрите сами
вы перебираете все компоненты на форме

for i := 0 to form.Componentcount-1 do

и если компонент is TEdit то вы что то делаете с компонентом т.е. ссылаетесь на какой то объект в массиве eds

и догадайтесь что происходит когда у вас на форме кроме TEdit есть например кнопка, следовательно вы выходите за диапазон массива eds.

вам нужно делать так, так как у вас изначально известно что в массиве eds хранятся ссылки только на TEdit, значит вам не нужно перебирать все компоненты на форме а обращаться непосредственно к элементам массива .

Код:
 
for i := 0 to length(eds)-1 do
begin
eds[i].Clear;
end;


Неизвестный
23.09.2008, 15:46
общий
это ответ
Здравствуйте, Maksim Trofimov!

1) у вас дебаггер вылетает из-за неправильного использования массива.
Вы выделяете eds_number элементов, а обращаетесь (записываетев массив) по индексу компонента. Вот и получается, что если Вы выделили место под 1 элемент, а на форма у Вас 3 компонента - то будет ошибка - обращение к несуществующему элементу при I = 2 к примеру. Индексы обращения к разным массивам должны быть разными.
Правильно будет так:

var
eds: array of TEdit;
i, j: integer;
begin
SetLength(eds, eds_number);
J := 0;
for i := 0 to form.Componentcount-1 do
begin
if form.components[i] is TEdit then
begin
eds[j]:= TEdit(form.components[i]);
eds[j].Clear;
Inc(j);
end;
end;
end;

2) От GetMem толк есть. Если использовать тип PPointerArray (для Вашего примера):

var
eds: PPointerArray;
i, j: integer;
begin
eds := GetMem(eds_number * sizeof(TEdit))
J := 0;
for i := 0 to form.Componentcount-1 do
begin
if form.components[i] is TEdit then
begin
eds^[j]:= form.components[i];
TEdit(eds^[j]).Clear;
Inc(j);
end;
end;
end;

давно
Мастер-Эксперт
425
4118
23.09.2008, 16:18
общий
Maksim Trofimov
А на сколько Вы уверены, что eds_number у Вас совпадает с количеством Edit'ов на форме?
Если Вы заранее не знаете количество, то динамический массив не самый удобный элемент. Проще использовать какой-либо класс эмулирующий динамический массив. Например TList:
Код:
Var
List: TList;
i: integer;
Begin
List:=TList.Create;
for i := 0 to form.Componentcount-1 do
if(form.components[i] is TEdit) then
List.Add(form.components[i]);
End;
Об авторе:
Я только в одном глубоко убеждён - не надо иметь убеждений! :)
Неизвестный
23.09.2008, 16:26
общий
А зачем нужно повторно обращаться к функции, где устанавливается размер динамического массива?
Если в процессе работы программы кол-во эдитов остаётся неизменным, тогда выполните один раз SetLength и всё.
При уменьшении или увеличении числа эдитов желательно обнулить размер массива SetLength(eds, 0) и заново заполнить его, но не
вижу смысла использовать здесь массив.

Неизвестный
23.09.2008, 17:00
общий
это ответ
Здравствуйте, Maksim Trofimov!
Судя по названию — функция для очистки TEdit компонент..
Но тогда, массив создавать не к чему.
Вот так, это может быть:
Код:
  procedure  clear_ed(form: TForm);
var i: integer;
begin
for i := 0 to form.Componentcount-1 do
begin
if (form.components[i] is TEdit) then
(form.components[i] as TEdit).Clear;
end ;
end ;
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
clear_ed(self);
end ;


Во вторых – создание динамических массивов в Delphi подчиняется темже принципам что и в C/C++. Но вы работаете не с простыми типами а с классами.
TEdit – это класс, потомок, далеко не первый, в дереве объектов.
И для его инициализации надо использовать метод Create.
Вот пример (в приложении) обоих процедур:


Приложение:
unit Unit1;

interface

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

type
TForm1 = class(TForm)
Edit1: TEdit;
Edit2: TEdit;
Edit3: TEdit;
Button1: TButton;
Label1: TLabel;
Label2: TLabel;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;

var
Form1: TForm1;
edit: array of TEdit;
implementation
procedure clear_ed(form: TForm);
var i: integer;
begin
for i := 0 to form.Componentcount-1 do
begin
if(form.components[i] is TEdit) then
(form.components[i] as TEdit).Clear;
end;
end;

procedure create_ed(form: TForm; count:integer);
var i: integer;
begin
SetLength(edit,count);
for i := 0 to count-1 do
begin
edit[i] := TEdit.Create(nil);
edit[i].Parent := form;
edit[i].Top := 136+i*21+2;
edit[i].Left := 528;
end;
end;
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
clear_ed(self); //очищаем все поля ввода self - указатель на текущую форму.. можно передать любую форму.
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
create_ed(self,5); // создать 5 TEdit
end;

procedure TForm1.FormCreate(Sender: TObject);
begin

end;

end.

Неизвестный
23.09.2008, 17:08
общий
Судя по тому, что делает процедура - городить массив не к чему.
Просто, есть попытка "прикрутить" подход C/C++ к Delphi, но, как я уже говорил, далеко не всегда и в СИ срабатывают конструкторы по умолчанию, они могут быть и не определены (например).. Даже там (С/С++) new, malloc и т.д. Вы выделяете память под размер структур (классов) но не инициализируете их. Хотя, это рассылка по Delphi, потому, разговор о других языках не уместен.
Неизвестный
24.09.2008, 01:49
общий
Все ясно, но разъесните мне пожайлуста, в обоих случаях(массив по setlength и getmem) мы получаем массив указателей, а для List? Получается, если мы получаем массив указателей, то удалять элементы нельзя вот так: eds[i].destroy(); ? Но сам массив удалить то можно, вот так freemem(eds), но это для getmem(), а для setlength() ? Хотелось бы разобраться с памятью в delphi, а то что в моей задачи не нужно массива, это просто пример )
давно
Мастер-Эксперт
425
4118
24.09.2008, 05:39
общий
Maksim Trofimov
Теория.
TList это тоже массив указателей, только более современного и удобного вида, оформленный как класс. Если тип класса в этом массиве заранее неизвестен, то его надо приводить к определённому типу.
Destroy() - этот метод используется самим классом, Вам вместо него нужно вызывать универсальный метод Free чтобы удалить элемент.
Практика.
Если Вы собираетесь делать массив компонентов, а не вообще всего на свете, то для этого есть специальный класс - TComponentList. При его использовании, для манипуляции с компонентами которые в нём хранятся, делать приведение типов уже необязательно, т.к. по умолчанию предполагается, что там хранятся именно TComponent и его наследники:
Код:
Var
List: TComponentList;
i: integer;
Begin
List:=TComponentList.Create;
for i := 0 to form.Componentcount-1 do
if(form.components[i] is TEdit) then
List.Add(form.components[i]);
//А вот и изюминка
for i:=0 To List.Count-1 Do
List[i].Clear;
//Как только List становится ненужен - удаляем его
//При этом автоматически вызывается деструктор хранимых в List компонентов
List.Free;
End;

На сегодняшний день использование динамических массивов уже во многом устаревшая практика и использовать его, по сравнению с классом, неудобно. Например удалив динамический массив, Вы оставляете в памяти объекты, которые он хранил и полностью теряете связь с этими объектами. Вам это надо? А программе, которая потеряла большой объём памяти и уже не может до него добраться?
Об авторе:
Я только в одном глубоко убеждён - не надо иметь убеждений! :)
Неизвестный
24.09.2008, 10:11
общий
Все ясно, но разъесните мне пожайлуста, в обоих случаях(массив по setlength и getmem) мы получаем массив указателей

Нет. "массив указателей", это нечто иное. таким образом, мы выделяем память размером равную структуре объекта (не важно, какой язык, класс, запись, структура, тип определенный пользователем и т.д.) умноженную на количество этих элементов (если это массив).
Инициализация же подразумевает заполнение внутренней структуры объекта (конкретного, т.е. экземпляра/элемента массива) определенными значениями. Потому, понятие выделить память и инициализировать объект есть не одно и то же.
Например, struct { int x; float y; char *s;} имеет размер ~ 12 байт.. Но, попробуйте использовать эту структуру без инициализации.. x, y будет содержать мусор, а s вообще будет ссыласться неизвестно на что.. таким образом:

Код:
  typedef struct {
int x;
float y;
char *s;
} _MSTR;

_MSTR *m = (_MSTR*) malloc(1);
printf("%i\t%f\t%s",m->x,m->y,m->s);


вызовет ошибку – не определен указатель s
а значения x и y будут иметь
x = 13788852
y = 6,1040561105989E-42
т.е. мусор.

Теперь, Вы поняли чем отличается "выделить память" и "инициализировать" ? И для чего нужны конструкторы как в C/C++ так и в Delphi ?
Создавая массив экземпляров классов (а почти все есть классы, за исключением простых типов), надо использовать механизмы работы с классами и методы самих классов.
Неизвестный
24.09.2008, 11:40
общий
sir Henry

Все отлично.., но если мне не нужно будет очищать список компонентов в текущем блоке и компоненты должны существовать до конца выполнения программы? То есть, мне обязательно надо вызвать метод free() перед завершением onclose() или это за меня сделает ось?

Например удалив динамический массив, Вы оставляете в памяти объекты, которые он хранил и полностью теряете связь с этими объектами

Вы имели ввиду: удалив указатель на массив, мы теряем доступ к объектам в памяти?

На сегодняшний день использование динамических массивов уже во многом устаревшая практика и использовать его, по сравнению с классом, неудобно.

Удобно то удобно, но а если нужно будет высокое бистродействие программы? Тут то на помощь придут дедовские массивы ))
давно
Мастер-Эксперт
425
4118
24.09.2008, 12:11
общий
1. Не ОСь, а сама программа при своём завершении. Сделает.
Вопрос в другом. Как только Вы вышли из процедуры, Вы автоматически теряете доступ к списку компонентов вне зависимости от того, с помощью чего этот список составлен - класса или динамического массива.
На самом деле этот вопрос некорректный, т.к. Вы не пояснили, какой список Вы имеете в виду - если список Form.Components, то он как был, так и остался, а вот список внутри процедуры Вы однозначно теряете навсегда.
2. Да.
3. А Вы протестируйте. Измерьте время выполнения.
Об авторе:
Я только в одном глубоко убеждён - не надо иметь убеждений! :)
Неизвестный
24.09.2008, 12:44
общий
sir Henry

если список Form.Components, то он как был, так и остался

Не, ну это понятно )

Как только Вы вышли из процедуры, Вы автоматически теряете доступ к списку компонентов вне зависимости от того, с помощью чего этот список составлен - класса или динамического массива.

Я вот о чем... получается, что каждый раз, когда вызывается функция очистки полей edit (на нашем примере), список компонентов (list) составляется занова, при этом он очищается программой автоматически, да?

А Вы протестируйте. Измерьте время выполнения.

Видимо, я еще не готов. Вот когда стану суровым программистом... ))
А пока...лень матушка )
Неизвестный
24.09.2008, 12:54
общий
Виктор Пырлик

Теперь, Вы поняли чем отличается "выделить память" и "инициализировать" ? И для чего нужны конструкторы как в C/C++ так и в Delphi ?

Это то я понял... Я вот узнать хотел что за звери такие Initialize(); Finalize(); и есть ли функция очистки, того что setlength() выделила?
давно
Мастер-Эксперт
425
4118
24.09.2008, 13:40
общий
Maksim Trofimov
Если следовать алгоритму Вашей процедуры, то вот что получается:
1. Создаётся List.
2. Сканируются все элементы формы и если элемент типа TEdit, то он добавляется в List.
3. Добавленный элемент очищается.
Таким образом у Вас выполняются эти действия каждый раз, как только Вы запускаете процедуру.
Видимо, я еще не готов. Вот когда стану суровым программистом... ))
А пока...лень матушка )

Суровый программист становится на основании опыта, а опыт - результат проведения тех или иных действий. А если лень - действий нет. Что тогда будет?
==========================================
Ваш пример нехорош для показа работы динамического массива или класса. Поэтому чтобы показать, где лучше применять динамический массив, а где класс, нужно работать с теми элементами, которые ещё не созданы, а не с теми, которые уже есть. В Вашем случае список элементов уже готов - это Form.Component. Создавать его дубль даже только с TEdit'ами бессмысленно. Попробуйте составить задачу, где список нужен не для готовых элементов, а которые Вы будете создавать при занесении в список и с которыми потом будете проводить групповую, однотипную работу. Вот к примеру:
Код:
Var
i: Integer;
List: TComponentList;
Begin
List:=TComponentList.Create;
For i:=0 To 9 Do
Begin
List.Add(TEdit.Create(Form));
List[i].Parrent:=Form;
List[i].Top:=(i+1)*20;
List[i].Bottom:=20;
List[i].Heght:=20;
List[i].Width:=100;
List[i].Text:=IntToStr(i);
End;
End;
Об авторе:
Я только в одном глубоко убеждён - не надо иметь убеждений! :)
Неизвестный
24.09.2008, 14:30
общий
Это то я понял... Я вот узнать хотел что за звери такие Initialize(); Finalize(); и есть ли функция очистки, того что setlength() выделила?

Всё это, и не только, описано в учебниках. Я не "отсылаю".. Просто, расписывать тут теорию нет смысла. Вы гораздо больше узнаете из учебников/книг.
Коротко - мы потому и говорим "используйте методы и свойства классов и инструменты работы с классами", что классы скрывают от вас часть служебных функций.. Они не думают что "программист тупой", просто они грамотно (часто или иногда) разработаны и обязаны иметь конструкторы и деструкторы. Которые вызывают последовательно в «правильном» порядке создание, инициализацию или освобождение занимаемых ресурсов, повторяю, мы же говорим не о простых типах. А что бы не получилось – создали массив классов (TEdit например) а потом просто «освободили память для этого массива», при этом, сами экземпляры классов остались висеть в памяти (вот и утечка), и существуют методы, например Free и другие. По умолчанию, как только теряется видимость для данного объекта (создали его в процедуре или функции и выходим из неё) - вызывается Деструктор этого объекта. Так же, вызываются деструкторы и при корректном завершении приложения, но уже для всего что занимало приложение. Даже явно вызывать, например Destroy, не корректно и часто неверно. Объектное программирование – не так просто как кажется. И не корректное использование инструментов ООП сегодня приводит к гораздо большим, трудно выявляемым ошибкам – та-же утечка памяти.
К слову, почти все объекты в Delphi порождены от одного – Tobject.. и дерево это довольно развесистое.
Неизвестный
24.09.2008, 14:35
общий
sir Henry

Если следовать алгоритму Вашей процедуры, то вот что получается:
1. Создаётся List.
2. Сканируются все элементы формы и если элемент типа TEdit, то он добавляется в List.
3. Добавленный элемент очищается
.

4. Выход из процедуры
5. Что происходит с List? Он удаляется из памяти?
6. Запуск процедуры.
7. 1-7

Вот что я имел ввиду )

Суровый программист становится на основании опыта, а опыт - результат проведения тех или иных действий.

А delphi как раз для ленивых программистов. RAD - за программиста все делает среда ))

delphi-программисты настолько суровы, что создают ftp-сервер одной кнопкой... )


Неизвестный
24.09.2008, 14:50
общий
Виктор Пырлик

Спасибо...
Так не охота книжки читать... Кстати говорят, что последовательное обучение даже хуже чем обучение, когда учишся на ошибках и ищешь решение проблемы в момент ее появления..
давно
Мастер-Эксперт
425
4118
24.09.2008, 18:30
общий
Maksim Trofimov
5. Теоретически да, память занятая List освобождается. Неосвобождается только память содержимого List. Например, Вы заходите в магазин и Вам дают коробку с конфетами. При выходе коробку у Вас отбирают. Вопрос - а конфеты? А вот конфеты остаются у Вас. Если их есть куда положить (карманы, например) - хорошо, конфеты Вы не потеряли. А вот если некуда, то вместе с коробкой Вы теряете и конфеты.
Кстати говорят, что последовательное обучение даже хуже чем обучение, когда учишся на ошибках и ищешь решение проблемы в момент ее появления..

Обучение только тогда идёт хорошо, когда оно основано на осмысленных примерах. На искусственных примерах, которые совершено не трогают душу обучаемого, учиться не только бесполезно, но и вредно. Такое учение приводит только к неврозу. А уж последовательное оно или параллельное - дело десятое.
delphi-программисты настолько суровы, что создают ftp-сервер одной кнопкой

Вы уверены? Это будет очень суровый сервер, который не отдаёт файлы.
Об авторе:
Я только в одном глубоко убеждён - не надо иметь убеждений! :)
Неизвестный
25.09.2008, 01:28
общий
sir Henry

5. Интересная теория )) Вот моя теория.
Хорошо, что список list хранит указатели на компоненты, а не их самих, что ведет к рациональному использованию памяти...
И вот на примере: Вы заходите в магазин и Вам дают коробку с конфетами. И вот привкушая как Вы будете есть очень вкусные конфетки, Вы открываете коробку, а там место каждой конфетки записка, что мол идите на склад и берите на 408 полке, 109 ящичке. И так вместо каждой конфетки, причем ящички отнють не смежные ))
Наверно после этого Вы и записочки брать то не будете, если у Вас коробку отберут...да ну ее на, скажете Вы ))
давно
Мастер-Эксперт
425
4118
25.09.2008, 05:43
общий
Maksim Trofimov
Я просто схожу на склад и съем рекомендованные к поеданию конфеты.
Да, согласен, работа с указателями это дольше и длиннее. Но в случае использования классов этот недостаток скрыт от программиста самим классом. Если вернуться к конфетам, то Вы суёте бумажку с надписью в щель считывателя и транспортёр (или отдел доставки) привозит Вам нужные конфеты.
Об авторе:
Я только в одном глубоко убеждён - не надо иметь убеждений! :)
Неизвестный
25.09.2008, 06:06
общий
sir Henry

Ну что могу сказать? Вопрос исчерпан, однако вот еще что:
delphi-программисты настолько суровы, что к ним ходят конфеты сами ))
давно
Мастер-Эксперт
425
4118
25.09.2008, 06:44
общий
Нет, дельфи-программисты настолько суровы, что от одного их взгляда служба доставки привозит им конфеты побросав в ужасе все остальные дела (чаепитие, перекуры и т.п.)
Об авторе:
Я только в одном глубоко убеждён - не надо иметь убеждений! :)
Форма ответа