Chinaunix首页 | 论坛 | 博客
  • 博客访问: 403158
  • 博文数量: 96
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 415
  • 用 户 组: 普通用户
  • 注册时间: 2015-05-22 09:08
个人简介

最近的研究方向:Nginx

文章分类
文章存档

2017年(2)

2016年(59)

2015年(35)

我的朋友

分类: C/C++

2015-10-24 14:29:21

原文:http://www.cnblogs.com/bugman/archive/2011/09/25/2190389.html



0.数组和指针并不是相同的

我们声明数组时,同时分配了一些内存空间,用于容纳数组元素,但是当我们声明一个指针时,只分配了用于容纳指针本身的内存空间。

从这个方面也可以理解sizeof后面跟数组名和指针名的不同。

什么时候数组和指针相同呢?

c语言标准对此做了如下说明

规则1.表达式中的数组名被编译器当作一个指向该数组的一个元素的指针

规则2.下标总是与指针和偏移量相同

规则3.在函数参数的声明中(形式参数),数组名被编译器当作指向该数组第一个元素的指针

1.数组名是一个常量指针,并不是一个左值

复制代码
1 #include 2 int main(int argc,char **argv) 3 { 4 int array[]={1,2,3,4}; 5 array++; 6 return 0; 7 }
复制代码

test.c|5| error: lvalue required as increment operand

数组名不是常量指针的情况只有两种,就是当数组名是sizeof和&的操作数时,前者产生整个数组的占用的字节数,后者产生一个指向数组的指针

2.下标引用和间接操作是一样的

复制代码
 1 #include  2 int main(int argc,char **argv)  3 {  4 int array[]={1,2,3,4};  5 int *b=array+1;  6 printf("%d\n",b[1]);  7 printf("%d\n",*(b+1));  8 printf("%d\n",b[-1]);  9 printf("%d\n",b[10]); 10 printf("%d\n",1[b]); 11 return 0; 12 }
复制代码


输出

3 3 1 32595 3

这个例子说明了几个很有意思的事实,b是指针,但是b还是可以使用下标操作符,c在处理下标操作符时把b[1]看成*(b+1)

这也是为什么1[b]是合法的原因,1[b]被看成了*(1+b),在编译器看来b[1]和1[b]并没有区别

并且c语言不进行下标检查,这是基于相信coder的设计思想,并且检查下标要消耗一定的资源

3.当要传递一个数组是形参可以是两种形式

int strlen(char *string); int strlen(char string[]);
//甚至是下面的方法都可以
int strlen(char string[100]);//里面的数字随意
复制代码
void print1(int *a)
{
	int i;
	for(i=0;i < SIZE;i++)
		printf("%d\n",a[i]);
}
void print2(int a[])
{
	int i;
	for(i=0;i< 5;i++)
		printf("%d\n",a[i]);
} 
//数组的下标使用变量是可以的
调用时,实参只能是一个指针
int a[]={1,2,3,4,5};
print1(a);
print2(a);
print2(&a[1]);//这样传过来就是数组的一部分。
实参即使不是真的数组都是合法的
int b=10;
print2(&b);
实际上传递的就是指针
复制代码

这个相等行暗示指针和数组名是相等的,但千万不要被它糊弄了,这个声明确实相等,但是只存在于当前的这个上下文环境中。

3.sizeof 的不同

1 char *a = "hello"; 2 char b[]= {'h','e','l','l','o'}; 3 //sizeof(a)是指针占的内存地址大小,所有类型的指针的大小都一样,是系统的字长,32位系统就是4个字节,64位系统就是8个字节 4 //sizeof(b)是数组b占的字节数,这里是5个char加上一个‘\0',是6个字节
 
上面是博主原文:但是经过我的实测, 
//sizeof(b)是数组b占的字节数,是5个字节
只有这样赋值时
char b[] = {"hello"};
结果才为6.原因就不用我说了
 


注意指针在这里的用法,只能对字符串常量采用char *a = "hello";

int *a = {1,2,3}; //error: (twice)excess element in scalar initializer, initialization makes pointer from interger without a cast 
 
char *b = {'h','e','l','l','o'};//error:(four times)excess element in scalar initializer, initialization makes pointer from interger without a cast 
 
char *c = 'h'; //error: initialization makes pointer form interger without a cast


char *d = "h"; //correct char *e ="hello" //correct

4.由第三条引出了第四条

先讲一下历史

由于 char message[]={'h','e','l','l','o'};这种初始化字符数组的方式用于比较长的字符数组显得比较傻,c语言标准提出了一种快速方法用于初始化字符数组

char message[]=“hello"; 尽管它看上去有点像字符串常量,但是它并不是,它只是前例的初始化列表的一种写法。

一个字符串常量在用于初始化一个字符数组时,它是一个初始化列表,在其它任何情况下,他都是一个字符串常量

注意区分

char message1[] = "hello"; //message1是个数组,message1也是一个常量指针,不能改变message1的指向,"hello"是数组的初始化列表,可以通过下标或者间接操作来改变值 
 
char *message2 = "hello"; //message2指向字符串常量,message2是一个变量指针,可以改变message2的指向。不能通过下标或者间接操作来改变值。如果硬要改变的话是编译通过运行出错
 
其实这2个可以通过存储类来理解,因为
char message1[] = "hello"; 是给一个数组赋值,所以message1本身就存储在数据区或栈上,而message1为数组名,所以肯定不能改变的,而数组的元素是可以改变的。
char *message2 = "hello";
是给一个指向字符类型的指针赋值,所以“hello”本身是存储在代码段的,所以不能改变,message2存储的是hello字符串存储在代码段的首地址,所以是可以改变的。 

5.指向数组的指针

int matrix[3][10];

matrix可以看作成是一个一维数组,它包含3个元素,每个元素是包含10个整形元素的数组(这个是在人看来的,对计算机而言matrix就是个指针,计算机没有数组的概念,它只懂得指针,数组和下标提出来只不过是方便人理解,这是我到目前为止的看法)。(如同df -h)

matrix:一个常量指针,指向它的第一个元素,所以matrix是一个指向包含10个整形元素的数组的指针

matrix+1:仍然是一个指针,在matrix的地址的基础上向后移了4*10=40个字节。但是这里不是matrix[1],maxtrix[1]相当于*(matrix+1),再次强调下标和间接操作是一样的。

matrix[0]和 *matrix:maxtrix是一个指向数组的指针,那么*maxtrix就是一个数组,包含(*maxtrix)[0]到(*matrix)[9]这么10个元素,由于数组名是第一个元素的地址,所以*matrix相当于&(*matrix)[0]也就是&matrix[0][0],这是一个指向整数的指针。

*(*(matrix+1)+1):有了前面的基础,这个就相当简单了,matrix[1][1]。

但是很有意思的是matrix和matrix[0]的值竟然是一样的,都是&matrix[0][0],换句话说&matrix[0]和matrix[0]是一样的(这块我暂时不能解释,以后解决了再说)

点击(此处)折叠或打开

  1. #include<stdio.h>
  2.  int main(int argc,char **argv)
  3.  {
  4.      int matrix[3][10]={
  5.          {1,2,3,4,5,6,7,8,9,10},
  6.          {10,9,8,7,6,5,4,3,2,1},
  7.          {5,4,3,2,1,6,7,8,9,10}
  8.      };
  9.      printf("%p\t%p\n",matrix,matrix[0]);
  10.      printf("%p\t%p\n",matrix+1,matrix[1]);
  11.      printf("%p\t%p\n",matrix+2,matrix[2]);
  12.      return 0;
  13. }



//结果


 0x7ffff4609f50  0x7ffff4609f50
 0x7ffff4609f78  0x7ffff4609f78
 0x7ffff4609fa0  0x7ffff4609fa0

我们发现下面的都比上面的多出4*10=40个字节的地址

那么指向数组的指针怎么表示呢


int (*p)[10]=maxtrix;

看一下怎么使用

复制代码
int a[10]; int (*pa)[10] = &a;
由于对于数组来说&a和a的值是一样的,所以也可以写成
int(*pa)[10]=a;//但是这样写在gcc上会得到一个warning,所以还是不要这样写的好
值虽然一样但是类型不一样,这点很重要。
那么int *pa=a;和int (*pa)[10]=&a;有什么区别呢
这两个pa的值虽然是一样的,但是类型不一样,还是这句话,第一个pa指向的是一个整数,第二个pa指向的是一个数组,第一个++pa的步长是4,第二个++pa的步长是40
 
		

点击(此处)折叠或打开

  1. int a1[3];
  2. int a2[2][3];
  3. int (*ap)[3];
  4. ap=&a1;
  5. ap=a2;
用法也不一样

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

int (*pa)[10]=&a;

int *pb=a;

printf("%p\t%p\t%p\t%p\n",pa,pb,a,&a);
printf("%d\t%d\t%d\n",(*pa)[1],pb[1],pa[1]);//很奇怪,其实声明已经很清楚了(*pa)[1]这样才得到一个int,pa[1]编译器显示是int *类型
/*
细想,原因就在与(*pa)[1],会被编译器解释成*(*(pa+0)+1),所以访问的是个二维数组中的元素,而pa[1]会被编译器解释成*(pa+1);可以通过打印2个量的地址来验证正确性。
*/
printf("%p\t%p\t%p\n",&(*pa)[1],&pb[1],&pa[1]);

//打印出来pa[1]会是乱码,因为已经越界访问了,而&pa[1]会比pa的地址多40字节。
输出:

    0xbff086b0 0xbff086b0 0xbff086b0 0xbff086b0
    2 2 -1074755880
    0xbff086b4 0xbff086b4 0xbff086d8
复制代码结果也确实如此。

这个声明看起来比我们见到过的所有声明都复杂,但事实上并不是很难。你只要假定它是一个表达式,并对它求值。下标引用的优先级高于间接访问,但由于括号的存在,首先执行的还是间接访问,所以p是一个指针,那么它指向什么呢?接下来是下标引用,所以它指向的是某种类型的数组(包含10个元素),这个表达式没有更多的操作,所以p是一个指向整形数组的指针。

那么上面matrix的类型是 int (*)[10],而matrix[0]的类型是int *

这里讲一句题外话,数组不能整体赋值给另外一个数组,只能一个一个元素赋值。

6.指针数组

注意和上面的指向数组的指针区别

int *p[10]

下标引用的优先级高于间接访问,首先执行下标引用,因此p是某种类型的数组(它包含的元素数为10)。在取得一个值以后,进行了间接访问操作,这个表达式不再有其它操作,得到的是一个int型。总结一下:对数组的某个元素进行间接操作后得到了一个int型,所以p是一个数组,它包含的元素是指向整型的指针。

点击(此处)折叠或打开

  1. #include
  2. int main(void)
  3. {
  4.     const char *keyword_table[5]={
  5.         "do",
  6.         "while",
  7.         "if",
  8.         "else",
  9.         "switch"
  10.     };
  11.     printf("%p\t%p\t%p\t%p\t%p\t%p\t%p\t%c\n",&keyword_table,keyword_table,&keyword_table[0],&"do","do",keyword_table[0],&keyword_table[0][0],*keyword_table[0]);
  12.     return 0;
  13. }


输出:
0x7fff46c5f790  0x7fff46c5f790  0x7fff46c5f790  0x4006ac        0x4006ac        0x4006ac        0x4006ac        d


阅读(1716) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~