#include<iostream>
#include<cstring>
using namespace std;
class Animal
{
protected:
int age;
char* name;
public:
Animal()
{
name=new char[1];
age = 0;
strcpy(name," ");
}
Animal(int _age, char* _name)
{
age=_age;
name = new char[strlen(_name)+1];
strcpy(name, _name);
}
~Animal()
{
delete[] name;
}
friend istream& operator >>(istream& in, Animal& a);
friend ostream& operator <<(ostream& out, const Animal& a);
};
istream& operator >>(istream& in, Animal& a)
{
in>>a.name>>a.age;
return in;
}
ostream& operator <<(ostream& out, const Animal& a)
{
out<<a.name<<a.age;
return out;
}
int main()
{
Animal a;
cin>>a;
cout<<a;
return 0;
}
这段代码让我有机会输入a
,然后打印它,然后屏幕冻结并停止工作。如果我删除析构函数,它可以正常工作。为什么会这样?是因为析构函数呢?
答案 0 :(得分:4)
您分配大小为1的C字符串,并复制大小为2的C字符串“”。你也可以在'istream&amp; amp;中找到未知数量的字符。运算符&gt;&gt;(istream&amp; in,Animal&amp; a)`。两者都会破坏名称指向的内存,并且可以使用std :: string:
轻松修复class Animal
{
protected:
int age;
std::string name;
public:
Animal()
: age(0)
{}
Animal(int age_, std::string name_)
: age(age_), name(name_)
{}
};
这可以避免编写构造中缺少的析构函数和复制构造函数和赋值运算符(参见:Rule of three)。
答案 1 :(得分:3)
如果你真的不想使用std::string
,你最好的选择就是(live at coliru):
#include<iostream>
#include<cstring>
using namespace std;
class Animal {
private:
// copy a string
inline static char* dstr(const char* string) {
if( !string ) return NULL;
size_t l = strlen(string);
if( !l ) return NULL;
return strcpy(new char[++l], string);
}
protected:
int age;
char* name;
public:
// initialize an "empty" Animal
Animal() : age(0), name(NULL) {}
// initialize an animal by age and name
Animal(int _age, const char* _name): age(_age), name(dstr(_name)) {}
// initialize an animal from another animal:
// copy the name string
Animal(const Animal& _a): age(_a.age), name(dstr(_a.name)) {}
// assign an animal from another animal:
// first delete the string you have, then copy the string
Animal& operator=(const Animal& _a) {
// for exception-safety, save the old "name" pointer,
// then try to allocate a new one; if it throws, nothing happens
// to *this...
char* oldname = name;
name = dstr(_a.name);
age = _a.age;
delete[] oldname;
return *this;
}
// if C++11
// we have something called "move" constructor and assignment
// these are used, for instance, in "operator>>" below
// and they assume that _a will soon be deleted
Animal(Animal&& _a): Animal() {
swap(age, _a.age);
swap(name, _a.name);
}
Animal& operator=(Animal&& _a) {
swap(age, _a.age);
swap(name, _a.name);
return *this;
}
~Animal() { delete[] name; }
friend ostream& operator <<(ostream& out, const Animal& a);
};
istream& operator >>(istream& in, Animal& a) {
const size_t MAX_ANIMAL_NAME = 2048;
int age;
char n[MAX_ANIMAL_NAME+1];
if( in.getline(n, MAX_ANIMAL_NAME) >> age )
a = Animal(age, n);
return in;
}
ostream& operator <<(ostream& out, const Animal& a) {
return out<<a.name<<endl<<a.age<<endl;
}
int main() {
Animal a { 23, "bobo" };
cout<<a;
cin>>a;
cout<<a;
}
这不会泄漏内存,没有未定义的行为,也没有缓冲区溢出。
您还可以将“需要管理内存”分离到单独的类:
#include<iostream>
#include<cstring>
using namespace std;
class AnimalName {
private:
char *n;
inline static char* dstr(const char* string) {
if( !string ) return NULL;
size_t l = strlen(string);
if( !l ) return NULL;
return strcpy(new char[++l], string);
}
public:
AnimalName() : AnimalName(NULL) {}
AnimalName(const char *_n) : n(dstr(_n)) {}
AnimalName(const AnimalName& _n) : n(dstr(_n.n)) {}
// see exception-safety issue above
AnimalName& operator=(const AnimalName& _n) { char *on = n; n = dstr(_n.n); delete[] on; return *this; }
AnimalName(AnimalName&& _n) : AnimalName() { swap(n, _n.n); }
AnimalName& operator=(AnimalName&& _n) { swap(n, _n.n); return *this; }
~AnimalName() { delete[] n; }
operator const char*() const { return n; }
friend istream& operator>>(istream& i, AnimalName& n) {
const size_t MAX_ANIMAL_NAME = 2048;
char name[MAX_ANIMAL_NAME+1];
if( i.getline(name, MAX_ANIMAL_NAME) )
n = name;
return i;
}
};
class Animal {
protected:
int age;
AnimalName name;
public:
// initialize an "empty" Animal
Animal() : age(0) {}
// initialize an animal by age and name
Animal(int _age, const char* _name): age(_age), name(_name) {}
friend ostream& operator <<(ostream& out, const Animal& a) {
return out<<a.name<<endl<<a.age<<endl;
}
};
istream& operator >>(istream& in, Animal& a) {
AnimalName n;
int age;
if( in >> n >> age )
a = Animal(age, n);
return in;
}
int main() {
Animal a { 23, "bobo" };
cout<<a;
cin>>a;
cout<<a;
return 0;
}
这样你就可以遵循“rule of zero”(基本上,没有唯一负责管理内存/资源的类应该不管理内存,因此应不实现复制/移动构造函数,赋值或析构函数。)
这让我们明白了你应该使用std::string
的真正的原因:它不仅为你做了内存管理,而且还很好地照顾你的IO需求,消除了在您的示例中需要“最大动物名称”:
#include<iostream>
#include<string>
using namespace std;
class Animal {
protected:
string name; // name first, for exception-safety on auto-gen assignment?
int age;
public:
// initialize an "empty" Animal
Animal() : age(0) {}
// initialize an animal by age and name
Animal(int _age, const string& _name): age(_age), name(_name) {}
friend ostream& operator <<(ostream& out, const Animal& a) {
return out<<a.name<<endl<<a.age<<endl;
}
};
istream& operator >>(istream& in, Animal& a) {
string n;
int age;
if( getline(in, n) >> age )
a = Animal(age, n);
return in;
}
int main() {
Animal a { 23, "bobo" };
cout<<a;
cin>>a;
cout<<a;
}
答案 2 :(得分:2)
一个简单的解决方法是使用 std::string
作为字符串。
你得到的具体错误几乎无关紧要。但只是为了掩盖它,已经在Animal
的构造函数中,
Animal()
{
name=new char[1];
age = 0;
strcpy(name," ");
}
您有未定义的行为,只需分配一个元素数组,然后使用strcpy
顶部复制两个char
值。在数组后覆盖一些内存。
然后在operator>>
中,UB趋势继续。
等等。
使用std::string
。
答案 3 :(得分:1)
您的内存管理错误,这会破坏内存。您正在为name
的一个字符分配空间。但是
strcpy(name," ");
将超出您分配的内存,因为cstring
被null
终止,它实际上会放两个字符,有效地破坏您的内存(您正在访问未被您的程序分配的内存)。它本身有未定义的行为。
此外,您正在删除析构函数中显然未知的内存量,该内存也具有未定义的行为。
答案 4 :(得分:-2)
您的代码中存在多个错误。 第一个是在构造函数
中Animal()
{
name=new char[1];
age = 0;
strcpy(name," ");
}
字符串文字" "
由两个字符组成:空格字符和终止零'\ 0;。所以你需要动态分配2个字节
name=new char[2];
在该函数strcpy之后使用。
或者代替字符串文字" "
,您应该使用仅包含终止零'\ 0'的“空”字符串文字""
。
功能中的另一个错误
istream& operator >>(istream& in, Animal& a)
{
in>>a.name>>a.age;
return in;
}
由于您最初只按名称指定了1个字节,因此您可能不会使用运算符
in>>a.name;
因为您将覆盖不属于已分配范围的内存。
例如,您可以通过以下方式定义运算符
std::istream& operator >>( std::istream& in, Animal &a )
{
char itsName[25];
in >> itsName >> a.age;
char *tmp = new char[std::strlen( itsName ) + 1];
std::strcpy( tmp, itsName );
delete [] name;
name = tmp;
return in;
}
在这种情况下,您可以输入一个不超过24个字符的名称。
考虑到如果要将一个对象分配给另一个对象,还需要定义复制构造函数和复制赋值运算符。