深入理解C语言中使用频率较高的指针与数组
目录
- 定义
- 指针与二维数组
- 指针数组与数组指针
- 数组指针的应用
- 操作
- 总结
定义
指针:C语言中某种数据类型的数据存储的内存地址,例如:指向各种整型的指针或者指向某个结构体的指针。
数组:若干个相同C语言数据类型的元素在连续内存中储存的一种形态。
数组在编译时就已经被确定下来,而指针直到运行时才能被真正的确定到底指向何方。所以数组的这些身份(内存)一旦确定下来就不能轻易的改变了,它们(内存)会伴随数组一生。
而指针则有很多的选择,在其一生他可以选择不同的生活方式,比如一个字符指针可以指向单个字符同时也可代表多个字符等。
指针和数组在C语言中使用频率是很高的,在极个别情况下,数组和指针是“通用的”,比如数组名表示这个数组第一个数据的指针。
如下代码:
#include <stdio.h> char array[4] = {1, 2, 3, 4}; int main(void) { char * p; int i = 0; p = array; for (; i < 4; i++) { printf("*array = %d\n", *p++); } return (0); }
这里我们将数组名array作为数组第一个数据的指针赋值给p。但是不能写成*array++。准确来说数组名可以作为右值,不能作为左值(左值和右值的概念这里不再展开讲解)。
数组名的值其实是一个指针常量,这样我想你就明白了数组名为什么不能做为左值了。如果想用指针p访问array的下面2的数据,以下写法是合法的。
char data; /*第一种写法*/ p = array; data = p[2]; /*第二种写法*/ p = array; data = *(p+2); /*第三种写法*/ p = array +
指针与二维数组
先说一下二维数组,二维数组在概念上是二维的,有行和列,但在内存中所有的数组元素都是连续排列的,它们之间没有“缝隙”。以下面的二维数组 a 为例:
int a[3][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11} };
从概念上理解,a 的分布像一个矩阵:
- 0 1 2 3。
- 4 5 6 7。
- 8 9 10 11。
但是内存是连续的,没有这样的“矩阵内存”,所以二维数组a分布是连续的一块内存。
C语言允许把一个二维数组分解成多个一维数组来处理。对于数组 a,它可以分解成三个一维数组,即 a[0]、a[1]、a[2]。每一个一维数组又包含了 4 个元素,例如 a[0] 包含 a[0][0]、a[0][1]、a[0][2]、a[0][3]。那么定义如下指针如何理解呢?
int (*p)[4];
括号中的*表明 p 是一个指针,它指向一个数组,数组的类型为int [4],这正是 a 所包含的每个一维数组的类型。那么和下面定义有什么区别呢?
int *p[4];
这里就要先说明*和[]的优先级了,[]的优先级是高于*的,所以int *p[4];等同于int *(p[4]);。所以它是一个指针数组。这里很绕,总接下:
int (*p)[4];是数组指针,它指向二维数组中每个一维数组的类型,它指向的是一个数组。
int *p[4];是指针数组,它是一个数组,数组中每个数是指向int型的指针。
指针数组与数组指针
对于指针数组,说的已经很明确了,不再详细讲解,下面说一下数组指针。举例看一下:
#include <stdio.h> int main() { int a[3][4] = {{0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11}}; int(*p)[4]; p = a; printf("%d\n", sizeof(*(p + 1))); return (0); }
对于数组指针p如下:
那么printf("%d\n", sizeof(*(p + 1)));的结果就是16。如果想打印a[1][0]的值,代码如下:
printf("%d\n", *(*(p + 1)));
如果想打印a[1][1]的值,代码如下:
printf("%d\n", *(*(p + 1)+1));
这个代价自行体会,p是数组指针,它指向的是一个数组,所以对获取它指向的值,也就是*p,是指向一个数组还是一个值,指向a[0]。获取获取a[0][0],就需要写成**p。
对指针进行加法(减法)运算时,它前进(后退)的步长与它指向的数据类型有关,p 指向的数据类型是int [4],那么p+1就前进 4×4 = 16 个字节,p-1就后退 16 个字节,这正好是数组 a 所包含的每个一维数组的长度。也就是说,p+1会使得指针指向二维数组的下一行,p-1会使得指针指向数组的上一行。
最后再次捋一下数组指针和指针数组。
int *p1[4];是指针数组。
int (*p2)[4];是数组指针。
“[]”的优先级比“*”要高。
对于指针数组,p1先和“[]”结合,构成一个数组的定义,数组名为p1,int *修饰的是数组的内容,即数组的每个元素。那么它本质是一个数组,这个数组里有4个指向int类型数据的指针。
对于数组指针,“()”的优先级比“[]”高,“*”号和p2 构成一个指针的定义,指针变量名为p2,int 修饰的是数组的内容,即数组的每个元素。数组在这里并没有名字,是个匿名数组。那么它本质是一个指针,它指向一个包含4个int 类型数据的数组。
既然深入谈了指针数组和数组指针,就多聊一下。
#include <stdio.h> int main() { char a[5] = {'A', 'B', 'C', 'D'}; char(*p3)[5] = &a; char(*p4)[5] = a; return 0; }
上面代码是编译编译是报了waring的,报警如下:
注意:不同的编译器编译结果可能不同,我的编译方法请参考《使用vscode编译C语言》。p3 这个定义的“=”号两边的数据类型完全一致,而p4 这个定义的“=”号两边的数据类型就不一致了。左边的类型是指向整个数组的指针,右边的数据类型是指向单个字符的指针。所以才有了上面的警告。
但由于&a 和a 的值一样,而变量作为右值时编译器只是取变量的值,所以运行并没有什么问题。不过编译器仍然警告你别这么用。
再举一个栗子:
int vector[10]; int matrix[3][10]; int *vp,*vm; vp = vector; vm = matrix;
上面的代码第5行是错误的,因为vm是指向整型的指针,但是matrix不是指向正向的指针,他是指向整型数组的指针。下面是正确的写法:
int matrix[3][10]; int (*vm)[10]; vm = matrix;
数组指针的应用
上面说了那么多,可能大部分开发者用不到,数组指针在很多时候都是可以代替二维数组的,有些程序员喜欢用指针数组来代替多维数组,一个常见的用法就是处理字符串。
#include <stdio.h> char *Names[] = { "Bill", "Sam", "Jim", "Paul", "Charles", 0}; void main() { char **nm = Names; while (*nm != 0) printf("%s \n", *nm++); }
具体运行我就不讲解了,运行结果如下:
注意数组中的最后一个元素被初始化为0,while循环以次来判断是否到了数组末尾。具有零值的指针常常被用做循环数组的终止符。
这种零值的指针称为为空指针(NULL)。采用空指针作为终止符,在增删元素时就不必改动遍历数组的代码,因为此时数组仍然以空指针作为结束。
操作
写到这里想到一个“操作”,先看下面代码是否正确。
p[-1]=0;
初看这句代码,觉得奇怪,甚至觉得它就是错误,日常C语言开发基本有见到小标是负数的,但是仔细想想没有哪一本书说过下标能为负数的。看下面代码:
void main() { int data[4] = {0, 1, 2, 3}; int *p; p = data +2; printf("p[-1] is %d\n",p[-1]); printf("*(p-1) is %d\n",*(p-1)); }
运行结果如下:
不仅可以编译通过,还能正确的输出结果为1。这表明,C的下标引用和间接访问表达式是一样的。当然不鼓励这种操作,代码需要很强的可读性,而不是这样的操作,这里只是演示下标引用和简介表达式的关系。
总结
到此这篇关于C语言中使用频率较高的指针与数组的文章就介绍到这了,更多相关C语言指针与数组内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!