Qt змінити алгоритм порівняння рядків. QT: працюємо зі списком рядків QStringList та стандартними контейнерами Tulip

Займався я сьогодні написанням деякої демонстраційно-тестової програми з використанням Qt. Додаток це має виконувати роль GUI-обгортки над об'єктами певної спеціалізованої бібліотеки, яку я написав у boost для деякої прикладної області.

Робота виконується у Linux. Дистрибутив Ubuntu 11:10. Локаль – UTF-8.

І ось виникла у мене проблема. Засобами boost::system::error_code, в ядрі моєї бібліотеки формувалося деяке локалізоване повідомлення (тобто російською мовою), яке мені знадобилося відобразити засобами Qtв екземплярі класу QTextEdit.

Взагалі, треба було б давно заборонити використання національних мов у системних повідомленнях. Однак, мабуть, це комусь здається недемократичним, тому програмісти щодня тисячами підриваються на цих проблемах і ми постійно в готових додаткахстикаємося з кракозябрами там, де могли прочитати нормальний англійський текст. Адже у чому проблема? Домогосподарки не спроможні читати навіть повідомлення прикладного рівня, і уявити домогосподарку, яка зрозуміє, що треба робити прочитавши системне повідомлення з мережевої підсистеми ядра типу "Адреса вже використовується" рідною мовою, мені здається неймовірним. А якщо це не для домогосподарки, то навіщо ускладнювати життя фахівцям, які все вміють читати англійською? До цього можна додати проблему некоректних перекладів. Зрозуміло, питання риторичне. Проте хочеться висловитися.

Насправді проблеми я не зрозумів і був вкрай здивований, коли перетворивши отримане повідомлення з std::stringв QStringі вивівши його потім у вікно редактора, я побачив кракозябри. Справді. В чому може бути проблема? Якби я, не дай боже, працював під Windows, то дивуватися було б не чому. Давно не цікавився як там справи зараз, але за часів Windows XP мене веселило три одночасно використовувані кодові сторінки в російських версіях операційної системи. От уже справді, не операційна системаа система латок. Але звідки могла взятися проблема в Linux? Якщо в ній, в моїй збірці, використовується одна кодова сторінка UTF-8 у всіх підсистемах ядра і користувальницького простору.

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

QTextCodec *codec = QTextCodec::codecForName("UTF8"); if (codec) ( std::string str = boost_lib->getLastError(); // Get string from a boost library QByteArray ba(str.c_str()); // Convert to QByteArray QString msg = codec->toUnicode(ba ); // Qt magic !!!m_pteLog->append(msg); // Append msg to a QTextEdit object )

Загалом латку я знайшов, але в проблемі розбиратися не став. Мабуть, за замовчуванням, текст, що отримується в Qt з рядкових однобайтових типів, сприймаються в кодуванні Latin1, тому і виникало, в моєму випадку, неприємне неправильне перекодування.

Наприкінці цієї короткої замітки я хотів би висловити надію, що якщо хтось із читачів знає подробиці механізму трансляції кодів символів, що зачіпається тут, то нехай він його пояснить у коментарях чи листом мені, а я зроблю доповнення.

Клас QString призначений для зберігання та обробки рядків (тексту).

Найпростіший спосіб ініціалізації об'єкта записується як

QString str="Hello";

QString str("Hello");

Для QString можна використовувати оператори "+" та "+=". Це з прикладів реалізації принципу поліморфізму, тобто. одні й самі оператори (функції) можуть обробляти дані різних типів. Така можливість значно спрощує роботу користувача.

str += "World!";

Крім того, є функція QString::append(), яка ідентична за своєю дією оператору "+=":

str.append("World!");

Ще один спосіб "складання" рядка з інших рядків і чисел, а також ще один приклад поліморфізму - використання функції arg () за рахунок використання символів, що управляють:

str = "%1%2 (%3s-%4s)";

str.arg("permissive").arg("society").arg(1950).arg(1970);

У цьому прикладі символи "%1" будуть замінені словом "permissive", "%2" – "society", "%3" – "1950" та "%4" – "1970". В результаті вийде рядок "permissive society (1950-1970)". Клас має кілька перевантажених функцій arg() для обробки різних типівданих. Деякі з них мають Додаткові параметри, що управляють довжиною вихідного рядка, базою системи числення та точністю представлення чисел.

QString дозволяє перетворювати числа в їх рядкове уявлення за допомогою статичної функції QString::number():

str = QString::number(59.6);

або за допомогою QString::setNum():

str.setNum(59.6);

Зворотне перетворення може бути виконано функціями toInt(), toDouble() тощо, наприклад:

double d = str.toDouble(&ok);

Ці функції можуть приймати необов'язковий аргумент типу bool, де повертається ознака успіху перетворення. Якщо перетворення може бути виконано, вони повертають 0.

Найчастіше виникає ситуація, коли необхідно витягти частину рядка. Функція mid() повертає підрядок заданої довжини, починаючи із заданої позиції у вихідному рядку. Наприклад, наступний код виводить рядок "pays":

QString str="polluter pays principle";

qDebug()<< str.mid(9, 4);

Якщо опустити другий аргумент (або передати як другий аргумент число -1), функція поверне підрядок, починаючи з заданої позиції до кінця вихідного рядка. Наприклад, наступний код виведе рядок "pays principle":

qDebug()<< str.mid(9);

Додатково є функції left() та right(). Вони обидві приймають кількість символів n і повертають перші або останні символи n вихідного рядка, відповідно. Наприклад, наступний код виведе рядок "polluter principle":

qDebug()<< str.left(8).ascii() << str.right(9) << endl;

Якщо потрібно виконати перевірку – чи починається чи закінчується рядок певною комбінацією символів, для цих цілей існують функції startsWith() та endsWith():

if (uri.startsWith("http:") && uri.endsWith(".png"))

Для рядків визначено оператора порівняння рядків "==". Він чутливий до регістру символів. Для виконання реєстронезалежного порівняння, можна скористатися функціями upper() або lower(), наприклад:

if (fileName.lower() == "readme.txt")

Для заміни одного підрядка в рядку на інший підрядок, використовуйте функцію replace():

QString str="a sunny day";

str.replace(2, 5, "cloudy");

в результаті вийде рядок "a cloudy day". Те саме дію може виконано за допомогою функцій remove() і insert():

str.remove(2, 5);

str.insert(2, "cloudy");

У першому рядку видаляється п'ять символів, починаючи з 2-ї позиції, в результаті виходить рядок "a day" (з двома пробілами), потім у другу позицію вставляється слово "cloudy". Існують перевантажені версії функції replace(), які замінюють усі входження першого аргументу на другий. Наприклад, щоб замінити всі символи "&" у рядку на "&":

str.replace("&", "&");

Дуже часто виникає необхідність викинути з початку та кінця рядка всі зайві пробільні символи (такі як пробіли, символи табуляції, символи перекладу рядка). Для цієї мети існує функція stripWhiteSpace():

QString str="BOB\tTHE\nDOG\n";

qDebug()<< str.stripWhiteSpace();

Для демонстрації того, що робота з іншими контейнерами може бути організована аналогічно, можна змінити тільки наповнення методу doFile() . У наведеному нижче коді рядки виду "ключ:значення", що вводяться користувачем, де "ключ" - ціле число, а "значення" - рядок, записуються в мультихеш (асоціативний масив, в якому одному ключу може відповідати кілька значень).

Отриманий мультихеш виводиться в консоль налагодження. У реальному завданні після його формування можна виконувати будь-які необхідні дії з даними, наприклад, отримати список усіх значень, що відповідають ключу "0", ми можемо так:

QList lst=hash.values(0);

Приклад введення користувача для формування хеша:

1:345 1 2 3:44 1:567 1:789 string

І висновок у консолі налагодження QT для нього:

Element: "1" Element: "1:345" Element: "1:567" Element: "1:789" Element: "2" Element: "3:44" Element: "string" Key= 0 Value= "1 Key= 1 Value= "2" Key= 1 Value= "789" Key= 1 Value= "567" Key= 1 Value= "345" Key= 2 Value= "(!LANG:string" Key= 3 Value= "44" !}

Ось змінений метод doFile() , також довгим коментарем пояснено перший регулярний вираз:)

Void Widget::doFile(void) ( QString String = this->textEdit->toPlainText(); QStringList list = String.split("\n"); list.removeDuplicates(); //Прибрали дублюючі один одного рядки list. sort(Qt::CaseInsensitive);//Сортували з ігноруванням регістру літер QRegExp regExp("^(?!\\s*$).+"); //^ (?! \\s * $) .+ // поч негативна роздільник нуль кон будь-який символ // перевірка або більше хоча б один //Виходить: //"якщо в рядку є хоч один символ - не треба роздільників зліва від нього" list = list.filter(regExp); //прибрали рядки тільки з роздільників list.replaceInStrings(QRegularExpression("\s+")," "); //прибрали зайві роздільники між словами list.replaceInStrings(QRegularExpression("^\s+|\s+$"),""); //прибрали зайві роздільники на початку або наприкінці рядків //Користувацька частина віджету QMultiHash hash; // Ключ - число, значення - рядки QStringList:: iterator it = list.begin(); int key = 0; //Ключ для елементів, яким його не дав користувач for (;it!=list.end();++it) ( //Пройти за списком елементів "ключ:значення" QStringList item = (*it).split(" :",QString::SkipEmptyParts); //розбити елемент по роздільнику ":" if (item.size()<2) hash.insertMulti(key++,item.at(0)); else hash.insertMulti(item.at(0).toInt(),item.at(1)); //Добавили в хэш ключ (наш или заданный пользователем) и значение qDebug() << "Element: " << (*it); } //Вывести в консоль отладки мультихэш QMultiHash::iterator i = hash.begin(); for (;i!=hash.end();++i) qDebug()<< "Key=" << i.key() << "Value="<< i.value(); //кінець частини користувача this->(!LANG:textEdit->clear(); this->textEdit->append(list.join("\n")); } !}

Символьні рядки

Основний спосіб представлення символьних рядків С++ полягає у застосуванні масиву символів char,завершується нульовим байтом ("\0"). Наступні чотири функції демонструють роботу таких рядків:

01 void hello1()

03 const char str = (

04 "H", "e", "l", "l", "o", " ", "w", "o", "r" "l", "d", "\0"

08 void hello2()

10 const char str="Hello world!";

13 void hello3()

17 void hello4()

19 const char *str = "Hello world!";

У першій функції рядок оголошується як масив та ініціалізується посимвольно. Зверніть увагу на символ у кінці "\0", що позначає кінець рядка. Друга функція має аналогічне визначення масиву, але для ініціалізації масиву використовується рядковий літерал. У С++ рядкові літерали – це просто масиви символів const char,завершуються символом "\0", який не вказується у літералі. У третій функції рядковий літерал використовується без надання йому імені. Після перекладу на інструкції машинної мови вона буде ідентична першим двом функцій.

Четверта функція трохи відрізняється, оскільки створює не тільки масив (без імені), але й змінну-покажчик з ім'ям str,у якій зберігається адреса першого елемента масиву. Незважаючи на це, семантика даної функції ідентична семантиці попередніх трьох функцій, і компілятор, що оптимізує, видалить зайву змінну str.

Функції, що приймають як аргументи рядка С++, зазвичай оголошують їх як char *або const char*.Нижче наводиться коротка програма, що ілюструє обидва підходи:

03 using namespace std;

04 void makeUppercase(char *str)

06 for (int i = 0; str[i] != "\0"; ++i)

07 str[i] = toupper(str[i]);

09 void writeLine(const char * str)

13 int main(int argc, char *argv)

15 for (int i = 1; i

16 makeUppercase(argv[i]);

17 writeLine(argv[i]);

У С++ тип charзазвичай займає 8 біт. Це означає, що у масиві символів charлегко можна зберігати рядки в кодуванні ASCII, ISO 8859-1 (Latin-1)і в інших 8-бітових кодуваннях, але не можна зберігати довільні символи Unicode,якщо не вдаватися до багатобайтових послідовностей. Qt надає потужний клас QString,який зберігає рядки Unicodeу вигляді послідовностей 16-бітових символів QCharі за їх реалізації використовує оптимізацію неявного суміщення даних («копіювання під час запису»). Детальніше рядки QStringрозглядаються у розділі 11 («Класи-контейнери») та у розділі 17 («Інтернаціоналізація»).