C++ Programming/Code/Variables

From testwiki
Revision as of 06:31, 19 September 2007 by imported>Panic2k4 (References: moved to temporary location in the & operator)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

Variables

Much like a person has a name that distinguishes him or her from other people, a variable assigns a particular instance of an object type a name or label by which the instance can be referred to. Typically a variable is bound to a particular address in computer memory that is automatically assigned to at runtime, with a fixed number of bytes determined by the size of the object type of a variable and any operations performed on the variable effects one or more values stored in that particular memory location. If the size and location of a variable is unknown beforehand, then where an object instance can be found is used as the value of the variable instead and the size of the variable is determined by the size needed to hold the location value. This is called referencing.

Variables reside in a specific scope. The scope of a variable determines the life-time of a variable. Entrance into a scope begins the life of a variable and leaving scope ends the life of a variable. This becomes important later as the constructor of variables are called when entering scope and the destructor of variables are called when leaving scope. A variable is visible when in scope unless it is hidden by a variable with the same name inside an enclosed scope. A variable can be in global scope, namespace scope, file scope or block scope.

Types

Just as there are different types of values (integer, character, etc.), there are different types of variables. A variable can refer to simple values like integers and strings called a primitive type or to a set of values called a composite type that are made up of primitive types and other composite types. Types consist of a set of valid values and a set of valid operations which can be performed on these values. A variable must declare what type it is before it can be used in order to enforce value and operation safety and to know how much space is needed to store a value.

Major functions that type systems provide are:

  • Safety - types make it impossible to code some operations which cannot be valid in a certain context. This mechanism effectively catches the majority of common mistakes made by programmers. For example, an expression "Hello, Wikipedia"/1 is invalid because a string literal cannot be divided by an integer in the usual sense. As discussed below, strong typing offers more safety, but it does not necessarily guarantee complete safety (see type-safety for more information).
  • Optimization - static type checking might provide useful information to a compiler. For example, if a type says a value is aligned at a multiple of 4, the memory access can be optimized.
  • Documentation - using types in languages also improves documentation of code. For example, the declaration of a variable as being of a specific type documents how the variable is used. In fact, many languages allow programmers to define semantic types derived from primitive types; either composed of elements of one or more primitive types, or simply as aliases for names of primitive types.
  • Abstraction - types allow programmers to think about programs in higher level, not bothering with low-level implementation. For example, programmers can think of strings as values instead of a mere array of bytes.
  • Modularity - types allow programmers to express the interface between two subsystems. This localizes the definitions required for interoperability of the subsystems and prevents inconsistencies when those subsystems communicate.

C++ Programming/Data Types Reference

standard types

C++ has five basic primitive types called standard types, specified by particular keywords, that store a single value.

The type of a variable determines what kind of values it can store:

  • bool - a boolean value: true; false
  • int - Integer: -5; 10; 100
  • char - a character in some encoding, often something like ASCII, ISO-8859-1 ("Latin 1") or ISO-8859-15: 'a', '=', 'G', '2'.
  • float - floating-point number: 1.25; -2.35*10^23
  • double - double-precision floating-point number: like float but more decimals

Template:NOTE

The float and double primitive data types are called 'floating point' types and are used to represent real numbers (numbers with decimal places, like 1.435324 and 853.562). Floating point numbers and floating point arithmetic can be very tricky, due to the nature of how a computer calculates floating point numbers.

Template:NOTE

Declaration

C++ is a statically typed language. Hence, any variable cannot be used without specifying its type. This is why the type figures in the declaration. This way the compiler can protect you from trying to store a value of an incompatible type into a variable, e.g. storing a string in an integer variable. Declaring variables before use also allows spelling errors to be easily detected. Consider a variable used in many statements, but misspelled in one of them. Without declarations, the compiler would silently assume that the misspelled variable actually refers to some other variable. With declarations, an "Undeclared Variable" error would be flagged. Another reason for specifying the type of the variable is so the compiler knows how much space in memory must be allocated for this variable.

The simplest variable declarations look like this (the parts in []s are optional):

[specifier(s)] type variable_name [ = initial_value];

To create an integer variable for example, the syntax is

int sum; 

where sum is the name you made up for the variable. This kind of statement is called a declaration. It declares sum as a variable of type int, so that sum can store an integer value. Every variable has to be declared before use and it is common practice to declare variables as close as possible to the moment where they are needed. This is unlike languages, such as C, where all declarations must precede all other statements and expressions.

In general, you will want to make up variable names that indicate what you plan to do with the variable. For example, if you saw these variable declarations:

char firstLetter; 
char lastLetter; 
int hour, minute; 

you could probably make a good guess at what values would be stored in them. This example also demonstrates the syntax for declaring multiple variables with the same type in the same statement: hour and minute are both integers (int type). Notice how a comma separates the variable names.

int a = 123;
int b (456);

Those lines also declare variables, but this time the variables are initialized to some value. What this means is that not only is space allocated for the variables but the space is also filled with the given value. The two lines illustrate two different but equivalent ways to initialize a variable. The assignment operator '=' in a declaration has a subtle distinction in that it assigns an initial value instead of assigning a new value. The distinction becomes important especially when the values we are dealing with are not of simple types like integers but more complex objects like the input and output streams provided by the iostream class.

The expression used to initialize a variable need not be constant. So the lines:

int sum;
sum = a + b;

can be combined as:

int sum = a + b;

or:

int sum (a + b);

Declare a floating point variable 'f' with an initial value of 1.5:

float f = 1.5 ;

Floating point constants should always have a '.' (decimal point) somewhere in them. Any number that does not have a decimal point is interpreted as an integer, which then must be converted to a floating point value before it is used.

For example:

 double a = 5 / 2;

will not set a to 2.5 because 5 and 2 are integers and integer arithmetic will apply for the division, cutting off the fractional part. A correct way to do this would be:

 double a = 5.0 / 2.0;

You can also declare floating point values using scientific notation. The constant .05 in scientific notation would be 5×102. The syntax for this is the base, followed by an e, followed by the exponent. For example, to use .05 as a scientific notation constant:

 double a = 5e-2;

Template:NOTE

Below is a program storing two values in integer variables, adding them and displaying the result:

C++ Programming/Code/Variables/Examples/Adds two numbers and prints their sum

OR, if you like to save some space, the same above statement can be written as:

C++ Programming/Code/Variables/Examples/Adds two numbers and prints their sum1

Type Modifiers

There are several modifiers that can be applied to data types to change the range of numbers they can represent.

const

A variable declared with this specifier cannot be changed (as in read only). Either local or class-level variables (scope) may be declared const indicating that you don't intend to change their value after they're initialized. You declare a variable as being constant using the const keyword.

const unsigned int DAYS_IN_WEEK = 7 ; 

declares a positive integer constant, called DAYS_IN_WEEK, with the value 7. Because this value cannot be changed, you must give it a value when you declare it. If you later try to assign another value to a constant variable, the compiler will print an error.

 int main(){
   const int i = 10;

   i = 3;            // ERROR - we can't change "i"

   int &j = i;       // ERROR - we promised not to
                     // change "i" so we can't
                     // create a non-const reference
                     // to it

   const int &x = i; // fine - "x" is a reference
                     // to "i"

   return 0;
 }

The full meaning of const is more complicated than this; when working through pointers or references, const can be applied to mean that the object pointed (or referred) to will not be changed via that pointer or reference. There may be other names for the object, and it may still be changed using one of those names so long as it was not originally defined as being truly const.

It has an advantage for programmers over #define command because it is understood by the compiler, not just substituted into the program text by the preprocessor, so any error messages can be much more helpful.

With pointer it can get messy...

T const *p;                     // p is a pointer to a const T
T *const p;                     // p is a const pointer to T
T const *const p;               // p is a const pointer to a const T
                                                           

If the pointer is a local, having a const pointer is useless. The order of T and const can be reversed:

const T *p == T const *p;

Template:NOTE

volatile

A hint to the compiler that a variable's value can be changed externally; therefore the compiler must avoid aggressive optimization on any code that uses the variable.

Unlike in Java, C++'s volatile specifier does not have any meaning in relation to multi-threading. Standard C++ does not include support for multi-threading (though it is a common extension) and so variables needing to be synchronized between threads need a synchronization mechanisms such as mutexes to be employed, keep in mind that volatile implies only safety in the presence of implicit or unpredictable actions by the same thread (or by a signal handler in the case of a volatile sigatomic_t object). Accesses to mutable volatile variables and fields are viewed as synchronization operations by most compilers and can affect control flow and thus determine whether or not other shared variables are accessed, this implies that in general ordinary memory operations cannot be reordered with respect to a mutable volatile access. This also means that mutable volatile accesses are sequentially consistent. This is not (as yet) part of the standard, it is under discussion and should be avoided until it gets defined.

mutable

This specifier may only be applied to a non-static, non-const member variables. It allows the variable to be modified within const member functions.

mutable is usually used when an object might be logically constant, i.e, no outside observable behavior changes, but not bitwise const, i.e. some internal member might change state.

The canonical example is the proxy pattern. Suppose you have created an image catalog application that shows all images in a long, scrolling list. This list could be modeled as:

class image {
 public:
   // construct an image by loading from disk
   image(const char* const filename); 

   // get the image data
   char const * data() const;
 private:
   // The image data
   char* m_data;
}

class scrolling_images {
   image const* images[1000];
};

Note that for the image class, bitwise const and logically const is the same: If m_data changes, the public function data() returns different output.

At a given time, most of those images will not be shown, and might never be needed. To avoid having the user wait for a lot of data being loaded which might never be needed, the proxy pattern might be invoked:

class image_proxy {
  public:
   image_proxy( char const * const filename )
      : m_filename( filename ),
        m_image( 0 ) 
   {}
   ~image_proxy() { delete m_image; }
   char const * data() const {
      if ( !m_image ) {
         m_image = new image( m_filename );
      }
      return m_image->data();
   }
  private:
   char const* m_filename;
   mutable image* m_image;
};

class scrolling_images {
   image_proxy const* images[1000];
};

Note that the image_proxy does not change observable state when data() is invoked: it is logically constant. However, it is not bitwise constant since m_image changes the first time data() is invoked. This is made possible by declaring m_image mutable. If it had not been declared mutable, the image_proxy::data() would not compile, since m_image is assigned to within a constant function.

Template:NOTE

short

The short specifier can be applied to the int data type. It can decrease the number of bytes used by the variable, which decreases the range of numbers that the variable can represent. Typically, a short int is half the size of a regular int -- but this will be different depending on the compiler and the system that you use. When you use the short specifier, the int type is implicit. For example:

short a;

is equivalent to:

short int a;

Template:NOTE

long

The long specifier can be applied to the int and double data types. It can increase the number of bytes used by the variable, which increases the range of numbers that the variable can represent. A long int is typically twice the size of an int, and a long double can represent larger numbers more precisely. When you use long by itself, the int type is implied. For example:

long a;

is equivalent to:

long int a;

The shorter form, with the int implied rather than stated, is more idiomatic (i.e., seems more natural to experienced C++ programmers).

Use the long specifier when you need to store larger numbers in your variables. Be aware, however, that on some compilers and systems the long specifier may not increase the size of a variable. Indeed, most common 32-bit platforms (and one 64-bit platform) use 32 bits for int and also 32 bits for long int.

Template:NOTE

unsigned

The unsigned specifier makes a variable only represent positive numbers and zero. It can be applied only to the char, short,int and long types. For example, if an int typically holds values from -32768 to 32767, an unsigned int will hold values from 0 to 65535. You can use this specifier when you know that your variable will never need to be negative. For example, if you declared a variable 'myHeight' to hold your height, you could make it unsigned because you know that you would never be negative inches tall.

Template:NOTE

signed

The signed specifier makes a variable represent both positive and negative numbers. It can be applied only to the char, int and long data types. The signed specifier is applied by default for int and long, so you typically will never use it in your code.

Template:NOTE

Enumeration Types

An enum or enumerated type is a type that describes a set of named values. Every enumerated type is stored as an integral type. While it is possible to assign a constant integer value to a named value of an enumerated type, most often implicit integer values will be used, with the first named value having an integer value of 0 and all others in turn having an integer value one more than its predecessor.

For example:

enum colour {Red, Green, Blue};

defines colour as a set of named constants called Red, Green and Blue with values of 0, 1 and 2. Enumerations are useful for when you have set of related values which can help to make code more readable by removing magic numbers. Consider the following:

if (yourColour == Red)
{
  std::cout << "You chose red" << std::endl;
}

to the slightly less meaningful

if (yourColour == 0)
{
  std::cout << "You chose red" << std::endl;
}

In the first example using the enum makes the meaning clearer, whereas in the second example one may wonder what the 0 means. By consistently using enumerated types in a program rather than hard coding values the code becomes easier to understand and reduces the chance of accidental error such as the quandary, "Was it a 0 or a 1 that should be tested for the colour red?"

I can also count with enums, such as:

enum month
{ JANUARY, FEBRUARY, MARCH, APRIL, MAY, JUNE, JULY, AUGUST, SEPTEMBER, OCTOBER, NOVEMBER, DECEMBER };

for (int monthcount = JANUARY; monthcount <= DECEMBER; monthcount++)
{
   std::cout << monthcount << std::endl;
}

Enumerations do not necessarily need to begin at 0, and although each successive variable is always increased by one, this can be overridden by assigning a value.

enum colour {Red=2, Green, Blue=6, Orange};

In the above example Red is 2, Green is 3, Blue is 6 and Orange is 7.


User Input

In the previous program two numbers are added together which are specified as part of the program. If we want to find the sum of any other pair of numbers using the above program, we would have to edit the program, change the numbers, save and recompile. In other words, the numbers are hard-coded into the program and any change requires recompilation.

It is always better to write programs to be more flexible, in that they do not assume any values but allow the user to specify them. One way to allow the user to specify values is to prompt the user for the values and get them, like so:

Enter number 1: 230
Enter number 2: -35
The sum of 230 and -35 is 195.

Text that is italicized is typed by the user and the bold text is output by the program.

Derived Types

Template:TODO