Cpp一些基本问题

Cpp一些基本问题

问题描述:

我的任务如下: 创建一个具有char * name和int age类的Person。使用动态分配内存的变量,析构函数,函数init和好友函数show来实现构造器。然后将此类转换为头文件和cpp文件并在其他程序中实现。好了,所以这里是我的Person类:Cpp一些基本问题

#include <iostream> 
using namespace std; 

class Person { 
    char* name; 
    int age; 
public: 

    Person(){ 
     int size=0; 
     cout << "Give length of char*" << endl; 
     cin >> size; 
     name = new char[size];  
     age = 0; 
    } 

    Person::~Person(){ 
     cout << "Destroying resources" << endl; 
     delete [] name; 
     delete take_age(); 
    } 

    friend void show(Person &p); 

    int* take_age(){ 
     return &age; 
    } 

    char* take_name(){ 
     return name;  
    } 

    void init(char* n, int a) { 
     name = n; 
     age = a; 
    } 
}; 

void show(Person *p){ 
    cout << "Name: " << p->take_name() << "," << "age: " << p->take_age() << endl; 
} 

int main(void) { 
    Person *p = new Person; 
    p->init("Mary", 25); 

    show(p); 

    system("PAUSE"); 
    return 0; 
} 

现在通过页眉/实现部分:
- 我需要在头/执行文件介绍构造?如果是 - 如何?
- 我的show()函数是一个友好的函数。我应该以某种方式考虑它吗?

我已经没有我的考试返回此任务,但我仍想知道如何实现它。

+3

如果你不已经有一个,你应该考虑让初学者C++在[权威的C++书指南和列表]上市丛书之一(http://*.com/questions/388242/the -definitive-C-书指南和列表)。 – 2010-05-24 23:29:27

+1

对不起,如果听起来很刺耳,但你的代码真的显示,你完全不知道发生了什么。这只是一个事实。你真的需要读些东西。 – 2010-05-24 23:35:41

+0

C++不叫cpp。 C预处理器被称为cpp。 – hobbs 2010-05-24 23:52:20

解决很多问题的,通过切换从char *std::string。你会很高兴你做到了。

std::string类负责内存分配的,和释放以及复制。

如果这是作业,请说服您的教授使用std::string为初学者和保存char *的指针部分。同时提醒您的教授,C++语言与C语言不同。这是其中的一个领域。

+0

不同意。任何C++程序员都应该知道如何使用char *字符串以及std :: string实例。 C++是比C复杂得多的语言,但它们都有相同的基础,任何C++程序员都应该知道它们。 – ebasconp 2010-05-25 00:52:42

+0

@ebasconp:他们是否应该知道这是一个不同的问题,因为它是否适合这个作业问题。所以我发现自己同意托马斯。 – 2010-05-25 15:27:01

+0

+1不需要强制用户在C++代码中使用'char *',除非与需要API的接口连接。它使事情变得复杂,并留下内存泄漏/管理不善的空间。 – 2010-05-25 15:49:26

使用deletedelete[]时,不需要*。只需提供一个指针变量,例如。

delete[] name; 

此外,您take_age成员声称返回int*但实际上你返回int成员本身。如果你想这样做,你需要使用&的成员地址。由于@杰里评论说这是而不是你想在这里做什么。

+2

...或将其返回类型更改为int。返回一个指向内部对象的指针是你通常应该避免的东西(在这种情况下,似乎没有理由这么做)。 – 2010-05-24 23:37:34

+0

@Jerry:绝对如此。 – Troubadour 2010-05-24 23:40:09

我想你应该调查下面的代码片段(如,什么是他们脚下,这里发生了什么,等...)

int * take_age(); // You should return plain `int` here, I assume 


~Person(){ 
    cout << "Destroying resources" << endl; 
    delete *[] name; // Do you understand why did you put `*` here? 
    delete * take_age(); // Do you understand why did you write this? What behaviour you were trying to achieve? 

而且,实际上,等等。只有当你完成了basic的东西,我想,你可以转到头设计问题和朋友功能。

虽然这个网站上的一些人显然认为这是完全可以接受的,但是最好的做法(请参阅Can a constructor return a NULL value?),您应该避免在对象的构造函数中进行流操作等操作。在外面读取数据流,然后用结果调用函数。

也就是说,恕我直言,你应该采取的第一步。

+0

充分尊重,这至多是不相干的,更糟糕​​的是完全错误。 OP的问题来自于只有初学者对该语言的了解,而不受你的教条建议的帮助。有一个反序列化的构造函数是完全正确的,即使你个人不喜欢它们。事实上,对于不可变的对象来说,它实际上是实现序列化的唯一方法。 – 2010-05-25 15:29:59

+0

是的,我想如果他们想出去找一份工作,这只是一个很好的建议。如果他们想成为一个体面的业余爱好开发人员,这也是一个很好的建议。如果他们想要的只是一个黑客攻击,如果在构造函数中调用控制台流来完成工作...... – 2010-05-25 15:39:32

+0

不,对任何人都不是好的建议。我没有看到任何合理的论据支持你的结论,即施工人员必须尽量减少他们的行为,以及许多好的论点。由于某种宗教信仰而任意限制自己不是成为黑客的替代品,而是成为一名合格专业人士的替代选择。 – 2010-05-26 19:49:39

我认为你的问题是这条线: 朋友无效(人& p); 它需要什么。

我需要在头/执行文件介绍构造? 构造函数可以位于.h或.cpp文件中。没关系。通常如果函数很短,可以将其包含在.h文件中。任何更长的时间应该在.cpp中。

我的show()函数是一个友好的函数。 不确定这是什么意思。朋友函数存在于类定义之外。你的演出功能是在课堂上定义的,所以不需要成为朋友。

在一个典型的情况下,管理一个指针和动态分配的内存块(例如本例中的名称)对于一个类是足够的责任。因此,托马斯马修斯是正确的:在这种情况下你应该真的使用字符串。如果你要自己处理它,你仍然应该把这个职责分解成它自己的一个类,然后把这个类的一个对象嵌入到你的Person对象中。如果有的话,std::string已经尝试做太多;你会更好,而不是更少。

您的删除应该与您的分配完全匹配。在这种情况下,唯一的分配是:

name = new char[size];  

所以才删除应该是:

delete [] name; 

至于friend功能去的,通常要在类定义内friend声明,但功能定义在类定义之外:

class Person { 
// ... 
    friend void show(Person const &); 
// ... 
}; 

void show(Person const &p) { 
    // ... 
} 

还有其他的可能性,但这是一般的想法。特别是,朋友从来不是会员功能。你所拥有的是一个名为show的单一全局函数的声明和一个完全独立的成员函数的定义 - 它碰巧具有相同的名称,但根本不是完全相同的函数。

这表明另外一点:const正确性。您将参数作为参考传递给Person。除非要修改Person对象(在这种情况下,show()看起来像名称的一个糟糕的选择),它可能应该引用一个const对象。同样的总体思路适用于take_age() - 因为它只检索一个值,它应该是一个const功能:

int take_age() const { return age; } 

我可能已经试图掩盖太多了,所以我会闭嘴的时刻...

首先,试图找到正确的方式来实现你的班级,特别是在已经错过了答案后的荣誉。

从你顶部的描述中,我想你可能误解了一些被要求完成这项任务的人。首先,我的解释是设置名称和年龄的值应该在init()函数中进行,而不是在构造函数中进行。正如其他几张海报所提到的,您的构造函数应该简单地将您的类初始化为已知状态。例如,

Person() { 
    name = NULL; 
    age = 0; 
} 

然后在您的初始化函数中,您可以分配值。查看你原来的init()函数,可能应该提到,简单地将一个指针值(char *)分配给另一个指针(char *)只会复制指针的值,而不是它所表示的数据。因此,为了分配名称值,您需要计算所需缓冲区的大小,分配缓冲区并自行复制数据。一个基本的init()函数可能看起来像

init(const char *n, int a) { 
    // Calculate the required name length plus a NULL-terminator 
    size_t nameLen = strlen(n) + 1; 

    // Free any previous value. 
    if (name != NULL) { 
     delete[] name; 
    } 

    // Allocate the name buffer and copy the name into it. 
    name = new char[nameLen]; 
    memcpy(name, n, nameLen); 

    // Store the age. 
    age = a; 
} 

最后,在你的析构函数,你释放你的类分配的资源,在这种情况下,名称缓冲区。

~Person() { 
    if (name != NULL) { 
     delete[] name; 
    } 
} 

如果你有一本书或与您的类相关联的东西,你可能要审查指针的信息。他们可能有点棘手,但重要的是学习。我怀疑这就是为什么使用char *指定字符串而不是STL字符串类的原因。

对于将信息放置在头文件和源文件中的问题,创建包含类声明和成员函数原型的头文件通常被认为是很好的做法,然后在单独的源文件中提供方法的实现。对于一些简单的函数,您可以直接在头文件中提供实现。

在一个单独的源文件提供类成员定义时,关键是要提供类名正常范围的函数(即,人::)。所以,你的头文件可能包含一个类定义像

// Header file (e.g., person.h) 

class Person { 
private: 
    char *name; 
    int age; 

public: 
    Person() { name = NULL; age = 0 }; 
    ~Person() { if (name != NULL) delete[] name; } 

    void init(const char *n, int a); 

    // Other method declarations and/or definitions 
}; 

然后在源文件中使用您的个人类只需要包括你的类定义的头文件

// Source file (e.g., person.cpp) 

void Person::init(const char *n, int a) { 
    // ... 
} 

// Rest of method definitions 

源文件。

除了以前发布的答案,我有两点建议分给你:

  • 不使用“朋友”。这里有些人可能不同意我的看法,但'朋友'应该不再是C++的一部分,因为它违背了OOP的含义。
  • 命名您的方法:避免命名您的方法,如'take_name'或'take_age'。通常情况下,因为这些是getter,所以可以考虑将它们命名为'getName'和'getAge'。你最终会以这种方式获得开发者的更多尊重。
+0

虽然'朋友'当然可以是一种代码味道,并且通常有更好的方法,但也有一些地方非常需要,包括STL内部。它可能不是纯粹的OOP,但C++也不是。然而,对于名字达成一致。 – 2010-05-25 15:33:06