Java程序员 - C ++人如何使用类?指向类,默认参数的指针?

时间:2009-02-19 10:38:35

标签: c++ pointers constructor

我了解面向对象编程的方法,但我已经习惯了Java和我 直到最近才接触过C ++。

我认为我的问题与语法无关,而与哲学有关 C ++中的OOP。我理解指针和地址之间的区别 堆栈和堆,以及东西,但我仍然觉得我错过了什么。

这是一个例子:我有一个包含一些数据的类(Shape)。我还有一个 class(App)使用了许多Shapes。

class Square {
    private: 
        int x;
        int y;
        int size;

    public:
        /* constructor */
        Square(int x, int y, int size);
}


class App {
    private:
        Square redSquare;
        Square blueSquare;

    public:
        void setup();
        void draw();
}

在某些时候,某些事情将实例化我的应用程序并调用setup()。问题在于,当我声明App类(在App.hpp中,比方说)时,“redSquare”和“blueSquare”被实例化,而不仅仅是声明。作为一名Java程序员,我将在本例中在setup()中实例化我的类。但是这意味着我不能像上面那样做,我必须将redSquare和blueSquare设置为POINTERS,然后我可以在setup()中使用new创建它们。

但是你会怎么做呢?或者你会使用默认的构造函数 参数,如上创建redSquare和blueSquare,然后设置 App.setup()中这些方块的值,使用类似的东西 Square.init(x,y,size)还是什么?或者其他一些方式?

您是聚合类还是仅指针?

我当然可以通过这种方式或其他方式进行破解,因此它可以自己运行,但我有一个 我觉得我在做“Java方式”并没有得到C ++程序员的想法。

12 个答案:

答案 0 :(得分:6)

在我看来,问题来自于“setup()”方法的存在。 IIUC,意图是使用App作为:

App a;
a.setup();

在调用setup()之前,状态是“a”?如果调用draw()会发生什么?如果答案类似于“在使用App对象完成任何操作之前必须调用setup()”,那么这意味着setup()是App的“真正”构造函数,而App的当前ctor是无意义的。结论:删除“setup()”方法并将其替换为有意义的ctor。

答案 1 :(得分:2)

您需要在App构造函数中为构造函数指定参数,例如

App(int bx, int by, bsize, int rx, int ry, int rsize) : 
blueSquare(bx, by, bsize), redSquare(rx, ry, rsize) {}

这只是一个例子,你可能会使用更好的设计IRL。

答案 2 :(得分:2)

给app一个构造函数:

App :: App() 
    : redSquare( 10, 20, 100 ), blueSquare( 50, 500, 25 ) {
}

答案 3 :(得分:2)

首先,如果在setup()之前调用draw(),则需要选择App类的行为方式:

  1. 如果您希望默认使用一些预定义的方块,请在App初始化列表中初始化它们。

  2. 如果您不希望这种情况发生,请使用指针并防止这种情况发生。

  3. 关于2的一些注释:

    • 您没有C / C ++中的垃圾收集器,您必须“删除”您的新内容
    • 您可以使用"smart pointers" to have RAII(资源分配是初始化),这将使内存管理更简单
    • 您应该使用Null Design Pattern来避免许多'if(redSquare == NULL)'块。
    • 在任何情况下,将App的初始化列表中的指针初始化为NULL或空方对象。

答案 4 :(得分:1)

Square类有一个带3个参数的构造函数。 App类没有定义构造函数 - 这意味着编译器将提供默认构造函数(不带任何参数的构造函数)。在类的默认构造函数中,所有成员变量都由默认构造函数初始化。由于Square类没有默认构造函数,因此您的代码将无法编译。

构造函数应该使用其成员变量的默认值初始化一个类实例。如果您需要更改任何这些变量,那么您可以定义一些getter和setter:

class Square {
    private: 
        int x;
        int y;
        int size;

    public:
        /* constructor */
        Square(int x, int y, int size);

        int getX() const {return x;}
        int getY() const {return y;}
        int getSize() const {return size;}

        void setX(int nx) {x = nx;}
        void setY(int ny) {y = ny;}
        void setSize(int s) {size = s;}
}

然后调用这些函数来改变Square的内部变量。

答案 5 :(得分:1)

我会使用Square *而不是Square作为App的私人成员。这样就可以保存内存,App中Square对象的构造可以在需要时延迟到,或者可以在App的构造函数中完成,默认或其他方式。 当然,如果创建了两个Square对象,析构函数应该销毁它们。

答案 6 :(得分:1)

您应该使用指针而不是实际对象。

使用指针将使您的类与Java类似(它将强制您在“setup”方法或构造函数上显式实例化它)。在Java中,每个对象都是一个引用(与指针类似{但不等于}),并且没有与使用C ++完成的内容相同:

class A {
   B b;
}

希望它有所帮助。

答案 7 :(得分:1)

为什么使用setup / init功能?使用构造函数,初始化对象是构造函数的 raison d'être。可能存在于不可用状态的对象是PItA,它们需要每个函数来测试我们是否可以对它们做任何好事。加强他们的不变性并禁止他们存在,直到你能够完全构建它们为止。

即使在Java中,init / setup函数对我来说也是一个值得怀疑的做法。

答案 8 :(得分:0)

使用指针,或使App构造函数采用上面建议的参数。

(in App.h)
Square* redSquare;
Square* blueSquare;
void setup(int,int,int,int);
...

(in App.cpp)
void App::setup(int x1,int y1,int x2,int y2){
    redSquare=new Square(x1,y1);
    blueSquare=new Square(x2,y2);   // don't forget to "delete" these later
    ...
}

答案 9 :(得分:0)

我会让代码说话。 警告:未经测试的代码!

/**
    Typically we will use Shape as a base class.
    This defines the interface that clients of the
    Shape class can expect and use reliably. Of
    course, some shapes can be dough-nuts and have 
    a eat() method, but that's just them dough-nuts.
*/
class Shape
{
public:
    /**
        The base class's default ctor.
        The compiler generates one if you don't specify any ctors,
        but if you specify at least one, this is skipped, so we 
        to mention it explicitly
    */
    Shape(void) {} 
    Shape(const std::string& c) { std::cout << c.length() << std::endl; } /* print the color */
    ~Shape(void) {}

    /**
        The interface. The '=0' makes it virtual. This class
        has now become abstract. Subclasses MUST define this
        member function.
    */
    virtual size_t area() = 0 { return 0; }
};

/**
    The more interesting objects, real world shapes.
    They have their own version of implementing an
    algorithm to find out area.
*/
class Circle : public Shape
{
public:
    Circle(const std::string& color) /* no default parameters */
         /* the base class's default ctor is called automagically */
        :_color(color) /* the user must specify the color */        
    {}
    ~Circle(void) {}

    virtual size_t area() /* = 0 */ /*we dont want this to be abstract! */
            { return 42; }

private:
    std::string _color;
};

class Triangle: public Shape
{
public:
    Triangle(const std::string& color = "red") 
        Shape(color)/* this base class ctor has to be called by you */
        : _color(color) /* use initializer lists to populate ctor parameters */
    {}
    ~Triangle(void) {}

    virtual size_t area() { return 42 + sizeof Triangle; }
private:
    std::string _color;
};

/**
    We will also use design patterns. And an oft-abused 
    one -- the Singleton. We want only one app to run after
    all.
*/
class App {
public:
    /**
        This would typically be in a differnt '.cpp' file.
    */
    int Run() {
        /* create a few shapes */
        Triangle t1;
        Circle c1; // error -- no default parameter
        Circle c2("green");

        /* You want to sum the areas of all these objects */
        size_t total_area = t1.area() + c2.area();

        /* Now, how do you process different shapes in one go? */
        /* Put them in a container for base class pointers */
        std::list<Shape *> shape_list;
        shape_list.insert(&t1); 
        shape_list.insert(&c2);

        /* process them -- calculate total area differently */
        size_t total_area2 = 0;
        std::list<Shape *>::iterator f = shape_list.end();
        for (std::list<Shape *>::iterator i = shape_list.begin();
            i != f;
            ++i) {
            total_area2 += i->area(); 
            /* see why we need virtual functions? */
        }

        /* or use the STL to the hilt and do */
        size_t total_area3 = std::accumulate(shape_list.begin(), shape_list.end(), 
            0, 
            std::bind2nd(std::plus<size_t>(std::mem_fun(&Shape::area))));


    }
private:
    App();
    App(const App&);
    App& operator(const App&);
};

/* the program entry-point, you have to have one, and only one */
int main() {
    App.Run();
}

答案 10 :(得分:0)

如果您的 setup()确实没有参数并且没有返回任何值,那么将该功能放在构造函数中(我假设您只是为了简洁而留下了这些 )。

有一个 setup()函数,必须在构造函数不是最佳之后调用,但在构造函数中抛出异常时可能完全有效不是一个选项。您的 setup()功能可以更灵活的方式处理问题。

但是:不要让构造函数将对象保留在稍后会导致问题的状态,特别是:

  • 初始化所有指针成员变量!!通常,您应该将所有内容设置为一些有意义的默认值。在绝对最小值处有一个保护位来确定 setup()是否已完成并处理每个情况下它可能会咬你。
  • 在每个需要调用 setup()的函数中,确保它已经拥有或调用它。
  • 当然,在析构函数中释放所有分配!

换句话说:成员变量初始化就像在婆婆访问之前清理你的房子 - 要么立即进行,要么放置一个警卫,以便在需要之前进行。

哦,不要浪费太多时间来避免使用C ++中的指针,但要确保学会如何避免陷阱。

答案 11 :(得分:0)

没有C ++方法可以做到这一点。

C ++方式是:

  

做你想做的事,我会尽可能多地给你绳索   地狱男人!你甚至可以把它挂起来,我不会阻止你!

你可以使用指针,但你必须要注意内存泄漏!您必须定义谁(如在哪个类/对象中)拥有此对象,谁拥有删除它的专有权!

或者,你可以插入一个垃圾收集器(我听说有一些流行的收集器,但从未尝试过任何自己),然后使用指针,就像你在java中一样。