Chapter 2
IN THIS CHAPTER
Discovering all the different data types
Casting and converting
Using structures with your data
Comparing and manipulating structures
C++, being a computer language and all, provides you with a lot of ways to manipulate data — numbers, letters, strings, arrays — anything you can store inside the computer memory. To get the most out of C++, you should know as much as you can about the fundamental data types. This chapter covers them and how to use them.
This chapter refers to the ANSI standard of C++. ANSI is the American National Standards Institute. The information provided in this chapter deals with the ANSI standard (singular) of C++. Fortunately, the GNU gcc compiler that comes with Code::Blocks is ANSI-standard-compliant.
In the sections that follow, you see how to manipulate data, consider the data types available to you, and discover how you can change one data type to another.
The ANSI C++ standard dictates the fundamental C++ types shown in Table 2-1.
The precise values of some of these types, such as long double
, can vary by compiler. The best way to ensure that you understand the limits of your compiler is to run a simple test. The VarTypes
example, shown in Listing 2-1, demonstrates the maximum values for each data type found in Table 2-1.
TABLE 2-1: ANSI C++ Character Types
Name |
Size in Bytes |
Range |
---|---|---|
|
1 |
–128 to 127 |
|
1 |
0 to 255 |
|
2 |
–32,768 to 32,767 |
|
2 |
0 to 65,535 |
|
4 |
-2,147,483,648 to 2,147,483,647 |
|
4 |
0 to 4,294,967,295 |
|
8 |
-9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 |
|
8 |
0 to 18,446,744,073,709,551,615 |
|
1 |
true/false |
|
4 |
1.17549e-038 to 3.40282e+038 |
|
8 |
2.22507e-308 to 1.79769e+308 |
|
12 |
3.3621e-4932 to 1.18973e+4932 |
LISTING 2-1: Testing Maximum Type Values
#include <iostream>
#include <climits>
#include <cfloat>
using namespace std;
int main() {
char Char = CHAR_MAX;
unsigned char UChar = UCHAR_MAX;
short Short = SHRT_MAX;
unsigned short UShort = USHRT_MAX;
int Int = INT_MAX;
unsigned int UInt = UINT_MAX;
long Long = LONG_MAX;
unsigned ULong = ULONG_MAX;
long long LongLong = LLONG_MAX;
unsigned long long ULongLong = ULLONG_MAX;
bool Bool = true;
float Float = FLT_MIN;
double Double = DBL_MIN;
long double LDouble = LDBL_MIN;
cout << "Char\t\t\t" << Char << "\t\t\t" <<
sizeof(Char) << endl;
cout << "Unsigned Char\t\t" << UChar << "\t\t\t" <<
sizeof(UChar) << endl;
cout << "Short\t\t\t" << Short << "\t\t\t" <<
sizeof(Short) << endl;
cout << "Unsigned Short\t\t" << UShort << "\t\t\t" <<
sizeof(UShort) << endl;
cout << "Int\t\t\t" << Int << "\t\t" <<
sizeof(Int) << endl;
cout << "Unsigned Int\t\t" << UInt << "\t\t" <<
sizeof(UInt) << endl;
cout << "Long\t\t\t" << Long << "\t\t" <<
sizeof(Long) << endl;
cout << "Unsigned Long\t\t" << ULong << "\t\t" <<
sizeof(ULong) << endl;
cout << "Long Long\t\t" << LongLong << "\t" <<
sizeof(LongLong) << endl;
cout << "Unsigned Long Long\t" << ULongLong << "\t" <<
sizeof(ULongLong) << endl;
cout << "Bool\t\t\t" << (Bool ? "True" : "False") <<
"\t\t\t" << sizeof(Bool) << endl;
cout << "Float\t\t\t" << Float << "\t\t" <<
sizeof(Float) << endl;
cout << "Double\t\t\t" << Double << "\t\t" <<
sizeof(Double) << endl;
cout << "Long Double\t\t" << LDouble << "\t\t" <<
sizeof(LDouble) << endl;
return 0;
}
Char 127 1
Unsigned Char 255 1
Short 32767 2
Unsigned Short 65535 2
Int 2147483647 4
Unsigned Int 4294967295 4
Long 2147483647 4
Unsigned Long 4294967295 4
Long Long 9223372036854775807 8
Unsigned Long Long 18446744073709551615 8
Bool True 1
Float 1.17549e-038 4
Double 2.22507e-308 8
Occasionally, when you look at error messages (or if you read the ANSI standard), you see the terms lvalue
and rvalue
. The l and r refer to left and right, respectively. In an assignment statement, an lvalue
is any expression that can be on the left side of the equals sign, and an rvalue
is an expression that can be on the right side of an equals sign.
ploggle = 3;
On the left side, you cannot have items that are strictly an rvalue
. The following is not allowed because 2
is strictly an rvalue
:
2 = ploggle;
The number 2
can’t appear on the left (setting it equal to something else makes no sense), therefore it isn’t an lvalue
. In fact, anything you can set equal to something else is an lvalue
.
The main reason you need to know these terms is their tendency to show up in error messages. If you try to compile the line 2 = ploggle
, you see an error message similar to this one:
non-lvalue in assignment
If you don’t know what the term lvalue
means, these messages can be confusing. Although seeing the problem with 2 = ploggle
is pretty easy, sometimes the problem is not that obvious. Look at this:
ChangeMe() = 10;
In most cases, putting a function call on the left doesn’t make sense, so you don’t do it. In other words, you must consider whether the expression ChangeMe()
is considered an lvalue
. Look at this code from the LValueAndRValue
example:
#include <iostream>
using namespace std;
int uggle;
int &ChangeMe() {
return uggle;
}
int main() {
ChangeMe() = 10;
cout << ChangeMe() << endl;
return 0;
}
The function ChangeMe()
returns a reference to an integer; this line is valid:
ChangeMe() = 10;
The expression ChangeMe()
refers to the variable uggle
, and thus this line of code stores 10
in uggle
. You can still use ChangeMe()
as a function, as shown in the next line with the cout
, so it can still stand alone.
Although C++ has all these great data types, such as int
and char
, the fact is that the CPU just stores them as numbers. And sometimes you may have a character and need to use its underlying number. To do this, you can cast the data into a different type.
The way you cast is to take a variable of one type and type the variable’s name, preceded by the other type you would like it to be. You put that other type in parentheses, as shown in the SimpleCast
example that follows.
#include <iostream>
using namespace std;
int main() {
char buddy = 'A';
int underneath = (int)buddy;
cout << underneath << endl;
return 0;
}
When you run this code, you obtain an output value of 65
. If you substituted a lowercase a
, the output would be 97
because uppercase and lowercase letters have different numeric values.
The idea behind casting is to take some data and, without changing it, use it in another way. For example, you could have an array containing the characters Apple
. But inside the memory, each letter is stored as a number. For example, the A
is stored as 65
, p
is stored as 112
, l
as 108
, and e
as 101
. Therefore, you use code like that found in the CastOrConvert
example that follows when you want to cast each character to an integer:
char str[] = {'A','p','p','l','e','\0'};
cout << str << endl;
for (int x: str)
cout << x << endl;
where str
is the string Apple
(notice the null value required to end the string). The for each loop casts each character in str
, one at a time, to an int
, and then prints it out onscreen. This act would print out the numerical equivalents of each letter, as shown here:
Apple
65
112
112
108
101
0
In other words, the code casts the characters to integers — but doesn’t actually change any data.
Converting, however, is different. If you want to take the number 123
, casting it to a string will not create a string 123
.
The string 123
is made up of three underlying characters. The numbers for the string 123
are 49
, 50
, and 51
, respectively. Casting the number 123
into a char
won’t produce the string
, "123"
. Instead, you would need to convert the number to a string using code like this, as shown in CastOrConvert
.
int value = 123;
char strValue[4];
strValue[3] = '\0';
for (int counter = 2; counter >= 0; counter--) {
strValue[counter] = (char)(value % 10 + 48);
value = value / 10;
}
cout << strValue << endl;
In this case, the code works backward to create the string from the number by using a combination of integer division and modulus. The content in value
is destroyed in the process, but strValue
contains the correct string in the end. To get an idea of how this works, 123 % 10 = 3
, while 123 / 10 = 12
. The value 3 + 48 = 51
comes out to the char value ’3’
when cast. Of course, there is a much easier way to perform this task (you must #include <string>
):
string EasyValue = to_string(123);
cout << EasyValue << endl;
As is true of most techniques, there are times when casting won’t work as expected. One of those times come into play when converting between floats and integers. Instead of using a conversion function, the C++ compiler automatically converts from float to integer and vice versa if you try to cast one to the other. Ugh. That goes against the rest of the rules, so be careful. Here’s an example of converting a float to an integer:
float f = 6.3;
int i = (int)f;
But the crazy part is that you can also do the same thing without even using the cast:
float f = 6.3;
int i = f;
The ANSI standard of C++ comes with all kinds of goodies that make life easier than it used to be. Casting is one example. Originally, you could just cast all you wanted and change from one data type to another, possibly causing a mess, especially if you take existing code and compile it under a different operating system or perhaps even under a different compiler on the same operating system. One type may have a different underlying representation, and then, when you convert it on one system, you get one thing; take it to a different system and you get something else. That’s bad. It creates bugs!
So the ANSI standard for C++ gives some newer and better ways of casting between items of data. These include dynamic_cast
, static_cast
, and const_cast
. (There is also a reinterpret_cast
, but it’s incredibly unsafe to use and therefore not demonstrated.)
When the makers of C++ came up with these new ways of casting, their motivation was this: Think in terms of conversions. A cast simply takes one data type and tells the compiler to treat it as another data type. So first ask yourself whether one of the conversions will work for you. If not, you can consider one of the new ways of casting.
But remember, a cast tells the compiler to treat some data as another type of data. But the new ways of casting prevent you from doing a cast that doesn’t make sense. For example, you may have a class hierarchy, and you have a pointer to a base class. But because an instance of a derived class can be treated as an instance of a base class, this instance that you’re looking at could actually be an instance of a derived class.
In the old style of C and C++ programming, you could just cast the instance and have at it:
DoSomethingCool( (derivedclass *) someptr);
This code assumes that someptr
is of type pointer-to-base-class that, in fact, points to a derivedclass
instance. It may point to derivedclass
, but that depends on how you wrote the application. But, relying on assumptions rather than actual knowledge is a great way to create a buggy application.
However, with the new ANSI ways of casting, you can be sure that someptr
points to a derivedclass
instance. The DynamicCast
example, shown in Listing 2-2, is a complete application that demonstrates a proper down-cast that uses a pointer to a base class and casts it down to a pointer of a derived class.
LISTING 2-2: Casting Instances Dynamically for Safety
#include <iostream>
using namespace std;
class King {
protected:
string CrownName;
public:
virtual string &MyName() { return CrownName; }
virtual ~King(){}
};
class Prince : public King {
public:
string School;
};
void KingInfo(King *inst) {
cout << "=========" << endl;
cout << inst->MyName() << endl;
Prince *asPrince = dynamic_cast<Prince *>(inst);
if (asPrince != 0)
{
cout << asPrince->School << endl;
}
}
int main() {
Prince George;
George.MyName() = "George I";
George.School = "School of the Kings";
KingInfo(&George);
King Henry;
Henry.MyName() = "Henry II";
KingInfo(&Henry);
return 0;
}
When you run this code, you see output that looks like this:
=========
George I
School of the Kings
=========
Henry II
Some strange things are going on in this code. Starting with main()
, the code calls KingInfo()
, first passing it the address of George
(a Prince
instance, derived from King
) and then the address of Henry
(a King
instance).
The KingInfo()
function first prints the information that is common to both due to inheritance using the MyName()
function and prints the resulting name. Then comes the important part: the dynamic cast. To do the dynamic cast, the code calls dynamic_cast
and saves inst
(which can be of type King
or Prince
) in a pointer variable called asPrince
. Notice the syntax of dynamic_cast
. It looks like a template in that you include a type in angle brackets. Then you put the variable you want to cast in parentheses (in this case inst
).
If the dynamic cast works, it returns a pointer that you can save as the type inside angle brackets. Otherwise, the dynamic cast returns 0
. After calling dynamic_cast
, the code tests the result against 0
. If the result is not 0
, the dynamic cast worked, which means that inst
is of type Prince
. Then, in the if
block, the code retrieves and prints the School
member, which is part of Prince
, not King
.
Notice the unique design of the King
class in Listing 2-2. For dynamic_cast
to work, the base class involved must have at least one virtual function. Thus the base class — and each of its derived classes — has a virtual table (also needed for dynamic_cast
to work). In addition, the Code::Blocks compiler raises a warning message when you don’t provide a virtual destructor:
warning: 'class King' has virtual functions but non-virtual destructor
Consequently, the example includes a virtual destructor as well. Notice also that this class uses good design by keeping CrownName
private and providing an accessor function, MyName()
, to it.
The fundamental difference between an old-style direct cast and a dynamic_cast
is that the compiler generates code that automatically does an old-style cast, regardless of whether the cast is valid, during compile time. That is, the cast is hardcoded. But dynamic_cast
tests the types at runtime. The dynamic cast may or may not work depending on the type of the object.
When you use a dynamic cast, you can cast either a pointer or a reference. The KingInfo()
function shown previously in Listing 2-2 uses a pointer. Here’s a modified form that uses a reference:
void KingInfoAsReference(King &inst) {
cout << "=========" << endl;
cout << inst.MyName() << endl;
try {
Prince &asPrince = dynamic_cast<Prince &>(inst);
cout << asPrince.School << endl;
} catch (…) { }
}
To make this version work, you have to use an exception handler (which is a way to deal with unusual situations; see Chapter 3 in this minibook for more information on exception handlers). The reason for using an exception handler is that with a pointer, you can simply test the result against 0
. But with references, you have no such thing as a null
reference or 0
reference. The reference must work or you get a runtime error. In C++, the way you can catch a situation that didn’t work is by typing the word try
, followed by your code that attempts to do the job, in braces. Follow that with the word catch
and a set of parentheses containing three periods. Following that, you put braces — and possibly any code you want to run — just in case the earlier code didn’t work.
This code doesn’t do anything inside the catch
block because the application will continue to work even if the call fails — the output simply lacks the school name. C++ requires that all try
blocks are matched with a catch
block, so you must include the catch
block even when it doesn’t do anything.
The ANSI C++ standard includes a special type of cast that does no type checking. If you have to cast directly without the help of dynamic_cast
, you should opt for static_cast
instead of the old C-style cast.
When you want to do a static cast, call static_cast
and follow it with angle brackets containing the type you want to cast to. Then put the item being cast inside parentheses, as in the following:
FinalType *f = static_cast<FinalType *>(orig);
The advantage of using static_cast
is that it does some type checking at compile time, whereas old C-style casts do not. The compiler allows you to do static_cast
only between related objects. You can do a static_cast
from an instance of one class to an instance of a derived or base class. But if two classes are not related, you will get a compiler error. For example, suppose that you have these two lines of code:
class FinalType {};
class AnotherType {};
They’re unrelated classes. Then, if you have these lines of code
AnotherType *orig = new AnotherType;
FinalType *f = static_cast<FinalType *>(orig);
and you try to compile the code, you get an error:
static_cast from 'AnotherType *' to 'FinalType *'
The following code, found in the StaticCast
example, shows how to make the casting work:
#include <iostream>
using namespace std;
class FinalType {};
class AnotherType : public FinalType {};
int main() {
AnotherType *orig = new AnotherType;
FinalType *f = static_cast<FinalType *>(orig);
}
If you’re just doing a conversion between floating-point numbers and integers, you can do an old-style cast. (That’s because an old-style cast is really a conversion, not a cast.) Alternatively, of course, you’re welcome to use static_cast
to get the same job done:
float f = static_cast<float>(x);
Sometimes you need to add or remove const
from a variable in order to perform a cast. The variable itself doesn’t change, but the cast output does. For example, if you want to send a const
value to a function that doesn’t accept a const
value, you need to perform a const_cast
. Likewise, you may have a volatile
variable, one that is changed by code outside the current application. (This is a common process in embedded applications; see the article at https://www.tutorialspoint.com/What-does-the-volatile-keyword-mean-in-Cplusplus
for more information.) You may need to cast the volatile
variable as a common variable. The ConstCast
example that follows shows both techniques:
#include <iostream>
using namespace std;
void PrintIt(int *out) {
cout << "The value is: " << *out << endl;
}
int main() {
volatile int X = 20;
const int Y = 30;
PrintIt(const_cast<int*>(&X));
PrintIt(const_cast<int*>(&Y));
return 0;
}
In the first case, if you were to try PrintIt(&X)
, you’d see error: invalid conversion from ’volatile int*’ to ’int*’
during compilation. Likewise, in the second case, PrintIt(&Y)
would produce an error: invalid conversion from ’const int*’ to ’int*’
error message. Of course, neither X
nor Y
has its attributes removed; you simply strip the volatile
or const
attribute off for the purpose of sending the value to PrintIt()
.
Before C++ came to life, C had something that was similar to classes, called structures. The difference was that structures had only properties — no methods. Here’s an example of a structure:
struct Dimensions {
int height;
int width;
int depth;
int weight;
int price;
};
This block of code is similar to a class; as you can see, it has some properties but no methods. Nor does it have any access control (such as public, private, or protected).
But not only did the designers of C++ add classes to C++, they also enhanced the structures in C++. So now you can use structures more powerfully in C++ than you could in C. The main change to structures in C++ is that they can have methods and access control. Thus, you can add to the Dimensions
structure like so (making struct
and class
equivalent):
struct Dimensions {
private:
int price;
public:
int height;
int width;
int depth;
int weight;
int GetPrice() { return price; }
};
Then create an instance of Dimensions
in your code like this:
Dimensions FirstIem;
Dimensions *SecondItem = new Dimensions;
In other words, programmers use struct
for simple data types that are a collection of smaller data types. (That is, they use struct
s the same way C originally used them.) The sections that follow tell you about some of these data-structure issues.
struct Dimensions another;
but all you really need is
Dimensions another;
A common use of structures is as an advanced data type made up of underlying data types. For example, a lot of operating systems that deal with graphics include libraries that require a Point
structure. Typically, a Point
structure is simply a grouping of an X-coordinate and a Y-coordinate, all in one package like this:
struct Point {
int x;
int y;
};
Then, when you need to call a function that requires such a structure — such as the function created for this example called DrawDot()
— you would simply declare a Point
and call the function, as in the following:
Point onedot;
onedot.x = 10;
onedot.y = 15;
DrawDot(onedot);
The DrawDot
function would have a prototype that looks like this:
void DrawDot(Point pt);
Note that the function doesn’t take a pointer to a Point
, nor does it take a reference to a Point
. It just gets right to the Point
directly.
Point seconddot = { 30, 50 };
DrawDot(seconddot);
Setting simple structures that are equal to another structure is easy. The C++ compiler automatically handles this by copying the members one by one. The EquateStruct
example, shown in Listing 2-3, is an example of this process in action.
LISTING 2-3: Copying Structures Easily
#include <iostream>
using namespace std;
struct Point3D {
double x;
double y;
double z;
};
int main() {
Point3D FirstPoint = { 10.5, 22.25, 30.8 };
Point3D SecondPoint = FirstPoint;
cout << SecondPoint.x << endl;
cout << SecondPoint.y << endl;
cout << SecondPoint.z << endl;
return 0;
}
class Point3D {
public:
double x;
double y;
double z;
};
No matter which form of the application you use, the output is simple. When you run this application, you see output similar to this:
10.5
22.25
30.8
Because simple structures are just a grouping of smaller data items, you can treat them as one chunk of data. For that reason, you can easily return them from functions without having to use pointers. The following function (found in the CompoundData
example) shows how to return a structure:
Point3D StartingPoint(float x) {
Point3D start;
start.x = x;
start.y = x * 2;
start.z = x * 3;
return start;
}
This function relies on the Point3D
struct
defined in the preceding section, “Equating structures.” The following code shows how to use this function:
int main() {
Point3D MyPoint = StartingPoint(5.2);
Point3D OtherPoint = StartingPoint(6.5);
cout << MyPoint.x << endl;
cout << MyPoint.y << endl;
cout << MyPoint.z << endl;
cout << endl;
cout << OtherPoint.x << endl;
cout << OtherPoint.y << endl;
cout << OtherPoint.z << endl;
}
These cout
statements produce the following output:
5.2
10.4
15.6
6.5
13
19.5
Note that StartingPoint()
creates a local variable, start
, of type Point3D
. This variable isn’t a pointer or reference. The return is an unmodified start
. Calling StartingPoint()
copies the value of the returned structure into variables in main()
, first MyPoint
and then OtherPoint
.
Point3D MyPoint = StartingPoint(5.2);
At the assembly level, StartingPoint()
receives the address of MyPoint
. Then at the end of the function, again at the assembly level, the compiled code copies the contents of start
into the MyPoint
structure by using the pointer to MyPoint
. So StartingPoint()
doesn’t actually return anything; instead, the data is copied. Thus, if your structure includes a pointer variable (for example), you get a copy of the pointer variable as well — that is, your pointer variable will point to the same thing as the pointer in the function. That may or may not be what you want, depending on your situation. So be careful and make sure you fully understand what you’re doing when you return a structure from a function!
It’s often nice to be able to use a common name for a variable or other item without fear that the name will clash with a preexisting identifier. For example, somewhere in a header file, you may have a global variable called Count
, and somebody else may want to make a variable called Count
in an application that uses your header file. Or you may want to name a function GetData()
— but you need to ensure that it doesn’t conflict with another header that already has a GetData()
function. These are examples of potential naming clashes (or sometimes called a name collision). The following sections describe how to create and use namespaces to your benefit.
You can use namespaces to group identifiers, such as all your classes, under a single name. If you called this group Menagerie
, for example, Menagerie
is your namespace. You would then put your classes inside it, as shown in the SimpleNamespace
example:
namespace Menagerie {
class Oxen {
public:
int Weight;
int NumberOfTeeth;
};
class Cattle {
public:
int Weight;
int NumberOfChildren;
};
}
The names Oxen
and Cattle
are unique within the Menagerie
namespace. You are free to reuse these names in other namespaces without worrying about a clash. Then, if you want to use either of the two classes outside the Menagerie
namespace, you fully qualify the names of the classes, like so (notice the use of the double colons between Menagerie
and Cattle
):
Menagerie::Cattle bessie;
bessie.Weight = 643;
If you plan to use the names in the Menagerie
namespace without having to retype the namespace name each time, just put a line after the namespace declaration in the other namespace (but somewhere preceding the use of the names Cattle
and Oxen
in your code), like this:
using namespace Menagerie;
Then you can access the names as if they’re not in a namespace:
Cattle bessie;
bessie.Weight = 643;
void cattleranch() {
Cattle x;
}
using namespace Menagerie;
void dairy() {
Cattle x;
}
Here the first function won’t compile because the compiler won’t know the name Cattle
. To get it to work, you have to replace Cattle
with Menagerie::Cattle
. But the second function will compile because you included using namespace Menagerie;
.
The using namespace
line is good only for lines that follow it. If you put using namespace
inside a code block — inside curly braces {
and }
, as you would inside a function — the line applies only to lines that follow it within the same code block. Thus, in this case:
void cattleranch() {
using namespace Menagerie;
Cattle x;
}
void dairy() {
Cattle x;
}
the compiler will be happy with the first function, cattleranch()
but not with the second function, dairy()
. The using namespace
line is good only for the length of the cattleranch()
function; it’s inside that function’s code block.
using namespace Menagerie;
using namespace Ocean;
you can successfully refer to identifiers in both the Menagerie
and the Ocean
namespaces.
You can put variables in a namespace and then later refer to them through the namespace, as in the following:
namespace Menagerie {
int CattleCount;
}
And do it again later — for example, in your main()
— like this:
Menagerie::CattleCount = 10;
But remember: A namespace is not a class! Only one instance of the CattleCount
variable exists; it just happens to have a full name of Menagerie::CattleCount
. You can’t get away with creating multiple instances of Menagerie
because it’s a namespace. (Think of it like a surname: There could be multiple people named John, and to distinguish between them in a meeting at work, you might tack on their last names: John Squibbledash and John Poltzerbuckin.) Although the namespace name comes first in Menagerie::CattleCount
, it’s analogous to the last name. Two variables can be called CattleCount
: one in the Menagerie
namespace and one in the Farm
namespace. Their full names are Menagerie::CattleCount
and Farm::CattleCount
.
You can use only a portion of a namespace if desired. Using the Menagerie
namespace declared earlier in this section, you could do something like this outside the namespace:
using Menagerie::Oxen;
Oxen ollie;
(Notice that no namespace
word appears after using
.) The first line tells the compiler about the name Oxen
, and the second line creates an instance of Oxen
. Of course, if you have using namespace Menagerie
, the using Menagerie::Oxen
isn’t very useful because the Oxen
name is already available from the using namespace Menagerie
line.
To understand how one name becomes a part of two namespaces, consider the Namespace
example, shown in Listing 2-4.
LISTING 2-4 Pulling Names into Other Namespaces with the using Declaration
#include <iostream>
using namespace std;
namespace A {
int X;
}
namespace B {
using A::X;
}
int main() {
A::X = 2;
cout << B::X << endl;
return 0;
}
This code has two namespaces, A
and B
. The first namespace, A
, has a variable called X
. The second namespace, B
, has a using
statement that pulls the name X
into that namespace. The single variable that lives inside A
is now part of both namespaces, A
and B
. main()
verifies this: It saves a value in the X
variable of A
and prints the value in the X
variable of B
with an output of:
2
A::X
and B::X
refer to the same variable, thanks to the using
declaration!