Techniques of Using Variables
Techniques of Using Variables
Constant Values
Introduction
A constant is a value that does not change while the program in running. There are various categories of constants you will use: those that are mathematically defined (such as numbers) and those that are defined by, and are part of, the C++/CLI language. To make their management easier, these constant values have been categorized and defined in particular libraries.
The algebraic numbers you use all the time are constants because they never change. Examples of constant numbers are 12, 0, 1505, or 88146. Therefore, any number you can think of is a constant.
Every letter of the alphabet is a constant and is always the same. Examples of constant letters are d, n, c.
Some characters on your keyboard represent symbols that are neither letters nor digits. These are constants too. Examples are #, &, |, !.
There are two main techniques you use to display a constant value in C++/CLI. To simply display it using Write or WriteLine, you can type its value in the parentheses. Here is an example:
using namespace System; int main() { Console::WriteLine(2850); return 0; }
This would produce:
2850 Press any key to continue
You can also define it first using an appropriate name, and then using that name to display the constant. Here is an example:
using namespace System; int main() { typedef unsigned int Positive; Positive Number = 1450; Console::WriteLine(Number); Console::WriteLine(); return 0; }
You can create your own constant that represents anything you want. The safest technique of using a constant is to give it a name. This allows you to manage it from one standpoint. For example, if you plan to use a number such as 3.14, you can simply use the constant 3.14. Imagine you want to use 3.14 in various sections of the program. If you decide to change the number from 3.14 to 3.14159 or another value, you would have to find every mention of 3.14; this can be cumbersome and considered bad programming. The alternative is to declare a variable and assign it the desired value. That new and defined value is called a constant.
To create a constant, you use the const keyword. The simplest way you can do this is to type the const keyword followed by a data type, followed by a name, followed by =, followed by the desired value, and terminated by a semi colon. Here is an example:
const int NumberOfDoors = 4;
Here is an example:
using namespace System; int main() { const int NumberOfDoors = 16; Console::Write("Number of doors = "); Console::WriteLine(NumberOfDoors); return 0; }
This would produce:
Number of doors = 16 Press any key to continue
If you already know (and have included) a constant, you can initialize your new constant with it. Here is an example:
using namespace System; int main() { const double Value = 125.558; const unsigned short int Alley = 88; const double Root = Value; return 0; }
Language Constants |
Because constants are highly used in mathematics and other scenarios, both the C++ language and the .NET Framework are equipped with various constants. Although you can create your own constants anytime, you should be aware of these constants and usually use them instead of creating new ones, unless yours are more reliable.
Some of the constants that are part of the C++ language are defined in the limits.h library. Other constants are defined in various other files such as float.h. For example, the minimum value of the short integer is SHRT_MIN; in the same way, the maximum short integer is identified by SHRT_MAX. You can use either of these constants as follows:
#include <limits.h> using namespace System; int main() { Console::Write("The maximum of a short value is "); Console::WriteLine(SHRT_MAX); return 0; }
The constant values defined in limits.h are
Name | Value | Description |
SCHAR_MAX | 127 | Maximum of a signed char |
SCHAR_MIN | –128 | Minimum of a signed char |
UCHAR_MAX | 255 (0xff) | Maximum of an unsigned char |
CHAR_BIT | 8 | Number of bits in a char |
USHRT_MAX | 65535 (0xffff) | Maximum unsigned short value |
SHRT_MAX | 32767 | Maximum (signed) short value |
SHRT_MIN | –32768 | Minimum (signed) short value |
UINT_MAX | 4294967295 (0xffffffff) | Maximum unsigned int value |
ULONG_MAX | 4294967295 (0xffffffff) | Maximum unsigned long value |
INT_MAX | 2147483647 | Maximum (signed) int value |
INT_MIN | –2147483647–1 | Minimum (signed) int value |
LONG_MAX | 2147483647 | Maximum (signed) long value |
LONG_MIN | –2147483647–1 | Minimum (signed) long value |
CHAR_MAX | 127 (255 if /J option used) | Maximum char value |
CHAR_MIN | –128 (0 if /J option used) | Minimum char value |
MB_LEN_MAX | 2 | Maximum number of bytes in multibyte char |
_I64_MAX | 9223372036854775807 | Maximum (signed) __int64 value |
_I64_MIN | -9223372036854775807-1 | Minimum (signed) __int64 value |
_UI64_MAX | 0xffffffffffffffff | Maximum (unsigned) __int64 value |
The constant values defined in float.h are:
Name | Value | Description |
DBL_DIG | 15 | # of decimal digits of precision |
DBL_EPSILON | 2.2204460492503131e-016 | Smallest such that 1.0+DBL_EPSILON !=1.0 |
DBL_MANT_DIG | 53 | # of bits in mantissa |
DBL_MAX | 1.7976931348623158e+308 | Maximum value |
DBL_MAX_10_EXP | 308 | Maximum decimal exponent |
DBL_MAX_EXP | 1024 | Maximum binary exponent |
DBL_MIN | 2.2250738585072014e-308 | Minimum positive value |
DBL_MIN_10_EXP | (-307) | Minimum decimal exponent |
DBL_MIN_EXP | (–1021) | Minimum binary exponent |
_DBL_RADIX | 2 | Exponent radix |
_DBL_ROUNDS | 1 | Addition rounding: near |
FLT_DIG | 6 | Number of decimal digits of precision |
FLT_EPSILON | 1.192092896e-07F | Smallest such that 1.0+FLT_EPSILON !=1.0 |
FLT_MANT_DIG | 24 | Number of bits in mantissa |
FLT_MAX | 3.402823466e+38F | Maximum value |
FLT_MAX_10_EXP | 38 | Maximum decimal exponent |
FLT_MAX_EXP | 128 | Maximum binary exponent |
FLT_MIN | 1.175494351e-38F | Minimum positive value |
FLT_MIN_10_EXP | (–37) | Minimum decimal exponent |
FLT_MIN_EXP | (–125) | Minimum binary exponent |
FLT_RADIX | 2 | Exponent radix |
FLT_ROUNDS | 1 | Addition rounding: near |
Using #define |
A macro is an action you want the compiler to perform for your program. The C language provides a preprocessor used to create macros. The particularity of macros, or most macros, is that the compiler counts on you to know what you are doing. Many of such macros are created using the #define preprocessor. Based on this concept of macros, you can create a constant.
The formula of creating a constant using the #define is:
#define ConstantName ConstantValue
The # symbol and the define word are required; they inform the compiler that the name following them represents a constant.
The ConstantName represents a valid name for the desired constant; the name follows the same rules we learned for defining names. To distinguish the constant from other names of variables, it is sometimes a good idea to write it in uppercase, although it is perfectly normal to have it in lowercase or any case combination you desire.
The ConstantValue can be a character, an integer, a floating-point value, or an expression. If the constant value is an integer or a floating-point value, you can type it. If the value is a character, include it between single-quotes. If the constant value is a string, include it between double-quotes. The definition of the constant does not end with a semi-colon.
Examples of declaring constants are:
#define AGE 12 // AGE represents the constant integer 12 #define ANSWER ‘y’ #define MAXSTUDENTS 35 #define And "&&" #define PI 3.14159 // PI represents 3.14159 #define Country = “New Zealand”; |
As you can see, there is no precision as to what type of constant you are creating when using #define. The truth is that #define doesn't create a constant. It is simply telling the compiler that the word after #define will be used to represent whatever follows that ConstantName. This means that, in reality, what follows ConstantName can be anything; it can even be an expression. here is an expression:
using namespace System; int main() { #define Welcome Console::WriteLine("Welcome to the wonderful world of C++/CLI!") #define Print Console::WriteLine() Welcome; Print; return 0; }
This would produce:
Welcome to the wonderful world of C++/CLI! Press any key to continue
As you can see, there is no precision as to what type of constant you are creating when using #define. In reality, #define doesn't create a constant. It is simply telling the compiler that the word after #define will be used to represent whatever follows that ConstantName. This means that what follows ConstantName can be anything; it can even be an expression. here is an expression:
using namespace System; int main() { #define Add255To1250 255 + 1250 Console::Write("Addition: "); Console::WriteLine(Add255To1250); return 0; }
In reality, the #define technique was implemented in the C language and C++, as a child of C, simply inherited it from its parent. The #define routine is still supported and you will encounter it in many documents. In fact, you are not necessarily discouraged from using it but the golden rule is to know what you are doing.
Introduction |
The computer has a significantly large memory, ranging in the millions of bytes. At one time, it may have to deal with different applications of various types. Each application has different requests and assignments. To better manage this, a good deal of the job is handed to the compiler. For example, as you declare and initialize variables, the compiler has to keep track of what variable is where? How much memory space a variable needs? Importantly, when is a variable needed? And when is a variable not needed anymore? For example, we saw that, when you declare a value variable, the compiler reserves a space for it in an area reserved and called the stack memory. When such a variable is not used anymore, for example when the application gets closed, the compiler removes that variable from memory to make that memory available to other applications:
This also means that the compiler is in charge of managing the memory used by value variables. Of course, the compiler needs to keep track of the various variables used in your program. In fact, each variables has an address. To know the address of a variable, which will not mean much to you anyway, you can precede it with the & operator when accessing. This operation should be performed using the cout class. Here is an example:
#include <iostream> using namespace std; using namespace System; int main() { int Nbr; cout << "Address of Number: " << &Nbr; Console::WriteLine(); return 0; }
This would produce:
Address of Number: 0012F3D0 Press any key to continue . . .
Pointer Declaration |
In C++, instead of declaring a variable on the stack, the compiler reserves another area of memory called the heap. When declaring a variable, you can ask the compiler to "put" the variable in that area. To do this, precede the variable with an asterisk operator. Here is an example:
#include <iostream> using namespace std; using namespace System; int main() { int *Number; cout << "Address of Number: " << &Number; Console::WriteLine(); return 0; }
Such a variable is not a variable in the strict sense as that of a value. Instead, it is used to "point" to the memory location of another variable. For this reason, it is called a pointer to a variable or simply, a pointer.
Pointers allow you to manage computer memory and resources. When you declare a variable, the compiler reserves an amount of memory for that variable and waits for you to use that section of memory. If you declare a variable but do not use it throughout the life time of your program, that section of memory would have been busy, holding garbage, until your program ends. That section of memory would not be available to any other variable and you cannot prevent the compiler from creating it, as long as the unused variable exists. Therefore, another reason you would prefer pointers over regular variables is because pointers give you more control over the use of computer memory. Pointers allow you to decide when you need to use memory for a variable and when to free that memory space even if the program is still running.
Pointers allow you to create callback functions. These are functions whose name can be used as variables. This allows a function to be assigned as a value and to be passed as an argument. Something that is not possible with regular functions.
When declaring a pointer, the asterisk (*) lets the compiler know that the variable that follows is a pointer. There are three ways you can type the asterisk. These are
DataType* PointerName; DataType * PointerName; DataType *PointerName;
By default, it does not matter how you append the *, the compiler will know that the variable that follows is a pointer, as long as the word or group of words on the left side of the asterisk represents a valid data type.
Pointer Initialization |
If you declare a value variable, you can initialize it with an appropriate value of its type. Here is an example:
#include <iostream> using namespace std; using namespace System; int main() { int Number = 224; cout << "Address of Number: " << &Number; Console::WriteLine(); return 0; }
If you declare a pointer, because it doesn't hold an explicit value like a value variable, you cannot just initialize it with any value: you would receive an error. If you want, you initialize it with 0, considered the null value. Here is an example:
#include <iostream> using namespace std; using namespace System; int main() { int Nbr = 224; int *Number = 0; Console::WriteLine(); return 0; }
Because a pointer "points" to another variable, to initialize it, you can assign it a declared and initialized variable preceded with the ampersand operator &. Here is an example:
#include <iostream> using namespace std; using namespace System; int main() { int Nbr = 224; int *Number = &Nbr; Console::WriteLine(); return 0; }
You can also initialize a pointer on a different line, after declaring it. This time, you should not use the asterisk on the pointer, but the compiler should know that the pointer will point to a variable's address. Therefore, the name of the variable will still use the & operator. This is done as follows:
using namespace System; int main() { int Nbr = 224; int *Number; Number = &Nbr; Console::WriteLine(); return 0; }
Just like a regular variable has an address, a pointer, as a variable, has an address too. To find out the address of a pointer variable, type the ampersand on its left, without the *.
Once you have declared and initialized a pointer variable, it holds the same value as the variable it points to. To display the value that a pointer holds, use its name preceded by the *. Here is an example:
using namespace System; int main() { int Nbr = 224; int *Number; Number = &Nbr; Console::Write("Number1: "); Console::WriteLine(Nbr); Console::Write("Number2: "); Console::WriteLine(*Number); return 0; }
The program would produce:
Number1: 224 Number2: 224 Press any key to continue . . .
If you want to display the value held by a pointer, make sure you know what variable the pointer is pointing to. In other words, make sure you initialize it. If you attempt to display the value of an un-initialized pointer, the compiler would throw an error and would stop. Therefore, before accessing the value of a pointer, know what value that pointer is holding. Otherwise, wait until you have initialized it before accessing its value:
You do not have to always initialize either the pointer or the variable it is pointing to. You can first declare both the variable and a pointer, then initialize them later on, when needed.
Once you have declared a variable and assign it to a pointer, during the course of your program, the value of a variable is likely to change but as long as the pointer points to the same variable, it would hold the same value as the variable it points to.
Instead of pointing to a regular variable, a pointer can be made to point to another pointer. To apply this, always remember that you must specify what target a pointer is pointing to. Consider the following example:
using namespace System; int main() { int value = 26; int *pointer; pointer = &value; Console::WriteLine(" Value = {0}", value); Console::WriteLine("*Pointer = {0}", *pointer); return 0; }
This would produce:
Value = 26 *Pointer = 26 Press any key to continue . . .
In this program, if necessary, you can declare a new variable that is a pointer that itself points to another pointer. When declaring such a variable, precede it with two *. After declaring the pointer, before using it, you must initialize it with a reference to a pointer, that is, a reference to a variable that was declared as a pointer. Here is an example:
using namespace System; int main() { int value = 26; int *pointer; int **pointerToPointer; pointer = &value; pointerToPointer = &pointer; Console::WriteLine(" Value = {0}", value); Console::WriteLine(" *Pointer = {0}", *pointer); Console::WriteLine("**Pointer = {0}", **pointerToPointer); return 0; }
This would produce:
Value = 26 *Pointer = 26 **Pointer = 26 Press any key to continue . . .
Just as demonstrated earlier, after initializing a pointer, if you change the value of the variable it points to, the pointer would be updated. Consider the following program:
using namespace System; int main() { int value = 26; int *pointer; int **pointerToPointer; pointer = &value; pointerToPointer = &pointer; Console::WriteLine(" Value = {0}", value); Console::WriteLine(" *Pointer = {0}", *pointer); Console::WriteLine("**Pointer = {0}\n", **pointerToPointer); value = 4805; Console::WriteLine("After changing the value of the main variable..."); Console::WriteLine(" Value = {0}", value); Console::WriteLine(" *Pointer = {0}", *pointer); Console::WriteLine("**Pointer = {0}\n", **pointerToPointer); return 0; }
This would produce:
Value = 26 *Pointer = 26 **Pointer = 26 After changing the value of the main variable... Value = 4805 *Pointer = 4805 **Pointer = 4805 Press any key to continue . . .
Notice that, by changing the value of the original variable, when accessing its pointer or the pointer to its pointer, they reflect the new value. In the same way, instead of (directly) changing the value of the variable, you can change the value of its pointer. You can also change the value of the pointer to its pointer: all variables would be updated. Consider the following program:
using namespace System; int main() { int value = 26; int *pointer; int **pointerToPointer; pointer = &value; pointerToPointer = &pointer; Console::WriteLine(" Value = {0}", value); Console::WriteLine(" *Pointer = {0}", *pointer); Console::WriteLine("**Pointer = {0}\n", **pointerToPointer); value = 4805; Console::WriteLine("After changing the value of the main variable..."); Console::WriteLine(" Value = {0}", value); Console::WriteLine(" *Pointer = {0}", *pointer); Console::WriteLine("**Pointer = {0}\n", **pointerToPointer); *pointer = -728; Console::WriteLine("After changing the value of the main variable..."); Console::WriteLine(" Value = {0}", value); Console::WriteLine(" *Pointer = {0}", *pointer); Console::WriteLine("**Pointer = {0}\n", **pointerToPointer); **pointerToPointer = 945580; Console::WriteLine("After changing the value of the main variable..."); Console::WriteLine(" Value = {0}", value); Console::WriteLine(" *Pointer = {0}", *pointer); Console::WriteLine("**Pointer = {0}\n", **pointerToPointer); return 0; }
This would produce:
Value = 26 *Pointer = 26 **Pointer = 26 After changing the value of the main variable... Value = 4805 *Pointer = 4805 **Pointer = 4805 After changing the value of the main variable... Value = -728 *Pointer = -728 **Pointer = -728 After changing the value of the main variable... Value = 945580 *Pointer = 945580 **Pointer = 945580 Press any key to continue . . .
Type Defining a Pointer |
The C++ language also allows you to type define a pointer to a data type. To use this feature, type the typedef keyword, followed by a known data type, followed by an asterisk and followed by a common for the pointers you want to create. Here is an example:
typedef int *Positive;
After using the typedef keyword like this, the Positive word become
synonym to a pointer to int. It can be used to declare variables that are
pointers to an integer. Here is an example:
// Programmer-defined data type typedef int *Positive; Positive Age, Category;
In this example, Age is a pointer to int; so is Category.
To hint that the name of a variable is a pointer, it is a tradition to start it with p, P, ptr, or Ptr. Many of the pointers you will encounter in the Visual Component Library and in the MSDN documentation use this convention. The pointers we have seen so far could be declared as follows:
int *pAge; unsigned int *ptrCategory; double* PSalary; long double * PtrDenominator; double *PHourlySalary, *PWeeklySalary; typedef int *PPositive; Positive pAge, pCategory;
Pointers and Memory Management |
Introduction |
Imagine you write the following program:
using namespace System; int main() { typedef double Salary; Salary Monthly; Salary Hourly, Weekly; double WeeklyHours; return 0; }
When your program launches, that is, when it loads in the computer memory, the compiler reserves an appropriate amount of space in the heap memory for each declared variable. This technique of providing memory space is called static allocation, the memory space is "allocated" to that variable. Just after the program has launched and memory has been allocated (or assigned) to each variable, each memory space is filled 0. Check it with the following program:
using namespace System; int main() { typedef double Salary; Salary Monthly; Salary Hourly, Weekly; double WeeklyHours; Console::WriteLine("Employee Payroll"); Console::Write("Weekly Hours: "); Console::WriteLine(WeeklyHours); Console::Write("Hourly Salary: "); Console::WriteLine(Hourly); Console::Write("Weekly Salary: "); Console::WriteLine(Weekly); Console::Write("Monthly Salary: "); Console::WriteLine(Monthly); return 0; }
This would produce the following warnings:
warning C4700: uninitialized local variable 'WeeklyHours' used warning C4700: uninitialized local variable 'Hourly' used warning C4700: uninitialized local variable 'Weekly' used warning C4700: uninitialized local variable 'Monthly' used
It would display the following:
Employee Payroll Weekly Hours: 0 Hourly Salary: 0 Weekly Salary: 0 Monthly Salary: 0 Press any key to continue
As you can see, the memory allocated to each variable is filled with a 0 value. If you decide to use a pointer to a variable, you can declare and initialize such a variable. This system of declaring and initializing a pointer also allows the compiler to allocate a memory space for the pointer in the heap. Remember, just like a regular variable, a pointer is also a variable and uses its own memory space. This pointer can also be assigned a value that would be shared with the pointed to variable:
using namespace System; int main() { typedef double Salary; Salary Monthly; Salary Hourly, Weekly; double WeeklyHours; double *PHourly; Hourly = 12.55; WeeklyHours = 42.50; PHourly = &Hourly; Weekly = Hourly * WeeklyHours; Console::WriteLine("Payroll Preparation"); Console::WriteLine("Enter Employee's Weekly Hours: "); Console::WriteLine("Employee Payroll"); Console::Write("Weekly Hours: "); Console::WriteLine(WeeklyHours); Console::Write("Hourly Salary: "); Console::WriteLine(*PHourly); Console::Write("Weekly Salary: "); Console::WriteLine(Weekly); Console::Write("Monthly Salary: "); Console::WriteLine(Monthly); return 0; }
This would produce:
Payroll Preparation Enter Employee's Weekly Hours: Employee Payroll Weekly Hours: 42.5 Hourly Salary: 12.55 Weekly Salary: 533.375 Monthly Salary: 0 Press any key to continue . . .
Notice that the Monthly variable is still holding 0 and it has not been used throughout the program.
The new Operator |
Instead of letting the compiler provide memory when your program comes up, you can ask it to use memory only when necessary. In that case, the compiler would allocate memory when the program is executing. This is called dynamic allocation. Dynamic allocation allows you to use memory "as needed" and not "as required".
To dynamically assign memory, the C++ language provides the new operator. Here is the syntax to use it:
PointerName = new DataType;
The PointerName must be a declared pointer. You could have declared it previously or you can declare it when performing memory allocation.
The assignment operator "=" and the new keyword are required. they let the compiler know that you are about to request memory.
The DataType parameter can be any of those we are already familiar with, but it must be the same as the variable it is pointing to. This means that, if PointerName points to an integer variable, the data type must be an integer:
using namespace System; int main() { typedef double Salary; Salary Monthly; Salary Hourly, Weekly; double WeeklyHours; double *PHourly; Hourly = 12.55; WeeklyHours = 42.50; PHourly = &Hourly; *PHourly = 14.25; Weekly = Hourly * WeeklyHours; Console::WriteLine("Payroll Preparation"); Console::WriteLine("Employee Payroll"); Console::Write("Weekly Hours: "); Console::WriteLine(WeeklyHours); Console::Write("Hourly Salary: "); Console::WriteLine(*PHourly); Console::Write("Weekly Salary: "); Console::WriteLine(Weekly); Console::Write("Monthly Salary: "); Console::WriteLine(Monthly); PHourly = new double; return 0; }
Whenever you (decide to) dynamically allocate memory to a pointer variable, if the pointer had been previously initialized, the value that was stored in that pointer is lost (obsolete). Furthermore, if the pointer was pointing to an already declared variable, the relationship is broken: the pointer would not point to that variable anymore. In other words, both the pointer and the variable it was pointing to do not hold the same value anymore. The pointed to variable keeps its value because it is independent from the pointer (it is the pointer that is pointing to the variable and not vice-versa).
Practical Learning: Introducing Pointers |
using namespace System; int main() { long *propertyNumber = new long; __wchar_t *propertyType = new __wchar_t; Byte *stories = new Byte; unsigned int *bedrooms = new unsigned int; float *bathrooms = new float; unsigned int *yearBuilt = new unsigned int; double *marketValue = new double; *propertyNumber = 204755; *propertyType = 'C'; *stories = 1; *bedrooms = 2; *bathrooms = 2.0F; *yearBuilt = 2002; *marketValue = 248550; Console::WriteLine("=//= Altair Realty =//="); Console::WriteLine("-=- Properties Inventory -=-"); Console::Write("Property #: "); Console::WriteLine(*propertyNumber); Console::Write("Property Type: "); Console::WriteLine(*propertyType); Console::Write("Stories: "); Console::WriteLine(*stories); Console::Write("Bedrooms: "); Console::WriteLine(*bedrooms); Console::Write("Bathrooms: "); Console::WriteLine(*bathrooms); Console::Write("Year Built: "); Console::WriteLine(*yearBuilt); Console::Write("Market Value: "); Console::WriteLine(*marketValue); return 0; }
=//= Altair Realty =//= -=- Properties Inventory -=- Property #: 490724 Property Type: S Stories: 2 Bedrooms: 5 Bathrooms: 3.5 Year Built: 1962 Market Value: 652540 Press any key to continue . . .
The delete Operator |
After dynamically allocating memory, you can assign a new value to the pointer for any purpose. Once the memory is not in use anymore, you should reclaim it. This is done with the delete operator. The formula to use it is:
delete PointerName;
The delete keyword is required. It lets the compiler know that you do not want to use the pointer anymore.
The PointerName is the name of the pointer that was dynamically allocated with the new operator. You cannot use the delete operator if you did not previously use the new operator to allocate memory.
To use the delete operator, simply type delete followed by the name of the pointer whose memory you want to reclaim. This operation is referred to as deallocating the memory (you are deallocating the memory that was dynamically allocated):
using namespace System; int main() { double * PHourly = new double; Console::WriteLine("Stuff..."); delete PHourly; return 0; }
Practical Learning: Deleting Pointers |
using namespace System; int main() { long *propertyNumber = new long; __wchar_t *propertyType = new __wchar_t; Byte *stories = new Byte; unsigned int *bedrooms = new unsigned int; float *bathrooms = new float; unsigned int *yearBuilt = new unsigned int; double *marketValue = new double; *propertyNumber = 204755; *propertyType = 'C'; *stories = 1; *bedrooms = 2; *bathrooms = 2.0F; *yearBuilt = 2002; *marketValue = 248550; Console::WriteLine("=//= Altair Realty =//="); Console::WriteLine("-=- Properties Inventory -=-"); Console::Write("Property #: "); Console::WriteLine(*propertyNumber); Console::Write("Property Type: "); Console::WriteLine(*propertyType); Console::Write("Stories: "); Console::WriteLine(*stories); Console::Write("Bedrooms: "); Console::WriteLine(*bedrooms); Console::Write("Bathrooms: "); Console::WriteLine(*bathrooms); Console::Write("Year Built: "); Console::WriteLine(*yearBuilt); Console::Write("Market Value: "); Console::WriteLine(*marketValue); delete propertyNumber; delete propertyType; delete stories; delete bedrooms; delete bathrooms; delete yearBuilt; delete marketValue; return 0; }
Handle Types |
Introduction |
When studying pointers, we saw that the compiler reserved an area in the heap memory for the pointers of your application. We also saw that, when you declared a pointer and allocated memory using the new operator, you should remember to de-allocate the memory using the delete operator. Although modern compilers are equipped to check this, if you are working with one that would not empty the memory for you, your program could create a memory leak. The error being human, this has been one of the regular concerns when programming with C and C++. One of the improvements of C++/CLI was to assist you with this issue of managing the memory space that your application would use.
If you (decide to) use pointers in your program, the heap memory is highly under your control. Notice that we use "if". This means that, in C++, you can create a complete program that doesn't or hardly use pointers. For a serious programmer, there is no way you can simply decide not to use pointers at all. Since you are going to use them, as we mentioned, the compiler reserves some heap memory for you. If you don't manage it right, you can create a mess. To avoid this, no more "if", the common language runtime (CLR) suggests and sometimes requires that you use another technique. This time, instead of your being in control of the heap, the compiler sets aside its own portion of heap memory and becomes in charge of it. To differentiate both portions of memory, the heap we mentioned when studying pointers is referred to as Native Heap. The heap used by the CLR is called Managed Heap:
Computer Memory | |
A Tracking Reference |
In our introduction to pointers, we saw that a reference was a technique of declaring an additional variable that references a first variable. This was viewed as creating a variable that simply referred to another, primarily declared variable. Such a variable declared as a reference is stored in the stack. The compiler is then in charge of “cleaning” it, that is, in charge of reclaiming its memory when the reference is not used anymore. C++/CLI allows you to declare a reference that, if you want, would be stored in the CLR heap so the garbage collector would be in charge of managing it. Such a variable is called a tracking reference. As done for a regular reference, you can also create a tracking reference of a primitive type. To do this, declare and initialize a variable. Here is an example: double HourlySalary = 24.68; |
To create a tracking reference, declare the variable but type the % operator between the data type and the name of the variable. Here is an exaple:
double % HourlyWage
To indicate the variable it is referencing, assign it the variable that was previously defined. Here is an example:
double % HourlyWage = HourlySalary;
After creating and initializing a tracking reference, you can access its value. You can access the variable and its reference using their names. Here is an example:
using namespace System; int main() { double HourlySalary = 24.68; double % HourlyWage = HourlySalary; Console::WriteLine("Payroll Preparation"); Console::Write("Hourly Salary: "); Console::WriteLine(HourlySalary); Console::Write("Hourly Wage: "); Console::WriteLine(HourlyWage); Console::WriteLine(); return 0; }
The tracking reference and the variable it's referencing would always hold the same value: if you change the value of one, the other's would be changed also. This is illustrated in the following program:
using namespace System; int main() { double HourlySalary = 24.68; double % HourlyWage = HourlySalary; Console::WriteLine("Payroll Preparation"); Console::Write("Hourly Salary: "); Console::WriteLine(HourlySalary); Console::Write("Hourly Wage: "); Console::WriteLine(HourlyWage); HourlySalary = 15.25; Console::WriteLine("\nNew Payroll Information"); Console::Write("Hourly Salary: "); Console::WriteLine(HourlySalary); Console::Write("Hourly Wage: "); Console::WriteLine(HourlyWage); Console::WriteLine(); return 0; }
This would produce:
Payroll Preparation Hourly Salary: 24.68 Hourly Wage: 24.68 New Payroll Information Hourly Salary: 15.25 Hourly Wage: 15.25 Press any key to continue . . .
Creating a Handle |
Besides the native heap, when your application starts, the common language runtime (CLR) reserves another portion of memory that it is in charge of managing. This portion of memory is called the managed heap. To store a variable in the managed heap, you must create it as a handle. To create a handle, as opposed to the asterisk "*" of a pointer, you use the cap "^" operator. Here is an example:
using namespace System; int main() { double ^ ItemPrice; Console::WriteLine(); return 0; }
As mentioned for the ampersand & operator of the reference and the asterisk operator of the pointer, there are three basic positions the cap ^ can have:
double^ ItemPrice; double ^ ItemPrice; double ^ItemPrice;
The compiler will assume the same. In this case, ItemPrice is a handle to double. After creating a handle, the compiler reserves an amount of space in the managed heap memory for the variable but leaves that space empty. To access the value held by a handle, you can simply use its name. Here is an example:
using namespace System; int main() { double ^ ItemPrice; Console::WriteLine("Department Store"); Console::Write("Item Price: "); Console::WriteLine(ItemPrice); Console::WriteLine(); return 0; }
This would produce:
Department Store Item Price: Press any key to continue . . .
Notice that the handle holds a null value. When studying pointers, we mentioned that you could not initialize a newly created pointer with just any value. You could initialize it with 0, which represented the null value. To initialize a handle with a null value, you can assign it the nullptr value. Here is an example:
using namespace System; int main() { double ^ ItemPrice = nullptr; Console::WriteLine("Department Store"); Console::Write("Item Price: "); Console::WriteLine(ItemPrice); Console::WriteLine(); return 0; }
This would produce:
Department Store Item Price: Press any key to continue . . .
You assign the nullptr if you don't have or don't know the value that the handle holds. In this case, the compiler would only reserve an amount of space in the managed heap for the variable but it would leave it empty. After creating a handle, instead of keeping its memory empty, you can store a value in it. To do this, simply assign it a value, either when declaring the variable or after. Here is an example:
using namespace System; int main() { double ^ ItemPrice = nullptr; Console::WriteLine("Department Store"); Console::Write("Item Price: "); Console::WriteLine(ItemPrice); ItemPrice = 148.95; Console::Write("Marked Price: "); Console::WriteLine(ItemPrice); Console::WriteLine(); return 0; }
This would produce:
Department Store Item Price: Marked Price: 148.95 Press any key to continue . . .
We saw that there were some details related to using the native heap: you could hesitate to use or not use a pointer, you could forget to delete a pointer, you could even misuse it. To solve these and some other problems, the common language runtime reserves its own portion of the garbage collected heap to make it available when you use handles. Based on this, when creating a handle, to allocate memory for it, you can instantiate it using the gcnew operator. The formula to use it is:
DataType ^ VariableName = gcnew DataType;
The DataType is the type whose handle you want to create.
The ^, =, and gcnew operators are required.
The VariableName is the name of the variable that will be created.
Here is an example:
double ^ DiscountRate = gcnew double;
After creating the handle, you can access it using its name. Here is an example:
using namespace System; int main() { double ^ ItemPrice = 148.95; double ^ DiscountRate = gcnew double; Console::WriteLine("Department Store"); Console::Write("Item Price: "); Console::WriteLine(ItemPrice); Console::Write("Discount Rate: "); Console::Write(DiscountRate); Console::WriteLine("%"); Console::WriteLine(); return 0; }
You can also access the handle by type the asterisk operator to the left of its name. Here is an example:
using namespace System; int main() { double ^ ItemPrice = 148.95; double ^ DiscountRate = gcnew double; Console::WriteLine("Department Store"); Console::Write("Item Price: "); Console::WriteLine(ItemPrice); Console::Write("Discount Rate: "); Console::Write(*DiscountRate); Console::WriteLine("%"); Console::WriteLine(); return 0; }
In both cases, the program would produce:
Department Store Item Price: 148.95 Discount Rate: 0% Press any key to continue . . .
Notice that, when using the gcnew operator, the memory allocated for the handle is initially set to 0 instead of being left empty. After declaring the variable, you can assign it a value. You can assign the value to its name:
using namespace System; int main() { double ^ ItemPrice = 148.95; double ^ DiscountRate = gcnew double; DiscountRate = 0.20; Console::WriteLine("Department Store"); Console::Write("Item Price: "); Console::WriteLine(ItemPrice); Console::Write("Discount Rate: "); Console::Write(*DiscountRate * 100); Console::WriteLine("%"); Console::WriteLine(); return 0; }
You can also assign the value using its name equipped with an asterisk to its left. Here is an example:
using namespace System; int main() { double ^ ItemPrice = 148.95; double ^ DiscountRate = gcnew double; *DiscountRate = 0.20; Console::WriteLine("Department Store"); Console::Write("Item Price: "); Console::WriteLine(ItemPrice); Console::Write("Discount Rate: "); Console::Write(*DiscountRate * 100); Console::WriteLine("%"); Console::WriteLine(); return 0; }
In both cases, the program would produce:
Department Store Item Price: 148.95 Discount Rate: 20% Press any key to continue . . .
Another option (that will become clear when we study classes and their constructors) you can use is to include the desired value in parentheses that follow the data type after the gcnew operator. Here is an example:
using namespace System; int main() { double ^ ItemPrice = 148.95; double ^ DiscountRate = gcnew double(0.20); Console::WriteLine("Department Store"); Console::Write("Item Price: "); Console::WriteLine(ItemPrice); Console::Write("Discount Rate: "); Console::Write(*DiscountRate * 100); Console::WriteLine("%"); Console::WriteLine(); return 0; }
If you want to involve a handle in an operation, remember to access it using the asterisk operator. Here are two examples:
using namespace System; int main() { double ^ ItemPrice = 148.95; double ^ DiscountRate = gcnew double(0.20); double ^ DiscountAmount = *ItemPrice * *DiscountRate; Console::WriteLine("Department Store"); Console::Write("Item Price: "); Console::WriteLine(ItemPrice); Console::Write("Discount Rate: "); Console::Write(*DiscountRate * 100); Console::WriteLine("%"); Console::Write("Discount Amount: "); Console::WriteLine(DiscountAmount); delete DiscountRate; Console::WriteLine(); return 0; }
Like a pointer whose memory is allocated with the new operator, when a handle is not used anymore, you can reclaim its memory by using the delete operator. Here is an example:
using namespace System; int main() { double ^ ItemPrice = 148.95; double ^ DiscountRate = gcnew double(0.20); Console::WriteLine("Department Store"); Console::Write("Item Price: "); Console::WriteLine(ItemPrice); Console::Write("Discount Rate: "); Console::Write(*DiscountRate * 100); Console::WriteLine("%"); delete DiscountRate; Console::WriteLine(); return 0; }
Unlike a pointer, you don't need to de-allocate a handle's memory. The garbage collector would take care of it.
Practical Learning: Using Handles |
using namespace System; int main() { long ^ propertyNumber = gcnew long; __wchar_t ^ propertyType = gcnew __wchar_t; Byte ^ stories = gcnew Byte; unsigned int ^ bedrooms = gcnew unsigned int; float ^ bathrooms = gcnew float; unsigned int ^ yearBuilt = gcnew unsigned int; double ^ marketValue = gcnew double; propertyNumber = 937494; *propertyType = 'T'; *stories = 2; *bedrooms = 3; *bathrooms = 1.5F; *yearBuilt = 1974; *marketValue = 315850; Console::WriteLine("=//= Altair Realty =//="); Console::WriteLine("-=- Properties Inventory -=-"); Console::Write("Property #: "); Console::WriteLine(propertyNumber); Console::Write("Property Type: "); Console::WriteLine(propertyType); Console::Write("Stories: "); Console::WriteLine(stories); Console::Write("Bedrooms: "); Console::WriteLine(bedrooms); Console::Write("Bathrooms: "); Console::WriteLine(bathrooms); Console::Write("Year Built: "); Console::WriteLine(yearBuilt); Console::Write("Market Value: "); Console::WriteLine(marketValue); delete propertyNumber; delete propertyType; delete stories; delete bedrooms; delete bathrooms; delete yearBuilt; delete marketValue; return 0; }
=//= Altair Realty =//= -=- Properties Inventory -=- Property #: 937494 Property Type: T Stories: 2 Bedrooms: 3 Bathrooms: 1.5 Year Built: 1974 Market Value: 315850 Press any key to continue . . .
|
|||
Previous | Copyright © 2006-2025, FunctionX | 14 October 2008, 10:12 | Next |
|