1.指针和函数
(1)传递指针给函数
当函数的形参类型是指针类型时,使用该函数时,需要传递指针、地址或者数组给该形参。
代码示例:传递指针或地址给函数。
#include <stdio.h>
//函数原型声明
void fun(int *);
int main()
{
//定义变量
int num=100;
//定义指针
int *ptr=#
//传递地址给函数
fun(&num);
printf("%d\n",num);
//传递指针
fun(ptr);
printf("%d",*ptr);
return 0;
}
void fun(int *p)
{
*p+=1;
}
(2)传数组给函数
数组名本身就代表数组首地址,因此传数组的本质就是传地址。
C语言中没有指针,数组传递不了给函数。
#include <stdio.h>
int fun(int *, int );
int main()
{
int arr[5] = {11, 23, 44, 34, 45};
int *ptr=&arr[0];
int length = 5;
printf("%d\n", fun(ptr, 5));
return 0;
}
int fun(int *ary, int length)
{
for (int i = 0; i < length; i++)
{
printf("%d\n",*(ary+i));//printf("%d\n",ary[i])
}
return *ary;
}
#include <stdio.h>
int fun(int *, int );
int main()
{
int arr[5] = {11, 23, 44, 34, 45};
int *ptr=&arr[0];
int length = 5;
fun(arr,5);
printf("%d\n", arr[1]);
return 0;
}
int fun(int *ary, int length)
{
ary[1]=24;//在函数中通过指针修改数组的值,在main函数中也会修改。
//因为fun函数中的指针,指向的就是main函数中的arr数组
return *ary;
}
(3)指针函数(返回指针的函数)
如果函数的返回值是一个指针,这样的函数称为指针函数。
语法规则:返回类型 *指针函数名(参数列表)
<1>请编写一个函数 strlong(),返回两个字符串中较长的一个.
#include <stdio.h>
#include <string.h>
char *strlong(char *str1, char *str2)
{
return strlen(str1) > strlen(str2) ? str1 : str2;//返回的是指针
}
int main()
{
char str1[100];
char str2[100];
char *ptr = NULL;
printf("请输入字符串:");
scanf("%s", &str1);
printf("请输入字符串:");
scanf("%s", &str2);
ptr = strlong(str1, str2);
printf("%s", ptr);
return 0;
}
<2>返回值不能指向局部变量
函数运行结束后会销毁在它内部定义的所有局部数据,包括局部变量、局部数组和形式参数,所以函数返回的指针不能指向这些数据。
如果确实有这样的需求,需要定义为静态局部变量。
#include <stdio.h>
int *fun()
{
static int num=100;//延长周期
return #
}
int main()
{
int *ptr=fun();
int n=*ptr;
printf("%d",n);
return 0;
}
输出100
如果不用static,局部变量无法在主函数区域内声明。
<3>编写一个函数,它会生成10个随机数,并使用数组名作为返回值。
说明:生成随机数可以使用 rand() 函数,由C语言标准库 <stdlib.h> 提供,可以返回一个随机整数。
示例:
#include <stdio.h>
#include <stdlib.h>
// 函数返回一个数组
int *fn()
{
static int arr[10]; // 必须加上static ,让arr 的空间在静态数据区分配
for (int i = 0; i < 10; i++)
{
arr[i] = rand();
}
return arr;
}
int main()
{
int *ptr = fn(); // p 指向是在 f1 生成的数组的首地址(即第一个元素的地址)
for (int i = 0; i < 10; i++)
{
printf("第%d号元素%d\n",i,ptr[i]);
}
return 0;
}
我的:
#include <stdio.h>
#include <stdlib.h>
int *fn()
{
// 定义数组
static int arr[10]; // 便于主函数内调用,加静态static延长生命周期
for (int i = 0; i < 10; i++)
{
arr[i] = rand(); // 循环遍历随机整数
}
return arr; // 返回值不是数组,应是数组首元素的地址,故函数定义为指针类型
}
int main()
{
int *vay = fn(); // 返回值是数组首元素地址,需要以指针接收
for (int i = 0; i < 10; i++)
{
printf("第%d个元素输出整数 %d\n",i,vay[i]); // array[i]或者*(array++)
}
return 0;
}
(4)函数指针
一个函数总是占用一段连续的内存区域,函数名在表达式中有时也会被转换为该函数所在内存区域的首地址,这和数组名非常类似。
把函数的这个首地址(或称入口地址)赋予一个指针变量,使指针变量指向函数所在的内存区域,然后通过指针变量就可以找到并调用该函数,这种指针就是函数指针。
语法规则:
返回类型 (*函数指针名)(参数列表); int (*ptr)(int )
注意:()的优先级高于*,第一个括号不能省略,如果省略,就成了指针函数。
示例代码:
用函数指针来实现对函数的调用,返回两个整数中的最大值。
#include <stdio.h>
//定义函数
int max(int a,int b)
{
return a>b?a:b;
}
int main()
{
//函数指针还是指针,存的是函数的地址,听过地址找到并调用函数
int (*ptr)(int a,int b)=max;
printf("%d",(*ptr)(11,34));
return 0;
}
函数的指针,对嵌入式开发来说,实际用处不大,以上例子完全可以直接调用函数即可。
(5)回调函数
函数指针可以作为某个函数的参数来使用的,回调函数就是一个通过函数指针调用的函数。
简单的讲,回调函数是由别的函数执行时调用你传入的函数。
示例代码:
使用回调函数的方式,给一个整型数组int arr[10] 赋10个随机数。
#include <stdio.h>
#include <stdlib.h>
int *fun(int *ptr,int (*sn)())
{
for(int i=0;i<10;i++)
{
ptr[i]=(*sn)();
}
return ptr;
}
int main()
{
//初始化一个数组
int arr[10];
//函数
fun(arr,rand);//传rand函数,用到函数指针。
for(int i=0;i<10;i++)
{
printf("元素:%d 值是:%d\n",i,arr[i]);
}
return 0;
}
明悟:数组和函数要想传参到定义函数,需要用到指针。指针函数能够通过返回指针或地址来赋值数组的值,函数指针则能通过指针寻找到函数。
2.多级指针
(1)介绍
指向指针的指针是一种多级间接寻址的形式,或者说是一个指针链。通常,一个指针包含一个变量的地址。当我们定义一个指向指针的指针时,第一个指针包含了第二个指针的地址,第二个指针指向包含实际值的位置。
指针也是有地址的,并且和指向数值的地址不同。
(2)多级指针的定义与使用
声明多级指针时,需要使用多个星号来表示指针的级别。
int *ptr; // 一级指针
int **pptr; // 二级指针
int ***ppptr; // 三级指针
初始化多级指针时,你需要逐级给指针赋值,确保每个级别的指针指向正确的目标。
int var;
int *ptr = &var; // 一级指针指向 var
int **pptr = &ptr; // 二级指针指向 ptr
int ***ppptr = &pptr; // 三级指针指向 pptr
解引用多级指针时,需要根据指针的级别使用适当数量的星号解引用操作。
printf("Value of var: %d\n", var);
printf("Value of ptr: %d\n", *ptr); // 解引用一次
printf("Value of pptr: %d\n", **pptr); // 解引用两次
printf("Value of ppptr: %d\n", ***ppptr); // 解引用三次
(3)案例演示:
#include <stdio.h>
int main()
{
int a=100;
int *p1=&a;//一级指针,存a的地址
int **p2=&p1;//二级指针,存指针p1的地址
int ***p3=&p2;//三级指针,存二级指针p2的地址
printf("%d\n",*p1);
printf("%d",**p2);//虽然指向的地址不同,但值都是一样的
return 0;
}
3.空指针
赋为NULL 值的指针被称为空指针,NULL 是一个定义在标准库 <stdio.h>中的值为零的宏常量。
声明指针变量的时候,如果没有确切的地址赋值,为指针变量赋一个NULL 值是好的编程习惯。
int *ptr=NULL;
4.野指针
野指针就是指针指向的位置是不可知(随机性,不正确,没有明确限制的)
(1)野指针的成因:
1.指针使用前未初始化
指针变量在定义时如果未初始化,其值是随机的,此时操作指针就是去访问一个不确定的地址,所以结果是不可知的。此时p就为野指针。
int *p;
printf("%d\n", *p);
在没有给指针变量显式初始化的情况下,一系列的操作(包括修改指向内存的数据的值)也是错误的。
int *p;
*p = 10;
2.指针越界访问
int arr[4] = {10,20,30,40};
int *p = arr;
p += 4;
printf("%d", *p); // 此时 *p 即为越界访问
当 p += 4之后,此时 *p 访问的内存空间不在数组有效范围内,此时 *p 就属于非法访问内存空间,p为野指针。
3.指针指向已释放的空间
#include <stdio.h>
int *test()
{
int a = 10;
return &a; //&a=0x0012ff40
}
int main()
{
int *p = test();
printf("%d", *p);
return 0;
}
调用test()函数将返回值赋给p,test函数的返回值是局部变量a的地址,函数调用结束局部变量会被销毁,其所占用的内存空间会被释放,p 指向的是已经释放的内存空间,所以 p 是野指针。
(2)避免野指针
- 指针初始化如果没有确切的地址赋值,为指针变量赋一个 NULL 值是好的编程习惯。
- 小心指针越界。
- 避免返回指向局部变量的指针。
- 指针使用之前检查指针是否为 NULL。