As well as being something simple like an int
or a double
a variable can also be an object. Typically an object holds structured data and
exhibits certain behaviour. The data access can be restricted; the data inside the object is said to be encapsulated.
The definition of what data an object will contain and what functionality
it will have is specified in a class. An object variable is said to be
an instance of a class. A string
is an example of an object.
the string
library contains the definition of the string
class.
Consider a possible Student
class, which defines objects that contain the following three
data items:
int
string
int
Assuming that the class Student
has been defined (we see later in the course how to do that), we can create an instance of the Student
class using either of the following
definitions:
Student st(1234, "Mary Smith", 2420); Student st = Student(1234, "Mary Smith", 2420);
We can also use the default constructor (if there is one defined for the class)
as follows to create an instance of Student
that contains default
data:
Student st;
Note that we do not write Student st();
.
You can use assignment with objects. If st1
and st2
were both objects of type Student
, we could do the following:
st1 = st2;
The following statement changes the contents of the already-existing object:
st = Student(5678, "Bill Brown", 1320);
The data stored in an object is not generally immediately accessible to the outside world. Access
is defined by the object's member functions which expose the data through
the object's interface. For example the Student
class could
have an accessor member function get_number()
that defines
access to the student's ID number as follows:
st.get_number();
// returns the ID number of the Student
object
st
reg_no = st.get_number();
// assigns the ID number to the variable
reg_no.
To access the properties and member functions of an object, use the dot operator
as shown above. Objects can also have mutator member functions that allow
the properties to be modified. For example, the Student
class might have a
set_name()
function which you could use to change the name
property of
a student:
st.set_name("Betty Jones");
changes the name of st from Bill Brown to Betty Jones.
What you can do with an object depends on how the class has been defined.
For example, whether you can say cout << st;
or if (st1 == st2)
(where st1 and st2 are objects of type Student
), depends on whether Student
has been defined in such a way as to allow you to do this. If it has, you can; if it hasn't, you can't.
You include the standard C++ libraries by using a #include<library>
statement. However, if you want to include non-standard libraries (Horstmann's
for example), you use a statement of the form #include "filename"
. If the libraries contain names defined in the std
namespace, you may need to put the #include
after the using namespace std;
statement.
Conditions take the form:
if (condition) {statements executed if the condition is true }or
if (condition) {statements executed if the condition is true } else {statements executed if the condition is not true } Execution resumes here
Note that the first line does not end with a semicolon. The curly brackets are necessary only if there are several statements. If you are only executing one statement as a result of the condition, you can place it on the same line or the next. For example:
double first, second; // first and second serves, in tennis cin >> first >> second; if (first /second < 1.2) cout << "Practise more serves"; // Statement on the next line, but indented else cout << "Serve is OK"; // Statement on the same line if (first / second < 1.2) { cout << "How many double faults?"; double dfault; cin >> dfault; if (dfault / second > 0.25) cout << "Could do better"; } // curly brackets to enclose the compound statement after the if
Relational operators allow you to compare data items.
== | is equal to |
---|---|
!= | is not equal to |
> | is greater than |
< |
is less than |
>= | is greater than or equal to |
<= | is less than or equal to |
The < and > operators can be used with strings to determine alphabetical
order. For example, if s
has the value "abc" and t
has the value "def"
(s < t)
is true. When dealing with non-alphanumeric characters, you need to know which character set you are using before you can determine the collating order of a pair of strings. The most common character set is the ASCII (American Standard Code for Information Interchange). The EBCDIC (Extended Binary Coded Decimal Interchange Code) is used in IBM mainframes. The first 32 characters of the ASCII character set (numbered 0 to 31) are non-printable control codes. Features of ASCII worth remembering are that the space character (number 32) comes before all the printable characters, the digits come before the letters, and the upper-case alphabet comes before the lower-case alphabet.
Boolean operators are used to compare true
(1 or non-zero) and
false
(0) values
pre-'98 | 1998 standard |
---|---|
! |
not |
&& |
and |
|| |
or |
The order of precedence is not
, then and
, then or
. For example A or not B and C
means A or ((not B) and C)
. The
and
operator evaluates to true
only if both operands are true
;
or
evaluates to true
if either operand is true
;
not
evaluates to the complement of the operand (if A
is true, not A
is false
and vice versa).
Be very careful to write "==" rather than "=". If you write if (x = 0)
, this is an assignment, not an equality test, and the effect will be to assign 0 to x
. How does it evaluate an assignment as true
or false
? If the value assigned is zero, it evaluates it as false
; if it is anything else, it evaluates it as true
.
When expressing "If x or y is greater than zero", you must write:
if (x > 0 or y > 0)
rather than
if (x or y > 0)
which are both correct code but logically different. The second expression literally
means "if x is true
or y is greater than zero", and x
is always true
for non-zero values of x.
When expressing "If x is between 0 and 10 inclusive", you must write:
if (x >= 0 and x <= 10)
rather than
if (0 <= x <= 10)
To evaluate the second, it first decides whether zero is less than or equal to x
. Either it is (so the expression is true
) or it isn't (so the expression is false
). It then decides whether this
result (true
, or false
) is less than or equal to 10. How can true
(or false
)
be less than or equal to 10? It makes no sense. But it resolves the problem
by treating true
as 1 and false
as 0. Both of these are less than 10, so
the expression is true regardless of the value of x
.
In general, in C++, there is no guarantee of the order in which the operands
of an expression will be evaluated. For example, given the expression
(x + 2) * (y - 1)
, does it
first evaluate the (x + 2)
and then the (y - 1)
and then multiply them together, or does it first evaluate the (y - 1)
and then the (x + 2)
and then do the multiplying?
The answer is that it might do either - the language does not specify.
Note that this is a different matter from deciding where the brackets go in an unbracketed expression; we have already seen that that is decided by the precedence and associativity of the operators. Precedence and associativity decide where the implied brackets go - they decide what the expression means. The above expression is fully bracketed; operator precedence is not an issue here - there is no doubt about what the expression means. What we are talking about now is the temporal order in which the different operands of an expression are evaluated.
Most of the time, it does not matter which way round it does it, but it
might. If the evaluation of one operand affected the value of the other,
it might make a difference. Suppose an integer variable x
had
the value 3, and you had ++x * (10 - x)
. Doing the left side
first changes the value of x
from 3 to 4, so the right-hand
side evaluates to 6 and the result is 24. But if it did the right side first,
(10 - x)
comes to 7, ++x
comes to 4, so the
result is 28.
This would apply also to a cout
statement. For example, in
cout << expressionA << expressionB << endl;while you can be sure that the value of expressionA will be output before the value of expressionB, you cannot be sure that expressionA will be evaluated before expressionB. There is no guarantee that the following will output 1234:
int x = 0; cout << ++x << ++x << ++x << ++x << endl;
In general you should be careful not to write expressions that rely on a particular order of evaluation.
The boolean operators, however, are an exception to this general rule.
The operands of and
and or
are always evaluated
left to right. For example, given the expression x < 3 and y > 10
,
x < 3
will always be evaluated first.
Given a pair of conditional expressions joined by a boolean operator, C++ always evaluates
the left-hand one first. It also uses lazy evaluation, in which boolean
expressions are evaluated only until their result is clear. Since false
and
anything is always false
, then, if it is evaluating an and
expression and it evaluates the left side to false
, it can (and does) stop evaluating since it already knows the result for the whole expression. Likewise with evaluating the left side of an or
expression to true
.
This can be useful. For example:
if (second > 0 and first / second > 1.2) {... }
In this case if second
is indeed zero, then the potentially fatal
calculation of first / second
is never executed.
A boolean variable is a variable that can hold only the values true
and false
. Suppose we are processing
applications for theatre tickets and suppose that a discount applies if
the performance is on a Monday or the customer is under 15 or the ticket
is for one of the back rows. Rather than evaluate this condition
repeatedly in the program, we might evaluate it just once and store the
outcome in a boolean variable:
bool half_price = day == "Mon" or age < 15 or row >= "w";You might like to put the boolean expression in brackets, but you don't have to:
bool half_price = (day == "Mon" or age < 15 or row >= "w");Then, when we want to say "If the discount is applicable," we just say
if (half_price)
. (For "If the discount is not applicable," we just say
if (not half_price)
.) Note that there is no need to say
if (half_price == true)
. Having established whether half_price
is true, it already has the result it needs.
Asking it now to test whether true is equal to true (or whether false is
equal to true) is redundant.
Languages that don't have boolean variables have to make do with some other type of variable for this job, such as:
string half_price; if (day == "Mon" or age < 15 or row >= "w") half_price = "T"; else half_price = "F";And then later to say
if (half_price == "T")
.
But it's all too easy to write if (half_price == "t")
or if (half_price == "true")
or if (half_price == "yes")
or ...
It is possible to abbreviate some condition statements using the ? : operator as follows:
conditional_expression ? expression_1 : expression_2
in which, if conditional_expression is true
, expression_1
is returned, otherwise expression_2 is returned. Note that the expressions
must be of the same type.
max = x > y ? x : y; // Assigns to max the larger of x and y cout << (s.length() < t.length() ? s : t); // prints the shorter of s and t
The conditional operator doesn't do anything that an if
statement couldn't do, but it sometimes enables you to express something
more succinctly.
The switch
statement is C++'s multi-way branch, allowing you to specify a number of different cases, rather than simply
true
or false.
Here is an example:
int x = 12; int z; cin >> z; switch (z) { case 1: cout << x + 1; case 2: cout << x - 2; case 3: cout << x * 3; case 4: cout << x / 4; default: cout << "Invalid value"; }
The switch
statement requires an expression after the word switch
and then it jumps to the statement
whose case matches the expression. All subsequent statements are executed as
well, so that if z were 3 in this example, the output would not just be 36
, but
363Invalid value
. You can avoid this state of affairs by placing
a break
keyword at the end of each case
statement,
to go to the statement after the curly brackets, thus:
switch (z) { case 1: cout << x + 1; break; case 2: cout << x - 2; break; case 3: cout << x * 3; break; case 4: cout << x / 4; break; default: cout << "Invalid value"; }
switch
statements
are of limited use: the expression must always evaluate to an integral value, e.g.
int
or char
(not double
or string
); you
must specify constant values after the case
- you can't have expressions here -
and you can have only one value per case and not, for example, a range of values, though you can implement a (small) range rather clumsily as follows:
switch (z) { case 1: case 2: case 3: cout << "One, two or three"; break;
which will output "One, two or three" in the event that z has the value 1 or 2 or 3.
Notes on R. Mitton's lectures by S.P. Connolly, edited by R. Mitton, 2000