- Game Programming Using Qt Beginner's Guide
- Witold Wysota Lorenz Haas
- 1626字
- 2025-04-04 20:19:16
Time for action – a simple quiz game
To introduce you to the main usage of QRegularExpression
, let's imagine this game: a photo, showing an object, is shown to multiple players and each of them has to estimate the object's weight. The player whose estimate is closest to the actual weight wins. The estimates will be submitted via QLineEdit
. Since you can write anything in a line edit, we have to make sure that the content is valid.
So what does valid mean? In this example, we define that a value between 1 g and 999 kg is valid. Knowing this specification, we can construct a regular expression that will verify the format. The first part of the text is a number, which can be between 1 and 999. Thus, the corresponding pattern looks like [1-9][0-9]{0,2}
, where [1-9]
allows—and demands—exactly one digit, except zero, which is optionally followed by up to two digits including zero. This is expressed through [0-9]{0,2}
. The last part of the input is the weight's unit. With a pattern such as (mg|g|kg)
, we allow the weight to be input in milligrams (mg), grams (g), or kilograms (kg). With [ ]?
, we finally allow an optional space between the number and unit. Combined together, the pattern and construction of the related QRegularExpression
object looks like this:
QRegularExpression regex("[1-9][0-9]{0,2}[ ]? (mg|g|kg)"); regex.setPatternOptions(QRegularExpression:: CaseInsensitiveOption);
What just happened?
In the first line, we constructed the aforementioned QRegularExpression
object while passing the regular expression's pattern as a parameter to the constructor. We also could have called setPattern()
to set the pattern:
QRegularExpression regex; regex.setPattern("[1-9][0-9]{0,2}[ ]?(mg|g|kg)");
Both the approaches are equivalent. If you have a closer look at the unit, you can see that right now, the unit is only allowed to be entered in lowercase. We want, however, to also allow it to be in uppercase or mixed case. To achieve this, we can of course write (mg|mG|Mg|MG|g|G|kg|kG|Kg|KG)
. Not only is this a hell of a work when you have more units, this is also very error-prone, and so we opt for a cleaner and more readable solution. On the second line of the initial code example, you see the answer: a pattern option. We used setPatternOptions()
to set the QRegularExpression::CaseInsensitiveOption
option, which does not respect the case of the characters used. Of course, there are a few more options that you can read about in Qt's documentation on QRegularExpression::PatternOption
. Instead of calling setPatternOptions()
, we could have also passed the option as a second parameter to the constructor of QRegularExpression
:
QRegularExpression regex("[1-9][0-9]{0,2}[ ]?(mg|g|kg)", QRegularExpression::CaseInsensitiveOption);
Now, let's see how to use this expression to verify the validity of a string. For the sake of simplicity and better illustration, we simply declared a string called input
:
QString input = "23kg"; QRegularExpressionMatch match = regex.match(input); bool isValid = match.hasMatch();
All we have to do is call match()
, passing the string we would like to check against it. In return, we get an object of the QRegularExpressionMatch
type that contains all the information that is further needed—and not only to check the validity. With QRegularExpressionMatch::hasMatch()
, we then can determine whether the input matches our criteria, as it returns true
if the pattern could be found. Otherwise, of course, false
is returned.
Attentive readers surely will have noticed that our pattern is not quite finished. The hasMatch()
method would also return true
if we matched it against "foo 142g bar". So, we have to define that the pattern is checked from the beginning to the end of the matched string. This is done by the \A
and \z
anchors. The former marks the start of a string and the latter the end of a string. Don't forget to escape the slashes when you use such anchors. The correct pattern would then look as follows:
QRegularExpression regex("\\A[1-9][0-9]{0,2}[ ]?(mg|g|kg)\\z", QRegularExpression::CaseInsensitiveOption);
Extracting information out of a string
After we have checked that the sent guess is well formed, we have to extract the actual weight from the string. In order to be able to easily compare the different guesses, we further need to transform all values to a common reference unit. In this case, it should be a milligram, the lowest unit. So, let's see what QRegularExpressionMatch
can offer us for this task.
With capturedTexts()
, we get a string list of the pattern's captured groups. In our example, this list would contain "23kg" and "kg". The first element is always the string that was fully matched by the pattern followed by all the sub strings captured by the used brackets. Since we are missing the actual number, we have to alter the pattern's beginning to ([1-9][0-9]{0,2})
. Now, the list's second element is the number and the third element is the unit. Thus, we can write the following:
int getWeight(const QString &input) { QRegularExpression regex("\\A([1-9][0-9]{0,2}) [ ]?(mg|g|kg)\\z"); regex.setPatternOptions(QRegularExpression:: CaseInsensitiveOption); QRegularExpressionMatch match = regex.match(input); if(match.hasMatch()) { const QString number = match.captured(1); int weight = number.toInt(); const QString unit = match.captured(2).toLower(); if (unit == "g") { weight *= 1000; } else if (unit == "kg") { weight *= 1000000 ; } return weight; } else { return -1; } }
In the function's first two lines, we set up the pattern and its option. Then, we match it against the passed argument. If QRegularExpressionMatch::hasMatch()
returns true
, the input is valid and we extract the number and unit. Instead of fetching the entire list of captured text with capturedTexts()
, we query specific elements directly by calling QRegularExpressionMatch::captured()
. The passed integer argument signifies the element's position inside the list. So, calling captured(1)
returns the matched digits as a QString
.
Tip
QRegularExpressionMatch::captured()
also takes QString
as the argument's type. This is useful if you have used named groups inside the pattern, for example, if you have written (?<number>[1-9][0-9]{0,2})
, then you can get the digits by calling match.captured("number")
. Named groups pay off if you have long patterns or when there is a high probability that further brackets will be added in future. Be aware that adding a group at a later time will shift the indices of all the following groups by 1
and you will have to adjust your code!
To be able to calculate using the extracted number, we need to convert QString
into an integer. This is done by calling QString::toInt()
. The result of this conversion is then stored in the weight
variable. Next, we fetch the unit and transform it to lowercase characters on-the-fly. This way, we can, for example, easily determine whether the user's guess is expressed in grams by checking the unit against the lowercase "g". We do not need to take care of the capital "G" or the variants "KG", "Kg", and the unusual "kG" for kilogram.
To get the standardized weight in milligrams, we multiply weight
by 1,000 or 1,000,000, depending on whether this was expressed in g or kg. Lastly, we return this standardized weight. If the string wasn't well formed, we return -1
to indicate that the given guess was invalid. It is then the caller's duty to determinate which player's guess was the best.
Note
Pay attention to whether your chosen integer type can handle the weight's value. For our example, 100,000,000 is the biggest possible value that can be held by a signed integer on a 32-bit system. If you are not sure whether your code will be compiled on a 32-bit system, use qint32
, which is guaranteed to be a 32-bit integer on every system that Qt supports, allowing decimal notations.
As an exercise, try to extend the example by allowing decimal numbers so that 23.5g is a valid guess. To achieve this, you have to alter the pattern in order to enter decimal numbers and you also have to deal with double
instead of int
for the standardized weight.
Finding all pattern occurrences
Lastly, let's have a final look at how to find, for example, all numbers inside a string, even those leading with zeros:
QString input = "123 foo 09 1a 3"; QRegularExpression regex("\\b[0-9]+\\b"); QRegularExpressionMatchIterator i = regex.globalMatch(input); while (i.hasNext()) { QRegularExpressionMatch match = i.next(); qWarning() << match.capturedTexts(); }
The input
QString instance contains an exemplary text in which we would like to find all numbers. The "foo" as well as "1a" variables should not be found by the pattern since these are not valid numbers. Therefore, we set up the pattern defining that we require at least one digit, [0-9]+
, and that this digit—or these digits—should be wrapped by word boundaries, \b
. Note that you have to escape the slash. With this pattern, we initiate the QRegularExpression
object and call globalMatch()
on it. Inside the passed argument, the pattern will be searched. This time, we do not get QRegularExpressionMatch
back but, instead, an iterator of the QRegularExpressionMatchIterator
type. Since QRegularExpressionMatchIterator
behaves like a Java iterator, with hasNext()
, we check whether there is a further match and if so we bring up the next match by calling next()
. The type of the returned match is then QRegularExpressionMatch
, which you already know.
Tip
If you need to know about the next match inside the while
loop, you can use QRegularExpressionMatchIterator::peekNext()
to receive it. The upside of this function is that it does not move the iterator.
This way, you can iterate all pattern occurrences in the string. This is helpful if you, for example, want to highlight a search string in text.
Our example would give the output: ("123"), ("09") and ("3")
.
Taking into account that this was just a brief introduction to regular expressions, we would like to encourage you to read the Detailed Description section in the documentation to QRegularExpression
, QRegularExpressionMatch
, and QRegularExpressionMatchIterator
. Regular expressions are very powerful and useful, so, in your daily programming life, you can benefit from the profound knowledge of regular expressions!