std :: string在类对象中传递函数时丢失值

时间:2014-07-27 10:54:08

标签: c++ pointers stl allocation stdstring

我真的很困惑编译器如何分配STL对象。请考虑以下代码:

#include <string>

using namespace std ;

class s { 
    public:
    string k ; 

    s(string k) : k(k) {}
} ;

void x ( s obj ) {
    string k = (obj.k) ;
    k += "haha" ;

}



int main () {
    std::string mystr ("laughter is..") ;
    s mys(mystr) ;
    x(mys) ;

    printf ("%s", mystr.c_str() ) ;
}

此程序的输出为laughter is..,我希望输出为: laughter is haha

为什么mystr字符串不能获得haha。我需要将它作为代码的一部分存储在类中。

如果我通过值传递mystr到函数x,则字符串mystr会有haha

a)如何以及何时分配STL对象?我认为mystr在堆栈中,并且必须可以访问从main()调用的所有函数。

b)如果我需要将STL对象存储在需要“void *”的旧式链表中,该怎么办?我不能这样做:

std::string mystr ("mystring.." );

MyList.Add((void*)&mystr) ;

fun(MyList) ;

这个功能有用吗,现在可以通过访问MyList来使用和修改mystr?

c)作为(b)的替代方案,我可以使用传递参考。问题是我可以声明一个类来保留mystr的引用吗?我的意思是MyList的构造函数可以是这样的:

class MyList { 
    string& mStr ;
    ...
};


MyList::MyList ( string& mystr ) {
      mStr = mystr ;

}

该构造函数有效吗?该课程有效吗?

3 个答案:

答案 0 :(得分:3)

你的课程让你的情况变得复杂。你有完全相同的问题:

void x ( string str ) {
    str += "haha" ;
}

int main () {
    std::string mystr ("laughter is..") ;
    x(mystr) ;

    printf ("%s", mystr.c_str() ) ;
}

我已经摆脱了课程。我没有将mystr放入s对象并将s对象传递给x,而是直接传递mystrx然后尝试将"haha"添加到字符串中。

问题是x通过值获取其参数。如果按值传递对象,您将获得它的副本。也就是说,str对象是mystr的不同对象。它是它的副本,但它是一个不同的对象。如果您修改str,则根本不会影响mystr

如果您希望x能够修改其参数,则需要将其作为参考:

void x ( string& str ) {
    str += "haha" ;
}

但是,我理解你为什么介绍这门课程。您正在考虑“好吧,如果我将字符串提供给另一个对象,然后传递该对象,该字符串应该在函数外部和内部相同。”情况并非如此,因为您的类正在存储字符串的副本。也就是说,您的类有一个成员string k;,它将 该类类型的任何对象的一部分。字符串kmystr不是同一个对象。

如果要在函数之间修改对象,则需要某种形式的引用语义。这意味着使用指针或引用。

关于你的问题:

  1. 是的,string对象mystr在堆栈中。这与它来自标准库无关。如果在函数内部编写声明,那么该对象将在堆栈​​中,无论是int x;string s;SomeClass c;还是其他。

    另一方面,mystr内的数据的内部存储是动态分配的。必须是因为std::string的大小可以变化,但C ++中的对象总是有固定的大小。一些动态分配是必要的。但是,你不应该关心这个。此分配由类封装。您可以将mystr视为字符串。

  2. 请不要使用存储void*的链接列表。请改用std::list。如果您需要字符串的链接列表,则需要std::list<std::string>。但是,是的,如果你有一个对象存储指向其他对象的指针并且你按值传递该对象,那么副本中的指针仍将指向相同的位置,因此你仍然可以修改它们指向的对象。

  3. 如果你有一个std::list<std::string>并且想要将它传递给一个函数以便该函数可以修改容器的内容,那么你需要通过引用传递它。如果您还需要列表中的元素作为对列表外部创建的对象的引用,则需要使用std::list<std::reference_wrapper>代替。

    就初始化参考成员而言,您需要使用成员初始化列表:

    MyList::MyList(string& mystr)
      : mStr(mystr)
    { }
    

答案 1 :(得分:0)

您在函数k中操作的字符串x是对象k中字符串obj的副本。而且obj本身已经是你传递的内容的副本,你传递和存储在obj中的字符串也已经是副本了。因此,它与您希望被改变的原始神秘的距离非常非常远。

提出其他问题: a)是的,这种方式的对象是堆栈分配的。不只是stl,任何对象。否则,您需要使用new。 b)不,你不能这样传递它,因为它分配的内存将变得无效。您需要使用new分配它。 c)是的,你可以通过引用传递,但同样重要的是你分配的东西。

正如其他人所指出的那样,这些是一些非常基本的问题,您需要阅读堆与堆栈分配并通过引用传递并首先按值传递,然后查看一些基本的STL类和容器。

答案 2 :(得分:0)

严格地说,即使你接受了STL&#34;你的问题也与STL无关。作为C ++标准库&#34;的正确&#34;容器,迭代器和算法的同义词。 std::string曾经在历史上看起来像一个容器(一个字符的容器,就是这样),但它通常以与“真实的”#34;真实的方式不同的方式使用。容器类,如std::vectorstd::set

反正

  

为什么没有mystr字符串得到&#34;哈哈&#34;

因为您没有使用引用x修改参数的副本;同样,string k = (obj.k)创建字符串的副本。这是带引用的代码:

void x ( s &obj ) {
    string &k = (obj.k) ;
    k += "haha" ;
}
  

a)如何以及何时分配STL对象?

容器对象本身在您定义时分配。内部内部分配内存的方式由其分配器模板参数定义,默认为std::allocator。你真的不想知道std::allocator的内部 - 它几乎总是做正确的事情。无论如何,我不认为你的问题是内部分配。

  

我认为mystr在堆栈中,并且必须可以访问从main()

调用的所有函数

  

b)如果我需要将STL对象存储在旧式链接列表中,该怎么办?   需要&#34; void *&#34;。

使用std::list<void*>

但你不必这样做。使用std::list<std::string>,您可能根本不需要在代码中使用指针。

至于您的进一步代码示例:

std::string mystr ("mystring.." );
MyList.Add((void*)&mystr) ;
fun(MyList) ;
  

这个功能有用吗,现在可以通过访问MyList来使用和修改mystr?

是。但是,代码有两个问题。较小的是(void*)&mystr。通常,您应该避免使用C风格的强制转换,但使用static_castreinterpret_castconst_castdynamic_cast之一,具体取决于您需要的转换。无论如何,在这段代码中,你根本不需要演员。

更大的问题是将局部变量的地址添加到看起来像是期望动态分配的对象的地方。如果从函数返回MyList,则mystr将被销毁,复制的列表将包含指向死对象的指针,最终导致未定义的结果。

要解决此问题,您必须详细了解newdelete以及可能的智能指针。这超出了简单答案的范围,结果可能仍然比std::list<std::string>更差。

  

问题是我可以声明一个类来保留mystr的引用吗?

是的,但你通常应该避免它,因为它很容易导致悬挂引用,即引用死对象,原因如上所述。

class MyList { 
    string& mStr ;
    ...
};


MyList::MyList ( string& mystr ) {
      mStr = mystr ;

}
  

该构造函数是否有效?

不,它不会编译。您需要使用初始化列表:

MyList::MyList ( string& mystr ) : myStr(mystr) {}

我只能重复上面的建议。 使用std::list<std::string>