Namespaces
Variants
Actions

Difference between revisions of "intro/classes"

From cppreference.com
(changed pagetitle to “Structs and Classes”)
(Construction: changed comments in code)
Line 99: Line 99:
 
     // auto p = point{1,2,3};
 
     // auto p = point{1,2,3};
 
      
 
      
     // always been possible, and still works,
+
     // always been possible, but dangerous
     // though now with zero-initialization:
+
     // now safe thanks to zero-initialization:
 
     point p1;
 
     point p1;
 
      
 
      

Revision as of 15:07, 9 October 2013


Let's assume we want to calculate the distance between two points in space; the formula for this is quite simple: Sum the squares of the distances in every dimension and take the square-root:

distance = sqrt(abs(x1-x2)² + abs(y1-y2)² + abs(z1-z2)²)

Since this is somewhat heavy to write every time, we'll use a function for that:

#include <iostream>
#include <cmath> // needed for sqrt() and abs()
 
double square(double number)
{
    return number * number;
}
 
 
double distance(double x1, double y1, double z1, double x2, double y2, double z2)
{
    auto squared_x_distance = square(std::abs(x1-x2));
    auto squared_y_distance = square(std::abs(y1-y2));
    auto squared_z_distance = square(std::abs(z1-z2));
    auto sum = squared_x_distance + squared_y_distance + squared_z_distance;
    return std::sqrt(sum);
}
 
int main()
{
    std::cout << "The points (0,1,2) and (4,1,0) have the distance "
        << distance(0,1,2,4,1,0) << '\n';
}

Output:

The points (0,1,2) and (4,1,0) have the distance 4.47214

The solution is working, but if we are honest, it isn't really nice: Passing the points into the function by throwing in six arguments is not only ugly, but also error-prone. Luckily C++ has solutions for this: Structs and classes. The biggest difference between these two are conventional, not technical, so we can look into them together.

A struct is basicaly a collection of values. In our example a point is represented by three doubles which even got implicit names: x, y, and z. So let's create a new type that is exactly that:

#include <iostream>
#include <cmath> // needed for sqrt() and abs()
 
struct point {
    double x;
    double y;
    double z;
};
 
double square(double number)
{
    return number * number;
}
 
double distance(const point& p1, const point& p2)
{
    auto squared_x_distance = square(std::abs(p1.x-p2.x));
    auto squared_y_distance = square(std::abs(p1.y-p2.y));
    auto squared_z_distance = square(std::abs(p1.z-p2.z));
    auto sum = squared_x_distance + squared_y_distance + squared_z_distance;
    return std::sqrt(sum);
}
 
int main()
{
    std::cout << "The points (0,1,2) and (4,1,0) have the distance "
        << distance(point{0,1,2}, point{4,1,0}) << '\n';
}

Output:

The points (0,1,2) and (4,1,0) have the distance 4.47214

Reducing six arguments to two, which in addition share semantics is clearly an improvement. It is obvious that the code got way cleaner.

Construction

Above we created our points by writing point{0,1,2}. This worked because point is an extremely simple structure. In general (we'll discuss the exact circumstances later) we need to implement the initialization ourself though.

Considering our current struct: Leaving variables uninitialized is evil and there is no exception for variables in structs and later on classes. So let's make sure, that they are zero, unless explicitly changed:

#include <iostream>
 
struct point {
    // this makes sure that x, y and z get zero-initialized 
    // at the construction of a new point:
    double x = 0.0;
    double y = 0.0;
    double z = 0.0;
};
 
int main()
{
    // no longer possible:
    // auto p = point{1,2,3};
 
    // always been possible, but dangerous 
    // now safe thanks to zero-initialization:
    point p1;
 
    // this is exactly the some as above:
    point p2{};
 
    std::cout << "p1: " << p1.x << '/' << p1.y << '/' << p1.z << '\n';
    std::cout << "p2: " << p2.x << '/' << p2.y << '/' << p2.z << '\n';
}

Output:

p1: 0/0/0
p2: 0/0/0

This works but we lose the great advantage of initializing a point with the values we want in a comfortable way. The solution to this is called a constructor. It is a special function that is part of a struct and is called when the object is created.

Let's create one that behaves like the one we had in the beginning:

#include <iostream>
 
struct point {
    // a constructor has neither returntype nor is it
    // possible to return a value from it. Aside from that,
    // it's name is identical with that of it's class:
    point(double x_arg, double y_arg, double z_arg)
    {
        // we can access all member-variables of the struct 
        // inside the constructor:
        x = x_arg;
        y = y_arg;
        z = z_arg;
    }
 
    double x = 0.0;
    double y = 0.0;
    double z = 0.0;
};
 
int main()
{
    // now these constructions work again:
    point p1{1,2,3};
    auto p2 = point{4,5,6};
 
    std::cout << "p1: " << p1.x << '/' << p1.y << '/' << p1.z << '\n';
    std::cout << "p2: " << p2.x << '/' << p2.y << '/' << p2.z << '\n';
}

Output:

p1: 1/2/3
p2: 4/5/6