编程语言乱炖(三)数据结构:数组和字符串

在现实中经常需要处理大量同类的数据,如果一个一个表示就太麻烦了,如能“一组一组”表示会方便很多。于是就有了“数组”。

各种编程语言几乎都有数组(或类似的数据结构)。除此之外,还有表、字典、元组之类的类似结构。而字符串,往往也是用数组的结构来存放。

C语言数组

我们知道,要想把数据放入内存,必须先要分配内存空间。放入4个整数,就得分配4个int类型的内存空间:int a[4];这样,就在内存中分配了4个int类型的内存空间,共 4×4=16 个字节,并为它们起了一个名字,叫a。我们把这样的一组数据的集合称为数组(Array),它所包含的每一个数据叫做数组元素(Element),所包含的数据的个数称为数组长度(Length),例如int a[4];就定义了一个长度为4的整型数组,名字是a。看看,不但节省了代码,而且节省了命名。

数组不但简化了表示,而且提高了操作效率。数组中的每个元素都有一个序号,这个序号从0开始,而不是从我们熟悉的1开始,称为下标(Index)。使用数组元素时,指明下标即可,形式为:arrayName[index]
arrayName 为数组名称,index 为下标。例如,a[0] 表示第0个元素,a[3] 表示第3个元素。这样,数组就变得可索引了。

接下来我们就把第一行的4个整数放入数组:a[0]=20;a[1]=345;a[2]=700;a[3]=22;
这里的0、1、2、3就是数组下标,a[0]、a[1]、a[2]、a[3] 就是数组元素。我们来总结一下数组的定义方式:dataType arrayName[length];
dataType 为数据类型,arrayName 为数组名称,length 为数组长度。例如:float m[12];char ch[9];
注意:1) 数组中每个元素的数据类型必须相同,对于int a[4];,每个元素都必须为 int。2) 数组下标必须是整数,取值范围为 0 ≤ index < length。3) 数组是一个整体,它的内存是连续的,下面是int a[4];的内存示意图:

a[1] a[2] a[3] a[4]

注意,在内存中是连续存放的,所以节省空间,寻址快速。

数组的初始化
上面的代码是先定义数组再给数组赋值,我们也可以在定义数组的同时赋值:

int a[4] = {20, 345, 700, 22};

{ }中的值即为各元素的初值,各值之间用,间隔。对数组赋初值需要注意以下几点:

  1. 可以只给部分元素赋初值。当{ }中值的个数少于元素个数时,只给前面部分元素赋值。例如:

    int a[10]={12, 19, 22 , 993, 344};

表示只给 a[0]~a[4] 5个元素赋值,而后面5个元素自动赋0值。当赋值的元素少于数组总体元素的时候,剩余的元素自动初始化为 0:对于short、int、long,就是整数0;对于char,就是字符 '\0';对于float、double,就是小数0.0。我们可以通过下面的形式将数组的所有元素初始化为 0:

int a[10] = {0};
char c[10] = {0};
float f[10] = {0};

由于剩余的元素会自动初始化为0,所以只需要给第0个元素赋0值即可。
示例:输出数组元素。

#include <stdio.h>
int main()
{
  int a[6] = {299, 34, 92, 100};
  int b[6], i;
  //从控制台输入数据为每个元素赋值
  for(i=0; i<6; i++){
    scanf("%d", &b[i]);
  }
  //输出数组元素
  for(i=0; i<6; i++){
    printf("%d ", a[i]);
  }
  putchar('\n');
  for(i=0; i<6; i++){
    printf("%d ", b[i]);
  }
  putchar('\n');

  return 0;
}
  1. 只能给元素逐个赋值,不能给数组整体赋值。例如给十个元素全部赋1值,只能写为:

    int a[10]={1, 1, 1, 1, 1, 1, 1, 1, 1, 1};

而不能写为:int a[10]=1;

  1. 如给全部元素赋值,那么在数组定义时可以不给出数组的长度。例如:

    int a[]={1,2,3,4,5};

等价于int a[5]={1,2,3,4,5};

二维数组

上节讲解的数组可以看作是一行连续的数据,只有一个下标,称为一维数组。在实际问题中有很多量是二维的或多维的,因此C语言允许构造多维数组。多维数组元素有多个下标,以确定它在数组中的位置。本节只介绍二维数组,多维数组可由二维数组类推而得到。

二维数组定义的一般形式是:

dataType arrayName[length1][length2];

其中,dataType 为数据类型,arrayName 为数组名,length1 为第一维下标的长度,length2 为第二维下标的长度。例如:int a[3][4];
定义了一个3行4列的数组,共有3×4=12个元素,数组名为a,即:
a[0][0], a[0][1], a[0][2], a[0][3]
a[1][0], a[1][1], a[1][2], a[1][3]
a[2][0], a[2][1], a[2][2], a[2][3]
在二维数组中,要定位一个元素,必须给出一维下标和二维下标,就像在一个平面中确定一个点,要知道x坐标和y坐标。例如,a[3][4] 表示a数组第3行第4列的元素。二维数组在概念上是二维的,但在内存中地址是连续的,也就是说存储器单元是按一维线性排列的。那么,如何在一维存储器中存放二维数组呢?有两种方式:一种是按行排列, 即放完一行之后顺次放入第二行。另一种是按列排列, 即放完一列之后再顺次放入第二列。在C语言中,二维数组是按行排列的。也就是先存放a[0]行,再存放a[1]行,最后存放a[2]行;每行中的四个元素也是依次存放。数组a为int类型,每个元素占用4个字节,整个数组共占用4×(3×4)=48个字节。

【示例】一个学习小组有5个人,每个人有三门课的考试成绩。求全组分科的平均成绩和各科总平均成绩。

科目
Math 80 61 59 85 76
C 75 65 63 87 77
English 92 71 70 90 85

可设一个二维数组a[5][3]存放五个人三门课的成绩。再设一个一维数组v[3]存放所求得各分科平均成绩,设变量average 为全组各科总平均成绩。编程如下:

#include <stdio.h>
int main(){
  int i, j; //二维数组下标
  int sum=0; //当前科目的总成绩
  int average; //总平均分
  int v[3]; //各科平均分
  int a[5][3]; //用来保存每个同学各科成绩的二维数组
  printf("Input score:\n");
  for(i=0; i<3; i++){
    for(j=0; j<5; j++){
      scanf("%d", &a[j][i]); //输入每个同学的各科成绩
      sum+=a[j][i]; //计算当前科目的总成绩
    }
    v[i]=sum/5; // 当前科目的平均分
    sum=0;
  }
  average =(v[0]+v[1]+v[2])/3;
  printf("Math: %d\nC Languag: %d\nEnglish: %d\n", v[0], v[1], v[2]);
  printf("Total:%d\n", average);
  return 0;
}

程序中首先用了一个双重循环。在内循环中依次读入某一门课程的各个学生的成绩,并把这些成绩累加起来,退出内循环后再把该累加成绩除以5送入v[i]之中,这就是该门课程的平均成绩。外循环共循环三次,分别求出三门课各自的平均成绩并存放在v数组之中。退出外循环之后,把v[0]、v[1]、v[2]相加除以3即得到各科总平均成绩。最后按题意输出各个成绩。

二维数组的初始化
二维数组的初始化可以按行分段赋值,也可按行连续赋值。例如对数组a[5][3],按行分段赋值可写为:int a[5][3]={ {80,75,92}, {61,65,71}, {59,63,70}, {85,87,90}, {76,77,85} };
按行连续赋值可写为:int a[5][3]={80, 75, 92, 61, 65, 71, 59, 63, 70, 85, 87, 90, 76, 77, 85};
这两种赋初值的结果是完全相同的。

对于二维数组初始化赋值还有以下说明

  1. 可以只对部分元素赋初值,未赋初值的元素自动取0值。例如:

    int a[3][3]={{1},{2},{3}};

是对每一行的第一列元素赋值,未赋值的元素取0值。 赋值后各元素的值为:
1 0 0
2 0 0
3 0 0

int a [3][3]={{0,1},{0,0,2},{3}};

赋值后的元素值为:
0 1 0
0 0 2
3 0 0

  1. 如对全部元素赋初值,则第一维的长度可以不给出。例如:

    int a[3][3]={1,2,3,4,5,6,7,8,9};

可以写为:

int a[][3]={1,2,3,4,5,6,7,8,9};
  1. 数组是一种构造类型的数据。二维数组可以看作是由一维数组的嵌套而构成的。设一维数组的每个元素都又是一个数组,就组成了二维数组。当然,前提是各元素类型必须相同。根据这样的分析,一个二维数组也可以分解为多个一维数组。C语言允许这种分解。

如二维数组a[3][4],可分解为三个一维数组,其数组名分别为:a[0]、a[1]、a[2]。

对这三个一维数组不需另作说明即可使用。这三个一维数组都有4个元素,例如:一维数组a[0]的元素为a[0][0], a[0][1], a[0][2], a[0][3]。必须强调的是,a[0], a[1], a[2]不能当作下标变量使用,它们是数组名,不是一个单纯的下标变量。

总的来看,C数组还是很强大的。

C语言字符串

字符串(String)在处理自然语言时是不可或缺的。但是,在C语言中,没有专门的字符串变量,没有string类型,通常就用一个字符数组来存放一个字符串。

用来存放字符的数组称为字符数组,例如:

char a[10]; //一维字符数组
char b[5][10]; //二维字符数组
char c[20]={'c', ' ', 'p', 'r', 'o', 'g', 'r', 'a','m'}; // 给部分数组元素赋值
char d[]={'c', ' ', 'p', 'r', 'o', 'g', 'r', 'a', 'm' }; //对全体元素赋值时可以省去长度

字符数组实际上是一系列字符的集合,也就是字符串。

C语言规定,可以将字符串直接赋值给字符数组,例如:

char str[30] = {"c.biancheng.net"};
char str[30] = "c.biancheng.net"; //这种形式更加简洁,实际开发中常用

数组第0个元素为 'c',第1个元素为 '.',第2个元素为 'b',后面的元素以此类推。也可以不指定数组长度,例如:

char str[] = {"c.biancheng.net"};
char str[] = "c.biancheng.net"; //这种形式更加简洁,实际开发中常用

在C语言中,字符串总是以'\0'作为串的结束符。上面的两个字符串,编译器已经在末尾自动添加了'\0'。'\0'是ASCII码表中的第0个字符,用NUL表示,称为空字符。该字符既不能显示,也不是控制字符,输出该字符不会有任何效果,它在C语言中仅作为字符串的结束标志。puts 和 printf 在输出字符串时会逐个扫描字符,直到遇见 '\0' 才结束输出。

需要注意的是,用字符串给字符数组赋值时由于要添加结束符 '\0',数组的长度要比字符串的长度(字符串长度不包括 '\0')大1。例如:

char str[] = "C program";

该数组在内存中的实际存放情况为:

C p r o g r a m \0

字符串长度为 9,数组长度为 10。

字符串处理

C语言提供了丰富的字符串处理函数,例如字符串的输入、输出、合并、修改、比较、转换、复制、搜索等,使用这些现成的函数可大大减轻编程的负担。用于输入输出的字符串函数,例如printf、puts、scanf、gets等,使用时应包含头文件stdio.h,使用其它字符串函数则应包含头文件string.h。

1)字符串操作
strcpy(p, p1) 复制字符串
strncpy(p, p1, n) 复制指定长度字符串
strcat(p, p1) 附加字符串
strncat(p, p1, n) 附加指定长度字符串
strlen(p) 取字符串长度
strcmp(p, p1) 比较字符串
strcasecmp忽略大小写比较字符串
strncmp(p, p1, n) 比较指定长度字符串
strchr(p, c) 在字符串中查找指定字符
strrchr(p, c) 在字符串中反向查找
strstr(p, p1) 查找字符串
strpbrk(p, p1) 以目标字符串的所有字符作为集合,在当前字符串查找该集合的任一元素
strspn(p, p1) 以目标字符串的所有字符作为集合,在当前字符串查找不属于该集合的任一元素的偏移
strcspn(p, p1) 以目标字符串的所有字符作为集合,在当前字符串查找属于该集合的任一元素的偏移 * 具有指定长度的字符串处理函数在已处理的字符串之后填补零结尾符

2)字符串到数值类型的转换
strtod(p, ppend) 从字符串 p 中转换 double 类型数值,并将后续的字符串指针存储到 ppend 指向的 char* 类型存储。
strtol(p, ppend, base) 从字符串 p 中转换 long 类型整型数值,base 显式设置转换的整型进制,设置为 0 以根据特定格式判断所用进制,0x, 0X 前缀以解释为十六进制格式整型,0 前缀以解释为八进制格式整型
atoi(p) 字符串转换到 int 整型
atof(p) 字符串转换到 double 浮点数
atol(p) 字符串转换到 long 整型

C的字符串操作函数还算丰富,只是有些操作起来不算方便。

字符串的输出

在C语言中,输出字符串的函数有两个:puts():直接输出字符串,并且只能输出字符串。printf():通过格式控制符 %s 输出字符串。除了字符串,printf() 还能输出其他类型的数据。

这两个函数主要用于格式化输出结果,请看下面的代码:

#include <stdio.h>
int main(){
  int i;
  char str[] = "http://c.biancheng.net";
  printf("%s\n", str); //通过变量输出
  printf("%s\n", "http://c.biancheng.net"); //直接输出
  puts(str); //通过变量输出
  puts("http://c.biancheng.net"); //直接输出
  return 0;

}

运行结果:
http://c.biancheng.net
http://c.biancheng.net
http://c.biancheng.net
http://c.biancheng.net

在 printf() 函数中使用%s输出字符串时,在变量列表中给出数组名即可,不能写为printf("%s", str[]);。

字符串的输入

在C语言中,输入字符串的函数有两个:
scanf():通过格式控制符 %s 输入字符串。除了字符串,scanf() 还能输入其他类型的数据。
gets():直接输入字符串,并且只能输入字符串。

  1. 使用 scanf() 读取字符串
    请先看下面的例子:

    include <stdio.h>

    int main(){
    char str1[30], str2[30];
    printf("Input str1: ");
    scanf("%s", str1);
    printf("Input str2: ");
    scanf("%s", str2);
    printf("str1: %s\nstr2: %s\n", str1, str2);
    return 0;
    }

运行结果:
Input str1: c.biancheng.net↙
Input str2: Java Python C-Sharp↙
str1: c.biancheng.net
str2: Java

由于字符数组长度为30,因此输入的字符串长度必须小于30,以留出一个字节用于存放字符串结束标志\0对程序的说明:① 我们本来希望将 "Java Python C-Sharp" 赋值给 str2,但是 scanf() 只读取到 "Java",这是因为 scanf() 读取到空格时就认为字符串输入结束了,不会继续读取了。

② 在《从键盘输入数据》中讲到,scanf 的各个变量前面要加取地址符&
,用以获得变量的地址,例如:

int a, b;
scanf("%d %d", &a, &b);

但是在本节的示例中,将字符串读入字符数组却没有使用&
,例如:

char str1[20], str2[20], str3[20], str4[20];
scanf("%s %s %s %s",str1, str2, str3, str4);

这是因为C语言规定,数组名就代表了该数组的地址。整个数组是一块连续的内存单元,如有字符数组char c[10],在内存可表示为:


C语言还规定,数组名所代表的地址为第0个元素的地址,例如char c[10];,c
就代表c[0]的地址。第0个元素的地址就是数组的起始地址,称为首地址。也就是说,数组名表示数组的首地址。设数组c的首地址为0X2000,也即c[0]地址为0X2000,则数组名c就代表这个地址。因为c已经表示地址,所以在c前面不能再加取地址符&,例如写作scanf("%s",&c);是错误的。

有了首地址,有了字符串结束符'\0',就可以在内存中完整定位一个字符串了。例如:

printf("%s", c);

printf 函数会根据数组名找到c的首地址,然后逐个输出数组中各个字符直到遇到 '\0' 为止。int、float、char 类型的变量表示数据本身,数据就保存在变量中;而数组名表示的是数组的首地址,数组保存在其他内存单元,数组名保存的是这块内存的首地址。

  1. 使用 gets() 读取字符串
    gets 是 get string 的缩写,意思是获取用户从键盘输入的字符串,语法格式为:gets(arrayName);
    arrayName 为字符数组。从键盘获得的字符串,将保存在 arrayName 中。请看下面的例子:

    include <stdio.h>

    int main(){
    char str1[30], str2[30];
    printf("Input str1: ");
    gets(str1);
    printf("Input str2: ");
    gets(str2);
    printf("str1: %s\nstr2: %s\n", str1, str2);

    return 0;
    }

运行结果:
Input str1: Java Python C-Sharp↙
Input str2: http://c.biancheng.net
str1: Java Python C-Sharp
str2: http://c.biancheng.net

可以发现,当输入的字符串中含有空格时,输出仍为全部字符串,这说明 gets() 函数不会把空格作为输入结束的标志,而只把回车换行作为输入结束的标志,这与 scanf() 函数是不同的。总结:如果希望读取的字符串中不包含空格,那么使用 scanf() 函数;如果希望获取整行字符串,那么使用 gets() 函数,它能避免空格的截断。

关于排序和查找

学完了数组,有两个重要的实际需求,那就是排序(Sort)和查找(Search),比如:给你 10 个打乱顺序的整数,要能够按照从小到大或者从大到小的顺序输出;
给定一个字符串 str1,以及一个子串 str2,要能够判断 str2 是否在 str1 中。

排序和查找的方法有很多种,C语言没有相关内置函数,主要使用别人的函数。这稍微弱些。

数组指针

C语言异于其他语言之处在于它有指针。同样也有数组指针和指针数组。
通过指针引用数组
C语言规定:如果指针变量p已指向数组中的一个元素,则p+1指向同一数组中的下一个元素。引入指针变量后,就可以用两种方法来访问数组元素了。这为C数组带来了灵活多变的访问形式。当然,也容易出错。

Java数组

在Java中,数组不属于基本数据类型,而属于引用类型。引用类型变量由类的构造函数创建,可以使用它们访问所引用的对象。这些变量在声明时被指定为一个特定的类型,比如Employee、Pubby等。变量一旦声明后,类型就不能被改变了。
对象、数组都是引用数据类型。所有引用类型的默认值都是null。一个引用变量可以用来引用与任何与之兼容的类型。
例子:Animal animal = new Animal(“giraffe”)。

Java在C的基础上对数组进行了完善。
首先,Java明确区分了数组的声明、创建和初始化。
在C中,我们写int a[10]时,既是声明了一个整型数组a,也在内存中创建了它。
而Java首先必须声明数组变量,才能在程序中使用数组。下面是声明数组变量的语法样式:

int[] myList; // 首选的方法
int myList[]; // 仿C/C++,效果相同,但不是首选方法

Java语言使用new操作符来创建数组,样式如下:

mylist = new int[10];

上面的语法语句做了两件事:
一、使用 int[10]创建了一个数组。
二、把新创建的数组的引用赋值给变量mylist。

数组变量的声明和创建数组可以用一条语句完成,如下所示:

int[] mylist = new int[10];

另外,你还可以使用如下的枚举方式创建数组。
dataType[] arrayRefVar = {value0, value1, ..., valuek};
数组的元素是通过索引访问的。数组索引从0开始,所以索引值从0到arrayRefVar.length-1。

示例
该实例完整地展示了如何创建、初始化和操纵数组:

public class TestArray { 
  public static void main(String[] args) { 
    double[] myList = {1.9, 2.9, 3.4, 3.5}; 
   // 打印所有数组元素
    for (int i = 0; i < myList.length; i++) { 
        System.out.println(myList[i] + " "); 
    } 
    // 计算所有元素的总和
    double total = 0; 
    for (int i = 0; i < myList.length; i++) { 
        total += myList[i]; 
    } 
    System.out.println("Total is " + total); 
    // 查找最大元素 
    double max = myList[0]; 
    for (int i = 1; i < myList.length; i++) {
        if (myList[i] > max) max = myList[i]; 
    } 
    System.out.println("Max is " + max); 
  }
}

以上实例编译运行结果如下:
1.9
2.9
3.4
3.5
Total is 11.7
Max is 3.5

JDK 1.5 引进了一种新的循环类型,被称为foreach循环或者加强型循环,它能在不使用下标的情况下遍历数组。
示例
该实例用来显示数组myList中的所有元素:

public class TestArray { 
    public static void main(String[] args) { 
        double[] myList = {1.9, 2.9, 3.4, 3.5}; 
        // 打印所有数组元素 
        for (double element: myList) { 
            System.out.println(element); 
        } 
    }
}

以上实例编译运行结果如下:
1.9
2.9
3.4
3.5

Arrays类

作为类语言的代表,java.util.Arrays类能方便地操作数组,它提供的所有方法(共4个)都是静态的。具有以下功能:
给数组赋值:通过fill方法。
对数组排序:通过sort方法,按升序。
比较数组:通过equals方法比较数组中元素值是否相等。
查找数组元素:通过binarySearch方法能对排序好的数组进行二分查找法操作。
具体说明请查看下表:
1.public static int binarySearch(Object[] a, Object key)用二分查找算法在给定数组中搜索给定值的对象(Byte,Int,double等)。数组在调用前必须排序好的。如果查找值包含在数组中,则返回搜索键的索引;否则返回 (-(插入点) - 1)。

2.public static boolean equals(long[] a, long[] a2)如果两个指定的 long 型数组彼此相等,则返回 true。如果两个数组包含相同数量的元素,并且两个数组中的所有相应元素对都是相等的,则认为这两个数组是相等的。换句话说,如果两个数组以相同顺序包含相同的元素,则两个数组是相等的。同样的方法适用于所有的其他基本数据类型(Byte,short,Int等)。

3.public static void fill(int[] a, int val)将指定的 int 值分配给指定 int 型数组指定范围中的每个元素。同样的方法适用于所有的其他基本数据类型(Byte,short,Int等)。

4.public static void sort(Object[] a)对指定对象数组根据其元素的自然顺序进行升序排列。同样的方法适用于所有的其他基本数据类型(Byte,short,Int等)。

相比其他语言,Java的数组方法不算多,但都是最常用的。

String类

相对于C语言没有字符串类型(只能用字符数组),字符串类型广泛应用在Java编程中,在Java中字符串属于对象,Java提供了String类来创建和操作字符串。

创建字符串
创建字符串最简单的方式如下:

String greeting = "Hello world!";

在代码中遇到字符串常量时,这里的值是"Hello world!",编译器会使用该值创建一个String对象。
和其它对象一样,可以使用关键字和构造方法来创建String对象。
String类有11种构造方法,这些方法提供不同的参数来初始化字符串,比如提供一个字符数组参数:

char[] helloArray = { 'h', 'e', 'l', 'l', 'o', '.'}; 
String helloString = new String(helloArray); 

注意:String类是不可改变的,所以你一旦创建了String对象,那它的值就无法改变了。 如果需要对字符串做很多修改,那么应该选择使用StringBuffer & StringBuilder 类

输出格式化字符串
我们知道输出格式化数字可以使用printf()和format()方法。String类使用静态方法format()返回一个String对象而不是PrintStream对象。
String类的静态方法format()能用来创建可复用的格式化字符串,而不仅仅是用于一次打印输出。如下所示:

System.out.printf("The value of the float variable is " + 
                          "%f, while the value of the integer " + 
                          "variable is %d, and the string " + 
                          "is %s", floatVar, intVar, stringVar);

你也可以这样写:

String fs;
fs = String.format("The value of the float variable is " + 
                           "%f, while the value of the integer " +
                           "variable is %d, and the string " + 
                            "is %s", floatVar, intVar, stringVar);
System.out.println(fs);

字符串的方法

Java拥有非常丰富的字符串操作方法,下面是String类支持的方法,更多详细,参看Java API文档:
1 char charAt(int index)
返回指定索引处的 char 值。
2 int compareTo(Object o)
把这个字符串和另一个对象比较。
3 int compareTo(String anotherString)
按字典顺序比较两个字符串。
4 int compareToIgnoreCase(String str)
按字典顺序比较两个字符串,不考虑大小写。
5 String concat(String str)
将指定字符串连接到此字符串的结尾。
6 boolean contentEquals(StringBuffer sb)
当且仅当字符串与指定的StringButter有相同顺序的字符时候返回真。
7 static String copyValueOf(char[] data)
返回指定数组中表示该字符序列的 String。
8 static String copyValueOf(char[] data, int offset, int count)
返回指定数组中表示该字符序列的 String。
9 boolean endsWith(String suffix)
测试此字符串是否以指定的后缀结束。
10 boolean equals(Object anObject)
将此字符串与指定的对象比较。
11 boolean equalsIgnoreCase(String anotherString)
将此 String 与另一个 String 比较,不考虑大小写。
12 byte[] getBytes()
使用平台的默认字符集将此 String 编码为 byte 序列,并将结果存储到一个新的 byte 数组中。
13 byte[] getBytes(String charsetName)
使用指定的字符集将此 String 编码为 byte 序列,并将结果存储到一个新的 byte 数组中。
14 void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)
将字符从此字符串复制到目标字符数组。
15 int hashCode()
返回此字符串的哈希码。
16 int indexOf(int ch)
返回指定字符在此字符串中第一次出现处的索引。
17 int indexOf(int ch, int fromIndex)
返回在此字符串中第一次出现指定字符处的索引,从指定的索引开始搜索。
18 int indexOf(String str)
返回指定子字符串在此字符串中第一次出现处的索引。
19 int indexOf(String str, int fromIndex)
返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始。
20 String intern()
返回字符串对象的规范化表示形式。
21 int lastIndexOf(int ch)
返回指定字符在此字符串中最后一次出现处的索引。
22 int lastIndexOf(int ch, int fromIndex)
返回指定字符在此字符串中最后一次出现处的索引,从指定的索引处开始进行反向搜索。
23 int lastIndexOf(String str)
返回指定子字符串在此字符串中最右边出现处的索引。
24 int lastIndexOf(String str, int fromIndex)
返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索。
25 int length()
返回此字符串的长度。
26 boolean matches(String regex)
告知此字符串是否匹配给定的正则表达式。
27 boolean regionMatches(boolean ignoreCase, int toffset, String other, int ooffset, int len)
测试两个字符串区域是否相等。
28 boolean regionMatches(int toffset, String other, int ooffset, int len)
测试两个字符串区域是否相等。
29 String replace(char oldChar, char newChar)
返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。
30 String replaceAll(String regex, String replacement
使用给定的 replacement 替换此字符串所有匹配给定的正则表达式的子字符串。
31 String replaceFirst(String regex, String replacement)
使用给定的 replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。
32 String[] split(String regex)
根据给定正则表达式的匹配拆分此字符串。
33 String[] split(String regex, int limit)
根据匹配给定的正则表达式来拆分此字符串。
34 boolean startsWith(String prefix)
测试此字符串是否以指定的前缀开始。
35 boolean startsWith(String prefix, int toffset)
测试此字符串从指定索引开始的子字符串是否以指定前缀开始。
36 CharSequence subSequence(int beginIndex, int endIndex)
返回一个新的字符序列,它是此序列的一个子序列。
37 String substring(int beginIndex)
返回一个新的字符串,它是此字符串的一个子字符串。
38 String substring(int beginIndex, int endIndex)
返回一个新字符串,它是此字符串的一个子字符串。
39 char[] toCharArray()
将此字符串转换为一个新的字符数组。
40 String toLowerCase()
使用默认语言环境的规则将此 String 中的所有字符都转换为小写。
41 String toLowerCase(Locale locale)
使用给定 Locale 的规则将此 String 中的所有字符都转换为小写。
42 String toString()
返回此对象本身(它已经是一个字符串!)。
43 String toUpperCase()
使用默认语言环境的规则将此 String 中的所有字符都转换为大写。
44 String toUpperCase(Locale locale)
使用给定 Locale 的规则将此 String 中的所有字符都转换为大写。
45 String trim()
返回字符串的副本,忽略前导空白和尾部空白。
46 static String valueOf(primitive data type x)
返回给定data type类型x参数的字符串表示形式。

Java StringBuffer和StringBuilder类

如上所述,Java中的String类适用于常量,当对字符串进行修改的时候,需要使用StringBuffer和StringBuilder类。
和String类不同的是,StringBuffer和StringBuilder类的对象能够被多次的修改,并且不产生新的未使用对象。
StringBuilder类在Java 5中被提出,它和StringBuffer之间的最大不同在于StringBuilder的方法不是线程安全的(不能同步访问)。
由于StringBuilder相较于StringBuffer有速度优势,所以多数情况下建议使用StringBuilder类。然而在应用程序要求线程安全的情况下,则必须使用StringBuffer类。
实例

public class Test{
     public static void main(String args[]){ 
            StringBuffer sBuffer = new StringBuffer(" test");
            sBuffer.append(" String Buffer"); 
            System.out.println(sBuffer); 
    }
}

以上实例编译运行结果如下:
test String Buffer

StringBuffer 方法
以下是StringBuffer类支持的主要方法:
序号 方法描述
1.public StringBuffer append(String s)将指定的字符串追加到此字符序列。

2.public StringBuffer reverse() 将此字符序列用其反转形式取代。

3.public delete(int start, int end)移除此序列的子字符串中的字符。

4.public insert(int offset, int i)将 int
参数的字符串表示形式插入此序列中。

5.replace(int start, int end, String str)使用给定 String
中的字符替换此序列的子字符串中的字符。

其他方法和String类的方法类似。

JavaScript 数组

在程序语言中数组的重要性不言而喻,JavaScript中数组也是最常使用的对象之一(数组是对象),数组是值的有序集合,由于弱类型的原因,JavaScript中数组十分灵活、强大,不像是Java等强类型高级语言数组只能存放同一类型或其子类型元素,JavaScript在同一个数组中可以存放多种类型的元素,而且是长度也是可以动态调整的,可以随着数据增加或减少自动对数组长度做更改。这就使JavaScript数组异常灵活和强大。

创建数组

在JavaScript2种方式创建数组——构造函数和字面量

构造函数
1.无参构造函数,创建一空数组

var a1=new Array();

2.一个数字参数构造函数,指定数组长度(由于数组长度可以动态调整,作用并不大),创建指定长度的数组

var a2=new Array(5);

3.带有初始化数据的构造函数,创建数组并初始化参数数据

var a3=new Array(4,'hello',new Date());

字面量
1.使用方括号,创建空数组,等同于调用无参构造函数

var a4=[];

2.使用中括号,并传入初始化数据,等同于调用调用带有初始化数据的构造函数

var a5=[10];

注意点
1.在使用构造函数创建数组时如果传入一个数字参数,则会创建一个长度为参数的数组,如果传入多个,则创建一个数组,参数作为初始化数据加到数组中

var a1=new Array(5); 
console.log(a1.length);//5 
console.log(a1); //[] ,数组是空的 
var a2=new Array(5,6); 
console.log(a2.length);//2 
console.log(a2); //[5,6]

但是使用字面量方式,无论传入几个参数,都会把参数当作初始化内容

var a1=[5];
console.log(a1.length);//1
console.log(a1); //[5]

var a2=[5,6];
console.log(a2.length);//2
console.log(a2); //[5,6]

数组的索引与长度

数组的值可以通过自然数索引访问进行读写操作,下标也可以是一个得出非负整数的变量或表达式

var a1=[1,2,3,4];
console.log(a1[0]); //1
var i=1;
console.log(a1[i]); //2
console.log(a1[++i]); //3

数组的索引可以不是连续的,访问index不存在的元素的时候返回undefined

var a=new Array(1,2,3);
        a[100]=100;
        console.log(a.length); //101
        console.log(a[3]); //undefined
        console.log(a[99]); //undefined
        console.log(a[100]); 100

上面的例子中,虽然直接对a[100]赋值不会影响a[4]或a[99],但数组的长度却受到影响,数组length属性等于数组中最大的index+1,我们知道数组的length属性同样是个可写的属性,当强制把数组的length属性值设置为小于等于最大index值时,数组会自动删除indexd大于等于length的数据,在刚才代码中追加几句

a.length=2 console.log(a);//[1,2]

这时候会发现a[2]和a[100]被自动删除了,同理,如果把length设置为大于最大index+1的值的时候,数组也会自动扩张,但是不会为数组添加新元素,只是在尾部追加空空间

a.length=5; 
console.log(a); //[1,2] //后面没有3个undefined

修改已有数组中的值
如需修改已有数组中的值,只要向指定索引(下标号)添加一个新值即可:

var mycars=new Array("Saab","Volvo","BMW")
mycars[0]="Opel";
document.write(mycars[0]);

将输出:Opel

元素添加/删除
上面例子已经用到向数组内添加元素方法,直接使用索引就可以(index没必要连续)

var a=new Array(1,2,3); 
a[3]=4; 
console.log(a);//[1, 2, 3, 4]

前面提到数组也是对象,索引只是特殊的属性,所以我们可以使用删除对象属性的方法,使用delete 删除数组元素

delete a[2];
console.log(a[2]); //undefined

这样和直接把a[2]赋值为undefined类似,不会改变数组长度,也不会改变其他数据的index和value对应关系

数组的属性##

JavaScript的数组是对象,所以有属性。共三大属性:
[constructor] 返回对创建此对象的数组函数的引用。
例子 1
在本例中,我们将展示如何使用 constructor 属性:

<script type="text/javascript">
    var test=new Array();
    if (test.constructor==Array){
        document.write("This is an Array");
    }if (test.constructor==Boolean){
        document.write("This is a Boolean");
    }if (test.constructor==Date){
        document.write("This is a Date");
    }if (test.constructor==String){
        document.write("This is a String");
    }
</script>

输出:
This is an Array

例子 2
在本例中,我们将展示如何使用 constructor 属性:

<script type="text/javascript">
function employee(name,job,born{
    this.name=name;
    this.job=job;
    this.born=born;
}
var bill=new employee("Bill Gates","Engineer",1985);
document.write(bill.constructor);
</script>

输出:
function employee(name,job,born) { this.name=name; this.job=job; this.born=born; }

[length]设置或返回数组中元素的数目。
语法
arrayObject.length
说明
length不是方法而是属性。数组的 length 属性总是比数组中定义的最后一个元素的下标大 1。对于那些具有连续元素,而且以元素 0 开始的常规数组而言,属性 length 声明了数组中的元素的个数。
数组的 length 属性在用构造函数 Array() 创建数组时被初始化。给数组添加新元素时,如果必要,将更新 length 的值。
设置 length 属性可改变数组的大小。如果设置的值比其当前值小,数组将被截断,其尾部的元素将丢失。如果设置的值比它的当前值大,数组将增大,新的元素被添加到数组的尾部,它们的值为 undefined。
在本例中,我们将展示如何使用 length 属性返回并设置数组的长度:

var arr = new Array(3)
arr[0] = "John"
arr[1] = "Andy"
arr[2] = "Wendy"
document.write("Original length: " + arr.length)
document.write("<br />")
arr.length=5
document.write("New length: " + arr.length)

输出:
Original length: 3
New length: 5

[prototype]使您有能力向对象添加属性和方法。
语法
object.prototype.name=value
实例
在本例中,我们将展示如何使用 prototype 属性来向对象添加属性:

function employee(name,job,born){
    this.name=name;
    this.job=job;
    this.born=born;
}
var bill=new employee("Bill Gates","Engineer",1985);
employee.prototype.salary=null;
bill.salary=20000;
document.write(bill.salary);

输出:
20000

Array 对象方法##

JavaScript的数组方法也很多。
[concat()]连接两个或更多的数组,并返回结果。
[join()]把数组的所有元素放入一个字符串。元素通过指定的分隔符进行分隔。
[pop()]删除并返回数组的最后一个元素
[push()]向数组的末尾添加一个或更多元素,并返回新的长度。
[reverse()]颠倒数组中元素的顺序。
[shift()]删除并返回数组的第一个元素
[slice()]从某个已有的数组返回选定的元素
[sort()]对数组的元素进行排序
[splice()]删除元素,并向数组添加新元素。
[toSource()]返回该对象的源代码。
[toString()]把数组转换为字符串,并返回结果。
[toLocaleString()]把数组转换为本地数组,并返回结果。
[unshift()]向数组的开头添加一个或更多元素,并返回新的长度。
[valueOf()]返回数组对象的原始值

了解了这些看看数组真的很了不得啊,即强大有灵活,但是在遍历元素,及获取元素位置等也有一定的不便,这些在ECMAScript中已经得到解决。

ES6对数组的扩展##

Array.from()
可以将各种值、对象转为真正的数组,并且还提供map功能。这实际上意味着,只要有一个原始的数据结构,你就可以先对它的值进行处理,然后转成规范的数组结构,进而就可以使用数量众多的数组方法。

Array.from({ length: 2 }, () => 'jack')// ['jack', 'jack']

上面代码中,Array.from的第一个参数创建了一个长度为2的数组,第二个参数将其每个值都赋为“jack”。这种特性可以让该方法的用法变得非常灵活。

Array.from方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括ES6新增的数据结构Set和Map)。
下面是一个类似数组的对象,Array.from
将它转为真正的数组。

let arrayLike = { 
    '0': 'a', 
    '1': 'b',
    '2': 'c', 
    length: 3
};
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']

实际应用中,常见的类似数组的对象是DOM操作返回的NodeList集合,以及函数内部的arguments对象。Array.from都可以将它们转为真正的数组。

Array.from()的另一个应用是,将字符串转为数组,然后返回字符串的长度。因为它能正确处理各种Unicode字符,可以避免JavaScript将大于\uFFFF的Unicode字符,算作两个字符的bug。

Array.from('hello')// ['h', 'e', 'l', 'l', 'o']

Array.of()
Array.of方法用于将一组值,转换为数组。

Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
Array.of(3).length // 1

这个方法的主要目的,是弥补数组构造函数Array()
的不足。因为参数个数的不同,会导致Array()
的行为有差异。

Array() // []
Array(3) // [, , ,]
Array(3, 11, 8) // [3, 11, 8]

上面代码中,Array方法没有参数、一个参数、三个参数时,返回结果都不一样。只有当参数个数不少于2个时,Array()才会返回由参数组成的新数组。参数个数只有一个时,实际上是指定数组的长度。
Array.of基本上可以用来替代Array()或new Array(),并且不存在由于参数不同而导致的重载。它的行为非常统一。

Array.of() // []
Array.of(undefined) // [undefined]
Array.of(1) // [1]
Array.of(1, 2) // [1, 2]

Array.of总是返回参数值组成的数组。如果没有参数,就返回一个空数组。

新方法
数组实例的copyWithin()、find()和findIndex()、fill()、entries(),keys()和values()]、includes()都是新的方法。

ES6提供三个新的方法——entries(),keys()和values()——用于遍历数组。它们都返回一个遍历器对象(详见《Iterator》一章),可以用for...of循环进行遍历,唯一的区别是keys()是对键名的遍历、values()是对键值的遍历,entries()
是对键值对的遍历。这个异常强大。

includes()则是完美的确定数组是否包含给定的值的方法。find方法,用于找出第一个符合条件的数组成员。

数组空位
数组的空位指,数组的某一个位置没有任何值。比如,Array
构造函数返回的数组都是空位。

Array(3) // [, , ,]

上面代码中,Array(3)返回一个具有3个空位的数组。注意,空位不是undefined
,一个位置的值等于undefined,依然是有值的。空位是没有任何值,in运算符可以说明这一点。

ES6则是明确将空位转为undefined。
Array.from方法会将数组的空位,转为undefined,也就是说,这个方法不会忽略空位。

Array.from(['a',,'b'])// [ "a", undefined, "b" ]

由于空位的处理规则非常不统一,所以建议避免出现空位。

JavaScript字符串##

JavaScript有字符串对象,不但非常灵活,而且与正则表达式紧密结合,十分强大。
String 对象用于处理文本(字符串)。
创建 String 对象的语法:
new String(s);
String(s);
参数 s 是要存储在 String 对象中或转换成原始字符串的值。
返回值
当 String() 和运算符 new 一起作为构造函数使用时,它返回一个新创建的 String 对象,存放的是字符串 s 或 s 的字符串表示。
当不用 new 运算符调用 String() 时,它只把 s 转换成原始的字符串,并返回转换后的值。
实际中一般使用如下的简单初始化法创建字符串:

var txt="Hello world!";

这等同于:

var txt = new Strings("Hello world!");

像数组对象一样,String 对象也有三大属性:
constructor 对创建该对象的函数的引用
length 字符串的长度
prototype 允许您向对象添加属性和方法

最常用的是length属性:

document.write(txt.length)

需要注意的是,JavaScript 的字符串是不可变的(immutable),String 类定义的方法都不能改变字符串的内容。像 String.toUpperCase() 这样的方法,返回的是全新的字符串,而不是修改原始字符串。

字符串方法###

String 类定义了大量操作字符串的方法,例如从字符串中提取字符或子串,或者检索字符或子串,甚至有很多关于现实样式的。

[anchor()]创建 HTML 锚。

[big()]用大号字体显示字符串。

[blink()]显示闪动字符串。

[bold()]使用粗体显示字符串。

[charAt()]返回在指定位置的字符。

[charCodeAt()]返回在指定的位置的字符的 Unicode 编码。

[concat()]连接字符串。

[fixed()]以打字机文本显示字符串。

[fontcolor()]使用指定的颜色来显示字符串。

[fontsize()]使用指定的尺寸来显示字符串。

[fromCharCode()]从字符编码创建一个字符串。

[indexOf()]检索字符串。

[italics()]使用斜体显示字符串。

[lastIndexOf()]从后向前搜索字符串。

[link()]将字符串显示为链接。

[localeCompare()]用本地特定的顺序来比较两个字符串。

[match()]找到一个或多个正则表达式的匹配。

[replace()]替换与正则表达式匹配的子串。

[search()]检索与正则表达式相匹配的值。

[slice()]提取字符串的片断,并在新的字符串中返回被提取的部分。

[small()]使用小字号来显示字符串。

[split()]把字符串分割为字符串数组。

[strike()]使用删除线来显示字符串。

[sub()]把字符串显示为下标。

[substr()]从起始索引号提取字符串中指定数目的字符。

[substring()]提取字符串中两个指定的索引号之间的字符。

[sup()]把字符串显示为上标。

[toLocaleLowerCase()]把字符串转换为小写。

[toLocaleUpperCase()]把字符串转换为大写。

[toLowerCase()]把字符串转换为小写。

[toUpperCase()]把字符串转换为大写。

toSource()代表对象的源代码。

[toString()]返回字符串。

[valueOf()]返回某个字符串对象的原始值。

ES6对字符串的加强###

本来JavaScript的字符串功能已经很强大了,ES6又做了很多补充。
首先,对Unicode的支持大大增强,且提供了codePointAt
方法,能够正确处理4个字节储存的字符。

ES6为字符串添加了遍历器接口,使得字符串可以被for...of
循环遍历。

for (let codePoint of 'foo') { 
    console.log(codePoint)
}// "f"// "o"// "o"

传统上,JavaScript只有indexOf方法,可以用来确定一个字符串是否包含在另一个字符串中。ES6又提供了三种新方法。
includes():返回布尔值,表示是否找到了参数字符串。
startsWith():返回布尔值,表示参数字符串是否在源字符串的头部。
endsWith():返回布尔值,表示参数字符串是否在源字符串的尾部。

var s = 'Hello world!';
s.startsWith('Hello') // true
s.endsWith('!') // true
s.includes('o') // true

repeat方法返回一个新字符串,表示将原字符串重复n次。

'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"

ES7推出了字符串补全长度的功能。如果某个字符串不够指定长度,会在头部或尾部补全。padStart用于头部补全,padEnd用于尾部补全。

最强悍的地方,是模板的增强:
模板字符串(template string)是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。

// 字符串中嵌入变量
var name = "Bob",
time = "today";
Hello ${name}, how are you ${time}?

模板字符串中嵌入变量,需要将变量名写在{}之中。 function authorize(user, action) { if (!user.hasPrivilege(action)) { throw new Error( // 传统写法为 // 'User ' // + user.name // + ' is not authorized to do ' // + action // + '.' `User{user.name} is not authorized to do ${action}.`); }}

大括号内部可以放入任意的JavaScript表达式,可以进行运算,以及引用对象属性。

var x = 1;
var y = 2;
`${x} + ${y} = ${x + y}`// "1 + 2 = 3"

模板字符串之中还能调用函数。

function fn() { 
    return "Hello World";
}
`foo ${fn()} bar`// foo Hello World bar

模板字符串的功能,不仅仅是上面这些。它可以紧跟在一个函数名后面,该函数将被调用来处理这个模板字符串。这被称为“标签模板”功能(tagged template)。
标签模板其实不是模板,而是函数调用的一种特殊形式。“标签”指的就是函数,紧跟在后面的模板字符串就是它的参数。

var a = 5;
var b = 10;
tag`Hello ${ a + b } world ${ a * b }`;

上面代码中,模板字符串前面有一个标识名tag,它是一个函数。整个表达式的返回值,就是tag函数处理模板字符串后的返回值。

“标签模板”的一个重要应用,就是过滤HTML字符串,防止用户输入恶意内容。
标签模板的另一个应用,就是多语言转换(国际化处理)。

i18n`Welcome to ${siteName}, 
you are visitor number ${visitorNumber}!`// "欢迎访问xxx,您是第xxxx位访问者!"

模板字符串本身并不能取代Mustache之类的模板库,因为没有条件判断和循环处理功能,但是通过标签函数,你可以自己添加这些功能。
// 下面的hashTemplate函数// 是一个自定义的模板处理函数

var libraryHtml = hashTemplate
` <ul> #for book in ${myBooks} 
      <li><i>#{book.title}</i> by #{book.author}</li> #end
  </ul>`;

除此之外,你甚至可以使用标签模板,在JavaScript语言之中嵌入其他语言。

是不是无比强大了?

Swift数组

在Swift 语言中,数组是集合类型(Collection)的一种,用来存储复合数据。数组(Arrays)是有序数据的集。集合(Sets)是无序无重复数据的集。字典(Dictionaries)是无序的键值对的集。

Swift 语言中的Arrays、Sets和Dictionaries中存储的数据值类型必须明确(统一)。这意味着我们不能把不正确的数据类型插入其中(JavaScript在同一个数组中可以存放多种类型的元素),同时这也说明Swift更注重语言安全和效率。

数组使用有序列表存储同一类型的多个值。相同的值可以多次出现在一个数组的不同位置中。

数组的创建##

Swift中可以使用构造函数和字面量两种方法创建数组。
创建一个空数组
我们可以使用构造语法来创建一个由特定数据类型构成的空数组:

var someInts = [Int]()
print("someInts is of type [Int] with \(someInts.count) items.")
// 打印 "someInts is of type [Int] with 0 items."

注意,通过构造函数的类型,someInts的值类型被推断为[Int]。
或者,如果代码上下文中已经提供了类型信息,例如一个函数参数或者一个已经定义好类型的常量或者变量,我们可以使用空数组语句创建一个空数组,它的写法很简单:[](一对空方括号):

someInts = []   

可以用append方法来简单地给数组添加元素

someInts.append(3)
// someInts 现在包含一个 Int 值 

创建一个带有默认值的数组
Swift 中的Array类型还提供一个可以创建特定大小并且所有数据都被默认的构造方法。我们可以把准备加入新数组的数据项数量(count)和适当类型的初始值(repeatedValue)传入数组构造函数:

var threeDoubles = [Double](count: 3, repeatedValue:0.0)

// threeDoubles 是一种 [Double] 数组,等价于 [0.0, 0.0, 0.0]

这种创建大量相同值的方法别的语言还真没有。更强的是,还可以用类似字符串的“+”法来拼接两个数组创建新数组。我们可以使用加法操作符(+)来组合两种已存在的相同类型数组。新数组的数据类型会被从两个数组的数据类型中推断出来:

var anotherThreeDoubles = Array(count: 3, repeatedValue: 2.5)
// anotherThreeDoubles 被推断为 [Double],等价于 [2.5, 2.5, 2.5]

var sixDoubles = threeDoubles + anotherThreeDoubles
// sixDoubles 被推断为 [Double],等价于 [0.0, 0.0, 0.0, 2.5, 2.5, 2.5]

用字面量构造数组

我们可以使用字面量来进行数组构造,这是一种用一个或者多个数值构造数组的简单方法。字面量是一系列由逗号分割并由方括号包含的数值:

[value 1, value 2, value 3]。

下面这个例子创建了一个叫做shoppingList并且存储String的数组:

var shoppingList: [String] = ["Eggs", "Milk"]
// shoppingList 已经被构造并且拥有两个初始项。

shoppingList变量被声明为“字符串值类型的数组“,记作[String]。 因为这个数组被规定只有String一种数据结构,所以只有String类型可以在其中被存取。 在这里,shoppinglist数组由两个String值("Eggs" 和"Milk")构造,并且由字面量定义。

在这个例子中,字面量仅仅包含两个String值。匹配了该数组的变量声明(只能包含String的数组),所以这个字面量的分配过程可以作为用两个初始项来构造shoppinglist的一种方式。

由于 Swift 的类型推断机制,当我们用字面量构造只拥有相同类型值数组的时候,我们不必把数组的类型定义清楚。 shoppinglist的构造也可以这样写:

var shoppingList = ["Eggs", "Milk"]

因为所有字面量中的值都是相同的类型,Swift 可以推断出[String]是shoppinglist中变量的正确类型。

数组的下标##

Swift数组同样可以通过下标来访问和修改。
可以直接使用下标语法来获取数组中的数据项,把我们需要的数据项的索引值放在直接放在数组名称的方括号中:

var firstItem = shoppingList[0]
// 第一项是 "Eggs"

注意:
第一项在数组中的索引值是0而不是1。 Swift 中的数组索引总是从零开始。
我们也可以用下标来改变某个已有索引值对应的数据值:

shoppingList[0] = "Six eggs"
// 其中的第一项现在是 "Six eggs" 而不是 "Eggs"

还可以利用下标来一次改变一系列数据值,即使新数据和原有数据的数量是不一样的。下面的例子把"Chocolate Spread","Cheese",和"Butter"替换为"Bananas"和 "Apples":

shoppingList[4...6] = ["Bananas", "Apples"]
// shoppingList 现在有6项

注意:
不可以用下标访问的形式去在数组尾部添加新项。

数组的属性和方法##

Swift语言中数组同样有很多操作方法,这里只介绍主要的几个。
可以使用数组的只读属性count来获取数组中的数据项数量:

print("The shopping list contains \(shoppingList.count) items.")
// 输出 "The shopping list contains 2 items."(这个数组有2个项)

使用布尔值属性isEmpty作为检查count属性的值是否为 0 的捷径:

if shoppingList.isEmpty {
    print("The shopping list is empty.")
} else {
    print("The shopping list is not empty.")
}
// 打印 "The shopping list is not empty."(shoppinglist 不是空的)

也可以使用append(_:)方法在数组后面添加新的数据项:

shoppingList.append("Flour")
// shoppingList 现在有3个数据项,有人在摊煎饼

除此之外,使用加法赋值运算符(+=)也可以直接在数组后面添加一个或多个拥有相同类型的数据项:

shoppingList += ["Baking Powder"]
// shoppingList 现在有四项了
shoppingList += ["Chocolate Spread", "Cheese", "Butter"]
// shoppingList 现在有七项了

调用数组的insert(_:atIndex:)方法来在某个具体索引值之前添加数据项:

shoppingList.insert("Maple Syrup", atIndex: 0)
// shoppingList 现在有7项
// "Maple Syrup" 现在是这个列表中的第一项

这次insert(_:atIndex:)方法调用把值为"Maple Syrup"的新数据项插入列表的最开始位置,并且使用0作为索引值。

类似的我们可以使用removeAtIndex(_:)方法来移除数组中的某一项。这个方法把数组在特定索引值中存储的数据项移除并且返回这个被移除的数据项(我们不需要的时候就可以无视它):

let mapleSyrup = shoppingList.removeAtIndex(0)
// 索引值为0的数据项被移除
// shoppingList 现在只有6项,而且不包括 Maple Syrup
// mapleSyrup 常量的值等于被移除数据项的值 "Maple Syrup"

注意:
如果我们试着对索引越界的数据进行检索或者设置新值的操作,会引发一个运行期错误。我们可以使用索引值和数组的count属性进行比较来在使用某个索引之前先检验是否有效。除了当count等于 0 时(说明这是个空数组),最大索引值一直是count - 1,因为数组都是零起索引。

数据项被移除后数组中的空出项会被自动填补,所以现在索引值为0的数据项的值再次等于"Six eggs":

firstItem = shoppingList[0]
// firstItem 现在等于 "Six eggs"

如果我们只想把数组中的最后一项移除,可以使用removeLast()方法而不是removeAtIndex(_:)方法来避免我们需要获取数组的count属性。就像后者一样,前者也会返回被移除的数据项:

let apples = shoppingList.removeLast()
// 数组的最后一项被移除了
// shoppingList 现在只有5项,不包括 Apples

// apples 常量的值现在等于 "Apples" 字符串

数组的遍历

我们可以使用for-in循环来遍历所有数组中的数据项:

for item in shoppingList {
    print(item)
}
// Six eggs

// Milk
// Flour
// Baking Powder
// Bananas

如果我们同时需要每个数据项的值和索引值,可以使用enumerate()方法来进行数组遍历。enumerate()返回一个由每一个数据项索引值和数据值组成的元组。我们可以把这个元组分解成临时常量或者变量来进行遍历:

for (index, value) in shoppingList.enumerate() {
    print("Item \(String(index + 1)): \(value)")
}
// Item 1: Six eggs
// Item 2: Milk
// Item 3: Flour
// Item 4: Baking Powder
// Item 5: Bananas

更多关于for-in循环的介绍请参见for 循环。

Swift数组##

Swift中有内置的字符串(String)类型。String是例如"hello, world","albatross"这样的有序的Character(字符)类型的值的集合。通过String类型来表示。 一个String的内容可以用变量的方式读取,它包括一个Character值的集合。
创建和操作字符串的语法与 C 语言中字符串操作相似,轻量并且易读。 字符串连接操作只需要简单地通过+符号将两个字符串相连即可。与 Swift 中其他值一样,能否更改字符串的值,取决于其被定义为常量还是变量。你也可以在字符串内插过程中使用字符串插入常量、变量、字面量表达成更长的字符串,这样可以很容易的创建自定义的字符串值,进行展示、存储以及打印。
尽管语法简易,但String类型是一种快速、现代化的字符串实现。 每一个字符串都是由编码无关的 Unicode 字符组成,并支持访问字符的多种 Unicode 表示形式(representations)。

字符串是值类型(Strings Are Value Types)
Swift 的String类型是值类型。 如果您创建了一个新的字符串,那么当其进行常量、变量赋值操作,或在函数/方法中传递时,会进行值拷贝。 任何情况下,都会对已有字符串值创建新副本,并对该新副本进行传递或赋值操作。 值类型在 结构体和枚举是值类型 中进行了详细描述。

Swift 默认字符串拷贝的方式保证了在函数/方法中传递的是字符串的值。 很明显无论该值来自于哪里,都是您独自拥有的。 您可以确信传递的字符串不会被修改,除非你自己去修改它。

在实际编译时,Swift 编译器会优化字符串的使用,使实际的复制只发生在绝对必要的情况下,这意味着您将字符串作为值类型的同时可以获得极高的性能。

简易的创建和连接,方便的插值,对Unicode的强大支持……这些是Swift字符串的主要特点。

其他具体内容见《基础数据结构》部分。

推荐阅读更多精彩内容