《C#高级编程》【第六章】数组 -- 学习笔记

       为了解决大量的同类型元素,于是数组就孕育而生了。数组是具有一定顺序关系的若干对象的集合体,一维数组可以看作是定长的线性表。反之,n为的数组可以看作线性表的推广。从存储结构上来看,数组是一段连续的存储空间。现在我们看看在C#中的数组:

1、普通数组

在C#中普通数组又可以分为一维数组、多维数组和锯齿数组。

<1>一维数组

我们现在先看看一维数组的声明语法:

类型[] 变量名;

知道怎么声明了,现在我们继续看看数组的初始化吧,在C#中有4种初始化的方式:

//n为数组长度,an为数组内部元素
类型[] 数组名 = new 类型[n];
//为数组分配内存,但是没有赋初值(vs会自动为其赋初值为0)
类型[] 数组名 = new 类型[n]{a1, a2, …, an}	//初始化,并赋初值
类型[] 数组名 = new 类型[]{a1, a2 ,…, an}
//还可以不指定数组长度,编译器会自动统计元素个数
类型[] 数组名 = {a1, a2,…, an}		//C风格的初始化并赋值

访问数组时,以”数组名[i]”的方式访问第 i-1个元素。如果不知道数组的长度,可以使用Length属性。

注意:如果数组中的元素类型是引用类型,就必须为每个元素分配内存。在C#中”类型[]”是一个不可分割的整体,”类型[]”可以看成是 数组类型。

<2>多维数组

看完一维数组,现在我们推广到多维数组,声明语法:

类型[,] 数组名;	//二维数组
类型[,,] 数组名;	//三维数组

相信你也发现了吧,方括号内的逗号数 + 1 就是数组的维数。我们以二维数组为例,来看看多维数组的初始化:

int[,] arr = new int[2,3]{{1,2,3},{4,5,6}};

借这个例子我想说明多维数组和一维数组初始化的区别就是,多维数组初始化时,每一维度都必须使用大括号括起来。其余的和一维数组初始化方法一样。

<3>锯齿数组

在使用多维数组的过程中,我们有时并不需要每一维度都一样,于是我们就引入了锯齿数组。(在C++中的Vector也有类似的功能)。上一幅图说明二维数组与锯齿数组的区别:

现在我们看看他的声明语法:

类型[][] 数组名 = new 类型[n][];	//n为锯齿数组的维度,后一个方括号为空

我们用一个具体实例来说看看他的使用方法:

int[][] Testarray = new int[2][];
Testarray[0] = new int[3]{1,2,3};	//当然也可以先不赋初值,建议都先赋初值
Testarray[1] = new int[4]{1,2,3,4};

这时候有些人可能会有疑问,每一维度的长度不同,那样怎么简单的遍历整个数组呢?这时Length属性就可以发挥它的优势了。我们以上述的为例:

for(int i = 0; i < Testarray.Length; i++){
	for(int j = 0; j < Testarray[i].Length; j++){
	//TODO:
	}
}

<4>数组作为参数

既然我们将数组看成一个类型,那么它自然也是可以作为参数传递给方法,也可以从方法中返回。C#数组还支持协变,但是数组协变只能用于引用类型,不能用于值类型。

<5>数组段ArraySegment<T>

ArraySegment<T>可以和数组之间建立一个映射,直接针对数组的某一片段进行操作,其操作后的结果会直接反映在数组上,反之数组上的变化也会反映到数组段上。我们来看看具体的使用吧:

ArraySegment<int> Test = new ArraySegment<int>(arr, 1, 4);

上述例子,表示Test,从arr[1]开始引用了4个元素。Test.Offset就表示第一个引用的元素,也就是arr[1]。

2、Array类

我们之前使用方括号声明数组,实际上就是隐式的使用了Array类。换一个角度看,我们使用的,例如:int[], double[] 我们都可以把他们看成是派生自Array的子类,这样我们可以使用Array为数组定义方法和属性。

<1>创建数组

Array是抽象类,所以不能实例化。但是可以使用静态方法CreateInstance()来创建数组。因为CreateInstance()有多个重载版本,我们就其中一个为例:

//创建一个int型,长度为5的数组,Test
Array Test = Array.CreateInstance(typeof(int), 5);
//我们将Test[3]的值,赋值为5
Test.SetValue(5, 3);
//我们要返回 Test[3]的值
Test.GetValue(3);
//将它变为int[]的数组
int[] T1 = (int[])Test;

<2>复制数组

我们可以使用Clone()方法来复制数组,但是如果数组是引用类型那么就只能复制对方的引用。如果数组是值类型,那么才能完整的将对方复制过来。我们还可以使用Copy()方法创建浅表副本。

注意:Clone()和Copy()最大的区别:Copy()方法必须使用与原数组相同阶数且有足够的元素空间,但是Cone()方法会创建一个和原数组等大的数组。

<3>排序

Array类还提供了QuickSort排序算法。使用Sort()方法可以对数组进行排序。但是使用Sort()方法需要实现IComparable接口(.Net已经为基本数据类型实现了IComparable接口,默认从小到大)。对于自定义类型,我们就必须实现IComparable<T>接口,这个接口只用一个方法CompareTo()。如果两者相等,就返回0。如果该实例在参数对象的前面,就返回小于0的值,反之就返回大于0的值。

我们也可以是通过实现IComparer<T>和IComparer接口。我们现在着重看看这个和IComparable接口的区别:

①IComparable在要比较对象的类中实现,可以比较该对象和另一个对象。

②IComparer要在单独一个类中实现,可以比较任意两个对象。

3、枚举

在foreach语句中使用枚举,可以迭代集合中的元素,而且不需要知道集合中的元素个数。foreach语句使用了一个枚举器,我们需要实现IEnumerable接口就可以使用foreach来迭代集合。(数组和集合已经默认实现了IEnumerable接口)。

<1>foreach原理 和 IEnumerator 接口

foreach使用了IEnumerator接口的方法和属性。

//per为Person类的对象
foreach(var p in per)
{
	Consle.WriteLine(p);
}

C#编译器会将这段代码解析为

IEnumerator<Person>  em = per.GetEnumerator();
while(em.MoveNext())
{
	Person p = em.Current;
	Console.WriteLine(p);
}

IEnumerator接口的MoveNext()方法作用是:移动到集合的下一个元素,如果有则返回true,否则为false。Current属性为当前的值。

<2>yield语句

由于创建枚举器的工作过于繁琐,于是我们就引入了yield语句,来帮助我们减轻工作量,yield return 返回集合的一个元素,yield break 可停止迭代。

下面我们可以通过一个简单的例子,来了解yield的用法:

public class TFoo
{
	public IEnumerator<string> GetEnumerator()
	{
		yield return “Hello”;
		yield return “World”;
	}
}

现在我们通过foreach迭代集合

int cnt = 0;	//我们用这个来看看集合在foreach中迭代了几次
var Test = new TFoo();
foreach(var s in Test)
{
	cnt++;
	Console.WriteLine(s);
}

最后我们可以得到cnt = 2且会输出Hello World。通过这个实例我们就可以大致的了解yield的工作方式。在之前学习泛型的时候我们在链表中已经使用过一次yield了。

注意:yield不能出现在匿名方法中

4、元组(Tuple)

数组是为了处理大量的同类型数据,那么我们要对不同类型的数据可以用什么类似的方法处理吗?当然,为此我们就引入了Tuple类。.Net中定义了8个泛型Tuple类,和一个静态的Tuple。例如:Tuple<T1>包含一个类型为T1的元素,Tuple<T1,T2>则包含两个类型为T1,T2的元素,依次类推。

如果元组元素超过8个那么第8个就可以使用Tuple类定义,例如:

Tuple<T1, T2, T3, T4, T5, T6, T7, TRest>	//TRest为另一个元组

我们通过这样的方法就可以创建带任意多个的元组了。

我们使用Create()方法创建元组,例如:

var Test = Tuple.Create<int,int>(2,5);

5、结构比较

数组和元组都实现接口IStructuralEquatable 和 IStructuralComparable。这两个接口不仅仅可以用来比较引用,还可以比较内容。因为这些接口是显示实现的,所以在使用时需要把数组和元组强制转化为这个接口。

IStructuralEquatable接口用于比较两个数组或元组是否具有相同的内容。

IStructuralComparable接口用于给数组或者元组排序。

我们用一个实例来简单的认识IStructuralEquatable接口的用法:

public class Test
{
    public int Id { get; set; }
    public override bool Equals(object obj)
    {
        if (obj == null)
            return base.Equals(obj);
        else
            return this.Id == (obj as Test).Id;
    }
}

我们现在再定义两个类内容相同的类对象t1,t2。

var t1 = new Test[2]{new Test{Id = 2}, new Test{Id = 3}};
var t2 = new Test[2]{new Test{Id = 2}, new Test{Id = 3}};

如果我们直接使用“==”或者“!=”比较那么编译器只会把我们的引用进行比较。这是我们就需要用到IStructuralEquatable接口了。

(t1 as IStructuralEquatable).Equals(t2, EqualityComparer<Test>.Default)

这样我们比较的就是t1,t2的内容了,因为是内容的比较所以它们将会返回True。EqualityComparer<Test>.Default调用的是Test默认的Equals()方法,所以我们只要重写它默认的Equals()方法,给重写的Equals()方法类内容比较的规则,那么我们就可以比较类对象间,是否具有相同的内容。

对于元组e1,e2,我们直接使用e1.Equals(e2)我们就可以比较元组间的内容,但是同样的如果使用比较运算符“==”和“!=”我们还是只能比较他们的引用。

(如有错误,欢迎指正,转载请注明出处)

时间: 2024-10-31 00:13:12

《C#高级编程》【第六章】数组 -- 学习笔记的相关文章

【读书笔记】C#高级编程 第六章 数组

(一)同一类型和不同类型的多个对象 如果需要使用同一类型的多个对象,就可以使用数组或集合(后面章讲). 如果需要使用不同类型的多个对象,可以使用Tuple(元组)类型. (二)简单数组 如果需要使用同一类型的多个对象,可以使用数组.数组是一种结构,它可以包含同一类型的多个元素. 1.数组的声明 在声明数组时,应先定义数组总元素的类型,其后是一堆空方括号和一个变量名. 例子: 以下代码声明了一个包含整形类型的数组 int[] intArray; 2.数组的初始化 声明了数组后,就必须为数组分配内存

C#高级编程 25.5 System.Transactions学习笔记

在.NET 1.x中,基本上是通过ADO.NET实现对不同数据库访问的事务..NET 2.0增加了System.Transactions名称空间,为.NET应用程序带来了一个新的事务变成模型. 所有的事务组件或者类型均定义在System.Transactions程序集中的System.Transactions命名空间下,我们直接称基于此的事务为System.Transactions事务. System.Transactions事务变成模型使我们可以显式(通过System.Transactions

语法》第六章 数组

(本文为阮一峰js标准教程的学习笔记,旨在总结该教程中涉及的知识点大纲及个人所做的一些拓展,方便作为"目录"或者"大纲"复习和查漏补缺,详细内容请参见阮一峰教程原文) 第二部分 语法 *********第六章 数组*********** 一.数组的定义1.概念:按次序排列的一组数,每个值都有编号(从0开始)整个数组用[]表示2.可以定义时赋值,也可定以后赋值.arr[0]='a';3.任何数据类型都可放入数组,[1,'1',[1,2],{},function(){}

第六章 shell学习之变量和引用

变量 本地变量:类似于局部变量,只在当前shell进程有效 环境变量:适用于所有登录进程所产生的子进程 位置参数:用于向shell脚本传递参数,只读 变量替换和赋值 引用变量值就称为变量替换,$就为变量替换符号,如a为变量名则$a或${a}为变量值 将值赋给某个变量名就称为变量赋值,格式:variable=value或${ variable=value },如值中包含空格则必须用"" 清除变量的值: unset 变量名 设置只读变量: variable=value readonly v

python核心编程--第六章 6.22 练习

6.22 练习 初学python,如果代码有问题,欢迎指正. #!/usr/bin/python # -*- coding: utf-8 -*- #6–1. 字符串.string 模块中是否有一种字符串方法或者函数 #可以帮我鉴定一下一个字符串是否是另一个大字符串的一部分? import string ss = "I'm chinese." substr = 'chinese' index = ss.find(substr) if index == -1: print("No

蓝鸥Unity开发基础—— 二维数组学习笔记

蓝鸥Unity开发基础-- 二维数组学习笔记 一.二维数组 有两个下标的数组叫做二维数组 类似[,]数组名=new类型[常量表达式1,常量表达式2] int[,] numbers= new int[2,3]; [0,0] [0,1] [0,2] [1,0] [1,1] [1,2] 举例说明 using System; namespace Lesson16{    class MainClass    {        public static void Main (string[] args)

Opencv学习笔记(六)SURF学习笔记

原创文章,转载请注明出处:http://blog.csdn.net/crzy_sparrow/article/details/7392345 本人挺菜的,肯定有非常多错误纰漏之处 ,希望大家不吝指正. 看了harris角点检測之后,開始研究SURF角点检測,发现挺复杂的,一时也仅仅了解了大概,把了解的东西总结下,以便下次深入学习. SURF角点检測算法是对SIFT的一种改进,主要体如今速度上,效率更高.它和SIFT的主要差别是图像多尺度空间的构建方法不同. 在计算视觉领域,尺度空间被象征性的表述

后缀数组学习笔记【详解|图】

后缀数组学习笔记[详解] 老天,一个后缀数组不知道看了多少天,最后终于还是看懂了啊! 最关键的就是一会儿下标表示排名,一会用数值表示排名绕死人了. 我不知道手跑了多少次才明白过来.其实我也建议初学者手跑几遍,但是一定要注意数组的意义,否则就是无用功. 数组含义: s[ ]:输入的字符串,预处理的时候会在末尾加上一个0 sa[ ]:它的下标就是后缀排名 x[ ] = t[ ]:用来保存第一关键字排名,注意!它的数值是排名.初始时恰好是字符串的ASCII码.字典序嘛! y[ ] = t2[ ]:它的

六、Android学习笔记_JNI_c调用java代码

1.编写native方法(java2c)和非native方法(c2java): package com.example.provider; public class CallbackJava { // C调用java空方法 public void helloFromJava() { System.out.println("hello from java"); } // C调用java中的带两个int参数的方法 public int Add(int x, int y) { int res

蓝鸥Unity开发基础—— 一维数组学习笔记

蓝鸥Unity开发基础-- 一维数组学习笔记 一.数组 之前我们学过很多数据类型,今天我们来学习数字,数字也是一种数据类型,那么,具体的数组是如何定义的? 数组:相同数据类型的成员组成的一组数据 Int类型数组:4  7  12  3  5--数组元素 Float数据类型数字:11.5 4.62 7.1 2.21 9.3--数组元素 数组中每一个元素都会分配一个数组下标,数组下标是从0开始的,有序排列,如:0 1 2 3 4 二.声明并初始化数组: 数组也是数据类型,所以也可以声明变量.使用ne