指针是一个值为地址的变量,即存储地址的变量,地址没有数据类型之说,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);
指向整型指针的指针和指向整型数组的指针不是一回事。首先整型数组并不是整型指针,只有在某些表达式中,可以作为数组名可以作为一个指针常量,而且二维数组作为函数传参只能省略第一维参数 。