Chapter 2 Data, Variables, and Calculations, Section 3

Applies to: Visual Studio 2010

Published: April, 2010

Provided by: Ivor Horton

Book Cover

Buy This Book from Publisher

This topic contains the following sections.

I have mentioned namespaces several times, so it’s time you got a better idea of what they are about. They are not used in the libraries supporting MFC, but the libraries that support the CLR and Windows forms use namespaces extensively, and of course, the C++ standard library does, too.

You know already that all the names used in the ISO/IEC C++ standard library are defined in a namespace with the name std. This means that all the names used in the standard library have an additional qualifying name, std; for example, cout is really std::cout. You have already seen how you can add a using declaration to import a name from the std namespace into your source file. For example:

using std::cout;

This allows you to use the name cout in your source file and have it interpreted as std::cout.

Namespaces provide a way to separate the names used in one part of a program from those used in another. This is invaluable with large projects involving several teams of programmers working on different parts of the program. Each team can have its own namespace name, and worries about two teams accidentally using the same name for different functions disappear.

Look at this line of code:

using namespace std;

This statement is a using directive and is different from a using declaration. The effect of this is to import all the names from the std namespace into the source file so you can refer to anything that is defined in this namespace without qualifying the name in your program. Thus, you can write the name cout instead of std::cout and endl instead of std::endl. This sounds like a big advantage, but the downside of this blanket using directive is that it effectively negates the primary reason for using a namespace — that is, preventing accidental name clashes. There are two ways to access names from a namespace without negating its intended effect. One way is to qualify each name explicitly with the namespace name; unfortunately, this tends to make the code very verbose and reduce its readability. The other possibility that I mentioned early on in this chapter is to introduce just the names that you use in your code with using declarations as you have seen in earlier examples, like this, for example:

using std::cout;             // Allows cout usage without qualification
using std::endl;             // Allows endl usage without qualification

Each using declaration introduces a single name from the specified namespace and allows it to be used unqualified within the program code that follows. This provides a much better way of importing names from a namespace, as you only import the names that you actually use in your program. Because Microsoft has set the precedent of importing all names from the System namespace with C++/CLI code, I will continue with that in the C++/CLI examples. In general, I recommend that you use using declarations in your own code rather than using directives when you are writing programs of any significant size.

Of course, you can define your own namespace that has a name that you choose. The following section shows how that’s done.

Declaring a Namespace

You use the keyword namespace to declare a namespace — like this:

namespace myStuff
{
  // Code that I want to have in the namespace myStuff...
}

This defines a namespace with the name myStuff. All name declarations in the code between the braces will be defined within the myStuff namespace, so to access any such name from a point outside this namespace, the name must be qualified by the namespace name, myStuff, or have a using declaration that identifies that the name is from the myStuff namespace.

You can’t declare a namespace inside a function. It’s intended to be used the other way around; you use a namespace to contain functions, global variables, and other named entities such as classes in your program. You must not put the definition of main() in a namespace, though. The function main() is where execution starts, and it must always be at global namespace scope; otherwise, the compiler won’t recognize it.

You could put the variable value in the previous example in a namespace:

// Ex2_09.cpp
// Declaring a namespace
#include <iostream>
         
namespace myStuff
{
  int value = 0;
}
        
int main()
{
  std::cout << "enter an integer: ";
  std::cin  >> myStuff::value;
  std::cout << "\nYou entered " << myStuff::value
          << std::endl;
  return 0;
}

The myStuff namespace defines a scope, and everything within the namespace scope is qualified with the namespace name. To refer to a name declared within a namespace from outside, you must qualify it with the namespace name. Inside the namespace scope, any of the names declared within it can be referred to without qualification — they are all part of the same family. Now, you must qualify the name value with myStuff, the name of our namespace. If not, the program will not compile. The function main() now refers to names in two different namespaces, and in general, you can have as many namespaces in your program as you need. You could remove the need to qualify value by adding a using directive:

// Ex2_10.cpp
// Using a using directive
#include <iostream>
        
namespace myStuff
{
  int value = 0;
}
        
using namespace myStuff;            // Make all the names in myStuff available
        
int main()
{
  std::cout << "enter an integer: ";
  std::cin  >> value;
  std::cout << "\nYou entered " << value
          << std::endl;
  return 0;
}

You could also have a using directive for std as well, so you wouldn’t need to qualify standard library names either, but as I said, this defeats the whole purpose of namespaces. Generally, if you use namespaces in your program, you should not add using directives all over your program; otherwise, you might as well not bother with namespaces in the first place. Having said that, I will add a using directive for std in some of our examples to keep the code less cluttered and easier for you to read. When you are starting out with a new programming language, you can do without clutter, no matter how useful it is in practice.

Multiple Namespaces

A real-world program is likely to involve multiple namespaces. You can have multiple declarations of a namespace with a given name, and the contents of all namespace blocks with a given name are within the same namespace. For example, you might have a program file with two namespaces:

namespace sortStuff
{
   // Everything in here is within sortStuff namespace
}
        
namespace calculateStuff
{
  // Everything in here is within calculateStuff namespace
  // To refer to names from sortStuff they must be qualified
}
        
namespace sortStuff
{
  // This is a continuation of the namespace sortStuff
  // so from here you can refer to names in the first sortStuff namespace
  // without qualifying the names
}

A second declaration of a namespace with a given name is just a continuation of the first, so you can reference names in the first namespace block from the second without having to qualify them. They are all in the same namespace. Of course, you would not usually organize a source file in this way deliberately, but it can arise quite naturally with header files that you include into a program. For example, you might have something like this:

#include <iostream>       // Contents are in namespace std
#include "myheader.h"     // Contents are in namespace myStuff
#include <string>         // Contents are in namespace std
        
// and so on...

Here, iostream and string are ISO/IEC C++ standard library headers, and myheader.h represents a header file that contains our program code. You have a situation with the namespaces that is an exact parallel of the previous illustration.

This has given you a basic idea of how namespaces work. There is a lot more to namespaces than I have discussed here, but if you grasp this bit, you should be able to find out more about it without difficulty, if the need arises.

NoteNote

The two forms of #include directive in the previous code fragment cause the compiler to search for the file in different ways. When you specify the file to be included between angled brackets, you are indicating to the compiler that it should search for the file along the path specified by the /I compiler option, and failing that along the path specified by the INCLUDE environment variable. These paths locate the C++ library files, which is why this form is reserved for library headers. The INCLUDE environment variable points to the folder holding the library header and the /I option allows an additional directory containing library headers to be specified. When the file name is between double quotes, the compiler will search the folder that contains the file in which the #include directive appears. If the file is not found it will search in any directories that #include the current file. If that fails to find the file it will search the library directories.

C++/CLI provides a number of extensions and additional capabilities to what I have discussed in this chapter up to now. I’ll first summarize these additional capabilities before going into details. The additional C++/CLI capabilities are:

  • All the ISO/IEC fundamental data types can be used as I have described in a C++/CLI program, but they have some extra properties in certain contexts that I’ll come to.

  • C++/CLI provides its own mechanism for keyboard input and output to the command line in a console program.

  • C++/CLI introduces the safe_cast operator that ensures that a cast operation results in verifiable code being generated.

  • C++/CLI provides an alternative enumeration capability that is class-based and offers more flexibility than the ISO/IEC C++ enum declaration you have seen.

You’ll learn more about CLR reference class types beginning in Chapter 4, but because I have introduced global variables for native C++, I’ll mention now that variables of CLR reference class types cannot be global variables.

Let’s begin by looking at fundamental data types in C++/CLI.

C++/CLI Specific: Fundamental Data Types

You can and should use the ISO/IEC C++ fundamental data type names in your C++/CLI programs, and with arithmetic operations, they work exactly as you have seen in native C++. Although all the operations with fundamental types you have seen work in the same way in C++/CLI, the fundamental type names in a C++/CLI program have a different meaning and introduce additional capabilities in certain situations. A fundamental type in a C++/CLI program is a value class type and can behave either as an ordinary value or as an object if the circumstances require it.

Within the C++/CLI language, each ISO/IEC fundamental type name maps to a value class type that is defined in the System namespace. Thus, in a C++/CLI program, the ISO/IEC fundamental type names are shorthand for the associated value class type. This enables the value of a fundamental type to be treated simply as a value or be automatically converted to an object of its associated value class type when necessary. The fundamental types, the memory they occupy, and the corresponding value class types are shown in the following table:

Fundamental Type

Size (bytes)

CLI Value Class

bool

1

System::Boolean

char

1

System::SByte

signed char

1

System::SByte

unsigned char

1

System::Byte

short

2

System::Int16

unsigned short

2

System::UInt16

int

4

System::Int32

unsigned int

4

System::UInt32

long

4

System::Int32

unsigned long

4

System::UInt32

long long

8

System::Int64

unsigned long long

8

System::UInt64

float

4

System::Single

double

8

System::Double

long double

8

System::Double

wchar_t

2

System::Char

By default, type char is equivalent to signed char, so the associated value class type is System::SByte. Note that you can change the default for char to unsigned char by setting the compiler option /J, in which case, the associated value class type will be System::Byte. System is the root namespace name in which the C++/CLI value class types are defined. There are many other types defined within the System namespace, such as the type String for representing strings that you’ll meet in Chapter 4. C++/CLI also defines the System::Decimal value class type within the System namespace, and variables of type Decimal store exact decimal values with 28 decimal digits precision.

As I said, the value class type associated with each fundamental type name adds important additional capabilities for such variables in C++/CLI. When necessary, the compiler will arrange for automatic conversions from the original value to an object of a value class type, and vice versa; these processes are referred to as boxing and unboxing, respectively. This allows a variable of any of these types to behave as a simple value or as an object, depending on the circumstances. You’ll learn more about how and when this happens in Chapter 9.

Because the ISO/IEC C++ fundamental type names are aliases for the value class type names in a C++/CLI program, in principle, you can use either in your C++/CLI code. For example, you already know you can write statements creating integer and floating-point variables like this:

int count = 10;
double value = 2.5;

You could use the value class names that correspond with the fundamental type names and have the program compile without any problem, like this:

System::Int32 count = 10;
System::Double value = 2.5;

Note that this is not exactly the same as using the fundamental type names such as int and double in your code, rather than the value class names System::Int32 and System::Double. The reason is that the mapping between fundamental type names and value class types I have described applies to the Visual C++ 2010 compiler; other compilers are not obliged to implement the same mapping. Type long in Visual C++ 2010 maps to type Int32, but it is quite possible that it could map to type Int64 on some other implementation. On the other hand, the representations of the value class type that are equivalents to the fundamental native C++ types are fixed; for example, type System::Int32 will always be a 32-bit signed integer on any C++/CLI implementation.

Having data of the fundamental types being represented by objects of a value class type is an important feature of C++/CLI. In ISO/IEC C++, fundamental types and class types are quite different, whereas in C++/CLI, all data is stored as objects of a class type, either as a value class type or as a reference class type. You’ll learn how you define reference class types in Chapter 7.

Next, you’ll try a CLR console program.

Try it Out: A Fruity CLR Console Program

Create a new project and select the project type as CLR and the template as CLR Console Application. You can then enter the project name as Ex2_11, as shown in Figure 2-14.

Figure 2-14

Referenced Screen

When you click on the OK button, the Application Wizard will generate the project containing the following code:

// Ex2_11.cpp : main project file.
        
#include "stdafx.h"
        
using namespace System;
        
int main(array<System::String ^> ^args)
{
    Console::WriteLine(L"Hello World");
    return 0;
}

I’m sure you noticed the extra stuff between the parentheses following main. This is concerned with passing values to the function main() when you initiate execution of the program from the command line, and you’ll learn more about this when you explore functions in detail. If you compile and execute the default project, it will write “Hello World” to the command line. Now, you’ll convert this program to a CLR version of Ex2_02 so you can see how similar it is. To do this, you can modify the code in Ex2_11.cpp as follows:

// Ex2_11.cpp : main project file.
#include "stdafx.h"
        
using namespace System;
        
int main(array<System::String ^> ^args)
{
   int apples, oranges;                   // Declare two integer variables
   int fruit;                             // ...then another one
   apples = 5; oranges = 6;                 // Set initial values
   fruit = apples + oranges;                // Get the total fruit

   Console::WriteLine(L"\nOranges are not the only fruit...");
   Console::Write(L"- and we have ");
   Console::Write(fruit);
   Console::Write(L" fruits in all.\n");
   return 0;
}

The new lines are shown shaded and those in the lower block replace the two lines in the automatically generated version of main(). You can now compile and execute the project. The program should produce the following output:

Oranges are not the only fruit...
- and we have 11 fruits in all.

Remember, if the command window showing the output disappears before you can see it, you can add the following statement immediately before the return statement in the preceding code:

Console::ReadLine();

The program will pause when this statement is reached, waiting for you to press the Enter key.

TipTip

The only significant difference is in how the output is produced. The definitions for the variables and the computation are the same. Although you are using the same type names as in the ISO/IEC C++ version of the example, the effect is not the same. The variables apples, oranges, and fruit will be of the C++/CLI type, System::Int32, that is specified by type int, and they have some additional capabilities compared with the ISO/IEC type. The variables here can act as objects in some circumstances or as simple values as they do here. If you want to confirm that Int32 is the same as int in this case, you could replace the int type name with Int32 and recompile the example. It should work in exactly the same way.

Evidently, the following line of code produces the first line of output:

   Console::WriteLine(L"\nOranges are not the only fruit...");

The WriteLine() function is a C++/CLI function that is defined in the Console class in the System namespace. You’ll learn about classes in detail in Chapter 7, but for now, the Console class represents the standard input and output streams that correspond to the keyboard and the command line in a command- line window. Thus, the WriteLine() function writes whatever is between the parentheses following the function name to the command line and then writes a newline character to move the cursor to the next line ready for the next output operation. Thus, the preceding statement writes the text “\nOranges are not the only fruit...” between the double quotes to the command line. The L that precedes the string indicates that it is a wide-character string where each character occupies two bytes.

The Write() function in the Console class is essentially the same as the WriteLine() function, the only difference being that it does not automatically write a newline character following the output that you specify. You can therefore use the Write() function when you want to write two or more items of data to the same line in individual output statements.

Values that you place between the parentheses that follow the name of a function are called arguments. Depending on how a function was written, it will accept zero, one, or more arguments when it is called. When you need to supply more than one argument, they must be separated by commas. There’s more to the output functions in the Console class, so I want to explore Write() and WriteLine() in a little more depth.

C++/CLI Output to the Command Line

You saw in the previous example how you can use the Console::Write() and Console::WriteLine() methods to write a string or other items of data to the command line. You can put a variable of any of the types you have seen between the parentheses following the function name, and the value will be written to the command line. For example, you could write the following statements to output information about a number of packages:

int packageCount = 25;                 // Number of packages
Console::Write(L"There are ");         // Write string - no newline
Console::Write(packageCount);          // Write value - no newline
Console::WriteLine(L" packages.");     // Write string followed by newline

Executing these statements will produce the output:

There are 25 packages.

The output is all on the same line because the first two output statements use the Write() function, which does not output a newline character after writing the data. The last statement uses the WriteLine() function, which does write a newline after the output, so any subsequent output will be on the next line.

It looks a bit of a laborious process having to use three statements to write one line of output, and it will be no surprise to you that there is a better way. That capability is bound up with formatting the output to the command line in a .NET Framework program, so you’ll explore that a little next.

C++/CLI Specific — Formatting the Output

Both the Console::Write() and Console::WriteLine() functions have a facility for you to control the format of the output, and the mechanism works in exactly the same way with both. The easiest way to understand it is through some examples. First, look at how you can get the output that was produced by the three output statements in the previous section with a single statement:

int packageCount = 25;
Console::WriteLine(L"There are {0} packages.", packageCount);

The second statement here will output the same output as you saw in the previous section. The first argument to the Console::WriteLine() function here is the string L“There are {0} packages.”, and the bit that determines that the value of the second argument should be placed in the string is “{0}.” The braces enclose a format string that applies to the second argument to the function, although in this instance, the format string is about as simple as it could get, being just a zero. The arguments that follow the first argument to the Console::WriteLine() function are numbered in sequence starting with zero, like this:

referenced by:                       0     1     2   etc.
Console::WriteLine("Format string", arg2, arg3, arg4,... );

Thus, the zero between the braces in the previous code fragment indicates that the value of the packageCount argument should replace the {0} in the string that is to be written to the command line.

If you want to output the weight as well as the number of packages, you could write this:

int packageCount = 25;
double packageWeight = 7.5;
Console::WriteLine(L"There are {0} packages weighing {1} pounds.",
                                                      packageCount, packageWeight);

The output statement now has three arguments, and the second and third arguments are referenced by 0 and 1, respectively, between the braces. So, this will produce the output:

There are 25 packages weighing 7.5 pounds.

You could also write the statement with the last two arguments in reverse sequence, like this:

Console::WriteLine(L"There are {1} packages weighing {0} pounds.",
                                                      packageWeight, packageCount);

The packageWeight variable is now referenced by 0 and packageCount by 1 in the format string, and the output will be the same as previously.

You also have the possibility to specify how the data is to be presented on the command line. Suppose that you wanted the floating-point value packageWeight to be output with two places of decimals. You could do that with the following statement:

Console::WriteLine(L"There are {0} packages weighing {1:F2} pounds.",
                                                      packageCount, packageWeight);

In the substring {1:F2}, the colon separates the index value, 1, that identifies the argument to be selected from the format specification that follows, F2. The F in the format specification indicates that the output should be in the form “±ddd.dd. . .” (where d represents a digit) and the 2 indicates that you want to have two decimal places after the point. The output produced by the statement will be:

There are 25 packages weighing 7.50 pounds.

In general, you can write the format specification in the form {n,w : Axx} where the n is an index value selecting the argument following the format string, w is an optional field width specification, the A is a single letter specifying how the value should be formatted, and the xx is an optional one or two digits specifying the precision for the value. The field-width specification is a signed integer. The value will be right-justified in the field if w is positive and left-justified when it is negative. If the value occupies less than the number of positions specified by w, the output is padded with spaces; if the value requires more positions than that specified by w, the width specification is ignored. Here’s another example:

Console::WriteLine(L"Packages:{0,3} Weight: {1,5:F2} pounds.",
                                                      packageCount, packageWeight);

The package count is output with a field width of 3 and the weight with a field width of 5, so the output will be:

Packages: 25 Weight:  7.50 pounds.

There are other format specifiers that enable you to present various types of data in different ways. Here are some of the most useful format specifications:

Format Specifier

Description

C or c

Outputs the value as a currency amount.

D or d

Outputs an integer as a decimal value. If you specify the precision to be more than the number of digits, the number will be padded with zeroes to the left.

E or e

Outputs a floating-point value in scientific notation, that is, with an exponent. The precision value will indicate the number of digits to be output following the decimal point.

F or f

Outputs a floating-point value as a fixed-point number of the form ±dddd.dd. . . .

G or g

Outputs the value in the most compact form, depending on the type of the value and whether you have specified the precision. If you don’t specify the precision, a default precision value will be used.

N or n

Outputs the value as a fixed-point decimal value using comma separators between each group of three digits when necessary.

X or x

Outputs an integer as a hexadecimal value. Upper or lowercase hexadecimal digits will be output depending on whether you specify X or x.

That gives you enough of a toehold in output to continue with more C++/CLI examples. Now, you’ll take a quick look at some of this in action.

Try it Out: Formatted Output

Here’s an example that calculates the price of a carpet in order to demonstrate output in a CLR console program:

// Ex2_12.cpp : main project file.
// Calculating the price of a carpet
#include "stdafx.h"
        
using namespace System;
        
int main(array<System::String ^> ^args)
{
  double carpetPriceSqYd = 27.95;
  double roomWidth = 13.5;             // In feet
  double roomLength = 24.75;           // In feet
  const int feetPerYard = 3;
  double roomWidthYds = roomWidth/feetPerYard;
  double roomLengthYds = roomLength/feetPerYard;
  double carpetPrice = roomWidthYds*roomLengthYds*carpetPriceSqYd;
        
  Console::WriteLine(L"Room size is {0:F2} yards by {1:F2} yards",
                                roomLengthYds, roomWidthYds);
  Console::WriteLine(L"Room area is {0:F2} square yards",
                                roomLengthYds*roomWidthYds);
  Console::WriteLine(L"Carpet price is ${0:F2}", carpetPrice);
  return 0;
}

The output should be:

Room size is 8.25 yards by 4.50 yards
Room area is 37.13 square yards
Carpet price is $1037.64
TipTip

The dimensions of the room are specified in feet whereas the carpet is priced per square yard, so you have defined a constant, feetPerYard, to use in the conversion from feet to yards. In the expression to convert each dimension, you are dividing a value of type double by a value of type int. The compiler will insert code to convert the value of type int to type double before carrying out the multiplication. After converting the room dimensions to yards, you calculate the price of the carpet by multiplying the dimensions in yards to obtain the area in square yards and multiplying that by the price per square yard.

The output statements use the F2 format specification to limit the output values to two decimal places. Without this, there would be more decimal places in the output that would be inappropriate, especially for the price. You could try removing the format specification to see the difference.

Note that the statement to output the area has an arithmetic expression as the second argument to the WriteLine() function. The compiler will arrange to first evaluate the expression, and then, the result will be passed as the actual argument to the function. In general, you can always use an expression as an argument to a function, as long as the result of evaluating the expression is of a type that is consistent with the function parameter type.

C++/CLI Input from the Keyboard

The keyboard input capabilities that you have with a .NET Framework console program are somewhat limited. You can read a complete line of input as a string using the Console::ReadLine() function, or you can read a single character using the Console::Read() function. You can also read which key was pressed using the Console::ReadKey() function.

You would use the Console::ReadLine() function like this:

String^ line = Console::ReadLine();

This reads a complete line of input text that is terminated when you press the Enter key. The variable line is of type String^ and stores a reference to the string that results from executing the Console::ReadLine() function; the little hat character, ^, following the type name, String, indicates that this is a handle that references an object of type String. You’ll learn more about type String and handles for String objects in Chapter 4.

A statement that reads a single character from the keyboard looks like this:

char ch = Console::Read();

With the Read() function, you could read input data character by character, and then, analyze the characters read and convert the input to a corresponding numeric value.

The Console::ReadKey() function returns the key that was pressed as an object of type ConsoleKeyInfo, which is a value class type defined in the System namespace. Here’s a statement to read a key press:

ConsoleKeyInfo keyPress = Console::ReadKey(true);

The argument true to the ReadKey() function results in the key press not being displayed on the command line. An argument value of false (or omitting the argument) will cause the character corresponding to the key pressed being displayed. The result of executing the function will be stored in keyPress. To identify the character corresponding to the key (or keys) pressed, you use the expression keyPress.KeyChar. Thus, you could output a message relating to a key press with the following statement:

  Console::WriteLine(L"The key press corresponds to the character: {0}",
                                                              keyPress.KeyChar);

The key that was pressed is identified by the expression keyPress.Key. This expression refers to a value of a C++/CLI enumeration (which you’ll learn about very soon) that identifies the key that was pressed. There’s more to the ConsoleKeyInfo objects than I have described. You’ll meet them again later in the book.

While not having formatted input in a C++/CLI console program is a slight inconvenience while you are learning, in practice, this is a minor limitation. Virtually all the real-world programs you are likely to write will receive input through components of a window, so you won’t typically have the need to read data from the command line. However, if you do, the value classes that are the equivalents of the fundamental types can help.

Reading numerical values from the command line will involve using some facilities that I have not yet discussed. You’ll learn about these later in the book, so I’ll gloss over some of the details at this point.

If you read a string containing an integer value using the Console::ReadLine() function, the Parse() function in the Int32 class will convert it to a 32-bit integer for you. Here’s how you might read an integer using that:

Console::Write(L"Enter an integer: ");
int value = Int32::Parse(Console::ReadLine());
Console::WriteLine(L"You entered {0}", value);

The first statement just prompts for the input that is required, and the second statement reads the input. The string that the Console::ReadLine() function returns is passed as the argument to the Parse() function that belongs to the Int32 class. This will convert the string to a 32-bit integer and store it in value. The last statement outputs the value to show that all is well. Of course, if you enter something that is not an integer, disaster will surely follow.

The other value classes that correspond to native C++ fundamental types also define a Parse() function, so, for example, when you want to read a floating-point value from the keyboard, you can pass the string that Console::ReadLine() returns to the Double::Parse() function. The result will be a value of type double.

Using safe_cast

The safe_cast operation is for explicit casts in the CLR environment. In most instances, you can use static_cast to cast from one type to another in a C++/CLI program without problems, but because there are exceptions that will result in an error message, it is better to use safe_cast. You use safe_cast in exactly the same way as static_cast. For example:

  double value1 = 10.5;
  double value2 = 15.5;
  int whole_number = safe_cast<int>(value1) + safe_cast<int>(value2);

The last statement casts each of the values of type double to type int before adding them together and storing the result in whole_number.

C++/CLI Enumerations

Enumerations in a C++/CLI program are significantly different from those in an ISO/IEC C++ program. For a start, you define an enumeration in C++/CLI like this:

enum class Suit{Clubs, Diamonds, Hearts, Spades};

This defines an enumeration type, Suit, and variables of type Suit can be assigned only one of the values defined by the enumeration — Hearts, Clubs, Diamonds, or Spades. When you refer to the constants in a C++/CLI enumeration, you must always qualify the constant you are using with the enumeration type name. For example:

Suit suit = Suit::Clubs;

This statement assigns the value Clubs from the Suit enumeration to the variable with the name suit. The :: operator that separates the type name, Suit, from the name of the enumeration constant, Clubs, is the scope resolution operator that you have seen before, and it indicates that Clubs exists within the scope of the Suit enumeration.

Note the use of the word class in the definition of the enumeration, following the enum keyword. This does not appear in the definition of an ISO/IEC C++ enumeration as you saw earlier, and it identifies the enumeration as C++/CLI. In fact, the two words combined, enum class, are a keyword in C++/CLI that is different from the two keywords, enum and class. The use of the enum class keyword gives a clue to another difference from an ISO/IEC C++ enumeration; the constants here that are defined within the enumeration — Hearts, Clubs, and so on — are objects, not simply values of a fundamental type as in the ISO/IEC C++ version. In fact, by default, they are objects of type Int32, so they each encapsulate a 32-bit integer value; however, you must cast a constant to the fundamental type int before attempting to use it as such.

NoteNote

You can use enum struct instead of enum class when you define an enumeration. These are equivalent so it comes down to personal choice as to which you use. I will use enum class throughout.

Because a C++/CLI enumeration is a class type, you cannot define it locally, within a function, for example, so if you want to define such an enumeration for use in main(), for example, you would define it at global scope.

This is easy to see with an example.

Try it Out: Defining a C++/CLI Enumeration

Here’s a very simple example using an enumeration:

// Ex2_13.cpp : main project file.
// Defining and using a C++/CLI enumeration.
#include "stdafx.h"
        
using namespace System;
        
// Define the enumeration at global scope
enum class Suit{Clubs, Diamonds, Hearts, Spades};
        
int main(array<System::String ^> ^args)
{
    Suit suit = Suit::Clubs;
    int value = safe_cast<int>(suit);
    Console::WriteLine(L"Suit is {0} and the value is {1} ", suit, value);
    suit = Suit::Diamonds;
    value = safe_cast<int>(suit);
    Console::WriteLine(L"Suit is {0} and the value is {1} ", suit, value);
    suit = Suit::Hearts;
    value = safe_cast<int>(suit);
    Console::WriteLine(L"Suit is {0} and the value is {1} ", suit, value);
    suit = Suit::Spades;
    value = safe_cast<int>(suit);
    Console::WriteLine(L"Suit is {0} and the value is {1} ", suit, value);
    return 0;
}

This example will produce the following output:

Suit is Clubs and the value is 0
Suit is Diamonds and the value is 1
Suit is Hearts and the value is 2
Suit is Spades and the value is 3
TipTip

Because it is a class type, the Suit enumeration cannot be defined within the function main(), so its definition appears before the definition of main() and is therefore defined at global scope. The example defines a variable, suit, of type Suit and allocates the value Suit::Clubs to it initially with the statement:

Suit suit = Suit::Clubs;

The qualification of the constant name Clubs with the type name Suit is essential; without it, Clubs would not be recognized by the compiler.

If you look at the output, the value of suit is displayed as the name of the corresponding constant — “Clubs” in the first instance. This is quite different from what happens with native enums. To obtain the constant value that corresponds to the object in a C++/CLI enum, you must explicitly cast the value to the underlying type, type int in this instance:

value = safe_cast<int>(suit);

You can see from the output that the enumeration constants have been assigned values starting from 0. In fact, you can change the type that is used for the enumeration constants. The next section looks at how that’s done.

Specifying a Type for Enumeration Constants

The constants in a C++/CLI enumeration can be any of the following types:

short       int         long         long long     signed char    char
unsigned    unsigned    unsigned     unsigned      unsigned       bool
short       int         long         long long     char

To specify the type for the constants in an enumeration, you write the type after the enumeration type name, but separated from it by a colon, just as with the native C++ enum. For example, to specify the enumeration constant type as char, you could write:

enum class Face : char {Ace, Two, Three, Four, Five, Six, Seven,
                        Eight, Nine, Ten, Jack, Queen, King};

The constants in this enumeration will be of type System::Sbyte and the underlying fundamental type will be type char. The first constant will correspond to code value 0 by default, and the subsequent values will be assigned in sequence. To get at the underlying value, you must explicitly cast the value to the type.

Specifying Values for Enumeration Constants

You don’t have to accept the default for the underlying values. You can explicitly assign values to any or all of the constants defined by an enumeration. For example:

enum class Face : char {Ace = 1, Two, Three, Four, Five, Six, Seven,
                        Eight, Nine, Ten, Jack, Queen, King};

This will result in Ace having the value 1, Two having the value 2, and so on, with King having the value 13. If you wanted the values to reflect the relative face card values with Ace high, you could write the enumeration as:

enum class Face : char {Ace = 14, Two = 2, Three, Four, Five, Six, Seven,
                        Eight, Nine, Ten, Jack, Queen, King};

In this case, Two will have the value 2, and successive constants will have values in sequence, so King will still be 13. Ace will be 14, the value you have explicitly assigned.

The values you assign to enumeration constants do not have to be unique. This provides the possibility of using the values of the constants to convey some additional property. For example:

enum class WeekDays : bool { Mon = true, Tues = true, Wed = true,
                            Thurs = true, Fri = true, Sat = false, Sun = false };

This defines the enumeration WeekDays where the enumeration constants are of type bool. The underlying values have been assigned to identify which represent workdays as opposed to rest days. In the particular case of enumerators of type bool, you must supply all enumerators with explicit values.

Operations on Enumeration Constants

You can increment or decrement variables of an enum type using ++ or --, providing the enumeration constants are of an integral type other than bool. For example, consider this fragment using the Face type from the previous section:

Face card = Face::Ten;
++card;
Console::WriteLine(L"Card is {0}", card);

Here, you initialize the card variable to Face::Ten and then increment it. The output from the last statement will be:

Card is Jack

Incrementing or decrementing an enum variable does not involve any validation of the result, so it is up to you to ensure that the result corresponds to one of the enumerators so that it makes sense.

You can also use the + or operators with enum values:

card = card – Face::Two;

This is not a very likely statement in practice, but the effect is to reduce the value of card by 2 because that is the value of Face::Two. Note that you cannot write:

card = card – 2;                       // Wrong! Will not compile.

This will not compile because the operands for the subtraction operator are of different types and there is no automatic conversion here. To make this work, you must use a cast:

card = card - safe_cast<Face>(2);      //OK!

Casting the integer to type Face allows card to be decremented by 2.

You can also use the bitwise operators ^, |, &, and ~ with enum values but, these are typically used with enums that represent flags, which I’ll discuss in the next section. As with the arithmetic operations, the enum type must have enumeration constants of an integral type other than bool.

Finally, you can compare enum values using the relational operators:

==

!=

<

<=

>

>=

I’ll be discussing the relational operators in the next chapter. For now, these operators compare two operands and result in a value of type bool. This allows you to use expressions such as card == Face::Eight, which will result in the value true if card is equal to Face::Eight.

Using Enumerators as Flags

It is possible to use an enumeration in quite a different way from what you have seen up to now. You can define an enumeration such that the enumeration constants represent flags or status bits for something. Most hardware storage devices use status bits to indicate the status of the device before or after an I/O operation, for example, and you can also use status bits or flags in your programs to record events of one kind or another.

Defining an enumeration to represent flags involves using an attribute. Attributes are additional information that you add to program statements to instruct the compiler to modify the code in some way or to insert code. This is rather an advanced topic for this book so I won’t discuss attributes in general, but I’ll make an exception in this case. Here’s an example of an enum defining flags:

[Flags] enum class FlagBits{ Ready = 1, ReadMode = 2, WriteMode = 4,
                                                        EOF = 8, Disabled = 16};

The [Flags] part of this statement is the attribute and it tells the compiler that the enumeration constants are single bit values; note the choice of explicit values for the constants. It also tells the compiler to treat a variable of type FlagBits as a collection of flag bits rather than a single value, for example:

FlagBits status = FlagBits::Ready | FlagBits::ReadMode | FlagBits::EOF;

The status variable will have the value,

 0000 0000 0000 0000 0000 0000 0000 1011

with bits set to 1 corresponding to the enumeration constants that have been OR-ed together. This corresponds to the decimal value 11. If you now output the value of status with the following statement:

Console::WriteLine(L"Current status: {0}", status);

the output will be:

Current status: Ready, ReadMode, EOF

The conversion of the value of status to a string is not considering status as an integer value, but as a collection of bits, and the output is the names of the flags that have been set in the variable separated by commas.

To reset one of the bits in a FlagBits variable, you use the bitwise operators. Here’s how you could switch off the Ready bit in status:

status = status & ~FlagBits::Ready;

The expression ~FlagBits::Ready results in a value with all bits set to 1 except the bit corresponding to FlagBits::Ready. When you AND this with status, only the FlagBits::Ready bit in status will be set to 0; all other bits in status will be left at their original setting.

Note that the op= operators are not defined for enum values so you cannot write:

status &= ~FlagBits::Ready;           // Wrong! Will not compile.

Native Enumerations in a C++/CLI Program

You can use the same syntax as native C++ enumerations in a C++/CLI program, and they will behave the same as they do in a native C++ program. The syntax for native C++ enums is extended in a C++/CLI program to allow you to specify the type for the enumeration constants explicitly. I recommend that you stick to C++/CLI enums in your CLR programs, unless you have a good reason to do otherwise.

The native typeid operator does not work with CLR reference types. However, C++/CLI has its own mechanism for discovering the type of an expression. For variables x and y, the expression (x*y).GetType() will produce an object of type System::Type that encapsulates the type of an expression. This will automatically be converted to a System::String object when you output it. For example:

  int x = 0;
  double y = 2.0;
  Console::WriteLine(L"Type of x*y is {0}", (x*y).GetType());

Executing this fragment will result in the following output:

Type of x*y is System.Double

Of course, you could use the native typeid operator with the variables x and y and get a type_info object, but because C++/CLI represents a type as a System::Type object, I recommend that you stick to using GetType().

C++/CLI also has its own version of typeid that you can only apply to a single variable or a type name. You can write x::typeid to get the System::Type object encapsulating the type of x. You can also write String::typeid to get the System::Type object for System::String.

This chapter covered the basics of computation in C++. You have learned about all the elementary types of data provided for in the language, and all the operators that manipulate these types directly.

Although I have discussed all the fundamental types, don’t be misled into thinking that’s all there is. There are more complex types based on the basic set, as you’ll see, and eventually, you will be creating original types of your own.

You can adopt the following coding strategies when writing a C++/CLI program:

  • You should use the fundamental type names for variables, but keep in mind that they are really synonyms for the value class type names in a C++/CLI program. The significance of this will be more apparent when you learn more about classes.

  • You should use safe_cast and not static_cast in your C++/CLI code. The difference will be much more important in the context of casting class objects, but if you get into the habit of using safe_cast, you generally can be sure you will avoid problems.

  • You should use enum class to declare enumeration types in C++/CLI.

  • To get the System::Type object for the type of an expression or variable, use GetType().

Exercises

  1. Write an ISO/IEC C++ program that asks the user to enter a number and then prints it out, using an integer as a local variable.

  2. Write a program that reads an integer value from the keyboard into a variable of type int, and uses one of the bitwise operators (i.e. not the % operator!) to determine the positive remainder when divided by 8. For example, 29 = (3x8)+5 and –14 = (–2x8)+2 have positive remainder 5 and 2, respectively, when divided by 8.

  3. Fully parenthesize the following expressions, in order to show the precedence and associativity:

    1 + 2 + 3 + 4
            
    16 * 4 / 2 * 3
            
    a > b? a: c > d? e: f
            
    a & b && c & d
    
  4. Create a program that will calculate the aspect ratio of your computer screen, given the width and height in pixels, using the following statements:

       int width = 1280;
    
       int height = 1024;
    
       double aspect = width / height;
    

    When you output the result, what answer will you get? Is it satisfactory — and if not, how could you modify the code, without adding any more variables?

  5. (Advanced) Without running it, can you work out what value the following code is going to output, and why?

       unsigned s = 555;
            
       int i = (s >> 4) & ~(~0 << 3);
       cout << i;
    
  6. Write a C++/CLI console program that uses an enumeration to identify months in the year, with the values associated with the months running from 1 to 12. The program should output each enumeration constant and its underlying value.

  7. Write a C++/CLI program that will calculate the areas of three rooms to the nearest number of whole square feet that have the following dimensions in feet:

    Room1: 10.5 by 17.6 Room2: 12.7 by 18.9 Room3: 16.3 by 15.4

    The program should also calculate and output the average area of the three rooms and the total area; in each case, the result should be to the nearest whole number of square feet.

Topic

Concept

The main()

A program in C++ consists of at least one function called main().

The function body

The executable part of a function is made up of statements contained between braces.

Statements

A statement in C++ is terminated by a semicolon.

Names

Named objects in C++, such as variables or functions, can have names that consist of a sequence of letters and digits, the first of which is a letter, and where an underscore is considered to be a letter. Uppercase and lowercase letters are distinguished.

Reserved words

All the objects, such as variables, that you name in your program must not have a name that coincides with any of the reserved words in C++.

Fundamental types

All constants and variables in C++ are of a given type. The fundamental types in ISO/IEC C++ are char, signed char, unsigned char, wchar_t, short, unsigned short, int, unsigned int, long, unsigned long, long long, unsigned long long, bool, float, double, and long double.

Declarations

The name and type of a variable is defined in a declaration statement ending with a semicolon. Variables may also be given initial values in a declaration.

The const modifier

You can protect the value of a variable of a basic type by using the modifier const. This will prevent direct modification of the variable within the program and give you compiler errors everywhere that a constant’s value is altered.

Automatic variables

By default, a variable is automatic, which means that it exists only from the point at which it is declared to the end of the scope in which it is defined, indicated by the corresponding closing brace after its declaration.

static variables

A variable may be declared as static, in which case, it continues to exist for the life of the program. It can be accessed only within the scope in which it was defined.

Global variables

Variables can be declared outside of all blocks within a program, in which case, they have global namespace scope. Variables with global namespace scope are accessible throughout a program, except where a local variable exists with the same name as the global variable. Even then, they can still be reached by using the scope resolution operator.

Namespaces

A namespace defines a scope where each of the names declared within it is qualified by the namespace name. Referring to names from outside a namespace requires the names to be qualified.

The native C++ standard library

The ISO/IEC C++ Standard Library contains functions and operators that you can use in your program. They are contained in the namespace std. The root namespace for C++/CLI libraries has the name System. You can access individual objects in a namespace by using the namespace name to qualify the object name by using the scope resolution operator, or you can supply a using declaration for a name from the namespace.

Lvalues

An lvalue is an object that can appear on the left-hand side of an assignment. Non-const variables are examples of lvalues.

Ivor Horton’s Beginning Visual C++® 2010, Copyright © 2010 by Ivor Horton, ISBN: 978-0-470-50088-0, Published by Wiley Publishing, Inc., All Rights Reserved. Wrox, the Wrox logo, Wrox Programmer to Programmer, and related trade dress are trademarks or registered trademarks of John Wiley & Sons, Inc and/or its affiliates.

Show: