标签:
1.类和对象
1.1类和对象的关系
为什么要采用类和对象思想进行程序设计与开发?
面向对象的程序设计和问题求解更符合人们的思维习惯。
类和对象的定义?
什么是对象?实际上,现实世界就是由各种对象组成的,如人、汽车、动物、植物等。复杂的对象可以由简单的对象组成。对象都具有各自的属性,如形状、颜色、重量等;对外界都呈现出各自的行为,如人可以走路、说话、唱歌;汽车可以启动、加速、减速、刹车、停止等。
在OOP中,对象就是变量和相关的方法的集合。其中变量表明对象的属性,方法表明对象所具有的行为。一个对象的变量构成了这个对象的核心,包围在它外面的方法使这个对象和其他对象分离开来。例如:我们可以把汽车抽象为一个对象,用变量来表示它当前的状态,如速度、油量、型号、所处的位置等,它的行为则为上面提到的加速、刹车、换档等。操作汽车时。不用去考虑汽车内部各个零件如何运作的细节,而只需根据汽车可能的行为使用相应的方法即可。实际上,面向对象的程序设计实现了对象的封装,使我们不必关心对象的行为是如何实现的这样一些细节。通过对对象的封装,实现了模块化和信息隐藏。有利于程序的可移植性和安全性,同时也利于对复杂对象的管理。简单地说,对象非常类似于本书前面讨论的结构类型。略为复杂的对象可能不包含任何数据,而是只包含函数,表示一个过程。
在研究对象时主要考虑对象的属性和行为,有些不同的对象会呈现相同或相似的属性和行为,如轿车、卡车、面包车。通常将属性及行为相同或相似对象归为一类。类可以看成是对象的抽象,代表了此类对象所具有的共同属性和行为。典型的类是“人类”,表明人的共同性质。比如我们可以定义一个汽车类来描述所有汽车的共性。通过类定义人们可以实现代码的复用。我们不用去描述每一个对象(如某辆汽车),而是通过创建类(如汽车类)的一个实例来创建该类的一个对象,这样大大碱化了软件的设计。
类是对一组具有相同特征的对象的抽象描述,所有这些对象都是这个类的实例。在C#中,类是一种数据类型,而对象是该类型的变量,变量名即是某个具体对象的标示名。
类的定义:
C#使用class关键字来定义类。其基本结构如下:
Class MyClass
{
// class members
}
这段代码定义了一个类MyClass。定义了一个类后,就可以对该类进行实例化。在默认情况下,类声明为内部的,即只有当前代码才能访问,可以用intemal访问修饰符关键字显式指定,如下所示(但这是不必要的):
internal class MyClass
{
// class members
}
另外,还可以制定类是公共的,可以由其它任意代码访问。为此,需要使用关键字public:
public class MyClass
{
// class members
}
除了这两个访问修饰符关键字外,还可以指定类是抽象的(不能实例化,只能继承,可以有抽象成员)或密封的(sesled,不能继承)。为此,可以使用两个互斥的关键字abstract或sealed。所以,抽象类必须用下述方式声明:
public abstract class MyClass
{
// class members, may be abstract
}
密封类的声明如下所示:
public sealed class MyClass
{
//class members
}
还可以在类定义中指定继承。C#支持类的单一继承,即只能有一个基类,语法如下:
class MyClass : MyBaseClass
{
// class members
}
在C#的类定义中,如果继承了一个抽象类,就必须执行所继承的所有抽象成员(除非派生类也是抽象的)。
编译器不允许派生类的可访问性比其基类更高。也就是说,内部类可以继承于一个公共类,但公共类不能继承于一个内部类。因此,下述代码就是不合法的:
internal class MyBaseClass
{
// class members
}
public class MyClass : MyBaseClass
{
// class members
}
在C#中,类必须派生于另一个类。如果没有指定基类,则被定义的类就继承于基类System.Object。
除了以这种方式指定基类外,还可以指定支持的接口。如果指定了基类,它必须紧跟在冒号的后面,之后才是指定的接口。必须使用逗号分隔基类名(如果有基类)和接口名。
例如,给MyClass添加一接口,如下所示:
class MyClass : IMyInterface
{
// class memebrs
}
所有的接口成员都必须在支持该接口的类中实现,但如果不想使用给定的接口成员,可以提供一个“空”的执行方式(没有函数代码)。
下面的声明是无效的,因为基类MyBaseClass不是继承列表中的第一项:
class MyClass : IMyInterface, MyBaseClass
{
// class members
}
指定基类和接口的正确方式如下:
class : MyBaseClass, ImyInterface
{
// class members
}
可以指定多个接口,所以下面的代码是有效的:
public class MyClass : MyBaseClass, ImyInterface, ImySecondInterface
{
// class members
}
类和对象之间关系?
类是所有人,对象是某个人
1.2定义类
定义类的语法?
public class Class1
{
public Class1()//构造函数
{
}
public string test()//方法1
{
return "this is a test!";
}
public string test(string s)//方法2(重载了方法1)
{
return s;
}
}用关键字class 来声明类,
成员变量定义类的属性,
属性定义与字段类似,但内容比较多。属性有两个类似于函数的块,一个用于获取属性的值,一个用于设置属性的的值,例子
class MyClass
{
private int myInt;//字段
//下面为属性
public int MyInt{get;set;}
}
get块一般有一个属性类型的返回值,简单的属性一般与
一个私有字段相关蓝,用来控制对这个字段的访问,这是get
块可以直接返回该字段的值;
set函数以类似的方式把一个值赋给字段,这是可以使用
关键字value引用用户提供的属性值;value值等于类型和属性
相同的一个值,如果属性的类型和字段的类型相同,就不用
进行类型转换。
属性可以使用vartual、override和abstract关键字,但是
不能将这几个关键字用于字段。
成员方法定义类的行为?
1.3创建对象
new运算符?
对象的创建过程?
先看代码:
namespace Temp { class Program { static void Main(string[] args) { Class1 c = new Class1(); } } class BaseClass { int z = 3; public BaseClass() { MethodA(); } public virtual void MethodA() { Console.WriteLine("BaseClass.MethodA"); } } class Class1 : BaseClass { int x = 1; int y; public Class1() { y = 2; } public override void MethodA() { Console.WriteLine(x + y); } } }
以上是一个简单的继承层次结构。不要使用 VS 测试,脑子分析一下最终输出了什么?
分析过程中脑子存在任何疑问的同学,请马上动手测试一下吧,在 Main 方法中设个断点单步跟踪一下。
这里描述一下单步调试的整个过程:
这里说明了几个顺序问题:
总结:当执行 new 语句时,发生了以下几件事情(更细的情形本文暂不探讨):
1.4成员变量和局部变量
类的成员变量,又称为字段
介绍变量的作用域,
变量的作用域是可以访问该变量的代码域,确定作用域可以根据以下规则。
(1)字段(也称成员变量)所属的类在某个作用域内,则字段也在该作用域内。
(2)在while、for、do或类似语句中声明的局部变量存在于该循环体内。
(3)局部变量存在于表示声明该变量的块语句或方法结束的封闭括号之前的作用域内,一旦离开这个作用域后要再次使用这个局部变量名则要求用户重新声明,虽然这两个变量同名但完全是两个变量,因为它们有个自的作用域。
在代码段“1”处两个循环都在使用变量I,可以这样的原因是在两次声明中I都是在循环内部声明的,这样变量I对于循环来说是局部变量。而对于代码代“2”中的变量j,因为变量j以经在for循环开始前定义,在执行for循环时j处于其作用域内。这样便和循环内的同名变量冲突,编译器无法区别这两个变量。
说明成员变量和局部变量的定义和区别
值类型,
C#的所有值类型均隐式派生自System.ValueType:
结构体:struct(直接派生于System.ValueType);
数值类型:
整 型:sbyte(System.SByte的别名),short(System.Int16),int(System.Int32),long (System.Int64),byte(System.Byte),ushort(System.UInt16),uint (System.UInt32),ulong(System.UInt64),char(System.Char);
浮点型:float(System.Single),double(System.Double);
用于财务计算的高精度decimal型:decimal(System.Decimal)。
bool型:bool(System.Boolean的别名);
用户定义的结构体(派生于System.ValueType)。
枚举:enum(派生于System.Enum);
可空类型(派生于System.Nullable<T>泛型结构体,T?实际上是System.Nullable<T>的别名)。
每种值类型均有一个隐式的默认构造函数来初始化该类型的默认值。例如:
int i = new int();
等价于:
Int32 i = new Int32();
等价于:
int i = 0;
等价于:
Int32 i = 0;
使用new运算符时,将调用特定类型的默认构造函数并对变量赋以默认值。在上例中,默认构造函数将值0赋给了i。MSDN上有完整的默认值表。
关于int和Int32的细节,在我的另一篇文章中有详细解释:《理解C#中的System.Int32和int》。
所有的值类型都是密封(seal)的,所以无法派生出新的值类型。
值得注意的是,System.ValueType直接派生于System.Object。即System.ValueType本身是一个类类型,而 不是值类型。其关键在于ValueType重写了Equals()方法,从而对值类型按照实例的值来比较,而不是引用地址来比较。
可以用Type.IsValueType属性来判断一个类型是否为值类型:
TestType testType=new TestType();
if(testType.GetType().IsValueType)
{
Console.WriteLine(“{0}is value type”,testType.ToString());
}
引用类型的概念
C#有以下一些引用类型:
数组(派生于System.Array)
用户用定义的以下类型:
类:class(派生于System.Object);
接口:interface(接口不是一个“东西”,所以不存在派生于何处的问题。Anders在《C# Programming Language》中说,接口只是表示一种约定[contract]);
委托:delegate(派生于System.Delegate)。
object(System.Object的别名);
字符串:string(System.String的别名)。
可以看出:
引用类型与值类型相同的是,结构体也可以实现接口;
引用类型可以派生出新的类型,而值类型不能;
引用类型可以包含null值,值类型不能(可空类型功能允许将 null 赋给值类型);
引用类型变量的赋值只复制对对象的引用,而不复制对象本身。而将一个值类型变量赋给另一个值类型变量时,将复制包含的值。
对于最后一条,经常混淆的是string。我曾经在一本书的一个早期版本上看到String变量比string变量效率高;我还经常听说String是引用类型,string是值类型,等等。例如:
string s1 = "Hello, ";
string s2 = "world!";
string s3 = s1 + s2;//s3 is "Hello, world!"
这确实看起来像一个值类型的赋值。再如:
string s1 = "a";
string s2 = s1;
s1 = "b";//s2 is still "a"
改变s1的值对s2没有影响。这更使string看起来像值类型。实际上,这是运算符重载的结果,当s1被改变时,.NET在托管堆上为s1重新分配了内存。这样的目的,是为了将做为引用类型的string实现为通常语义下的字符串。
值类型和引用类型在内存中的部署
经常听说,并且经常在书上看到:值类型部署在栈上,引用类型部署在托管堆上。实际上并没有这么简单。
MSDN上说:托管堆上部署了所有引用类型。这很容易理解。当创建一个应用类型变量时:
object reference = new object();
关键字new将在托管堆上分配内存空间,并返回一个该内存空间的地址。左边的reference位于栈上,是一个引用,存储着一个内存地址;而这个 地址指向的内存(位于托管堆)里存储着其内容(一个System.Object的实例)。下面为了方便,简称引用类型部署在托管推上。
再来看值类型。《C#语言规范》 上的措辞是“结构体不要求在堆上分配内存(However, unlike classes, structs are value types and do not require heap allocation)”而不是“结构体在栈上分配内存”。
2值类型和引用类型
2.1值类型
值类型的概念
判断类型是否是值类型
值类型的存储状态
2.2引用类型
引用类型的概念
new运算符
引用类型的存储状态
对象的内存分配
2.3比较值类型的存储状态
3.4对象的生命周期
每个对象都一个明确定义的生命周期,即从使用类定义开始一直到删除它为止。在对象的生命周期中,除了“正在使用”的正常状态之外,还有两个重要的阶段:
● 构造阶段——对象最初进行实例化的时期。这个初始化过程称为构造阶段,由构造函数完成。
● 析构阶段——在删除一个对象时,常常需要执行一些清理工作,例如释放内存,由析构函数完成。
1、方法签名
通过指定方法的访问级别(例如 public 或 private)、可选修饰符(例如 abstract 或sealed)、返回值、名称和任何方法参数,可以在类或结构中声明方法。这些部分统称为方法的“签名”。
为进行方法重载,方法的返回类型不是方法签名的一部分。但是,在确定委托和委托所指向方法之间的兼容性时,返回类型是方法签名的一部分。
方法参数括在括号中,并用逗号隔开。空括号表示方法不需要参数。下面的类包含三个方法:
abstract class Motorcycle
{
// Anyone can call this.
public void StartEngine() {/* Method statements here */ }
// Only derived classes can call this.
protected void AddGas(int gallons) { /* Method statements here */ }
// Derived classes can override the base class implementation.
public virtual int Drive(int miles, int speed) { /* Method statements here */ return 1; }
// Derived classes must implement this.
public abstract double GetTopSpeed();
}
2、方法访问
在对象上调用方法类似于访问字段。在对象名称之后,依次添加句点、方法名称和括号。参数在括号内列出,并用逗号隔开。因此,可以按以下示例中的方式调用Motorcycle 类的方法:
class TestMotorcycle : Motorcycle
{
public override double GetTopSpeed()
{
return 108.4;
}
static void Main()
{
TestMotorcycle moto = new TestMotorcycle();
moto.StartEngine();
moto.AddGas(15);
moto.Drive(5, 20);
double speed = moto.GetTopSpeed();
Console.WriteLine("My top speed is {0}", speed);
}
}
3、方法形参和实参
方法定义指定所需任何“形参”的名称和类型。调用代码在调用方法时,将为每个形参提供称为“实参”的具体值。实参必须与形参类型兼容,但调用代码中使用的实参名称(如果有)不必与方法中定义的形参名称相同。例如:
public void Caller()
{
int numA = 4;
// Call with an int variable.
int productA = Square(numA);
int numB = 32;
// Call with another int variable.
int productB = Square(numB);
// Call with an integer literal.
int productC = Square(12);
// Call with an expression that evaulates to int.
productC = Square(productA * 3);
}
int Square(int i)
{
// Store input argument in a local variable.
int input = i;
return input * input;
}
4、通过引用传递与通过值传递
默认情况下,将值类型传递给方法时,传递的是副本而不是对象本身。因此,对参数所做的更改对于调用方法中的原始副本没有影响。可以使用 ref 关键字通过引用传递值类型。
“引用类型”通过引用进行传递。将引用类型的对象传递给方法时,引用指向原始对象而不是副本。因此,通过此引用所进行的更改将反映在调用方法中。引用类型是通过使用 class 关键字创建的,如下面的示例中所示:
public class SampleRefType
{
public int value;
}
现在,如果将基于此类型的对象传递给方法,则会通过引用传递该对象。例如:
public static void TestRefType()
{
SampleRefType rt = new SampleRefType();
rt.value = 44;
ModifyObject(rt);
Console.WriteLine(rt.value);
}
static void ModifyObject(SampleRefType obj)
{
obj.value = 33;
}
此示例的效果本质上与前一示例相同。但是,由于使用的是引用类型,因此ModifyObject 所做的更改反映在 TestRefType 方法中创建的对象中。因此,TestRefType方法将显示值 33。
5、返回值
方法可以向调用方返回值。如果返回类型(方法名称前列出的类型)不是 void,则方法可以使用 return 关键字来返回值。如果语句中 return 关键字的后面是与返回类型匹配的值,则该语句将该值返回给方法调用方。return 关键字还会停止方法的执行。如果返回类型为 void,则可使用没有值的 return 语句来停止方法的执行。如果没有 return关键字,方法执行到代码块末尾时即会停止。具有非 void 返回类型的方法才能使用return 关键字返回值。例如,下面的两个方法使用 return 关键字来返回整数:
class SimpleMath
{
public int AddTwoNumbers(int number1, int number2)
{
return number1 + number2;
}
public int SquareANumber(int number)
{
return number * number;
}
}
若要使用从方法返回的值,调用方法可以在本来使用同一类型的值就已足够的任何位置使用方法调用本身。还可以将返回值赋给变量:例如,下面的两个代码示例可实现相同的目的:
int result = obj.AddTwoNumbers(1, 2);
obj.SquareANumber(result);
obj.SquareANumber(obj.AddTwoNumbers(1, 2));
可以选择使用局部变量(本例中为 result)来存储值。这有助于提高代码的可读性,并且如果要为方法的整个范围存储参数的原始值,可能必须这样做。
标签:
原文地址:http://www.cnblogs.com/gaoweixiao99/p/4827783.html