发新话题
打印

12 D语言 数组 Arrays

12 D语言 数组 Arrays

知识若不分享 实在没有意义 http://www.d-programming-language-china.org 20070422

点击下面网址查看原文:
http://www.d-programming-language-china.org

by: uFramer D语言论坛 http://www.d-programming-language-china.org
from: http://www.digitalmars.com/d/arrays.html
version: 基于D 1.013 (Apr 19, 2007)

有四种数组:

int* p; 指向数据的指针
int[3] s 静态数组
int[] a 动态数组
int[char[]] x; 关联数组

1 Pointers指针

int* p;

这是最简单的指向数据的指针,等价于 C 指针。提供指针的目的是提供与 C 的接口并给 D 完成特定的系统级工作的能力。没有与之相关的数组长度,所以就没有办法在编译或运行时进行越界检查之类的工作。大多数传统的指针用法可以使用动态数组、out 和 ref 参数及引用型别代替。

2 Static Arrays静态数组

int[3] s;

这就是C语言的数组。静态数组的长度是在编译时就确定的。 静态数组的大小不能超过16MB。大的数组应该使用动态数组。

A static array with a dimension of 0 is allowed, but no space is allocated for it. It's useful as the last member of a variable length struct, or as the degenerate case of a template expansion.

3 Dynamic Arrays动态数组

int[] a;

动态数组由数组长度和指向数据的指针组成。多个动态数组可能共享数组中的全部或者部分数据。( 本文出处: http://www.d-programming-language-china.org )

Array Declarations数组声明

有两种声明数组的方式:前缀法和后缀法。前缀法通常更好一些,尤其是用于声明非平凡型别的数组时。

1 Prefix Array Declarations前缀数组声明

前缀声明出现在被声明的标志符之前,应该从右向左读,所以:

int[] a;        // int 的动态数组
int[4][3] b;    // 含有 3 个元素的数组,每个元素是含 4 个 int 的数组
int[][5] c;    // 含有 5 个元素的数组,每个元素是 int 的动态数组
int*[]*[3] d;    // 含有 3 个元素的数组,每个元素是指向含指向 int 的动态数组的指针
int[]* e;        // 所指元素为动态数组的指针

2 Postfix Array Declarations后缀数组声明

后缀声明出现在被声明的标志符之后,并且从左向右读。下面的每一组内的声明都是等价的:

// int 的动态数组
int[] a;
int a[];

// 含有 3 个元素的数组,每个元素是含 4 个 int 的数组
int[4][3] b;
int[4] b[3];
int b[3][4];

// 含有 5 个元素的数组,每个元素是 int 的动态数组
int[][5] c;
int[] c[5];
int c[5][];

// 含有 3 个元素的数组,每个元素是指向含指向 int 的动态数组的指针
int*[]*[3] d;
int*[]* d[3];
int* (*d[3])[];

// 所指元素为动态数组的指针
int[]* e;
int (*e[]);

原理:后缀形式同 C 和 C++ 采用的形式完全一样,提供这种支持会给用惯了这种形式的程序员提供一条容易的迁移路径。

Usage用法

对数组的操作可以分为两大类——对数组引用的操作和对数组内容的操作。C中只是第一种操作,在 D 中,这两种操作都有。( 本文出处: http://www.d-programming-language-china.org )

int* p;
int[3] s;
int[] a;

int* q;
int[3] t;
int[] b;

p = q;        // p 和 q 指向同一个东西
p = s;        // p 指向数组 s 的第一个元素
p = a;        // p 指向数组 a 的第一个元素

s = ...;        // 错误,因为 s 是一个编译时静态数组引用

a = p;        // 错误,因为 p 不带有数组的长度
a = s;        // 被初始化为指向数组 s
a = b;        // a 和 b 指向同一个数组

Slicing切片

切割 一个数组意味着得到它的一个子数组。数组切片并不复制数组的数据,它只是返回另一个引用。例如:

int[10] a;    // 声明一个含有 10 个 int 数组
int[] b;

b = a[1..3];    // a[1..3] 是含有 2 个元素的数组,它的内容是 a[1] 和 a[2]
foo(b[1]);    // 等价于 foo(0)
a[2] = 3;
foo(b[1]);    // 等价于 foo(3)

简写方式 [] 代表整个数组。例如,下面这些对 b 的赋值:

int[10] a;
int[] b;

b = a;
b = a[];
b = a[0 .. a.length];

在语义上都是等价的。
切割不只是在引用数组中的某个部分时很方便,还可以将指针转换为带有越界检查的数组:

int* p;
int[] b = p[0..8];

Array Copying数组复制

当切割运算符作为赋值表达式左值出现时,意味着赋值的目标是数组的内容而不是对数组的引用。当左值是一个切片,右值是一个数组或者对应类型的指针时,会发生复制。

int[3] s;
int[3] t;

s[] = t;            // t[3] 的 3 个元素被复制到 s[3] 中
s[] = t[];        // t[3] 的 3 个元素被复制到 s[3] 中
s[1..2] = t[0..1];        // 等价于 s[1] = t[0]
s[0..2] = t[1..3];        // 等价于 s[0] = t[1], s[1] = t[2]
s[0..4] = t[0..4];        // 错误,s 中只有 3 个元素
s[0..2] = t;        // 错误,左值和右值的长度不同

重叠的复制会被视为错误:( 本文出处: http://www.d-programming-language-china.org )

s[0..2] = s[1..3];        // 错误,重叠的复制
s[1..3] = s[0..2];        // 错误,重叠的复制

相对于 C 的串行语义来说,禁止重叠的复制使编译器可以进行更为激进的并行代码优化。

Array Setting数组赋值

如果一个切割运算符作为赋值表达式的左值出现,并且右值的类型同数组元素的类型相同,那么作为左值的数组的内容将被设置为右值的值。

int[3] s;
int* p;

s[] = 3;        // 等价于 s[0] = 3, s[1] = 3, s[2] = 3
p[0..2] = 3;    // 等价于 p[0] = 3, p[1] = 3

Array Concatenation数组连接

二元运算符 ~ 是连接运算符。它用来连接数组:

int[] a;
int[] b;
int[] c;

a = b ~ c;    // 将两个数组连接构成一个新的数组

许多语言重载了 + 运算符完成连接操作。这会产生令人不解的结果:

"10" + 3

它的值等于 13 还是字符串 "103" ?这并不是一目了然的,语言设计者不得不抖擞精神,小心地编写规则来避免模棱两可的情况——而这些规则难以正确实现、容易被忽视、忘记。如果使用 + 代表加法,使用另一个运算符代表数组连接会好得多。
类似地,~= 运算符代表追加, 如:( 本文出处: http://www.d-programming-language-china.org )

a ~= b;        // a 变为 a 和 b 的连接的结果

连接操作总是创建操作数的一份拷贝,即使一个操作数时长度为 0 的数组时也是如此:

a = b            // a 指向 b
a = b ~ c[0..0]        // a 指向 b 的一个拷贝

Pointer Arithmetic指针数学运算

int[3] abc;        // 包含 3 个 int 的静态数组
int[] def = [ 1, 2, 3 ];    // 包含 3 个 int 的动态数组

void dibb(int *array)
{
    array[2];        // 等价于 *(array + 2)
    *(array + 2);    // 得到下标为 2 的元素
}

void diss(int[] array)
{
    array[2];        // ok
    *(array + 2);    // 错误,数组不是指针
}

void ditt(int[3] array)
{
    array[2];        // ok
    *(array + 2);    // 错误,数组不是指针
}

Rectangular Arrays矩形数组

有经验的 FORTRAN 数值计算程序员都知道多维“矩形”数组对于类似矩阵运算的东西来说大大快于通过指向指针的指针的访问方式(就像“存储数组指针的数组”那样的语义)。例如,D 的语法:

double[][] matrix;

声明了 matrix 为元素为指向数组指针的数组(动态数组被实现为指向数组数据的指针)。因为数组可以有不同的大小(可以动态设置大小),所以有时它被称作“交替数组”。对于代码优化来说更为糟糕的是,数组的行有时会互相指向!幸运的是,D 的静态数组,使用相同的语法,被实现为固定的矩形分布:

double[3][3] matrix;

它声明了一个 3 行 3 列的矩形矩阵,在内存中连续分布。在其他语言中,它被称为多维数组并且声明类似于:

double matrix[3,3];

Array Length数组长度

在静态或动态数组的 [ ] 中,变量 length 被隐式的声明并被设为数组的长度。( 本文出处: http://www.d-programming-language-china.org )

int[4] foo;
int[]    bar = foo;
int*    p = &foo[0];

// 下面这些表达式是等价的:
bar[]
bar[0 .. 4]
bar[0 .. length]
bar[0 .. bar.length]

p[0 .. length]    // 'length' 未定义,因为 p 不是数组
bar[0]+length    // 'length' 未定义,因为它不在 [ ] 内

bar[length-1]    // 得到数组内的最后一个元素

Array Properties数组属性

静态数组的属性有:

.sizeof 返回以字节为单位的数组的大小。
.length 返回数组中元素的个数。对于静态数组来说这是一个定值。
.ptr 返回指向数组第一个元素的指针。(d-programming-language-china.org注:pointer to first?)
.dup 创建一个同样大小的动态数组并将原数组的内容复制到新数组中。
.reverse 将数组中的元素按原来的逆序排列。返回数组。
.sort 将数组中的元素按照适当的方法排序。返回数组。

动态数组的属性有:
.sizeof 返回动态数组引用的大小,在 32 位平台上是 8 。
.length Get/set 数组中元素的个数。
.ptr 返回指向数组第一个元素的指针。
.dup 创建一个同样大小的动态数组并将原数组的内容复制到新数组中。
.reverse 将数组中的元素按原来的逆序排列。返回数组。
.sort 将数组中的元素按照适当的方法排序。返回数组。

For the .sort property to work on arrays of class objects, the class definition must define the function: int opCmp(Object). This is used to determine the ordering of the class objects. Note that the parameter is of type Object, not the type of the class.

For the .sort property to work on arrays of structs or unions, the struct or union definition must define the function: int opCmp(S) or int opCmp(S*). The type S is the type of the struct or union. This function will determine the sort ordering.( 本文出处: http://www.d-programming-language-china.org )

示例:

p.length        // 错误,对于指针来说没有数组大小的信息
s.length        // 编译时常量 3
a.length        // 运行时值

p.dup        // 错误,长度未知,无法复制
s.dup        // 创建一个含有 3 个元素的数组,将元素复制到它内
a.dup        // 创建一个含有 a.length 个元素的数组,将元素复制到它内

1 Setting Dynamic Array Length设置动态数组的长度

动态数组的 .length 属性可以作为 = 运算符的左值:

array.length = 7;

这会造成数组被在适当的位置被重新分配,现有的内容被原封不动的复制到新的数组中。如果新的数组比原数组短,将只保留新数组能够容纳的那些内容。如果新数组比原数组长,新数组长出的部分将会使用元素的默认初始值填充。
为了达到最高的效率,运行时总是试图适当地调整数组的大小(resize)以避免额外的复制工作。如果数组不是由 new 运算符或者上一个 resize 操作分配的,并且新数组比原数组大,那么运行时将总是进行复制。

这意味着如果有切片依赖于要被重设长度的数组的话,新的数组可能会同那个切片重叠,也就是:

char[] a = new char[20];
char[] b = a[0..10];
char[] c = a[10..20];

b.length = 15;    // 总是会被适当的重置大小,因为它是位于 a[] 上的一个切片,
        // 而 a[] 的长度大于 15
b[11] = 'x';    // a[1] 和 c[1] 也受到影响

a.length = 1;
a.length = 20;    // 内存分布没有改变
c.length = 12;    // 总是会进行复制,因为 c[] 不在 gc 分配的块的开始处
c[5] = 'y';    // 不影响 a[] 和 b[] 的内容

a.length = 25;    // 可能会也可能不会进行复制
a[3] = 'z';    // 可能会也可能不会影响到 b[3] ,b[3] 仍然同原来的 a[3] 重叠

如果要确保总是进行复制,使用 .dup 属性,这样会得到一个可以进行 resize 的独立数组。
这些讨论也同样适用于使用 ~ 和 ~= 运算符进行的连接操作。( 本文出处: http://www.d-programming-language-china.org )

重置动态数组的大小是一个相对昂贵的操作。所以,尽管下面这个用于填充数组的方法结果正确:

int[] array;
while (1)
{    c = getinput();
    if (!c)
        break;
    array.length = array.length + 1;
    array[array.length - 1] = c;
}

它的效率也不会高。一个更为实际的方法是尽量减少对 resize 操作的使用:

int[] array;
array.length = 100;    // 猜测
for (i = 0; 1; i++)
{    c = getinput();
     if (!c)
    break;
     if (i == array.length)
    array.length = array.length * 2;
     array = c;
}
array.length = i;

选择一个好的猜测值是一门艺术,但是通常你能找到一个适用于 99% 的情况的值。例如,当从控制台采集用户输入时——长度不太可能超过 80 。

2 Functions as Array Properties

If the first parameter to a function is an array, the function can be called as if it were a property of the array:

int[] array;
void foo(int[] a, int x);

foo(array, 3);
array.foo(3);    // means the same thing

Array Bounds Checking数组越界检查

如果使用小于 0 或大于等于数组长度的数作为数组下标是一个错误。若下标越界,如果是由运行时检测到,会抛出一个 ArrayBoundsError 异常;如果是在编译时检测到,会产生一个错误。程序不应该依赖于数组越界异常检查,例如,下面这个程序的方法是错误的:( 本文出处: http://www.d-programming-language-china.org )

try
{
    for (i = 0; ; i++)
    {
    array = 5;
    }
}
catch (ArrayBoundsError)
{
    // 终止循环
}

正确的写法是:

for (i = 0; i < array.length; i++)
{
    array = 5;
}

实现注记:编译器应该在编译时尝试检查数组越界错误,例如:

int[3] foo;
int x = foo[3];        // 错误,越界,仅有元素0,1,2

应该可以在编译时通过一个选项决定是否插入动态数组越界检查代码。

Array Initialization数组初始化

1 Default Initialization默认初始化

指针被初始化为 null 。
静态数组的内容被初始化为数组元素类型的默认初始值。
动态数组被初始化为拥有 0 个元素。
关联数组被初始化为拥有 0 个元素。

2 Void Initialization

Void initialization happens when the Initializer for an array is void. What it means is that no initialization is done, i.e. the contents of the array will be undefined. This is most useful as an efficiency optimization. Void initializations are an advanced technique and should only be used when profiling indicates that it matters.( 本文出处: http://www.d-programming-language-china.org )

3 Static Initialization of Static Arrays静态数组的静态初始化

Static initalizations are supplied by a list of array element values enclosed in [ ]. The values can be optionally preceded by an index and a :. If an index is not supplied, it is set to the previous index plus 1, or 0 if it is the first value.

int[3] a = [ 1:2, 3 ];    // a[0] = 0, a[1] = 2, a[2] = 3

当数组下标识为枚举类型时,这种写法是最方便的:

enum Color { red, blue, green };
int value[Color.max + 1] = [ Color.blue:6, Color.green:2, Color.red:5 ];

These arrays are static when they appear in global scope. Otherwise, they need to be marked with const or static storage classes to make them static arrays.

Special Array Types特殊数组型别

1 Strings字符串

字符串就是一个以字符为元素的数组。字符串文字量就是一种写字符数组的简单的方法。字符串文字量永远不变(只读)。

char[] str;
char[] str1 = "abc";
str[0] = 'b';    // error, "abc" is read only, may crash

char[] 字符串采用 UTF-8 格式。wchar[] 字符串采用 UTF-16 格式。dchar[] 字符串采用 UTF-32 格式。
字符串复制、比较、连接和追加:( 本文出处: http://www.d-programming-language-china.org )

str1 = str2;
if (str1 < str3) ...
func(str3 ~ str4);
str4 ~= str1;
with the obvious semantics.所有生成的临时对象都会被垃圾收集程序回收(或者使用 alloca())。而且,这对于所有的数组都适用,而不仅仅是对字符串数组。

可以使用指向 char 的指针:

char *p = &str[3];        // 指向第四个元素
char *p = str;        // 指向第一个元素

但是,因为 D 中的字符串数组不是以 0 终止的,所以如果要把一个字符串指针传递给 C 时,应该加上终止的 0 :

字符串的类型在编译的语义分析阶段决定。类型是下列之一:char[]、wchar[]、dchar[] ,并且按照隐式转换规则决定。如果有两个可用的等价的转换,会被认为是一个错误。为了避免出现这种模棱两可的局面,应该使用类型转换或加后缀c,w或d:

cast(wchar [])"abc"    // 这是 wchar 字符数组
"abc"w            // 一样是

字符串文字量如果没有使用后缀,隐式地在 char、wchar 和 dchar 之间转换。
长度为一个字符的字符串可以被转换为 char、wchar 或者 dchar 常量:

char c;
wchar w;
dchar d;

c = 'b';        // c is assigned the character 'b'
w = 'b';        // w is assigned the wchar character 'b'
w = 'bc';        // error - only one wchar character at a time
w = "b"[0];        // w is assigned the wchar character 'b'
w = \r[0];        // w is assigned the carriage return wchar character
d = 'd';        // d is assigned the character 'd'

2 C's printf() and Strings

printf() 是一个 C 函数,它不是 D 的一部分。printf() 输出以 0 终止的 C 字符串。有两种用 printf() 输出 D 字符串的方法。第一种方法是加一个终止的 0 ,并将结果转换为 char* :( 本文出处: http://www.d-programming-language-china.org )

str ~= "\0";
printf("the string is '%s'\n", (char *)str);

或者:

import std.string;
printf("the string is '%s'\n", std.string.toStringz(str));

String literals already have a 0 appended to them, so can be used directly:

printf("the string is '%s'\n", cast(char*)"string literal");

So, why does the first string literal to printf not need the cast? The first parameter is prototyped as a char*, and a string literal can be implicitly cast to a char*. The rest of the arguments to printf, however, are variadic (specified by ...), and a string literal is passed as a (length,pointer) combination to variadic parameters.

第二种方法使用精度指示符。D 数组的内存分布是, 首先是数组长度,所以下面这种方法可以工作:

printf("the string is '%.*s'\n", str);

将来,或许应该为 printf() 添加一个新的格式指示符代替这种依赖于实现细节的方法。( 本文出处: http://www.d-programming-language-china.org )

printf("the string is '%.*s'\n", str);

最好的方法是使用能够处理D字符串的std.stdio.writefln:

import std.stdio;
writefln("the string is '%s'", str);

3 Implicit Conversions隐式转换

指针 T* 可以被隐式的转换为::
void*

静态数组 T[dim] 可以被隐式地转换为下列之一:
T[]
U[]
void[]

动态数组 T[] 可以被隐式地转换为下列之一:
U[]
void[]
Where U is a base class of T.

Associative Arrays关联数组

关联数组的下标不一定非得是一个整数,因而有广泛的用途。关联数组的下标叫做 关键字,它的类型是KeyType 。
在关联数组的声明中,关键字 的类型声明位于 [] 内:( 本文出处: http://www.d-programming-language-china.org )

int[char[]] b;        // 关联数组 b 以 int 为数组元素,下标为字符数组,KeyType是char[]
b["hello"] = 3;        // 设定关键字 "hello" 对应的值为 3
func(b["hello"]);        // 传递给 func() 的参数为 3

可以使用 remove函数删除关联数组中的某个键值:

b.remove("hello");

如果指定键存在于关联数组内,In表达式 生成一个指针值,null或者不是:

int* p;
p = ("hello" in b);
if (p != null)
    ...

键类型不能是函数或者是voids。

如果键类型是结构,默认机制是计算hash并比内部的二进制值,可以象下面函数那样自定机制:

uint toHash();
int opCmp(KeyType* s);

示例:

import std.string;

struct MyString
{
    char[] str;

    uint toHash()
    {    uint hash;
        foreach (char c; s)
            hash = (hash * 9) + c;
        return hash;
    }

    int opCmp(MyString* s)
    {
        return std.string.cmp(this.str, s.str);
    }
}

1 Using Classes as the KeyType

类也可以用于KeyType,这就要求在类定义中重载下面的类对象的成员函数:

hash_t toHash()
int opEquals(Object)
int opCmp(Object)

Note that the parameter to opCmp and opEquals is of type Object, not the type of the class in which it is defined.

For example:

class Foo
{
    int a, b;

    hash_t toHash() { return a + b; }

    int opEquals(Object o)
    {    Foo f = cast(Foo) o;
    return f && a == foo.a && b == foo.b;
    }

    int opCmp(Object o)
    {    Foo f = cast(Foo) o;
    if (!f)
        return -1;
    if (a == foo.a)
        return b - foo.b;
    return a - foo.a;
    }
}

The implementation may use either opEquals or opCmp or both. Care should be taken so that the results of opEquals and opCmp are consistent with each other when the class objects are the same or not.

Using Structs or Unions as the KeyType
Structs or unions can be used as the KeyType. For this to work, the struct or union definition must define the following member functions:( 本文出处: http://www.d-programming-language-china.org )

hash_t toHash()
int opEquals(S) or int opEquals(S*)
int opCmp(S) or int opCmp(S*)
Note that the parameter to opCmp and opEquals can be either the struct or union type, or a pointer to the struct or untion type.

For example:

struct S
{
    int a, b;

    hash_t toHash() { return a + b; }

    int opEquals(S s)
    {
    return a == s.a && b == s.b;
    }

    int opCmp(S* s)
    {
    if (a == s.a)
        return b - s.b;
    return a - s.a;
    }
}

The implementation may use either opEquals or opCmp or both. Care should be taken so that the results of opEquals and opCmp are consistent with each other when the struct/union objects are the same or not.

2 Properties关联数组的属性有:

.sizeof 返回指向关联数组的引用的大小;典型值是 8 。
.length 返回关联数组中值的个数。与动态数组不同的是,它是只读的。
.keys 返回动态数组,数组的元素是关联数组的关键字。
.values 返回动态数组,数组的元素是关联数组的值。
.rehash 适当地重新组织关联数组以提高查找效率。例如,程序已经载入了符号表并将开始进行查找时,rehash 会提高效率。返回指向新数组的引用。

3 Associative Array Example: word count关联数组示例:单词计数

import std.file;    // D file I/O
import std.stdio;

int main (char[][] args)
{
    int word_total;
    int line_total;
    int char_total;
    int[char[]] dictionary;

    writefln("    lines    words    bytes file");
    for (int i = 1; i < args.length; ++i)    // program arguments
    {
    char[] input;    // input buffer
    int w_cnt, l_cnt, c_cnt; // word, line, char counts
    int inword;
    int wstart;

    // read file into input[]
    input = cast(char[])std.file.read(args);

    foreach (j, char c; input)
    {
        if (c == '\n')
            ++l_cnt;
        if (c >= '0' && c <= '9')
        {
        }
        else if (c >= 'a' && c <= 'z' ||
            c >= 'A' && c <= 'Z')
        {
        if (!inword)
        {
            wstart = j;
            inword = 1;
            ++w_cnt;
        }
        }
        else if (inword)
        {
        char[] word = input[wstart .. j];
        dictionary[word]++;    // increment count for word
        inword = 0;
        }
        ++c_cnt;
    }
    if (inword)
    {
        char[] word = input[wstart .. input.length];
        dictionary[word]++;
    }
    writefln("%8d%8d%8d %s", l_cnt, w_cnt, c_cnt, args);
    line_total += l_cnt;
    word_total += w_cnt;
    char_total += c_cnt;
    }

    if (args.length > 2)
    {
    writef("-------------------------------------\n%8ld%8ld%8ld total",
        line_total, word_total, char_total);
    }

    writefln("-------------------------------------");
    foreach (word; dictionary.keys.sort)
    {
    writefln("%3d %s", dictionary[word], word);
    }
    return 0;
}

d-programming-language-china.org整理手记_小标题要保留英文方便索引

脱离具体应用而读D语言手册无疑是枯燥的。有点昏昏欲睡了。忽然想到:D语言英文手册翻译成中文后会有一个问题,就是不利于英文关键词索引。翻译成中文只是为了方便初学者,实际编程却是英文的世界。原来虽然想到此问题,并把文章标题加上了英文。现在想来,所有二级标题,三级标题全部要有英文。这样我们就可能实现在写代码时,一键调出D语言关键字,或方法对应的中文chm帮助文档。( 本文出处: http://www.d-programming-language-china.org )

( lastupdate:20070426 最新文章请访问http://www.d-programming-language-china.org )

关于一大步成功社区:
yidabu提倡在交流中学习,在分享中提高
收集感兴趣的知识,写下心得,通过网络与别人一起分享
理解一点就实践一步,收获什么就分享什么,成功就是这样一点点一步步累积起来的
网络只是一个工具,只有自己身心提高才是实实在在的。d-programming-language-china.org为大家提供一个学习交流各种知识的平台

TOP

发新话题