这几天在看C++标准,其中看到C++引用,用于函数传递参数要比之前传递指针安全,方便.所以想到把C#里相关概念拿来比较下.
如下是在C#的测试代码:
public class Book { public string Name { get; set; } public int Id { get; set; } } public struct StructBook { public string Name { get; set; } public int Id { get; set; } } class Program { static void testValue(StructBook book) { book.Id = 1; book.Name = "n1"; } static void testPtr(Book book) { book.Id = 2; book.Name = "n2"; book = new Book(); book.Id = 3; book.Name = "n3"; } static void testRef(ref StructBook book) { book.Id = 4; book.Name = "n4"; //book = new StructBook(); //book.Id = 5; //book.Name = "n5"; var xx = new StructBook(); xx.Id = 5; xx.Name = "n5"; book = xx; } static void testPtrRef(ref Book book) { book.Id = 6; book.Name = "n6"; book = new Book(); book.Id = 7; book.Name = "n7"; } static void Main(string[] args) { StructBook sutbook = new StructBook(); sutbook.Id = 0; sutbook.Name = "n0"; Console.WriteLine("----value:"); testValue(sutbook); Console.WriteLine(sutbook.Id); Console.WriteLine(sutbook.Name); Book book = new Book(); book.Id = sutbook.Id; book.Name = sutbook.Name; Console.WriteLine("----reference value:"); testPtr(book); Console.WriteLine(book.Id); Console.WriteLine(book.Name); sutbook.Id = book.Id; sutbook.Name = book.Name; Console.WriteLine("----value reference:"); testRef(ref sutbook); Console.WriteLine(sutbook.Id); Console.WriteLine(sutbook.Name); book.Id = sutbook.Id; book.Name = sutbook.Name; Console.WriteLine("----reference reference:"); testPtrRef(ref book); Console.WriteLine(book.Id); Console.WriteLine(book.Name); Console.Read(); } }
参数传递
上面主要演示C#中值传递与引用传递二种类型(值类型与引用类型)的不同.先看一下运行结果.
结果没什么好说的,差不多是被大家讲烂了的.简单来说,C#里默认全是值传递,值传递就是在函数内生成一份临时变量,对临时变量的修改不会影响本身.不同的值类型传递的是本身,而引用类型传递的是他引用.对应的结果就是上面的testPtr与testRef.一种是值类型,修改的是临时变量,在函数调用外是不变的.另外是引用类型,如果是修改引用里的值,这是可以的,而如果修改的是引用本身(这个是值传递的临时变量),就是testPtr里的book = new Book();这个是修改是影响不到函数外部的.
至于testRef与testPtrRef,都属性引用传递,那么他传递的就是他本身,在函数内的修改都会直接修改他本身,都会影响函数外面.
我们对应上面几个函数,在C++中,也增加相应的一些方法,大家可以看到一些相同与不同之处.
using namespace std; class Book { public: string name; int id; Book() { cout << "Create new Book: " << this << endl; } ~Book() { cout << "Delete Book in: " << this << endl; } //void operator = (const Book& book) //{ //} string(iName)() { return name; } }; void testValue(Book book) { cout << "temp address:" << &book << endl; book.id = 1; book.name = "n1"; } void testPtr(Book* book) { book->id = 2; book->name = "n2"; cout << "old address:" << book << endl; book = new Book(); cout << "new address:" << book << endl; book->id = 3; book->name = "n3"; delete book; } void testRef(Book& book) { book.id = 4; book.name = "n4"; cout << "old address:" << &book << endl; auto xx = Book(); xx.id = 5; xx.name = "n5"; //这句话会调用 = 操作符.并没有发生地址操作. //所以后面的删除的临时变量不会对book本身有影响 book = xx; cout << "new address:" << &book << endl; //内存分配的位置不管是在堆还上栈上不影响结果 //auto nb = new Book(); //nb->id = 55; //nb->name = "n55"; //cout << "old value:" << &book << endl; //book = *nb; //cout << "new value:" << &book << endl; //delete nb; } void testPtrRef(Book*& book) { book->id = 6; book->name = "n6"; auto xx = new Book(); xx->id = 7; xx->name = "n7"; cout << "old address:" << book << endl; delete book; book = xx; cout << "new address:" << book << endl; } void test19() { auto xb = new Book(); xb->id = 0; xb->name = "n0"; cout << "----value:" << endl; testValue(*xb); cout << xb->id << endl; cout << xb->name << endl; cout << "----pointer value:" << endl; testPtr(xb); cout << xb->id << endl; cout << xb->name << endl; cout << "----reference value:" << endl; testRef(*xb); cout << xb->id << endl; cout << xb->name << endl; cout << "----pointer reference:" << endl; testPtrRef(xb); cout << xb->id << endl; cout << xb->name << endl; delete xb; }
C++ 函数传递
我们可以看到与C#中相似的结果.在C++中,主要是值传递,指针传递,引用传递.指针传递也可以归到值传递中,因为指针传递的指针也是要生成临时指针.就表现的现象来说,C++中的值传递和C#的值传递值类型比较相似,C++中的指针传递和C#中的值传递引用类型比较相似,C++中的指针引用传递和C#的引用传递(包含值类型与引用类型)比较相似.
C#中,结构是值类型,类是引用类型,而C++中,结构与类可以说没有区别,所以上面比对中,C++的传递方式要对比C#的传递方式在类型方式,还有上面比对中,我没说C++的引用传递和C#的什么比较像,只说C++中的指针引用传递和C#的引用传递比较像.那么C++的单独引用传递和C#的有那些不同.
C++中,testValue里我们可以看到,book的地址变了,说明和C#一样,book是临时变量,在testValue完成后,直接调用析构函数删除临时变量.
在testPtr中,我们可以看到book在new后,book是指向了新的地址,但是在外部调用显示book时,还是前面2与n2,这里delete book有和没有,结果是一样的,只是会有内存泄漏.这个的表现和C#值传递引用类型一样,在重新new后,对book所做的更改都无效,因为指针传递说到底还是值传递,传递的是地址,就是说,这个在testPtr会重新生成一份,但是他的指向与原变量一样.所以前面针对id与name的设置都会更新到函数外部,但是这个临时变量重新new后,他就已经和main里的book所指向的位置不一样,在家可以对比看下输出.我们通过这个也能更容易理解C#值传递引用类型内部发生的情况.
而在C++的testRef中,虽然结果和C#的testRef一样,但是又可以说完全不是一回事.在C++,引用有比较严格的规定,比如引用初始化后,就不能再修改引用指向的对象.可以看输出结果里的地址的对比.而C#中的引用传递的值是可以重新修改指向的对象.具体可看传递引用类型参数(C# 编程指南).C++的引用像是* const,或是说加强限定的指针与一些语法糖的组合.至于结果显示是n5与5,主要是默认的赋值操作引起的,我们可以在Book类里重新定义赋值操作,什么都不做,就可以看到结果是4与n4了.
在testPtrRef,我们传递指针的引用过去,通过输出结果上的地址显示,我们可以看到我们在函数内重新指向新的位置,改动能带到函数外.当然指针的指针也能做到这个效果.就和前面说的一样,引用应是一种特殊的指针表示方式,引用能做的到,指针也能做到,反之就不一定了.同时我们也可以猜测C#中,引用类型当值传递时,相当于默认加上指针,而用引用传递时,相当于有个二级指针.
下面是原来看过几道测试题,最后一个如何具体化?
//float(*(*f)(float, float))(float); void test7() { float(**a)[10];//float 数组 指针 指针 float(*aa)[10]; //float 数组 指针 float aaa[10]; aa = &aaa; a = &aa; aaa[0] = 4; cout << ***a << endl; float*(*b)[10];//float* 数组 指针 float* bb[10];//指针数组 bb[0] = new float[2]; b = &bb; bb[0][0] = 5; cout << ***b << endl; float(*c[10])(); c[0] = [](){return 6.0f; }; cout << (**c)() << endl; float*((*d)[10]);//float* 数组 指针 float*(dd[10]); dd[0] = new float(7); d = ⅆ dd[0][0] = 7.0f; cout << ***d << endl; float(*e)(); e = [](){ return 8.0f; }; cout << e() << endl; //如何具体化. float(*(*f)(float, float))(float); auto ff = fvv; }