C++:為何不建議用string作為函數參數

在C++中,函數形參一般帶“&”和“*”,例如:int*、char*、int&等。這是為什麼呢?此處以常用的string字符串數據類型來引入今天要講的話題;

在C語言中是沒有字符串這個數據類型的,C++在C語言的基礎上升級了string類。如果很多從CSharp、Java等高級語言轉C++的開發人員在用字符串做函數參數的時候都會很自然的用string類型,但是作為正宗的C++開發者都會建議你不要用string類型作為函數參數。

CSharp的數據類型

CSharp裡面把數據類型分為兩大類,值類型和引用類型;值類型包括基本數據類型(int ,double等),結構和枚舉;引用類型包括接口,數組,Object類型,類,委託,字符串,null類型等;一般來說,結構體類型的數據就是值類型,而類構造的數據就是引用類型;

引用類型和值類型都繼承自System.Object類。不同的是,幾乎所有的引用類型都直接從System.Object繼承,而值類型則繼承其子類,即直接繼承System.ValueType。

請看下面這段代碼:

 public class People
{
public string Name { get; set; }
public int Age { get; set; }
public void reset(string name, int age)
{
name = "xiaoli";
age = 200;
}
public void reset(People p)
{
p.Name = "xiaoli";
p.Age = 200;

}
}
class Program
{
static void Main(string[] args)
{
string name = "xiaoming";
int age = 100;
People p = new People();
p.Name = name;
p.Age = age;
People p2 = new People();
p2.Name = "xiaohua";
p2.Age = 90;
Console.WriteLine(p.Name);
Console.WriteLine(p.Age);
p.reset(name,age);
Console.WriteLine(name);
Console.WriteLine(age);
p.reset(p);
Console.WriteLine(p.Name);
Console.WriteLine(p.Age);
Console.Read();
}

運行結果為:

xiaoming
100
xiaoming
100
xiaoli
200

int類型為值類型,形參是不能改變實參的值的,但是People是引用類型,形參可以改變實參的值。引用類型指向的其實是一個內存地址,string 雖然是引用類型 不過是不可變的。

但是CSharp提供ref關鍵字來使得值類型的實參在傳遞時也可以被修改

public class People
{

public string Name { get; set; }
public int Age { get; set; }
public void reset(ref string name, ref int age)
{
name = "xiaoli";
age = 200;
}
public void reset(ref People p)
{
p.Name = "xiaoli";
p.Age = 200;
}
}
class Program
{
static void Main(string[] args)
{
string name = "xiaoming";
int age = 100;
People p = new People();
p.Name = name;
p.Age = age;
People p2 = new People();
p2.Name = "xiaohua";
p2.Age = 90;
Console.WriteLine(p.Name);
Console.WriteLine(p.Age);
p.reset(ref name,ref age);
Console.WriteLine(name);
Console.WriteLine(age);
p.reset(ref p);
Console.WriteLine(p.Name);
Console.WriteLine(p.Age);
Console.Read();
}
}

為何C++函數參數都帶*或者&

前面通過CSharp講到數據分值類型和引用類型,在C++中,引用類型是要顯示定義的。

int n;
int& r = n;

如果在函數參數傳遞時,形參不聲明為引用類型,參數傳遞方式是傳值的。傳引用的方式要求函數的形參是引用。

那究竟形參帶&與不帶有什麼區別呢?

void func(int& i){i = 110 ;}
void func2(int i){i = 110 ;}
void main()
{
\tint n = 200 ;
\tint n2 = 300 ;
\tfunc(n);
\tfunc2(n2);
\tcout<\tcout<\tsystem("pause");
}

首先看運行結果:

110
300

再看彙編的區別:

\tfunc(n);
00C4443C lea eax,[n]
00C4443F push eax
00C44440 call func (0C41271h)
00C44445 add esp,4
\tfunc2(n2);
00C44448 mov eax,dword ptr [n2]
00C4444B push eax
00C4444C call func2 (0C4143Dh)
00C44451 add esp,4

先說一下幾個指令:

lea:load effective address, 加載有效地址,可以將有效地址傳送到指定的的寄存器;

mov:在CPU內或CPU和存儲器之間傳送字或字節

push:入棧

從上面運行結果和彙編代碼來看,當採用引用類型作為形參時,它將變為實參列表中相應變量的別名,對形參進行的任何更改都將真正更改正在調用它的函數中的變量。引用變量本身並不需要分配內存,而是直接對被引用對象取的別名;

C++:為何不建議用string作為函數參數

引用變量示意

當採用值傳遞方式時:當函數被調用時,在“棧”中就會分配出一塊新的存儲空間,用來存放形參和函數中定義的變量(局部變量)。實參的值會被複制到棧中存放對應形參的地方,所以形參的值才等於實參。函數執行過程中對形參的修改,其實只是修改了實參的一個拷貝,因此不會影響實參。

好處

從彙編代碼可以看出,普通值傳遞的傳參方式,是需要複製實參的,然後將實參的賦本傳遞給形參。而引用傳參方式直接取實參的有效地址,所以在運行效率上引用傳參速度更快更節省內存開銷。實參對象所佔內存越大,開銷越大,對效率的影響也越大;

指針傳遞(地址調用)

還有一種方式也能避免普通值傳參方式的大開銷和低效率問題,那就是直接將對象的地址傳遞給形參。

void func(int& i){i = 110 ;}
void func2(int i){i = 110 ;}
void func3(int* i){*i = 110 ;}
void main()
{

\tint n = 200 ;
\tint n2 = 300 ;
\tint n3 = 400 ;
\tint* n4 = &n3 ;
\tfunc(n);
\tfunc2(n2);
\tfunc3(&n3);
\tfunc3(n4);
\tcout<\tcout<\tcout<\tsystem("pause");
}

查看彙編代碼:

\tfunc(n);
00327009 lea eax,[n]
0032700C push eax
0032700D call func (0321271h)
00327012 add esp,4
\tfunc2(n2);
00327015 mov eax,dword ptr [n2]
00327018 push eax
00327019 call func2 (032143Dh)
0032701E add esp,4
\tfunc3(&n3);
00327021 lea eax,[n3]
00327024 push eax
00327025 call func3 (0321442h)
0032702A add esp,4
\tfunc3(n4);
0032702D mov eax,dword ptr [n4]
00327030 push eax
00327031 call func3 (0321442h)
00327036 add esp,4

從上述彙編代碼可以看出func3(n4);本質上是值傳遞,它所傳遞的是一個地址值,不管其所指向的對象如何,其在函數調用時複製的只是一個四字節的地址而已,對內存開銷和效率影響不大。而func3(&n3);卻又是引用傳參方式。

如何避免實參被修改

有沒有什麼辦法既保證效率又保證實參的安全性?既然不想實參在函數調用過程中被修改,那麼C++有一個關鍵字--const可以達到目的。

void func(const int& i){i = 110 ;}
void func3(const int* i){*i = 110 ;}

上述兩個方法體的代碼都會報錯:表達式必須是可修改的左值。


分享到:


相關文章: