Chapter 2. Variables and Basic Types

2.1 Primitive Built-in Types

C++ defines a set of primitive types that include the arithmetic types and a special type named void. The arithmetic types represent characters, integers, boolean value, and floating-point number. The void type has no associated values and can be used in only a few circumstances, most commonly as the return type for functions that do not return a value.

2.1.1 Arithmetic Types

The arithmetic types and divided into two categories: integral type (which include character and boolean types) and floating-point types.

The smallest chunk of addressable memory is referred to as a byte. The basic unit of storage, usually a small number of bytes, is referred to as a word.

Except for bool and the extended character types, the integral types may be signed or unsigned. A signed type represents negative of positive numbers (including zero); an unsigned type represents only values greater than or equal to zero.

There are three distinct basic character types char, signed char, and unsigned char, but there are only two representations: signed and unsigned. The char type uses one of these representations. Which of the other two character representations is equivalent to char depends on the compiler.

2.1.2 Type Conversions

The type of an object defines the data that an object might contain and what operations that object can perform. Among the operations that many types support is the ability to convert objects of the given type to other, related types.

Programs that contain undefined behaviour can appear to execute correctly in some circumstances and/or some compilers. There is no guarantee that the same program, compiled under a different compiler or even a subsequent release of the same compiler, will continue to run correctly.

As with any other out-of-range value, -1 will be transformed to an unsigned value.

2.1.3 Literals

literal is value self-evident.

Integer literals that begin with 0 are interpreted as octal. Those that begin with either 0x or oX are interpreted as hexadecimal.

20 /* decimal */  024 /* octal */  0x14 /* hexadecimal */

Although integer literals may be stored in signed types, technically speaking, the value of a decimal literal is never a negative number, the minus sign is not part of the literals.

Floating-point literals include either a decimal point or an exponent specified using scientific notation.

3.14159  3.14159E0  0.  0e0  .001

A character enclosed within single quotes is a char literal. Zero or more characters enclosed in double quotation marks is a string literal:

'a' /* character literal */  "Hello World!" /* string literal */

Some characters are non-printable, the escape sequence to represent such characters. An escape sequence begins with a backslash.

new line

\n

horizontal table

\t

alert(bell)

\a

vertical tab

\v

backspace

\b

double quote

\"

backslash

\\

question mark

\?

single quote

\'

carriage return

\r

formfeed

\f

The default type of an integer, floating-point, or character literal can be override by supplying a suffix or prefix.

Prefix

Suffix

u

char16_t (Unicode 16 character)

u or U

unsigned

U

char32_t (Unicode 32 character)

l or L

long

L

wchar_t (wide character)

ll or LL

long long

u8

char (utf-8)

f or F

float

l or L (for floating-point number)

long double

The words true and false are literals of type bool.

2.2 Variables

A variable provides the named storage that programs can manipulate.

2.2.1 Variable Definitions

A simple variable definition consists of a type specifier, followed by a list of one or more variable names separated by commas, and ends with a semicolon.

An object that is initialised gets the specified value at the moment it is created. Initialisation is not assignment. Initialisation happens when a variable is given a value when it is created. Assignment obliterates an objects's current value and replaces that value with a new one.

int units_sold = 0;
int units_sold = {0};
int units_sold{0}; // cannot to initilse built-in type by list initialisation.
int units_sold(0);

When we define a variable without an initialiser, the variable is default initialised. Such variables are given the "default" value. The value of an object of built-in type that is not explicitly initialised depends on where it is defined. Variables defined outside any function body are initialised to zero. With one exception, variables of built-in type defined inside a function are uninitialised.

2.2.2 Variable Declarations and Definitions

separate compilation lets user split the programs into several files, each of which can be compiled independently.

A declaration makes a name known to the program. A definition creates the associated entity. An extern that has an initialiser is a definition:

extern int i; // declares but does not define i
int j; // declares and defines j
extern double pi = 3.14159; // definition

Variable must be defined exactly once but can be declared many times.

2.2.3 Identifiers

identifiers in C++ can be composed of letters, digits, and the underscore character. The language imposes no limit on name length. Identifiers must begin with either a letter or an underscore. Identifiers are case-sensitive; upper- and lowercase letters are distinct.

2.2.4 Scope of a Name

A scope is a part of the program in which a name has a particular meaning. For global scope, once declared, names at the global scope are accessible throughout the program. For block scope, it is only accessible in the body of function or block which defines the variable.

Once a name has been declared in a scope, that name can be used by scopes nested inside that scope. Names declared in the outer scope can also be redefined in an inner scope:

#include <iostream>

int reused = 42;    // reused has global scope
int main () {
    int unique = 0; // unique has block scope
    // output #1: uses global reused; prints 42 0
    std::cout << reused << " " << unique << std::endl;
    int reused = 0; // new, local object named reused hides global reused
    // output #2: uses local reused; prints 0 0
    std::cout << reused << " " << unique << std::endl;
    // output #3: explicitly requests the global reused; prints 42 0
    std::cout << ::reused << " " << unique << std::endl;
}

2.3 Compound Types

A compound type is a type that is defined in terms of another type. C++ has several compound types, two of which -- references and pointers.

2.3.1 References

A reference defines an alternative name for an object, the compiler only binds the reference to its initialiser instead of copying the initialiser's value.

A reference is not an object. Instead, a reference is just another name for an already existing object.

The type of a reference and the object to which the reference refers must match exactly.

int &refValue1 = 10; // error: initialiser must an object
double dval = 0.0;
int &refValue2 = dval; // error: initialiser must be an int object

2.3.2 Pointers

A pointer is a compound type that "points to" another type. Unlink a reference, a pointer is an object in its own right. Pointers can be assigned and copied; a single pointer can point to several different objects over its lifetime. Unlink a reference, a pointer need not be initialised at the time it is defined.

A pointer holds the address of another object. User can get the address of an object by using the address-of operator (the & operator); When a pointer points to an object, it can use the dereference operator (the * operator) to access that object.

A null pointer does not point to any object. There several ways to obtain a null point:

int *p1 = nullptr;
int *p2 = 0;
int *p3 = NULL; // must include cstdlib

The most important is that a reference is not an object. Once a reference has been defined, there is no way to make that reference refer to a different object.

A pointer can be used in condition, if the pointer is 0, then the condition is false. The pointers can compare by the equality (==) or inequality (!=) operator.

The type void* is a special pointer type that can hold the address of any object.

For defining multiple variable of pointer:

int* p1, p2; // p1 is pointer to int; p2 is an int
int *p3, *p4; // both p3 and p4 are pointer to int

The level of pointer can be indicated by its own *:

int ival = 2014;
int *pi = &ival;    // pi points to an int
int **ppi = &pi;    // ppi points to a pointer to an int

2.4 const Qualifier

The const can make a variable unchangeably. When a const object is initialised from a compile-time constant, the compiler will usually replace uses of the variable with its corresponding value during compilation.

Sometime we have a const variable that we want to share across multiple files but whose initialiser is not a constant expression. In this case, we don't want the compiler to generate a separate variable in each file. Instead, we want the const object to behave like other variables. We want to define the const in one file, and declare it in thee other files that use that object. To define a single instance of a const variable, we use the keyword extern on both its definition and declaration(s):

extern const int bufSize = fcn(); // definition
extern const int bufSize; // declaration

2.4.1 References to const

The reference to const is a reference that refers to a const type, it cannot be used to change the object to which the reference is bound:

const int ci = 1024;
const int &r1 = ci;
r1 = 42;         // error r1 is a reference to const
int &r2 = ci;    // error: nonconst reference to a const object

2.4.2 Pointers and const

A pointer to const may not be used to change the object to which the pointer points, it must be initialised, and once initialised, its value may not be changed.

2.4.3 Top-Level const

The top-level const to indicate that the pointer itself is a const. A pointer to a const object refers to that const as a low-level const.

The distinction between top-level and low-level matters when we copy an object. When we copy an object, top-level const are ignored. On the other hand, low-level const is never ignored. When we copy an object, both objects must have the same low-level const qualification or there must be a conversion between the types of the two objects.

2.4.4 constexpr and Constant Expressions

A constant expression is an expression whose value cannot change and that can be evaluated at compile time. We can ask the compiler to verify that a variable is a constant expression by declaring the variable in a constexpr declaration.

constexpr int mf = 20;
constexpr int limit = mf + 1;
constexpr int sz = size();

Because a constant expression is one that can be evaluated at compile time, there are limits on the types that we can use in a constexpr declaration. The types we can use in a constexpr are known as "literal types" because they are simple enough to have literal values.

A pointer in a constexpr declaration, the constexpr specifier applies to the pointer, not the type to which the pointer point:

const int *p = nullptr;     // p is a pointer to a const int
constexpr int *q = nullptr;    // q is a const pointer to int

2.5 Dealing with Types

2.5.1 Type Aliases

A type alias is a name that is a synonym for another type. There are two ways to define a type alias, the first one is typedef:

typedef double wages;      // wages is a synonym for double
typedef wages base, *p;    // base is a synonym for double, p for double*

The second ways is via an alias declaration:

using SI = Sales_item;

2.5.2 The auto Type Specifier

The compiler can figure out the type by using the auto type specifier.

The type that the compiler infers for auto is not always exactly the same as the initialiser's type. Instead, the compiler adjusts the type to conform to normal initialisation rules.

// first, using the object to which the reference refers.
int i = 0;
int &r = i;
auto a = r;    // a is an int

// second, auto ordinarily ignores top-level const
const int ci = i;
auto b = ci;    // b is an int
const auto f = ci; // f has type const int

2.5.3 The decltype Type Specifier

The decltype returns the type of its operand.

decltype(f()) sum = x;

The compiler does not call f, but it uses the type that such a call would return as the type for sum.

The decltype of a parenthesised variable is always a reference:

decltype((i)) d; // error: d is int& and must be initialised
decltype(i) e;    // ok, e is an int

2.6 Defining Our Own Data Structure

Our class begins with the keyword struct, followed by the name of the class and a class body.

The most common technique for making it safe to include a header multiple times relies on the preprocessor. The preprocessor, which C++ inherits from C, is a program that runs before the compiler and changes the source text of the programs.

C++ program also use the preprocessor to define header guards. The #define directive takes a name and defines that name as a preprocessor variable; #ifdef is true if the variable has been defined, and #ifndef is true is the variable has not been defined; If the test is true, then everything following the #ifdef or #ifndef is processed up to the matching #endif.

Last updated

Was this helpful?