C ++ - 字符串复制任务

时间:2015-09-25 10:27:35

标签: c++ pointers

在我准备考试的任务之一中,我仍然没有看到指针方法,我还在学习的开始(我只学习了Java)。

所以任务是string s被复制多少次以及在哪里。

我认为在t1中,由于指向地址的指针,字符串将被复制。我不确定。此外,我无法弄清楚字符串后的& - 符号是什么。

以下是代码:

#include <string>
using namespace std;
string t1(string z) { return z; }
string *t2(string &z) { return &z; }
string& t3(string *z) { return *z; }
string& t4(string& z) { return z; }
string t5(string &z) { return z; }

int main() {
  string s;
  t1(s);
  t2(s);
  t3(&s);
  t4(s);
  t5(s);
  return 0;
}

3 个答案:

答案 0 :(得分:0)

我认为分析这种行为的一个好方法是使用一个类似下面的简单类:

class Test{
public:
    int id;
    Test(){ cout << "Constructor is called!  id:" << id << endl; }
    Test(const Test &obj){
        id = obj.id;
        cout << "Copy-Constructor is called!  id:" << id << endl;
    }
    ~Test(){ cout << "Destructor is called!  id:" <<  id << endl; }
};

您需要特别注意的一件事是,当将对象传递给函数或作为函数的返回值而不是constructor时,会调用另一个名为copy constructor的函数将值从一个对象复制到另一个对象一个安全的,你可以自己定义它,你可以在我的示例类中看到。 (为了实现它真的存在,你可以省略我的类中的这个函数并测试你的f1,看看没有构造函数的输出,但是有一个用于析构函数。)

现在回答你的问题我使用的是这个类,而不是string。我还想象作为函数参数发送的对象有id==100,并且在每个函数返回部分之前还有一个z.id = 55;z->id = 55;

Test t1(Test z) { z.id = 55; return z; }

通过调用此函数,您会看到copy-constructor输出两次。一旦复制id==100这是参数对象,另一个复制id==55的返回部分。在这些构造函数调用之后,我们可以看到id==55的两个析构函数调用在函数中被更改为z.id

Test *t2(Test &z) { z.id = 55; return &z; }
Test &t4(Test &z) { z.id = 55; return z; }

在这些函数中,由于你正在使用引用和指针,所以不会有任何构造函数或析构函数调用,因此不会为参数和返回部分创建新对象。(顺便说一下,如果你&#39;我不确定这两者之间的区别是here.

Test &t3(Test *z) { z->id = 55; return *z; }

在这个中,也不会有任何新对象,但不同之处在于,由于返回值采用引用的形式,因此您可以返回对象(*z instead of z)的值,但如果你使用参考

Test t5(Test &z)  { z.id = 55; return z;  }

最后,在此功能中,当您到达返回部分时会创建一个新对象。

答案 1 :(得分:0)

在我开始讨论之前,让我们了解一下大多数字符串课程的一些特别之处。字符串类通常实现为一种指向字符串缓冲区的智能指针。这意味着:

std::string s1("testing");
std::string s2;

s2 = s1;

虽然s2是唯一的字符串类,但在赋值s2 = s1之后,它们之间仍然只有一个字符串缓冲区。该缓冲区未被复制,它以一种只读方式共享。如果对s2中的字符串进行了更改,则会在此时创建一​​个副本,以使两个字符串指向不同的缓冲区。

你的问题可能不是关于缓冲区本身,而是关于操作这些缓冲区的字符串对象,但它在字符串的情况下是切向相关的(同样地,由于类似的原因,它与std :: shared_ptr相关)复制表现而言。复制std :: string类通常比复制底层缓冲区要少得多。

那就是说,还有一个关于你的代码样本值得解决的问题,以及这些函数的返回值是做什么的(部分是因为你问过&amp;之后做了什么?其中两个中的字符串)。

轻微扩张重复:

#include <string>
using namespace std;
string t1(string z) { return z; }
string *t2(string &z) { return &z; }
string& t3(string *z) { return *z; }
string& t4(string& z) { return z; }
string t5(string &z) { return z; }

int main() {
  string s; string x; string *xp
  x  = t1(s);
  xp = t2(s);
  x  = t3(&s);
  x  = t4(s);
  x  = t5(s);
  return 0;
}

现在,将功能t1扩展一下非常重要。这里有理论和实际结果,它们在所有现代C ++编译器中都有所不同。在考试中,我希望你能回答纯粹的理论,忽略在这里发挥作用的省略副本。考虑x = t1(s),其中理论s被复制为函数的参数,此时函数内的z是来自调用者的s的副本。返回值是按值,因此理论上会创建第二个副本以返回。然后,理论上,在分配x时执行另一个副本。现在,如果您在调试器中跟踪它,那么这也可能是您所见证的。但是,除了最天真的编译器之外,所有这些副本都将被省略,这样x将收到s的副本,好像x = s被写入一样(并且大多数编译器会检查这些文字代码,实现什么都不做,并发出一个只返回的程序。

现在,关于x = t2(s);该参数是对字符串的引用(这些事情是从右到左解释的,所以想想引用一个字符串,即使大多数人都说&#34;字符串引用&#34;。那意味着函数没有使用副本,它是调用者的。这个函数返回该字符串的地址,一个指针,这意味着没有复制s - 最多我们会说返回指针的副本。这与写入xp = &s;

相同

x = t3(&s)我们有一个奇怪的案例。该函数接受一个指针,该指针需要&s以取s的地址来提供该指针,因此在函数调用时不会复制s。该函数返回对字符串的引用(如前所述,从右到左读取,尽管有些人可能会说字符串引用)。由于这是指针的取消引用,结果只是通过它的地址引用s,并且在返回中不进行复制。返回是参考的事实进一步支持了这一点。引用实现为指针。它是一种特殊的指针,但在引擎盖下,它是一个指针 - 没有复制。但是,由于x是唯一对象,因此在为其分配x时,从该引用的赋值中进行复制。它解决了与编写x = s;

相同的问题

此功能支持的其他使用案例值得单独考虑:

string xr( t3( &s ) );

在这种情况下,引用用于初始化xr(从t3返回的引用)。它与string xr( s );类似。到目前为止,不是一个启示。但是,与t2和t1相比,请考虑使用返回的字符串。

t1(s).length();
t2(s)->length();
t3(&s).length();

这里,每个函数的返回用于调用string的成员。使用t1调用已将s复制到函数中,然后再次复制以返回临时字符串,然后销毁该字符串(将调用析构函数),这是您在查询中无法解决的问题。

然而,使用t2和t3的呼叫实际上是使用s进行呼叫,而不暗示任何副本。但是,在t2情况下,调用是通过指针进行的。 t2情况类似于写入(奇数)(&amp; s) - &gt; length(),而t3情况与写入s.length()相同。

T4与t3完全相同,只是调用的方式不同,以及与null可能传递给t3的可能性相关的暗示(导致解除引用时崩溃),这可能是&#39 t发生在t4。

T5与t4(和t3)的不同之处仅在于因为按值返回而隐含了副本。返回的内容类似于t1,操作类似于t1,并且仅与t1不同,暗示t5不会为函数体创建一个副本,它只是为返回创建一个副本。

假设您提供的示例代码,在调用t5后附加main:

string a, b;

// t1 is like having written:

a = s;
b = a;
x = b;

// t5 is like having written:

b = s;
x = b;

意思是,t1的第一个副本被t5取代了一个事实而不是一个值。

在现代C ++中,我们通常忽略理论在t1或t4,t5等情况下的性能含义。我们更关心为什么使用引用而不是副本,因为使用引用的副作用是对函数t5中的字符串所做的更改是对调用者进行的,而副本是隐含在t1中,因此呼叫者不会改变。这是您问题的重要组成部分。

如上所述,理论将始终复制书面所暗示的副本,但在实践中,由于优化,副本被省略(避免)。例如,在t1的情况下,该文字代码省略了所有隐含的副本 - 不会执行任何副本。但是,如果在t1的函数体内对z进行了更改,则会更改内容。如果对t1进行了更改,编译器会意识到更改z的副作用会改变s,除非进行复制,这意味着将创建t1的pass by value参数隐含的一个副本,以避免副作用,但仍然是以价值回报所隐含的副本。

答案 2 :(得分:0)

如果由于编译器优化可能导致复制省略,至少需要一个副本,我将回答:

  • t1:返回一个新副本(与传递的字符串不同):COPY
  • t2:你得到一个引用并返回一个指针:NO COPY
  • t3:你拿一个地址并返回一个引用:NO COPY - 如果指针为空则可能会崩溃
  • t4:您参考并返回参考:NO COPY
  • t5:您参考并返回一个值:COPY

如果没有优化,t1将需要2个副本:1个用于创建临时原始字符串,另一个用于在调用范围内创建返回的副本,但只有一个可以发生,如果有省略

t5只需要一个副本就可以在调用范围

中创建返回的副本