关于函数(二)数组指针和指针数组

来源:互联网 发布:andreja pejic 知乎 编辑:程序博客网 时间:2024/05/22 02:29

参考http://blog.csdn.net/touch_2011/article/details/6966980

1、概述

  • 指针数组:指针数组可以说成是”指针的数组”,首先这个变量是一个数组,其次,”指针”修饰这个数组,意思是说这个数组的所有元素都是指针类型,在32位系统中,指针占四个字节。
  • 数组指针:数组指针可以说成是”数组的指针”,首先这个变量是一个指针,其次,”数组”修饰这个指针,意思是说这个指针存放着一个数组的首地址,或者说这个指针指向一个数组的首地址。
    根据上面的解释,可以了解到指针数组和数组指针的区别,因为二者根本就是种类型的变量。

2、数组指针(指向数组的指针)

(1)数组在内存中的表示

创建一个数组就是在内存里面开辟一块连续的空间,比如int a[4];就是在内存里面开辟了一个大小为4*sizeof(int)字节的内存空间。二维数组是特殊的一维数组。
先来看一段代码:

void main()  {      int a[2][2]={1,2,3,4};//这是一个2*2的二维数组      int (*p)[2];//数组指针      p=a;//令p指向数组a  }

注意到代码中这句话:int (*p)[2];这里的p是一个数组指针变量。
a中各个元素在内存中是这样存放的:
指针

(2)理解数组名和数组指针变量

现在我们思考a,a[0],a[1],p,a+1,a[0]+1,p+1到底是什么?
指针
分析:
1. a是一个数组名,类型是指向一维数组的指针,不是变量,a的值是指针常量,即不能有a++或者a=p这些操作。a指向这块连续空间的首地址,值是&a[0][0]。
2. a[0]是一维数组名,类型是指向整型的指针,值是&a[0][0],这个值是一个常量。
3. a[1]是一维数组名,类型是指向整型的指针,值是&a[1][0],这个值是一个常量。
4. p是一个数组指针变量,指向一维数组的指针变量,值是&a[0][0]。可以执行p++;p=a等操作。
5. a+1表示指向下一行元素,也可以理解为指向下一个一维数组。
6. *(a+1)是取出第一行的首地址。
7. a[0]+1是指向第0行第1个元素,也可以理解为指向一维数组a[0]的第一个元素。
8. p+1同a+1
9. *(p+1)*(a+1)
10. 虽然a跟a[0]值是一样,但类型不一样,表示的意义不一样。通过分析就不难理解为什么((a+i)+j)和a[i][j]等效了。

       既然pa是一个指针,存放一个数组的地址,那么在我们定义一个数组时,数组名称就是这个数组的首地址,那么这二者有什么区别和联系呢?

int  a[2];

a是一个长度为2的整型数组,a是这个数组的首元素首地址。既然a是地址,p是指向数组的指针,那么能将a赋值给p吗?答案是不行的!因为a是数组首元素首地址,p存放的却是数组首地址,a是int*类型,a+1,a的值会实实在在的加1,而p是int[2]类型的,p+1,p则会加2,虽然数组的首地址和首元素首地址的值相同,但是两者操作不同,所以类型不匹配不能直接赋值,但是可以这样:p = &a,p相当与二维数组的行指针,现在它指向a[2]的地址。

(3)指针是数组的迭代器

#include<stdio.h>  #define M 2  #define N 3  int main()  {      int a[M][N]={1,2,3,4,5,6};      int *start=&a[0][0];      int * const end=start+M*N;      for(;start!=end;start++)          printf("%-5d",*start);      putchar('\n');      return 0;  }

       理解这段代码,用指针遍历一个二维数组,是不是很像C++标准库里面vector的迭代器。注意这里只用了一个for循环,这也可以说明二维数组其实就是特殊的一维数组。

(4)数组名与数组指针变量的区别

       从(2)中的分析中得出数组名是指针,类型是指向元素类型的指针,但值是指针常量,声明数组时编译器会为声明所指定的元素数量保留内存空间。数组指针是指向数组的指针,声明指针变量时编译器只为指针本身保留内存空间。

看看这个代码:

#include<stdio.h>  void main()  {      int a[2][2]={1,2,3,4};//这是一个2*2的二维数组      int (*p)[2];//数组指针      p=a;//令p指向数组a      printf("%d\n%d\n",sizeof a,sizeof p);  }

结果
       注意当sizeof用于变量时返回这个变量占用的实际空间的大小。当sizeof用于数组名时,返回整个数组的大小(这里的大小指占用的字节数)。p是一个指针变量,这个变量占用四个字节。而a是数组名,所以sizeof a返回数组a中的全部元素占用的字节数。了解了sizeof,看下面这段代码输出什么

#include<stdio.h>  void main()  {      int a[2][2]={1,2,3,4};//这是一个2*2的二维数组      int (*p)[2];//数组指针      p=a;//令p指向数组a      printf("%d\n%d\n",sizeof(a+1),sizeof(p+1));      printf("%d\n%d\n",sizeof(a+0),sizeof(p+0));  }

结果
从结果中看出,a在做+运算时是转化成了指针变量,此时a+i的类型是一个指针变量,而不是一个数组名。但a[i]是一个一维数组的数组名,sizeof(a[0])的值是8。

现在再来看一段代码:

#include<stdio.h>  void f(int a[][2])  {      printf("%d\n",sizeof a);  }  void main()  {      int a[2][2]={1,2,3,4};//这是一个2*2的二维数组      printf("%d\n",sizeof a);      f(a);  }

结果
       解释:这是因为传参的时候数组名转化成指针变量,注意到函数f中f(int a[][2])这里并不需要指定二维数组的长度,此处可以改为int (*a)[2]。所以传过来的就是一个数组指针变量。

       总结: 数组名的类型是指向元素类型的指针,值是指针常量。(a+1)的类型是一个指针变量。把数组名作为参数传递的时候实际上传递的是一个指针变量。sizeof对变量和数组名操作时返回的结果会不一样。数组指针是指向数组的指针,其值可以是变量。

2、指针数组(存放指针的数组)

(1)认识指针数组

       一个存放int类型的数组称为整型数组,那么存放指针的数组就叫指针数组。

#include<stdio.h>  void main()  {      int i=1,j=2;      //p先跟[]结合,然后再跟*结合      int *p[2];//指针数组,存放指针的数组      p[0]=&i;      p[1]=&j;      printf("%d",sizeof(p));  }

调试
此例数组p就两个元素,p[0]是指向i的指针,p[1]是指向j的指针。这两个指针都是int型指针,所以p是存放int型指针的数组。sizeof(p)返回数组占用的总空间,所以程序输出是8。

(2)深入了解

       首先先定义一个指针数组,既然是数组,名字就叫arr。

char *arr[4] = {"hello", "world", "shannxi", "xian"};//arr就是我定义的一个指针数组,它有四个元素,每个元素是一个char *类型的指针,这些指针存放着其对应字符串的首地址。

       当一个变量出现左右都出现一个运算符时,没有记住运算符优先级的人就会纠结arr变量到底跟哪一个运算符先结合。如果是自己定义一个指针数组,搞不清楚运算符的优先级,那么就加上小括号(),比如定义一个指针数组,可以写成char *(arr[4]),不过在定义之前一定要清楚自己定义的变量,如果目的是一个数组,那就把arr[4]括起来,如果是一个指针,就把*arr括起来。如果是看到一段这样的代码,可以从他的初始化来分别它是数组还是指针,很明显,我这定义的是一个数组,如果是指针,会用NULL来初始化。
       这个指针数组有多大呢?答案是16个字节,因为它是一个指针数组。每当出现这些问题时,脑子里一定要第一时间反应出内存映像图。
内存映像图
       这里最左侧一列是一个很简陋但能说明意思的内存图,一般情况下,从栈区到代码区,是从高地址到低地址。栈向下增长,堆向上增长。

       arr[4]是一个在主函数定义的数组。把它对应到对应到内存中,arr是一个在栈区,有四个元素的数组,而每一个数组又是一个指针,所以说它的四个元素各占四个字节,所以变量arr的大小是16个字节。

       初始化arr的{“hello”, “world”, “shannxi”, “xian”};的这四个存在在内存中,只是跟arr这个变量不在同一段空间,它们被分配在只读数据区,数组arr[4]的四个指针元素,分别存放着这四个字符串的首地址,想象一下,从栈区有四只无形的手指向数据区的空间。arr+1会跳过四个字节,。也就是一个指针的大小
这就相当与定义char *p1 = “hello”,char *p1 = “world”,char *p3 = “shannxi”, char *p4 = “xian”,这是四个指针,每个指针存放一个字符串首地址,然后用arr[4]这个数组分别存放这四个指针,就形成了指针数组。

(3)指针数组排序

排序
指针数组的排序非常有趣,因为这个数组中存放的是指针,通过比较指针指向的字符串的字典序,排序这些指针。函数实现如下:

void sort(char **pa, int n)//冒泡排序{    int i, j;    char *tmp = NULL;    for(i = 0; i < n-1; i++){        for(j = 0; j < n-1-i; j++){            if(strcmp(*(pa+j), *(pa+j+1)) > 0){                tmp = *(pa + j);                *(pa + j) = *(pa + j + 1);                *(pa + j + 1) = tmp;            }        }    }}

3、声明返回数组指针的函数

1、数组不能被拷贝,函数不能返回数组,只能返回数组的指针或者引用。

typedet int arr[10]; //arr是类型别名,表示的类型含有10个整数的数组  using arr=int[10];    //与上面等效
int arr[10];   //arr是一个含有10个整数的数组  int *p1[10];  //p1和[ ]的结合度要高,所以p1首先是一个10个大小的数组,即这里的p是一个数组名,其次是与*结合,表示数组中每个元素都是整型指针  int (*p)[10]=&arr;  //由于括号p首先与*结合,即p是一个指针,然后才与[ ]结合,说明p指针指向大小为10的一个数组,且p指针初始化,p是一个数组指针 

2、声明返回数组的指针

①返回函数指针的函数形式如下:

Type(*function(parameter_list))[dimension] int (*func(int i))[10];
  • func(int i) 意味着调用func函数时需要一个int类型的实参
  • (*func(int)) 意味着对函数调用的结果是一个指针
  • (*func(int i))[10] 意味着对函数调用结果的指针是一个指向数组,数组大小为10
  • int (*func(int))[10] 意味着对函数调用结果的指针指向的数组中,数组元素都是整型的。

但如果我们使用类型别名,声明一个返回数组指针的函数的函数就看起来要优雅多了

using arrT=int[10]; //arr是大小为10的数组的一个别名,即相当于把int[10]看成是一种类型,该类型是一个整型数组,大小为10  arrT* func(int i);  

3、使用尾置返回类型

       任何函数的定义都能使用尾置返回,它在形参列表后以一个 -> 符号开头,为表示函数真正的返回类型在形参列表之后,我们在最前放置一个auto,比如:

auto func(int i)->int(*)[10];  //func接受一个int类型的实参,返回一个指针,指针指向10个整数的数组 

于是现在就可以清楚地看到func函数返回的是一个指针,该指针指向10个整数的数组。

4、使用decltype

       如果知道函数返回的指针指向哪个数组还可以使用decltype关键字声明返回类型,比如一个典型的例子:

#include<iostream>  int odd[] = { 1, 3, 5, 7, 9 };  int even[] = { 0, 2, 4, 6, 8 };  decltype(odd)* func(int i)  {      return (i & 1) ? &odd : &even;  }  int main()  {      std::cout << (*func(1))[2]<< std::endl;      std::cout << (*func(2))[2] << std::endl;  }  

结果
deltype的作用只是负责推导出表达式的类型,并不负责将数组类型转换成对应的指针,在这里是推导出odd的类型是一个大小为5的数组,但只是数组这种类型,要让成为一个指向这个类型的的指针,和正常一样加上*即可。

阅读全文
0 0
原创粉丝点击