functions

Objects and Functions in C++

Let’s take a look at constructor overloading, objects as function arguments, and returning objects from functions.

CDistance.h

#ifndef CDISTANCE_H
#define CDISTANCE_H

class CDistance
{
    public:
        CDistance() : _meters(0), _centimeters(0.0) {}
        CDistance(int meters, double centimeters) : _meters(meters), _centimeters(centimeters) {}

        void getDist();
        void showDist();
        voidd addDist(CDistance a, CDistance b);

    private:
        int _meters;
        double _centimeters;
};

#endif // CDISTANCE_H

Our overloaded constructors use initialization lists to specify what we want to set the member variables to.

The scope resolution operator, ::, is used to specify what class something is associated with. We use the scope resolution operator when defining member functions outside the class declaration.

CDistance.cpp

#include "CDistance.h"
#include <iostream>

void CDistance::getDist() {
    std::cout << "Please enter meters: ";     
    std::cin >> _meters;
    std::cout << "Please enter centimeters: ";     
    std::cin >> _centimeters;
}

void CDistance::showDist(){
    std::cout << _meters << " m " << _centimeters << " cm "; 
} 

void CDistance::addDist(CDistance a, CDistance b){     
        _centimeters = a._centimeters + b._centimeters;     
        _meters = 0;    
     if(_centimeters >= 100){
        _centimeters -= 100;
        _meters++;
    }
    _meters += a._meters + b._meters;
}

The syntax for function parameters that are objects is the same as that for arguments are are simple times such as int or char.

A special example of passing in an object as an argument is the default copy constructor. The default copy constructor performs a member-wise copy of one object’s values to another.

#include "CDistance.h"

using namespace std;

int main()
{
    CDistance dOne;
    CDistance dTwo(5, 50);
    CDistance dThree;


    cout << "First Distance: " ;
    dOne.showDist();
    cout << endl;

    cout << "Second Distance: " ;
    dTwo.showDist();
    cout << endl;

    //default copy constructor
    dOne = dTwo;

    dThree.addDist(dOne, dTwo);

    dTwo.addDist(dOne, dThree);

    cout << "First Distance: " ;
    dOne.showDist();
    cout << endl;

    cout << "Second Distance: " ;
    dTwo.showDist();
    cout << endl;

    cout << "Third Distance: " ;
    dThree.showDist();
    cout << endl;


    return 0;
}

 

Arrays and Functions in C++

Let’s design a function to add the elements in an array.

In C++, as in C, the array name itself is often treated as a de facto pointer. For our function to add the array, we will pass as the first argument the name of the array, and the second argument will be the int bounds of the array.

#include <iostream>

int sumArray(int iArray[], int iBounds);

using namespace std;

int main(void){

    int iNums[] = {4, 8, 15, 16, 23, 42};
    
    int iBounds = sizeof(iNums) / sizeof(int);
    
    cout << "The number of elements in the array is: " << iBounds << endl;
    
    cout << "The sum of the elements is: " << sumArray(iNums, iBounds) << endl;
    
    return 0;
}

int sumArray(int iArray[], int iBounds){
    int iSum = 0;
    for(int i = 0; i < iBounds; i++){
        iSum += iArray[i];
    }
    return iSum;
}

Again, in most contexts C++ treats the name of an array as if it were a pointer, specifically to the first element in the array. If we wish to use pointer syntax in the array’s parameter list, we may do so.

#include <iostream>

using namespace std;

//function overloading
//first function handles c-style strings
//second hands int arrays
void printArray(char *szString);
void printArray(int *iNums, int iBounds);


int main(void){

    char *szText = "El Psy Congroo.";
    char *szText2 = "";
    int iNums[] = {53, 88, 118, 389, 443, 546};
    
    //no need to use reference operator on
    //array indentifiers
    printArray(szText);
    printArray(szText2);
    printArray(iNums, sizeof(iNums) / sizeof(int)); 
    
    return 0;
}

void printArray(char *szString){
    while(*szString!=''){
        cout << *szString << " ";
        szString++;
    }
}

void printArray(int *iNums, int iBounds){
    int i;
    for(i = 0; i < iBounds; i++){
        cout << *(iNums+i) << " "; 
    }
}

We recall here that adding one to a pointer, which includes an array name in this context, in fact adds a value equal to the byte size of the type to which the pointer is pointing. Thus, pointer arithmetic and array subscription are equivalent methods of iterating through a array.

We must explicitly pass the size of the array to the function because sizeof() only yields the size of the pointer, and not the size of the entire array.

#include <iostream>

using namespace std;

void sizeOfArray(int iArray[]);
void sizeOfArray(double *dArray);

int main(void){

    double dArry[] = {4.55, 4.29, 19.99, 21.27};
    int iArry[] = {1138, 47, 8088, 64};
    
    cout << "size of double array in main(): " << sizeof(dArry) << endl;
    sizeOfArray(dArry);
    
    cout << "size of int array in main(): " << sizeof(iArry) << endl;
    sizeOfArray(iArry);
    
    return 0;
}

void sizeOfArray(int iArray[]){
    cout << "sizeof int array (in the user-defined function): " << sizeof(iArray) << endl;
}

void sizeOfArray(double *dArray){
    cout << "sizeof double array (in the user-defined function): " << sizeof(dArray) << endl;
}

Since a function with an array name argument has access to the original array, not a copy, we can use a function call to assign values to the arrays elements. We can use a loop to read successive values into the array, and then utilize a special flag value to indicate the end of input.

#include <iostream>

using namespace std;

int populateArray(int iArray[], int iBounds){
    int i, iValue;
    cout << "Fill the array with positive integer values." << endl;
    cout << "Up to " << iBounds << " separate entries allowed." << endl;
    for(i = 0; i < iBounds; i++){
        cout << "Enter value #" << i + 1 << " (Enter any negative number to finish): ";
        cin >> iValue;
        if(!cin){
            //bad input!
            cin.clear();
            while(cin.get()!='\n'){ continue; }
            cout << "Bad input, returning..." << endl;
            return i;
        }
        else if(iValue < 0){
            return i;
        }
        else {
            iArray[i] = iValue;
        }
    }
}

double arrayAverage(int iArray[], int iBounds){
    int iSum = 0;
    for(int i = 0; i < iBounds; i++){
        iSum += iArray[i];
    }
    return (double)iSum / iBounds;
}

int main(void){

    int iArray[50];
    int iBounds = populateArray(iArray, 50);
    double dAverage = arrayAverage(iArray, iBounds);
    
    cout << "Number of elements in the array: " << iBounds << endl;
    cout << "Average is " << dAverage << endl;
   
    return 0;
}

One problem for us to think about is the function to sum up the elements of the array. What if we accidentally modified the value passed in to the function? For elements passed in by value, it wouldn’t be such a big deal – only the local variable would be incorrect, and the problem would be contained. However, if we are dealing with a value passed in by reference, for example an array, any accidental change we make to it will be permanent.

To guarantee that a function won’t alter the value of the original data structure, we can safeguard the parameter using the keyword const when we define the array.

#include <iostream>

using namespace std;

void displayArray(const double dArr[], int n){
    for(int i = 0; i < n; i++){
        cout << "Element # " << (i + 1) << ": ";
        cout << dArr[i] << endl;
    }
}

void displayReversedArray(const double dArr[], int n){
    for(int i = n - 1; i >= 0; i--){
        cout << "Element # " << (i + 1) << ": ";
        cout << dArr[i] << endl;
    }
}


int main(void){

    double dArray[] = {72.91, 18.01, 54.25, 5.1, 5.48};
    
    displayArray(dArray, sizeof(dArray) / sizeof(double));
    displayReversedArray(dArray, sizeof(dArray) / sizeof(double));
    
    return 0;
}

If you get the chance, please take a look at my C programming book:

Functions in C++

A function definition is a function header followed by a statement or statements contained within curly braces. The function header declares the return type, the name of the function, and the parameter list. A function may have zero to multiple parameters.

A void function doesn’t return a value. A void function is declared by giving the return type of void.

A function is called by using its name in a statement.

#include <iostream>

using namespace std;

void printDoubleLine(){
    cout << endl << endl;
}

void printIntArgumentTwice(int iparamN){
    cout << iparamN << endl;
    cout << iparamN << endl;
}


int main(void){

    int i = 47;

    cout << "calling function printIntArgumentTwice()" << endl;

    printIntArgumentTwice(i);

    cout << "calling function printDoubleLine()" << endl;

    cout << "calling function printIntArgumentTwice() again" << endl;

    printIntArgumentTwice(4004);

    return 0;
}

Note that even when a function takes no arguments we still need to include the parentheses in the function header and when calling the function.

All C and C++ programs have at least one function, the main() function. This function accepts no arguments in our examples (although it can be specified to accept command line arguments) and returns an int value.

Functions that return a value must include a return statement. The return statement must return a value of the type specified in the function definition.

#include <iostream>

using namespace std;

string returnQuote(){
    string strQuote = "Human sacrifice, dogs and cats living together... mass hysteria!";
    return strQuote;
}

double returnDouble(){
    return 0.57721;
}


int main(void){

    string strOne = "I solemnly swear that I am up to no good.";
    string strTwo = returnQuote();
    
    cout << strOne << endl;
    cout << strTwo << endl;
    cout << returnDouble() << endl;

    return 0;
}

C++ supports passing arguments by value and by reference. By default, parameters are a copy of the values passed in as arguments. We can specify call by reference by placing an ampersand before the parameter’s name. When passing a value be reference, any modification made to the value will continue beyond the lifetime of the function, i.e. be permanent.

#include <iostream>

using namespace std;

void funcSwitchIntValues(int &i, int &j){
    int iTemp = i;
    i = j;
    j = iTemp;
}

int main(void){

    int iValueOne, iValueTwo;

    iValueOne = 1138;
    iValueTwo = 8088;

    cout << "iValueOne = " << iValueOne << endl;
    cout << "iValueTwo = " << iValueTwo << endl;
    cout << "calling funcSwitchIntValues()" << endl;

    funcSwitchIntValues(iValueOne, iValueTwo);

    cout << "iValueOne = " << iValueOne << endl;
    cout << "iValueTwo = " << iValueTwo << endl;


    return 0;

}

Having functions that accept parameters by reference enables the function to return multiple values. Whether or not that is a good thing is up to the programmer and the situation the function is being used in. Generally speaking, passing by reference does often help to optimize a program and conserve system memory.

Adding the qualifier const in front of a parameter that has accepted an argument by reference prevents the variable’s contents from being altered in the function, essentially the same as passing a pointer to a constant value, as in const int * constiPtr.

#include <iostream>

using namespace std;

struct SPerson{
    string firstName;
    string lastName;
    int age;
};


void funcInitializeSPerson(struct SPerson &Sparam){
    Sparam.firstName = "John";
    Sparam.lastName = "Doe";
    Sparam.age = 32;
}

void funcPrintSPerson(const struct SPerson &Sparam){
    cout << "First Name: " << Sparam.firstName << endl;
    cout << "Last Name: " << Sparam.lastName << endl;
    cout << "Age: " << Sparam.age << endl;
}

int main(void){

    struct SPerson Sx;

    funcInitializeSPerson(Sx);
    funcPrintSPerson(Sx);

    return 0;
}

For our next example, we will write a small program to count vowels. In the program, we will have one function call another function. As a general design principle, it’s best to have one function perform one task, even though the additional function calls might impose some extra overhead, it makes it easier to reuse code and debug problems.

#include <iostream>

using namespace std;

bool funcIsVowel(const char &c){
    return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u';
}

void funcConvertToLower(string &s){
    int i = 0;
    while(s[i]){
        s[i] = tolower(s[i]);
        i++;
    }
}

int funcCountVowels(string s){
    funcConvertToLower(s);
    int i = 0;
    int iReturnValue = 0;
    while(s[i]){
        if(funcIsVowel(s[i])){
            iReturnValue++;
        }
        i++;
    }

    return iReturnValue;
}

int main(void){
    
    string s = "The President has been kidnapped by ninjas. Are you a bad enough dude to rescue the President?";

    cout << "string " << s << " has " << funcCountVowels(s) << " vowels." << endl;

    return 0;

}