C++Builder
  Начало   Форум   Помощь Войти Регистрация  
Страниц: [1]   Вниз
  Печать  
Автор Тема: STL map  (Прочитано 5262 раз)
SilverFish
Участник


Сказали спасибо: +14/-6
Offline

Сообщений: 786


STL map
« : 22 Июль 2014, 16:19:53 »

Удобный контейнер STL map хранит ключ=значение и сортирует по ключу.
Мне хотелось бы иметь контейнер, который хранил бы по ключу несколько значений, типа так:
ключ=
String значение1
String значение2
String значение3

Я думаю, что можно сделать так:
std::map MyCustomContent;
Как правильно заполнять второй параметр TStringList такого контейнера?
Как правильно обратиться, например, к значению №2?
Как правильно выделить память для TStringLis в этом синтаксисе и чтобы не было утечки памяти?
Можно пример?
Спасибо.


« Последнее редактирование: 22 Июль 2014, 19:30:03 от SilverFish » Записан
S0mbre
Глобальный модератор


Сказали спасибо: +1113/-22
Offline

Сообщений: 2132

S0mbre


Re: STL map
« Ответ #1 : 22 Июль 2014, 19:34:56 »

Вторым параметром может быть любой объект (и даже, например, указатель на функцию или функтор).
Если тебе надо хранить в значении только список строк, это можно сделать множеством способов:
1) TStringList (как ты предлагаешь), TDynArray или другие VCL/RTL объекты
2) STL контейнер, например std::vector, std::vector<:string>, std::deque<...>, std::list<...> и т.п.
3) собственный класс или структура, например
Код
struct TMyClass
{
  String* Data;
  size_t Size;
  __fastcall TMyClass(const size_t ASize) :
  Size(ASize), Data(new String[ASize]) { }
  __fastcall ~TMyClass() { delete [] Data; }
};
 

Лучше определить названия типов для сложного ассоциативного контейнера. Например
Код
typedef std::mapString, TStringList*> TStrListMap;
typedef TStrListMap::iterator TStrListMap_I;
typedef TStrListMap::const_iterator TStrListMap_CI;
 

Создаем пустой контейнер:
Код
TStrListMap MyMap;
 

Теперь можно добавлять и удалять данные. Т.к. значения требуют в данном случае динамического выделения памяти, лучше использовать функции. Например для удаления 1 элемента:
Код
void __fastcall DeleteElement(const String& AKey)
{
  TStrListMap_I it = MyMap.find(AKey);
  if(it != MyMap.end()) { // если ключ найден
     delete it->second; // освобождаем память, используемую значением (2-я часть пары-элемента)
     MyMap.erase(it); // удаляем элемент из контейнера
  }
}
 

Для очистки всего контейнера:
Код
void __fastcall ClearMap()
{
  for(TStrListMap_I it=MyMap.begin(); it!=MyMap.end(); ++it) delete it->second;
  MyMap.clear();
}
 

Добавление и доступ к элементам проще некуда:
Код
MyMap["One"] = new TStringList(); // добавляем 1-ый элемент
MyMap["One"]->Add("One 1"); // добавляем строки в 1-ый элемент
MyMap["One"]->Add("One 2");
MyMap["One"]->Add("One 3");
MyMap["Two"] = new TStringList(); // добавляем 2-1 элемент
MyMap["Two"]->Add("Two 1"); // добавляем строки во 2-й элемент
MyMap["Two"]->Add("Two 2");
 
Записан
SilverFish
Участник


Сказали спасибо: +14/-6
Offline

Сообщений: 786


Re: STL map
« Ответ #2 : 22 Июль 2014, 22:39:06 »

Спасибо, теперь вижу, как добавлять и очищать элементы. 
Мне пока не понятно, а как получить доступ к конкретному элементу списка?
Допустим итератор стоит на первом элементе списка:
std::map::iterator It = MyCustomContent.begin();
Как прочитать значение номер 2 из TStringLis? Читать я умею только так (*It).second, но в нашем случае это не то, что надо, ведь second состоит из трех элементов в нашем случае…

Общая задача у меня такая:
У меня есть INI файл:
[FILE1]
time=08:00:00
name=all7-3sec.mp4
about = Утренние завтраки до 12 часов дня

[FILE2]
time=13:00:00
name=all720-new.mp4
about = Ланч со скидкой 10% до 14 часов

и т.д.

Я в цикле, читаю INI, заполняю контейнер map где ключом контейнера является секция time. Таким образом, я получаю автоматом отсортированный по времени контейнер. Это то, что мне нужно в программе. Мне надо во втором параметре контейнера хранить список, в первом значении списках хранить имя секции (FILE№), во втором значении списках хранить значение секции name, в третьем элементе списка хранить секцию about. Я решила, что лучший вариант описать такой блок это map

Такой контейнер map будет удобен во многих случаях, например:
если я где то в программе меняю ключ-время, то прочитав имя секции из второго параметра контейнера, я буду знать куда в INI записать это новое время.


Записан
S0mbre
Глобальный модератор


Сказали спасибо: +1113/-22
Offline

Сообщений: 2132

S0mbre


Re: STL map
« Ответ #3 : 23 Июль 2014, 07:43:53 »

В этом конкретно случае я бы создал свою структуру и хранил бы в контейнере ее объекты. Например
Код
// структура для хранения данных из 1 секции ini файла
struct TIniSection
{
TDateTime FDateTime; // время (=time)
String Name; // =name
String About; // =about
 
__fastcall TIniSection(const TDateTime& ADateTime=TDateTime(),
const String& AName="", const String& AAbout="") :
FDateTime(ADateTime), Name(AName), About(AAbout) { }
};
 
// определения типов
typedef std::mapString, TIniSection> TIniMap;
typedef TIniMap::iterator TIniMap_I;
typedef TIniMap::const_iterator TIniMap_CI;
 
// функция для заполнения контейнера из ИНИ файла
void __fastcall GetIniMap(TIniFile* IniFile, TIniMap& AMap)
{
// очищаем контейнер
AMap.clear();
// список названий секций
std::unique_ptrTStringList> slSections(new TStringList());
// получаем названия секций из ini
IniFile->ReadSections(slSections.get());
// цикл по секциям
for(int i=0; islSections->Count; i++) {
// добавляем след. элемент в контейнер
AMap[slSections->Strings[i]] = TIniSection(
IniFile->ReadDateTime(slSections->Strings[i], "time", TDateTime()),
IniFile->ReadString(slSections->Strings[i], "name", ""),
IniFile->ReadString(slSections->Strings[i], "about", "")
);
}
}
 

В ассоциативных контейнерах (map, multimap...) нет прямого доступа по индексу. На то они и ассоциативные, чтобы доступ был по ключу, например
Код
TIniMap AMap;
GetIniMap(std::unique_ptrTIniFile>(new TIniFile("inifile.ini")).get(), AMap);
TDateTime DT = AMap["FILE1"].FDateTime; // время из 1-й секции ini файла ("FILE1")
 

Если нужен доступ по индексу, используй другие контейнеры - vector, deque и т.д.
Записан
SilverFish
Участник


Сказали спасибо: +14/-6
Offline

Сообщений: 786


Re: STL map
« Ответ #4 : 23 Июль 2014, 11:45:44 »

Большое спасибо!
Записан
SilverFish
Участник


Сказали спасибо: +14/-6
Offline

Сообщений: 786


Re: STL map
« Ответ #5 : 23 Июль 2014, 13:35:24 »

Можно еще получить разъяснения по найденному мною коду. Хотелось бы понять в образовательных целях.

Код
std::mapString, std::unique_ptrTStringList> > MyCustomContent;
MyCustomContent["key"].reset(new TStringList);
//**************
TStringList *values = MyCustomContent["key"];
values->Add("value1");
values->Add("value2");
values->Add("value3");
//**************
String value = MyCustomContent["key"]->Strings[1]; // second value

Что делает метод reset?
Как происходит инициализация без оператора new:
TStringList *values = MyCustomContent["key"];
?
Записан
S0mbre
Глобальный модератор


Сказали спасибо: +1113/-22
Offline

Сообщений: 2132

S0mbre


Re: STL map
« Ответ #6 : 24 Июль 2014, 10:58:58 »

Метод reset удаляет объект из динамического ("умного") указателя и заменяет его тем, который указан в параметре. Т.е.
Код
MyCustomContent["key"].reset(new TStringList);
 
делает примерно то же, что и:
Код
delete MyCustomContent["key"];
MyCustomContent["key"] = new TStringList();
 

Инициализации без оператора new быть не может - в этом случае. В этой строке -
Код
TStringList *values = MyCustomContent["key"];
 
мы просто получаем доступ к значению (элементу) контейнера по ключу (при учете что он там есть). На самом деле правильнее будет написать
Код
TStringList&* values = MyCustomContent["key"];
 
т.к. перегруженный оператор [] контейнера возвращает ссылку на элемент, а не его копию.
Записан
SilverFish
Участник


Сказали спасибо: +14/-6
Offline

Сообщений: 786


Re: STL map
« Ответ #7 : 24 Июль 2014, 17:27:37 »

Спасибо!
Записан
SilverFish
Участник


Сказали спасибо: +14/-6
Offline

Сообщений: 786


Re: STL map
« Ответ #8 : 24 Июль 2014, 22:19:10 »

Цитировать
TStringList&* values = MyCustomContent["key"];
А если поменять местами? Так правильнее?
TStringList * & values = MyCustomContent["key"];
Записан
S0mbre
Глобальный модератор


Сказали спасибо: +1113/-22
Offline

Сообщений: 2132

S0mbre


Re: STL map
« Ответ #9 : 26 Июль 2014, 15:58:28 »

Да, конечно, моя опечатка 
Код
TStringList*& values = MyCustomContent["key"];
 
Записан
SilverFish
Участник


Сказали спасибо: +14/-6
Offline

Сообщений: 786


Re: STL map
« Ответ #10 : 28 Июль 2014, 17:00:50 »

Как правильно проверить, что итератор принадлежит контейнеру map?
Код
if(ListView1->ItemIndex != -1)
{
 std::mapString, std::vectorString> >::iterator It = IniContent.begin();
 int pos = ListView1->Items->Item[ListView1->ItemIndex]->Index;
 for(int i = 0; i pos; i++)
 {
It++;
 }
Теперь надо убедиться, что итератор принадлежит диапазону значений из контейнера map и не вышел за границу.
Спасибо.
Записан
S0mbre
Глобальный модератор


Сказали спасибо: +1113/-22
Offline

Сообщений: 2132

S0mbre


Re: STL map
« Ответ #11 : 29 Июль 2014, 12:32:11 »

Цитата: SilverFish от 28 Июль 2014, 17:00:50
Как правильно проверить, что итератор принадлежит контейнеру map?
Код
if(ListView1->ItemIndex != -1)
{
 std::mapString, std::vectorString> >::iterator It = IniContent.begin();
 int pos = ListView1->Items->Item[ListView1->ItemIndex]->Index;
 for(int i = 0; i pos; i++)
 {
It++;
 }
Теперь надо убедиться, что итератор принадлежит диапазону значений из контейнера map и не вышел за границу.
Спасибо.

Можно проверять до цикла по итератору:
Код
typedef std::mapString, std::vectorString> >::iterator TStrVecMap_I; // тип итератора
 
const int Index = ListView1->ItemIndex;
if(Index 0 || Index >= IniContent.size()) throw Exception("List index out of bounds!"); // проверка индекса
TStrVecMap_I it=IniContent.begin();
for(int i=0; iIndex; i++) ++it; // теперь итератор в правильной позиции
 

Только, как я уже говорил, я не понимаю смысла проходить итератором по _ассоциативному_ контейнеру - они не для этого предназначены и хранят данные не по порядку (как в последовательном контейнере), а на основе слабой сортировки ключей. Т.е. элемент с ключом "А" всегда будет располагаться _до_ элемента с ключом "Б" или "Я".
Записан
SilverFish
Участник


Сказали спасибо: +14/-6
Offline

Сообщений: 786


Re: STL map
« Ответ #12 : 29 Июль 2014, 14:15:59 »

Спасибо.

Цитировать
Только, как я уже говорил, я не понимаю смысла проходить итератором по _ассоциативному_ контейнеру -

У меня значения контейнера map грузятся в список ListView. Значения автоматом сортируются по значению ключа, ключом выступает время. Это очень удобно и не требует доп. кода. Теперь мне надо при щелчке на список ListView правильно установить итератор на пункт списка, чтобы работать с данными map в месте щелчка. Я просто хотела добавить дополнительную проверку на валидность итератора и разобраться как правильно делать проверку.

« Последнее редактирование: 29 Июль 2014, 14:23:24 от SilverFish » Записан
S0mbre
Глобальный модератор


Сказали спасибо: +1113/-22
Offline

Сообщений: 2132

S0mbre


Re: STL map
« Ответ #13 : 29 Июль 2014, 15:12:18 »

Можно же проще установить итератор:
Код
if(ListView1->ItemIndex >= 0) {
 TStrVecMap_I it = IniContent.find(ListView1->Items->Strings[ListView1->ItemIndex]);
 if(it != IniContent.end()) { // если найден элемент ...
   // работа с элементом через итератор
 }
 // ...
}
 
Записан
SilverFish
Участник


Сказали спасибо: +14/-6
Offline

Сообщений: 786


Re: STL map
« Ответ #14 : 29 Июль 2014, 15:54:20 »

Цитировать
ListView1->Items->Strings[ListView1->ItemIndex]

Я не могу добраться до времени таким образом. Время находиться в item->Data[sText] и располагается справа в списке.
Вот пример заполнения списка:
Код
const System::UnicodeString sText = L"CA";
void __fastcall TMainForm::ListView1UpdatingObjects(const TObject *Sender, const TListViewItem *AItem,
         bool &AHandled)
{
//взято из стандартных примеров студии - делает метку справа в списке, там покажем время
TListItemText * LCaption = reinterpret_castTListItemText*>((const_castTListViewItem*>(AItem))->Objects->FindObject(sText));
if (LCaption == NULL)
{
LCaption = new TListItemText((TListItem*)AItem);
LCaption->Name = sText;
LCaption->Align = TListItemAlign::Trailing;
LCaption->VertAlign = TListItemAlign::Trailing;
LCaption->PlaceOffset->X = 15;
LCaption->PlaceOffset->Y = 12;
LCaption->TextAlign = TTextAlign::Center;
LCaption->Trimming = TTextTrimming::Character;
LCaption->IsDetailText = True;
LCaption->Width = 90;
LCaption->Height = 45;
}
}
 
//заполнение списка
std::mapString, std::vectorString> >::iterator It = IniContent.begin();
  int i = 1;
  ListView1->BeginUpdate();
while (It != IniContent.end())
 {
  TListViewItem * item = ListView1->Items->Add();
  item->Text = L"№" + IntToStr(i) + " " + IniContent[(*It).first][1];//имя файла из ini
  item->Detail = MainForm->IniContent[(*It).first][2];//about из ini файла
  item->BitmapRef = Image1->Bitmap;
  item->Data[sText] = TValue::FromUnicodeString>((*It).first);//время из ini пишем справа в списке
  It++;
  i++;
 }
  ListView1->EndUpdate();
 

Получаем список нужной структуры (рис.).
Возникла идея дать возможность пользователю редактировать время в списке и тут не нашла решение как это сделать. Теперь надо смотреть в сторону ListBox...
Записан
SilverFish
Участник


Сказали спасибо: +14/-6
Offline

Сообщений: 786


Re: STL map
« Ответ #15 : 31 Июль 2014, 09:18:41 »

Если, к примеру, предполагается такое заполнение контейнера map:
Код:
std::map > MyCustomContent;
std::vector &values = MyCustomContent["key"];//ссылка
values.push_back("value1");
values.push_back("value2");
values.push_back("value3");

То достаточно ли для полной очистки контейнера map просто написать:
MyCustomContent.clear(); ?
Записан
S0mbre
Глобальный модератор


Сказали спасибо: +1113/-22
Offline

Сообщений: 2132

S0mbre


« Ответ #16 : 31 Июль 2014, 09:59:09 »

Цитата: SilverFish от 31 Июль 2014, 09:18:41
Если, к примеру, предполагается такое заполнение контейнера map:
Код:
std::map > MyCustomContent;
std::vector &values = MyCustomContent["key"];//ссылка
values.push_back("value1");
values.push_back("value2");
values.push_back("value3");

То достаточно ли для полной очистки контейнера map просто написать:
MyCustomContent.clear(); ?

Да, конечно. Память под элементы ведь выделяется не динамически, т.е. в них присутствуют не указатели, а непосредственно объекты. Метод clear() последовательно вызывает деструкторы пар "ключ-значение" - деструкторы ключей и значений. В твоем случае - это:
Ключ: UnicodeString::~UnicodeString()
Значение: vector::~vector(), который в свою очередь для каждого элемента вызывает UnicodeString::~UnicodeString()   
Записан
Страниц: [1]   Вверх
  Печать  
 
Перейти в:  




....

Powered by SMF 1.1.21 | SMF © 2025, Simple Machines
Страница сгенерирована за 0.136 секунд. Запросов: 21.