在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()
{
int n = 200 ;
int n2 = 300 ;
func(n);
func2(n2);
cout<<n<<endl;
cout<<n2<<endl;
system("pause");
}
首先看运行结果:
110
300再看汇编的区别:
func(n);
00C4443C lea eax,[n]
00C4443F push eax
00C44440 call func (0C41271h)
00C44445 add esp,4
func2(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:入栈
从上面运行结果和汇编代码来看,当采用引用类型作为形参时,它将变为实参列表中相应变量的别名,对形参进行的任何更改都将真正更改正在调用它的函数中的变量。引用变量本身并不需要分配内存,而是直接对被引用对象取的别名;
引用变量示意当采用值传递方式时:当函数被调用时,在“栈”中就会分配出一块新的存储空间,用来存放形参和函数中定义的变量(局部变量)。实参的值会被复制到栈中存放对应形参的地方,所以形参的值才等于实参。函数执行过程中对形参的修改,其实只是修改了实参的一个拷贝,因此不会影响实参。
好处
从汇编代码可以看出,普通值传递的传参方式,是需要复制实参的,然后将实参的赋本传递给形参。而引用传参方式直接取实参的有效地址,所以在运行效率上引用传参速度更快更节省内存开销。实参对象所占内存越大,开销越大,对效率的影响也越大;
指针传递(地址调用)
还有一种方式也能避免普通值传参方式的大开销和低效率问题,那就是直接将对象的地址传递给形参。
void func(int& i){i = 110 ;}
void func2(int i){i = 110 ;}
void func3(int* i){*i = 110 ;}
void main()
{
int n = 200 ;
int n2 = 300 ;
int n3 = 400 ;
int* n4 = &n3 ;
func(n);
func2(n2);
func3(&n3);
func3(n4);
cout<<n<<endl;
cout<<n2<<endl;
cout<<n3<<endl;
system("pause");
}
查看汇编代码:
func(n);
00327009 lea eax,[n]
0032700C push eax
0032700D call func (0321271h)
00327012 add esp,4
func2(n2);
00327015 mov eax,dword ptr [n2]
00327018 push eax
00327019 call func2 (032143Dh)
0032701E add esp,4
func3(&n3);
00327021 lea eax,[n3]
00327024 push eax
00327025 call func3 (0321442h)
0032702A add esp,4
func3(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 ;}
上述两个方法体的代码都会报错:表达式必须是可修改的左值。