C++: Passing objects by value to a member function

2019-07-01 16:50发布

I'm a beginner in C++ and I've just started learning about OOP. In the following program I've added objects of the same classes and displayed the result. However, I'm not able to understand the fact that if I pass the objects to the function by value then how is the change reflected in the calling function. The addNumbers() function expects two objects of the class Complex and the object which is used to invoke the function (c3.addNumbers(c1, c2)) is implicitly passed to the function but how are the values of c3.real and c3.imaginary affected in the calling function since addNumbers() has no access to their "location" in the memory. Any help will be appreciated!

Thanks in advance!

class complex {
private:
    int real;
    int imaginary;

public:
/* Using member initializers to assign values to members */    
    complex()
        : real(0)
        , imaginary(0)
    {}

    void readData(int x, int y);

    void printData();

    void addNumbers(complex, complex);
};     

void complex::readData(int x, int y)
{
    real      = x;
    imaginary = y;
}

void complex::printData()
{
    cout << real << "+" << imaginary << "i" << endl;
}   

void complex::addNumbers(complex c1, complex c2)
{
    real      = c1.real + c2.real;
    imaginary = c1.imaginary + c2.imaginary;
}

int main(void)
{
    complex c1, c2, c3;
    c1.readData(-5,17);
    c2.readData(11,7);
    c3.addNumbers(c1,c2);
    c3.printData();

    return 0;
}

3条回答
女痞
2楼-- · 2019-07-01 17:26

I made a few comments in your original code to explain why real and imaginary are affect below. (Look for //MABVT)

In addition: I will provide another useful example for you to progress further!

REVIEW

class complex {
private:
    int real;
    int imaginary;

public:
    /* Using member initializers to assign values to members */    
    complex()
        : real(0)
        , imaginary(0)
    {}

    void readData(int x, int y);

    void printData();

    // MABVT: You provide two complex numbers which you want to add 
    //        together!
    void addNumbers(complex, complex);
};     

void complex::readData(int x, int y)
{
    real      = x;
    imaginary = y;
}

void complex::printData()
{
    cout << real << "+" << imaginary << "i" << endl;
}   

void complex::addNumbers(complex c1, complex c2)
{
    // MABVT: Use c1.component and c2.component, add them up and store them 
    //        in this class' instance.
    real      = c1.real      + c2.real;
    imaginary = c1.imaginary + c2.imaginary;

    // MABVT: c3.real and c3.imaginary are affected at this exact location
    //        since you overwrite the values with the addition-results.
    //        Since the function addNumbers(complex, complex) is invoked
    //        on the complex instance 'c3', real and imaginary of c3 are 
    //        known in this context, and consequently you can use them.
    //
    //        To attach to your statement that the c3 instance's pointer is 
    //        implicitly passed: 
    //        Yes it is passed as the first parameter invisibly as 
    //         'complex* this'
    //
    //        So you could also write:
    //          this->real = c1.real + c2.real; (see the use of this?)
}

int main(void)
{
    complex c1, c2, c3;
    c1.readData(-5,17);
    c2.readData(11,7);
    c3.addNumbers(c1,c2);
    c3.printData();

    return 0;
}

ALTERNATIVE

// Example program
#include <iostream>
#include <string>

class Complex { // Give class names capital first letter
private:
    int m_real;      // Just a recommendation: I'd like to be able to distinguish parameter for member in the identifier already!
    int m_imaginary; // Just a recommendation: I'd like to be able to distinguish parameter for member in the identifier already!

public:
    /* Using member initializers to assign values to members */    
    inline Complex()   // Inline it, if you define this class in a header and reuse it multiple times...
        : m_real(0)
        , m_imaginary(0)
    {}

    // Provide initializing constructor to be able to construct 
    // a complex number quickly. Replaces your readData(...);
    inline Complex(
        int inRealPart,
        int inImaginaryPart)
        : m_real(inRealPart)
        , m_imaginary(inImaginaryPart)
    {}

    // Getters to read the values
    inline int real()      const { return m_real; }
    inline int imaginary() const { return m_imaginary; }

    void printData();

    // Local assignment-add operator to add another complex
    // to this specific instance of complex and modify the internal
    // values. Basically what you did as the second part of addNumbers.
    Complex& operator+=(const Complex& r);
};     

void Complex::printData()
{
    std::cout << m_real << "+" << m_imaginary << "i" << std::endl;
}   

// Member add-assign operator definition adding this instance and another instance 'r' by adding up the values and storing them in the instance this operator is called on.
Complex& Complex::operator +=(const Complex& r) 
{ 
    std::cout << "Local" << std::endl;

    this->m_real      += r.real();
    this->m_imaginary += r.imaginary();

    return *this;
}

// Static global operator+ definition, taking two values and creating a 
// third, NEW one initialized with the results.
// This was the first part of addNumbers
static Complex operator+(const Complex& l, const Complex& r) { 
   std::cout << "Static Global" << std::endl;

   return Complex(
            (l.real()      + r.real()), 
            (l.imaginary() + r.imaginary())
          );
}

int main(void)
{ 
    // Same as before
    Complex c1(-5, 17);
    Complex c2(11, 7);
    Complex c3(1, 2);

    // Test output
    c1.printData();
    c2.printData();
    c3.printData();

    std::cout << std::endl;

    Complex  c3 = (c1 + c2);           // Calls static global and c3 is overwritten with the result. Exactly like your addNumbers call
    c1 += c2;                          // instance local, will change c1's internal values ( see print out below )
    Complex  c5 = ::operator+(c1, c2); // Static global, c5 is initialized with the result. Exactly like your addNumbers call

    std::cout << std::endl;

    c1.printData();
    c2.printData();
    c3.printData();
    c5.printData();

    return 0;
}

This should be quite much for you as a beginner.

Some explanation

Static global vs. local operator overloads

Reading on the topic: http://en.cppreference.com/w/cpp/language/operators

All the operators you use (+, -, *, /, %, +=, -=, ...) are just functions, which are predefined for primitive types and provided by libstd for STD types.

You can override/define them though.

I did that in two ways:

Static global operator+:

Accepts two arbitrary Complex instances and adds their components. Finally a NEW instance is created and initialized with the results.

Basically this is just a static function, which is linked to "+" by the compiler.

And:

Local member operator+=:

Accepts another instance of Complex and adds its component values to the component values of the instance the operator is called on: `l += r -> Called on l, whose values will be modified by adding the values of r'

All op-assignment operators (+=, -=, *=, /=, etc...) must be defined within the class and can neither be global, nor static.

const Type&

Reading with much more info on const: https://www.cprogramming.com/tutorial/const_correctness.html

Const reference to instances of whatever type will ensure two things for you:

  1. &: You only copy the address, but this way you function could change all public values or call most functions.
  2. const: The instance is not modifyable and nothing can be changed

In combination that means: You don't have to copy the instance (pass-by-value), but provide it's address reference only (pass-by-reference). Usually that enhances performance, especially once you pass around large and complex objects.

查看更多
Root(大扎)
3楼-- · 2019-07-01 17:35

When you call c3.addNumbers(c1, c2)), addNumbers receives implicitely the pointer to c3 not a copy of c3. This pointer can be used explicitely with the this keyword.

So your function can be rewritten like this:

void complex::addNumbers(complex c1, complex c2)
{
    this->real      = c1.real + c2.real;
    this->imaginary = c1.imaginary + c2.imaginary;
}

which is strictly equivalent to your original addNumbers function.

In other words: each time you use a class member inside a member function, an implicit this-> is prepended to that member; so if member is a class member, then member is always equivalent to this->member inside a class member function.

查看更多
Animai°情兽
4楼-- · 2019-07-01 17:41

The imaginary and the real are private properties, but they can be accessed through the member function (also known as the object's method). When the c3.addNumbers (c1, c2) statement is executed, it will be equivalent to the following two statements:

    c3.real = c1.real + c2.real;

    c3.imaginary = c1.imaginary + c2.imaginary

The reason we can access c3.real and c3.imaginary is because the addNymbers () function is a member of the Complex class.

查看更多
登录 后发表回答