Modern C++:Efficient and Scalable Application Development
上QQ阅读APP看书,第一时间看更新

C++ Scoping of Variables

The compiler will compile your source files as individual items called translation units. The compiler will determine the objects and variables you declare and the types and functions you define, and once declared you can use any of these in the subsequent code within the scope of the declaration. At its very broadest, you can declare an item at the global scope by declaring it in a header file that will be used by all of the source files in your project. If you do not use a namespace it is often wise when you use such global variables to name them as being part of the global namespace:

    // in version.h 
extern int version;

// in version.cpp
#include "version.h"
version = 17;

// print.cpp
#include "version.h"
void print_version()
{
std::cout << "Version = " << ::version << std::endl;
}

This code has the C++ for two source files (version.cpp and print.cpp) and a header file (version.h) included by both source files. The header file declares the global variable version, which can be used by both source files; it declares the variable, but does not define it. The actual variable is defined and initialized in version.cpp; it is here that the compiler will allocate memory for the variable. The extern keyword used on the declaration in the header indicates to the compiler that version has external linkage, that is, the name is visible in files other than where the variable is defined. The version variable is used in the print.cpp source file. In this file, the scope resolution operator (::) is used without a namespace name and hence indicates that the variable version is in the global namespace.

You can also declare items that will only be used within the current translation unit, by declaring them within the source file before they are used (usually at the top of the file). This produces a level of modularity and allows you to hide implementation details from code in other source files. For example:

    // in print.h 
void usage();

// print.cpp
#include "version.h"
std::string app_name = "My Utility";
void print_version()
{
std::cout << "Version = " << ::version << std::endl;
}

void usage()
{
std::cout << app_name << " ";
print_version();
}

The print.h header contains the interface for the code in the file print.cpp. Only those functions declared in the header will be callable by other source files. The caller does not need to know about the implementation of the usage function, and as you can see here it is implemented using a call to a function called print_version that is only available to code in print.cpp. The variable app_name is declared at file scope, so it will only be accessible to code in print.cpp.

If another source file declares a variable at file scope, that is called app_name, and is also a std::string the file will compile, but the linker will complain when it tries to link the object files. The reason is that the linker will see the same variable defined in two places and it will not know which one to use.

A function also defines a scope; variables defined within the function can only be accessed through that name. The parameters of the function are also included as variables within the function, so when you declare other variables, you have to use different names. If a parameter is not marked as const then you can alter the value of the parameter in your function.

You can declare variables anywhere within a function as long as you declare them before you use them. Curly braces ({}) are used to define code blocks, and they also define local scope; if you declare a variable within a code block then you can only use it there. This means that you can declare variables with the same name outside the code block and the compiler will use the variable closest to the scope it is accessed.

Before finishing this section, it is important to mention one aspect of the C++ storage class. A variable declared in a function means that the compiler will allocate memory for the variable on the stack frame created for the function. When the function finishes, the stack frame is torn down and the memory recycled. This means that, after a function returns, the values in any local variables are lost; when the function is called again, the variable is created anew and initialized again.

C++ provides the static keyword to change this behavior. The static keyword means that the variable is allocated when the program starts just like variables declared at global scope. Applying static to a variable declared in a function means that the variable has internal linkage, that is, the compiler restricts access to that variable to that function:

    int inc(int i) 
{
static int value;
value += i;
return value;
}

int main()
{
std::cout << inc(10) << std::endl;
std::cout << inc(5) << std::endl;
}

By default, the compiler will initialize a static variable to 0, but you can provide an initialization value, and this will be used when the variable is first allocated. When this program starts, the value variable will be initialized to 0 before the main function is called. The first time the inc function is called, the value variable is incremented to 10, which is returned by the function and printed to the console. When the inc function returns the value variable is retained, so that when the inc function is called again, the value variable is incremented by 5 to a value of 15.