1、方法声明及调用
1.2 方法(函数)概述
方法是一种用于实现可以有对象(或类)执行的计算或操作的成员,是一个已命名的语句集。每个方法都有一个名称和一个主体。方法名应该是一个有意义的标识符,应描述出方法的用途。方法主体包含了调用方法时实际执行的语句。用户可以为大多数方法提供一些数据来进行处理。并让其返回一些信息(通常是处理结果)方法是一种基本的,功能强大的编进机制
方法位置:通常放在类当中,并且与其他方法保持平级关系
1.2 方法声明
声明格式
修饰符 返回值类型 方法名称(参数列表)
{
方法体
}
修饰符:public(公共的) private(私有的) static(静态的)等等
返回值类型 void(不返回) int double等
参数列表可为数组(方法调用时,参数要匹配,即类型,个数,顺序一一对应)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace _1_方法声明及使用
{
class Program
{
static void Factorial(int num)
{
int factorial = 1;
for (int i = num; i > 0; i--) factorial *= i;
Console.WriteLine("{0}的阶乘结果是{1}" ,num, factorial);
}
static int Add(int a, int b)
{
return a + b;
}
static void Main(string[] args)
{
//方法的调用
Factorial(8);
Factorial(7);
Factorial(6);
Factorial(5);
Console.WriteLine("加法方法的调用,计算25,26的和是"+Add(25,26));
Console.ReadLine();
}
}
}
1.3 参数讲解(包括不定参数)
- 1)参数要匹配:方法调用时,参数要匹配,即类型,个数,顺序一一对应
- 2)参数数组:允许使用个数不定的参数调用方法,可使用params关键字来定义;参数数组可简化代码,因为在调用代码中不必传递数组,而是传递同类型的几个参数
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace _2_方法的声明和使用2
{
class Program
{
//编写一个方法用于计算给定整型数组元素的和
static int Add(params int[] nums) //
{
int sum = 0;
foreach (int outnum in nums) sum += outnum;
return sum;
}
static void Main(string[] args)
{
//调用
int[] mintArray = { 2, 5 ,9,34,42};
Console.WriteLine(Add(mintArray));
Console.ReadKey();
}
}
}
- 3)引用参数与值参数
值参数:传值给方法,方法中的变量修改不影响参数列表
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace _2_方法的声明和使用2
{
class Program
{
//编写一个方法用于求平方(没有返回值)
//此时的参数传递就是值传递,方法中的变量修改不影响参数列表
static void Square(int num)
{
num *= num;
Console.WriteLine(num);
}
static void Main(string[] args)
{
//值传递的方法引用及参数影响
Console.WriteLine("值传递");
int num = 10;
Console.WriteLine(num); //此处输出是10
Square(num);
Console.WriteLine(num); //此处输出依然是10
Console.ReadKey();
}
}
}
引用参数:ref关键字指定。使用ref两个限定 1)调用函数变量必须非常量,2)调用变量必须初始化
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace _2_方法的声明和使用2
{
class Program
{
//引用传递,会影响参数列表
static void Square2(ref int a)
{
a *= a;
Console.WriteLine(a);
}
static void Main(string[] args)
{
//调用
// 引用传递的方法调用及参数影响
Console.WriteLine("引用传递");
//const int num2 = 100; //引用传递不能定义为常量,此时常量不能作为引用参数
//int num2; //只是声明了变量,没有初始化值。此时不能用作引用参数
int num2 = 100;
Console.WriteLine(num2); //此处输出是100
Square2(ref num2);
Console.WriteLine(num2); //因为是引用传递,其参数受到影响,输出值是10000
Console.ReadKey();
}
}
}
- 4)输出参数
执行方式与ref完全一样,关键字为out
当希望返回多个值是,out非常有用
输出参数与引用参数的区别
未初始化变量用作ref非法,out合法
函数嗲用out参数量,必须把它当做尚未复制(即可以把已赋值的变量当做out参数,但存储在该变量中的值在方法执行时会丢失)
out ref必须在调用及定义方法时声明
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace _2_方法的声明和使用2
{
class Program
{
//编写一个方法,求出数组中最大的值进行返回,并返回最大值的一个索引
static int MaxNum(int [] nums, out int MaxNumIndex)
{
int maxNmu = nums[0];
MaxNumIndex = 0;
for (int i = 0; i < nums.Length; i++)
{
if (maxNmu < nums[i])
{
maxNmu = nums[i];
MaxNumIndex = i;
}
}
return maxNmu;
}
static void Main(string[] args)
{
int[] myIntArray = { 1, 342, 453, 6, 9674, 43634 };
int MaxNumIndex; //只需要声明,不需要初始化就可以,如初始化,调用方法后,初始化值将被丢失
Console.WriteLine("次数组的最大值是{0},最大值的索引是{1}",MaxNum(myIntArray,out MaxNumIndex),MaxNumIndex);
Console.ReadKey();
}
}
}
2、静态方法和实例方法
判断是否是静态方法的标识:有static关键字就是静态方法,没有static关键字就是实例方法(方法只有静态方法和实例方法两种,不是静态方法就是实例方法)
成员:类的属性(变量),成员也区分静态成员和实例成员
- 静态方法:不对特定实例进行操作,静态方法只能访问类中的静态成员,而不能使用实例成员。访问静态方法只能使用类名。而不需要创建对象,也不能使用对象名来引用
- 实例方法:可以使用类的任何成员(包括静态成员和实例成员)。调用实例方法时,必须使用类的实例或对象来引用。实例方法对类的某个给定的实例进行操作,在实例方法类中可以使用this来访问该实例。调用实例方法是,必须先创建一个对象。
- 静态方法只能访问静态成员,实例方法可以访问静态和实例成员:之所以不允许静态方法访问实例成员变量,是因为实例成员变量时属于某个对象的,而静态方法在执行时,并不一定存在对象。同样因为实例方法可以访问实例成员变量,如果允许静态方法调用实例放啊,将间接第允许静态方法使用实例成员,这是错误的。基于同样的道理,静态方法中也不能使用关键字this
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace _3静态方法与实例方法
{
class Program
{
int exampleVar=0; //声明一个实例成员
static int statciVar = 0; //声明一个静态成员
static void statciMethod() //声明一个静态方法
{
statciVar = 1; //静态方法中只能调用静态成员
// exampleVar = 2; //因为exampleVar是实例成员,因此在静态方法中无法调用
}
void exampleMethod() //声明一个实例方法
{
//statciVar = 1;
//等价于上面的语句 this.statciVar = 1;
exampleVar = 2;
}
void exampleMethod2() //声明一个实例方法
{
statciVar = 1;
//对于静态成员不用使用this关键字, 因此 this.statciVar = 1;是非法的
exampleVar = 2;
this.exampleVar = 2; //等价于上面的语句
this.exampleMethod2();
}
static void Main(string[] args)
{
//调用静态方法,
statciMethod(); //直接调用
//访问静态方法只能使用类名。而不需要创建对象,也不能使用对象名来引用
Program.statciMethod(); //类名.静态方法名 调用(statciMethod()是定义在Program类的)
//调用实例方法
Program p = new Program(); //将类实例化(创建对象)
p.exampleMethod(); // 使用类的实例化调用静态方法
// Program.exampleMethod(); //不能通过访问静态方法的途径访问实例
//exampleMethod(); //实例方法不能直接调用
}
}
}
3、虚方法
- 若一个实例方法的声明中含有virtual修饰符,则称该方法为虚方法。若其中没有virtual修饰符则称该方法为非虚方法
- 非虚方法的实现是一成不变的:无论该方法是在声明它的类的实例上调用还是在派生类(继承类)的实例上调用,实现均相同。与此相反,虚方法的实现可以由派生类取代。取代所继承的虚方法的实现过程称为重写(override)该方法。在虚方法调用中,该调用所涉及的那个实例的运行时类型确定了要被调用的究竟是该方法的哪一种实现。在非虚方法调用中,相关实例的编译时类型时决定性因素
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace _4_虚方法
{
class class1 //类的声明
{
//默认的方法被指定为私有的,只能在当前类中进行访问 private
//需要在其他类中访问,就需要将其指定为public;访问权限最高,只要在项目内部都可以进行访问
//virtual关键字必须位于void前面
public virtual void virtualMethod() // 因为虚方法能够在派生类中进行重写
{
Console.WriteLine("这是一个虚方法");
}
public void nonVirtualMethod()
{
Console.WriteLine("这是一个非虚方法");
}
}
class class2 : class1 //将calss2继承与class1
{
//重写一个虚方法
public override void virtualMethod()
{
Console.WriteLine("这是一个新写的虚方法");
}
public new void nonVirtualMethod()
{
Console.WriteLine("这是一个新的非虚方法");
}
}
class Program
{
static void Main(string[] args)
{
//实例:创建一个虚方法与一个非虚方法,然后通过另一个类继承,并比较他们的调用结果。
class1 c1 = new class1(); //将类的实例化
c1.virtualMethod(); //调用虚方法
c1.nonVirtualMethod();
class2 c2 = new class2();
c2.virtualMethod(); //
c2.nonVirtualMethod();
c1 = c2;
c1.virtualMethod();
c1.nonVirtualMethod();
Console.ReadKey();
}
}
}
4、重写方法
- 重写方法用相同的签名(方法名称和参数列表统称为签名)重写所继承的虚方法。虚方法声明用于引入新方法,而重写方法声明则用于使现有的继承方法专用化。由override声明所写的那个方法称为已重写了的基方法。
- 重写声明和已重写的基方法具有相同的声明可访问性。换句话说,重写声明不能更改所对应的虚方法的可访问性(重写的基方法的可访问性与重写声明的可访问性,例如:重写声明方法的可访问性是public,则重写的基方法也必须是Public),但是如果已重写的基方法是protected时,并且声明它的程序集不是包含重写方法的程序集,则重写方法声明的可访问性必须是protected。(protected没有明白,此处注意代码中的sealed关键字,限制重写方法的再次被重写)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace _5_重写方法
{
//新建一个类存放虚方法
class class1
{
public virtual void Write()
{
Console.WriteLine("这是一个虚方法,虚方法可以重写");
}
}
//再新建一个继承类用于重写方法
class class2:class1
{
// 如果不想让继承class2的类区重写Write()方法,应该怎么做?
//那就采用关键字sealed,加入修饰符后继承与class2的类不能再重写相应的方法
//sealed只能用于修饰重写的方法
public override sealed void Write()
{
Console.WriteLine("这是一个重写的方法,被称为已经重写的基方法");
}
}
class Program
{
static void Main(string[] args)
{
class1 c1 = new class1();
c1.Write();
class2 c2 = new class2();
c2.Write();
Console.ReadKey();
}
}
}
5、外部方法
- 当方法声明包含extern修饰符时,称该方法为外部方法。外部方法时在外部实现的,编程语言通常使用C#以外的语言。外部方法不可以是泛型。
- extern修饰符通常与DLLIMPORT属性一起使用,从而是外部方法可以以有DLL(动态链接库)实现。执行环境可以支持其他用来提供外部方法实现的机制。当外部方法包含DLLImport属性时,该方法必须同时包含一个static修饰符。
利用extern修饰符和DLLImport属性调用“User32.dll”实现自定义提示框功能
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace 外部方法
{
class Program
{
//使用之前应该引入命名空间System.Runtime.InteroopServices
[DllImport("User32.dll")] //注意此处D是大写ll是小写
//声明外部方法,使用关键extern由于配合DllImport属性适应必须配合static关键字
public static extern int MessageBox(int h, string m, string c, int type);
static int Main(string[] args)
{
Console.WriteLine("请输入您的姓名");
string name = Console.ReadLine();
//利用return进行弹出对话框,所以需要将main方法改为有返回值
return MessageBox(0,"您好:"+name+"\n"+"不断努力就能成功","欢迎提示",0);
}
}
}
6、分部方法(有争议)
- 分部方法:方法声明中含有partial修饰符
- 分部方法必须在分布类或分布结构中声明,必须私有
- 分部方法有着严格的限制
第一、方法必须返回void,只能默认为private
第二、分部方法不能为virtual和extern
第三、分部方法可以有ref参数,但不能有out参数 - 因为任何针对没有被实现的分布方法的调用都会简单第被忽略,所以这些限制是非常必要的
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace _7分部方法
{
public partial class Program
{
//分布方法必须在分布类或分布结构中进行声明和定义
//声明与定义一个分部方法,首先声明一个分部类
//在分布类中声明和定义分部方法
//方法默认为私有,也可以认为加上private
partial void Write(); //声明
partial void Write() //分部方法定义
{
Console.WriteLine("这是一个分部方法");
}
}
public partial class Program
{
static void Main(string[] args)
{
//调用分部方法——不能直接调用,需要对类实例化
Program p = new Program();
p.Write();
Console.ReadKey();
}
}
}
8、方法重载
- 方法重写和方法重载是不同的——重写是对虚方法(virtual override)
- 方法重载是一种操作性多态。有时候,可能需要在多个不同的实现中对不同的数据执行相同的逻辑操作。以WriteLine方法为例,有事肯恩向它传递一个格式字符串和其他一些参数,也可能只向它传递一个整数。两者的具体实现肯定是不同的。单逻辑上,这个方法负责的是输出数据。至于方法内部具体是如何实现的,很多读者也不关心。
- 在面向对象这样的高级语言中,都允许在一个类中定义多个方法名相同、方法中参数个数和参数顺序不同的方法,对于参数个数不同或者参数列表不同的情况称之为参数列表不同。
- 需要注意的是这里没有提到方法的返回值(能否实现重载与是否有返回值没有关系)
8.1 决定方法是否构成重载有三个条件
- 在同一个类中
- 方法名相同
- 参数列表不同
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace 方法重载
{
class Program
{
//写在同一个类中
//利用重载分别求圆、矩形三角形的面积
static void WriteArea(double radius) //圆的面积
{
double area = System.Math.PI * radius * radius;
Console.WriteLine("您求的圆的面积是:"+area);
}
static void WriteArea(int width, int length) //矩形面积
{
int area = width * length;
Console.WriteLine("您求的矩形面积是:"+area);
}
static void WriteArea(int a, int b, int c) //三角形面积
{
double p = (a + b + c) / 2;
double area = System.Math.Sqrt(p * (p - a) * (p - b) * (p - c));
Console.WriteLine("您求的矩形面积是:" + area);
}
static void Main(string[] args)
{
//利用方法的重载进行调用方法
WriteArea(3,4,5);
WriteArea(25);
WriteArea(10,20);
Console.ReadKey();
}
}
}
main方法
- main方法是程序的入口点,程序从这里开始,也从这里结束
- 执行过程:程序在执行编写的源代码时,是先找到main方法,然后开始执行main方法中“{”开始后的第一句代码,依次执行,如果遇到main方法中有调用其他的方法时,便会根据方法名称找到定义方法的代码,然后执行这个方法内的代码,执行这个方法后,再返回到main方法继续执行。直到遇到Main方法的结束符“}”,执行结束。
- main方法必须是一个类的静态成员。main方法可以是void类型或返回类型时int,并可以寄接收字符串数组形式的命令行参数
- main方法有四种表现形式
- public static void Main()
- public static int Main()
- public static void Main(string[] args)
- public static int Main(string[] args)
- 上述有返回值int时,可以用于表示应用程序的终止方式,通常用作一种错误提示(但这不是强制的)。一般情况下,返回0反映了“正常”终止(即引用程序执行完毕,并安全终止)
- main的可选参数args是从应用程序的外部接受信息的方法,这些信息在运行应用程序时,以命令行参数的形式指定
实例:在执行控制台应用程序时,指定的任何命令行参数都放在args数组中,接着可以根据需要在应用程序中使用这些参数
- 当创建一个项目的时候,编译器会默认创建一个第三种形式的Main方法,默认使用这个Main方法
- 在一个程序中,Main方法只能有一个,并且该方法的位置不必固定,C#编译器找到Main方法,并将其作为程序入口
- 在编译程序时,精良不要修改编译器自动生成的main方法,也没有必要区修改它
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace _9main方法
{
class Program
{
//main方法有四种表现形式
//public static void Main()
//public static int Main()
//public static void Main(string[] args)
//public static int Main(string[] args)
static void Main(string[] args)
{
//查看命令行参数数组长度
Console.WriteLine(args.Length+"个命令行参数被指定");
foreach (string outstr in args) Console.WriteLine(outstr);
Console.ReadKey();
}
}
}
10、本章小结及任务实施
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace 本章小结及任务实施
{
class Program
{
static void Multiple(int num)
{
Console.WriteLine("{0}的乘法表是:",num);
for (int i = 1; i < num; i++)
{
if (num % i != 0) continue;
else
{
Console.WriteLine("{0,2} X {1,2} = {2,2}", i, num / i, num);
}
}
}
static void Add(int num)
{
Console.WriteLine("{0}的加法表是:", num);
for (int i = 0; i <=num; i++)
{
Console.WriteLine("{0,2} + {1,2} = {2,2}",i,num-i,num);
}
}
static void Main(string[] args)
{
//实现用户输入数字的加法表与乘法表
//例如:12 0+12=12 1+11=12 …… 1*12=12 6*2=12 ……
Console.WriteLine("请输入一个整数");
int num = Convert.ToInt32(Console.ReadLine());
Multiple(num);
Add(num);
Console.ReadKey();
}
}
}