string在类对象内部的函数中传递时失去值

std::string loses value when passed in function inside a class object

本文关键字:失去 函数 对象 内部 string      更新时间:2023-10-16

我真的很困惑编译器如何分配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 ;
}

构造函数是否有效?这个类有效吗?

你的课只会让情况变得更复杂。你在这里有完全相同的问题:

void x ( string str ) {
    str += "haha" ;
}
int main () {
    std::string mystr ("laughter is..") ;
    x(mystr) ;
    printf ("%s", mystr.c_str() ) ;
}

我已经摆脱了这门课。我没有将mystr放入s对象并将s对象传递给x,而是直接传递mystr。然后x尝试将"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* s的链表。使用std::list代替。如果你想要一个字符串链表,你想要std::list<std::string>。但是,如果你有一个对象,它存储了指向其他对象的指针,并且你按值传递该对象,那么副本中的指针仍然指向相同的位置,所以你仍然可以修改它们指向的对象。

  3. 如果你有一个std::list<std::string>,你想把它传递给一个函数,这样函数就可以修改容器的内容,那么你需要通过引用传递它。如果还需要列表的元素是对列表外部创建的对象的引用,则需要使用std::list<std::reference_wrapper>

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

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

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

对于你的其他问题:a)是的,这样的对象是堆栈分配的。不只是静止,任何物体。否则需要使用new。b)不,你不能像这样传递它,因为它的堆栈分配的内存将变得无效。您需要使用new对它进行堆分配。c)是的,你可以通过引用传递,但是,你在哪里分配东西很重要。

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

严格地说,你的问题与STL无关,即使你接受"STL"作为正确的"c++标准库的容器、迭代器和算法"的同义词。std::string在历史上一度被设计成一个容器(即一个字符容器),但它的使用方式通常与"真正的"容器类(如std::vectorstd::set)完全不同。

,

为什么mystr字符串没有得到"haha"

因为不使用引用x修改参数的copy;同样,string k = (obj.k)创建字符串的副本。下面是引用代码:

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

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

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

我假设mystr在堆栈上,并且必须可以被main()调用的所有函数访问

是的。

b)如果我需要在老式链表中存储STL对象该怎么办其中需要"void*"

使用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将被销毁,并且复制的列表将包含指向死对象的指针,最终导致未定义的结果。

为了解决这个问题,你必须更多地了解new, delete,可能还有智能指针。这超出了一个简单答案的范围,结果可能仍然比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>