• Tidak ada hasil yang ditemukan

Expressions

Dalam dokumen ePUB eBook Customization Guide (Halaman 100-106)

Computation

3.3 Expressions

just by writing a lot of statements. Why do we mention this now? At this stage you (or at least many readers) have little idea about what code is, and it will be months before you are ready to write code upon which other people could depend for their lives or livelihood. We mention it to help you get the emphasis of your learning right. It is very tempting to dash ahead, focusing on the parts of programming that – like what is described in the rest of this chapter – are concrete and immediately useful and to ignore the “softer,”

more conceptual parts of the art of software development. However, good programmers and system designers know (often having learned it the hard way) that concerns about structure lie at the heart of good software and that ignoring structure leads to expensive messes.

Without structure, you are (metaphorically speaking) building with mud bricks. It can be done, but you’ll never get to the tenth floor (classical mud bricks lack the structural strength for that). If you have the ambition to build something reasonably permanent or something large, you pay attention to matters of code structure and organization along the way, rather than having to come back and learn them after failures.

Click here to view code image

length = 99; // assign 99 to length

Here, as the left-hand operand of the assignment, length means “the object named length,” so that the assignment expression is read “Put 99 into the object named length.” We distinguish between length used on the left-hand side of an assignment or an initialization (the lvalue of length: “the object named length”) and length used on the right-hand side of an assignment or initialization (the rvalue of length: “the value of the object named length,” or just “the value of length”). In this context, we find it useful to visualize a variable as a box labeled by its name (§2.2, §2.5):

That is, length is the name of an object of type int containing the value 99. Sometimes (as an lvalue) length refers to the box (object) and sometimes (as an rvalue) length refers to the value in that box.

We can make more complicated expressions by using operators, such as +

and *, in just the way that we are used to. When needed, we can use parentheses to group expressions:

Click here to view code image

int perimeter = (length+width)*2; // add then multiply

Without parentheses, we’d have had to say Click here to view code image

int perimeter = length*2+width*2;

which is clumsy, and we might even have made this mistake:

Click here to view code image

int perimeter = length+width*2; // add width*2 to length

This last error is logical and cannot be found by the compiler. All the

compiler sees is a variable called perimeter initialized by a valid expression. If the result of that expression is nonsense, that’s your problem. You know the

mathematical definition of a perimeter, but the compiler doesn’t.

The usual mathematical rules of operator precedence apply, so

length+width*2 means length+(width*2). Similarly a*b+c/d means (a*b)+(c/d) and not a*(b+c)/d. See cppreference (§0.4.1) for an operator precedence table.

The first rule for the use of parentheses is simply “If in doubt,

parenthesize,” but please do learn enough about expressions so that you are not in doubt about a*b+c/d. Overuse of parentheses, as in (a*b)+(c/d), decreases readability.

Why should you care about readability? Because you and possibly others will read your code. Ugly code slows down reading and comprehension. Ugly code is not just hard to read, it is also much harder to get correct. Ugly code often hides logical errors. Don’t write absurdly complicated expressions such as

Click here to view code image

a*b+c/d*(e/-f/g)/h+7 // too complicated

and always try to choose meaningful names.

3.3.1 Constant expressions

Programs typically use a lot of constants. For example, a geometry program might use pi and an inch-to-centimeter conversion program will use a

conversion factor such as 2.54. Obviously, we want to use meaningful names for those constants (as we did for pi; we didn’t say 3.14159). Similarly, we don’t want to change those constants accidentally. Consequently, C++ offers the notion of a symbolic constant, that is, a named object to which you can’t give a new value after it has been initialized. For example:

Click here to view code image

constexpr double pi = 3.14159;

pi = 7; // error: assignment to constant

double c = 2*pi*r; // OK: we just read pi; we don’t try to change it

Such constants are useful for keeping code readable. You might have

recognized 3.14159 as an approximation to pi if you saw it in some code, but would you have recognized 299792458? Also, if someone asked you to change some code to use pi with the precision of 12 digits for your computation, you

could search for 3.14 in your code, but if someone incautiously had used 22/7, you probably wouldn’t find it. It would be much better just to change the definition of pi to use the more appropriate value:

Click here to view code image

constexpr double pi = 3.14159265359;

AA

Consequently, we prefer not to use literals (except very obvious ones, such as

0 and 1) in most places in our code. Instead, we use constants with descriptive names. Non-obvious literals in code (outside definitions of symbolic

constants) are derisively referred to as magic constant. And by the way,

299792458 is one of the fundamental constants of the universe: the speed of light in vacuum measured in meters per second. If you didn’t instantly recognize that, why would you expect not to be confused and slowed down by other literals embedded in code? Avoid magic constants!

XX

A constexpr symbolic constant must be given a value that is known at compile time (a constant expression). For example:

Click here to view code image

constexpr int max = 100;

int n;

cin >> n;

constexpr int c1 = max+7; // OK: c1 is 107

constexpr int c2 = n+7; // error: we don’t know the value of n

To handle cases where the value of a constant that is initialized with a value that is not known at compile time but never changes after initialization, C++

offers a second form of constant (a const):

Click here to view code image

int n;

cin >> n;

const int c3 = n; // OK

c3 = 7; // error: c3 is a const

Such “const variables” are very common for two reasons:

C++98 did not have constexpr, so people used const.

“Variables” that are not constant expressions (their value is not known at compile time) but do not change values after initialization are in

themselves widely useful.

3.3.2 Operators

We just used the simplest operators. However, you will soon need more as you want to express more complex operations. Most operators are

conventional, so we’ll just explain them later as needed and you can look up details if and when you find a need. Here are some common operators:

Example Name Comment

lval=a assignment not to be confused with ==

++lval pre-increment increment and use the incremented value

−−lval pre-decrement decrement and use the decremented value

!a not result is bool

−a unary minus

a*b multiply

a/b divide

a%b modulo

(remainder)

only for integer types

a+b add

a−b subtract

out<<b write b to out where out is an ostream in>>b read from in into

b

where in is an istream

lval*=a compound

assignment

lval = lval*a; also for /, %, +,

f(a) function call pass a to f as an argument (§3.5)

f<T>(a) function template call

pass a to f<T> as an argument (§21.2)

[](a){S} lambda expression

create a function object taking a as an argument (§21.2.3)

We used lval (short for lvalue, that is a value that can appear on the left-hand side of an assignment) where the operator modifies an operand.

Example Name Comment

a<b less than result is bool

a<=b less than or equal result is bool

a>b greater than result is bool

a>=b greater than or equal result is bool

a==b equal not to be confused with = a!=b not equal result is bool

a&&b logical and result is bool a||b logical or result is bool

T{a} widening conversion result is the value of a converted to type T

See cppreference (§0.4.1) for a complete list of operators. For examples of the use of the logical operators && (and), || (or), and ! (not), see §4.5.1, §6.7, and §9.3.1.

XX

Note that a<b<c means (a<b)<c and that a<b evaluates to a Boolean value:

true or false. So, a<b<c will be equivalent to either true<c or false<c. In

particular, a<b<c does not mean “Is b between a and c?” as many have naively (and not unreasonably) assumed. Thus, a<b<c is basically a useless

expression. Don’t write such expressions with two comparison operations,

and be very suspicious if you find such an expression in someone else’s code – it is most likely an error.

An increment can be expressed in at least three ways:

++a a+=1 a=a+1

AA

Which notation should we use? Why? We prefer the first version, ++a,

because it more directly expresses the idea of incrementing. It says what we want to do (increment a) rather than how to do it (add 1 to a and then write the result to a). In general, a way of saying something in a program is better than another if it more directly expresses an idea. The result is more concise and easier for a reader to understand. If we wrote a=a+1, a reader could easily wonder whether we really meant to increment by 1. Maybe we just mistyped

a=b+1, a=a+2, or even a=a−1; with ++a there are far fewer opportunities for such doubts. Please note that this is a logical argument about readability and

correctness, not an argument about efficiency. Contrary to popular belief, modern compilers tend to generate exactly the same code from a=a+1 as for

++a when a is one of the built-in types. Similarly, we prefer a*=scale over

a=a*scale.

Dalam dokumen ePUB eBook Customization Guide (Halaman 100-106)