Класс basic_stringstream

Введение в класс std::string_view

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

В стандарте C++17 вводится еще один способ использования строк — с помощью класса std::string_view, который находится в заголовочном файле string_view.

В отличие от объектов класса std::string, которые хранят свою собственную копию строки, класс std::string_view обеспечивает представление (англ. «view») для заданной строки, которая может быть определена где-нибудь в другом месте.

Попробуем переписать код предыдущего примера, заменив каждое вхождение на :

#include <iostream>
#include <string_view>

int main()
{
std::string_view text{ «hello» }; // представление для строки «hello», которое хранится в бинарном виде
std::string_view str{ text }; // представление этой же строки — «hello»
std::string_view more{ str }; // представление этой же строки — «hello»

std::cout << text << ‘ ‘ << str << ‘ ‘ << more << ‘\n’;

return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13

#include <iostream>
#include <string_view>

intmain()

{

std::string_viewtext{«hello»};// представление для строки «hello», которое хранится в бинарном виде

std::string_viewstr{text};// представление этой же строки — «hello»

std::string_viewmore{str};// представление этой же строки — «hello»

std::cout<<text<<‘ ‘<<str<<‘ ‘<<more<<‘\n’;

return;

}

В результате мы получим точно такой же вывод на экран, как и в предыдущем примере, но при этом у нас не будут созданы лишние копии строки . Когда мы копируем объект класса std::string_view, то новый объект std::string_view будет «смотреть» на ту же самую строку, на которую «смотрел» исходный объект. Ко всему прочему, класс std::string_view не только быстр, но и обладает многими функциями, которые мы изучили при работе с классом std::string:

#include <iostream>
#include <string_view>

int main()
{
std::string_view str{ «Trains are fast!» };

std::cout << str.length() << ‘\n’; // 16
std::cout << str.substr(0, str.find(‘ ‘)) << ‘\n’; // Trains
std::cout << (str == «Trains are fast!») << ‘\n’; // 1

// Начиная с C++20
std::cout << str.starts_with(«Boats») << ‘\n’; // 0
std::cout << str.ends_with(«fast!») << ‘\n’; // 1

std::cout << str << ‘\n’; // Trains are fast!

return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

#include <iostream>
#include <string_view>

intmain()

{

std::string_viewstr{«Trains are fast!»};

std::cout<<str.length()<<‘\n’;// 16

std::cout<<str.substr(,str.find(‘ ‘))<<‘\n’;// Trains

std::cout<<(str==»Trains are fast!»)<<‘\n’;// 1

// Начиная с C++20

std::cout<<str.starts_with(«Boats»)<<‘\n’;// 0

std::cout<<str.ends_with(«fast!»)<<‘\n’;// 1

std::cout<<str<<‘\n’;// Trains are fast!

return;

}

Т.к. объект класса std::string_view не создает копии строки, то, изменив исходную строку, мы, тем самым, повлияем и на её представление в связанном с ней объектом std::string_view:

#include <iostream>
#include <string_view>

int main()
{
char arr[]{ «Gold» };
std::string_view str{ arr };

std::cout << str << ‘\n’; // Gold

// Изменяем ‘d’ на ‘f’ в arr
arr = ‘f’;

std::cout << str << ‘\n’; // Golf

return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

#include <iostream>
#include <string_view>

intmain()

{

chararr{«Gold»};

std::string_viewstr{arr};

std::cout<<str<<‘\n’;// Gold

// Изменяем ‘d’ на ‘f’ в arr

arr3=’f’;

std::cout<<str<<‘\n’;// Golf

return;

}

Изменяя , можно видеть, как изменяется и . Это происходит из-за того, что исходная строка является общей для этих переменных. Стоит отметить, что при использовании объектов класса std::string_view лучше избегать модифицирования исходной строки, пока существуют связанные с ней объекты класса std::string_view, так как в противном случае, это может привести к путанице и ошибкам.

Совет: Используйте std::string_view вместо строк C-style. Для строк, которые не планируете изменять в дальнейшем, предпочтительнее использовать класс std::string_view вместо std::string.

Шаблоны с переменным числом аргументов

Проблема с текущей реализацией QStringBuilder заключается в том, что он реализует контейнер с произвольным числом строк посредством вложения. Каждый экземпляр QStringBuilder может содержать ровно два элемента, будь то строки или другие экземпляры QStringBuilder.

Это означает, что все экземпляры QStringBuilder являются своего рода двоичным деревом, в котором QString являются конечными узлами. Всякий раз, когда нужно что-то делать с содержащимися строками, QStringBuilder должен обрабатывать свое левое поддерево, а затем правое поддерево рекурсивно.

Вместо того, чтобы создавать бинарные деревья, можно использовать шаблоны с переменным числом аргументов (доступно с C++11, которое не было доступно при создании QStringBuilder). Шаблоны с переменным числом аргументов позволяют создавать классы и функции с произвольным числом аргументов шаблона.

Это означает, что с помощью std::tuplewe можно создать шаблон класса QStringBuilder, который содержит столько строк, сколько хочется:

template <typename... Strings>
class QStringBuilder {
    std::tuple<Strings...> _strings;
};

Когда получим новую строку для добавления в QStringBuilder, можно просто добавить его в кортеж, используя std::tuple_cat, который объединяет два кортежа (будем использовать operator% вместо operator+, так как этот оператор также поддерживается QStringand QStringBuilder):

template <typename... Strings>
class QStringBuilder {
    std::tuple<Strings...> _strings;

    template <typename String>
    auto operator%(String&& newString) &&
    {
        return QStringBuilder<Strings..., String>(
            std::tuple_cat(_strings, std::make_tuple(newString)));
    }
};

Свёртка параметров шаблона

Это все хорошо, но вопрос в том, как обрабатывает пакеты свёртка параметров шаблона (fold expression) (часть Strings).

С C++17 получили новую конструкцию для обработки пакетов параметров, называемую свёртка параметров шаблона.

Общая форма свёртки параметров шаблона выглядит следующим образом (operator+ можно заменить другим бинарным оператором, таким как *, %…):

(init + ... + pack)

или:

(pack + ... + init)

Первый вариант называется

левая свёртка параметров шаблона (left fold expression)

, обрабатывает операцию, как left-associative (ассоциативную слева), а второй вариант называется

правая свёртка параметров шаблона (right fold expression)

, поскольку он обрабатывает операцию, как right-associative (ассоциативную справа).

Если бы желали объединить строки в пакете параметров шаблона с помощью свёртки параметров шаблона, можно было бы сделать это так:

template <typename... Strings>
auto concatenate(Strings... strings)
{
    return (QString{} + ... + strings);
}

Сначала будет вызван operator+ для начального значения QString{} и первого элемента пакета параметров. Затем он вызовет operator+ для результата предыдущего вызова и второй элемент пакета параметров. И так до тех пор, пока все элементы не будут обработаны.

Звучит знакомо, не правда ли?

То же самое поведение видели с std::accumulate. Единственное отличие состоит в том, что алгоритм std::accumulate работает с последовательностями данных во время выполнения (векторами, массивами, списками и т. д.). В то время, как свёртки параметров шаблона работают с последовательностями времени компиляции, то есть с пакетами параметров шаблона с переменным числом аргументов.

Можно выполнить те же шаги для оптимизации предыдущей реализации конкатенации, которую использовали для std::accumulate. Сначала нужно вычислить сумму всех длин строк. Это довольно просто с свёрткой параметров шаблона:

template <typename... Strings>
auto concatenate(Strings... strings)
{
    const auto totalSize = (0 + ... + strings.length());
    . . .
}

Когда свёртка параметров шаблона расширяет пакет параметров, оно получит следующее выражение:

0 + string1.length() + string2.length() + string3.length()

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

Как упоминалось ранее, свёртка параметров шаблона работает с бинарными операторами C++. Если хотим выполнить функцию для каждого элемента в пакете параметров, можно использовать один из самых странных операторов в C и C++ — оператор многоточия (comma operator).

template <typename... Strings>
auto concatenate(Strings... strings)
{
    const auto totalSize = (0 + ... + strings.length());
    QString result;
    result.reserve(totalSize);

    (result.append(strings), ...);

    return result;
}

Это вызовет добавление для каждой из строк в пакете параметров, давая, в итоге, объединенную строку.

Частоты отдельных слов

В строковом значении

Слово «она» встречается 3 раза, и частота «ее» указывается равной 3. Слово «он» встречается 2 раза, а частота «он», как говорят, равна 2. слова имеют частоту 1 каждое. Частоту каждого слова можно определить следующим образом:

Имейте все слова на карте C ++ без повторения. Рассмотрим следующее утверждение:

где mp — объект карты. В первый раз, когда этот оператор встречается в цикле while, он вписывается в новую пару ключ / значение, ключ которой является строковым словом переменной temp, а значение равно 1. В следующий раз, когда он встречается в том же цикле while- цикл, с этим конкретным ключом на карту не добавляется новая пара ключ / значение. Значение этой пары ключ / значение просто увеличивается.

Итак, стратегия состоит в том, чтобы этот оператор находился в цикле while, а все слова в буфере ssstream считывались во временную переменную. И каждое значение для каждой пары ключ / значение на карте, наконец, становится частотой ключа (слова). Следующая программа иллюстрирует это:

Результат:

Ключевые советы

Гайдлайны хорошего стиля:

  • используйте декомпозицию при объявлении переменных:
  • в параметрах всех функций и методов вместо принимайте невладеющий по значению
  • завершайте все блоки case, кроме последнего, либо атрибутом , либо инструкцией
  • смело пишите
  • избегайте вложенности пространств имён, а если не избежать, то объявляйте их с помощью

Гайдлайны по STL:

  • предпочитайте вместо для композиции объекта T, время жизни которого короче времени жизни владельца
  • используйте тип вместо enum или полиморфных классов в ситуации, когда состояния, такие как состояние лицензии, не могут быть описаны константами enum из-за наличия дополнительных данных
  • используйте тип вместо enum в ситуации, когда данные, такие как код ошибки в исключении, должны быть обработаны во всех вариантах, и неполная обработка вариантов должна приводить к ошибке компиляции
  • используйте тип вместо any везде, где это возможно
  • предпочитайте вместо boost::filesystem
  • используйте to_chars и from_chars

    не используйте их напрямую: рискуете сделать небрежную обработку ошибок

    для реализации библиотечных функций сериализации и парсинга чисел

  • используйте std::size для измерения длины C-style массива

Использование std::wstring (и, при необходимости, winrt::hstring) с Uri

Windows::Foundation::Uri формируется из winrt::hstring.

Но hstring имеет , позволяющие работать с ним, не учитывая этот факт. Ниже приведен пример кода, показывающий, как создать Uri из литерала широкой строки, представления широкой строки и std::wstring.

Метод доступа к свойству Uri::Domain имеет тип hstring.

Но опять же, знать об этом не обязательно благодаря .

Аналогичным образом IStringable::ToString возвращает hstring.

Uri реализует интерфейс IStringable.

Можно использовать функцию для получения стандартной широкой строки из hstring (так же, как и из std::wstring).

Если у вас есть hstring, значит, вы можете получить Uri.

Рассмотрим метод, который принимает hstring.

Все параметры, которые мы рассмотрели, также применяются в таких случаях.

Для hstring имеется член-оператор преобразования std::wstring_view, и преобразование осуществляется без дополнительных затрат.

Свободные операторы

Оператор Описание
Возвращает значение, указывающее, равны ли два параметра друг другу.
Возвращает новый объект HString , который приводит к объединению двух параметров.
Возвращает значение, указывающее, меньше ли первый параметр второго параметра.
Возвращает значение, указывающее, является ли первый параметр меньшим или равным второму параметру.
Возвращает значение, указывающее, равны ли два параметра друг другу.
Возвращает значение, указывающее, больше ли первый параметр второго параметра.
Возвращает значение, указывающее, больше или равен ли первый параметр второму параметру.

Шаблоны рекурсивных выражений

Теперь можно вернуться к эффективной конкатенации строк, используя operator+ в Qt.

QString result = statement + space + number + period;

Видно, что проблема с конкатенацией строк проистекает из того факта, что C++ оценивает предыдущее выражение поэтапно, вызывая operator+ несколько раз, когда каждый вызов возвращает новый экземпляр QString.

Хотя невозможно изменить способ оценки этого в C++, можно использовать технику, называемую expression templates (шаблонами выражений), для задержки фактического вычисления результирующей строки до тех пор, пока не будет определено все выражение. Это можно сделать, изменив возвращаемый тип operator+ не на QString, а на некоторый пользовательский тип, который просто хранит строки, которые должны быть объединены без фактического выполнения объединения.

Фактически, это именно то, что Qt делает с 4.6, если вы активируете быструю конкатенацию строк. Вместо возврата QString operator+ вернет экземпляр скрытого шаблона класса называемого QStringBuilder. Шаблон QStringBuilderclass — это просто фиктивный тип, который содержит ссылки на аргументы, передаваемые operator+.

Более сложная версия следующего:

template <typename Left, typename Right>
class QStringBuilder {
    const Left& _left;
    const Right& _right;
};

Когда вы объединяете несколько строк, вы получаете более сложный тип, в котором несколько QStringBuilders вложены друг в друга. Что-то вроде этого:

QStringBuilder<QString, QStringBuilder<QString, QStringBuilder<QString, QString>>>

Этот тип просто сложный способ сказать: «Я держу четыре строки, которые должны быть объединены».

Когда запрашиваем преобразование QStringBuilder в QString (например, присваивая его результату QString), он сначала вычисляет общий размер всех содержащихся строк, затем выделяет QStringinstance этого размера и, наконец, скопирует строки одну за другой в результирующую строку.

По сути, он будет делать то же самое, что делалось ранее, но это будет сделано автоматически, без необходимости «поднимать палец».

Конвертация строк

Класс QString содержит следующие методы: toInt(), toFloat() и toLong(), которые позволяют преобразовать строку в типы int, float и long int соответственно. Метод setNum() позволяет конвертировать различные числовые данные в строку. Данный метод является перегруженным, поэтому компилятор сам позаботится о том, чтобы вызвать подходящий вариант этого метода. В следующем примере мы конвертируем две строки в целочисленный тип данных, а затем складываем получившиеся числа. После этого мы уже выполняем обратную конвертацию: из чисел в строки и соединяем уже строки.

#include <QTextStream>

int main() {

QTextStream out(stdout);

// Наши строки
QString s1 = «12»;
QString s2 = «15»;
QString s3, s4;

// С помощью метода toInt() конвертируем строки в целочисленный тип данных, а затем складываем получившиеся числа
out << s1.toInt() + s2.toInt() << endl;

int n1 = 30;
int n2 = 40;

// С помощью метода setNum() выполняем конвертацию из целочисленного типа данных в QString, а затем соединяем уже строки
out << s3.setNum(n1) + s4.setNum(n2) << endl;

return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

#include <QTextStream>
 

intmain(){

QTextStream out(stdout);

// Наши строки

QString s1=»12″;

QString s2=»15″;

QString s3,s4;

// С помощью метода toInt() конвертируем строки в целочисленный тип данных, а затем складываем получившиеся числа

out<<s1.toInt()+s2.toInt()<<endl;

intn1=30;

intn2=40;

// С помощью метода setNum() выполняем конвертацию из целочисленного типа данных в QString, а затем соединяем уже строки

out<<s3.setNum(n1)+s4.setNum(n2)<<endl;

return;

}

Результат выполнения программы:

Создание строки

Строковые классы имеют ряд конструкторов, которые можно использовать для создания строк. Здесь мы рассмотрим каждого из них.

Примечание: преобразуется в , который является тем же целочисленным типом без знака, который возвращается оператором . Его фактический размер зависит от среды. Для целей этого руководства представьте его как .

Конструктор по умолчанию. Создает пустую строку.

Пример кода:

Вывод:

Конструктор копирования. Этот конструктор создает новую строку как копию .

Пример кода:

Вывод:

Этот конструктор создает новую строку, содержащую не более символов из , начиная с индекса .

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

Пример кода:

Вывод:

Этот конструктор создает новую строку из строки в стиле C, но не включая завершающий ноль.

  • Если результирующий размер превышает максимальную длину строки, будет сгенерировано исключение .
  • Предупреждение: не должна быть равна .

Пример кода:

Вывод:

Этот конструктор создает новую строку из первых символов из строки в стиле C.

  • Если результирующий размер превышает максимальную длину строки, будет сгенерировано исключение .
  • Предупреждение: только для этой функции нули в не обрабатываются как символы конца строки! Это означает, что можно выйти за конец строки, если значение слишком велико. Будьте осторожны, чтобы не переполнить строковый буфер!

Пример кода:

Вывод:

Этот конструктор создает новую строку, инициализированную вхождениями символа .

Если результирующий размер превышает максимальную длину строки, будет сгенерировано исключение length_error.

Пример кода:

Вывод:

Этот конструктор создает новую строку, инициализированную символами диапазона [, ).

Если результирующий размер превышает максимальную длину строки, будет сгенерировано исключение length_error.

Здесь нет примера, поскольку это достаточно запутанный способ создания строки, и вы вряд ли когда-то им не воспользуетесь.

Обзор классов строк

Весь строковый функционал стандартной библиотеки находится в заголовочном файле. Чтобы использовать его, просто включите заголовок :

На самом деле в заголовке есть 3 разных класса строк. Первый – это шаблонный базовый класс с именем :

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

Стандартная библиотека предоставляет две разновидности :

Это два класса, которые вы непосредственно будете использовать. используется для стандартных строк ASCII и UTF-8. используется для строк с расширенными символами / Unicode (UTF-16). Для строк UTF-32 нет встроенного класса (хотя вы можете расширить из свой собственный класс, если он вам нужен).

Хотя вы будете напрямую использовать и , весь строковый функционал реализован в классе . и могут получить доступ к этому функционалу напрямую благодаря шаблону. Следовательно, все представленные функции будут работать как для , так и для . Однако, поскольку является шаблонным классом, это также означает, что, если вы сделаете что-то синтаксически неверное со или , компилятор выдаст ужасно выглядящие ошибки шаблона. Не пугайтесь этих ошибок; они выглядят намного хуже, чем они есть на самом деле!

Вот список всех функций в строковом классе. Для обработки различных типов входных данных большинство этих функций имеют несколько разновидностей, которые мы рассмотрим более подробно в следующих уроках.

Функция Действие
Создание и уничтожение
конструктор Создает или копирует строку
деструктор Уничтожает строку
Размер и вместимость
Возвращает количество символов, которые могут храниться без перераспределения памяти
Возвращает логическое значение, указывающее, пуста ли строка
, Возвращает количество символов в строке
Возвращает максимальный размер строки, которая может быть размещена
Увеличить или уменьшить вместимость строки
Доступ к элементам
, Доступ к символу по определенному индексу
Модификация
, Присваивает новое значение строке
, , Добавляет символы в конец строки
Вставляет символы в строку по произвольному индексу
Удаляет все символы в строке
Стирает символы по произвольному индексу в строке
Заменяет символы с произвольным индексом на другие символы
Расширение или сжатие строки (обрезает или добавляет символы в конце строки)
Меняет местами значения двух строк
Ввод и вывод
, Считывает значения из входного потока в строку
Записывает значение строки в выходной поток
Возвращает содержимое строки как строку в стиле C с завершающим нулем
Копирует содержимое (не оканчивающееся нулем) в массив символов
То же, что . Неконстантная перегрузка позволяет выполнять запись в возвращаемую строку
Сравнение строк
, Сравнивает, равны или неравны две строки (возвращает )
, , , Сравнивает, являются ли две строки меньше/больше друг друга (возвращает )
Сравнивает, равны или неравны две строки (возвращает -1, 0 или 1)
Подстроки и конкатенация
Объединяет две строки
Возвращает подстроку
Поиск
Найти индекс первого символа/подстроки
Найти индекс первого символа из набора символов
Найти индекс первого символа, не входящего в набор символов
Найти индекс последнего символа из набора символов
Найти индекс последнего символа, не входящего в набор символов
Найти индекс последнего символа/подстроки
Поддержка итераторов и распределителей памяти (аллокаторов)
, Поддержка итератора прямого направления для начала/конца строки
Возвращает распределитель
, Поддержка итератора обратного направления для начала/конца строки

Хотя строковые классы библиотеки STL предоставляют множество функций, есть несколько заметных упущений:

  • поддержка регулярных выражений;
  • конструкторы для создания строк из чисел;
  • функции изменения регистра на верхний/нижний;
  • сравнение без учета регистра;
  • разбиение строки на массив;
  • простые функции для получения левой или правой части строки;
  • обрезка пробелов в начале и конце строки;
  • форматирование строки в стиле ;
  • преобразование из UTF-8 в UTF-16 или наоборот.

Для большинства из них вам придется либо написать свои собственные функции, либо преобразовать вашу строку в строку в стиле C (используя ) и использовать функции C, которые предлагают эту функциональность.

В следующих уроках мы более подробно рассмотрим различные функции класса . Хотя в наших примерах мы будем использовать , всё в равной степени применимо и к .

Доступ к элементам строки

Объект класса QString представляет собой последовательность QChar-«символов» (я специально взял в кавычки слово «символы»). Доступ к элементам строки можно получить c помощью оператора индексации или метода at(). В следующем примере мы будем выводить отдельные символы строки, которая является объектом класса QString:

#include <QTextStream>

int main() {

QTextStream out(stdout);

// Исходная строка
QString a = «Eagle»;

// Выводим первый символ строки
out << a << endl;

// Выводим пятый символ строки
out << a << endl;

// Выводим первый символ строки с помощью метода at()
out << a.at(0) << endl;

return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

#include <QTextStream>
 

intmain(){

QTextStream out(stdout);

// Исходная строка

QStringa=»Eagle»;

// Выводим первый символ строки

out<<a<<endl;

// Выводим пятый символ строки

out<<a4<<endl;

// Выводим первый символ строки с помощью метода at()

out<<a.at()<<endl;

return;

}

Результат выполнения программы:

Примечание: Убедитесь, что при использовании метода at() ваш индекс не выходит за границы массива.

Выравнивание строк

Для выравнивания строк используются методы leftJustified() и rightJustified(). В следующем примере мы выполним выравнивание первых частей (подстрок) каждой строки по правому краю:

#include <QTextStream>

int main() {

QTextStream out(stdout);

QString field1 = «Name: «;
QString field2 = «Occupation: «;
QString field3 = «Residence: «;
QString field4 = «Marital status: «;

// Вычисляем размер самой широкой строки
int width = field4.size();

out << field1.rightJustified(width, ‘ ‘) << «Robert\n»;
out << field2.rightJustified(width, ‘ ‘) << «programmer\n»;
out << field3.rightJustified(width, ‘ ‘) << «New York\n»;
out << field4.rightJustified(width, ‘ ‘) << «single\n»;

return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

#include <QTextStream>
 

intmain(){

QTextStream out(stdout);

QString field1=»Name: «;

QString field2=»Occupation: «;

QString field3=»Residence: «;

QString field4=»Marital status: «;

// Вычисляем размер самой широкой строки

intwidth=field4.size();

out<<field1.rightJustified(width,’ ‘)<<«Robert\n»;

out<<field2.rightJustified(width,’ ‘)<<«programmer\n»;

out<<field3.rightJustified(width,’ ‘)<<«New York\n»;

out<<field4.rightJustified(width,’ ‘)<<«single\n»;

return;

}

Результат выполнения программы:

Метод rightJustified() возвращает строку размером , которая содержит символ-заполнитель, за которым следует строка. В нашем случае этим символом-заполнителем является пробел. Если строка короче, то остаток заполняется символом-заполнителем:

out << field1.rightJustified(width, ‘ ‘) << «Robert\n»;

1 out<<field1.rightJustified(width,’ ‘)<<«Robert\n»;
Рейтинг
( Пока оценок нет )
Понравилась статья? Поделиться с друзьями:
Все про сервера
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: