`
lovnet
  • 浏览: 6703179 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
文章分类
社区版块
存档分类
最新评论

C++与C#对比学习:函数(一)C#参数传递存在的问题

 
阅读更多

函数可以把代码模块化,而且便于代码重复利用.继承自C++的C#,两者函数的用法大体差不多,不过的有些细小的差别.在整个C#的语法体系中对一些类型的检查相较C++更严,这样也减少了代码中可能潜在的错误.

函数是由返回值类型,函数名,参数列表,函数体四大部分组成.

我们知道每个变量都会取一个名字,在相同的作用域内不能定义两个相同名字的变量.函数自然也一样.不过区别函数不是只靠函数名,而是用函数名加参数列表区分,函数名和参数列表又叫函数签名.至于返回值类型和函数体同不同就不去管了啊.

参数类型

C++和C#中返回值类型都必须显式指定,如果返回类型为空必须用void表示.比如 void Function(int a, int b);而在C中是可以不显式指定.比如Function(int a);它默认返回值是int形.

我们定义函数中的参数列表我们又叫形参列表,当实际调用函数时会传入参数,此时参数的值叫实参.C++中定义形参时可以只指定类型,可以不指定类型名比如void Function(int , int);而在C#中这样是错误的,必须指定参数名.比如void Function(int a, int b);

当传入实参时编译器会做类型匹配的判断.如果类型不匹配会报错.如果实参类型能隐式转换成形参类型也可以.不过C++中类型转换要求比较松.所有的数值类型都可以让你互相转换.

比如void Fun(unsigned shortsh); //此时传int ,float,long,double进去都没问题. 不过显然传其他类型进去容易出问题.如果是正数的int,float之类的可能还只会精度不一样.但如果传个负数进去结果就完全错误了啊.当然如果传个string进去肯定就出错了.

而在C#中是只有进行类型转换时不带来任何精度的损失,并且形参是无符号的类型时不能传负数.比如void Fun(uint num); //此时传short , 或正数int都没问题.如果传-3,或者3.14之类的都不成.

C++参数传递

我们会经常听说啥按值传递,按引用传递.挺容易搞晕乎的.其实把参数和普通变量做个对比就很容易理解了啊.

比如有函数

void Arwen(int age, string& name)

{

age = 88;

name = "weiwen";

}

int myAge = 24;

string myName = "wen";

Arwen(myAge,myName);

cout<<myAge<<myName; //此时myAge仍为24,而myName为weiwen

我们可以把上面的过程等同下面的操作

//首先是定义两变量

int myAge = 24;

string myName = "wen";

//然后又定义两变量

{

int age = myAge;

int& name = myName;

age = 88;

name = "weiwen";

}

这下容易看明白了吧.只不过参数参数列表中的变量作用域只是整个函数体,而且实参名和形参名相同的话也没影响.

C#参数传递

上面讲的是C++,再来看下C#,C#中也一样有啥值传递,引用传递.值传递跟C++是一样了.而按引用的话就不是用&这样的符号,而是换了个用法在前面加关键字ref或out.

不过实际上C#中ref,out这样的按引用传递只能值类型比较有用,而对引用类型用处不大,因为引用类型实际上已经相当是一个引用类型了,所以你用ref或者不用其实达到的效果差不多.不过引用类型中有的string类型有点特殊,你用ref和不用是区别很大的.string虽然是引用类型,但C#使用了一些特殊处理使string用起来跟值类型效果一样了.

上面的函数应该这样写

void Arwen(int age,ref stringname)

{

age = 88;

name = "weiwen";

}

int myAge = 24;

string myName = "wen";

Arwen(myAge,ref myName);

Console.Write(myAge);

Console.Write(myName);

//此时myAge仍为24,而myName为weiwen

ref,out 用法基本差不多.只不过有点小区别.就是ref的话传值进去时要保证已经初始化赋值.

例如string myName;

Arwen(myAge,ref myName); //这样就会出错,myName还没初始化.

用out的话可以不初始化,但在函数体中传进去的值必须被修改重新赋值了.

例如void Arwen(out string name) { return;}

string myName ;

Arwen(out myName); //出错了,因为函数体中没有修改myName的值,

如果把函数改成void Arwen(out string name) { name = "wei";}

这样再用Arwen(out myName)调用就正确了.

ref,out机制要达到的目的很简单,就是在传值时保证在任何情况下都让传进去的参数都初始化了,要么传之前,要么传了之后.

C++指针形参

比如有函数

void Change( int* ip)

{

*ip = 22;

ip = 0;

}

int num = 11;

int* pNum = &num;

Change(pNum);

cout<<*pNum; //值为22.

仍然可以用前面说的方法把上面的等价看成下面操作

int num = 11;

int* pNum = &num;

{

int* ip = pNum;

*ip = 22;

ip = 0;

}

//有时为了防止指针所指的数值在函数体中被修改可以用const修饰指针就OK了,比如void Change(const int* ip);

C#中的 “指针形参”

C#中有个容易误导人的地方,就是有所谓的值类型,引用类型.这其实和按值传递,按引用传递是两个完全不一样的概念.

C#中的所有基本类型还有struct都是值类型,创建是在stack中.而string,数组和自定义的class都属于引用类型.虽然C#中没有指针,但实际上我们可以把所有的引用类型都看成指针类型.因为引用类型的值是两块的,一个是在stack中的一个保存地址的值,实际上就是C++中的指针.(32位系统中是两个字节),而实际的值在heap中,只不过C#中的heap都被CRL托管了,分配内存释放内存不用你管了.所以引用类型和C++中用new 关键字申请heap中一块内存然后把地址值赋给一个指针的原理一样.

所以C#的形参如果是引用类型的话则相当于C++中的指针形参,或者引用类型形参

反正C#中的引用类型就相当于是C++中的指针类型,或者更准确的讲有点像个const类型的指针,像个C++中的引用类型.

而按引用传递就跟C++的按引用传递差不多.

C++与C# "指针形参"优劣对比

我们知道如果一个参数太大的话用值传递不太好,在C++中就会用指针或引用传递.而在C#中是完全不用担心这个问题,因为C#中所有复杂的非基本的类型都是引用类型,传递引用类型时就赞同传递一个指针了.

不过假设如果有这样一种情况.某个参数比较大,我们自然就会用指针或引用去传递.但我知道知道指针或引用方便是方便了,但相较值传递有个不好的的地方就是指针或引用可以改变原参数的值,这样就可能有潜在的风险.在C++中很简单,直接用const修饰下指针或引用就OK.

但这个问题在C#中可就是个大问题了啊.

还是举个例子先吧,假如有个类Arwen.里面数据非常多,而且都非常重要,会占很多内存空间.在某个时候我们想把类Arwen当作一个参数传给某个函数.但同时由于类中的数据很重要,不希望在函数中被修饰了.这里涉及到了两个问题,

1.不传参数本身的值,只传个引用地址过去

2.不在函数中修改参数的值

针对1.不传所有值比较好办

在C#中的话

void Fun(Arwen an)

{

//do something

return;

}

Arwen an = new Arwen();

Fun(an); //这样就OK了,an传了就相当于是一个指向类Arwen的指针

在C++中的话指针,引用都行.

void Fun(Arwen* an){ return;}

Arwen* an = new Arwen();

Fun(an);

针对问题2不修改参数值

C++中

void Fun(const Arwen* an)

{

// do something

return;

}

那针对问题2,C#咋整呢?

你刚开始也想着像C++中指针或引用一样加个const吧,你编译的时候就出错了,不行.为啥不行呢,首先嘛C#引用类型虽然有点类似指针和引用.但毕竟只是类似.C#中是没有指针的概念的.而且如果在形参中如果能用const修饰的话,那要求你传的参数肯定也只能是const,那还不如直接把参数弄成const得了.直接const Arwen an;然后Fun(an)这样就行了嘛.

所以C#中形参除了用ref,out修饰外是不可以用其他任何东东修饰的.(另外补充下,C#中的const跟C++中的不一样,C#中的const具有c++中const的一切属性外还具有C++中static的属性.而C#中的readonly倒是和C++中的const是差不多一样的)

那既然C#中不能做到在函数中不让修饰传进来的参数,可咋整啊?只能傻眼了吗?

实际上不是完全没办法.只不过是个非常非常笨的办法.不传类Arwen的引用,而传它的值进去.在C++中传值的话就不用指针啥的,直接传就是传值了.但我们知道C#中引用类型默认都是传个引用的啊,咋传值啊? 直接肯定不行的,只能先声明另外一个变量sbArwen,然后把对象Arwen an;中的值拷贝过去.然后再传到函数中.而且要拷贝的话还得整个拷贝函数才行啊.如果类Arwen中没有继承个啥IClonable,弄个拷贝函数在那,那你也没法直接拷贝它.那咋整啊.那不用整了啊,这样就真没辙了.

所以针对问题1和问题2,C++是能完全优美的解决,而C#不能同时解决两个.如果要解决第2个问题就不能解决问题1,而且要用个非常笨的办法.

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics