千万不要忘了,当你把一个手指指向别人的时候,你手上的另外还有三个手指指向了你自己.... ---多疑间谍的格言
在笔记(7)里面我们也讲解了,多维数组和指针的一些知识和转换。这节内容我们将继续介绍数组与指针有关的知识。我们知道多维数组虽然看起来其存储结构是一张表,但是其实系统是决不允许程序按这种方式进行存储数据的。其单个元素的存储与引用都是以线性形式排列在内存中。如下图一所示:
图一
数组下标规则告诉我们如何计算左值array[i][j],首先找到array[i]的位置,然后根据偏移量[j]取得字符。因此,array[i][j]总是被编译器解释为:*(*(array+i)+j)。char *p[4]; //表示p是一个数组,数组里每个元素是一个指向char *的指针。这里我们主要对其进行了简化,指针实际上是声明为指向单个字符的。但是如果定义为指向字符的指针,就存在一种可能性,就是其他字符可能紧邻着它存储,隐式的形成了一个字符串。但是像下面这样的声明:
char (*p[4])[5];//这才是真正声明了一个指向字符串的指针数组,并且它限制了每个字符串的长度只能为5;
其结构图如下图二所示。
图二
这种数组必须用指向为字符串而分配的内存的指针进行初始化,可以在编译时用一个常量初始值,也可以在运行的时候用下面这样的代码进行初始化。for(i=0;i<4;i++) p[i]=malloc(5);或者是一次性的全部分配:
p=malloc(sizeof(char)*size_row*size_column);这样可以减少调用malloc的维护开销,但是缺点是当处理完一个字符串时无法单独将其释放。
具体区别如下图三和图四:
图三
图四
char p[50][256];但是很不幸,在这个字符串里只有一个字符串的长度达到了256,其他的长度都只有10个字节左右。如果我们经常这样做的话,内存的浪费将非常大。一种替代方法就是使用字符串指针数组,注意它的所有二级数组并不要求长度相同。
char *p[4];
如果根据需要为这些字符串分配内存,将会大大节省系统的资源,有些人把它称作为“锯齿状”数组是因为它右端的长度不一。
其结构图如下图五所示:
图五
具体实现代码如下:
#include <iostream> using namespace std; int main() { char *p[3]; char *p1="hello!"; char *p2="abc"; char *p3="This is my life!"; p[0]=p1; p[1]=p2; p[2]=p3; for(int i=0;i<3;i++) cout<<p[i]<<endl; system("pause"); return 0; }
运行结果如下:
注:有时候的数据共享和移动,只要可能,尽量不要拷贝整个字符串,拷贝一个指针比拷贝整个数组要快的多,而且还大大节省了内存空间。“数组名被改写成一个指针的参数”规则并不是递归定义的。数组的数组会改写为“数组的指针”,而不是“指针的指针”;
具体规则如下图六所示:
图六
你之所以能在main()函数中看到char **argv这样的参数,是因为argv是个指针数组(即char *argv[]).这个表达式被编译器改写为指向数组第一个元素的指针,也就是一个指向指针的指针。如果argv参数实事上被声明为一个数组的数组(也就是char argv[4][5]),它将被编译器翻译为char (*)[5](一个数组指针),而不是指针的指针char **argv。具体数组与指针的改写部分代码如下:
#include <iostream> using namespace std; void my_function1(int array[2][3][5]) { cout<<"1、It's OK"<<endl; } void my_function2(int (*array)[3][5]) { cout<<"2、It's OK"<<endl; } void my_function3(int array[][3][5]) { cout<<"3、It's OK"<<endl; } int main() { int arr[2][3][5]; my_function1(arr); my_function2(arr); my_function3(arr); int (*arr1)[3][5]=arr; my_function1(arr1); my_function2(arr1); my_function3(arr1); int (*arr2)[2][3][5]=&arr; my_function1(*arr2); my_function2(*arr2); my_function3(*arr2); system("pause"); return 0; }运行结果如下: 四、C语言中,怎么确保向函数传递一个多维的数组?
void my_function2(int array[][3][5]){}我们可以按照下列的方式调用:
int arr[100][3][5]; my_function(arr); //OK int arr[10][3][5]; my_function(arr); //OK但是像下面的就不行:
int arr[100][33][24]; my_function(arr); //ERROR int arr[10][4][6]; my_function(arr); //ERROR这将无法通过编译器。
方法二:void my_function3(int array[][5])。
这也就是方法一中提到的省略最左边第一维的数据。这样的做法表示每一行都必须正好是5个整数的长度。函数也可以类似的声明:
void my_function3(int array(*p)[5])其中的括号是必须要要的,确保它是一个指向5个int类型的数组指针,而不是一个5个int *类型元素的指针。
var arr:array[1..10] of integer; precedure function(a:array[1..15] of integer; function(arr);//无法通过编译,但是C语言中可以。
方法三:放弃传递二维数组。也就是说创建一个一维数组,数组中的元素是指向其他东西的指针。
回想一下前面介绍的main函数里,我们已经习惯了char *argv[]参数的形式,有时候我们也能看见,char **argv的形式。所以我们可以简单地传递一个指向数组参数的第一个元素的指针,如下:
void my_function3(int **array);注意:只有把二维数组改为一个指向向量的指针数组的前提下才可以这样做。
因为数组的存储都是连续的线性的特点,所以我们可以将多维数组降维处理。因为 C语言是数组的数组,所以我们可以将多维数组降为一维数组,这样我们处理起来就比较方便了!符合我们的习惯!
毕竟函数中数组的传递总是会被编译器将其翻译为指针,这样我们正好可以利用这一特性,有些时候可以简化多维数组的操作,(并不代表全部)。