我的示例代码有问题。
任务是创建一个至少包含一个动态成员的类,以创建一些对象,打印出某些内容并最终销毁它们。我已经编写了代码,它可以编译而没有任何错误或警告,但是它什么都不会打印(我使用的是VS 2017 Community Edition的Microsoft CL-Compiler)。
有人可以给我提示,我在做什么错吗?
dyn.h:
#include <iostream>
#include <string>
#ifndef __CLASS_DYN__
#define __CLASS_DYN__
class dyn{
private: std::string Name;
private: int* Age;
public: dyn(std::string, int);
public: dyn(const dyn&);
public: ~dyn();
public: std::string toString();
};
#endif
dyn.cpp
#include "dyn.h"
dyn::dyn(std::string Name, int Age){
this->Name = Name;
*this->Age = Age;
}
dyn::dyn(const dyn& a){
this->Name = a.Name;
*this->Age = *a.Age;
}
dyn::~dyn(){
delete this->Age;
}
std::string dyn::toString(){
std::string tmp = "Name of Person " + this->Name;
return (tmp);
}
main.cpp
#include <iostream>
#include "dyn.h"
int main(){
dyn* Person1 = new dyn{"Mike", 38};
dyn* Person2 = new dyn{"Thomas", 20};
dyn* Person3 = Person1;
std::cout << Person1->toString() << std::endl;
std::cout << Person2->toString() << std::endl;
std::cout << Person3->toString() << std::endl;
delete Person1;
delete Person2;
delete Person3;
return 0;
}
答案 0 :(得分:3)
在您分配给Age
的两行中。在这些情况下,由于Age
被声明为int *
,因此*this->Age
本身并不是this->Age
,而是this->Age
指向的内存地址值。此外,由于它们都在构造函数中,this->Age
尚未拥有有效的地址,因此尝试为其分配值将导致未定义的行为。
正确的做法是通过如下分配内存来确保this->Age
在分配之前具有有效地址:
dyn::dyn(std::string Name, int Age){
this->Name = Name;
this->Age = new int();
*this->Age = Age;
}
dyn::dyn(const dyn& a){
this->Name = a.Name;
this->Age = new int();
*this->Age = *a.Age;
}
幸运的是,您已经在析构函数中删除了Age
,所以在那里不需要修改。
答案 1 :(得分:1)
Thx。到@HolyBlackCat:
我更改了我的ctor ::
dyn::dyn(std::string Name, int Age){
this->Name = Name;
this->Age = new int(Age);
}
和
dyn::dyn(const dyn& a){
this->Name = a.Name;
this->Age = new int{*a.Age};
}
现在它可以按预期工作了:)
答案 2 :(得分:1)
但想学习c ++
因此,当您已经对问题本身有一个适当的answer时,我可能会给您一些其他提示...
首先,您已经发现了,可以将参数传递给指针的构造函数本身:
this->Age = new int(Age);
this->Age = new int{*a.Age};
Solely:有两种调用构造函数的方法,一种是带有括号的经典方法,另一种是通过花括号进行新的统一初始化。尽管后者显示出一些良好的意图,但我个人认为它是无效的,并且不在我自己的代码中使用(最喜欢的示例:std::vector<int>{1, 2, 3}
调用std::initializer_list
constructor,std::vector<int>{7}
调用经典构造函数来创建具有七个元素的向量;将其与std::vector<int>(7)
和std::vector<int>({7})
比较,后者显式调用std::initializer_list
构造函数)。好吧,您可能想进一步探讨该主题,并决定您自己是要跟随我还是赞成UI的人,但是无论您选择哪种方式,都应该始终使用而不是在两者之间切换...
然后习惯于使用构造函数的初始化程序列表(不要与std::initializer_list
混淆!):
dyn::dyn(std::string Name, int Age)
: Name(Name)
// ^ function argument
// ^ member
{
// ...
}
好吧,甚至 都可以使用相同的标识符(位置明确了哪个是哪个),您仍然可能更喜欢使用不同的标识符。本机类型(int,double,指针,...)和复杂类型并没有太大区别,但是事情有所变化:
dyn::dyn(std::string Name, int Age)
// default constructor for Name is s t i l l called!
{
this->Name = Name; // and now you do the assignment!
}
默认构造和赋值两者都可能或多或少地花费,在任何情况下,直接初始化(如通过使用前面所示的初始化器列表所发生的)效率更高。此外,引用和非默认可构造类型 only 可以通过这种方式初始化。
然后参数类型:C#根据类型确定是通过值还是通过引用传递参数;在C ++中,您可以按引用或按值(或第三个选项,通过指针)传递任何类型,但是您需要明确!
dyn::dyn(std::string Name, int Age) // by value (i. e. you make a c o p y of!)
dyn::dyn(std::string& Name, int Age) // by reference
dyn::dyn(std::string* Name, int Age) // by pointer
dyn::dyn(std::string&& Name, int Age) // for completeness: r-value reference
最后一个是允许移动语义的新概念(即,将内容从一个对象移动到另一个对象)。 tutorial已经很不错了,所以我不再赘述。
但是,通过接受引用,可以避免一个不必要的副本(并且由于您不打算更改该参数,因此应为const
):
dyn::dyn(std::string const& Name, int Age);
然后看看rule of three。的确,您获得了由C ++定义的默认赋值运算符,但是它将仅执行以下操作:
dyn::operator=(dyn const& other)
{
Name = other.Name;
Age = other.Age; // now both objects will point to the same address
// and you'll get a double deletion error!
}
好的,也定义了移动分配,但不会改变指针的任何内容。借助移动语义,Ro3扩展到了rule of five;尽管前者是(仍然)强制性的,但后者不是强制性的,但是如果您不遵循它,则会错过很大的优化可能性...
处理动态分配的内存的现代C ++方法是使用智能指针,它们有std::unique_ptr
,std::shared_ptr
和std::weak_ptr
(后者很少使用)。
class dyn
{
private:
std::unique_ptr<int> Age;
public:
dyn(int Age) : Age(std::make_unique<int>(Age)) { }
};
猜猜是什么:现在默认生成的析构函数,移动和复制构造函数以及赋值 all 已经可以了,您不需要显式实现任何这些...
return
不是一个函数,因此您不应在返回值周围使用括号。在C ++中,这甚至可以产生具体效果:
decltype(auto) f() { int n; return (n); } // returns a reference to n!
最后,关于命名约定的最后一点。通常,类标识符以大写字母开头,其中变量(成员,全局变量和局部变量)以小写字母开头。函数标识符不明确,有一些偏爱的大写字母(一种主要来自Microsoft的约定),而有些则更喜欢今天的初始小写字母(这是更古老的约定,并且仍然是更常用的约定)。对于功能,您应该选择其中之一,但无论如何,请始终遵循。