第八天
高级数据存储方式:结构,枚举,数组.
.如何在结构中存储值
.结构与类的异同
.枚举是什么?如何使用它来使程序更容易理解?
.如何声明数组并使用它来存储大量数据类型相同的值?
.使用关键字foreach来操纵数组
----------------------------------------------------------
结构
结构是c#提供的一种数据类型,跟类相似.也可以包含数据和方法定义,还可以包含构造函数,常量,字段,
方法,属性,索引器,运算符和嵌套类型.
结构与类的区别
主要区别:存储和访问方式,结构是一种值数据类型,而类是一种引用数据类型.
按值存储比按引用存储更消耗内存,但是在处理少量数据时更有利.
一个通用的经验规则:如果数据成员占用的内存不超过16个字节,使用结构;否则,使用类.
声明规则:
struct NAME
{
body;
}
例如:使用point结构
//stuctpoint.cs - 使用结构
//-------------------------
struct point
{
public int x;
public int y;
}
class pointApp
{
public static void Main()
{
point starting = new point();
point ending= new point();
starting.x = 1;
starting.y = 4;
ending.x = 10;
ending.y = 11;
System.Console.WriteLine("point 1:{0},{1}", starting.x, starting.y);
System.Console.WriteLine("point 2:{0},{1}", ending.x,ending.y);
}
}
定义结构使用关键字struct,访问结构成员的方式与类相同.
声明结构还可以使用这样的格式:point starting;
不使用关键字new
结构的嵌套
和类一样,结构中可以包含其他数据类型,包括其他结构.
建立方式与类相似,主要差别在于实例化line结构时,将给它分配内存并直接存储它.
//structLine.cs - 结构的嵌套
//---------------------------
struct point
{
public int x;
public int y;
}
struct line
{
public point starting;
public point ending;
}
class LineApp
{
public static void Main()
{
line myLine;
myLine.starting.x = 1;
myLine.starting.y = 4;
myLine.ending.x = 10;
myLine.ending.y = 11;
System.Console.WriteLine("point 1:{0},{1}",myLine.starting.x,myLine.starting.y);
System.Console.WriteLine("point 2:{0},{1}",myLine.ending.x,myLine.ending.y);
}
}
结构方法
和类一样,结构也可以包含方法和属性,声明方式与在类中相同,这包括于类中相同的限定符和属性.
可以重载结构方法,传递值以及返回值.
// structLine2.cs - 在结构中添加方法
//-----------------------------------
struct point
{
public int x;
public int y;
}
struct line
{
public point starting;
public point ending;
public double length()
{
double len = 0;
len = System.Math.Sqrt((ending.x - starting.x) * (ending.x - starting.x) + (ending.y - starting.y) * (ending.y - starting.y));
return len;
}
}
class lineApp
{
public static void Main()
{
line myLine;
myLine.starting.x = 1;
myLine.starting.y = 4;
myLine.ending.x = 10;
myLine.ending.y = 11;
System.Console.WriteLine("point 1:{0},{1}", myLine.starting.x, myLine.starting.y);
System.Console.WriteLine("point 2:{0},{1}", myLine.ending.x, myLine.ending.y);
System.Console.WriteLine("Length of line from poing 1 to point 2:{0}", myLine.length());
}
}
结构的构造函数
除了可包含常规方法外,结构还可以有构造函数.与类不同的是,为结构声明构造函数的时候.
必须包含参数,不能为结构声明一个不包含任何参数的构造函数.
//structPoint2.cs - 包含构造函数的point结构
//------------------------------------------
struct point
{
public int x;
public int y;
public point(int x, int y)
{
this.x = x;
this.y = y;
}
//public point()
//{
// this.x = 0;
// this.y = 0;
//}
}
class pointApp
{
public static void Main()
{
point point1 = new point();
point point2 = new point(8,8);
point1.x = 1;
point1.y = 4;
System.Console.WriteLine("point 1:{0},{1}", point1.x, point1.y);
System.Console.WriteLine("poing 2:{0},{1}", point2.x, point2.y);
}
}
结构的构造函数必须初始化结构的数据成员.
如上程序中,point point1=new point();这样不带参数调用结构时,将自动使用默认值初始化每个数据成员.
通常数据成员初始化为0.如果调用的是声明的构造函数,而不是默认的构造函数,则必须在构造函数中初始化
所有的数据成员.
虽然使用默认构造函数,可以省略new关键字,但使用参数来实例化结构时,不能省略.
如:point point(8,8);将出错
结构的析构函数
类可以有析构函数.结构不能还有析构函数,如果试图添加,将出错.
-------------------------------------------------------------------------------------
枚举
c#中可以使用另外一种数据类型,枚举
枚举可以创建有限的几个值的变量.格式如下:
modifiers enum enumName
{
enumMember1,
enumMember2,
...
enumMemberN
}
默认情况下,在枚举变量被声明时,其值被设置为0.
//color.cs - 使用枚举
//-------------------
using System;
class myApp
{
enum Color
{
red,
white,
blue
}
public static void Main()
{
string buffer;
Color myColor;
Console.WriteLine("Enter a value for a color: 0=Red,1=White,2=Blue");
buffer = Console.ReadLine();
myColor = (Color)Convert.ToInt32(buffer);
switch (myColor)
{
case Color.red:
System.Console.WriteLine("/nSwitched to Red...");
break;
case Color.white:
System.Console.WriteLine("/nSwitched to White...");
break;
case Color.blue:
System.Console.WriteLine("/nSwitched to Blue...");
break;
default:
System.Console.WriteLine("/nSwitched to default...");
break;
}
System.Console.WriteLine("/nColor is {0}({1})", myColor, (int)myColor);
}
}
对于所有的枚举来说,当枚举被创建时,第一个成员的值是0,第二个是1,第三个是2.其余依次类推
修改枚举的默认值有两种方法:
一种是在枚举的开始位置加上一个填充字符值,但不适用第一个成员的值是更大的数字.
另一种方法是设置枚举成员的值,可以将他们设置为字面值,其他枚举成员的值,或计算得到的值.
// bday.cs - 设置枚举成员的数值型值
//---------------------------------
using System;
public class myApp
{
enum Month
{
January=1,
February=2,
March=3,
April=4,
May=5,
June=6,
July=7,
August=8,
September=9,
October=10,
November=11,
December=12,
}
struct birthday
{
public Month bmonth;
public int bday;
public int byear;
}
public static void Main()
{
birthday MyBirthday;
MyBirthday.bmonth = Month.August;
MyBirthday.bday = 11;
MyBirthday.byear = 1091;
System.Console.WriteLine("My birthday is {0},{1},{2}", MyBirthday.bmonth, MyBirthday.bday, MyBirthday.byear);
}
}
在这个程序中,将枚举成员的值设置为1~12,而不是使用默认值0~11.
由于成员的值将在前一个值的基础上加1,因此可以不显式的设置第二个成员以及以后的各个成员的值.
可以将这些值设置为其他的值,甚至可以使用公式.
MyBirthday.bmonth = Month.August;将数据成员bmonth赋值为Month.Augst.
也可以使用强制转换的方式将bmonth的值设置为Month.August,但这样做没有那么清晰.
MyBirthday.bmonth=(Month)8;
同样,从输出结果中可以知道,存储的值为August,而不是数字8.
修改枚举的底层类型
前面的范例中,枚举的底层数据类型都是int,实际上枚举可以包含数据类型为:
byte,sbyte,int,uint,shot,ushort,long,ulong.
如未指定数据类型,则默认类型为int.使用如下格式可以转换默认的底层类型:
modifiers enum enumName:typeName{member(s)}
------------------------------------------------------------------------------
使用数组来存储数据
类和结构可以将不同类型的相关信息存储在一起.
有时候,还需要存储多个数据类型相同的值.这时候,数组是最好的解决方案.
声明数组
数组是可以存储多个值的单个数据变量.其中所有值的数据类型相同.
在声明变量时,在数据类型的后面加上[].数组的声明格式:
datatype[] name;
其中.datatype是将在数组中存储的数据类型;方括号指出声明的是一个数组.name是该数组变量的名称.
如:decimal [] balances;
创建了一个名为balances的数组,可以来存储decimal值.但没有为存储变量预留空间.
要预留空间,需要像创建其他对象那样,使用new关键字来实例化该变量.
实例化数组时,必须指出该数组将存储多少个值.
可以使用的方法之一是,在实例化数组时在方括号内指出元素数目.
balances = new decimal[12]
也可以在声明数组的同时进行实例化,如:
decimal[] balances=new decimal[12];
从上可知道,实例化的格式如下:
new datatype [nbr_of_elements]
其中datatype是数组的数组类型,nbr_of_elements是一个数值型值,指出该数组中存储元素的数目.
声明并实例化数组后,便可以使用它,数组中的条目被称为元素.
可以通过索引进行访问.索引是一个标识数组中的偏移量的数字.
第一个元素用索引0标识,这是因为第一个元素位于数组的开始位置,因此偏移量为0.
第二个元素的索引为1,因为其偏移量为1个元素.
最后一个元素的偏移量为数组长度减1.
要访问数组中某个特定的元素,可以使用后面跟方括号内代相应索引的数组名称.
如:要将数组balances的第一元素的值设置为1295.50,可以使用下面的代码
balances[0]=1297.50m;
将一个decimal值赋给数组balances的第三个元素,代码:
balances[2]=1000m;
// balances.cs - 使用数组
//-----------------------
using System;
public class myApp
{
public static void Main()
{
decimal[] balances = new decimal[12];
decimal ttl = 0m;
Random rnd = new Random();
for (int indx = 0; indx < 12; indx++)
{
balances[indx] = (decimal)((rnd.NextDouble() * 10000));
}
for (int indx = 0; indx < 12; indx++)
{
Console.WriteLine("Balance {0}:{1}", indx, balances[indx]);
ttl += balances[indx];
}
Console.WriteLine("======================================");
Console.WriteLine("Total of Balances = {0}", ttl);
Console.WriteLine("Average Balance = {0}", (ttl / 12));
}
}
使用balances数组的代码要比使用12个变量简单的多.结合使用数组名和索引,就像使用同类型的常规变量一样.
使用数组初始化数组元素
在声明和实例化数组的同时,可以初始化各个数组元素的值.如:
decimal[] balances=new decimal[12] {1000.00m,2000.00m,3000.00m,4000.00m,5000.00m,600m,0m.0m,9m,0m,0m,12000m};
声明创建数组balances,第一个元素值1000.00m,第二个2000m,依次类推
在使用这种方式声明的时候,方括号内不指定数组大小,直接给各个元素赋值,也生效.
如果这样初始化:decimal[] balances=new decimal[12] {111m,111r};
则将创建一个一个包含12个元素的数组.开始两个元素的值被初始化111
// fname.cs - 使用数组
//----------------------------
using System;
public class myApp
{
public static void Main()
{
char[] name = new char[] { 'B', 'r', 'a', 'd', 'l', 'e', 'y', (char)0 };
Console.WriteLine("Display content of name array ....");
int ctr = 0;
while (name[ctr] != 0)
{
Console.Write("{0}", name[ctr]);
ctr++;
}
Console.WriteLine("/n...Done.");
}
}
多维数组
多维数组是一个由数组组成的数组,甚至可以使用三维数组.这将是数组复杂化.不建议使用超过三维的数组.
通常使用的是二维数组.要声明二维数组,只需要对声明常规数组的方法进行扩展即可:
byte[,] scores=new byte[15,30];
声明的第一部分增加了一个逗号,而第二部分则使用了由逗号分隔的两个数字.
上述声明创建了一个包含15个元素的数字,其中每个元素都是一个包含30个元素的数组.
因此,数组scores总共可以存储450个数据类型为byte的值.
char[,] lecters=new char[2,3];{{'a','b','c'},{'x','y','z'}};
上述声明创建一个名为lecters的数组,它包含两个元素,每个元素又是一个包含三个字符元素的数组.并且声明的同时初始化了元素的值.
使用多维数组,如:lecters[0,0]代表第一个数组中的第一个元素.
在数组外初始化数组lecters
lecters[0,0]='a';
lecters[0,1]='a';
lecters[0,2]='a';
lecters[1,0]='a';
lecters[1,1]='a';
lecters[1,2]='a';
上面介绍的都是针对两个子数组的长度相同.有时候还将用到存储长度不同的数组.
char[][] myname=new char[3][];
myname[0]=new char[]{'B','r','d','l','e','y'};
myname[1]=new char[]{'l','.'};
myname[2]=new char[]{'j','o','n','e','s'};
数组myname是一个由数组组成的数组,包含三个长度各不相同的字符数组.由于他们的长度不同,所以声明方式如上
在引用数组中的元素时,格式为:myname[0][1],不能使用myname[2,0]这种方式.
如果声明的同时并不想初始化,可以在new char[]的方括号中声明子数组的包含元素个数.
检查数组长度和边界
数组长度被存储在一个名为length的成员中.对于balance数组,可以通过balance.length获取其长度.
通过传递数组的索引号来指出要返回哪个子数组的长度.
// names.cs - 使用锯齿形的二位数组
//--------------------------------------
using System;
public class myApp
{
public static void Main()
{
char[][] name = new char[3][];
name[0] = new char[7] { 'B', 'r', 'a', 'd', 'e', 'l', 'y' };
name[1] = new char[2] { 'l', '.' };
name[2] = new char[6] { 'J', 'u', 's', 't', 'i', 'n' };
Console.WriteLine("Display the sizes of the arrays.../n");
Console.WriteLine("Length of name array {0}", name.Length);
for (int ctr = 0; ctr < name.Length; ctr++)
{
Console.WriteLine("Length of name[{0}] is {1}", ctr, name[ctr].Length);
}
Console.WriteLine("/n/nDisplaying the content of the name array...");
for (int ctr = 0; ctr < name.Length; ctr++)
{
Console.Write("/n");
for (int ctr2 = 0; ctr2 < name[ctr].Length; ctr2++)
{
Console.Write("{0}", name[ctr][ctr2]);
}
}
Console.WriteLine("/n...Done displaying");
}
}
在类和结构中使用数组
数组可以在使用其他数据类型的地方创建和使用.
一般使用较多的都是基本数据类型的数组,类/结构或其他任何数据类型都可以来创建数组.
使用foreach语句
foreach语句使得使用数组更为简单,特别是遍历整个数组的时候.它不是使用代下标的数组名来引用数组
而是使用一个简单的变量.foreach语句的缺点在于,使用简单变量是只读的--不能gie它赋值.格式如下:
foreach(datatype varname in arrayName)
{
statements;
}
其中,datatype是数组的数据类型,varname是用于标识数组中各个元素的变量的名称;
arrayName是foreach语句要遍历的数组的名称.
按套路来,上程序
// foreach1.cs - 将foreach用于数组
//---------------------------------
using System;
public class myApp
{
public static void Main()
{
char[] name = new char[] { 'j', 'u', 's', 't', 'i', 'n' };
Console.WriteLine("Display content of name array...");
foreach (char x in name)
{
Console.Write("{0}", x);
}
Console.WriteLine("/n...Done.");
}
}
使用foreach语句,遍历数组,依次将每个元素称为x.然后将其输出.
第八天的知识点并不多,但是应用性很强,结构/枚举/数组,扩展了c#的数据类型.
================================================================================================
第九天 关于方法的高级主题
.如何给方法传递不同的参数
.如何重载方法
.方法的特征标(signature)
.再谈作用域
.创建自己的名称空间
-----------------------------------------------------------------------
重载方法
重载是面向对象编程的特性之一.
方法重载指的是创建多个名称相同的方法.其中每个方法都在某些方面具备唯一性.以便编译器能够区分它们.
下面程序包含了一个Circle类,其中Area方法被重载.
// circle.cs - polymorphce area method
//通过方法重载来说明多态
//--------------------------------------
using System;
public class Circle
{
public int x;
public int y;
public double radius;
private const float PI = 3.14159F;
public double Area()//uses values from data member
{
return Area(radius);
}
public double Area(double rad)
{
double theArea;
theArea = PI * rad * rad;
Console.WriteLine("The area for radis ({0}) is {1}", rad, theArea);
return theArea;
}
public double Area(int x1, int y1, double rad)
{
return Area(rad);
}
public double Area(int x1, int y1, int x2, int y2)
{
int x_diff;
int y_diff;
double rad;
x_diff = x2 - x1;
y_diff = y2 - y1;
rad = (double)Math.Sqrt((x_diff * x_diff) + (y_diff * y_diff));
return Area(rad);
}
public Circle()
{
x = 0;
y = 0;
radius = 0.0;
}
}
class CircleApp
{
public static void Main()
{
Circle myCircle = new Circle();
Console.WriteLine("Passing nothing...");
myCircle.Area();
Console.WriteLine("/nPassing a radius of 3...");
myCircle.Area(3);
Console.WriteLine("/nPassing a center of (2,4) and a radius of 3....");
Console.WriteLine("/nPassing center of (2,3) and a point of (5,6)...");
myCircle.Area(2, 3, 4, 5);
}
}
Circle类定义了四个Area方法,他们之间的区别在于传递的参数数目不同.
根据接受参数的不同,来区别执行哪个Area方法.
注:在这个程序中.PI使用的并不是常量.而是在第11行声明了一个固定变量.这样,当需要修改PI值的时候,
只要在一个地方修改即可.而不用对分布在应用程序各个地方的硬编码进行修改,后期维护方便.
虽然可以在名称相同的方法中实现完全不同的功能,但并不应该这样做.
习惯上名称相同的方法,得到的结果也是类似的.
构造函数重载
不止常规方法可以重载,还可以重载构造函数.
重载的构造函数可以在创建对象的同时,给他传递值.
// circle1.cs - A simple circle class with overloaded construotors
// 重载构造函数
//--------------------------------------------------------------------
using System;
public class Circle
{
public int x;
public int y;
public int radius;
private const float PI = 3.14150F;
public double area()
{
double theArea;
theArea = PI * radius * radius;
return theArea;
}
public double circumference()
{
double Circ;
Circ = 2 * PI * radius;
return Circ;
}
public Circle()
{
x = 0;
y = 0;
radius = 0;
}
public Circle(int r)
{
x = 0;
y = 0;
radius = r;
}
public Circle(int new_x, int new_y)
{
x = new_x;
y = new_y;
radius = 0;
}
public Circle(int new_x, int new_y, int r)
{
x = new_x;
y = new_y;
radius = r;
}
public void print_circle_info()
{
Console.WriteLine("Circle: Center = {0},{1}", x, y);
Console.WriteLine("Radius={0}", radius);
Console.WriteLine("Area={0}", area());
Console.WriteLine("Circum={0}", circumference());
}
}
class CircleApp
{
public static void Main()
{
Circle first = new Circle();
Circle second = new Circle(4);
Circle third = new Circle(3,4);
Circle fourth = new Circle(1,2,5);
Console.WriteLine("/nFirst Circle:");
first.print_circle_info();
Console.WriteLine("/nSecond Circle:");
second.print_circle_info();
Console.WriteLine("/nThird Circle:");
third.print_circle_info();
Console.WriteLine("/nFourth Circle:");
fourth.print_circle_info();
}
}
分析上述程序清单.
这个程序的重点是Circle类的构造函数.有多个构造函数,每一个接受的参数个数都不同.
在创建对象时,使用了不同的创建方式.分别包含不同个数的参数.
在创建的过程中分别调用相应的构造函数
所谓相应的构造函数,值得是其参数与调用的参数匹配的构造函数.
理解方法的特征标
方法之所以可以重载,是由于各个方法都有特征标.(signature)
如前面介绍的程序,方法的参数个数能决定哪个方法是合适的.
对于被重载方法,还有其他的方式可以区分各个方法.这些差异性最终构成了方法的特征标.
如下述重载方式,也是合法的
MyFun(int)
MyFun(float)
MyFun(ref int)
MyFun(val int)
这种方法并不是以参数个数来区分的,但是通用也可以使用
有些因素不能用作特征标的组成部分:
如返回类型,因为调用方法时,并不使用返回类型.
一个方法使用某种数据类型,另一个方法使用数组,也将出错
如:int myMethod (int)
int myMethod (int[])
也不能通过关键字params来区分.
最后说明下,可以根据需要重载方法任意次,只要每个方法有独特的特征标.
-----------------------------------------------------------------------------------
使用不同数目的参数
前面介绍了如何创建方法,如何给方法传递信息以及传递信息的多种方式--包括按值传递,按
引用传递以及传递可用于返回输出的变量.还可以使用关键字return从方法中返回一个值.
所有这一切,都要求有组织的使用方法.
当需要给方法传递不同数目的参数时,该怎么办呢?
如Console.Write,可以接受一个字符串以及不同数目的其他参数,这些参数的数据类型和值可各不相同.
要接受未知数目的参数,可以使用关键字params,它用于参数列表中,生命参数列表最后面的值.
params与数组一起使用.
看下面的程序,在一个方法中使用了关键字params.
// addem.cs - Using a varaible number of arguments
//------------------------------------------------------
using System;
public class AddEm
{
public static long Add(params int[] args)
{
int ctr = 0;
long Total = 0;
for (ctr = 0; ctr < args.Length; ctr++)
{
Total += args[ctr];
}
return Total;
}
}
class MyApp
{
public static void Main()
{
long Total = 0;
Total = AddEm.Add(1);
Console.WriteLine("Total of (1) = {0}", Total);
Total = AddEm.Add(1, 2);
Console.WriteLine("Total of (1,2) = {0}", Total);
}
}
这里没有创建AddEm的对象,因为Add方法是静态的,因此即使不创建对象,也可以使用类名AddEm来调用它.
下面使用params来指定多种数据类型
// garbage.cs - Using a variable number of arguments of different types
//------------------------------------------------------------------------------
using System;
public class Garbage
{
public static void Print(params object[] args)
{
int ctr = 0;
for (ctr = 0; ctr < args.Length; ctr++)
{
Console.WriteLine("Argument {0} is: {1}", ctr, args[ctr]);
}
}
}
class MyApp
{
public static void Main()
{
long Along = 1234567854545454;
decimal ADec = 1235.56m;
byte Abyte = 42;
string Astring = "Hello Justin";
Console.WriteLine("First call...");
Garbage.Print(1);
Console.WriteLine("Second call...");
Garbage.Print();
Console.WriteLine("/nThird call...");
Garbage.Print(Along,ADec, Abyte, Astring);
Console.WriteLine("/nFourth call....");
Garbage.Print(Astring, "is cool", '!');
}
}
当值被传递给方法时,编译器首先查看是否有匹配的方法.
如果有,则调用该方法.如果没有,将查看是否有包含参数params的方法.
如果找到这样的方法,则使用它.编译器将这些参数放到一个数组内.并将数组传递给方法.
-------------------------------------------------------------------------------------
Main方法和命令参数
Main方法是一个特殊的方法,因为它总是首先被调用.也可以接受不同数目的参数.
但不需要在Main方法中使用params关键字.
之所以不需要使用关键字params,是由于命令行参数将自动加入到一个字符串数组中.
基本上与params的功能相同.格式如下:
public static int/void Main (string[] args)
int和void是可选的,返回整数时使用int,返回其他数据类型使用void.
这里的重点是参数列表:一个名为args的字符串数组.
可以将args改为任何名称,但习惯上都使用args
// command.cs - Checking for command-Line arguments
//--------------------------------------------------
using System;
class CommandLine
{
public static void Main(string[] args)
{
int ctr = 0;
if (args.Length <= 0)
{
Console.WriteLine("NoCommand Line arguments were provided");
return;
}
else
{
for (ctr = 0; ctr < args.Length; ctr++)
{
Console.WriteLine("Argument {0} is {1}",ctr+1, args[ctr]);
}
}
}
}
注:应尽可能将各种东西设置为私有的,通过属性来提供对私有数据成员的共有访问权限.
-----------------------------------------------------------------------------------
作用域
理解被运行阶段环境释放之前,变量的存活时间至关重要.变量的寿命及其可访问性被成为作用域.
作用域有许多级别,最常用的两个数局部和全局.
全局作用域指的是在整个程序清单中都可见,局部指在小范围内可用的变量.
区分类变量和局部变量
区分类变量和局部变量的方法之一是总是引用类.
根据声明变量的方式,明确的引用类变量的方式有两种,如果类变量是标准的,非静态的.可以使用关
键字this.例如:this.x;如何访问静态数据成员,参照第七天的课程.
类作用域限定符
对于方法和数据成员,可以使用的限定符有两种:private和public.
使用public限定符,数据成员和成员函数在类的外面可以被访问.
使用限定符private,数据成员和方法只能在其所在的默认类中被访问.
默认情况下,数据成员和方法是私有的.
声明变量时,未指定是私有还是共有,则它将是私有的.
c#不允许在类的外面声明变量.
不能用于创建对象的类
静态方法和静态数据成员都属于类,而不是各个对象.如果声明了一个数据和方法都是静态的类.则使用
该类声明的对象将不包含任何值.
// MyMath.cs - Static members.
//--------------------------------
using System;
public class MyMath
{
public static long Add(params int[] args)
{
int ctr = 0;
long Answer = 0;
for (ctr = 0; ctr < args.Length; ctr++)
{
Answer += args[ctr];
}
return Answer;
}
public static long Subtract (int arg1,int arg2)
{
long Answer = 0;
Answer = arg1 - arg2;
return Answer;
}
}
class MyApp
{
public static void Main()
{
long Result = 0;
Result = MyMath.Add(1, 2, 3);
MyMath var=new MyMath();
Console.WriteLine("Add result is {0}", Result);
Result = MyMath.Subtract(5, 2);
Console.WriteLine("Subtract Result is {0}", Result);
}
}
上述程序,MyMath类声明了两个方法.在使用时,直接调用MyMath.Add.可以创建MyMath的对象var,虽然它没有起任何作用.
要禁止使用某个类来创建对象.可以创建一个私有的构造函数.
由于带关键private的方法只能在其所属的类中调用.这意味着不能在类的外面调用私有构造函数.
由于构造函数是在创建对象时被调用的,因此使用私有构造函数可以禁止创建对象.
如果给上面的程序添加一个私有构造函数后,再创建对象:MyMath var=new MyMath();
则出错.但是依然可以访问共有的静态类成员.
---------------------------------------------------------------------------------------------
反复验证上面的程序,终于搞清楚public static private的用法.差点想破头.
---------------------------------------------------------------------------------------------
再谈名称空间
名称空间可以用于帮助组织类和其他类型.
可以包含其他名称空间,类,结构,枚举,接口和代表.
接口和代表将在后面介绍.
给名称空间命名
名称空间的名称可以是任何合法的标识符,这意味着必须游标准字符和下划线组成.另外还可以包含句点.
和其他标识符一样,应该给名称空间取一个描述性的名称.
声明名称空间
要创建名称空间,使用关键字namespace后跟标识该名称空间的名称.然后用花括号将该名称空间包含的类型括起.
// namespace.cs - Declaring namespaces 声明名称空间
//-------------------------------------------------
using System;
namespace Consts
{
public class PI
{
public static double value = 3.14159;
private PI() { } //private constructor
}
public class three
{
public static int value = 3;
private three() { } //private constructor
}
}
namespace MyMath
{
public class Routine
{
public static long Add(params int[] args)
{
int ctr = 0;
long Answer = 0;
for (ctr = 0; ctr < args.Length; ctr++)
{
Answer += args[ctr];
}
return Answer;
}
public static long Subtract(int arg1, int arg2)
{
long Answer = 0;
Answer = arg1 - arg2;
return Answer;
}
}
}
class MyApp
{
public static void Main()
{
long Result = 0;
Result = MyMath.Routine.Add(1, 2, 3);
Console.WriteLine("Add result is {0}", Result);
Result = MyMath.Routine.Subtract(5, 2);
Console.WriteLine("Subtract result is {0}", Result);
Console.WriteLine("/nThe value of PI is {0}", Consts.PI.value);
Console.WriteLine("The value of three is {0}", Consts.three.value);
}
}
在外部使用名称空间包含的成员时,必须指定名称空间.可以使用using来省略.
using和名称空间
C#中的关键字using,使得使用名称空间更容易,并提供了两项功能.
首先,使用using给名称空间取一个别名;
using aliasname=namespaceOrClassName;
其中,aliasname是要使用的别名.namespaceOrClassName是名称空间或类的名称.
例:using doit=System.Console;
可以在使用的地方:doit.WriteLIne
其次,使用using后,不必使用全限定名称,使得访问名称空间中的类型将更为容易.
例:using System;
通过这行代码,再使用System中的类和类型时,不用再包含名称空间名称.
注意:必须将using语句放在其他代码元素之前,最好放在程序开始的位置.如果试图放在其他位置,将出错.
------------------------------------------------------------------------------------------------
第九天结束了.又开始犯老毛病了.总是不能从一而终.
一直以来都是自己的最大问题.对新事物的接受力很强,但坚持力不足.总是很快的转移兴趣.
好了,继续学习.
书看到这了,总体感觉这本书还是蛮不错的.挺适合自己.
重载方法,好像实际应用没什么机会用到吧.
作用域与名称空间.截止到现在,介绍的C#程序都是在同一个文件中操作代码.
不知道应用到项目上,是不是也像java一样啊.
看到最后,有个问答很好.解了我一直的疑惑
为什么不将所有的东西都声明为公有的以简化他们?
面向对象语言的优点之一是能够将数据和函数封装到可以看作是一个黑盒子的类中.将成员
设置成私有的,使得内部结构的修改不影响使用类的程序成为可能.
高级数据存储方式:结构,枚举,数组.
.如何在结构中存储值
.结构与类的异同
.枚举是什么?如何使用它来使程序更容易理解?
.如何声明数组并使用它来存储大量数据类型相同的值?
.使用关键字foreach来操纵数组
----------------------------------------------------------
结构
结构是c#提供的一种数据类型,跟类相似.也可以包含数据和方法定义,还可以包含构造函数,常量,字段,
方法,属性,索引器,运算符和嵌套类型.
结构与类的区别
主要区别:存储和访问方式,结构是一种值数据类型,而类是一种引用数据类型.
按值存储比按引用存储更消耗内存,但是在处理少量数据时更有利.
一个通用的经验规则:如果数据成员占用的内存不超过16个字节,使用结构;否则,使用类.
声明规则:
struct NAME
{
body;
}
例如:使用point结构
//stuctpoint.cs - 使用结构
//-------------------------
struct point
{
public int x;
public int y;
}
class pointApp
{
public static void Main()
{
point starting = new point();
point ending= new point();
starting.x = 1;
starting.y = 4;
ending.x = 10;
ending.y = 11;
System.Console.WriteLine("point 1:{0},{1}", starting.x, starting.y);
System.Console.WriteLine("point 2:{0},{1}", ending.x,ending.y);
}
}
定义结构使用关键字struct,访问结构成员的方式与类相同.
声明结构还可以使用这样的格式:point starting;
不使用关键字new
结构的嵌套
和类一样,结构中可以包含其他数据类型,包括其他结构.
建立方式与类相似,主要差别在于实例化line结构时,将给它分配内存并直接存储它.
//structLine.cs - 结构的嵌套
//---------------------------
struct point
{
public int x;
public int y;
}
struct line
{
public point starting;
public point ending;
}
class LineApp
{
public static void Main()
{
line myLine;
myLine.starting.x = 1;
myLine.starting.y = 4;
myLine.ending.x = 10;
myLine.ending.y = 11;
System.Console.WriteLine("point 1:{0},{1}",myLine.starting.x,myLine.starting.y);
System.Console.WriteLine("point 2:{0},{1}",myLine.ending.x,myLine.ending.y);
}
}
结构方法
和类一样,结构也可以包含方法和属性,声明方式与在类中相同,这包括于类中相同的限定符和属性.
可以重载结构方法,传递值以及返回值.
// structLine2.cs - 在结构中添加方法
//-----------------------------------
struct point
{
public int x;
public int y;
}
struct line
{
public point starting;
public point ending;
public double length()
{
double len = 0;
len = System.Math.Sqrt((ending.x - starting.x) * (ending.x - starting.x) + (ending.y - starting.y) * (ending.y - starting.y));
return len;
}
}
class lineApp
{
public static void Main()
{
line myLine;
myLine.starting.x = 1;
myLine.starting.y = 4;
myLine.ending.x = 10;
myLine.ending.y = 11;
System.Console.WriteLine("point 1:{0},{1}", myLine.starting.x, myLine.starting.y);
System.Console.WriteLine("point 2:{0},{1}", myLine.ending.x, myLine.ending.y);
System.Console.WriteLine("Length of line from poing 1 to point 2:{0}", myLine.length());
}
}
结构的构造函数
除了可包含常规方法外,结构还可以有构造函数.与类不同的是,为结构声明构造函数的时候.
必须包含参数,不能为结构声明一个不包含任何参数的构造函数.
//structPoint2.cs - 包含构造函数的point结构
//------------------------------------------
struct point
{
public int x;
public int y;
public point(int x, int y)
{
this.x = x;
this.y = y;
}
//public point()
//{
// this.x = 0;
// this.y = 0;
//}
}
class pointApp
{
public static void Main()
{
point point1 = new point();
point point2 = new point(8,8);
point1.x = 1;
point1.y = 4;
System.Console.WriteLine("point 1:{0},{1}", point1.x, point1.y);
System.Console.WriteLine("poing 2:{0},{1}", point2.x, point2.y);
}
}
结构的构造函数必须初始化结构的数据成员.
如上程序中,point point1=new point();这样不带参数调用结构时,将自动使用默认值初始化每个数据成员.
通常数据成员初始化为0.如果调用的是声明的构造函数,而不是默认的构造函数,则必须在构造函数中初始化
所有的数据成员.
虽然使用默认构造函数,可以省略new关键字,但使用参数来实例化结构时,不能省略.
如:point point(8,8);将出错
结构的析构函数
类可以有析构函数.结构不能还有析构函数,如果试图添加,将出错.
-------------------------------------------------------------------------------------
枚举
c#中可以使用另外一种数据类型,枚举
枚举可以创建有限的几个值的变量.格式如下:
modifiers enum enumName
{
enumMember1,
enumMember2,
...
enumMemberN
}
默认情况下,在枚举变量被声明时,其值被设置为0.
//color.cs - 使用枚举
//-------------------
using System;
class myApp
{
enum Color
{
red,
white,
blue
}
public static void Main()
{
string buffer;
Color myColor;
Console.WriteLine("Enter a value for a color: 0=Red,1=White,2=Blue");
buffer = Console.ReadLine();
myColor = (Color)Convert.ToInt32(buffer);
switch (myColor)
{
case Color.red:
System.Console.WriteLine("/nSwitched to Red...");
break;
case Color.white:
System.Console.WriteLine("/nSwitched to White...");
break;
case Color.blue:
System.Console.WriteLine("/nSwitched to Blue...");
break;
default:
System.Console.WriteLine("/nSwitched to default...");
break;
}
System.Console.WriteLine("/nColor is {0}({1})", myColor, (int)myColor);
}
}
对于所有的枚举来说,当枚举被创建时,第一个成员的值是0,第二个是1,第三个是2.其余依次类推
修改枚举的默认值有两种方法:
一种是在枚举的开始位置加上一个填充字符值,但不适用第一个成员的值是更大的数字.
另一种方法是设置枚举成员的值,可以将他们设置为字面值,其他枚举成员的值,或计算得到的值.
// bday.cs - 设置枚举成员的数值型值
//---------------------------------
using System;
public class myApp
{
enum Month
{
January=1,
February=2,
March=3,
April=4,
May=5,
June=6,
July=7,
August=8,
September=9,
October=10,
November=11,
December=12,
}
struct birthday
{
public Month bmonth;
public int bday;
public int byear;
}
public static void Main()
{
birthday MyBirthday;
MyBirthday.bmonth = Month.August;
MyBirthday.bday = 11;
MyBirthday.byear = 1091;
System.Console.WriteLine("My birthday is {0},{1},{2}", MyBirthday.bmonth, MyBirthday.bday, MyBirthday.byear);
}
}
在这个程序中,将枚举成员的值设置为1~12,而不是使用默认值0~11.
由于成员的值将在前一个值的基础上加1,因此可以不显式的设置第二个成员以及以后的各个成员的值.
可以将这些值设置为其他的值,甚至可以使用公式.
MyBirthday.bmonth = Month.August;将数据成员bmonth赋值为Month.Augst.
也可以使用强制转换的方式将bmonth的值设置为Month.August,但这样做没有那么清晰.
MyBirthday.bmonth=(Month)8;
同样,从输出结果中可以知道,存储的值为August,而不是数字8.
修改枚举的底层类型
前面的范例中,枚举的底层数据类型都是int,实际上枚举可以包含数据类型为:
byte,sbyte,int,uint,shot,ushort,long,ulong.
如未指定数据类型,则默认类型为int.使用如下格式可以转换默认的底层类型:
modifiers enum enumName:typeName{member(s)}
------------------------------------------------------------------------------
使用数组来存储数据
类和结构可以将不同类型的相关信息存储在一起.
有时候,还需要存储多个数据类型相同的值.这时候,数组是最好的解决方案.
声明数组
数组是可以存储多个值的单个数据变量.其中所有值的数据类型相同.
在声明变量时,在数据类型的后面加上[].数组的声明格式:
datatype[] name;
其中.datatype是将在数组中存储的数据类型;方括号指出声明的是一个数组.name是该数组变量的名称.
如:decimal [] balances;
创建了一个名为balances的数组,可以来存储decimal值.但没有为存储变量预留空间.
要预留空间,需要像创建其他对象那样,使用new关键字来实例化该变量.
实例化数组时,必须指出该数组将存储多少个值.
可以使用的方法之一是,在实例化数组时在方括号内指出元素数目.
balances = new decimal[12]
也可以在声明数组的同时进行实例化,如:
decimal[] balances=new decimal[12];
从上可知道,实例化的格式如下:
new datatype [nbr_of_elements]
其中datatype是数组的数组类型,nbr_of_elements是一个数值型值,指出该数组中存储元素的数目.
声明并实例化数组后,便可以使用它,数组中的条目被称为元素.
可以通过索引进行访问.索引是一个标识数组中的偏移量的数字.
第一个元素用索引0标识,这是因为第一个元素位于数组的开始位置,因此偏移量为0.
第二个元素的索引为1,因为其偏移量为1个元素.
最后一个元素的偏移量为数组长度减1.
要访问数组中某个特定的元素,可以使用后面跟方括号内代相应索引的数组名称.
如:要将数组balances的第一元素的值设置为1295.50,可以使用下面的代码
balances[0]=1297.50m;
将一个decimal值赋给数组balances的第三个元素,代码:
balances[2]=1000m;
// balances.cs - 使用数组
//-----------------------
using System;
public class myApp
{
public static void Main()
{
decimal[] balances = new decimal[12];
decimal ttl = 0m;
Random rnd = new Random();
for (int indx = 0; indx < 12; indx++)
{
balances[indx] = (decimal)((rnd.NextDouble() * 10000));
}
for (int indx = 0; indx < 12; indx++)
{
Console.WriteLine("Balance {0}:{1}", indx, balances[indx]);
ttl += balances[indx];
}
Console.WriteLine("======================================");
Console.WriteLine("Total of Balances = {0}", ttl);
Console.WriteLine("Average Balance = {0}", (ttl / 12));
}
}
使用balances数组的代码要比使用12个变量简单的多.结合使用数组名和索引,就像使用同类型的常规变量一样.
使用数组初始化数组元素
在声明和实例化数组的同时,可以初始化各个数组元素的值.如:
decimal[] balances=new decimal[12] {1000.00m,2000.00m,3000.00m,4000.00m,5000.00m,600m,0m.0m,9m,0m,0m,12000m};
声明创建数组balances,第一个元素值1000.00m,第二个2000m,依次类推
在使用这种方式声明的时候,方括号内不指定数组大小,直接给各个元素赋值,也生效.
如果这样初始化:decimal[] balances=new decimal[12] {111m,111r};
则将创建一个一个包含12个元素的数组.开始两个元素的值被初始化111
// fname.cs - 使用数组
//----------------------------
using System;
public class myApp
{
public static void Main()
{
char[] name = new char[] { 'B', 'r', 'a', 'd', 'l', 'e', 'y', (char)0 };
Console.WriteLine("Display content of name array ....");
int ctr = 0;
while (name[ctr] != 0)
{
Console.Write("{0}", name[ctr]);
ctr++;
}
Console.WriteLine("/n...Done.");
}
}
多维数组
多维数组是一个由数组组成的数组,甚至可以使用三维数组.这将是数组复杂化.不建议使用超过三维的数组.
通常使用的是二维数组.要声明二维数组,只需要对声明常规数组的方法进行扩展即可:
byte[,] scores=new byte[15,30];
声明的第一部分增加了一个逗号,而第二部分则使用了由逗号分隔的两个数字.
上述声明创建了一个包含15个元素的数字,其中每个元素都是一个包含30个元素的数组.
因此,数组scores总共可以存储450个数据类型为byte的值.
char[,] lecters=new char[2,3];{{'a','b','c'},{'x','y','z'}};
上述声明创建一个名为lecters的数组,它包含两个元素,每个元素又是一个包含三个字符元素的数组.并且声明的同时初始化了元素的值.
使用多维数组,如:lecters[0,0]代表第一个数组中的第一个元素.
在数组外初始化数组lecters
lecters[0,0]='a';
lecters[0,1]='a';
lecters[0,2]='a';
lecters[1,0]='a';
lecters[1,1]='a';
lecters[1,2]='a';
上面介绍的都是针对两个子数组的长度相同.有时候还将用到存储长度不同的数组.
char[][] myname=new char[3][];
myname[0]=new char[]{'B','r','d','l','e','y'};
myname[1]=new char[]{'l','.'};
myname[2]=new char[]{'j','o','n','e','s'};
数组myname是一个由数组组成的数组,包含三个长度各不相同的字符数组.由于他们的长度不同,所以声明方式如上
在引用数组中的元素时,格式为:myname[0][1],不能使用myname[2,0]这种方式.
如果声明的同时并不想初始化,可以在new char[]的方括号中声明子数组的包含元素个数.
检查数组长度和边界
数组长度被存储在一个名为length的成员中.对于balance数组,可以通过balance.length获取其长度.
通过传递数组的索引号来指出要返回哪个子数组的长度.
// names.cs - 使用锯齿形的二位数组
//--------------------------------------
using System;
public class myApp
{
public static void Main()
{
char[][] name = new char[3][];
name[0] = new char[7] { 'B', 'r', 'a', 'd', 'e', 'l', 'y' };
name[1] = new char[2] { 'l', '.' };
name[2] = new char[6] { 'J', 'u', 's', 't', 'i', 'n' };
Console.WriteLine("Display the sizes of the arrays.../n");
Console.WriteLine("Length of name array {0}", name.Length);
for (int ctr = 0; ctr < name.Length; ctr++)
{
Console.WriteLine("Length of name[{0}] is {1}", ctr, name[ctr].Length);
}
Console.WriteLine("/n/nDisplaying the content of the name array...");
for (int ctr = 0; ctr < name.Length; ctr++)
{
Console.Write("/n");
for (int ctr2 = 0; ctr2 < name[ctr].Length; ctr2++)
{
Console.Write("{0}", name[ctr][ctr2]);
}
}
Console.WriteLine("/n...Done displaying");
}
}
在类和结构中使用数组
数组可以在使用其他数据类型的地方创建和使用.
一般使用较多的都是基本数据类型的数组,类/结构或其他任何数据类型都可以来创建数组.
使用foreach语句
foreach语句使得使用数组更为简单,特别是遍历整个数组的时候.它不是使用代下标的数组名来引用数组
而是使用一个简单的变量.foreach语句的缺点在于,使用简单变量是只读的--不能gie它赋值.格式如下:
foreach(datatype varname in arrayName)
{
statements;
}
其中,datatype是数组的数据类型,varname是用于标识数组中各个元素的变量的名称;
arrayName是foreach语句要遍历的数组的名称.
按套路来,上程序
// foreach1.cs - 将foreach用于数组
//---------------------------------
using System;
public class myApp
{
public static void Main()
{
char[] name = new char[] { 'j', 'u', 's', 't', 'i', 'n' };
Console.WriteLine("Display content of name array...");
foreach (char x in name)
{
Console.Write("{0}", x);
}
Console.WriteLine("/n...Done.");
}
}
使用foreach语句,遍历数组,依次将每个元素称为x.然后将其输出.
第八天的知识点并不多,但是应用性很强,结构/枚举/数组,扩展了c#的数据类型.
================================================================================================
第九天 关于方法的高级主题
.如何给方法传递不同的参数
.如何重载方法
.方法的特征标(signature)
.再谈作用域
.创建自己的名称空间
-----------------------------------------------------------------------
重载方法
重载是面向对象编程的特性之一.
方法重载指的是创建多个名称相同的方法.其中每个方法都在某些方面具备唯一性.以便编译器能够区分它们.
下面程序包含了一个Circle类,其中Area方法被重载.
// circle.cs - polymorphce area method
//通过方法重载来说明多态
//--------------------------------------
using System;
public class Circle
{
public int x;
public int y;
public double radius;
private const float PI = 3.14159F;
public double Area()//uses values from data member
{
return Area(radius);
}
public double Area(double rad)
{
double theArea;
theArea = PI * rad * rad;
Console.WriteLine("The area for radis ({0}) is {1}", rad, theArea);
return theArea;
}
public double Area(int x1, int y1, double rad)
{
return Area(rad);
}
public double Area(int x1, int y1, int x2, int y2)
{
int x_diff;
int y_diff;
double rad;
x_diff = x2 - x1;
y_diff = y2 - y1;
rad = (double)Math.Sqrt((x_diff * x_diff) + (y_diff * y_diff));
return Area(rad);
}
public Circle()
{
x = 0;
y = 0;
radius = 0.0;
}
}
class CircleApp
{
public static void Main()
{
Circle myCircle = new Circle();
Console.WriteLine("Passing nothing...");
myCircle.Area();
Console.WriteLine("/nPassing a radius of 3...");
myCircle.Area(3);
Console.WriteLine("/nPassing a center of (2,4) and a radius of 3....");
Console.WriteLine("/nPassing center of (2,3) and a point of (5,6)...");
myCircle.Area(2, 3, 4, 5);
}
}
Circle类定义了四个Area方法,他们之间的区别在于传递的参数数目不同.
根据接受参数的不同,来区别执行哪个Area方法.
注:在这个程序中.PI使用的并不是常量.而是在第11行声明了一个固定变量.这样,当需要修改PI值的时候,
只要在一个地方修改即可.而不用对分布在应用程序各个地方的硬编码进行修改,后期维护方便.
虽然可以在名称相同的方法中实现完全不同的功能,但并不应该这样做.
习惯上名称相同的方法,得到的结果也是类似的.
构造函数重载
不止常规方法可以重载,还可以重载构造函数.
重载的构造函数可以在创建对象的同时,给他传递值.
// circle1.cs - A simple circle class with overloaded construotors
// 重载构造函数
//--------------------------------------------------------------------
using System;
public class Circle
{
public int x;
public int y;
public int radius;
private const float PI = 3.14150F;
public double area()
{
double theArea;
theArea = PI * radius * radius;
return theArea;
}
public double circumference()
{
double Circ;
Circ = 2 * PI * radius;
return Circ;
}
public Circle()
{
x = 0;
y = 0;
radius = 0;
}
public Circle(int r)
{
x = 0;
y = 0;
radius = r;
}
public Circle(int new_x, int new_y)
{
x = new_x;
y = new_y;
radius = 0;
}
public Circle(int new_x, int new_y, int r)
{
x = new_x;
y = new_y;
radius = r;
}
public void print_circle_info()
{
Console.WriteLine("Circle: Center = {0},{1}", x, y);
Console.WriteLine("Radius={0}", radius);
Console.WriteLine("Area={0}", area());
Console.WriteLine("Circum={0}", circumference());
}
}
class CircleApp
{
public static void Main()
{
Circle first = new Circle();
Circle second = new Circle(4);
Circle third = new Circle(3,4);
Circle fourth = new Circle(1,2,5);
Console.WriteLine("/nFirst Circle:");
first.print_circle_info();
Console.WriteLine("/nSecond Circle:");
second.print_circle_info();
Console.WriteLine("/nThird Circle:");
third.print_circle_info();
Console.WriteLine("/nFourth Circle:");
fourth.print_circle_info();
}
}
分析上述程序清单.
这个程序的重点是Circle类的构造函数.有多个构造函数,每一个接受的参数个数都不同.
在创建对象时,使用了不同的创建方式.分别包含不同个数的参数.
在创建的过程中分别调用相应的构造函数
所谓相应的构造函数,值得是其参数与调用的参数匹配的构造函数.
理解方法的特征标
方法之所以可以重载,是由于各个方法都有特征标.(signature)
如前面介绍的程序,方法的参数个数能决定哪个方法是合适的.
对于被重载方法,还有其他的方式可以区分各个方法.这些差异性最终构成了方法的特征标.
如下述重载方式,也是合法的
MyFun(int)
MyFun(float)
MyFun(ref int)
MyFun(val int)
这种方法并不是以参数个数来区分的,但是通用也可以使用
有些因素不能用作特征标的组成部分:
如返回类型,因为调用方法时,并不使用返回类型.
一个方法使用某种数据类型,另一个方法使用数组,也将出错
如:int myMethod (int)
int myMethod (int[])
也不能通过关键字params来区分.
最后说明下,可以根据需要重载方法任意次,只要每个方法有独特的特征标.
-----------------------------------------------------------------------------------
使用不同数目的参数
前面介绍了如何创建方法,如何给方法传递信息以及传递信息的多种方式--包括按值传递,按
引用传递以及传递可用于返回输出的变量.还可以使用关键字return从方法中返回一个值.
所有这一切,都要求有组织的使用方法.
当需要给方法传递不同数目的参数时,该怎么办呢?
如Console.Write,可以接受一个字符串以及不同数目的其他参数,这些参数的数据类型和值可各不相同.
要接受未知数目的参数,可以使用关键字params,它用于参数列表中,生命参数列表最后面的值.
params与数组一起使用.
看下面的程序,在一个方法中使用了关键字params.
// addem.cs - Using a varaible number of arguments
//------------------------------------------------------
using System;
public class AddEm
{
public static long Add(params int[] args)
{
int ctr = 0;
long Total = 0;
for (ctr = 0; ctr < args.Length; ctr++)
{
Total += args[ctr];
}
return Total;
}
}
class MyApp
{
public static void Main()
{
long Total = 0;
Total = AddEm.Add(1);
Console.WriteLine("Total of (1) = {0}", Total);
Total = AddEm.Add(1, 2);
Console.WriteLine("Total of (1,2) = {0}", Total);
}
}
这里没有创建AddEm的对象,因为Add方法是静态的,因此即使不创建对象,也可以使用类名AddEm来调用它.
下面使用params来指定多种数据类型
// garbage.cs - Using a variable number of arguments of different types
//------------------------------------------------------------------------------
using System;
public class Garbage
{
public static void Print(params object[] args)
{
int ctr = 0;
for (ctr = 0; ctr < args.Length; ctr++)
{
Console.WriteLine("Argument {0} is: {1}", ctr, args[ctr]);
}
}
}
class MyApp
{
public static void Main()
{
long Along = 1234567854545454;
decimal ADec = 1235.56m;
byte Abyte = 42;
string Astring = "Hello Justin";
Console.WriteLine("First call...");
Garbage.Print(1);
Console.WriteLine("Second call...");
Garbage.Print();
Console.WriteLine("/nThird call...");
Garbage.Print(Along,ADec, Abyte, Astring);
Console.WriteLine("/nFourth call....");
Garbage.Print(Astring, "is cool", '!');
}
}
当值被传递给方法时,编译器首先查看是否有匹配的方法.
如果有,则调用该方法.如果没有,将查看是否有包含参数params的方法.
如果找到这样的方法,则使用它.编译器将这些参数放到一个数组内.并将数组传递给方法.
-------------------------------------------------------------------------------------
Main方法和命令参数
Main方法是一个特殊的方法,因为它总是首先被调用.也可以接受不同数目的参数.
但不需要在Main方法中使用params关键字.
之所以不需要使用关键字params,是由于命令行参数将自动加入到一个字符串数组中.
基本上与params的功能相同.格式如下:
public static int/void Main (string[] args)
int和void是可选的,返回整数时使用int,返回其他数据类型使用void.
这里的重点是参数列表:一个名为args的字符串数组.
可以将args改为任何名称,但习惯上都使用args
// command.cs - Checking for command-Line arguments
//--------------------------------------------------
using System;
class CommandLine
{
public static void Main(string[] args)
{
int ctr = 0;
if (args.Length <= 0)
{
Console.WriteLine("NoCommand Line arguments were provided");
return;
}
else
{
for (ctr = 0; ctr < args.Length; ctr++)
{
Console.WriteLine("Argument {0} is {1}",ctr+1, args[ctr]);
}
}
}
}
注:应尽可能将各种东西设置为私有的,通过属性来提供对私有数据成员的共有访问权限.
-----------------------------------------------------------------------------------
作用域
理解被运行阶段环境释放之前,变量的存活时间至关重要.变量的寿命及其可访问性被成为作用域.
作用域有许多级别,最常用的两个数局部和全局.
全局作用域指的是在整个程序清单中都可见,局部指在小范围内可用的变量.
区分类变量和局部变量
区分类变量和局部变量的方法之一是总是引用类.
根据声明变量的方式,明确的引用类变量的方式有两种,如果类变量是标准的,非静态的.可以使用关
键字this.例如:this.x;如何访问静态数据成员,参照第七天的课程.
类作用域限定符
对于方法和数据成员,可以使用的限定符有两种:private和public.
使用public限定符,数据成员和成员函数在类的外面可以被访问.
使用限定符private,数据成员和方法只能在其所在的默认类中被访问.
默认情况下,数据成员和方法是私有的.
声明变量时,未指定是私有还是共有,则它将是私有的.
c#不允许在类的外面声明变量.
不能用于创建对象的类
静态方法和静态数据成员都属于类,而不是各个对象.如果声明了一个数据和方法都是静态的类.则使用
该类声明的对象将不包含任何值.
// MyMath.cs - Static members.
//--------------------------------
using System;
public class MyMath
{
public static long Add(params int[] args)
{
int ctr = 0;
long Answer = 0;
for (ctr = 0; ctr < args.Length; ctr++)
{
Answer += args[ctr];
}
return Answer;
}
public static long Subtract (int arg1,int arg2)
{
long Answer = 0;
Answer = arg1 - arg2;
return Answer;
}
}
class MyApp
{
public static void Main()
{
long Result = 0;
Result = MyMath.Add(1, 2, 3);
MyMath var=new MyMath();
Console.WriteLine("Add result is {0}", Result);
Result = MyMath.Subtract(5, 2);
Console.WriteLine("Subtract Result is {0}", Result);
}
}
上述程序,MyMath类声明了两个方法.在使用时,直接调用MyMath.Add.可以创建MyMath的对象var,虽然它没有起任何作用.
要禁止使用某个类来创建对象.可以创建一个私有的构造函数.
由于带关键private的方法只能在其所属的类中调用.这意味着不能在类的外面调用私有构造函数.
由于构造函数是在创建对象时被调用的,因此使用私有构造函数可以禁止创建对象.
如果给上面的程序添加一个私有构造函数后,再创建对象:MyMath var=new MyMath();
则出错.但是依然可以访问共有的静态类成员.
---------------------------------------------------------------------------------------------
反复验证上面的程序,终于搞清楚public static private的用法.差点想破头.
---------------------------------------------------------------------------------------------
再谈名称空间
名称空间可以用于帮助组织类和其他类型.
可以包含其他名称空间,类,结构,枚举,接口和代表.
接口和代表将在后面介绍.
给名称空间命名
名称空间的名称可以是任何合法的标识符,这意味着必须游标准字符和下划线组成.另外还可以包含句点.
和其他标识符一样,应该给名称空间取一个描述性的名称.
声明名称空间
要创建名称空间,使用关键字namespace后跟标识该名称空间的名称.然后用花括号将该名称空间包含的类型括起.
// namespace.cs - Declaring namespaces 声明名称空间
//-------------------------------------------------
using System;
namespace Consts
{
public class PI
{
public static double value = 3.14159;
private PI() { } //private constructor
}
public class three
{
public static int value = 3;
private three() { } //private constructor
}
}
namespace MyMath
{
public class Routine
{
public static long Add(params int[] args)
{
int ctr = 0;
long Answer = 0;
for (ctr = 0; ctr < args.Length; ctr++)
{
Answer += args[ctr];
}
return Answer;
}
public static long Subtract(int arg1, int arg2)
{
long Answer = 0;
Answer = arg1 - arg2;
return Answer;
}
}
}
class MyApp
{
public static void Main()
{
long Result = 0;
Result = MyMath.Routine.Add(1, 2, 3);
Console.WriteLine("Add result is {0}", Result);
Result = MyMath.Routine.Subtract(5, 2);
Console.WriteLine("Subtract result is {0}", Result);
Console.WriteLine("/nThe value of PI is {0}", Consts.PI.value);
Console.WriteLine("The value of three is {0}", Consts.three.value);
}
}
在外部使用名称空间包含的成员时,必须指定名称空间.可以使用using来省略.
using和名称空间
C#中的关键字using,使得使用名称空间更容易,并提供了两项功能.
首先,使用using给名称空间取一个别名;
using aliasname=namespaceOrClassName;
其中,aliasname是要使用的别名.namespaceOrClassName是名称空间或类的名称.
例:using doit=System.Console;
可以在使用的地方:doit.WriteLIne
其次,使用using后,不必使用全限定名称,使得访问名称空间中的类型将更为容易.
例:using System;
通过这行代码,再使用System中的类和类型时,不用再包含名称空间名称.
注意:必须将using语句放在其他代码元素之前,最好放在程序开始的位置.如果试图放在其他位置,将出错.
------------------------------------------------------------------------------------------------
第九天结束了.又开始犯老毛病了.总是不能从一而终.
一直以来都是自己的最大问题.对新事物的接受力很强,但坚持力不足.总是很快的转移兴趣.
好了,继续学习.
书看到这了,总体感觉这本书还是蛮不错的.挺适合自己.
重载方法,好像实际应用没什么机会用到吧.
作用域与名称空间.截止到现在,介绍的C#程序都是在同一个文件中操作代码.
不知道应用到项目上,是不是也像java一样啊.
看到最后,有个问答很好.解了我一直的疑惑
为什么不将所有的东西都声明为公有的以简化他们?
面向对象语言的优点之一是能够将数据和函数封装到可以看作是一个黑盒子的类中.将成员
设置成私有的,使得内部结构的修改不影响使用类的程序成为可能.