刘铁猛《C#语言入门详解》全集

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
namespace 刘铁猛课程练习
{
internal class 参数
{
//传值参数(也称值参数):指在声明的时候不带任何修饰符的参数,这种参数的本质就是作用域为当前方法的一个局部变量,而且它的初始值为调用这个方法的时候赋给它的实参的值。
//输出参数:指在声明时使用out关键字进行修饰的参数
//引用参数:指在声明时使用ref关键字进行修饰的参数
//数组参数:指在声明时使用Params关键字进行修饰的参数
//具名参数:值在调用方法的时候,传进去的参数是带有名字的参数。
//可选参数:具有默认值的参数
//拓展方法(使用this参数)
static void Main()
{
//传值参数---------------------------------------------------------------
//传值参数—值类型
int y = 100;
传值参数_值类型(y); //输出101,副本的值经过运算发生改变
Console.WriteLine(y); //输出100,原本的值不会受到影响。
//引用类型—并且新创建对象
Student5 stu = new Student5() { Name = "Tim" };
传值参数_引用类型并且新创建对象(stu); //输出:46104728,Tim
Console.WriteLine($"{stu.GetHashCode()},{stu.Name}"); //输出:12289376,Tim
//可以看出输入Name的值都被赋予"Tim",但是其HashCode并不相同,这是两个独立存在的对象。
//引用类型—只操作对象不新创建对象
Student6 stu2 = new Student6() { Name = "Tim" };
传值参数_引用类型只操作对象不新创建对象(stu2); //输出:43495525,Tim
Console.WriteLine($"{stu2.GetHashCode()},{stu2.Name}"); //输出:43495525,Tim
//由于变量和参数指向的是同一个对象,通过参数修改对象的值后,当尝试用变量再去访问这个对象的时候
//拿到的也是更新之后的值。 通过HashCode可以看出,它们确实是同一个对象。
//引用参数---------------------------------------------------------------
//引用参数—值类型
int y1 = 1;
引用参数_值类型(ref y1); //输出:101
Console.WriteLine(y1); //输出:101
//因为方法的参数它指向的地址和方法外部变量指向的内存地址是同一个地址,这样当我们在方法内部改变了这个地址
//上的值之后,方法外部的变量它所指向的内存地址当中的值就已经改变了。这时候当我们通过外部的变量去访问内存地址当中
//的值的时候,拿到的就是更新之后的值。所以y1为101.
//引用参数—引用类型并创建新对象
Student7 outerStu = new Student7() { Name = "Tim" };
Console.WriteLine($"{outerStu.GetHashCode()},Name={outerStu.Name}"); //输出:55915408,Name = Tim
Console.WriteLine("————————————");
引用参数_引用类型并创建新对象(ref outerStu); //输出:33476626,Name = Tom
Console.WriteLine($"{outerStu.GetHashCode()},Name={outerStu.Name}\n"); //输出:33476626,Name = Tom
//55行对52行的outerStu类型新建了一个对象并初始化了Name属性的值,再将其应用到原本的外部变量outerStu上,
//所以53行与56行会发生改变。因为新建了对象。用新对象去改变的原来的外部变量。
//引用参数—引用类型不创建新对象只改变对象值
Student8 outerStu2 = new Student8() { Name = "Tim" };
Console.WriteLine($"{outerStu2.GetHashCode()},Name={outerStu2.Name}"); //输出:32854180,Name=Tim
Console.WriteLine("————————————");
引用参数_引用类型不创建新对象只改变对象值(ref outerStu2); //输出:32854180,Name=Tim
Console.WriteLine($"{outerStu2.GetHashCode()},Name={outerStu2.Name}\n"); //输出:32854180,Name=Tim
//由此可见一直在操作同一个对象。
//因为111行的方法:static void 引用参数_引用类型不创建新对象只改变对象值(ref Student8 stu)中stu这个参数和
//61行outerStu2这个变量它们所指向的内存地址就是同一个内存地址,而在这个内存地址里所存储的就是对象在堆内存中的地址。
//输出参数---------------------------------------------------------------
输出参数的使用示例();
//输出参数—值类型
double? x = 100;
bool b = 输出参数_值类型("qq", out x);
if (b)
{
Console.WriteLine(x + 1); //如果方法第一个参数正确,例如为"200",由于输出参数的特性,那么x的值会被覆盖为201。再打印输出为202。
}
else
{
Console.WriteLine($"是否有值:{x.HasValue}\n"); //输出False,为空值。
//即使在方法外定义过x的值=100,但是通过异常捕获流程赋予了x=null空值,由于输出参数的特性,外部变量被覆盖为空。
}
//输出参数—引用类型
Student9 stu3 = null;
bool b2 = 输出参数_引用类型("Tim", 34, out stu3); //如果符合条件,把新创建对象完成的改动(其中已初始化好Age和Name的值)向外输出到stu3这个示例。
if (b2)
{
Console.WriteLine($"Student:{stu3.Name},Age is:{stu3.Age}\n");
}
//数组参数---------------------------------------------------------------
//int[] myArray = new int[] { 1, 5, 5, 1, 3 };
//int result = 数组参数的使用示例(myArray); //在方法中加上或者不加params关键字,输出结果丝毫不受影响。
int result = 数组参数的使用示例(1, 5, 5, 1, 3);
//与上一行result的输出结果一样,这就意味着使用params数组参数,并不需要提前声明一个数组,而只需要把数组的元素一个一个列出来就可以了,
//因为当编译器发现括号中的参数是一个params参数的时候,会自动声明一个数组,然后把给出的值放入自动声明的数组再传进方法当中。
Console.WriteLine("{0}\n", result); //输出result的值,这种WriteLine输出形式也使用了params参数的重载。
string str = "Tim,Tom,Kity,Pig";
string[] result2 = str.Split(','); //Split方法用于分割字符串,也是带有params参数的方法。
foreach (var item in result2)
{
Console.WriteLine(item);
}
Console.WriteLine(); //单纯为了格式清晰。
//具名参数--------------------------------------------------------------
//这是不具名的调用
具名参数("Tom", 25); //因为第一个参数是string类型,第二个参数是int类型,需按指定的数据类型来填写,这种方式为不具名的调用。
//这是具名的调用
具名参数(Name: "Tom", age: 25);
具名参数(age: 25, Name: "Tom"); //顺序可以不一样,作用完全相同。
//这样的好处是让代码看起来更清晰,能够看出这个参数是干什么的,而且具名调用时顺序不再受约束。
Console.WriteLine(); //单纯为了格式清晰。
//可选参数---------------------------------------------------------------
可选参数(); //可以不写参数,因为声明方法时已赋予参数默认值,Name="Tim",Age=25,输出这两个值。
//拓展方法(使用this参数)-------------------------------------------------------------- 位于Student9类的下面,在DoubleExtention类中。
double p = 3.1415926;
double m = p.Round(4);
//原本定义的Round方法有两个参数,但此时可以发现Round方法只接收一个参数,是因为.前面的p就是Round方法的第一个参数
//现在只需要在Round方法中输入一个参数,也就是代表第二个参数就可以了。
}
//传值参数---------------------------------------------------------------
static void 传值参数_值类型(int x)
//参数x是传值参数,调用方法传进来的值在方法体内部有一个副本,方法体内的语句操作改变的是这个副本的值,
//并不会影响方法体外部变量的值,所以无论如何操作这个值,原本的值都不会受到影响。
{
x = x + 1;
Console.WriteLine(x);
}
static void 传值参数_引用类型并且新创建对象(Student5 stu)
{
//值参数创建变量的副本,对值参数的操作永远不影响变量的值。
stu = new Student5() { Name = "Tim" }; //新创建了一个对象,相当于生成了一个副本,对副本的改动不会影响到原本Student5类的实例stu的Name所赋予的值。
Console.WriteLine($"{stu.GetHashCode()},{stu.Name}");
//GetHashCode()隶属于Object类的方法,无论什么数据类型都有此方法,调用此方法时,
//得到的是一个用来代表对象的唯一值,所以每个对象的HashCode都不一样。
}
static void 传值参数_引用类型只操作对象不新创建对象(Student6 stu)
{
//参数的值和传进来的值都是对象在堆内存上的地址
stu.Name = "Tim";
Console.WriteLine($"{stu.GetHashCode()},{stu.Name}");
}
//引用参数---------------------------------------------------------------
//引用参数不会为传进来的实际参数创建副本,也就是说引用参数直接指向传进来的实际参数所指向的内存地址。
//正如文档中说:引用参数并不创建新的存储位置,引用参数表示的存储位置就是在方法调用中作为实参给出的那个变量所表示的存储位置。
//注意:当在调用带有引用参数的方法的时候,作为实际参数传进来的变量之前必须要有明确的赋值。
//而且在调用方法的时候,参数前面也要叫上ref关键字。 即 调用的引用参数方法(ref x);
//使用ref修饰符显示指出----此方法的副作用是改变实际参数的值。
static void 引用参数_值类型(ref int x)
{
x = x + 100;
Console.WriteLine(x);
}
static void 引用参数_引用类型并创建新对象(ref Student7 stu)
{
stu = new Student7() { Name = "Tom" };
Console.WriteLine($"{stu.GetHashCode()},Name={stu.Name}");
}
static void 引用参数_引用类型不创建新对象只改变对象值(ref Student8 stu)
{
stu.Name = "Tom";
Console.WriteLine($"{stu.GetHashCode()},Name={stu.Name}");
}
//输出参数---------------------------------------------------------------
//可通过使用输出参数来获得除返回值之外的额外的输出。也就是说拿输出参数当作一个值的输出。
//输出参数不为传进来的参数创建副本。也就是说输出参数和传进来的实参指向的是同一个内存地址。
//C#并不要求输出参数在传入方法之前要明确赋值,但是因为输出参数把值从方法体内输出到方法之外,
//所以要求在方法体内要求有明确的赋值。
//从语义上来讲——ref是为了“改变”,out是为了“输出”。
static void 输出参数的使用示例()
{
Console.WriteLine("请输入第一个数字");
string arg1 = Console.ReadLine();
double x = 0;
bool b1 = double.TryParse(arg1, out x); //TryParse方法返回的是一个bool类型的返回值,这里对x进行额外输出,输出值为输入值。
if (b1 == false)
{
Console.WriteLine("输入错误");
return;
}
Console.WriteLine("请输入第一个数字");
string arg2 = Console.ReadLine();
double y = 0;
bool b2 = double.TryParse(arg2, out y);
if (b2 == false)
{
Console.WriteLine("输入错误");
return;
}
double z = x + y;
Console.WriteLine($"{x}+{y}={z}\n");
}
//输出参数—值类型
static bool 输出参数_值类型(string input, out double? result)
{
try
{
result = double.Parse(input) + 1;
return true;
}
catch //没有括号代表不管什么异常,都会抓住
{
result = null;
return false;
}
}
//输出参数—引用类型
/// <summary>
/// <para>赋值前:</para>
/// 把引用类型的变量以输出参数的形式传进方法,此时输出参数不为变量创建副本,也就是说参数和变量它们指向同一个地址,
/// 输出参数可以有初始值,也可以没有初始值,对于一个引用类型的变量来说如果它有初始值,指的就是这个变量它引用着堆上的
/// 一个对象,如果没有初始值说明它没有引用任何对象。
///<para>赋值后:</para>
///大多数情况下,为一个引用变量进行赋值,赋值符号的右边都是new操作符的表达式,赋值后会把new操作符创建出来的对象它的地址
///交给输出参数,而输出参数和变量指向的是同一个地址,现在这个地址里所存储的就是新创建的对象在堆内存上的地址。
///<para>从代码上来看,我们在方法体内把一个新对象交由输出参数进行引用,实际效果是方法外的变量也引用上了新创建的对象。</para>
/// </summary>
static bool 输出参数_引用类型(string stuName, int stuAge, out Student9 result)
{
result = null;
if (string.IsNullOrEmpty(stuName) | stuAge < 20 | stuAge > 80)
{
return false;
}
result = new Student9() { Name = stuName, Age = stuAge };
return true;
}
//数组参数---------------------------------------------------------------
/// <summary>
/// 注意:在方法声明中的 params 关键字之后不允许任何其他参数(即数组参数必须放在参数的最后),并且在方法声明中只允许一个 params 关键字。
/// </summary>
/// <param name="intArray"></param>
/// <returns></returns>
static int 数组参数的使用示例(params int[] intArray)
{
int sum = 0;
foreach (var item in intArray)
{
sum += item;
}
return sum;
}
//具名参数--------------------------------------------------------------
static void 具名参数(string Name, int age)
{
Console.WriteLine($"Name:{Name},Age:{age}");
}
//可选参数---------------------------------------------------------------
/// <summary>
/// 可选参数指当在调用一个方法的时候,方法的参数可写也可不写
/// <para>可不写的原因是因为在声明方法的时候,给参数被赋予了默认值,对于带有默认值的参数,在调用方法的时候不写这个参数,
/// 那么这个参数自动获得声明时的默认值。</para>
/// </summary>
static void 可选参数(string Name = "Tim", int age = 25)
{
Console.WriteLine($"Name:{Name},Age:{age}");
}
}
class Student5
{
public string Name { get; set; }
}
class Student6
{
public string Name { get; set; }
}
class Student7
{
public string Name { get; set; }
}
class Student8
{
public string Name { get; set; }
}
class Student9
{
public int Age { get; set; }
public string Name { get; set; }
}
//拓展方法(使用this参数)--------------------------------------------------------------
//当我们无法对一个类型的源码进行修改的时候,可以使用拓展方法为这种目标数据类型来追加方法。
//注意:
//拓展方法必需是共有的,静态的,即被public static所修饰。
//this参数必须是参数列表中的第一个
//拓展方法必须放在一个静态类里
//约定俗成当打算拓展一个XXX的数据类型的时候,这个类应该叫XXXExtension。
/// <summary>
/// 拓展方法所隶属的类,要使用Extension来结尾,Extention翻译为—拓展名。
/// </summary>
static class DoubleExtention
{
public static double Round(this double input,int digits)
{
double result = Math.Round(input, digits);
return result;
}
}
}