You are here:

C++/passing values through a function

Advertisement


Question
Hello Mr. McArdell, I am an amatuer C++ programmer and I am having some difficulty grasping the concepts of passing values through a function.
Basically here's the situation in code.

function1{
global_str[global_int][0]=ABC;
global_str[global_int][1]=abc;
global_int++;
global_str[global_int][0]=DEF;
global_str[global_int][1]=def;
}

function2{
global_int++;
global_str[global_int][0]=GHI;
global_str[global_int][1]=ghi;
global_int++;
global_str[global_int][0]=JKL;
global_str[global_int][1]=jkl;
}

main{
int global_int=0;
string global_str[256][1];
int i=0;

function1();
function2();

for ( i = 0 ; i < global_int < i++ ) {
cout << global_str[i][0];
cout << global_str[i][1];
}

}

and so i expect the results...
ABCabcDEFdefGHIghiJKLjkl

help me out, thanks!

Answer
Well, you have several misconceptions in the code you posted.

First, it seems you have not checked out the difference between a C++ entity name - such as a type like int or an object (that is variable) such as i or global_int and literal string values such as "GHI" or "def" - notice the double quote characters around these values. Note that single character literals values (of type char) use only a single quote - such as 'a' or '='.

Secondly, functions in C++ _must_ specify a return type - if nothing is to be returned as in function1() and function2() then you specify a void return type. The return type of main is always int:

void function1()
{
//...
}

void function2()
{
//...
}

int main()
{
//...
}

Thirdly, you seem to have forgotten the difference between declaring the sizes of array dimensions and the maximum index value of that dimension (which is the size less 1) for the second dimension of global_str[256][1] - this is an array of 256 by 1 elements - the second dimension only having one valid index value, 0. I think you meant global_str[256][2].

Next we come to the issues of object (variable) scope. The objects you call global_int and global_str are in fact local objects - they are defined within the scope of a function and are only visible as a name until that scope closes with the closing brace of the function. In addition these objects are automatic objects that are defined on the stack-frame of the function call and are destroyed when they go out of scope within the function call.

So both global_int and global_str can be used as a name only within main and they only exist for the life time of the function call to main.

int main
{
       int global_int=0;      // name global_int declared and
         // object global_int defined,
         // created and initialised to 0

       string global_str[256][2];  // name global_str declared and
         // object global_str defined,
         // created and initialised using
         // array construction which calls
         // the default constructor of
         // string setting each to an empty
         // string


       // ...

} // global_str destroyed: each element has its destructor called
 // cleaning  up the string resources - such as the memory for the
 // string.

 // global_int destroyed calling the destructor for int types - which
 // does nothing.

You can restrict an object's life time and visibility further within a function by defining it within a set of braces:

       void  Func
       {
       // i not accessible here
         {
         int i(0);

         // i can be used here.

         } // i goes out of scope here, is destroyed and
         // is no longer accessible by name

       // i not accessible and no longer exists here
       }

Of course it is not too common you just add braces within a function like this - although the one use is to create a new scope for objects. It is more common to localise objects to a section of function such as a conditional section or loop body:

       void  Func
       {
         if ( state == some_state )
         {
         int i(0);

         // i can be used here.

         } // i goes out of scope here, is destroyed and
         // no longer accessible by name

       // i not accessible here
       }

Notice also that I used the C++ style for initialising objects - using the so called constructor call syntax - to initialise i to zero.

So if that is not the way to make an object global what is? And should you do so?

To answer the second question first - no. The use of global objects is frowned upon. They cause coupling between code that could otherwise be separated and can become a maintenance nightmare if overused. Of course occasionally you really, really, really, require a global variable - so you use one. But in most cases they just are not required and alternative, and often better, solutions are available.

Now how do you actually go about defining a global variable. Well you place its definition outside of any function. Notice that for anything else to see it the name must have been declared before it is used - luckily for you defining an object also declares it. As both your function1 and function2 use global_int and global_str they should obviously be declared before either function uses them:

int global_int=0;
string global_str[256][2];

void function1()
{
//...
}

void function2()
{
//...
}

int main()
{
//...
}

This places both global_int and global_str in the global namespace for the module and gives them external linkage - which means they can be used from other modules because they will be visible to the linker. They live through the lifetime of any one function call. In fact they will exist for the lifetime of the execution of the program.

Other modules wishing to access them must _not_ define them again. Defining an object does not just specify its name and other attributes such as type but also means storage will be set aside for it. They do not wish new versions of global_int and global_str, they just wish to use the existing ones defined in your module. In fact the standard makes it illegal to define them again as each entity must be defined exactly once. To do this they declare them, which for global objects means specifying the names as external using the extern keyword:

// some_other_module.cpp

extern int global_int;
extern string global_str[256][1];

To prevent global_int and global_str being seen by the linker (and therefore other modules) you can specify them as static which gives them internal linkage only - i.e. they can be seen through out the whole of the module they are defined in (from the point of declaration to the end of the source file).

These features C++ inherits from C. In C++ we have an alternative to specifying global objects as static to restrict their usage. C++ has the concept of namespaces - we can place a name within a certain namespace and then qualify such names with the namespace they are declared in:

namepace n
{
       int i;
}

namespace nn
{
        int i;
}

Note that namespaces are always defined outside of functions.

Now we have two names i - but they are different as we have n::i and nn::i. i on its own would not match either n::i or nn::i. A good example of the use of a namespace is the C++ standard library in which almost all names of things are in the std namespace - so in your code you should use names such as std::cout and std::string. However there are a couple of reasons why you can use the unqualified names cout and string as you do. First, maybe you are using an old C++ compiler and / or library implementation which predates the use of std or the compiler is being run in a mode which places such names in the global namespace (i.e. the namespace that might be thought of a no namespace!). Secondly, your program explicitly brought the names into the global namespace by using a using declaration or directive:

   // Using declarations:
       using std::string;
       using std::cout;


   // Using directive:
       using namespace std;

A using declaration will bring one name into the current namespace at a time (the global namespace in this case). A using directive will bring all the currently seen names for a whole namespace into the current namespace. I say currently seen as only names the compiler has seen can be processed - so any names not presented to the compiler - e.g. because they are declared in a header file that has not been included in you code - are not brought into the current namespace.

The namespace feature of interest here are anonymous namespaces - ones without a name. Each module can have its own namespace with no name. All the names in it are accessible to that module just like other names. Names of global objects defined in an anonymous namespace have external linkage as one would expect. However when the compiler generates such names for use by the linker it is as if a random unique namespace name is used to qualify such names - so effectively no other module can use them as they would need to specify this unknown unique name. So the preferred C++ way to define global objects for use only within the current module is to use an anonymous namespace:

namespace
{
       int global_int=0;
       string global_str[256][2];
}

void function1()
{
//...
}

void function2()
{
//...
}

int main()
{
//...
}

Now as I previously pointed out using global data is not a good thing, and in this case it can easily be avoided and brings us to the topic of your question: passing data between functions. There are two features of functions that allow data to be passed between function calls. One I touched on previously when talking about the return type. The return type implies there is a returned value and indeed this is the case. In many functions the returned value is obvious from their use for example maths functions such as sin as cos you expect to return floating point value (in fact I think you will find it is a double floating point type). main always returns an int to the execution environment which may indicate a failure value and which might be available for example to command shells and scripts. main is odd in that you can omit the return statement and it is taken to be understood that the returned value is 0. Notice that this applies only to main and no other function.

The other way to pass data into or out of a function is to use function parameters, and this is what we are interested in here. You place the arguments for a function in the parentheses following the function name - such as cos(0.1234). When defining or declaring a function information on these arguments has to be specified - this information is the type and name of each argument:

   // Possible function declaration (a.k.a. function prototype)
   // for cos:
       double cos( double angle );

   // cos function definition:
       double cos( double angle )
       {
       // ...
       }

Note that as with other named entities a function definition is also a function declaration. However as with all things in C++ there can only be one function definition. Functions are like global objects - they have external linkage unless specified a static - this is also a inherited from C. Note that unlike global objects their declarations do _not_ use the extern keyword and are sometimes referred to as function prototypes.

So what about your functions function1 and function2?

Well you need to pass in and out the array of strings and you need to pass in and out the value of the current index.

To pass values in we can use pass by value parameters as with cos above. This means that we pass in the value of the object and not the object itself. In fact what happens in pass by value is that a copy is made of the object passed in - so the double parameter called angle when in the cos function is passed in as if like so:

double callers_angle(0.1234);

double cos_angle = callers_angle;

So the angle parameter to cos is a totally separate object to that which was passed in by the caller but which is initialised to the same value as that passed in by the caller. This means that any changes to the angle parameter with a function call to cos does _not_ change the value passed in.

Obviously this is no good to us in this case - we want the values we change in the functions to be changes made to the objects passed in so they are present in the calling function (i.e. main). So to pass values back out again using function parameters we use pass by reference. With pass by reference we pass a reference to the object passed to the function and any changes to the object through the reference are made to the object passed in - just what we want. Now in C and C++ arrays are always (effectively) passed by reference. In C++ we can pass a value such as an int by reference by specifying the parameter type as a reference to an int rather than as an int. To do this we change the type from int to int & - & here meaning reference to and the type following reference to is in fact the type that precedes the & so int & reads as reference to int. This makes your function1 and function2 declarations look as follows (I'll just do the declarations for the moment - we'll get to the definitions in a bit!):

       void function1( std::string str[256][2], int & index );
       void function2( std::string str[256][2], int & index );

Now of course you have to fix up the definitions to use str and index, here is the definition for function1:

       void function1( std::string str[256][2], int & index )
       {
         str[index][0] = "ABC";
         str[index][1] = "abc";
         index++;
         str[index][0] = "DEF";
         str[index][1] = "def";
       }

Of course another way might be to pass the start index in and return the next unused index:

       int function1( std::string str[256][2], int index )
       {
         str[index][0] = "ABC";
         str[index][1] = "abc";
         ++index;
         str[index][0] = "DEF";
         str[index][1] = "def";

         return ++index;
       }

Here I return ++index - which uses pre-increment to add one to index _before_ returning its value. I now pass index by value as an int (not an int & as before) and made function1 return an int. So now if you pass in an index of 0 then 2 should be returned as the next available index. This value can be passed directly into function2 as its starting index, providing we modify function2 in a similar way:

       int end(function2( global_str, function1(global_str, 0)) );

So here is the final version of you program. I have also changed the names of some of the objects to be a bit more meaningful:

// Note no global data

// Note revised declaration and definition
// Note correct dimensions and std qualification on C++ name string
int function1( std::string str[256][2], int index )
{
// Note quoted string literals
       str[index][0] = "ABC";
       str[index][1] = "abc";
       ++index;
       str[index][0] = "DEF";
       str[index][1] = "def";
       return ++index;
 }

// Note revised declaration and definition
// Note correct dimensions and std qualification on C++ name string
int function2( std::string str[256][2], int index )
{
// Note quoted string literals
       str[index][0] = "GHI";
       str[index][1] = "ghi";
       ++index;
       str[index][0] = "JKL";
       str[index][1] = "jkl";
       return ++index;
}

int main(
{
// Note possibly more meaningful name and correct dimensions
// Note std namespace qualification of C++ library name string
       std::string letter_triples[256][2];

// Initialise end index value from return value of function2:
// Remember function1 and function2 now return the next available
// index which is one past end of the number of used indices.
// Again we pass the return value from function1 straight into
// function2 as its starting index, and pass 0 as the starting index
// for function 1.
       int end( function2( letter_triples
         , function1(letter_triples, 0)
         )
         );

// Loop from 0 until index reaches (one past the) end. Note
// preference for pre-increment when either ++index or index++
// can be used this not important here but can have performance
// ramifications in other code, so is a good habit to adopt.
// Note use of != instead of <. Again not important here but is
// important with C++ library container iterators so is also a good
// habit to adopt.
       for ( int index(0); index != end; ++index )
       {
       // Note std namespace qualification of C++ library name cout
       // Note outputting more than one value to std::cout in one statement
         std::cout << letter_triples[index][0]
         << letter_triples[index][1];
       }
}


There is much more I could say about the topics touched on here and many others I left out but I think that is enough for now for both of us. I suggest you get yourself a good set of C++ learning resources:

For absolute beginners there is a new book called "You Can Do It - A Beginner's Introduction to Computer Programming" by Francis Glassborow and Roberta Allen. I have not read this book personally but the reviews I have read have been very positive - noting only a few flaws - much better than most reviews I have read for beginner's C++ or programming books!

If you do have some prior programming experience then you might like to try "Accelerated C++" by Andrew Koenig and Barbara Moo - again I have not read this book, but it is often sited as a good place for people to start in with C++ if they have done some programming before.

As to other sources of information. A good place to start is the ACCU site at http://www.accu.org - they have a book reviews section and resource links as well as mentored development areas for members.

You might like to look at the C++ links at:

http://www.fz-juelich.de/zam/cxx/extern.html#ezine

However I cannot vouch for the quality of anything you find there - some I know of such as the "The On-Line C++ FAQ" by Marshall Cline on the parashift site, as it is the first place you are directed to before posting a question on the comp.lang.c++.moderated newsgroup.

Hope this helps and good luck.  

C++

All Answers


Answers by Expert:


Ask Experts

Volunteer


Ralph McArdell

Expertise

I am a software developer with more than 15 years C++ experience and over 25 years experience developing a wide variety of applications for Windows NT/2000/XP, UNIX, Linux and other platforms. I can help with basic to advanced C++, C (although I do not write just-C much if at all these days so maybe ask in the C section about purely C matters), software development and many platform specific and system development problems.

Experience

My career started in the mid 1980s working as a batch process operator for the now defunct Inner London Education Authority, working on Prime mini computers. I then moved into the role of Programmer / Analyst, also on the Primes, then into technical support and finally into the micro computing section, using a variety of 16 and 8 bit machines. Following the demise of the ILEA I worked for a small company, now gone, called Hodos. I worked on a part task train simulator using C and the Intel DVI (Digital Video Interactive) - the hardware based predecessor to Indeo. Other projects included a CGI based train simulator (different goals to the first), and various other projects in C and Visual Basic (er, version 1 that is). When Hodos went into receivership I went freelance and finally managed to start working in C++. I initially had contracts working on train simulators (surprise) and multimedia - I worked on many of the Dorling Kindersley CD-ROM titles and wrote the screensaver games for the Wallace and Gromit Cracking Animator CD. My more recent contracts have been more traditionally IT based, working predominately in C++ on MS Windows NT, 2000. XP, Linux and UN*X. These projects have had wide ranging additional skill sets including system analysis and design, databases and SQL in various guises, C#, client server and remoting, cross porting applications between platforms and various client development processes. I have an interest in the development of the C++ core language and libraries and try to keep up with at least some of the papers on the ISO C++ Standard Committee site at http://www.open-std.org/jtc1/sc22/wg21/.

Education/Credentials

©2016 About.com. All rights reserved.