2015-09-25 33 views
1

在我爲考試做準備的任務之一中,我仍然沒有看到指針方法,我仍然剛剛開始學習(我只學過Java)。C++ - 字符串複製任務

所以任務是複製string s多少次以及在哪裏。

我認爲在t1字符串將被複制,因爲指向一個地址。雖然我不確定。我也無法弄清楚字符串後面的& -Symbol。

下面是代碼:

#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; 
} 
+0

那麼究竟什麼是你的問題?請編輯您的帖子,並提供一些關於您希望我們如何幫助您的說明。 –

+1

這真的很寬泛;你在用什麼C++書?如果你花一些時間閱讀它,你會學到這些語言的基礎知識。 –

回答

0

在我開始討論肉食之前,讓我們介紹一下關於大多數字符串類的一些特別的事情。字符串類通常是作爲一種智能指針來實現字符串的緩衝區。這意味着:

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

s2 = s1; 

雖然S2是一個獨特的字符串類,分配s2 = s1後,還有他們之間只有一個字符串緩衝區。該緩衝區不被複制,它以一種只讀配置共享。如果在s2中對字符串進行了更改,那麼此時會創建一個副本,以使兩個字符串指向不同的緩衝區。

你的問題可能不是關於緩衝區本身,而是操作這些緩衝區的字符串對象,但它在字符串(和類似的std :: shared_ptr出於類似的原因)的情況下切線相關,其中複製性能是關心。複製std :: string類通常比複製底層緩衝區要少得多。

也就是說,關於你的代碼示例,還有一點值得解決,那就是使用這些函數的返回值做的事情(部分原因是因爲你詢問了其中兩個函數中的字符串後面是什麼)。

重複與輕微擴張:

#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);該參數是對字符串的引用(這些東西是從右向左解釋的,因此即使大多數人都說「字符串引用」,也應該認爲引用了字符串,這意味着函數沒有使用副本,它是調用者的函數,這個函數返回該字符串的地址,一個指針,這意味着沒有s的拷貝 - 我們至多會說一個指針的拷貝被返回,這與寫上xp = &s;

x = t3(&s)我們有一個好奇的例子,函數接受一個指針,它需要& s取s的地址來提供該指針,因此在函數調用時沒有s的副本,函數返回ar參照一個字符串(正如以前一樣,從右到左,儘管有些人可能會說一個字符串引用)。由於這是一個指針的解引用,所以結果只是通過它的地址引用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(); 

這裏,每個函數的返回值都是用來調用字符串的成員。與t1的調用已將s複製到函數中,然後再次複製以返回臨時字符串,然後銷燬該字符串(將調用析構函數),這是您在查詢中沒有真正解決的一點。

然而,t2和t3的呼叫實際上是使用s作爲呼叫而沒有任何暗示的副本。然而,在t2情況下,該調用是通過指針。 t2的情況就像寫了(奇怪的)(& s) - > length(),而t3的情況與寫入s.length()的情況相同。

T4與t3完全相同,只是在調用的方式和與nullptr傳遞給t3的可能性相關的含義上有所不同(導致解除引用導致崩潰),「 t發生在t4。

T5與t4(和t3)的區別僅在於,由於按值返回而隱含副本。返回的結果與t1類似,像t1一樣運行,並且僅與t1不同,暗示t5不會爲函數體創建一個副本,它只會爲返回創建一個副本。

假設你提供的,呼叫到t5之後追加主要示例代碼:

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所暗示的副本,以避免副作用,但仍然隱藏了按價值回報所隱含的副本。

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; } 

在這些功能,因爲您正在使用引用和指針工作,所以沒有新的對象既不是爲了論證,也沒有要求退貨創建的。(如果通過你的方式」不會有任何的構造函數和析構函數調用再不知道這兩者之間的區別是看看here.

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

在這其中不會有任何新的對象要麼,但不同的是,由於返回值是在基準形式您可以返回對象的值(*z instead of z),但如果您使用參考

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

最後,在到達返回部分時創建一個新對象。

0

我只會回答如果至少一個拷貝是因爲可能複製省略的必要的,因爲編譯器的優化:

  • T1:新副本返回(從傳遞的字符串不同):COPY
  • T2 :你得到一個參考並返回一個指針:NO COPY
  • t3:你拿一個地址並返回一個引用:NO COPY--如果指針爲空,可能會崩潰
  • t4:你接受一個引用並返回一個引用:無副本
  • T5:你把一個參考,並返回一個值:COPY

如果沒有優化,T1將需要2張:1創建一個從原始的字符串,另一個創造主叫返回副本臨時範圍,但只能有一個,如果有省音發生

T5只需要一個單一的副本創建呼叫者範圍返回副本