- 在C语言中,指针算是最难理解的部分之一,同时也是最重要的部分之一。数组也是最重要的部分之一。本文主要是对指针的简单理解和通过指针和数组的关系来深入理解数组。
1.初识指针
-
指针是什么
指针也是一个变量,只不过这个变量里面存放的不是具体的值,而是内存单元的地址(在内存中每个单元都有一个特定的编号,用来唯一标识一块内存单元,方便我们找到,该编号又叫地址。),我们可以通过内存地址来找到具体的值。就好比指针是一本特殊的户口本(里面写着所有人的家庭住址),而人是具体的值,我们可以通过查阅该本特殊的户口本找到某人的家庭住址,然后再根据住址找到某人。
指针的大小是固定的,无论它里面存放的地址找到的值是什么类型的,指针的大小在32位平台下是4个字节,在64位平台下是8个字节。 -
理解和操作一级指针
(1)一级指针的定义
指针是一个变量,故其使用前也要定义。
int * p;//定义一个指针变量p,而p中存放的是int类型的变量的内存地址。
char * p;//定义一个指针变量p,而p中存放的是char类型的变量的内存地址。
int * p1, * p2, * p3…;//当定义多个个指针变量时,应该这样定义。
(2)一级指针的初始化
int * p = NULL;//一般用NULL或空指针常量来初始化。
int i = 5;
int * p1 = &i;//&为取地址符,指针中存放的是变量的地址。存放的是i的地址。
char c = ‘c’;
char * p2 = &c;//初始化话一定要用地址来初始化。存放的是c的内存单元地址。
(3)一级指针的赋值
int i = 5;
int * p;
p = &i;//给指针p赋值,p中存放的是i的内存单元地址。
*p = 7;// * p为给指针p解引用,就是给p存放的内存地址赋值,即此时i值为7。
int a = 5;
int b = 10;
int * p1 = &a;//指针p1中存放a的内存地址。即p1指向a。
int * p2 = &b;//指针p2中存放b的内存地址。即p2指向b。
p1 = p2;//将p1指向了p2指向的,即此时p1和p2都指向b。
(4)一级指针的类型
int *p1 = NULL;//p1指向的内存中存放的值是int类型的。
char *p2 = NULL;//p2指向的内存中存放的值是char类型的。
short *p3 = NULL;//p3指向的内存中存放的值是short类型的。
float *p4 = NULL;//p4指向的内存中存放的值是float类型的。
double *p5 = NULL;//p5指向的内存中存放的值是double类型的。
long *p6 = NULL; //p6指向的内存中存放的值是long类型的。 -
指针的类型决定了指针加1是向前走几步。
#include <stdio.h>
int main()
{
int i = 0;
char c = 'c';
int *p1 = &i;
char *p2 = &c;
printf("p1 = %p, p1 + 1 = %p\n", p1, p1 + 1);
printf("p2 = %p, p2 + 1 = %p\n", p2, p2 + 1);
return 0;
}
- 由上面代码的结果可以看出,p1+1时加的是4个字节即一个int的大小,p2+1时加的是1个字节即一个char的大小。
- 故不难得到结论:指针加减n就是加减n个指针内存放的内存地址存放的变量类型的大小。
2.指针和数组的关系
(1)数组名是一个指针
- 数组名仅仅是一个标识数组的名字吗?------答案当然不是。
#include <stdio.h>
int main()
{
int arr[5] = {0, 1, 2, 3, 4};
printf("数组首元素地址:%p\n", &arr[0]);
printf("arr = %p\n", arr);
return 0;
}
- 根据上述代码运行的结果我们发现,对数组名以地址的形式输出和对数组的首元素取地址结果是一样的。
- 其实数组名可以看成是一个指针。它指向的是整个数组,里面存放的是数组的首元素地址。
- 数组名内存放的是数组首元素的地址,代表的是整个数组。
- 所以数组长度的计算方式是:sizeof(数组名),而计算数组的大小是数组长度/数组中每个元素的字节数。故我们在C语言之简单理解数组中用公式sizeof(arr) / sizeof(arr[0]) 或者 sizeof(arr) / sizeof(数组类型)来求数组的大小。
(2)通过数组名来访问数组
- 根据上面我们可以知道数组名内存着数组首元素的地址,是一个指针。又因为数组是内存中一块连续的空间,所以我们就可以根据指针的算术运算来访问一个数组。
#include <stdio.h>
int main()
{
int arr[5] = { 0, 1, 2, 3, 4 };
int size = sizeof(arr) / sizeof(int);
//或者int size = sizeof(arr) / sizeof(arr[0]);
for (int i = 0; i < size; ++i)
{
printf("(arr + %d) = %p, &arr[%d] = %p ", i, arr + i, i, &arr[i]);
printf("arr[%d] = %d\n", i, *(arr + i));
}
return 0;
}
- 通过上述代码运行的结果我们发现,(arr + i)其实就是数组中下标为i的元素的地址。
(3)数组的传参
- 在C语言中,当数组作为参数传给一个函数时,会退化成一个指针。
- 数组的传参就是将数组的地址传到函数里。
- 一维数组的地址就是首元素的地址,二维数组的地址是首行的地址。
Ⅰ. 一维数组的传参
- 一维数组的传参有以下几种方式:
#include <stdio.h>
void sheena1(int arr[])
{}
void sheena2(int arr[5])
{}
void sheena3(int *arr)
{}
int main()
{
int arr[5] = { 1, 2, 3, 4, 5 };
sheena1(arr);
sheena2(arr);
sheena3(arr);
return 0;
}
Ⅱ. 二维数组的传参
- 二维数组的传参有以下几种方式:
#include <stdio.h>
void sheena1(int arr[3][2])
{}
void sheena2(int arr[][2])
{}
void sheena3(int (*arr)[2])//数组指针
{}
int main()
{
int arr[3][2] = { 1, 2, 3, 4, 5, 6 };
sheena1(arr);
sheena2(arr);
sheena3(arr);
return 0;
}
- 二维数组传参的时候可以省略行大小,但是不可以省略列大小。
- 数组指针会在C语言之深入理解指针(进阶)中谈到。
3.指针的操作
- 指针的运算(算术运算和关系运算)
(1)指针加减一个整数
该种情况我们在上面指针的类型中已经提到,指针加减一个整数实际就是加减整数倍的指针存放的内存地址中存放的值的类型的大小。下面通过一个例子再次感受下:
#include <stdio.h>
int main()
{
int a = 10;
char b = 'c';
int *pi = &a;
char *pc = &b;
printf("pi = %p, pi + 2 = %p\n", pi, pi + 2);
printf("pc = %p, pc + 5 = %p\n", pc, pc + 5);
return 0;
}
- 由上面代码的结果可以看出,当pi指向的类型是int类型的值时,pi + 2,其实是加2乘4等于 8 个字节的大小(int的大小为4个字节)。当pc指向的类型是char类型的值时, pc + 5,其实是加5乘1等于 5 个字节的大小(char的大小为1个字节)。
(2)指针减指针
- 当一个指针减去一个指针,其实就是计算在内存中两个地址之间相距的大小。
- 注:两个指针之间不可以进行加法运算。
#include <stdio.h>
int main()
{
int a = 1, b = 2;
int *pa = &a, *pb = &b;
printf("&a - &b = %d\n", &a - &b);
printf("pa - pb = %d\n", pa - pb);
return 0;
}
(4)指针的关系运算
- 在指向同一数组元素的指针之间可以进行“> 、 < 、 <= 、>=”比较。
- 注:C语言标准中规定了,允许指向数组元素的指针与指向数组的最后一个元素后面的那个指针相比较,但不允许和指向数组第一个元素之前的那个指针比较。
4.二级指针
- 前面我们说了指针也是变量,有变量在内存中就有地址,那么指针的地址存放在哪里呢?---------二级指针。
- 二级指针中存放的是一级指针的内存单元地址。
- 二级指针——顾名思义,一级指针是存某种数据的地址,而二级指针就是指向指针的指针,即其指针变量存放的是一个指针的地址。一级指针类型为(类型) *(指针变量)(其中类型就是指针变量中存放的地址所属的数据的类型)。所以二级指针的一般类型为(类型) **(指针变量)(其中类型就是指针变量中存放的地址所属的指针的的类型)
- int i = 0;
int *pi = &i;//指针pi中存放i的内存地址。即p1指向i。
int **ppi = π //二级指针ppi存放的是指针pi的内存地址。即ppi指向指针pi。 - 如下面例子所示
#include <stdio.h>
int main()
{
int a = 5;
int *pa = &a;
int **ppa = &pa;
printf("&a = %p, pa = %p, *ppa = %p\n", &a, pa, *ppa);
printf("&pa = %p, ppa = %p\n", &pa, ppa);
printf("a = %d, *pa = %d, **ppa = %d\n", a, *pa, **ppa);
return 0;
}
- 由上述代码我们可知
①pa中存放的是a的地址,ppa中存放的pa的地址。
②故pa按照地址类型输出的是a的地址,而因为ppa中存放的是pa的地址,故对ppa进行一次解引用就是找到了pa,*ppa就是pa,故一次解引用按照地址类型输出的是a的地址。
③因为pa中存放的是a的地址,故对pa解引用就是找到了a,*pa就是a。而根据②的分析我们知道ppa的第一次解引用后就是pa,所以对ppa进行两次解引用后就是a。