C语言指针引用数组案例讲解
前言:C语言中指针玩的是什么,是内存,要想学好指针的小伙伴们要先对数据在内存中是怎么玩的做一番了解~
当在程序中定义一个变量时,系统会根据其数据类型为其开辟内存空间,例如Visual C++为整型变量分配四个字节的空间,为单精度浮点型变量分配四个字节,为字符型变量分配一个字节,内存中每个字节都有自己独立且唯一的一个编号,这就是地址 ,如下图,系统为变量i分配了2000~2004的存储单元。
_访问变量的方式_有如下图两种:
第一种直接访问方式,直接通过变量名访问,变量名与地址有一一对应关系,因此按此地址直接对变量i的存储单元进行访问;
第二种间接访问方式,先通过i_pointer找到i的地址的位置,再通过i的所存地址的位置找到i的地址2000,随后对变量i进行存取操作。间接访问的方式就要用到指针,所谓指针(2000)即为一个变量的地址,指针变量(i_pointer)是存储这个地址的用来指向另一个对象的变量。
关键字 | 变量类型 |
---|---|
int | 整型变量 |
char | 字符变量 |
类型名* | 指针变量 |
它们之间的关系为:指针变量的值是指针,指针是变量i的地址,变量i存放所需要存放的存储内容。
图片的中*为取值运算符,*i_pointer表示对i_pointer中存放的地址进行取值,相当于 变量i。
指针的定义:
基类型 *变量名
例:int *p
char *p
float p
注意 : 此时的与上文中提到的取值运算符并不是一个概念,此时的
*意思是定义一个变量,这个变量是指针变量。
指针的引用:
对指针进行赋值:
以下面程序为例:
int *p;
int a = 3;
p = &a;
*p = 2;
p = &a(&为取地址符,意思是取变量a的地址赋给指针变量P)
*p = 2(p上文中已经提到是对指针变量P中存储的地址进行取值p相当于变量a,对
*p进行赋值即相当于对变量a进行赋值)
指针变量做函数参数
以定义两个变量a和b,使其值进行交换为例进行阐述
#include<stdio.h> //值传递 void swap1(int x, int y) { int z; z = x; x = y; y = z; } //地址传递 void swap2(int *p1, int *p2) { int t = *p1; *p1 = *p2; *p2 = t; } /* 错误,指针变量t所指向的内容不可预见,对*t赋值就是向一个未知存储单元赋值 ,可能操纵到有用信息, 破坏系统的正常工作状态 ,这种指针叫做**野指针**; 那么如何解决野指针危险性呢: 可以对该指针进行初始化,使其指向NULL,NULL为地址为0的内存地址,在大多数操作系统上,该内存为操作系统保留, 用户不可操控 */ //void swap3(int *p1, int *p2) { // int *t; // *t = *p1; // p1 = *p2; //报错 // p2 = *t; //} /* C语言中实参变量与形参变量之间的数据传递是单向的“值传递”方式。 不能通过操作形参中指针变量的值企图改变实参中指针变量的值,但是可以通过形参接收到的实参传过来的地址 对指针变量指向的值进行操作。 */ void swap4(int *p1, int *p2) { int *t = NULL; t = p1; p1 = p2; p2 = t; } int main() { int a, b; scanf("%d %d", &a, &b); swap1(a, b); printf("%d %d\n", a, b); int *p1 = &a, *p2 = &b; swap2(p1, p2); printf("%d %d\n", a, b); swap4(p1, p2); printf("%d %d\n", *p1, *p2); //注:在swap2()函数中,a b的值发生了交换 return 0; } /* 运行结果: 1 3 1 3 3 1 3 1 */
指针指向数组
盆友们一定要记住这两句话再往下看***!!!***
首地址:一段内存空间的第一个存储单元,而不是第一个字节;
指针变量的加减:以指针指向的类型空间为单元进行偏移;
以定义一个数组,输入数值,最后输出数组中所有元素为例进行阐述
/* 须知: 1.单目运算符优先级比双目运算符高,同级下从右往左结合 2.*(p + i)等价于*(a+i)等价于a[i] 3.数组名为数组的首地址,即p = a相当于p = &a[0] 4.p + 1指向数组中的下一个元素,加上的不是简单的字节数,而是定义的指针的基类型的字节数 5.a[i]的[]为变址运算符,下标法中对下标的处理是转换成地址的,也是按照a + i计算地址,然后找出此地址单元中的值 6.利用p++的指针自加操作,指针直接指向元素,不必每次都计算地址,比下标法和计算指针地址后再取值的方法快 7.a是一个类型为int *的指针常量,指向数组首个元素的地址,不能企图使用a++的方式便利数组中的元素 8.指针的加减运算往往作用在同一数组下的元素上,虽然指针变量可以指向数组元素以后的存储单元,但是得到的数据往往是 不被我们所期待的数据,这种操作是毫无意义的 */ #include<stdio.h> int main() { int a[5]; //下标法 for(int i = 0; i < 5; i++) scanf("%d", &a[i]); //等价于scanf("%d", a + i); int *p, *p1; p = a; //等价于p = &a[0] p1 = &a[0]; for(int i = 0; i < 5; i++) printf("%d ", a[i]); printf("\n"); //指针法 /* p:a[0]的地址 *p:a[0]的值 *(p+1):a[1]的值 *(p+2):a[2]的值 p的值并未改变 test: #include<stdio.h> int main() { int a[5] = {1, 2, 3, 4, 5}; int *p = a; printf("%d\n", p); for(int i = 0; i < 5; i++) { printf("%d ", *(p + i)); } printf("\n%d\n", p); for(int i = 0; i < 5; i++) { printf("%d ", *p++); } printf("\n%d\n", p); } /* 运行结果: 6618608 1 2 3 4 5 6618608 6618628 */ */ for(int i = 0; i < 5; i++) printf("%d ", *(p + i)); printf("\n"); //a为指针常量不能改变,变的是 a + i本身 for(int i = 0; i < 5; i++) printf("%d ", *(a + i)); printf("\n"); //指针p在不断移动,因此在学习的过程中要时刻注意指针的位置!!! for(int p = a; p < (a + 5); p++) printf("%d ", *p); return 0; } /* 补充: 1.由于在C语言编程系统中,对下标的处理是转换成地址的,因此, p[i]即被处理成*(p + i)
数组名做函数参数
/* 须知: 1.数组名为数组首元素的地址,实参传递a传递给形参的是地址,形参需要用一个指针变量来接实参的地址 2.C编译都是将形参数组名作为指针变量来处理的,也就是说,程序中形参的a[]的a实际上是一个指针,对a[]的操控实际上 就是对指向实参中的数组进行操控 3.上文中已经提到p[i]与*(p+i)无条件等价 */ #include<stdio.h> void swap(int a[], int n) { int h = 0, t = n - 1, m = (n - 1) / 2; for(h; h <= m; h++) { int tmp = a[h]; a[h] = a[t]; //根据须知3: a[h]等价于*(a+h) a[t] = tmp; t--; } } void swap1(int *x, int n) { int *p, *i, *j; i = x; j = x + n - 1; p = x + (n - 1) / 2; for( ; i <= p; i++, j--) { int tmp = *i; *i = *j; *j = tmp; } } int main() { int a[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; for(int i = 0; i < 10; i++) printf("%d ", a[i]); printf("\n"); swap(a, 10); for(int i = 0; i < 10; i++) printf("%d ", a[i]); printf("\n"); swap1(a, 10); for(int i = 0; i < 10; i++) printf("%d ", a[i]); printf("\n"); return 0; } /* 运行结果: 0 1 2 3 4 5 6 7 8 9 9 8 7 6 5 4 3 2 1 0 0 1 2 3 4 5 6 7 8 9 */
指针指向 二维数组
a[3][4]数组的结构:
三个一维数组
四个int类型的元素
**
再次强调:
1.指针变量的加减:以指针指向的类型空间为单元进行偏移;
2.数组名代表数组元素的首地址
a是二维数组名,指向的第一个存储单元是a[0]这个一维数组,a的类型是指向一维数组的指针常量, a+1即偏移一个一维数组;
a[0],a[1], a[2]是一维数组名,代表一维数组中的元素的首地址,也就是说a[0]的值是&a[0][0],a[1]的值是&a[1][0],a[2]的值是&a[2][0]。 a[0],a[1], a[2]分别指向的第一个存储单元是a[0][0], a[1][0], a[2][0]这几个元素,它们的类型是指向元素的指针常量,a[0]+1即偏移一个元素;
为了让大家看清除,博主以表格形式展示出来:
首地址 | 指向 | 类型 | 移动一位 | 移动字节数 |
---|---|---|---|---|
二维数组的首地址a | a[0]这个一维数组 | int(*)[4] | a+1 | 16B |
以为数组的首地址a[0] | a[0][0]元素 | int* | a[0]+1 | 4B |
指针指向二维数组的各种表现形式
#include<stdio.h> int main() { int a[3][4] = {1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23}; /* a是指向行的指针常量,代表二维数组的首地址,即首行的首地址, a + 1:序号为 1的行的首地址 a + 2:序号为 2的行的首地址 在行指针前面加一个 * ,则转换成列指针(列地址),指向一维数组的首地址 ; *a:对第0行的地址进行取值,由于a并不是一个变量的存储单元,取值后得到一维数组的地址a[0]<->*(a+0)<->*a,指向列地址,即第0行一维数组的首地址 *(a + 1): 第1行一维数组的首地址 *(a + 2): 第2行一维数组的首地址 由于a[0] = *a, a[1] = *(a + 1), a[2] = *(a + 2),所以两种形式等价 a[0]: 代表0行一维数组的首地址,指向0行0列元素地址 a[1]: 代表1行一维数组的首地址,指向1行0列元素地址 a[2]: 代表2行一维数组的首地址,指向2行0列元素地址 a[0] + 0: 代表0行0列元素的地址 a[0] + 1: 代表0行1列元素的地址 a[0] + 2: 代表0行2列元素的地址 *(a[0] + 0): 0行0列元素的值,a[0][0],*(*(a + 0) + 0) *(a[0] + 1): 0行1列元素的值,a[0][1], *(*(a + 0) + 1) *(a[0] + 2): 0行2列元素的值,a[0][2], *(*(a + 0) + 2) 在列指针前面加一个 & , 则转换成行指针(行地址),行指针前面加上一个 &,则转换成指向整个数组的指针(表示为整个数组的地址),指向二维数组的首地址。 &a: 指向整个二维数组 &a[0]: 指向第0行的一维数组 &a[0][0]: 指向0行0列的元素,即指向a[0][0], 是a[0][0]这个元素的地址 */ printf("%d %d\n", a, *a); //a:表示0行首地址 *a:表示0行0列元素的地址 二者的值一样,但是指针类型不同 printf("%d %d\n", a[0], *(a + 0)); printf("%d %d\n", &a[0], &a[0][0]); printf("%d %d\n", a[1], a + 1); printf("%d %d\n", &a[1][0], *(a + 1) + 0); printf("%d %d\n", a[2], *(a + 2)); printf("%d %d\n", &a[2], a + 2); printf("%d %d\n", a[1][0], *(*(a + 1) + 0)); printf("%d %d\n", *a[2], *(*(a + 2) + 0)); return 0; } /* 运行结果: 6618608 6618608 6618608 6618608 6618608 6618608 6618624 6618624 6618624 6618624 6618640 6618640 6618640 6618640 9 9 17 17 */
指向二维数组的指针变量
#include<stdio.h> int main() { int a[3][4] = {1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23}; /* 指向数组元素的指针变量 */ int *p; for(p = a[0]; p < a[0] + 12; p++) { printf("%2d ", *p); } printf("\n\n"); /* 指向一维数组的指针变量 */ int (*q)[4] = a; //指针变量p指向4个整形元素的一维数组 for(int i = 0; i < 3; i++) { for(int j = 0; j < 4; j++) { printf("%2d ", *(*(q + i) + j)); } } return 0; }
用指向数组的指针做函数参数
#include<stdio.h> //指向变量的指针变量 void avg(float *p, int n) { float sum = 0, ans = 0; float *p1 = p + 11; for(p; p <= p1; p++) sum = sum + *p; ans = sum / n; printf("%.2f\n", ans); } //指向一维数组的指针变量 void search(float (*p)[4], int n) { for(int i = 0; i < 4; i++) { printf("%.2f ", *(*(p + n) + i)); } } int main() { float score[3][4] = {78, 90, 89, 34, 91, 61, 71, 84, 67, 76, 100, 53}; avg(*score,12); search(score, 2); return 0; }
指针指向 三维数组
a[2][3][4]数组的结构:
两个二维数组
三个一维数组
四个int类型的元素
首地址 | 指向 | 类型 | 移动一位 | 移动字节数 |
---|---|---|---|---|
a | a[0]这个二维数组 | int(*)[3][4] | a+1 | 48B |
a[0] | a[0][0]这个一维数组 | int(*)[4] | a[0]+1 | 16B |
a[0][0] | a[0][0][0]元素 | int * | a[0][0]+1 | 4B |
指针指向 多维数组
原理与二维数组三维数组一样
取元素的值: