C#图解教程 第十二章 数组

数组

数组



数组实际上是由一个变量名称表示的一组同类型的数据元素。每个元素通过变量名称和一个或多个方括号中的索引来访问:

数组名  索引
  ↓     ↓
MyArray[4]
定义

让我们从C#中与数组有关的重要定义开始

  • 元素 数组的独立数据项称为元素。数组的所有元素必须是同类型的,或继承自相同的类型
  • 秩/维度 数组可以有任何为正数的维度数。数组的维度数称作秩(rank)
  • 维度长度 数组的每个维度都有一个长度,就是这个方向的位置个数
  • 数组长度 数组的所有维度中的元素的总和称为数组的长度
重要细节

关于C#数组的一些要点:

  • 数组一旦创建,大小就固定了。C#不支持动态数组
  • 数组索引号从0开始。如果维度长度是n,索引号范围就是0到n-1

数组的类型



C#提供两种类型的数组

  • 一维数组可以认为是单行元素或元素向量
  • 多维数组是由主向量中的位置组成的。每个位置本身又是一个数组,称为子数组(subarray)。子数组向量中的位置本身又是一个子数组

另外,有两种类型的多维度数组:矩形数组(rectangular array)和交错数组(jagged array),它们有如下特性:

  • 矩形数组

    • 某个维度的所有子数组具有相同长度的多维数组
    • 不管有多少个维度,总是使用一组方括号 int x=myArray2[4,6,1]
  • 交错数组
    • 每个子数组都是独立数组的多维度数组
    • 可以有不同长度的子数组
    • 为数组的每个维度使用一对方括号 jagArray1[2][7][4]

数组是对象



数组实例是从System.Array继承的对象。由于数组从BCL(Base Class Library)基类继承,它们也继承了很多有用的方法。

  • Rank 返回数组维度数
  • Length 返回数组长度(数组中所有元素的个数)

数组是引用类型,与所有引用类型一样,有数据的引用以及数据对象本身。引用在栈或堆上,而数组对象本身总在堆上。

 
尽管数组总是引用类型,但是数组元素可以是值类型也可以是引用类型。

  • 如果存储的元素都是值类型,数组被称作值类型数组
  • 如果存储的元素都是引用类型,数组被称作引用类型数组

一维数组和矩形数组



一维数组和矩形数组的语法非常相似,因此放在一起阐述。然后再单独介绍交错数组。

声明一维数组或矩形数组 
声明一维数组或矩形数组,在类型和变量名称间使用一对方括号。 
矩形数组声明示例:

  • 可以使用任意多的秩说明符
  • 不能在数组类型区域中放数组维度长度。秩是数组类型的一部分,而纬度长度不是类型的一部分
  • 数组声明后,维度数就固定了。然而,纬度长度直到数组实例化时才确定
 秩说明符
    ↓
int[,,] firstArray;     //三维整型数组
int[,]  arr1;           //二维整型数组
long[,] arr3;           //三维long数组
long[3,2,6] SecondArray;//编译错误
       ↑
声明时不允许设置维度长度

和C/C++不同,方括号在基类型后,而不是在变量名称后。

实例化一维数组或矩形数组



要实例化数组,我们可以使用数组创建表达式。数组创建表达式由new运算符构成,后面是基类名称和一对方括号。方括号中以逗号分隔每个维度的长度。

例:

int[] arr2=new int[4];//包含4个int的一维数组
MyClass[] maArr=new MyClass[4];//包含4个MyClass引用的一维数组
int[,,] arr3=new int[3,6,2];//三维数组,数组长度是3*6*2=36

与对象创建表达式不一样,数组创建表达式不包含圆括号-即使是对于引用类型数组。

访问数组元素



在数组中使用整型值作为索引来访问数组元素。

  • 每个维度的索引从0开始
  • 方括号内的索引在数组名称之后
int[] intArr1=new int[15];
intArr1[2]=10;
int var1=intArr1[2];
int[,] intArr2=new int[5,10];
intArr2[2,3]=7;
int var2=intArr2[2,3];
int[] myIntArray;
myIntArray=new int[4];
for(int i=0;i<4;i++)
{
    myIntArray[i]-i*10;
}
for(int i=0;i<4;i++)
{
    Console.WriteLine("Value of element {0} = {1}",i,myIntArray[i]);
}

初始化数组



数组被创建后,每个元素被自动初始化为类型的默认值。对于预定义类型,整型默认值是0,浮点型默认值是0.0,布尔型默认值是false,而引用类型默认值则是null。 
例:一维数组的自动初始化

int[] intArr=new int[4];

显式初始化一维数组
  • 初始值必须以逗号分隔,并封闭在一组大括号内
  • 不必输入数组长度,因为编译器可以通过初始化值的个数来推断长度
  • 注意,在数组创建表达式和初始化列表中间没有分隔符。即,没有等号或其他连接运算符
int[] intArr=new int[]{10,20,30,40};

显式初始化矩形数组

要显式初始化矩形数组,需遵守以下规则:

  • 每个初始值向量必须封闭在大括号内
  • 每个维度也必须嵌套并封闭在大括号内
  • 除了初始值,每个维度的初始化列表和组成部分也必须使用逗号分隔
int[,] intArray2=new int[,]{{10,1},{2,10},{11,9}};

快捷语法

在一条语句中使用声明、数组创建和初始化时,可以省略语法的数组创建表达式部分。快捷语法如下:

int[] arr1=new int[3]{10,20,30};
int[] arr1=          {10,20,30};
int[,] arr=new int[2,3]{{0,1,2},{10,11,12}};
int[,] arr=            {{0,1,2},{10,11,12}};
隐式类型数组

上面声明的数组都是显式指定数组类型。然而,和其他局部变量一样,数组可以是隐式类型的。

  • 当初始化数组时,我们可以让编译器根据初始化语句的类型来推断数组类型。只要所有初始化语句能隐式转换为单个类型,就可以这么做
  • 和隐式类型的局部变量一样,使用var关键字

虽然用var替代了显式数组类型,但是仍然需要在初始化中提供秩说明符。

int[] intArr1=new int[]{10,20,30,40};
var   intArr2=new    []{10,20,30,40};
int[,] intArr3=new int[,]{{10,1},{2,10},{11,9}};
var    intArr4=new    [,]{{10,1},{2,10},{11,9}};
string[] sArr1=new string[]{"life","liberty","pursuit of happiness"};
var      sArr2=new       []{"life","liberty","pursuit of happiness"};
综合内容

例:综合示例

var arr=new int[,]{{0,1,2},{10,11,12}};
for(int i=0;i<2;i++)
{
    for(int j=0;j<3;j++)
    {
        Console.WriteLine("Element [{0},{1}] is {2}",i,j,arr[i,j]);
    }
}

交错数组



交错数组是数组的数组。与矩形数组不同,交错数组的子数组的元素个数可以不同。

例:二维交错数组

  • 第一个维度长度是3
  • 声明可以读作“jagArr是3个int数组的数组”
  • 图中有4个数组对象-其中一个针对顶层数组,另外3个针对子数组
int[][] jagArr=new int[3][];

声明交错数组

交错数组的声明语法要求每个维度都有一对独立的方括号。数组变量声明中的方括号数决定了数组的秩。 
和矩形数组一样,维度长度不能包括在数组类型的声明部分。

int[][] SomeArr;//秩等于2
int [][][] OhterArr;//秩等于3
快捷实例化

我们可以将数组创建表达式创建的顶层数组和交错数组的声明相结合。

int[][] jagArr=new int[3][];//3个子数组

不能在声明语句中初始化顶层数组之外的数组。

int[][] jagArr=new int[3][4];//不允许,编译错误
实例化交错数组

和其他类型数组不一样,交错数组的完全初始化不能在一个步骤中完成。由于交错数组是独立数组的数组-每个数组必须独立创建。

  1. 首先,实例化顶层数组
  2. 其次,分别实例化每个子数组,把新建数组的引用赋给它们所属数组的合适元素

例:实例化交错数组

int[][] Arr=new int[3][];
Arr[0]=new int[]{10,20,30};
Arr[1]=new int[]{40,50,60,70};
Arr[3]=new int[]{80,90,100,110,120};

比较矩形数组和交错数组



矩形数组和交错数组的结构区别非常大。 
例如,下图演示了3X3的矩形数组以及由3个长度为3的一维数组构成的交错数组的结构。

  • 两个数组都保存9个整数,但是它们结构很不相同
  • 矩形数组只有1个数组对象,而交错数组有4个数组对象

 
在CIL(Common Intermediate Laguage,公共中间语言)中,一维数组有特定的指令用于性能优化。矩形数组没有这些指令,并且不在相同级别进行优化。因此,有时使用一维数组(可以被优化)的交错数组比矩形数组(不能被优化)更有效率。 
另一方面,矩形数组的编程复杂度要小得多,因为它会被作为一个单元而不是数组的数组。

foreach语句



foreach语句允许我们连续访问数组中的每个元素。 
有关foreach语句的重点如下:

  • 迭代变量是临时的,并且和数组中元素的类型相同。foreach语句使用迭代变量来相继表示数组中的每个元素
  • foreach语句的语法如下
    • Type是数组中元素的类型。我们可以显式提供它的类型,或者使用var
    • Identifier是迭代变量名
    • ArrayName是数组名
    • Statement是要为数组中每个元素执行一次的单条语句或语句块
foreach(Type Identifier in ArrayName)//显式
{
    Statement
}
foreach(var Identifier in ArrayName)//隐式
{
    Statement
}

foreach如下方式工作:

  • 从数组第一个元素开始并把它赋值给迭代变量
  • 执行语句主体。在主体中,我们可以把迭代变量作为数组元素的只读别名
  • 主体执行后,foreach语句选择数组中的下一个元素并重复处理

例:foreach示例

int[] arr1={10,11,12,13};
foreach(int item in arr1)
{
    Console.WriteLine("Item Value: {0}",item);
}

迭代变量是只读的

迭代变量是只读的。但是,对于值类型数组和引用类型数组效果不一样。 
对于值类型数组,这意味着在用迭代变量时,我们不能改变它们。

例:尝试修改迭代变量报错

int[] arr1={10,11,12};
foreach(int item in arr1)
{
    item++;
}

对于引用变量,迭代变量保存的是数据的引用,而不是数据本身。所以,我们虽然不能改变引用,但是我们可以改变数据。

例:引用变量修改迭代变量

class MyClass
{
    public int MyField=0;
}
class Program
{
    static void Main()
    {
        MyClass[] mcArray=new MyClass[4];
        for(int i=0;i<4;i++)
        {
            mcArray[i]=new MyClass();
            mcArray[i].MyField=i;
        }
        foreach(var item in mcArray)
        {
            item.MyField+=10;
        }
        foreach(var item in mcArray)
        {
            Console.WriteLine("{0}",item.MyField);
        }
    }
}

foreach语句和多维数组

多维数组里,元素处理顺序是从最右边的索引号依次递增。

例:矩形数组示例

class Program
{
    static void Main()
    {
        int total=0;
        int[,] arr1={{10,11},{12,13}};
        foreach(var element in arr1)
        {
            total+=element;
            Console.WriteLine("Element:{0},Current Total:{1}",element,total);
        }
    }
}

 
例:交错数组示例

class Program
{
    static void Main()
    {
        int total=0;
        int[][] arr1=new int[2][];
        arr1[0]=new int[]{10,11};
        arr1[1]=new int[]{12,13,14};
        foreach(int[] array in arr1)
        {
            Console.WriteLine("Starting new array");
            foreach(int item in array)
            {
                total+=element;
                Console.WriteLine("Item:{0},Current Total:{1}",item,total);
            }
        }
    }
}

数组协变



某些情况下,即使某对象不是数组的基类型,我们也可以把它赋给数组元素。这种属性叫做数组协变(array covariance)。在下面情况下使用数组协变。

  • 数组是引用类型数组
  • 在赋值的对象类型和数组基类之间有隐式转换或显式转换

由于派生类和基类之间总有隐式转换,因此总是可以把派生类对象赋值给基类声明的数组。

例:派生类、基类 数组协变

class A{...}
class B:A{...}
class Program
{
    static void Main()
    {
        A[] AArray1=new A[3];
        A[] AArray2=new A[3];
        AArray1[0]=new A();AArray1[1]=new A();AArray1[2]=new A();
        AArray2[0]=new B();AArray2[1]=new B();AArray2[2]=new B();
    }
}

值类型数组没有协变

数组继承的有用成员



C#数组从System.Array类继承。它们从基类继承了很多有用的属性和方法。

public static void PrintArray(int[] a)
{
    foreach(var x in a)
    {
        Console.WriteLine("{0}",x);
    }
    Console.WriteLine("");
}
static void Main()
{
    int[] arr=new int[]{15,20,5,25,10};
    PrintArray(arr);
    Array.Sort(arr);
    PrintArray(arr);
    Array.Reverse(arr);
    PrintArray(arr);
    Console.WriteLine();
    Console.WriteLine("Rank = {0},Length = {1}",arr.Rank,arr.Length);
    Console.WriteLine("GetLength(0)      = {0}",arr.GetLength(0));
    Console.WriteLine("GetType()         = {0}",arr.GetType());
}

Clone方法

Clone方法为数组进行浅复制。即,它只创建了数组本身的克隆。如果是引用类型数组,它不会复制元素引用的对象。对于值类型数组和引用类型数组,效果不同。

  • 克隆值类型数组会产生两个独立数组
  • 克隆引用类型数组会产生指向相同对象的两个数组

Clone方法返回object类型的引用,它必须被强制转换成数组类型。

int[] intArr1={1,2,3};
int[] intArr2=(int[])intArr1.Clone();

例:Clone值类型数组示例

class Program
{
    static void Main()
    {
        int[] intArr1={1,2,3};
        int[] intArr2=(int[])intArr1.Clone();
        intArr2[0]=100;
        intArr2[1]=200;
        intArr2[2]=300;
    }
}

 
例:Clone引用类型数组示例

class A
{
    public int Value=5;
}
class Program
{
    static void Main()
    {
        A[] AArray1=new A[3]{new A(),new A(),new A()};
        A[] AArray2=(A[])AArray1.Clone();
        AArray2[0].Value=100;
        AArray2[1].Value=200;
        AArray2[2].Value=300;
    }
}

比较数组类型



下表总结了3中类型数组的重要相似点和不同点。

from: http://www.cnblogs.com/moonache/p/6255327.html

原文地址:https://www.cnblogs.com/GarfieldEr007/p/10126580.html

时间: 2024-10-05 04:08:37

C#图解教程 第十二章 数组的相关文章

Flask 教程 第十二章:日期和时间

本文翻译自The Flask Mega-Tutorial Part XII: Dates and Times 这是Flask Mega-Tutorial系列的第十二部分,我将告诉你如何以适配所有用户的方式处理日期和时间,无论他们身处地球上的何处. 显示日期和时间是Microblog应用中长期被忽略的其中一个方面. 直到现在,我也只是让Python渲染了User模型中的datetime对象,并且完全忽略了Post模型中的datetime对象. 本章的GitHub链接为:Browse, Zip, D

第十二章----数组

import java.util.Arrays; public class Test { public static void main(String[] args) { //数组的定义 //int[] a = new int[5]; //int a1[] = new int[3]; //int[] a2 = {2, 4, 6}; int[] a = new int[4]; boolean[] b = new boolean[4]; char[] c = new char[4]; System.

C#图解教程 第二十四章 反射和特性

反射和特性元数据和反射Type 类获取Type对象什么是特性应用特性预定义的保留的特性Obsolete(废弃)特性Conditional特性调用者信息特性DebuggerStepThrough 特性其他预定义特性有关应用特性的更多内容多个特性其他类型的目标全局特性自定义特性声明自定义特性使用特性的构造函数指定构造函数使用构造函数构造函数中的位置参数和命名参数限制特性的使用自定义特性的最佳实践访问特性使用IsDefined方法使用GetCustomAttributes方法 Note 类的元数据包含

C#图解教程 第十九章 LINQ

LINQ 什么是LINQLINQ提供程序 匿名类型 方法语法和查询语法查询变量查询表达式的结构 from子句join子句什么是联结查询主体中的from-let-where片段 from子句let子句where子句 orderby子句select-group子句查询中的匿名类型group子句查询延续:into子句 标准查询运算符 标准查询运算符的签名查询表达式和标准查询运算符将委托作为参数LINQ预定义的委托类型使用委托参数的示例使用Lamba表达式参数的示例 LINQ to XML 标记语言XM

C#图解教程 第二十五章 其他主题

其他主题 概述字符串使用 StringBuilder类把字符串解析为数据值关于可空类型的更多内容 为可空类型赋值使用空接合运算符使用可空用户自定义类型 Main 方法文档注释 插入文档注释使用其他XML标签 嵌套类型 嵌套类的示例可见性和嵌套类型 析构函数和dispose模式 标准dispose模式比较构造函数和析构函数 和COM的互操作 其他主题 概述 在本章中,我会介绍使用C#时的一些重要而又不适合放到其他章节的主题,包括字符串操作.可空类型.Main方法.文档注释以及嵌套类型. 字符串 对

C#图解教程 第十五章 接口

接口 什么是接口 使用IComparable接口的示例 声明接口实现接口 简单接口示例 接口是引用类型接口和as运算符实现多个接口实现具有重复成员的接口多个接口的引用派生成员作为实现显式接口成员实现 访问显式接口成员实现 接口可以继承接口不同类实现一个接口的示例 接口 什么是接口 接口是指定一组函数成员而不实现它们的引用类型.所以只能类和结构来实现接口. 这种描述比较抽象,直接来看个示例. 下例中,Main方法创建并初始化了一个CA类的对象,并将该对象传递给PrintInfo方法. class

c++第十二章 -(动态管理内存、动态数组和函数返回动态内存)

1.静态内存,编译时申请,存储在栈,如基本数据类型. 2.动态内存由一些没有名字,只有地址的内存块构成.那些内存块是在程序运行期间动态分配的.它们来自一个标准c++库替你管理的“大池子”(内存池),从内存池申请一些内存需要用new语句,他将根据你提供的数据类型分配一块大小适当的内存.你不必担心内存块的尺寸问题. 3.注意在用完内存块之后,应该用delete语句把它返还内存池.另外作为一种附加的保险措施,在释放内存之后还要把关联的指针置NULL,对空指针进行“解引用”会报错. class Comp

D3.js的v5版本入门教程(第十二章)—— D3.js中各种精美的图形

D3.js的v5版本入门教程(第十二章) D3中提供了各种制作常见图形的函数,在d3的v3版本中叫布局,通过d3.layout.xxx,来新建,但是到了v5,新建一个d3中基本的图形的方式变了(我也并不知道是不是还叫布局,我觉得也可以这么叫,反正布局指的也是一个绘图函数) 下面是d3中一些常见的部分图形 bubble —— 泡泡图 packing —— 打包图 bundling —— 捆图 force —— 力导向图 chord —— 弦图 pie——饼状图 tree——树状图 中国地图 我们利

C primer plus 第五版十二章习题

看完C prime plus(第五版)第十二章,随带完成了后面的习题. 1.不使用全局变量,重写程序清单12.4的程序. 先贴出12.4的程序,方便对照: 1 /* global.c --- 使用外部变量 */ 2 #include <stdio.h> 3 int units = 0; //一个外部变量 4 void critic(void); 5 int main(void) 6 { 7 extern int units; 8 9 printf ("How many pounds