指针和数组的关系

指针是一个值为地址的变量,即存储地址的变量,地址没有数据类型之说,char *,int *都是一样的长度,跟机器有关。

int *a表示a地址处存储的值为整型。

指针的初始化

int a = 10;
int *b = &a;

 或者

1 int a = 10;
2 int *b;
3 b = &a;

指针的解引用

通过*b,可以对指针b进行解引用(间接访问)从而访问得到a的值。在指针解引用之前需要对它进行检查,判断b是否为NULL

指针常量和常量指针

int *p表示指向整型的指针,也叫整型指针,int const *p和int * const p两个变量则不同,根据const和*的顺序,int const *p中表示常量指针,int * const p表示指针常量,其中常量指针也可写作const int *p。

常量指针(与整型指针类比),表示指针指向的值是常量,不可修改。指针常量,表示指针是常量,地址不可修改。

1 int a,b;
2 
3 int * const p = &a; //指针常量
4 *p = 9; //操作成功
5 p = &b; //操作错误
6 
7 int const * m = &a; //常量指针
8 *m = 9; //操作错误
9 m = &b; //操作成功

指向常量的指针常量该怎么写? 

 1 const int * const b = &a;//指向常量的指针常量 

 

数组

在讨论一维和二维数组之前,先讨论一下数组首地址和首元素地址虽然首元素地址和首地址在数值上是相同的,但是它们所表示的意义却不相同:

(1)数组的首元素地址:表示数组的首个元素的地址。

(2)数组的首地址:表示整个数组的地址。

只有使用“&数组名”时,才是取数组首地址;数组名或者&数组名[0]都是取得数组首元素地址,另外,首地址+1得到的是跳过整个数组的地址,首元素地址+1得到的是下一个元素的地址。

1.一维数组

int b[10],其中数组名b的值表示一个指针常量,是第一个元素的地址,该地址不可修改,但所指向的值可以修改。

当数组名在表达式中使用时,编译器为它产生一个指针常量,除了两种情况数组名不用指针常量来表示:sizeof和&。sizeof返回整个数组的长度(以字节为单位),而不是指向数组的指针的长度。&取一个数组名的地址所产生的是一个指向数组的指针,而不是一个指向指针常量的指针。

1 int a[10];
2 int *c;
3 
4 c = &a[0]; //第一个元素的地址
5 c = a; //c指向数组第一个元素
6 
7 a = c; //错误,数组名是常量指针,该值不可修改
8 *a = 1;//指向的值可以修改

(1)一维数组下标访问和指针间接访问

声明一个数组a,a和&a[0]表示同一个地址,对数组中的值进行访问时,*a和a[0]都表示第一个元素,*(a+n)和a[n]同。

 1 int a[10] = {0,1,2,3,4,5,6,7,8,9};
 2 
 3 int *b = a+1; //b指向a[1]
 4 //int *b = &a[1];
 5 
 6 //从b的角度访问
 7 printf("%d", *b++);
 8 printf("%d", b[0]);
 9 printf("%d", b[-1]);
10 
11 
12 //从a的角度访问
13 printf("%d", a[n]);
14 printf("%d", *(a+n));

上述代码中3、4两条语句表示的意思相同,表示b指向a[1]。C的下标引用和间接访问表达式是一样的,其中b[0]表示a[1],b[-1]表示a[0].

另外注意数组的间接访问形式,a数组名是指针常量不可以用如下方式访问:

1 printf("%d", *a++);
2 
3 a+=n;
4 printf("%d", *a);

(2)一维数组进行函数传参

一般的函数传参分为传值和传址,传值意味着对实参没有影响,仅仅是对拷贝进行操作;传址意味着可以访问实参指针所指向的值,也可以对其进行操作。

数组传参比较有趣的地方在于既是传址也是传值。传址是传递数组的地址或首元素的地址,可以通过该指针对数组进行间接访问,也修改数组值;传值是因为,对形参指针的操作并不影响实参指针。

 1 #include <stdio.h>
 2 #include<stdlib.h>
 3 
 4 void mytest_(int *a){
 5     a++;
 6     printf("指针形参sizeof(a) = %d\n", sizeof(a));
 7 }
 8 
 9 void mytest(int a[]){
10     *a = 100;
11     printf("数组形参sizeof(a) = %d\n", sizeof(a));
12 }
13 
14 int main(){
15     int a[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
16     printf("实参*a = %d\n", *a);
17     printf("数组sizeof(a) = %d\n", sizeof(a));
18     
19     mytest_(a);
20     printf("实参*a = %d\n", *a);
21     
22     mytest(a);
23     printf("实参*a = %d\n", *a);
24     system("pause");
25     return 0;
26 }

输出如下结果:

另外在前面C语言操作符提到过sizeof的操作数为数组名时,返回整个数组的长度,这个仅限于非传参函数的数组,如代码中main函数第一次sizeof所示。若数组作为参数进行传参,即使形参是数组形式,在函数中依然表示为指针,因此更加准确的参数形式应该为指针而非数组名,此时sizeof表示的是指向整型的指针的长度,而不是数组的长度。

(3)字符数组和字符串常量初始化

1 char a[] = "HELLO";
2 char *b = "HELLO";

字符数组初始化,可以写明元素个数,也可不写(自动计算数组的长度),但进行字符串处理时必须写明元素个数。对字符数组进行初始化,看上去与字符串常量相同,a为字符数组,字符串常量b表示指向字符的指针。

2.二维数组

 如果数组的维数不止一个,则为多维数组,这里以二维数组为例。

 1 int a[6][10]; 

a表示二维数组,实质上可以说是一维数组的一维数组,包含了6个元素的数组,每个元素是包含了10个元素的数组。

a这个名字的值是指向它第一个元素的指针,所以a是一个指向10个整型元素的数组的指针,又称数组指针

 1 char a[2][2] = {1, 2, 3, 4};
 2 
 3 
 4 //第二行第一个元素的地址 5 printf("%d\n", *(a + 1));
 6 printf("%d\n", a[1]);
 7 printf("%d\n", &a[1][0]);
 8 
 9 //第二行第一个元素
10 printf("%d\n", **(a + 1));
11 printf("%d\n", *a[1]);
12 printf("%d\n", a[1][0]);
13 
14 //第二行第二个元素地址
15 printf("%d\n", *(a + 1)+1);
16 printf("%d\n", a[1]+1);
17 printf("%d\n", &a[1][1]);
18 
19 //第二行第二个元素
20 printf("%d\n", *(*(a + 1)+1));
21 printf("%d\n", *(a[1]+1));
22 printf("%d\n", a[1][1]);

(1)访问地址

其中a是第一行元素的地址,*a和a[0]相同,表示第一行第一个元素的地址,&a[0][0]表示第一行第一个元素的地址,因此a=&a[0]=&(&a[0][0]),a表示地址的地址

a+n表示第n+1行元素地址,*(a+n)和a[n]相同,表示第n+1行第一个元素的地址,&a[n][0]表示第n+1行第一个元素的地址,因此a+n=&a[n]=&(&a[n][0])。

*(a+n)+n和a[n]+n表示第n+1行第n+1个元素地址

(2)访问元素

从上面地址中可以看出,访问第n+1行第n+1个元素,可以用a[n][n]和*(*(a+n)+n)

(3)二维数组进行函数传参

二维数组int a[2][3]中a是一个指向整型数组的指针,又名数组指针。

int a[2][2];
int(*p)[2] = a;

 多维数组进行函数传参时,下面两种形式任选一种即可

void func(int (*p)[2]);
void func(int p[][2]);

  二维数组传参时编译器必须知道第2个维度的长度才能对各下标进行求值(下标引用和间接访问一样),第1个维度的长度并不需要,因此可以写成指针的形式。但不能将其声明为

 1 void func(int **p); 

指向整型指针的指针和指向整型数组的指针不是一回事。首先整型数组并不是整型指针,只有在某些表达式中,可以作为数组名可以作为一个指针常量,而且二维数组作为函数传参只能省略第一维参数 。

posted @ 2018-12-13 09:16  两猿社  阅读(1766)  评论(1编辑  收藏  举报