一道关于string的题目
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void StrChange( string str)
{
str = "0";
}
static void IntChange(int num)
{
num = 0;
}
static void ArrayChange(int[] arr)
{
arr[0] = 0;
}
static void Main(string[] args)
{
string str = "1";
int num = 1;
int[] arr=new int[]{1,2,3};
StrChange(str);
IntChange(num);
ArrayChange(arr);
Console.WriteLine(str);
Console.WriteLine(num);
Console.WriteLine(arr[0]);
Console.ReadKey();
}
}
}
请问输出结果是什么?
正确答案是:1 1 0
按照上面的结果来看,string和int一样成值类型了吗?
这个问题的答案虽然很多人都知道,但是分析过程却千差万别。
有的人说string是特殊的引用类型,其实这跟什么string是不是“特殊的”引用类型没有关系。
让我们写一个关于类MyTest的“普通”的引用类型:
static void StrChange(MyTest str)
{
str = new MyTest { XX = "hello" };
}
static void Main()
{
MyTest str = new MyTest { XX = "123" };//申明一个字符串
StrChange(str);//调用方法
Console.WriteLine(str.XX);//输出字符串
Console.ReadKey();
}
class MyTest
{
public string XX;
}
输出结果仍然是123
对于这个问题到底该怎么解答好,CSDN网友的解答可以作为参考:
解答一.这个地方要搞清楚语法和实现的区别
在C#的语法中 static void StrChange(string str) 是值传递
static void StrChange(ref string str) 是引用传递,
但在实现上
static void StrChange(string str) 这种值传递,在函数体内对str进行修改之前,与函数外部的变量指向同一块内存,是“引用”传递,但在函数体内对str修改后,就会触发对该str重新分配一块内存。
解答二.看一下示例
private void button2_Click(object sender, EventArgs e)
{
string str="aaa";
string str1 = str;
str = "bbb";//注释掉此名就"yes",否则"no".这就说明str重新赋值的时候,
//其实是重新创建了一个名为str的字符串(内存中指向的位置是不同的),先前
//的那个str你就再也看不到了.
string str2 = str;
if (object.ReferenceEquals( str1,str2))
{
MessageBox.Show("yes");
}
else
{
MessageBox.Show("no");
}
}
{
string str="aaa";
string str1 = str;
str = "bbb";//注释掉此名就"yes",否则"no".这就说明str重新赋值的时候,
//其实是重新创建了一个名为str的字符串(内存中指向的位置是不同的),先前
//的那个str你就再也看不到了.
string str2 = str;
if (object.ReferenceEquals( str1,str2))
{
MessageBox.Show("yes");
}
else
{
MessageBox.Show("no");
}
}
解答三.这是按值传递参数的问题,和是否引用类型没关系,更和string类型没关系
NET中除非显式声明按引用传递,即ref和out关键字,全部是按值传递参数...也就是说在跳出方法时,对参数str本身所做的任何更改都被丢弃,不会改变str本身的值...值类型引用类型都是如此,没有不同...(MSDN佐证:http://msdn.microsoft.com/zh-cn/library/0f66670z.aspx)
按值传递的定义非常明确,即传递对象的“副本”,不管是引用类型还是值类型...
再强调一次,按值传递时引用类型传递的是“引用的副本”,在跳出方法时副本将被丢弃,对此副本所做的任何更改都不会影响对象本身...但引用类型和值类型还是有区别,由于引用可以看作是堆内存地址的指针,因此对此副本的成员所做的更改可能会更改对象的成员.
再强调一次,按值传递时引用类型传递的是“引用的副本”,在跳出方法时副本将被丢弃,对此副本所做的任何更改都不会影响对象本身...但引用类型和值类型还是有区别,由于引用可以看作是堆内存地址的指针,因此对此副本的成员所做的更改可能会更改对象的成员.
相信看了这三个解答之后,这个问题就差不多解决了。
参考一(摘自MSDN)
当“按引用”传递引用类型时,方法希望使用参数来返回对象的另一个实例。(通过引用传递引用类型也称为使用双指针、指针的指针或双重间接寻址)。如果使用“按值”传递的默认调用约定,则采用引用类型的参数已经收到指向对象的指针。值传递的是指针,而不是指针所指向的对象。通过值传递意味着方法无法将指针更改为指向引用类型的新实例,但可以改变指针所指向的对象的内容。对于大多数应用程序,这种改变已经足够,可以产生您需要的行为。
如果某方法必须返回不同的实例,请使用该方法的返回值完成此操作。有关对字符串进行操作和返回字符串的新实例的各种方法,请参见 System.String 类。通过使用此模型,由调用方来决定是否保留原始对象。
虽然返回值比较常见并经常使用,但正确应用 out 和 ref 参数需要中间设计和编码技能。针对普通用户进行设计的库架构师不应期望用户掌握了 out 或 ref 参数的使用方法。
当您使用大型结构参数时,复制这些结构所需要的附加资源在您按值传递时可能会造成性能影响。在这些情况下,您可以考虑使用 ref 或 out 参数。
如果某方法必须返回不同的实例,请使用该方法的返回值完成此操作。有关对字符串进行操作和返回字符串的新实例的各种方法,请参见 System.String 类。通过使用此模型,由调用方来决定是否保留原始对象。
虽然返回值比较常见并经常使用,但正确应用 out 和 ref 参数需要中间设计和编码技能。针对普通用户进行设计的库架构师不应期望用户掌握了 out 或 ref 参数的使用方法。
当您使用大型结构参数时,复制这些结构所需要的附加资源在您按值传递时可能会造成性能影响。在这些情况下,您可以考虑使用 ref 或 out 参数。
参考二:C# 类型体系(摘自MSDN)
C# 类型体系包含下列几种类别:
-
值类型的变量存储数据,而引用类型的变量存储对实际数据的引用。
通过装箱和取消装箱,可以将值类型转换为引用类型,然后再转换回值类型。除了装箱值类型外,无法将引用类型转换为值类型。
值类型主要由两类组成:结构和枚举
结构分为以下几类:整型、浮点数类型(float、double)、十进制类型(decimal)、字符类型(char)、布尔型(bool)、用户定义的结构。
-
引用类型也称为对象。
引用类型包括:类(class、object、string)、接口(interface)、数组(array)、代理(delegate) 类包括:用户自定义的类、object基类、字符串类
-
指针类型仅可用于 unsafe 模式。