Book Image

Microsoft Visual C++ Windows Applications by Example

By : Stefan Bjornander, Stefan Björnander
Book Image

Microsoft Visual C++ Windows Applications by Example

By: Stefan Bjornander, Stefan Björnander

Overview of this book

Table of Contents (15 chapters)
Microsoft Visual C++ Windows Applications by Example
Credits
About the Author
About the Reviewer
Preface
Index

Types and Variables


There are several types in C++. They can be divided into two groups: simple and compounded. The simple types can be further classified into integral, floating, and logical types. The compunded types are arrays, pointers, and references. They are all (directly or indirectly) constituted by simple types. We can also define a type with our own values, called the enumeration type.

Simple Types

There are five simple types intended for storing integers: char, wchar_t, short int, int, and long int. They are called the integral types. The types short int and long int may be abbreviated to short and long, respectively. As the names imply, they are designed for storing characters, small integers, normal integers, and large integers, respectively. The exact limits of the values possible to store varies between different compilers.

Furthermore, the integral types may be signed or unsigned. An unsigned type must not have negative values. If the word signed or unsigned is left out, a short int, int, and long int will be signed. Whether a char will be signed or unsigned is not defined in the standard, but rather depends on the compiler and the underlying operational systems. We say that it is implementation-dependent.

However, a character of the type char is always one byte long, which means that it always holds a single character, regardless of whether it is unsigned or not. The type wchar_t is designed to hold a character of a more complex sort; therefore, it usually has a length of at least two bytes.

The char type is often based on the American Standard Code for Information Exchange (ASCII) table. Each character has a specific number ranging from 0 to 127 in the table. For instance, 'a' has the number 97. With the help of the ASCII table, we can convert between integers and characters. See the last section of this chapter for the complete ASCII table.

int i = (int) 'a'; // 97
char c = (char) 97; // 'a'

The next category of simple types is the floating types. They are used to store real values; that is, numbers with decimal fractions. The types are float, double, and long double, where float stores the smallest value and long double the largest one. The value size that each type can store depends on the compiler. A floating type cannot be unsigned.

The final simple type is bool. It is used to store logical values: true or false.

Variables

A variable can be viewed as a box in memory. In almost every case, we do not need to know the exact memory address the variable is stored on. A variable always has a name, a type, and a value. We define a variable by simply writing its type and name. If we want to, we can initialize the variable; that is, assign it a value. If we do not, the variable's value will be undefined (it is given the value that happens to be on its memory location).

int i = 123, j;
double d = 3.14;
char c = 'a';
bool b = true;

As a char is a small integer type, it is intended to store exactly one character. A string stores a (possibly empty) sequence of characters. There is no built-in type for describing a string; however, there is a library class string with some basic operations. Note that characters are enclosed by single quotations while strings are enclosed by double quotations. In order to use strings, we have to include the header file string and use the namespace std. Header files, classes, and namespaces are described in the next chapter.

#include <string>
using namespace std;
char c = 'a';
string s = "Hello, World!";

We can transform values between the types by stating the new type within parentheses. The process of transforming a value from one type to another is called casting or type conversions.

int i = 123;
double x = 1.23;

int j = (int) x;
double y = (double) i;

Constants

As the name implies, a constant is a variable whose value cannot be altered once it has been initialized. Unlike variables, constants must always be initialized. Constants are often written in capital letters.

const double PI = 3.14;

Input and Output

In order to write to the standard output (normally a text window) and read from standard input (normally the keyboard), we use streams. A stream can be thought of as a connection between our program and a device such as the screen or keyboard. There are predefined objects cin and cout that are used for input and output. We use the stream operators >> and << to write to and read from a device. Similarily to the strings above, we have to include the header file iostream and use the namespace std.

We can write and read values of all the types we have gone through so far, even though the logical values true and false are read and written as one and zero. The predefined object endl represents a new line.

#include <iostream>
#include <string>
using namespace std;

void main()
{
  int i;
  double x;
  bool b;
  string s;
  cin >> i >> x >> b >> s;
  cout << "You wrote i: " << i << ", x: " << x << ", b: " << b
       << ", s: " << s << endl;
}

Enumerations

An enumeration is a way to create our own integral type. We can define which values a variable of the type can store. In practice, however, enumerations are essentially an easy way to define constants.

enum Cars {FORD, VOLVO, TOYOTA, VOLKSWAGEN};

Unless we state otherwise, the constants are assigned to zero, one, two, and so on. In the example above, FORD is an integer constant with the value zero, VOLVO has the value one, TOYOTA three, and VOLKSWAGEN four.

We do not have to name the enumeration type. In the example above, Cars can be omitted. We can also assign an integer value to some (or all) of the constants. In the example below, TOYOTA is assigned the value 10. The constants without assigned values will be given the value of the preceding constant before, plus one. This implies that VOLKSWAGEN will be assigned the value 11.

enum {FORD, VOLVO, TOYOTA = 10, VOLKSWAGEN};

Arrays

An array is a variable compiled by several values of the same type. The values are stored on consecutive locations in memory. An array may be initialized or uninitiated. An uninitiated array must always be given a size. In the following example, b is given the size 2 and c is given the size 4, even though only its first two values are defined, which may cause the compiler to emit a warning.

int a[3] = {11, 12, 13};
double b[2] = {1.2, 3.4};
char c[4] = {'a', 'b'}, d[3];

A value of an array can be accessed by index notation.

int i = a[2];
double x = b[0];
char t = c[1];

Pointers and References

A pointer is a variable containing the address of value. Let us say that the integer i has the value 999 which is stored at the memory address 10,000. If p is a pointer to i, it holds the value 10,000.

A clearer way to illustrate the same thing is to draw an arrow from the pointer to the value.

In almost all cases, we do not really need to know the address of the value. The following code gives rise to the diagram above, where the ampersand (&) denotes the address of the variable.

int i = 999;
int *p = &i;

If we want to access the value pointed at, we use the asterisk (*), which derefers the pointer, "following the arrow". The address (&) and the dereferring (*) operator can be regarded as each others reverses. Note that the asterisk is used on two occasions, when we define a pointer variable and when we derefer a pointer. The asterisk is in fact used on a third occasion, when multiplying two values.

int i = 999;
int *p = &i;
int j = *p; // 999

A reference is a simplified version of a pointer; it can be regarded as a constant form of a pointer. A reference variable must be initialized to refer to a value and cannot be changed later on. A reference is also automatically dereferred when we access its value. Neither do we need to state the address of the value the reference variable is initialized to refer to. The address-of (&) and dereferring (*) operators are only applicable to pointers, not to references. Note that the ampersand has two different meanings. It used as a reference marker as well as to find the address of an expression. In fact, it is also used as the bitwise and operator. A reference is usually drawn with a dashed line in order to distinguish it from a pointer.

int i = 999;
int &r = i;
int j = r; // 999

Pointers and Dynamic Memory

Pointers (but not references) can also be used to allocate dynamic memory. There is a section of the memory called the heap that is used for dynamically allocated memory blocks. The operators new and delete are used to allocate and deallocate the memory. Memory not dynamically allocated is referred to as static memory.

int *p = new int;
*p = 123;
delete p;

We can also allocate memory for a whole array. Even though p is a pointer in the example below, we can use the array index notation to access a value of the array in the allocated memory block. When we deallocate the array, we have to add a pair of brackets for the whole memory block of the array to be deallocated. Otherwise, only the memory of the first value of the array would be deallocated.

int *p = new int[3];
p[0] = 123;
p[1] = 124;
p[2] = 125;
delete [] p;

The predefined constant NULL (defined in the header file cstdlib) holds the pointer equivalence of the zero value. We say that the pointer is set to null. In the diagram, we simply write NULL.

#include <cstdlib>
// ...
int *p = NULL;

Sometimes, the electric ground symbol is used to symbolize a null pointer. For this reason, a null pointer is said to be a grounded pointer.

There is a special type void. It is not really a type, it is rather used to indicate the absence of a type. We can define a pointer to void. We can, however, not derefer the pointer. It is only useful in low-level applications where we want to examine a specific location in memory.

void* pVoid = (void*) 10000;

The void type is also useful to mark that a function does not return a value, see the function section later in this chapter.

In the example below, the memory block has been deallocated, but p has not been set to null. It has become a dangling pointer; it is not null and does not really point at anything. In spite of that, we try to access the value p points at. That is a dangerous operation and would most likely result in a run-time error.

int *p = new int;
*p = 1;
delete p;
*p = 2

In the example below, we allocate memory for two pointers, p and q. Then we assign p to q, by doing so we have created a memory leak. There is no way we can access or deallocate the memory block that was pointed at by p. In fact, we deallocate the same memory block twice as both pointers by then point at the same memory block. This dangerous operation will most likely also result in a run-time error.

int *p = new int; // (a)
int *q = new int;
*p = 1;
*q = 2;
p = q; // (b)
delete p; // Deallocates the same memory block twice, as p
delete q; // and q point at the same memory block.

As a reference variable must be initialized to refer to a value and it cannot be changed, it is not possible to handle dynamic memory with references. Nor can a reference take the value null.

If we continue to allocate dynamic memory from the heap, it will eventually run out of memory. There are two ways to handle that problem. The simplest one is to mark the new call with nothrow (defined in namespace std). In that case, new will simply return a null pointer when it is out of memory.

const int BLOCK_SIZE = 0x7FFFFFFF;
void* pBlock = new (nothrow) char[BLOCK_SIZE];

if (pBlock != NULL)
{
  cout << "Ok.";
  // ...
  delete [] pBlock;
}
else
{
  cout << "Out of memory.";
}

The other way is to omit the nothrow marker. In that case, the new call will throw the exception bad_alloc in case of memory shortage. We can catch it with a try-catch block.

using namespace std;
const int BLOCK_SIZE = 0x7FFFFFFF;

try
{
  void* pBlock = new char[BLOCK_SIZE];
  cout << "Ok.";
  // ...
  delete [] pBlock;
}

catch (bad_alloc)
{
  cout << "Out of memory.";
}

See the next chapter for more information on exceptions and namespaces.

Defining Our Own Types

It is possible to define our own type with typedef, which is a great tool for increasing the readability of the code. However, too many defined types tend to make the code less readable. Therefore, I advise you to use typedef with care.

int i = 1;

typedef unsigned int unsigned_int;
unsigned_int u = 2;

typedef int* int_ptr;
int_ptr ip = &i;

typedef unsigned_int* uint_ptr;
uint_ptr up = &u;

The Size and Limits of Types

T he operator sizeof gives us the size of a type (the size in bytes of a value of the type) either by taking the type surrounded by parentheses or by taking a value of the type. The size of a character is always one byte and the signed and unsigned forms of each integral type always have the same size. Otherwise, the sizes are implementation-dependent. Therefore, there are predefined constants holding the minimum and maximum values of the integral and floating types. The operator returns a value of the predefined type size_t. Its exact definition is implementation-dependent. However, it is often an unsigned integer.

#include <iostream>
using namespace std;

#include <climits> // The integral type limit constants.
#include <cfloat>  // The floating type limit constants.

void main()
{
  int iIntSize1 = sizeof (int);
  int iIntSize2 = sizeof iIntSize1;
  cout << "integer size: " << iIntSize1 << " " << iIntSize2
       << endl;

  int* pSize = &iIntSize1;
  int iPtrSize = sizeof pSize;
  cout << "pointer size: " << iPtrSize << endl;

  int array[3] = {1, 2, 3};
  int iArraySize = sizeof array;
  cout << "array size: " << iArraySize << endl << endl;

  cout << "Minimum signed char: " << SCHAR_MIN << endl;
  cout << "Maximum signed char: " << SCHAR_MAX << endl;
  cout << "Minimum signed short int: " << SHRT_MIN << endl;
  cout << "Maximum signed short int: " << SHRT_MAX << endl;
  cout << "Minimum signed int: " << INT_MIN << endl;
  cout << "Maximum signed int: " << INT_MAX << endl;
  cout << "Minimum signed long int: " << LONG_MIN << endl;
  cout << "Maximum signed long int: " << LONG_MAX << endl
       << endl;

  // The minimum value of an unsigned integral type is always
  // zero.
  cout << "Maximum unsigned char: " << UCHAR_MAX << endl;
  cout << "Maximum unsigned short int: " << USHRT_MAX << endl;
  cout << "Maximum unsigned int: " << UINT_MAX << endl;
  cout << "Maximum unsigned long int: " << ULONG_MAX << endl
       << endl;

  // There are no constants for long double.
  cout << "Minimum float: " << FLT_MIN << endl;
  cout << "Maximum float: " << FLT_MAX << endl;
  cout << "Minimum double: " << DBL_MIN << endl;
  cout << "Maximum double: " << DBL_MAX << endl;
}

Hungarian Notation

In order to identify a variable's type and thereby increase the readability of the code, naming them in accordance with the Hungarian Notation is a good idea. The name of a variable has one or two initial small letters representing its type. The notation is named after Microsoft programmer Charles Simonyi, who was born in Budapest, Hungary.

Letter(s)

Type

Example

i

int

int iNum;

d

double

double dValue;

c

char

char cInput;

u

UINT (unsigned integer)

UINT uFlags;

x

int, the variable is a position in the x direction.

int xPos;

y

int, the variable is a position in the y direction.

int yPos;

cx

int, the variable is a size in the x direction.

int cxSize;

cy

int, the variable is a size in the y direction.

int cySize;

st

string

string stName;

cr

COLORREF

COLORREF crText;

lf

LOGFONT

LOGFONT lfCurrFont;

Objects of some common classes have in the same manner two initial small letters representing the class. Note that the C++ class string and the MFC class CString have the same initial letters. However, the C++ string class will not be used in the MFC applications of this book.

Letters

Class

Example

st

CString

CString stBuffer;

pt

CPoint

CPoint ptMouse;

sz

CSize

CSize szText;

rc

CRect

CRect rcClip;

A pointer to an object has the initial p.

SyntaxTree* pTree;