Иногда, при печати содержимого контейнера хочется избежать ненужного хвостового разделителя.
Простейшее решение выглядит так:
#include <iostream>
#include <vector>
int main(int argc, char* argv[]) {
int a[] = { 1, 2, 3, 4, 5 };
std::vector<int> v(a, a + 5);
for (int i = 0; i < v.size(); ++i) {
std::cout << v[i];
if (i < v.size() - 1)
std::cout << ", ";
}
std::cout << std::endl;
return 0;
}
Условие в теле цикла решает поставленную задачу, но контейнеры лучше обходить через итераторы, поэтому следующая попытка может выглятеть так:
#include <iostream>
#include <vector>
int main(int argc, char* argv[]) {
int a[] = { 1, 2, 3, 4, 5 };
std::vector<int> v(a, a + 5);
for (std::vector<int>::const_iterator i = v.begin(); i != v.end(); ++i) {
std::cout << *i;
if (v.end() - i > 1)
std::cout << ", ";
}
std::cout << std::endl;
return 0;
Но такой подход не самый верный, ибо итераторы далеко не всех контейнеров поддерживают операцию вычетания. Например, при использовании std::list
вместо std::vector
будет ошибка компиляции (как, кстати, и для первого примера, но по другой причине). Поэтому правильнее было бы написать:
#include <iostream>
#include <vector>
int main(int argc, char* argv[]) {
int a[] = { 1, 2, 3, 4, 5 };
std::vector<int> v(a, a + 5);
typedef std::vector<int>::const_iterator iterator;
for (iterator i = v.begin(); i != v.end(); ++i) {
std::cout << *i;
if (std::distance<iterator>(i, v.end()) > 1)
std::cout << ", ";
}
std::cout << std::endl;
return 0;
}
Шаблонный класс std::distance
умеет рассчитывать расстояние между итераторами, и даже для тех, которые не поддерживают операции сложения и вычетания. Для таких итераторов будет делаться пошаговый обход от одного к другому для подсчета расстояния. На первый взгляд получается, что вычислительная сложность такого простого цикла будет уже не линейной, а квадратической. Еше надо таскать за собой описание типа дважды чтобы создать итератор цикла и экземпляр std::distance
. Например, Visual Studio 2008 требует указывать тип итератора для шаблона std::distance
и не может "угадать" его из параметров (другие компиляторы могут вести себя иначе). Получается, на ровном месте навернули какую-то ерунду.
Но есть весьма элегантный способ, который позволяет и использовать итераторы, и сохранить линейную сложность алгоритма для контейнеров, которые не умеют эффективно вычислять расстояние между элементами (например, std::list
), и писать красиво и компактно:
#include <iostream>
#include <vector>
int main(int argc, char* argv[]) {
int a[] = { 1, 2, 3, 4, 5 };
std::vector<int> v(a, a + 5);
for (std::vector<int>::const_iterator i = v.begin(); i != v.end(); ++i) {
std::cout << *i;
if (i != --v.end())
std::cout << ", ";
}
std::cout << std::endl;
return 0;
}
Трюк с оператором "--" позволяет эффективно проверить на последний элемент контейнера.
есть другой вариант, абсолютно универсальный и подходящий даже для си: печатать разделитель перед элементом, но не перед самым первым. Найти первый намного проще, правда ведь?
ОтветитьУдалитьbialix: Хм, ваша правда. Век живи, век учись.
ОтветитьУдалитьПо идее, конечно, должно быть без разницы, сравнивать с первым или последним итератором, так как они оба известны на старте. Но из-за особенности итераторов в C++, когда end() указывает за последний элементы, а не на него, и надо вычислять один элемент назад (что и является темой упражнения), то прием с проверкой на первый элемент выглядит очень красиво.
А вот, как это делается _не_ на C++:
ОтветитьУдалить$ python
>>> v = [1,2,3,4,5]
>>> print ", ".join([str(x) for x in v])
1, 2, 3, 4, 5
>>>
У С++ же сложности на пустом месте из-за бедной стандартной библиотеки.
не все контейнеры двунаправленные, так что operator-- не всегда поможет
ОтветитьУдалитьесть boost::algorithm::join
он как раз добавляет первый элемент, а потом джойнит все остальные через сепаратор:
if(itBegin!=itEnd)
{
detail::insert(Result, ::boost::end(Result), *itBegin);
++itBegin;
}
for(;itBegin!=itEnd; ++itBegin)
{
// Add separator
detail::insert(Result, ::boost::end(Result), ::boost::as_literal(Separator));
// Add element
detail::insert(Result, ::boost::end(Result), *itBegin);
}
return Result;
Спасибо за пост.
ОтветитьУдалитьА можно небольшой оффтопик-вопрос? Как вы выделяете код как код, да еще и с подсветкой? А-то я тут немного новичек в жужл-блогах.
Этот комментарий был удален автором.
ОтветитьУдалитьGreen FiLin: Я использую highlight. В шаблоне блога надо добавить загрузку этого скрипта, и после этого все тэги code будут автоматически раскрашиваться.
ОтветитьУдалитьЕсли ставить разделитель перед элментами, то указатель не обязвтельно сравнивать с первым вообще. Нужно ввести bool, показывающий печатался ли первый элемент. Если да, выводим разделитель.
ОтветитьУдалитьbool first_writen = false;
for (std::vector::const_iterator i = v.begin(); i != v.end(); ++i) {
if (first_writen)
std::cout << ", ";
std::cout << *i;
first_writen = true;
}
«Оптимазация» — два «цикла» с общим i.
td::vector::const_iterator i = v.begin();
if (i != v.end()) {
std::cout << *i;
i++;
}
for (; i != v.end(); ++i) {
std::cout << ", ";
std::cout << *i;
}
Но это, разумеется, нельзя назвать изящьным решением.