指针
变量指针与指针变量
指针变量做函数参数
指针变量做函数往往传递的变量的首地址,借助指针变量间接访问是可以修改实参数据的。
案例
需求:有a,b两个变量,要求交换后输出(要求函数处理,用指针变量做函数的参数)
方式1:交换指向(指针指向发生改变,指针指向的对象的值不变)
/************************************************************************* > File Name: demo01.c > Author: 刘孟丹 > Description: 案例1 > Created Time: 2025年05月20日 星期二 09时34分30秒 ************************************************************************/ #include <stdio.h> /** * 定义一个函数,实现两个数的交换 */ void swap(int *p_a,int *p_b) { int *p_t; //以下写法,只会改变指针指向,并不会改变指向对象的值 p_t = p_a; p_a = p_b;//p_a指向了b p_b = p_t;//p_b指向了a printf("交换后:%d,%d\n",*p_a,*p_b); } int main(int argc,char *argv[]) { int a = 3,b = 5; printf("a= %d,b= %d\n",a,b); swap(&a,&b); printf("a= %d,b= %d\n",a,b); return 0; }
方式2:交换数据(指针指向不变,指针指向对象的值发生改变)
我们通过形参去修改实参的值(因为我们的参数传递单向值传递,所以本质上来讲,是无 法通过形参去影响实参,但是我们可以通过指针的方式实现这一点)!
/*************************************************************************
> File Name: demo01.c
> Author: 刘孟丹
> Description: 案例1
> Created Time: 2025年05月20日 星期二 09时34分30秒
************************************************************************/
#include <stdio.h>
/**
* 定义一个函数,实现两个数的交换
*/
void swap(int *p_a,int *p_b)
{
int temp;
// 以下写法,不会影响指针的指向,只会改变指针指向对象的值
temp = *p_a;
*p_a = *p_b;// 将p_b指向对象b的值取出来,赋值给p_a指向的对象a
*p_b = temp;// 将缓存p_a指向对象a的值取出来,赋值给p_b指向的对象b
printf("交换后:%d,%d\n",*p_a,*p_b);
}
int main(int argc,char *argv[])
{
int a = 3,b = 5;
printf("a= %d,b= %d\n",a,b);
swap(&a,&b);
printf("a= %d,b= %d\n",a,b);
return 0;
}
指针变量指向数组元素
数组元素的指针
- 数组的指针就是数组中第一个元素的地址,也就是数组的首地址
- 数组元素的指针是指针数组的首地址。因此,同样可以
- 在C语言中,由于数组名代表数组的首地址,因此数组名实际上也是指针。访问数组名就 是访问数组首地址。
范例:
/*************************************************************************
> File Name: demo03.c
> Author: 小刘
> Description:
> Created Time: 2025年05月20日 星期二 10时15分56秒
************************************************************************/
#include <stdio.h>
int main(int argc,char *argv[])
{
//创建一个数组
int arr[] = {11,12,13};
int *p1 = &arr[0];// 数组中首元素的首地址
int *p2 = arr; // 数组的首地址,指针变量p2指向了数组arr的第一个元素
printf("%p,%p,%p\n",p1,p2,&arr);// 0x7ffc3c8ce480,0x7ffc3c8ce480,0x7ffc3c8ce480
return 0;
}
注意:虽然我们定义了一个指针变量接收了数组地址,但是不能理解为指向来了数组的元素(默认为第一个元素)
指针的运算
指针运算:前提是指针变量必须要指向数组的某个元素。(指针运算只能发生在同一数组内)
序号 | 指针运算 | 说明 |
---|---|---|
1 | 自增:p++、++p、p+=1 | 指向下一个元素的地址,适合在循环中 |
2 | 自减:p–、–p、p-=1 | 指向上一个元素的地址,适合在循环中 |
3 | 加n个数:p + n(n * sizeof(type)) | 后n个元素的(首)地址 |
4 | 加n个数:p - n | 前n个元素的(首)地址 |
5 | 指针相减 | p1 - p2 之间相差几个元素 |
6 | 指针比较 | 前面的指针小于后面的指针 |
①如果指针变量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。
/*************************************************************************
> File Name: demo04.c
> Author: 刘孟丹
> Description:
> Created Time: 2025年05月20日 星期二 10时34分12秒
************************************************************************/
#include <stdio.h>
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]
printf("%ld\n",p1-p2); // 4*4 - 1*4 = 12(相差的字节数) / 4(每个元素的大小) = 3(元素个数)
return 0;
}
案例
案例1
- 需求:通过下标法和指针法遍历数组
- 代码
/*************************************************************************
> File Name: demo05.c
> Author: 刘孟丹
> Description:
> Created Time: 2025年05月20日 星期二 10时46分37秒
************************************************************************/
#include <stdio.h>
/**
* 下标法遍历数组
* @param arr 需要遍历的数组,此时 int
* @param len 数组的大小
*/
void arr1(int arr[],int len)
{
for(register int i = 0;i < len ; i++)
{
printf("%-3d",arr[i]);
}
printf("\n");
}
/**
* 指针法遍历数组
* @param arr 需要遍历的数组,此时 int
* @param len 数组的大小
*/
void arr2(int arr[],int len)
{
int *p = arr;
register int i = 0;
for(; i < len; i++)
{
printf("%-3d",*(arr+i));
}
printf("\n");
}
/**
* 指针法遍历数组
* @param arr 需要遍历的数组,此时 int
* @param len 数组的大小
*/
void arr3(int arr[],int len)
{
int *p = arr;
register int i = 0;
for(; i < len; i++)
{
printf("%-3d",*p);
p++;
}
printf("\n");
}
/**
* 指针法遍历数组
* @param arr 需要遍历的数组,此时 int
* @param len 数组的大小
*/
void arr4(int arr[],int len)
{
int *p = arr;
register int i = 0;
for(; p < arr + len; p++)
{
printf("%-3d",*p);
}
printf("\n");
}
int main(int argc,char *argv[])
{
int arr[] = {11,22,33,44,55};
int len = sizeof(arr)/sizeof(arr[0]);
arr1(arr,len);
arr2(arr,len);
arr3(arr,len);
arr4(arr,len);
return 0;
}
案例2
需求:推导以下代码运行结果
代码:
/************************************************************************* > File Name: demo09.c > Author: 刘孟丹 > Description: > Created Time: 2025年05月24日 星期六 13时33分04秒 ************************************************************************/ #include <stdio.h> int arr2() { int arr[] = {11,22,33,44,55,66,77,88}; int *p = arr; printf("%d\n",*p);//输出p的值为11 p++; //指针偏移1个单位,也就是22的首地址 printf("%d\n",*p);//输出p的值为22 int x = *p++; //第1步:解引用p的值赋值给x,x= 22,第二步:p++,指针移动到了33这个元素 printf("%d,%d\n",x,*p);//输出p的值为33,x= 22 int y = *(++p); //第一步:++p,指针移动到44这个元素,第二步对44这个地址解引用,得到44 printf("%d,%d\n",y,*p);//输出p的值为44,y=44 (*p)++; //第一步先解引用,得到22,第二步,对44+1,得到45 printf("%d\n",*p);//输出p的值为44+1= 45 int z = ++(*p); printf("%d,%d\n",z,*p); } int main(int argc,char *argv[]) { arr2(); return 0; }
※小贴士
*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]
形式
②指针法:如*a(arr+i)
或者*(p+i)
其中arr是数组名,p指向数组元素的指针变量,其初始值:p = arr;
案例:
需求:有一个整型数组arr,有10个元素,输出数组中全部元素。
下标法:(通过)
#include <stdio.h> 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 <stdio.h> 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 <stdio.h> 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; }
注意:数组一旦创建就无法改变其值。
以上4种写法的比较:
第①种写法和第②种写法执行效率相同。系统是将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.取新地址的值 | 向前(←) | √ |
前置自增+取值 | *(++p) |
arr[++i] |
1.指针前移2.取新地址的值 | 向后 (→) | √ |
后置自减+取值 | *(p--) |
arr[i--] |
1. 取原地址的值 2.指针前移 | 向前(←) | √ |
后置自增+取值 | *(p++) |
arr[i++] |
1. 取原地址的值2. 指针后移 | 向后 (→) | √ |
后置自增(简写) | *p++ |
arr[i++] |
1. 取原地址的2. 指针后移 | 向后 (→) | √ |
取值后值自增 | (*p)++ |
arr[i]++ |
1.取原地址的值 2.值+1 | 不移动 | × |
数组名做函数参数
①形参和实参都是数组名
//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 *p,int len){..}
void main()
{
int arr[] = {11,22,33};
int len = sizeof(arr) / sizeof(arr[0]);
//arr,实参
fun(arr,len);
}
③实参和形参都用指针变量
//arr,形参变量
void fun(int *p,int len){..}
void main()
{
int arr[] = {11,22,33};
int len = sizeof(arr) / sizeof(arr[0]);
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]);
int *p;
//arr,实参
fun(*p,len);
}
案例
需求:将数组a中的n个整数按相反顺序存放
代码:
/*************************************************************************
> File Name: demo06.c
> Author: 刘孟丹
> Description:
> Created Time: 2025年05月20日 星期二 14时59分10秒
************************************************************************/
#include <stdio.h>
/**
* 数组的反转:下表法实现
*/
void inv1(int arr[],int len)
{
// 反转思路:将第0个和len-1个进行互换,将第1个len-2个互换..
// 定义循环变量和临时变量
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)//为什么需要外部传入len,因为数组传实参的时候会被降级为指针
{
// 反转思路:将第0个和len-1进行互换,将第1个len-2个互换..
// 定义循环变量和临时变量
int *i = p,*j = p + len - 1,temp; // i:指向数组第1个元素,j:指向数组最后一个元素
//遍历数组
for(;i < j; i++,j--)
{
temp = *i;
*i = *j;
*j = temp;
}
}
/**
* 遍历数组
*/
void list(const int arr[],int len)
{
for(int i = 0;i < len;i++)printf("%-3d",arr[i]);
printf("\n");
}
int main(int argc,char *argv[])
{
int arr[] = {11,22,33,44,55,66};
int len = sizeof(arr) /sizeof(arr[0]);
list(arr,len);
inv1(arr,len);
list(arr,len);
inv2(arr,len);
list(arr,len);
return 0;
}
数组指针和指针数组
数组指针
定义
**概念:**数组指针是指向数组的指针(指针变量),本质上还是指针。
特点:
①先有数组,再有指针
② 它指向的是一个完整的数组
语法:
数组类型(*指针变量名)[容量];
案例:
/*************************************************************************
> File Name: demo07.c
> Author: 刘孟丹
> Description:
> Created Time: 2025年05月20日 星期二 15时38分01秒
************************************************************************/
#include <stdio.h>
/**
*
*/
int main(int argc,char *argv[])
{
// 一位数组指针
// 先有数组,再有指针
// 指针不能独立存在,必须依赖于现有变量、数组、结构体、常量、函数
int arr[] = {100,200,300};
//计算数组的大小
int len = sizeof(arr) / sizeof(arr[0]);
//定义一个数组指针
int (*p)[3] = &arr; //arr 默认指向首元素,&arr指向整个数组,这两个对应的地址是相同一个地址,只是操作范围不同
//p++;此时不能p++,否则会越界
printf("%p,%p,%p\n",p,&arr,arr);// 输出地址相同,但是表示的范围不同
//如何访问数组指针
printf("%d\n",(*p)[2]); //p指向整个数组,*p表示整个数组
//遍历数组指针
for(int i = 0; i < len ;i++)
{
printf("%-6d",(*p)[i]);
}
printf("\n");
return 0;
}
我们之前所学的是指向数组元素的指针,本质上是指针变量;现在我们学的是指针向数组的指针,叫数组指针。
二维数组指针
语法:
数组类型(*指针变量名)[行容量][列容量];
案例
写法1:
/************************************************************************* > File Name: demo08.c > Author: 刘孟丹 > Description: > Created Time: 2025年05月20日 星期二 16时13分41秒 ************************************************************************/ #include <stdio.h> int main(int argc,char *argv[]) { int arr[][3] = {10,20,30,100,200,300,1000,2000,3000}; // 定义要给数组指针指向二维数组arr //int (*p)[][3] = &arr;// 二维数组指向指针指向二维数组,不推荐 int (*p)[3] = arr; //一维数组指针指向二维数组的行,推荐使用 //p++,此时p指向arr中的第一个元素(一维数组)所以p++指针从10的位置移动到100的位置 //遍历数组 for(int i = 0;i < 3;i++)//遍历行 { for(int j = 0;j < 3;j++) //遍历列 { printf("%-6d",p[i][j]);//arr的地址 等于p存储的值,意味着 p 可以提升为数组 } printf("\n"); } printf("\n"); return 0; }
写法2
/************************************************************************* > File Name: demo08.c > Author: 刘孟丹 > Description: > Created Time: 2025年05月20日 星期二 16时13分41秒 ************************************************************************/ #include <stdio.h> int main(int argc,char *argv[]) { int arr[][3] = {10,20,30,100,200,300,1000,2000,3000}; // 定义要给数组指针指向二维数组arr //int (*p)[][3] = &arr;// 二维数组指向指针指向二维数组,不推荐 int (*p)[3] = arr; //一维数组指针指向二维数组的行,推荐使用 //p++,此时p指向arr中的第一个元素(一维数组)所以p++指针从10的位置移动到100的位置 //获取数组长度 int rows = sizeof(arr)/sizeof(arr[0]); int cols = sizeof(arr[0])/sizeof(arr[0][0]); //遍历数组 for(int i = 0;i < rows;i++)//遍历行 { for(int j = 0;j < cols;j++) //遍历列 { printf("%-6d",(*(p+i))[j]);//arr的地址 等于p存储的值,意味着 p 可以提升为数组 } printf("\n"); } printf("\n"); return 0; }