我参考http://zetcode.com/gui/qt5/introduction/这个链接入坑,使用 macOS。网上看起来是 Windows 和 Linux 的教程居多,这个链接里的教程初看起来应该也是基于 Linux 的。谁让 Mac 的市场占有率低呢。

1 First Try

1.1 Show version

首先是从官网下载了 GUI 形式的安装器安装的 Qt,Qt 的默认安装位置是~/Qt。安装的 Qt 版本是 5.13.1。首先参照教程熟悉一下编译过程。教程链接里给出的例子是:

1
2
3
4
5
6
7
// version.cpp
#include <QtCore>
#include <iostream>

int main() {
std::cout << "Qt version: " << qVersion() << std::endl;
}

编译的命令是

1
g++ -o version version.cpp -I/usr/local/qt5/include/QtCore -I/usr/local/qt5/include -L/usr/local/qt5/lib -lQt5Core -fPIC

不过这个编译命令在 Mac 上无法成功。我使用的是如下命令:

1
2
3
4
5
6
7
8
9
10
11
g++ -o version version.cpp \
# Include path,需要指向包含QtCore的头文件的位置
-Ipath/to/qt/5.13.1/clang_64/lib/QtCore.framework/Headers \
# -F指定framework的路径,-framework则用来指明使用的framework的名字
-F/Users/lena/qt/5.13.1/clang_64/lib -framework QtCore
# 在mac上g++其实也是调用的clang,这里加上这个避免一些warning
-isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX10.13.sdk \
-mmacosx-version-min=10.12 \
# rpath是程序在运行的时候载入动态库(即QtCore.framework)的位置
-Wl,-rpath,/path/to/5.13.1/clang_64/lib
-pipe -stdlib=libc++ -O2 -std=gnu++11 -Wall -W -fPIC

编译成功后调用./version就可以看到运行成功并输出 Qt 版本了。

1.2 First GUI

下面我们来测试一个简单的 GUI 例子。首先新建一个文件如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// simple.cpp
#include <QApplication>
#include <QWidget>

int main(int argc, char *argv[]) {

QApplication app(argc, argv);

QWidget window;

window.resize(250, 150);
window.setWindowTitle("Simple example");
window.show();

return app.exec();
}

然后使用qmake工具,运行

1
qmake -project

这个命令会创建一个项目文件simple.pro文件的名字与根目录文件夹的名字相同。文件的内容应该类似于:

1
2
3
4
5
6
7
8
9
10
11
12
######################################################################
# Automatically generated by qmake (3.0) Fri Oct 30 17:11:00 2015
######################################################################

TEMPLATE = app
TARGET = simple
INCLUDEPATH += .

# Input
SOURCES += simple.cpp

QT += widgets

注意最后一行,需要我们手动添加 QtWidget 模块。然后运行qmake命令,生成编译使用的 Makefile。然后就可以编译了:

1
make

程序运行后会弹出一个空的窗口。

2 Strings

Qt 引入了QString来加强字符串处理的能力。

QtString表示一个 Unicode 字符串,其存储字符串为 16 比特的QChars。每个QChar代表一个 Unicode 4.0 字符。不同于很多其他的变成语言,QtString可以被修改如 Python 的字符串就是静态的,“修改”字符串实际是生成新的字符串

这里主要是了解QtString的能力,汇总记录就可以了,没有必要编译运行。

2.1 初始化

有多种初始化方法,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <QTextStream>

int main(void) {
QTextStream out(stdout);

QString str1 = "The night train";
out << str1 << endl;

QString str2("A yellow rose");
out << str2 << endl;

std::string s1 = "A blue sky";
QString str3 = s1.c_str();
out << str3 << endl;

std::string s2 = "A thick fog";
QString str4 = QString::fromLatin1(s2.data(), s2.size());
out << str4 << endl;

char s3[] = "A deep forest";
QString str5(s3);
out << str5 << endl;

return 0;
}

2.2 访问字符串元素

如前文所述,QStringQChar组成,我们可以使用[]操作符或者at()函数来访问元素:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <QTextStream>

int main(void) {

QTextStream out(stdout);

QString a = "Eagle";

out << a[0] << endl;
out << a[4] << endl;

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

if (a.at(5).isNull()) {
out << "Outside the range of the string" << endl;
}

return 0;
}

2.3 字符串长度

与字符串长度相关的函数有三个: size(), count()length。三个函数的作用是一样的。

2.4 字符串构建

参考如下的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <QTextStream>

int main() {
QTextStream out (stdout);

QString s1 = "There are %1 white roses";
int n = 12;

out << s1.args(n) << endl;

QString s2 = "The tree is %1 m high";
double h = 5.65;

out << s2.args(h) << endl;

QString s3 = "We have %1 lemons and %2 oranges";
int ln = 12;
int on = 4;

out << s3.arg(ln).arg(on) << endl;

return 0;
}

待被替换的占位符用%开头,后面借数字。需要注意的是多个占位符需要替换时,需要多次调用args()函数,而非为args()函数传递多个参数。

2.5 子字符串(Substring)

这里涉及left()mid()right()三个函数,分别代表从左侧开始的 Substring,中间一段的 Substring,右侧开始的 Substring。见下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <QTextStream>

int main(void) {

QTextStream out(stdout);

QString str = "The night train";

out << str.right(5) << endl;
out << str.left(9) << endl;
out << str.mid(4, 5) << endl;

QString str2("The big apple");
QStringRef sub(&str2, 0, 7);

out << sub.toString() << endl;

return 0;
}

2.6 遍历字符串

QString遵循了 C++的通常做法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <QTextStream>

int main(void) {

QTextStream out(stdout);

QString str = "There are many stars.";

foreach (QChar qc, str) {
out << qc << " ";
}

out << endl;

for (QChar *it=str.begin(); it!=str.end(); ++it) {
out << *it << " " ;
}

out << endl;

for (int i = 0; i < str.size(); ++i) {
out << str.at(i) << " ";
}

out << endl;

return 0;
}

2.7 字符串比较

QString::compare()函数用来比较两个字符串。该函数返回一个整型数。如果返回值小于 0,那么第一个字符串小于第二个字符串;如果返回 0,两个字符串相等;如果返回值大于 0,那么第一个字符串大于第二个字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <QTextStream>

#define STR_EQUAL 0

int main(void) {

QTextStream out(stdout);

QString a = "Rain";
QString b = "rain";
QString c = "rain\n";

if (QString::compare(a, b) == STR_EQUAL) {
out << "a, b are equal" << endl;
} else {
out << "a, b are not equal" << endl;
}

out << "In case insensitive comparison:" << endl;

if (QString::compare(a, b, Qt::CaseInsensitive) == STR_EQUAL) {
out << "a, b are equal" << endl;
} else {
out << "a, b are not equal" << endl;
}

if (QString::compare(b, c) == STR_EQUAL) {
out << "b, c are equal" << endl;
} else {
out << "b, c are not equal" << endl;
}

c.chop(1);

out << "After removing the new line character" << endl;

if (QString::compare(b, c) == STR_EQUAL) {
out << "b, c are equal" << endl;
} else {
out << "b, c are not equal" << endl;
}

return 0;
}

其中Qt::CaseInsensitive表示无视大小写的比较。

2.8 字符串解析

这里指将字符串解析为数字类型。这里涉及toInt(), toFloat(), toLong()等函数。反过来,setNum()函数可以将数字类型转化成字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <QTextStream>

int main(void) {

QTextStream out(stdout);

QString s1 = "12";
QString s2 = "15";
QString s3, s4;

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

int n1 = 30;
int n2 = 40;

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

return 0;
}

2.9 字符类型

QChar类型可以肥尾数字,祖母,空格以及标点符号,QChar提供了识别这些类型的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <QTextStream>

int main(void) {
QTextStream out(stdout);
int digits = 0;
int letters = 0;
int spaces = 0;
int puncts = 0;

QString str = "7 white, 3 red roses.";

foreach(QChar s, str) {
if (s.isDigit()) {
digits++;
} else if (s.isLetter()) {
letters++;
} else if (s.isSpace()) {
spaces++;
} else if (s.isPunct()) {
puncts++;
}
}

out << QString("There are %1 characters").arg(str.count()) << endl;
out << QString("There are %1 letters").arg(letters) << endl;
out << QString("There are %1 digits").arg(digits) << endl;
out << QString("There are %1 spaces").arg(spaces) << endl;
out << QString("There are %1 punctuation characters").arg(puncts) << endl;

return 0;
}

2.10 修改字符串

修改字符串的函数可以分为两类,一类返回一个修改后的副本,原字符串保持不变(如toLower());另一类是直接原地修改原字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <QTextStream>

int main(void) {

QTextStream out(stdout);

QString str = "Lovely";
str.append(" season");

out << str << endl;

str.remove(10, 3);
out << str << endl;

str.replace(7, 3, "girl");
out << str << endl;

str.clear();

if (str.isEmpty()) {
out << "The string is empty" << endl;
}

return 0;
}

2.11 字符串对齐

这里指输出格式调整,将输出的内容左对齐(leftJustified())或者右对齐(rightJustified())。如下面的例子:

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

int main(void) {

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
          Name: Robert
Occupation: programmer
Residence: New York
Marital status: single

2.12 Escaping

Qt5 提供了 toHtmlEscapted() 方法,将字符串中涉及的 html 下特殊的字符,如<, >, &, "等,替换成 HTML 下的编码。如

处理文件

1
2
3
4
5
6
7
8
#include <stdio.h>

int main(void) {

for (int i=1; i<=10; i++) {
printf("Bottle %d\n", i);
}
}

使用如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// html_escape.cpp
#include <QTextStream>
#include <QFile>

int main(void) {

QTextStream out(stdout);

QFile file("cprog.c");

if (!file.open(QIODevice::ReadOnly)) {
qWarning("Cannot open file for reading");
return 1;
}

QTextStream in(&file);

QString allText = in.readAll();
out << allText.toHtmlEscaped() << endl;

file.close();
return 0;
}

输出内容如下:

1
2
3
4
5
6
7
8
9
$ ./html_escape
#include &lt;stdio.h&gt;

int main(void) {

for (int i=1; i&lt;=10; i++) {
printf(&quot;Bottle %d\n&quot;, i);
}
}

3 Date and Time

这个部分还是继续讲 Qt 扩展的数据类型。本次涉及QDateQTimeQDateTime 三个类。

QDate 用于管理格里高利历的日历。QTime处理时钟,QDateTime则是二者的结合。

3.1 初始化 日期&时间对象

日期和时间对象的初始化有两种基本方法:要么用构造函数直接复制,要么构造一个空的对象后续赋值。参见下面的粒子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <QTextStream>
#include <QDate>
#include <QTime>

int main(void) {

QTextStream out(stdout);

// 年 - 月 - 日
QDate dt1(2015, 4, 12);
out << "The date is " << dt1.toString() << endl;

QDate dt2;
dt2.setDate(2015, 3, 3);
out << "The date is " << dt2.toString() << endl;

// 时 - 分 - 秒 - 毫秒
QTime tm1(17, 30, 12, 55);
out << "The time is " << tm1.toString("hh:mm:ss.zzz") << endl;

QTime tm2;
tm2.setHMS(13, 52, 45, 155);
out << "The time is " << tm2.toString("hh:mm:ss.zzz") << endl;
}

3.2 当前时间

见下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <QTextStream>
#include <QTime>
#include <QDate>

int main(void) {

QTextStream out(stdout);

QDate cd = QDate::currentDate();
QTime ct = QTime::currentTime();

out << "Current date is: " << cd.toString() << endl;
out << "Current time is: " << ct.toString() << endl;
}

注意,源文件不能叫time.cpp

1
QDate cd = QDate::currentDate();

返回的当前的日期。

1
QTime ct = QTime::currentTime();

返回当前的时间。toString()函数则将的日期和时间对象转化成字符串。

3.3 比较日期

关系操作符和用来比较日期(在日历上的位置)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <QTextStream>
#include <QDate>

int main(void) {

QTextStream out(stdout);

QDate dt1(2015, 4, 5);
QDate dt2(2014, 4, 5);

if (dt1 < dt2) {
out << dt1.toString() << " comes before "
<< dt2.toString() << endl;
} else {
out << dt1.toString() << " comes after "
<< dt2.toString() << endl;
}
}

3.4 闰年

使用QDate::isLeapYear()函数来判断

3.5 日期/时间 格式

3.5.1 预定义日期格式

Qt 有一些内建的日期格式。QDatetoString()函数可以接收一个日期格式描述对象作为参数。默认的参数是Qt::TextDate。一些其他的格式见下面的粒子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// dateformats.cpp
#include <QTextStream>
#include <QDate>

int main(void) {

QTextStream out(stdout);

QDate cd = QDate::currentDate();

out << "Today is " << cd.toString(Qt::TextDate) << endl;
out << "Today is " << cd.toString(Qt::ISODate) << endl;
out << "Today is " << cd.toString(Qt::SystemLocaleShortDate) << endl;
out << "Today is " << cd.toString(Qt::SystemLocaleLongDate) << endl;
out << "Today is " << cd.toString(Qt::DefaultLocaleShortDate) << endl;
out << "Today is " << cd.toString(Qt::DefaultLocaleLongDate) << endl;
out << "Today is " << cd.toString(Qt::SystemLocaleDate) << endl;
out << "Today is " << cd.toString(Qt::LocaleDate) << endl;
}
1
2
3
4
5
6
7
8
Today is Sat Oct 31 2015
Today is 2015-10-31
Today is 10/31/15
Today is Saturday, October 31, 2015
Today is 10/31/15
Today is Saturday, October 31, 2015
Today is 10/31/15
Today is 10/31/15

3.5.2 自定义日期格式

如下表:

3.5.3 预定义时间格式

和日期的类似,时钟对象的toString()也接收格式描述对象参数。默认的是Qt::TextDate。其他格式如下例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <QTextStream>
#include <QTime>

int main(void) {

QTextStream out(stdout);

QTime ct = QTime::currentTime();

out << "The time is " << ct.toString(Qt::TextDate) << endl;
out << "The time is " << ct.toString(Qt::ISODate) << endl;
out << "The time is " << ct.toString(Qt::SystemLocaleShortDate) << endl;
out << "The time is " << ct.toString(Qt::SystemLocaleLongDate) << endl;
out << "The time is " << ct.toString(Qt::DefaultLocaleShortDate) << endl;
out << "The time is " << ct.toString(Qt::DefaultLocaleLongDate) << endl;
out << "The time is " << ct.toString(Qt::SystemLocaleDate) << endl;
out << "The time is " << ct.toString(Qt::LocaleDate) << endl;
}

输出为

1
2
3
4
5
6
7
8
The time is 15:58:26
The time is 15:58:26
The time is 3:58 PM
The time is 3:58:26 PM CET
The time is 3:58 PM
The time is 3:58:26 PM CET
The time is 3:58 PM
The time is 3:58 PM

3.5.4 自定义时间格式

3.6 其他工具函数

  • daysOfWeek(): 周几,1 表示周一,7 表示周日;
  • daysInMonth(): 在月中的第几天;
  • daysInYear():: 在年中的第几天;
  • isValid(): 验证日期是否有效;
  • daysTo()daysFrom:计算日期的间距;

3.7 QDateTime

QDateTime类是日期和时间的组合,其接口也非常类似。