大家先看一个故事。张三和李四同时受雇于一家店铺,拿同样的薪水。一段时间后,张三青云直上,李四却原地踏步。李四想不通,老板为何厚此薄彼?
老板于是说:“李四,你现在到集市上去一下,看看今天早上有卖土豆的吗?”一会儿,李四回来汇报:“只有一个农民拉了一车土豆在卖。”
“有多少?”老板又问。
李四没有问过,于是赶紧又跑到集上,然后回来告诉老板:“一共40袋土豆。”
“价格呢?”
“您没有叫我打听价格。”李四委屈地申明。
于是赶紧又跑到集上,然后回来告诉老板:“3元一斤。”
“全买下可以打几折?”
……
老板又把张三叫来:
“张三,你现在到集市上去一下,看看今天早上有卖土豆的吗?”
张三也很快就从集市上回来了,他向老板汇报说:
“今天集市上只有一个农民卖土豆,我把那个农民带来了,他现在正在外面等您问话呢……”
张三的效率高,是因为他把一个封装所有有效信息的农民(对象)带回来了。而李四每次只带回一条信息。同理,对象作为参数或返回值传递效率要高。
此场景对应的程序包:(点击下载)bossFaculty.rar(下载附件 25.15 KB)
补充视频:警察抓小偷(可拖放)
头脑风暴:
一位名叫洪七的老记者(男,65岁)在街上采访一位名叫曾帅的路人(男,28岁)。记者问小伙子:“帅哥,贵姓?”小伙子回答记者:“哥姓曾!”
讨论:
1.这个场景可以定义多少个类呢?
2.可以用定义的类产生哪几个对象?
3.老记者对象要实现对曾帅这个小伙子对象的采访,如何在采访的方法中实现参数的传递(即对象之间的通信)呢?

值类型变量与引用类型变量
C#的数据类型分为两大类:值类型和引用类型。两种类型的变量在内存中的存储原理不同。
(1)值类型
简单地说,值类型变量就是一个包含实际数据的量。当定义一个值类型变量时,C#会根据变量所声明的类型分配一块堆栈存储区域给这个变量,然后对这个变量的读写就直接在这块内存区域进行。值类型包括简单类型(bool、int、double等)、结构类型和枚举类型。
上述有关值类型变量间赋值的代码解析如下:
int num1=139;//为num1分配一个4字节的存储区域,并将139存入这个存储区域
int num2=num1;
//为num2分配一个4字节的存储区域,并将num1的值复制后存入这个存储区域
num2=100;//将100存入num2 对应的存储区域
值类型变量间的赋值,赋的是变量值,赋值运算符两边的num1和num2是两个不同的存储区域,改变num2的值不会影响num1。
(2)引用赋值
引用类型变量存储的不是它所代表的实际数据,而是该实际数据的引用(地址)。创建一个引用类型变量,首先在堆栈内创建一个引用变量,然后在堆内分配一块存储区域来存储对象本身,再把对象在堆内的存储区域首地址赋值给引用变量。引用类型变量通常称为对象。引用类型包括类、接口、数组、委托和string等。
上述有关引用类型变量间赋值的代码解析如下:
Number num1=new Number() //创建并实例化一个引用变量num1
num1.Value=139; //为num1 的成员Value赋值为139
Number num2=num1; //创建一个引用变量num2,令num2指向num1所引用的对象,两者 //共同引用一个对象
num2.Value=100; //修改num2的成员值为100,由于num2和num1共同引用一个对象, //其实就是修改num1的成员
引用类型变量的赋值只是赋值对象的引用,而不复制对象本身。变量num1和num2共同引用一个对象,对其中任何一个做修改,另一个都会随之改变。
注意:引用类型变量的值与引用类型变量所引用的对象的区别:引用类型变量的值是一个地址,引用类型变量所引用的对象是该地址所指向的一个对象。
返回值与参数

方法的返回值可以是int、double等系统标准类型,也可以是用户自定义的类型,两者的用法没什么两样。调用时注意类型一致性。

因为C#的数据类型分为值类型和引用类型,传递参数的方式有传值方式和传引用方式,组合一下,C#传递参数有4种情况:值类型按传值方式传递,值类型按传引用方式传递,引用类型按传值方式传递,引用类型按传引用方式传递。


(建议全屏播放)

(1)定义一个“记者(Interviewer)”类,描述记者的姓名(name)、年龄(age)、性别(sex)、采访方法(interview)等成员。
(2)定义一个“路人(Interviewee)”类,描述行人的姓名(name)、年龄(age)、性别(sex)、回答方法(Response)等成员。
(3)在“路人”类中创建一个SetInterviewee的方法。
 |
| 图5.10 程序运行结果 |
(4)在Program程序类中根据给定的姓名、年龄和性别创建一个路人(Interviewee)对象并返回。
(5)在记者类中创建一个采访(Interview)方法,实现一个Interviewer对象对Interviewee的采访;在路人类中创建一个回答(Response)的方法。
案例运行结果如图5.10所示。

(1)巩固类的定义与对象的创建。
(2)掌握对象作为方法返回值的用法。
(3)掌握对象作为方法参数在传值方式下的用法。

(1)新建项目。具体操作如下:
① 在【文件】菜单中选择【新建】|【项目】命令,打开【新建项目】对话框。
② 在该对话框中选择【控制台应用程序】选项。
③ 在【名称】文本框中输入“p5_4”作为该项目的名称,在【位置】文本框中输入项目的保存目录,或者单击【浏览】按钮选择项目的保存目录。
(2)编写代码。具体操作如下:
① 双击【解决方案资源管理器】窗口中的Program.cs文件。
② 开始在Program.cs文件中编写代码。
(3)保存程序。
(4)调试运行程序。

本段代码中,请大家关注第30~34行的Interview()以及第56~59行的Response()两个方法。

(1)本案例程序代码中定义了一个“记者(Interviewer)”类,包含的字段有姓名name、年龄age、性别sex,还包含一个方法——Interview(Interviewee itvee),用于采访一个路人对象。
(2)定义了一个“路人(Interviewee)”类,包含的字段有姓名name、年龄age、性别sex,还包含一个方法——Response(Interviewer itver),用于回答记者的提问。
(3)在Program类中定义了一个CreateInterviewee()方法,这个方法用于创建一个路人对象并返回,返回值为路人对象。
路人和记者的年龄应该大于0小于120岁,为了防止不合法数据被赋给age,可以在age的属性中添加验证方法,代码修改如下:
由此例也可以看出,属性除了读写字段之外还具备更多的功能,例如,数据的合法性检查等。


方法的返回值可以是int、double等系统标准类型,也可以是用户自定义的类型,两者的用法没有什么区别。例如,案例p5_4中的方法CreateInterviewee()的返回值类型是自定义的Interviewee类型,用法与返回一个int 类型一样。调用时注意类型一致性:
Interviewee young = Program.CreateInterviewee("曾帅", "男", 26);

C#的数据类型分为两大类:值类型和引用类型。首先比较下面两段代码及其运行结果。值类型变量的赋值,程序运行结果如图5.11所示。
引用类型变量的赋值,运行结果如图5.12所示。

|  |
| 图5.11 值类型变量的赋值 | 图5.12 引用类型变量的赋值 |
为什么值类型变量间赋值与引用类型变量间赋值结果会有这么大的差别呢?原因是两种类型的变量在内存中的存储原理不同。正如前面所说,值类型变量间的赋值,赋的是变量值,赋值运算符两边的num1和num2是两个不同的存储区域,改变num2的值不会影响num1。引用类型变量的赋值只赋值对象的引用,而不复制对象本身。变量num1和num2共同引用一个对象,对其中任何一个做修改,另一个都会随之改变。
注意:引用类型变量的值与引用类型变量所引用的对象的区别:引用类型变量的值是一个地址,引用类型变量所引用的对象是该地址所指向的一个对象。

因为C#的数据类型分为值类型和引用类型,传递参数的方式有传值方式和传引用方式,根据数学中的排列组合可知,C#传递参数有4种情况:值类型按传值方式传递,值类型按传引用方式传递,引用类型按传值方式传递,引用类型按传引用方式传递。前两种关于值类型的传递可扩充案例p5_4_1来进一步学习引用类型参数的传递,把引用类型按传值方式传递与引用类型按引用方式传递的情况进行对比。
把采访案例程序代码中的Interviewee类的Response方法修改如下,代码的其余部分保持不变:
然后在Program类的Main()函数中添加一个以下语句:
程序运行结果如图5.13所示。
 |
| 图5.13 引用类型的值传递 |
这里,Response()方法中虽然加了如下两个语句:
Interviewer it=new Interviewer("梅超风","女",50);
itver=it;
但形参并没有改变实参,所以在Main()函数中,记者的名字仍为“洪七”。这个和值类型按值方式传递原则是一致的。
如果把Response()方法的形参加上ref,即public void Response(ref Interviewer itver),同时将Main()函数中的young.Response()的实参改为young.Response(refold),程序运行结果将会如图5.14所示。
 |
| 图5.14 引用类型的引用传递 |
结合运行结果,观察Response()方法,对象以传值方式传递,相当于两个引用类型变量进行赋值,传递的是对象的引用(地址),形参itver和实参old共同引用同一个对象(同一块内存区域)。因此,形参地址所指向的内容改变直接影响实参。
ref修饰符说明对象以传引用方式传递,传递的是引用类型变量本身的堆栈地址,那么在方法内部重新创建新对象赋值给形参,相当于直接在原old对象的地址上,用新创建的对象it将其覆盖了。