> 技术文档 > C语言:指针、变量指针与指针变量、数组指针与指针数组

C语言:指针、变量指针与指针变量、数组指针与指针数组


Day 14-C语言

指针

变量指针与指针变量

指针变量做函数参数

指针变量做函数参数往往传递的是变量首地址,借助于指针变量间接访问是可以修改实参变量数据的

指针有一个作用,通过形参修改实参,这样的参数称为输出型参数

案例
  • 需求:有a、b两个变量,要求交换输出,使用函数处理,用指针变量做函数的参数

    • 方式一:交换指向(指针指向改变,指向对象的数据不变)

    • 代码:

      #include /** * 交换指向 */void swap(int *p_a,int *p_b){ int *p_t; //交换 p_t = p_a; p_a = p_b; p_b = p_t; printf(\"交换后:%d,%d\\n\",*p_a,*p_b);}int main(int argc,char *argv[]){ int a = 3,b = 4; printf(\"交换前:%d,%d\\n\",a,b); swap(&a,&b); //传参的过程可以理解 int *p_a = &a,int *p_b = &b  return 0;}c
    • 运行结果

C语言:指针、变量指针与指针变量、数组指针与指针数组

  • 方式2:交换变量

  • 代码

    #include /** * 交换指向 */void swap(int *p_a,int *p_b){ int temp; //交换 temp = *p_a; *p_a = *p_b; // 将p_b指向对象的值赋给p_a指向的对象 *p_b = temp; printf(\"交换后:%d,%d\\n\",*p_a,*p_b);}int main(int argc,char *argv[]){ int a = 3,b = 4; printf(\"交换前:%d,%d\\n\",a,b); swap(&a,&b); //传参的过程可以理解 int *p_a = &a,int *p_b = &b  return 0;}
  • 运行结果

    C语言:指针、变量指针与指针变量、数组指针与指针数组

指针变量指向数组元素【重难点】
数组元素的指针
  • 数组的指针就是数组中第一个元素的地址,也就是数组的首地址。

  • 数组元素的指针是数组的首地址。因此,同样可以用指针变量来指向数组或数组元素。

  • 在C语言中,由于数组名代表数组的首地址,因此数组名实际上也是指针。访问数组名就是访问数组首地址

  • 案例:

    #include int main(int argc,char *argv[]){ //创建一个数组 int arr[] = {11,22,33}; int *p1 = &arr[0]; //指针变量指向数组arr第一个元素,指针的范围就是数组元素 int *p2 = arr; //等价于上面写法,数组名就是首元素地址 printf(\"%p,%p,%p\\n\",p1,p2,arr); return 0;}
  • 运行结果:

C语言:指针、变量指针与指针变量、数组指针与指针数组

​注意:虽然定义了一个指针变量接受了数组地址,但不能理解为指针变量指向了数组,而应该理解为指向了数组元(默认为第一个元素)。

指针的运算

指针运算:前提是指针变量必须要指向数组的某个元素。(指针元素只能在同一数组内进行,并且只能是元素之间的偏移

序号 指针运算 偏移量 说明 1 自增:p++++pp+=1 sizeof(type) 指向下一个元素的首地址(边界检测,防止越界) 2 自减:p----pp-=1 sizeof(type) 指向上一个元素的首地址(边界检测,防止越界) 3 加n个数:p+n n*sizeof(type) 指向后面n个元素的首地址(边界检测,防止越界) 4 减n个数:p-n n*sizeof(type) 指向前面n个元素的首地址(边界检测,防止越界) 5 指针相减:p1 - p2 |(p1-p2)|/type p1,p2之间相差几个元素 6 指针比较:p1 < p2 逻辑值:真-1,假-0 前面的指针小于后面的指针

注意:

  • type,是指针指向数组的元素的类型

  • sizeof(不支持运算) 举例:

    #include int main(int argc,char *argv[]){ int a = 10; printf(\"sizeof(a)=%lu,sizeof(int)=%lu,sizeof(++a)=%lu\\n\", sizeof(a), sizeof(int), sizeof(++a)); // sizeof(a)=4,sizeof(int)=4,sizeof(a++)=4 return 0;}
  • 说明

    ① 如果指针变量p已指向数组中的一个元素,则p+1指向同一数组中的 下一个元素,p-1指向同一数组中的上一个元素。即p+1或p-1也表示地址。但要注意的是,虽然指针变量p中存放的是地址,但p+1并不表示该地址加1,而表示在原地址的基础上加了该数据类型所占的字节数d(d = sizeof(数据类型)) 。

    ② 如果p原来指向a[0],执行++p后p的值改变了,在p的原值基础上加d,这样p就指向数组的下一个元素a[1]。d是数组元素占的字节数。

    ③ 如果p的初值为&a[0]则p+i 和a+i 就是数组元素a[i]的地址,或者说,它们指向a数组的第 i 个元素 。

    *(p+i)或*(a+i)是p+i或a+i所指向的数组元素,即a[i]。

    ⑤ 如果指针变量p1和p2都指向同一数组,如执行p2-p1,结果是两个地址之差除以数组元素的长度d。

案例
#include #include int main(int argc,char *argv[]){ //创建一个用来实现指针运算的数组 int arr[] = {11,22,33,44,55}; int *p1 = arr + 4; //55 等价于 arr[4] int *p2 = arr + 1; //22 等价于 arr[1] size_t size = fabs(p1 - p1); //3 b= fabs(22对应的地址 - 55对应的地址) / int的字节数 printf(\"*p1=%d,*p2=%d,size=%lu,&arr[1]+2=%d\\n\",*p1,*p2,size,*(&arr[1]+2)); return 0;}

运行结果

C语言:指针、变量指针与指针变量、数组指针与指针数组

案例
  • 需求:通过下标法和指针法遍历数组

  • 代码:

#include /** * 下标法 */void arr1(int arr[],int len){ for (register int i = 0;i < len;i++) printf(\"%-4d\",arr[i]); printf(\"\\n\");}/** * 指针法 */void arr2(int arr[],int len){ int *p = arr; for(register int i = 0;i < len;i++) printf(\"%-4d\",*(arr+i)); // 等价于*(p+i) printf(\"\\n\");}/** * 指针法 */void arr3(int arr[],int len){ int *p = arr; for(int i = 0;i < len;i++) { printf(\"%-4d\",*p); p++; } printf(\"\\n\");}/** * 指针法 */ void arr4(int arr[],int len){ int *p = arr; for(;p < len + arr;p++) printf(\"%-4d\",*p); printf(\"\\n\");}int main(int argc,char *argv[]){ int arr[] = {11,22,33}; int len = sizeof(arr)/sizeof(arr[0]); arr1(arr,len); arr2(arr,len); arr3(arr,len); arr4(arr,len); return 0;}
  • 运行结果:

C语言:指针、变量指针与指针变量、数组指针与指针数组

register 是一个关键字,用于建议编译器将某个变量存储在 寄存器(register) 中,而不是内存(RAM)中,以提高访问速度。

案例
  • 需求:推导以下代码的运行结果

  • 代码:

    #include int arr2(){ // 创建一个普通数组 int arr[] = {11,22,33,44,55,66,77,88}; int *p = arr; printf(\"%d\\n\", *p); // 11 p++; // 指针偏移 1 * sizeof(int) 指针移动到22这个位置 printf(\"%d\\n\", *p); // 22 int x = *p++; // 第1步:解引用p的值赋值给x, x = 22; 第2步:p++,指针移动到33这个位置 printf(\"%d,%d\\n\", x, *p);// 22,33 int y = *(++p);// 第1步:++p,指针偏移到44这个位置;第2步:对44这个地址解引用,得到44 printf(\"%d,%d\\n\", y, *p);// 44,44 (*p)++; // 第1步:对p解引用得到44;第2步:对44这个值+1,得到45 printf(\"%d\\n\",*p); // 45}x

※小贴士

*p++:先解引用p,然后p这个指针自增(指针自增)

int arr[] = {11,22,33}, *p = arr;int x = *p++; // x=11,*p=22 ① *p解引用,其实就是将指向的对象a的值赋值给x ② 指针p++,也就是指针偏移一位

(*p)++:先解引用p,然后使用解引用出来的数据自增(数值自增)

int arr[] = {11,22,33}, *p = arr;int x = (*p)++; // x=11,*p=12 ① *p解引用,其实就是将指向的对象a的值赋值给x ② 解引用出来的对象数据自增
通过指针引用数组元素

引用一个数组元素,可以用:

① 下标法:如arr[i]

② 指针法:如*(arr + 1) 或者*(p+i)。其中arr是数组名,p是指向数组元素的指针变量,其初始值:p = arr;

案例

需求:

  • 下标法:(通过改变下标输出所有元素)

    #include int main(){ int arr[10], i; // 给数组元素赋值 for (i = 0; i < 10; i++) scanf(\"%d\", &arr[i]); // 遍历数组元素 for (i = 0; i < 10; i++) printf(\"%-4d\", arr[i]); printf(\"\\n\"); return 0;}
  • 指针法(地址):(通过数组名计算出数组元素的地址,找出数组元素值)

    #include int main(){ int arr[10], i; // 给数组元素赋值 for (i = 0; i < 10; i++) scanf(\"%d\", &arr[i]); // 遍历数组元素 for (i = 0; i < 10; i++) printf(\"%-4d\", *(arr + i));  printf(\"\\n\"); return 0;}
  • 指针法(指针变量):(用指针变量指向数组元素)

    #include int main(){ int arr[10], i, *p; // 给数组元素赋值 for (i = 0; i < 10; i++) scanf(\"%d\", &arr[i]); // 遍历数组元素 for (p = arr; p < (arr + 10); p++) printf(\"%-4d\", *p); printf(\"\\n\"); return 0;}

    注意:数组一旦创建,就无法改变其值。

以上3种写法比较:

  • 第①种写法和第②种写法执行效率相同。系统是将arr[i]转换为*(arr+i)处理的,即先计算出地址,因此比较费时。
  • 第③种方法比第①②种方法快。用指针变量直接指向数组元素,不必每次都重新计算地址。(p++)能大大提高执行效率。
  • 用第①种写法比较直观,而用地址法或者指针变量的方法难以很快判断出当前处理的元素。

使用指针变量指向数组元素时(上面第③种写法),注意以下前两点:

*(p--) 相当于arr[i--],先*p,再p--; *(p++) 相当于arr[i++],先*p,再p++;

*(--p) 相当于arr[--i],先--p,再* *(++p) 相当于arr[++i],先++p,再*;

*p++ 先*p,再p++

(*p)++ 先*p,再*p++

具体关系参照下面表格:

操作类型 指针表达式 数组下标等价 执行顺序 指针移动方向 是否改变指针地址 前置自减+取值 *(--p) arr[--i] 1. 指针前移
2. 取新地址的值 向前(←) 1 前置自加+取值 *(++p) arr[++i] 1. 指针后移
2. 取新地址的值 向后(→) 1 后置自减+取值 *(p--) arr[i--] 1. 取原地址的值
2. 指针前移 向前(←) 1 后置自加+取值 *(p++) arr[i++] 1. 取原地址的值
2. 指针后移 向后(→) 1 后置自减(简写 *p-- arr[i--] 1. 取原地址的值
2. 指针前移 向前(←) 1 后置自加(简写) *p++ arr[i++] 1. 取原地址的值
2. 指针后移 向后(→) 1 取值后值自减 (*p)-- arr[i]-- 原地址的值
2. 值-1 不移动 0 取值后值自加 (*p)++ arr[i]++ 1. 取原地址的值
2. 值+1 不移动 0
数组名作函数参数

① 形参和实参都是数组名

// arr 数组 形参void fun(int arr[], int len){..}void main(){ int arr[] = {11,22,33}; int len = sizeof(arr) / sizeof(arr[0]); // arr 数组 实参 fun(arr, len);}

② 实参用数组名,形参用指针变量

// arr 指针 形参void fun(int *arr, int len){..}void main(){ int arr[] = {11,22,33}; int len = sizeof(arr) / sizeof(arr[0]); // arr 数组 实参 fun(arr, len);}

③ 实参和形参都用指针变量

// arr 指针 形参void fun(int *arr, int len){..}void main(){ int arr[] = {11,22,33}; int len = sizeof(arr) / sizeof(arr[0]); // arr 指针 实参 int *p = arr; fun(p, len);}

④ 实参用指针,形参用数组名

// arr 数组 形参void fun(int arr[], int len){..}void main(){ int arr[] = {11,22,33}; int len = sizeof(arr) / sizeof(arr[0]); // arr 指针 实参 int *p = arr; fun(p, len);}
案例:

需求:将数组a中的n个整数按相反顺序存放(数组反转)

代码:

#include /** * 数组的反转:下标法 */ void inv1(int arr[], int len){ // 反转思路:第0个和最后一个交换,第1个和倒数第二个交换... // 定义循环变量和临时变量 register int i = 0, temp; // 遍历数组 for (; i < len/2; i++) { temp = arr[i]; arr[i] = arr[len-1-i]; arr[len-1-i] = temp; }}/** * 数组的反转:指针法 */ void inv2(int *p, int len){ // 反转思路:第0个和最后一个交换,第1个和倒数第二个交换... // 定义循环变量和临时变量 int *i = p, *j = p + len - 1, temp; // 遍历数组 for (; i < j; i++, j--) { temp = *i; *i = *j; *j = temp; }}/** * 遍历数组 */ void list(const int *arr, int len) // const int *arr = arr;{ const int *p = arr; // 添加const之后,指针指向对象的值不变,指针指向可以改变 for (; p < arr + len; p++) printf(\"%-4d\", *p); printf(\"\\n\");}int main(int argc,char *argv[]){ int arr[] = {11,12,13,14,15}; int len = sizeof(arr) / sizeof(arr[0]); list(arr, len); inv1(arr, len); list(arr, len); inv2(arr, len); list(arr, len); return 0;}

运行结果:
C语言:指针、变量指针与指针变量、数组指针与指针数组

数组指针与指针数组

数组指针
定义

**概念:**数组指针是指向数组的指针(指针变量),本质上还是指针。

指针变量指向数组元素和数组指针的区别?

指针变量指向数组的元素;数组指针指向整个数组

特点:

① 先有数组,再有指针

② 它指向的是一个完整的数组

一维数组指针

语法:

数据类型 (*指针变量名)[容量];

案例:

#include int main(int argc,char *argv[]){ // 一维数组指针 int arr[] = {100,200,300}; int len = sizeof(arr) / sizeof(arr[0]); // 定义一个数组指针(一维数组指针) int (*p)[len] = &arr; // arr默认指向数组元素,&arr指向整个数组,需要注意的的是,它们表示的范围不同,地址相同 // p++:此时不能p++,否则会越界 printf(\"&arr=%p,arr=%p,&arr[0]=%p\\n\", &arr, arr, &arr[0]); // arr 等价于 &arr[0] // 如何访问数组指针 printf(\"%d\\n\", (*p)[2]); // 300 // 遍历数组指针 for (int i = 0; i < len; i++) printf(\"%-6d\", (*p)[i]); printf(\"\\n\"); return 0;}

运行结果:

C语言:指针、变量指针与指针变量、数组指针与指针数组

我们之前所学的是指向数组元素的指针,本质上是指针变量;现在我们学的是指向数组的指针,叫作数组指针。

二维数组指针

语法:

数据类型 (*指针变量名)[行容量][列容量];

案例:

  • 写法1:二维数组指针指向二维数组【不推荐】

    #include int main(int argc,char *argv[]){ // 创建一个二维数组 int arr[][3] = {10,20,30,100,200,300,1000,2000,3000}; // 定义一个二维数组指针指向二维数组 int (*p)[][3] = &arr; // 遍历数组 for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { printf(\"%-6d\", (*p)[i][j]); } } printf(\"\\n\"); return 0;}
  • 写法2:一维数组指针指向二维数组【推荐】

    #include int main(int argc,char *argv[]){ // 创建一个二维数组 int arr[][3] = {10,20,30,100,200,300,1000,2000,3000}; // 定义一个一维数组指针指向二维数组,相当于指针指向的是二维数组的行 [行容量] int (*p)[3] = arr; // 等价于 &arr[0] (*p):指向数组的行 int arr[] = {100, 200, 300}; int *p = arr; 解引用p 得到第一个元素 // 遍历数组 for(i = 0;i < 3;i++) for(j = 0;j < 3;j++) { printf(\"%-5d\",p[i][j]); } printf(\"\\n\"); for(i = 0;i < 3;i++) for(j = 0;j < 3;j++) { printf(\"%-5d\",*(*(p+i)+j)); } printf(\"\\n\"); for(i = 0;i < 3;i++) for(j = 0;j < 3;j++) { printf(\"%-5d\",(*(p+i))[j]); } printf(\"\\n\"); for(i = 0;i < 3;i++) for(j = 0;j < 3;j++) { printf(\"%-5d\",*(p[i]+j)); } printf(\"\\n\"); return 0;}

C语言:指针、变量指针与指针变量、数组指针与指针数组