在程序设计中,为了处理方便, 把具有相同类型的若干元素按无序的形式无间隔地存储在一起,这些无序排列的同类数据元素的集合称为数组。组成数组的各个变量称为数组的分量,也称为数组的元素,有时也称为下标变量。用于区分数组的各个元素的数字编号称为下标。
数据存储的一个原则就是“存得进去,取得出来,并且要考虑操作效率”。数组元素的数组因为存储在一起,且元素类型相同,元素长度相同,这样便可以按下标随机访问各元素。
除了用数组来批量存储数据,还可以使用链表来批量存储数据,链表不要求所有数据元素集中存储在一起,数据元素使用结构体,包括数据域和指针域,使用指针域来确定下一个元素的存储位置,这样,链表所有的元素便可以链在一起。
数组只能在定义时初始化,定义后不能再次初始化,也不能将一个数组批量赋值给另一个数组。
但是,您可以使用下标,并将值单独分配给任何数组的元素。
初始化数组时,提供的值可以少于数组元素。因此,很容易将数组的所有元素初始化为零:
long totals[111] = {0};
指针和数组紧密相连。如果ar是数组名,则表达式ar[i]解释为*(ar+i),数组名解释为数组第一个元素的地址。因此,数组名与指针具有相同的作用。反过来,您可以使用带有数组表示法的指针名称来访问由new分配的数组中的元素。
C要求该大小为常量整数表达式。c99/c11允许您使用非常量整数表达式;在这种情况下,数组被称为可变长度数组。
1 二维数组
二维数组代表一组一维数组。例如,声明如下5个一维数组的数组:
double sale[5][12];
sales数组包含五个元素,每个元素都是一个12个double元素的数组。这些一维数组中的第一个可以称为sales[0],第二个可以称为sales[1]等等,每个数组都是12个double元素的数组。使用第二个索引访问这些数组中的特定元素。例如,sales[2][5]是sales[2]的第6个元素,
二维数组就可以用一个一维的指针数组来表示(数组元素的指针指向一个一维数组),数组元素也可以按二维数组来表示。sales[2]是sales的第3个一维数组元素。
因为二维数组是数组的数组,所以数组名称相当于是一个二级指针常量。
定义二维数组时,若按一维格式初始化,则第一维的长度可以省略,此时,系统可根据初始化列表中值的个数及第二维的长度计算出省略的第一维长度,但无论如何,第二维的长度不能省略。没有初始化时,第一维和第二维的长度都不能省略。
有初始化时,只有第一维的长度可以省略,由初始化成员的数量和其他维的长度来推断。
2 字符数组与字符串
字符串是用字符数组来存储的。为什么传递一个数组需要两个参数(数组名和数组长度),而传递字符串只要一个参数(字符数组名)?数组传递通常需要两个参数:数组名和元素个数。字符串在C++中是存储在一个字符类型的数组中,所以传递一个字符串也是传递一个数组。但由于C++规定每个字符串必须以’\0’结束,所以传递字符串时就不需要指出元素个数了。(数组在作为参数时退化为指针,也就没有数组长度的消息)
C语言的字符串必须以'\0'结尾,否则有意想不到的错误,如求长度时,就是以此为标志去计算的,但不包括'\0'。
c语言中的字符串就是存储在字符数组中,且在数组末尾包含一个表示字符串末尾的空字符\0,这样该数组的内容就构成了一个字符串。所以严格意义上来说,字符数组不一定是字符串,而字符串一定是数组。
另外,下面表达式中字符串常量都是由编译器在构造字符数组时,在数组末尾添加一个空字符\0:
char *cp; cp = "abcdef"; char carr[] = "abcdef";
C语言中的字符串是以空字符(\0)结尾的字符数组,char类型数组。可以用字符串字面量(以双括号""界定)来赋值给字符数组或字符指针。区别在于数组名是常量,不能被更新,如不能做左值,使用++或+=运算符,而字符指针是变量。但其指向的却是一个常量,相当于const char* cp = "abcdef";
因为上式的右值存储在常量区,并能返回个指针常量。
不能把一个数组赋给另一个数组(结构体允许这种集合操作),所以要通过循环把数组中的每个元素赋给另一个数组相应的元素。有一个例外的情况是:使用strcpy()和strncpy函数来处理字符数组。mencpy()和menmove()函数提供类似的方法处理任意类型的数组。下面是这两个函数的原型:
void *memcpy(void * restrict s1, const void * restrict s2 size_t n); void *memmove(void *s1, const void *s2, size_t n);
数组可以初始化,但不能整体赋值,如
char a[] = "hello";
但不能:
char a[11]; a = "hello";
可以这样操作:
strcpy(a,"hello");
数组的下标相当于就是指针的算术运算和编移。
指针的算术运算可以实现指针的移动。
采用a[i]这种形式访问数组,编译器总会把其“改写”成像*(a+i)这种指针访问。
char *p; char a[11];
指针p的地址待定,而数组a的地址是以a开头;
char *p = "hi"; char a[] = "hi";
指针p指向一个常量字符串 "hi",此常量字符串存储于只读数据区,其首地址赋值给指针p, 无法单独更新其元素,如以下操作会出错:
p[1] = 'o'; //出错,不能单独更改常量元素
数组a[]是一数组,存储于栈区,并初始化为3个字符的数组存放 "hi", "hi"的元素可以修改。
a[1] = 'o'; //正确
所以说,数组名是指针常量,但其指向的内容可以改变。
而字符字符初始化一个字符串时,字符串存放到了只读数据区,变成了常量的,其内容不可以改变。
3 数组作为函数参数
要处理数组,函数必须知道数组在哪里,以及数组有多少个元素。数组地址提供“Where”;“What many”要么必须内置到函数中,要么作为单独的参数传递。第二种方法更为通用,因此相同的函数可以用于不同大小的数组。
因为数组传递本质上只是传递了数组的起始地址(数组退化成了指针,失去了元素个数信息),数组中的元素个数需要另一个变量来指出。数组做为参数时虽然退化成了指针,但使用sizeof()来衡量数组大小时,仍然是整个数组占用空间的大小,所以通常使用以下表达式来计算数组长度:
int len = sizeof(arr)/sizeof(arr[0]);
也可以将整个数组传递给函数,这时实际参数用的是数组名。数组传递的实质是传递地址。把实际参数中的数组首地址作为形式参数中的数组的首地址数组在函数中的定义:函数原型应该体现数组参数是一个数组,所以用无数组大小定义的方括号表示数组。你可能希望规定数组的大小,但在C语言中并不检查数组的界限,所以在函数中也没必要知道数组的大小。数组大小只在数组定义中明确(为了申请内存量)。
数组传递本质上传递的是数组的起始地址,真正的元素个数是通过另一个参数表示,因此形式参数中不需要说明数组的大小。
函数使用指向字符串首字符的指针来表示待处理的字符串。通常,对应的实际参数是数组名、指针变量或用双括号括起来的字符串。无论是哪种情况,传递的都是首字符的地址。一般而言,没必要传递字符串的长度,因为函数可以通过末尾的空字符确定字符串的结束。
导致数组引用“退化”为指针的规则只适用于数组,而结构却是一级对象,当你提到结构的时候,你得到的是整个结构。
4 动态数组
在C中,允许在程序运行时根据实际情况申请一片连续的内存空间。由于是连续的内存空间,所以在使用时与数组类似。
使用这种手段申请的内存空间位于程序内存中的堆区,所以也叫堆内存空间。
如果想求若干个数的平均数,并不想事先确定(在程序编写时事先确定)若干是多少?这样你就可以在程序运行时临时决定来申请若干堆内存变量。
int n, *arr; cin>>n; arr = new int[n];
5 变长数组
对于传统的C数组,要求size是整型常量表达式,但c99/c11允许使用整型非常量表达式,这种情况下的数组被称为变长数组。
c99的变长数组是指允许使用变量表示数组的维度,但一量创建数组,其大小就能再改变了。所谓的变,只是在创建数组时可以使用变量指定数组的维度,创建以后就能再变了。
8 向量vector
向量是一种能够实现动态增长的类似于数组、并封闭了若干方法的的模板类。
简单地说,vector是一个能够存放任意类型的动态数组,能够增加和压缩数据。 [1] [2]
简单的使用方法如下:
vector<int>test;//建立一个vector test.push_back(1); test.push_back(2);//把1和2压入vector这样test[0]就是1,test[1]就是2
我们可以用一个迭代器:
vector<int>::iterator iter=test.begin();//定义一个可以迭代int型vector的迭代器iter,它指向test的首位 for(;iter!=test.end();iter++) //iter++指的是向后迭代一位,直到iter到超出末端迭代器为止 cout<<(*iter);//输出迭代器指向的值
我们也可以使用at访问:
vector<int>test;//建立一个vector test.push_back(1); test.push_back(2);//把1和2压入vector这样test[0]就是1,test[1]就是2 int i =test.at(1);//i为2
9 数组指针与指针数组
数组指针是指一个指针,指向一个数组,指针数组是指一个数组,一个元素是指针的数组。
#include <stdio.h> #include <stdlib.h> //注意指针数组和数组指针分别是如何指向二维数组的 main() { static int m[3][4]={ {1,2,3,4}, {5,6,7,8}, {9,10,11,12}}; int (*p)[4];//数组指针 p是指针,指向一维数组,每个一维数组有4个int元素*p是个数组的地址,**p就是数组元素了 int i,j; int *q[3];//指针数组 q是数组,数组元素是指针,3个int指针 p=m; //p是指针,可以直接指向二维数组 printf("--通过数组指针、双重循环、行i列j、*(*(p+i)+j))输出元素:--\n"); for(i=0;i<3;i++) { for(j=0;j<4;j++) { printf("*(*(p+%d)+%d)=%2d ",i,j,*(*(p+i)+j)); } printf("\n"); } printf("注:p+i相当于第i行的首地址;\n"); printf("&m[0][0]=%x\n",&m[0][0]); printf("&m[1][0]=%x\n",&m[1][0]); printf("p+1=%x\n",p+1); printf("*(p+1)=%x\n",*(p+1)); printf("\n"); printf("--通过数组指针、循环行i、*(*p+j)输出元素:--\n"); for(i=0;i<3;i++,p++)//p可看成是行指针 { printf("**p:%x=%2d ",p,**p);//每一行的第一个元素 printf("*(*p+1):%x=%2d ",*p+1,*(*p+1));//每一行的第二个元素 printf("*(*p+2):%x=%2d ",*p+2,*(*p+2));//每一行的第三个元素 printf("*(*p+3):%x=%2d ",*p+3,*(*p+3));//每一行的第四个元素 printf("\n"); } printf("\n"); printf("--指针数组*q[3],输出元素q[i][j]或*(q[i]+j)--\n"); for(i=0;i<3;i++) q[i]=m[i];//q是数组,元素q[i]是指针,m[i]是行的一维数组; for(i=0;i<3;i++) { for(j=0;j<4;j++) { printf("q[%d][%d]=%2d ",i,j,q[i][j]);//q[i][j]可换成*(q[i]+j) } printf("\n"); } printf("\n"); q[0]=m[0]; for(i=0;i<3;i++) { for(j=0;j<4;j++) { printf("*(q[0]+%d+4*%d)=%2d ",j,i,*(q[0]+j+4*i)); } printf("\n"); } system("pause"); }
输出:
10 说在最后
头文件<string.h>或<cstring>包含了一系列处理C风格的字符串的函数,可以直接include来使用。
相对于C风格的字符串,将字符串封装为类,有更高的安全性,为此,C++提供了类库<string>,关于类库<string>及如何自定义,见后面的文章。
11 数组使用实例(给出日期,得出是星期几)
#include "stdio.h" #include "stdlib.h" void main() { int s; int y; int m; int d; int arr[12] = {31,28,31,30,31,30,31,31,30,31,30,31}; int n = 0; int week; printf("输入日期,如:2019 5 29\n"); scanf("%d%d%D",&y,&m,&d); if(y % 4 == 0 && y%100!=0 || y%400==0) arr[1]=29; for( int i = 0; i < m-1; i = i + 1 ) { n = n + arr[i]; } n = n+d; s = y -1 + int((y-1)/4) - int((y-1)/100) + int((y-1)/400) + n; week = s % 7; printf("公元%d年%d月%d日是星期%d\n",y,m,d,week); system("pause"); } /* 输入日期,如:2019 5 29 2019 5 29 公元2019年5月29日是星期3 */
-End-
本文暂时没有评论,来添加一个吧(●'◡'●)