Bitwise
Operators..................................................................................................2
List of bitwise operators
........................................................................................2
Example -- Convert to binary with bit operators
....................................................3
Problems
...........................................................................................................4
Typedef.................................................................................................................4
Macros
..................................................................................................................5
Macro
Arguments..............................................................................................5
Typecasting
..........................................................................................................6
Types of Typecasting
........................................................................................6
Assertions
.............................................................................................................7
Assertions and
error-checking...........................................................................7
Turning assertions
off........................................................................................8
The switch and case keywords
.............................................................................8
Summary
............................................................................................................11
Tips
.....................................................................................................................10
Bitwise Operators and Macros 2
Bitwise Operators
An operator that manipulates individual bits is called a bitwise operator.
The most
familiar operators are the addition operator (+) etc and these operators
work with bytes or
groups of bytes. Occasionally, however, programmers need to manipulate the
bits within
a byte.
C++ provides operators to work with the individual bits in integers. For
this to be useful,
we must have some idea of how integers are represented in binary. For
example the
decimal number 3 is represented as 11 in binary and the decimal number 5 is
represented
as 101 in binary.
List of bitwise operators
Purpose Operator example
complement ~i
and i&j
exclusive or i^j
inclusive or i|j
shift left i<<n
shift right i>>n
• can be used on any integer type (char, short,
int, etc.)
• right shift might not do sign extension
• used for unpacking compressed data
Bitwise AND operator
0 AND 0 = 0
0 AND 1 = 0
1 AND 0 = 0
1 AND 1 = 1
Bitwise OR operator
0 OR 0 = 0
0 OR 1 = 1
1 OR 0 = 1
1 OR 1 = 1
Bitwise OR operator
Bitwise Operators and Macros 3
0 XOR 0 = 0
0 XOR 1 = 1 x XOR 0 = x
1 XOR 0 = 1 x XOR 1 = ~x
1 XOR 1 = 0
Bitwise Left-Shift is useful when to want to MULTIPLY an integer (not
floating point
numbers) by a power of 2. The operator, like many others, takes 2 operands
like this:
a << b
This expression returns the value of a multiplied by 2 to the power of b.
Bitwise Right-Shift does the opposite, and takes away bits on the right.
Suppose we had:
a >> b
This expression returns the value of a divided by 2 to the power of b.
Applications of Bitwise operators
Bitwise operators have two main applications. The first is using them to
combine several
values into a single variable. Suppose you have a series of flag variables
which will
always have only one of two values: 0 or 1 (this could also be true or
false). The smallest
unit of memory you can allocate to a variable is a byte, which is eight
bits. But why
assign each of your flags eight bits, when each one only needs one bit?
Using bitwise
operators allows you to combine data in this way.
Example -- Convert to binary with bit operators
This program reads integers and prints them in binary, using the shift and
"and" operators
to extract the relevant bits.
// Print binary representation of integers
#include <iostream>
//using namespace std;
void main() {
int n;
while (cin >> n) {
cout << "decimal: " << n << endl;
// print binary with leading zeros
cout << "binary : ";
for (int i=31; i>=0; i--) {
int bit = ((n >> i) & 1)
cout << bit;
}
cout << endl;
Bitwise Operators and Macros 4
}//end loop
}
Problems
Here are some modifications that could be made to this code.
1. It's difficult to read long sequences of digits. It's common to put a
space after
every 4 digits.
2. Suppress leading zeros. This is done most easily by defining a bool flag,
setting it
to false at the beginning of each conversion, setting it to true when a
non-zero bit
is encountered, and printing zeros only when this flag is set to true. Then
there's
the case of all zeros that requires another test.
Typedef
A typedef declaration lets you define your own identifiers that can
be used in place of
type specifiers such as int, float, and double. The
names you define using typedef are
NOT new data types. They are synonyms for the data types or combinations of
data types
they represent.
A typedef declaration does not reserve storage. When an object is
defined using a
typedef identifier, the properties of the defined object are exactly the
same as if the
object were defined by explicitly listing the data type associated with the
identifier.
The following statements declare LENGTH as a synonym for int, then
use this typedefto declare length, width, and height as integral variables.
typedef int LENGTH;
LENGTH length, width, height;
The following declarations are equivalent to the above declaration:
int length, width, height;
Similarly, you can use typedef to define a struct type. For
example:
typedef struct {
int scruples;
int drams;
int grains;
} WEIGHT;
Bitwise Operators and Macros 5
The structure WEIGHT can then be used in the following declarations:
WEIGHT chicken, cow, horse, whale;
The proposed feature is intended to be a natural application of existing
template syntax to
the existing typedef keyword. Interactions with the rest of the
language are limited
because typedef templates do not create a new type or extend the type
system in any
way; they only create synonyms for other types.
Macros
A macro is a fragment of
code which has been given a name. Whenever the name is used,
it is replaced by the contents of the macro. There are two kinds of macros.
They differ
mostly in what they look like when they are used.
Object-like macros resemble
data
objects when used, function-like
macros resemble function calls.
You may define any valid identifier as a macro, even if it is a C keyword.
The
preprocessor does not know anything about keywords. This can be useful if
you wish to
hide a keyword such as const from an older compiler that does not understand
it.
However, the preprocessor operator defined can never be defined as a macro,
and C++'s
named operators cannot be macros when you are compiling C++.
Macro Arguments
Function-like macros can take
arguments, just like true functions. To define a macro that
uses arguments, you insert parameters
between the pair of parentheses in the macro
definition that make the macro function-like. The parameters must be valid C
identifiers,
separated by commas and optionally whitespace.
To invoke a macro that takes arguments, you write the name of the macro
followed by a
list of actual arguments in
parentheses, separated by commas. The invocation of the
macro need not be restricted to a single logical line--it can cross as many
lines in the
source file as you wish. The number of arguments you give must match the
number of
parameters in the macro definition. When the macro is expanded, each use of
a parameter
in its body is replaced by the tokens of the corresponding argument. (You
need not use all
of the parameters in the macro body.)
As an example, here is a macro that computes the minimum of two numeric
values, as it
is defined in many C programs, and some uses.
#define min(X, Y) ((X) < (Y) ? (X) : (Y))
x = min(a, b); ==> x = ((a) < (b) ? (a) : (b));
y = min(1, 2); ==> y = ((1) < (2) ? (1) : (2));
z = min(a + 28, *p); ==> z = ((a + 28) < (*p) ? (a + 28) : (*p));
Bitwise Operators and Macros 6
Typecasting
Typecasting is making a variable of one type, act like another type for one
single
application. To typecast something, simply put the type of variable you want
the actual
variable to act as inside parentheses in front of the actual variable. For
example (char)a
will make 'a' function as a char.
Types of Typecasting
There are two types of typecasting:
• Implicit typecasting
• Explicit typecasting (coercion)
Implicit typecasting is done by the compiler itself while the explicit
typecasting is done
by us, the developers.
Implicit type casting (coercion) is further divided in two types
• Promotion
• Demotion
Example:
#include <iostream.h>
int main()
{
cout<<(char)65;
//The (char) is a typecast, telling the computer to
//interpret the 65 as alphabet’s first letter “A”
//character, not as a number. It is going to give the
//ASCII output of the equivalent of the number 65(It
should //be the letter A).
return 0;
}
One use for typecasting for is when you want to use the ASCII
characters. For example,
assume that we want to create our own chart of all 256 ASCII characters. To
do this, we
will need to use to typecast to allow us to print out the integer as its
character equivalent.
#include <iostream.h>
int main()
{
for(int x=0; x<256; x++)
{ //The ASCII character set is from 0 to 255
cout<<x<<". "<<(char)x<<" ";
Bitwise Operators and Macros 7
//Note the use of the int version of x to
//output a number and the use of (char) to
// typecast the x into a character
//which outputs the ASCII character that
//corresponds to the current number
}
return 0;
}
Assertions
An assertion statement specifies a condition at some particular point in
your program. An
assertion specifies that a program satisfies certain conditions at
particular points in its
execution. There are three types of assertion:
Preconditions
• Specify conditions at the start of a function.
Post conditions
• Specify conditions at the end of a function.
Invariants
• Specify conditions over a defined region of a program.
An assertion violation indicates a bug in the program. Thus, assertions are
an effective
means of improving the reliability of programs. In other words, they are a
systematic
debugging tool.
Assertions and error-checking
It is important to distinguish between program errors and run- time errors:
1. A program error is a bug, and should never occur.
2. A run-time error can validly occur at any time during program execution.
Assertions are not a mechanism for handling run-time errors. For example, an
assertion
violation caused by the user inadvertently entering a negative number when a
positive
number is expected is poor program design. Cases like this must be handled
by
appropriate error-checking and recovery code (such as requesting another
input), not by
assertions.
Realistically, of course, programs of any reasonable size do have bugs,
which appear at
run-time. Exactly what conditions are to be checked by assertions and what
by run-time
error- checking code is a design issue. Assertions are very effective in
reusable libraries,
for example, since i) the is small enough for it to be possible to
guarantee bug-free
operation, and ii) the routines cannot perform error- handling
because they do not
know in what environment they will be used. At higher levels of a program,
where
operation is more complex, run-time error-checking must be designed into the
code.
Bitwise Operators and Macros 8
Turning assertions off
By default, ANSI C compilers generate code to check assertions at run-time.
Assertionchecking
can be turned off by defining the NDEBUG flag to your compiler, either by
inserting
#define NDEBUG
in a header file such as stdhdr.h, or by calling your compiler with the
-dNDEBUG option:
cc -dNDEBUG ...
This should be done only you are confident that your program is operating
correctly, and
only if program run-time is a pressing concern.
The switch and case keywords
The switch-case statement is a multi-way decision statement. Unlike the
multiple
decisions statement that can be created using if-else, the switch statement
evaluates the
conditional expression and tests it against numerous constant values. The
branch
corresponding to the value that the expression matches is taken during
execution.
The value of the expressions in a switch-case statement must be an (integer,
char, short,
long), etc. Float and double are not allowed. The syntax is :
switch( expression )
{
case constant-expression1: statements1;
[case constant-expression2: statements2;]
[case constant-expression3: statements3;]
[default : statements4;]
}
The case statements and the default statement can occur in any
order in the switch statement. The default clause is an optional
clause that is matched if none of the
constants in the case statements can be matched.
Consider the example shown below:
switch( Grade )
{
case 'A' : printf( "Excellent" );
case 'B' : printf( "Good" );
case 'C' : printf( "OK" );
case 'D' : printf( "Mmmmm...." );
Bitwise Operators and Macros 9
case 'F' : printf( "You must do better than this" );
default : printf( "What is your grade anyway?" );
}
Here, if the Grade is 'A' then the output will be
Excellent
Good
OK
Mmmmm....
You must do better than this
What is your grade anyway?
This is because, in the 'C' switch statement, execution continues on
into the next case
clause if it is not explicitly specified that the execution should exit the
switch statement.
The correct statement would be:
switch( Grade )
{
case 'A' : printf( "Excellent" );
break;
case 'B' : printf( "Good" );
break;
case 'C' : printf( "OK" );
break;
case 'D' : printf( "Mmmmm...." );
break;
case 'F' : printf( "You must do better than this" );
break;
default : printf( "What is your grade anyway?" );
break;
}
Although the break in the default clause (or in general, after
the last clause) is not
necessary, it is good programming practice to put it in anyway.
An example where it is better to allow the execution to continue into the
next case statement:
Bitwise Operators and Macros 10
char Ch;
.
.
switch( Ch )
{
/* Handle lower-case characters */
case 'a' :
case 'b' :
.
.
.
case 'z' :
printf( "%c is a lower-case character.\n", Ch );
printf( "Its upper-case is %c.\n" toupper(Ch) );
break;
/* Handle upper-case characters */
case 'A' :
case 'B' :
.
.
.
case 'Z' :
printf( "%c is a upper-case character.\n", Ch );
printf( "Its lower-case is %c.\n" tolower(Ch) );
break;
/* Handle digits and special
characters */
default :
printf( "%c is not in the alphabet.\n", Ch );
break;
}
..
Tips
• Take extreme care while using the Bitwise
operators as these operates on
individual bits.
• Bitwise Left Shift << operator is useful when
we to want to multiply an integer by
a power of two.
Bitwise Operators and Macros 11
• Bitwise Right Shift >> operator is useful
when we to want to divide an integer by
a power of two.
• Do remember that when an object is defined
using a typedef identifier, the
properties of the defined object are exactly the same as if the object were
defined
by explicitly listing the data type associated with the identifier.
• To invoke a macro that takes arguments, you
write the name of the macro
followed by a list of actual arguments in parentheses, separated by commas.
• Any type-casting done by us is considered to
be the explicit type casting. Implicit
typecasting is always done by the compiler
• Do remember that when we use typecasting,
then the data type of one variable is
temporarily changed, while the original data type remains the same
• Assertions are an effective means of
improving the reliability of programs. They
are a systematic debugging tool.
• The value of the expressions in a switch-case
statement must be an (integer, char,
short, long), etc. Float and double are not allowed.
Summary
In this lecture, we have learned about the three major bitwise operators
AND, OR, XOR.
Bitwise operators operates on individual bits.
Using “typedefs” provide an easy way to avoid the long names during the
declarations
and thus make our code more simple. We have also discussed about the
typecasting. It is
making a variable of one type, act like another type for one single
application. The two
types of type casting includes the implicit type casting and the explicit
type casting.
In C, the assertions are implemented with standard assert macro, the
argument to assert
must be true when the macro is executed, otherwise the programmes aborts and
printouts
an error message.
The switch-case statement is a multi-way decision statement. Unlike the
multiple
decisions statement that can be created using if-else, the switch statement
evaluates the
conditional expression and tests it against numerous constant values.
|