JumboWu 主页

版权声明:本文为Jumbo原创文章,采用[知识共享 署名-非商业性使用-禁止演绎 4.0 国际 许可协议],转载前请保证理解此协议 原文出处:https://www.jianshu.com/p/5e7f3542ae54

Unity从入门到精通-C#篇(二)

第二章 编程基础

数据类型

在C#中变量用于存储数据,分为一下几种类型:

值类型(Value types)

  1. 值类型变量可以直接分配给一个值。它们是从类 System.ValueType 中派生的。

  2. 值类型直接包含数据。比如 int、char、float,它们分别存储整数、字符、浮点数。当您声明一个 int 类型时,系统分配内存来存储值。

  3. 下表列出了 C#中的值类型:

类型 描述 取值范围 默认值
bool 布尔值 True 或 False False
byte 8位无符号整数 0到255 0
char 16位Unicode字符 U +0000到U+ffff ‘\0’
short 16位有符号整数类型 -32768到32767 0
int 32位有符号整数类型 -2,147,483,648 到 2,147,483,647 0
float 32位单精度浮点型 -3.4 x 1038 到 + 3.4 x 1038 0.0F
double 64位双精度浮点型 (+/-)5.0x10-324 到 (+/-)1.7 x 10308 0.0D
long 64位有符号整数类型 -923,372,036,854,775,808 到 9,223,372,036,854,775,807 0L
sbyte 8位有符号整数类型 -128到127 0
ushort 16位无符号整数类型 0到65535 0
uint 32位无符号整数类型 0到4,294,967,295  
ulong 64位无符号整数类型 0到 18,446,744,073,709,551,615 0
decimal 128位十进制,28到2位有效为,适合财务和货币计算 (-7.9 x 1028 - 7.9 x 1028) / (100 - 28) 0.0M

如果需要得到一个类型或者变量的存储大小,可以通过sizeof方法获取。表达式sizeof(type), 如sizeof(int) 等于4个字节

引用类型(Reference types)

  1. 引用类型不包含存储在变量中的实际数据,但它们包含对变量的引用;

  2. 也就是,它们指的是一个内存地址;

  3. 使用多个变量时,引用类型可以指向一个内存地址;

  4. 如果内存地址的数据是由一个变量改变的,其他变量会自动反映这个值的变化。

内置的 引用类型有:objectdynamic 和 string

对象(Object)类型 是 C# 通用类型系统(Common Type System -CTS)中所有数据类型的基类。Object 是 System.Object类的别名。所以对象(Object)类型可以被分配任何其他类型(值类型、引用类型、预定义类型或用户自定义类型)的值。但是,在分配值之前, 需要先进行类型转换。

当一个值类型转换为对象类型时,则被称为 装箱;反之,当一个对象类型转换为值类型时,则被称为 拆箱。(尽量避免频繁的装箱、拆箱操作,有性能消耗),例如:

object obj;

obj = 1;//装箱

int value = (int)obj;//拆箱

您可以存储任何类型的值在动态数据类型变量中。这些变量的类型检查是在运行时发生的。

声明动态类型的语法:

dynamic <变量名> = value

例如:

dynamic d = 1;

动态类型与对象类型相似,但是对象类型变量的类型检查是在编译时发生的,而动态类型变量的类型检查是在运行时发生的

字符串(String)类型 允许您给变量分配任何字符串值。字符串(String)类型是System.String 类的别名。它是从对象(Object)类型派生的。字符串(String)类型的值可以通过两种形式进行分配:引号和\@引号。

例如:

例如:

string fileName = "D:\\\\文本文件\\\\test.txt";

使用@后:

string fileName =  @"D:\\文本文件\\test.txt";

例如:

string str = "Hello"

\+ "World";

使用@后:

string str = @"Hello

World";

C#是不允许关键字作为标识符(类名、变量名、方法名、空间名等)使用的,但如果加上\@之后就可以了
例如:

public void @static(int @int){
       if (@int > 0){
            //do something
       }
}

指针类型(Pointer types)

指针类型变量存储另一种类型的内存地址。C# 中的指针与 C 或 C++ 中的指针有相同的功能。

声明指针类型的语法:

Type* identifier;

例如:

char* cptr;

int* iptr;

类型转换

在 C# 中,类型转换有两种形式:

隐式类型转换 - 这些转换是 C# 默认的以安全方式进行的转换,不会导致数据丢失。例如,从小的整数类型转换为大的整数类型,从派生类转换为基类。

例如:

  1. sbyte类型到short, int, long, float, double,或decimal类型。
  2. byte类型到short, ushort, int, uint, long, ulong, float, double ,或decimal类型。
  3. short类型到int, long, float, double ,或decimal类型。
  4. ushort类型到int, uint, long, ulong, float, double ,或decimal类型。
  5. int类型到long, float, double, 或decimal类型。
  6. uint类型到long, ulong, float, double, 或decimal类型。
  7. long类型到float, double, 或decimal类型。
  8. ulong类型到float, double, 或decimal类型。
  9. char类型到ushort, int, uint, long, ulong, float, double, 或decimal类型。
  10. float类型到double类型。

显式类型转换 -即强制类型转换。显式转换需要强制转换运算符,而且强制转换会造成数据丢失。

例如:

  1. sbytebyte, ushort, uint, ulong,或char

  2. bytesbytechar

  3. shortsbyte, byte, ushort, uint, ulong,或char

  4. ushortsbyte, byte, short,或char

  5. intsbyte, byte, short, ushort, uint, ulong,或char

  6. uintsbyte, byte, short, ushort, int,或char

  7. longsbyte, byte, short, ushort, int, uint, ulong,或char

  8. ulongsbyte, byte, short, ushort, int, uint, long,或char

  9. charsbyte, byte,或short

  10. floatsbyte, byte, short, ushort, int, uint, long, ulong, char,或decimal

  11. doublesbyte, byte, short, ushort, int, uint, long, ulong, char, float,或decimal

  12. decimalsbyte, byte, short, ushort, int, uint, long, ulong, char, float,或double

当转换有丢失信息或导致异常抛出,转换按照下列规则进行:

  1. 对于从一种整型到另一种整型的转换,编译器将针对转换进行溢出检测,如果没有发生溢出,转换成功,否则抛出一个OverflowException异常。这种检测还与编译器中是否设定了checked选项有关。

  2. 对于从float,double,或decimal到整型的转换,源变量的值通过舍入到最接近的整型值作为转换的结果。如果这个整型值超出了目标类型的值域,则将抛出一个OverflowException异常。

  3. 对于从double到float的转换,double值通过舍入取最接近的float值。如果这个值太小,结果将变成正0或负0;如果这个值太大,将变成正
    无穷或负无穷。如果原double值是Nan,则转换结果也是NaN。

  4. 对于从float或double到decimal的转换,源值将转换成小数形式并通过舍入取到小数点后28位(如果有必要的话)。如果源值太小,则结果为0;如果太大以致不能用小数表示,或是无穷和NaN,则将抛出InvalidCastException异常。

  5. 对于从decimal到float或double的转换,小数的值通过舍入取最接近的值。这种转换可能会丢失精度,但不会引起异常。

变量

变量是存储数据类型的一个名称。每个变量都有一个特定的类型,类型决定了变量在内存中的大小。

C#中基本的值类型大致如下:

类型 举例
整数类型 sbyte、 byte、 short、 ushort、 int、 uint、 long 、ulong、 char
浮点数 float 、 double
十进制类型 decimal
布尔类型 true或false
空类型 可为空值的数据类型
枚举类型 enum自定义类型

C#还可以定义其他自定义类型,例如引用类型:class类。

变量定义(声明)

<数据类型> <变量>

例如:

int i,j,k;

char ch;

float f;

double d;

bool b;

变量初始化

变量通过在等号后面跟一个常量表达式进行初始化(赋值)。

例如:

变量名 = 变量值

可以在声明时,初始化

int i = 1;

char ch = 'h';

byte bt = 33;

double pi = 3.141592;
  1. 初始化变量是一个良好的编程习惯

左值和右值

C# 中的两种表达式:

  1. 左值:表达式可以出现在赋值语句的左边或右边。

  2. 右值:表达式可以出现在赋值语句的右边,不能出现在赋值语句的左边。

变量是左值的,所以可以出现在赋值语句的左边。数值是右值的,因此不能被赋值,不能出现在赋值语句的左边。

下面是一个有效的语句:

int a = 10;

下面是一个无效的语句,会产生编译时错误:

1 = 2

常量

常量是固定值,程序执行期间不会改变。常量可以是任何基本数据类型,比如整数常量、浮点常量、字符常量或者字符串常量,还有枚举常量。

常量可以被当作常规的变量,只是它们的值在定义后不能被修改。

整数常量

整数常量可以是十进制、八进制或十六进制的常量。前缀指定基数:0x 或 0X 表示十六进制,0 表示八进制,没有前缀则表示十进制。

整数常量也可以有后缀,可以是 U 和 L 的组合,其中,U 和 L 分别表示 unsigned 和 long。后缀可以是大写或者小写,多个后缀以任意顺序进行组合。

这里有一些整数常量的实例:

常量 描述
118 合法
220u 合法
0xABBL 合法
039 非法:9不是一个八进制数字
025UU 非法:不能重复后缀

以下是各种类型的整数常量的实例:

常量 描述
118 十进制
0123 八进制
0xABBL 十六进制
39 int
39u uint
39l long
39ul ulong

浮点常量

一个浮点常量是由整数部分、小数点、小数部分和指数部分组成。您可以使用小数形式或者指数形式来表示浮点常量。

这里有一些浮点常量的实例:

常量 描述
3.141592 合法
3141592E-5L 合法
250E 非法:没有指数

使用小数形式表示时,必须包含小数点、指数或同时包含两者。使用指数形式表示时,必须包含整数部分、小数部分或同时包含两者。有符号的指数是用 e 或 E 表示的。

字符常量

字符常量是括在单引号里,例如,’x’,且可存储在一个简单的字符类型变量中。一个字符常量可以是一个普通字符(例如 ‘x’)、一个转义序列(例如 ‘\t’)或者一个通用字符(例如 ‘\u02C0’)。

在 C# 中有一些特定的字符,当它们的前面带有反斜杠时有特殊的意义,可用于表示换行符(\n)或制表符 tab(\t)。在这里,列出一些转义序列码:

转义序列 描述
\\ \ 字符
\’ ‘ 字符
\” “ 字符
\? ? 字符
\a Alert 或 bell
\b 退格键(Backspace)
\f 换页符(Form feed)
\n 换号符号(Newline)
\r 回车(Enter)
\t 水平制表符(Tab)
\v 垂直制表符(Tab)
\ooo 一到三位的八进制数
\xhh… 一个或多个数字的十六进制数

字符串常量

字符串常量是括在双引号 “” 里,或者是括在 @”“里。字符串常量包含的字符与字符常量相似,可以是:普通字符、转义序列和通用字符。

使用字符串常量时,可以把一个很长的行拆成多个行,可以使用空格分隔各个部分。

这里是一些字符串常量的实例。下面所列的各种形式表示相同的字符串。

string str1 = "hello, world"; // hello, world
string str2 = @"hello, world"; // hello, world
string str3 = @"one
two
three";

常量定义

常量用const关键字来定义

例如:

const 数据类型 常量名 = 常量值

const int a = 10;

运算符

运算符是一种告诉编译器执行特定数学或逻辑操作的符号。

算术运算符

下表显示了 C# 支持的所有算术运算符。假设变量 A 的值为 1,变量 B 的值为2,则:

运算符 描述 示例
+ 把两个操作数相加 A + B = 3
- 第一个操作数减去第二个操作数 A – B = -1
* 把两个操作数相乘 A * B = 2
/ 分子除以分母 B / A = 2
% 取模运算符,整除后的余数 B % A = 2
++ 自增运算符,整数值加1 A++后 A 等于2
-- 自减运算符,整数值减1 A—后 A 等0
  1. int C = A++ ; //A先把值1赋给C,此时C等于1,然后A自加1,此时A等于2

  2. int C = ++A ; //A先自加1,此时A等于2,再赋给C,此时C等于2

  3. int C = A– ; // A先把值1赋给C,此时C等于1,然后A自减1,此时A等于0

  4. int C = –A; // A先自减1,此时A等于0,再赋给C,此时C等于0

++,–运算符在变量左边,先运算,再赋值,反之再变量右边,先赋值,再运算

关系运算符

下表显示了 C# 支持的所有关系运算符。假设变量 A 的值为 1,变量 B 的值为2,则:

运算符 描述 示例
== 检查两个操作数的值是否相等,如果相等则条件为真。 A == B, 不为真
!= 检查两个操作数的值是否相等,如果不相等则条件为真。 A != B, 为真
> 检查左操作数的值是否大于右操作数的值,如果是则条件为真。 A > B, 不为真
< 检查左操作数的值是否小于右操作数的值,如果是则条件为真。 A < B, 为真
>= 检查左操作数的值是否大于或等于右操作数的值,如果是则条件为真。 A >= B, 不为真
<= 检查左操作数的值是否小于或等于右操作数的值,如果是则条件为真。 A <= B, 为真

逻辑运算符

下表显示了 C# 支持的所有逻辑运算符。假设变量 A 为布尔值true,变量 B 为布尔值 false,则:

运算符 描述 示例
&& 称为逻辑与运算符。如果两个操作数都非零,则条件为真。 A && B, 为假
|| 称为逻辑或运算符。如果两个操作数中有任意一个非零,则条件为真。 A || B, 为真
称为逻辑非运算符。用来逆转操作数的逻辑状态。如果条件为真则逻辑非运算符将使其为假。。 !(A && B), 为真

位运算符

位运算符作用于位,并逐位执行操作。&、 | 和 \^ 的真值表如下所示:

p q p&q p|q p\^q
0 0 0 0 0
0 1 0 1 1
1 0 0 1 1
1 1 1 1 0

假设如果 A = 80,且 B = 17,现在以二进制格式表示,它们如下所示:

A = 0101 0000

B = 0001 0001

-—————-

A&B = 0001 0000

A|B = 0101 0001

A\^B = 0100 0001

~A  = 1010 1111

下表列出了 C# 支持的位运算符。假设变量 A 的值为 80,变量 B 的值为17,则:

运算符 描述 示例
& 如果同时存在于两个操作数中,二进制 AND 运算符复制一位到结果中。 A & B = 16, 二进制0001 0000
| 如果存在于任一操作数中,二进制 OR 运算符复制一位到结果中。 A | B = 81, 二进制0101 0001
\^ 如果存在于其中一个操作数中但不同时存在于两个操作数中,二进制异或运算符复制一位到结果中。 A \^ B = 65, 二进制0100 0001
~ 按位取反运算符是一元运算符,具有”翻转”位效果,即0变成1,1变成0,包括符号位。 ~A = 175, 二进制1010 1111
>> 二进制右移运算符。左操作数的值向右移动右操作数指定的位数。 A >> 2 = 20, 二进制 0001 0100
<< 二进制左移运算符。左操作数的值向左移动右操作数指定的位数。 A << 2 = 320, 二进制 0001 0100 0000

赋值运算符

下表列出了 C# 支持的赋值运算符:

运算符 描述 示例
= 简单的赋值运算符,把右边操作数的值赋给左边操作数 C = A + B
+= 加且赋值运算符,把右边操作数加上左边操作数的结果赋值给左边操作数 C += A 相当于 C = C+ A
-= 减且赋值运算符,把左边操作数减去右边操作数的结果赋值给左边操作数 C -= A 相当于 C = C - A
*= 乘且赋值运算符,把右边操作数乘以左边操作数的结果赋值给左边操作数 C *= A 相当于 C = C * A
/= 除且赋值运算符,把左边操作数除以右边操作数的结果赋值给左边操作数 C /= A 相当于 C = C / A
%= 求模且赋值运算符,求两个操作数的模赋值给左边操作数 C %= A 相当于 C = C % A
<<= 左移且赋值运算符 C <<= 相当于 C = C << B
>>= 右移且赋值运算符 C >>= B相当于 C = C >> B
&= 按位与且赋值运算符 C &= B相当于 C = C & B
\^= 按位异或且赋值运算符 C \^= B相当于 C = C \^ B
|= 按位或且赋值运算符 C |= B相当于 C = C | B

其他运算符

下表列出了 C# 支持的其他一些重要的运算符,包括 sizeoftypeof 和 ?:

运算符 描述 示例
sizeof() 返回数据类型的大小 sizeof(int),返回4
typeof() 返回class的类型 typeof(Car)
& 返回变量的地址 \&A; 得到变量的实际地址
* 变量的指针 *A; 指向一个变量
?: 条件表达式 如果条件为真?则为A:否则为B
is 判断对象是否是某一类型 if (SUV is CAR)//检测实例化的SUV是否是CAR类的一个对象
as 强制转换,不会抛出异常 Object obj = new StringReader(“Good”); StringReader r = obj as StringReader;

运算符的优先级

运算符的优先级确定表达式中项的组合。这会影响到一个表达式如何计算。某些运算符比其他运算符有更高的优先级,例如,乘除运算符具有比加减运算符更高的优先级。

例如 x = 2 + 4 * 5,在这里,x 被赋值为 22,而不是 30,因为运算符 * 具有比 + 更高的优先级,所以首先计算乘法 4*5,然后再加上 2。

下表将按运算符优先级从高到低列出各个运算符,具有较高优先级的运算符出现在表格的上面,具有较低优先级的运算符出现在表格的下面。在表达式中,较高优先级的运算符会优先被计算。

类别 运算符 结合性
后缀 () [] -> . ++ – 从左到右
一元 + - ! ~ ++ – (type)* & sizeof 从右到左
乘除 * / % 从左到右
加减 + - 从左到右
移位 << >> 从左到右
关系 < <= > >= 从左到右
相等 == != 从左到右
位与AND & 从左到右
位异或XOR \^ 从左到右
位或OR | 从左到右
逻辑与AND && 从左到右
逻辑或OR || 从左到右
条件 ?: 从右到左
赋值 = += -= *= /= &= >>= <<= &= \^= |= 从右到左
逗号 , 从左到右

判断

if 语句

if(boolean_expression)
{
    /* 如果布尔表达式为真将执行的语句 */
}

if…else语句

if(boolean_expression)
{
   /* 如果布尔表达式为真将执行的语句 */
}
else
{
   /* 如果布尔表达式为假将执行的语句 */
}

if…else if…else 语句

一个 if 语句后可跟一个可选的 else if…else 语句,这可用于测试多种条件。

当使用 if…else if…else 语句时,以下几点需要注意:

if(boolean_expression 1)
{
    /* 当布尔表达式 1 为真时执行 */
}
else if( boolean_expression 2)
{
   /* 当布尔表达式 2 为真时执行 */
}
else if( boolean_expression 3)
{
   /* 当布尔表达式 3 为真时执行 */
}
else
{
   /* 当上面条件都不为真时执行 */
}

嵌套 if 语句

if( boolean_expression 1)
{
   /* 当布尔表达式 1 为真时执行 */
   if(boolean_expression 2)
   {
      /* 当布尔表达式 2 为真时执行 */
   }
}

switch 语句

switch (expression)
{
    case constant-expression :
        statement(s);
        break;
    case constant-expression :
        statement(s);
        break;
/* 您可以有任意数量的 case 语句 */
    default : /* 可选的 */
        statement(s);
        break;
}

switch 语句必须遵循下面的规则:

  1. switch 语句中的 expression 必须是一个整型或(enum)枚举类型,或者是一个 class 类型,其中class有一个单一的转换函数将其转换为整型或枚举类型。

  2. 在一个switch中可以有任意数量的 case 语句。每个 case 后跟一个要比较的值和一个冒号。

  3. case的 constant-expression 必须与switch中的变量具有相同的数据类型,且必须是一个常量。

  4. 当被测试的变量等于case中的常量时,case后跟的语句将被执行,直到遇到 break 语句为止。

  5. 当遇到 break 语句时,switch终止,控制流将跳转到 switch 语句后的下一行。

  6. 不是每一个 case 都需要包含 break。如果 case 语句为空,则可以不包含 break,控制流将会 继续 后续的 case,直到遇到 break 为止。

  7. C# 不允许从一个开关部分继续执行到下一个开关部分。如果 case 语句中有处理语句,则必须包含 break或其他跳转语句。

  8. 一个 switch 语句可以有一个可选的 default case,出现在 switch 的结尾。default case 可用于在上面所有 case 都不为真时执行一个任务。default case 中的 break 语句不是必需的。

  9. C# 不支持从一个 case 标签显式贯穿到另一个 case 标签。如果要使 C# 支持从一个 case 标签显式贯穿到另一个 case 标签,可以使用 goto 一个 switch-case 或 goto default。

嵌套 switch 语句

switch (ch1)
{
    case 'A':
        printf("这个 A 是外部 switch 的一部分" );
        switch(ch2)
        {
            case 'A':
                printf("这个 A 是内部 switch 的一部分" );
                break;
            case 'B': /* 内部 B case 代码 */
        }
        break;
    case 'B': /* 外部 B case 代码 */
}

循环

while 循环语句

while (condition)
{
    statement(s);
}

在这里,statement(s) 可以是一个单独的语句,也可以是几个语句组成的代码块。condition 可以是任意的表达式,当为任意非零值时都为真。当条件为真时执行循环。

当条件为假时,程序流将继续执行紧接着循环的下一条语句。

 for 循环语句

for ( init; condition; increment )
{
    statement(s);
}

下面是 for 循环的控制流:

  1. init 会首先被执行,且只会执行一次。这一步允许您声明并初始化任何循环控制变量。您也可以不在这里写任何语句,只要有一个分号出现即可。
  2. 接下来,会判断 condition。如果为真,则执行循环主体。如果为假,则不执行循环主体,且控制流会跳转到紧接着 for 循环的下一条语句。
  3. 在执行完 for 循环主体后,控制流会跳回上面的 increment 语句。该语句允许您更新循环控制变量。该语句可以留空,只要在条件后有一个分号出现即可。
  4. 条件再次被判断。如果为真,则执行循环,这个过程会不断重复(循环主体,然后增加步值,再然后重新判断条件)。在条件变为假时,for 循环终止。

foreach循环语句

C# 也支持 foreach 循环,使用foreach可以迭代数组或者一个集合对象。

以下实例有三个部分:

  1. 通过 foreach 循环输出整型数组中的元素。

  2. 通过 for 循环输出整型数组中的元素。

  3. foreach 循环设置数组元素的计算器。

foreach (var v in Array)
{
    //do something
}

do…while 循环语句

不像 for 和 while 循环,它们是在循环头部测试循环条件。do…while 循环是在循环的尾部检查它的条件。

do…while 循环与 while 循环类似,但是 do…while 循环会确保至少执行一次循环。

do
{
    statement(s);
}while ( condition );

请注意,条件表达式出现在循环的尾部,所以循环中的 statement(s) 会在条件被测试之前至少执行一次。

如果条件为真,控制流会跳转回上面的 do,然后重新执行循环中的 statement(s)。这个过程会不断重复,直到给定条件变为假为止。

C# 允许在一个循环内使用另一个循环,下面演示几个实例来说明这个概念。

嵌套 for 循环语句

for ( init; condition; increment )
{
    for ( init; condition; increment )
    {
        statement(s);
    }
    statement(s);
}

嵌套 while 循环语句

while (condition)
{
    while(condition)
    {
        statement(s);
    }
    statement(s);
}

嵌套 do…while 循环语句

do
{
    statement(s);
    do
    {
        statement(s);
    }while ( condition );
}while( condition );

关于嵌套循环有一点值得注意,您可以在任何类型的循环内嵌套其他任何类型的循环。比如,一个 for 循环可以嵌套在一个 while 循环内,反之亦然。

循环控制语句

break语句 终止for do…while whileswitch语句,程序执行下一条语句

continue语句 跳过循环内的剩余语句,重新条件判断,执行

无限循环语句

for (;;) 
{
    //do something  
}

do
{
    //do something
}while (true)

while(true)
{
    //do someting
}

面向对象

面向对象编程思想中的对象,是一种具有状态(属性)和行为(方法)的数据结构,用于描述现实世界中的某个事物,将现实世界的事物模型化。

类和对象

:对事物的一种抽象定义

对象:客观存在的具体事物

前面我们已经介绍过一个Car类,这里在简单描述下

声明类:

class ClassName //<类名>
{
    //Fields<变量>
    //Methods<方法>
    //Properties<属性>
}

创建对象:

类名 对象名 构造方法

ClassName obj = new ClassName(参数列表);

封装

隐藏内部实现,对外提供公有操作的方式,如:变量通过public访问修饰符的属性访问,对象就是封装了数据和操作这些数据的代码逻辑实体。

在一个对象内部,某些代码和数据不能被外界访问,对象通过这种方式对内部数据提供不同级别的保护,防止隐藏部分被错误操作。

继承

继承是面向对象程序设计中最重要的概念之一。继承允许我们根据一个类来定义另一个类,这使得创建和维护应用程序变得更容易。同时也有利于重用代码和节省开发时间。

当创建一个类时,程序员不需要完全重新编写新的数据成员和成员方法,只需要设计一个新的子类,继承了已有的类的成员即可。这个已有的类被称为的基类,这个新的子类被称为派生类

继承的思想实现了 属于(IS-A) 关系。例如, SUV属于(IS-A)汽车 , 奔驰SUV属于(IS-A) SUV,因此 奔驰SUV属于(IS-A) 汽车。

public class SUV : Car
{
    //Fields<变量>
    //Methods<方法>
    //Properties<属性>
}

public class BenzSUV : SUV
{
    //Fields<变量>
    //Methods<方法>
    //Properties<属性>
}

组合

组合是将各个部分组合在一起。程序设计中用已有的类对象来产生新的类。

例如:汽车主要由发动机、变速箱、悬架等部件组成。

新加两个类:

//汽车发动机
public class Engine
{
   
}

//汽车变速箱
public class GearBox
{

}

在Car类中,新加两个变量,同时初始化

//组合
private Engine engine = new Engine();//发动机

private GearBox gearbox = new GearBox();//变速箱

多态性

在面向对象编程语言中,多态性往往表现为”一个接口,多个功能”。

多态性可以是静态的或动态的。在静态多态性中,方法的响应是在编译时发生的。在动态多态性中,方法的响应是在运行时发生的。

静态多态性

在编译时,方法和对象的连接机制被称为早期绑定,也被称为静态绑定。C#提供了两种技术来实现静态多态性。分别为:

方法重载

Car类中两个构造方法,就是典型的方法重载

运算符重载

Car类中,新加

#region 运算符重载
//改装车
public static Car operator+ (Car a, Car b)
{
    Car car = new Car();
    car.manufacturer = a.manufacturer + b.manufacturer;
    car.vehicletype = a.vehicletype + b.vehicletype;
    car.color = a.color + b.color;
    return car;
}
#endregion 重载

可重载和不可重载运算符

下表描述了 C# 中运算符重载的能力:

运算符 描述
+ - ! ~ ++ – 这些一元运算符只有一个操作数,且可以被重载
+ - * / % 这些二元运算符带有两个操作数,且可以被重载
== != < > <= >= 这些比较运算符,可以重载
&& || 这些条件逻辑运算符,不能被直接重载
+= -= *= /= %= 这些赋值运算符,不能被重载
= . ?: -> new is sizeof typeof 这些运算符,不能被重载

例如:

动态多态性

C#允许您使用关键字 abstract 创建抽象类,用于提供接口的部分类的实现。当一个派生类继承自该抽象类时,实现即完成。抽象类包含抽象方法,抽象方法可被派生类实现。派生类具有更专业的功能。

下面是有关抽象类的一些规则:

  1. 您不能创建一个抽象类的实例。

  2. 您不能在一个抽象类外部声明一个抽象方法。

  3. 通过在类定义前面放置关键字 sealed,可以将类声明为密封类。当一个类被声明为 sealed 时,它不能被继承。抽象类不能被声明为 sealed。

抽象

//发动机抽象类
public abstract class BaseEngine
{
    //子类必须重写
    public abstract int Strat();//启动发动机
}

//汽车发动机
public class Engine : BaseEngine
{
     public override int Strat()
     {
         //throw new NotImplementedException();
     }
     
     //子类可重写,也可不重写
     public virtual void Setting()
     { 
         //花括号内可添加实现
     }
}

public class BenzEngine : Engine
{
     public override int Strat()
     {
         //Benz Engine Start
         return base.Strat();
     }

     public override void Setting()
     {
         //Benz Setting
         return base.Setting();
     }
}

public class AudiEngine : Engine
{
     public override int Strat()
     {
         //Audi Engine Start
         return base.Strat();
     }
}

当有一个定义在类中的函数需要在继承类中实现时,可以使用虚方法。虚方法是使用关键字 virtual 声明的。虚方法可以在不同的继承类中有不同的实现。对虚方法的调用是在运行时发生的。

动态多态性是通过 抽象类 和 虚方法 实现的。

接口(Interface)

  1. 接口定义了所有类继承接口时应遵循的语法规范。接口定义了语法规范 “是什么” 部分,派生类定义了语法规范“怎么做” 部分。

  2. 接口定义了属性、方法和事件,这些都是接口的成员。接口只包含了成员的声明。成员的定义是派生类的职责。接口提供了派生类应遵循的标准规范。

  3. 接口使得实现接口的类或结构在形式上保持一致。

抽象类在某种程度上与接口类似,但是,它们大多只是用在当只有少数方法由基类声明由派生类实现时。

定义接口:IGearBox

//汽车变速箱接口
public interface IGearBox
{
    //接口成员
    void Start();
    void Setting();
    int State();
}

//汽车变速箱
public class GearBox : IGearBox
{
    //接口实现
    public void Start()
    {
    
    }

    public void Setting()
    {
    
    }
    
    public int State()
    {
        return 0;
    }
}

模块化

模块化是一种处理复杂系统,分解为更好的可管理模块的方式。一个游戏中,我们会把功能模块化,常见:UI模块、资源模块、动画模块、声音模块、网络模块等

泛型

什么是泛型?

顾名思义有广泛类型的意思,广泛意味着具备通用性,可支持多种类型。所以泛型,我们可以这么初步理解。

在实际编程中,我们会遇到两个模块的功能非常相似,只是一个处理int数据类型,另一个处理float数据类型,或者其他自定义的数据类型,这种情况下,我们只能编写多个类或方法分别处理每个数据类型,因为方法的参数类型不同。有没有一种方法,在方法中传入通用的数据类型,这样代码不就可以合并了吗?泛型的出现就是专门解决这类问题。

泛型通过通用类型(T)占位符编写类或方法的规范。当编辑器遇到类的构造或方法调用时,会用实际类型替换通用类型T,即时编译器(JIT)生成本地代码。

泛型(Generic)的特性

使用泛型是一种增强程序功能的技术,具体表现在以下几个方面:

  1. 它有助于您最大限度地重用代码、保护类型的安全以及提高性能。

  2. 您可以创建泛型集合类。.NET 框架类库在 System.Collections.Generic 命名空间中包含了一些新的泛型集合类。您可以使用这些泛型集合类来替代 System.Collections 中的集合类。

  3. 您可以创建自己的泛型接口、泛型类、泛型方法、泛型事件和泛型委托。

  4. 您可以对泛型类进行约束以访问特定数据类型的方法。

  5. 关于泛型数据类型中使用的类型的信息可在运行时通过使用反射获取。

委托(Delegate)

C# 中的委托(Delegate)类似于 C 或 C++中函数的指针。委托(Delegate) 是存有对某个方法的引用的一种引用类型变量。引用可在运行时被改变。

委托(Delegate)适合用于实现事件和回调方法。所有的委托(Delegate)都派生自 System.Delegate 类。

声明委托(Delegate)

委托声明决定了可由该委托引用的方法。委托可指向一个与其具有相同标签的方法。

public delegate int MyDelegate (string s);

上面的委托可被用于引用任何一个带有一个单一的 string 参数的方法,并返回一个 int 类型变量。

声明委托的语法如下:

delegate \<返回类型\> \<委托名\> \<参数列表\>

例如:

using System;
namespace Example
{
    //声明委托类型
    delegate string printString(string s);
    class MainClass
    {
        static string str = string.Empty;
        public static string Who(string who)
        {
            str += who;
            return str;
        }
        
        public static string Say(string say)
        {
            str += " ";str += say;
            return str;
        }
        
        public static string GetWhat()
        {
            return str;
        }
        
        public static void Main (string[] args)
        {
            //创建委托实例
            printString ps1 = new printString (Who);
            printString ps2 = new printString (Say);
            //使用委托对象调用方法
            ps1 ("Jumbo");
            Console.WriteLine (GetWhat());
            ps2(" Hello ");
            Console.WriteLine (GetWhat ());
            //Console.WriteLine ("Hello World!");
        }
    }
}

委托的多播(Multicasting of a Delegate)

委托对象可使用 “+”运算符进行合并。一个合并委托调用它所合并的两个委托。只有相同类型的委托可被合并。”-“运算符可用于从合并的委托中移除组件委托。

使用委托的这个有用的特点,您可以创建一个委托被调用时要调用的方法的调用列表。这被称为委托的 多播(multicasting),也叫组播。下面的程序演示了委托的多播:

using System;
namespace Example
{
    //声明委托类型
    delegate string printString(string s);
    class MainClass
    {
        static string str = string.Empty;
        public static string Who(string who)
        {
            str += who;
            return str;
        }
        
        public static string Say(string say)
        {
            str += " ";
            str += say;
            return str;
        }
        
        public static string GetWhat()
        {
            return str;
        }
        
        public static void Main (string[] args)
        {
            //创建委托实例
            printString ps1 = new printString (Who);
            printString ps2 = new printString (Say);
            //使用委托对象调用方法
            //ps1 ("Jumbo");
            //Console.WriteLine (GetWhat());
            //ps2(" Hello ");
            //Console.WriteLine (GetWhat ());
            
            //委托多播
            printString ps;
            ps = ps1;
            ps += ps2;
            ps("Jumbo");
            Console.WriteLine (GetWhat ());
            //Console.WriteLine ("Hello World!");
        }
    }
}

委托(Delegate)的用途

委托 printString 可作为参数传递,如:

using System;
namespace Example
{
    //声明委托类型
    delegate string printString(string s);
    
    class MainClass
    {
        static string str = string.Empty;
        
        public static string Who(string who)
        {
            str += who;
            return str;
        }
        
        public static string Say(string say)
        {
            str += " ";
            str += say;
            return str;
        }
        
        public static string GetWhat()
        {
            return str;
        }
       
        //委托作为参数
        public static string SayWhat(printString ps)
        {
            ps ("I am a delegate param.");
            return str;
        }
        
        public static void Main (string[] args)
        {
            //创建委托实例
            printString ps1 = new printString (Who);
            printString ps2 = new printString (Say);
            
            //使用委托对象调用方法
            //ps1 ("Jumbo");
            //Console.WriteLine (GetWhat());
            //ps2(" Hello ");
            //Console.WriteLine (GetWhat ());
            
            //委托多播
            //printString ps;
            //ps = ps1;
            //ps += ps2;
            //ps("Jumbo");
            
            //委托作为参数类型
            SayWhat (ps1);
            Console.WriteLine (GetWhat ());
            SayWhat (ps2);
            Console.WriteLine (GetWhat ());
            //Console.WriteLine ("Hello World!");
        }
    }
}

事件

事件(Event) 基本上说是一个用户操作,如按键、点击、鼠标移动等等,或者是一些显示,如系统生成的通知。应用程序需要在事件发生时响应事件。例如,中断。事件是用于进程间通信。

通过事件使用委托

事件在类中声明且生成,且通过使用同一个类或其他类中的委托与事件处理程序关联。包含事件的类用于发布事件。这被称为 发布器(publisher) 类。其他接受该事件的类被称为 订阅器(subscriber) 类。事件使用 发布-订阅(publisher-subscriber) 模型。

发布器(publisher) 是一个包含事件和委托定义的对象。事件和委托之间的联系也定义在这个对象中。发布器(publisher)类的对象调用这个事件,并通知其他的对象。

订阅器(subscriber) 是一个接受事件并提供事件处理程序的对象。在发布器(publisher)类中的委托调用订阅器(subscriber)类中的方法(事件处理程序)。

声明事件(Event)

在类的内部声明事件,首先必须声明该事件的委托类型。例如:

public delegate void printString(string s);

然后,声明事件本身,使用 event 关键字:

//基于上面的委托定义事件
public event printString printStringEvent;
Fork me on GitHub