指针数组与函数指针

朱雀 2023-03-13 05:24 407阅读 0赞

写在前面:学习的第一门语言是Java,之前对C也了解一点,也只是了解一点,在加上长时间没有接触了,基本就只会一个Hello World了。现在由于准备升本考试,不得不从头开始学C。这里从零开始,记录C语言学习点滴。欢迎正在学习C语言的小伙伴一起学习,未来可期,一起加油!

上一篇写了指针的定义及基本使用,这章我们来看一下数组指针的使用。

指针、数组和地址间的关系

在定义数组时,编译器必须分配基地址和足够的存储空间,以存储数组的所有元素。数组的基地址是在内存中存储数组的起始位置,它是数组中第一个元素(下标为0)的地址,因此数组名本身是一个地址即指针值。在访问内存方面,指针和数组几乎是相同的,当然也有区别,如:指针是以地址作为值的变量,而数组名的值是一个特殊的固定地址,可以把它看作是指针常量。假设给出以下定义:

  1. int a[100], *p;

系统分别把编号为3000, 3002, 3004 … 的内存字节作为数组元素a[0], a[1], a[2], … , a[99]的地址(假定系统int型变量的长度为2个字节),那么其中内存位置3000是数组a的基地址,也是a[0]的地址。由于数组名a是一个地址常量,因此以下两条语句是等价的。

  1. p = a;
  2. p = &a[0];

它们都是把3000这个地址值赋给了指针p。同样如下语句也是等价的:

  1. p = a + 1 == p + 1;
  2. p = &a[1];

它们都是把3002赋给p。数组a和指针p之间的关系如下:
在这里插入图片描述
【注】p = a + 1和p = p + 1语句是正确的,但a = a+1语句是错误的。

如果已经对数组a的元素进行了赋值,求数组所有元素的和。代码如下:

  1. int sum = 0;
  2. for(p = a; p <= &a[99]; p++)
  3. sum += *p;

在循环中,指针变量p的初值是数组a的基地址,p连续取值&a[0], &a[1], … , &a[99]。
一般而言,如果i是int型变量,那么p + i 就是距地址p的第i个偏移,类似地,a + i 是距数组a的基地址的第i个偏移,*(a + i)与a[i]等价。下面是对数组元素求和的第二种方法:

  1. int sum = 0;
  2. for(i = 0; i < 100; i++)
  3. sum += *(a + i);

正如表达式 *(a+i)与a[i]等价一样,表达式 *(p + i)与p[i]等价。下面是对数组求和的第三种方法:

  1. p = a;
  2. int sum = 0;
  3. for(i = 0; i < 100; i++)
  4. sum += p[i];

由此可知,数组名可以使用指针形式,而指针变量也可以转换为数组形式。

指针数组

上面简单讲述了指针、数组和地址间的关系,也看到了指针指向数组的简单使用,下面来看一下数组指针的使用。

C语言中的数组可以是任何类型,如果数组的各个元素都是指针类型,用于存放内存地址,那么这个数组就是指针数组。

一维指针数组定义的格式为:类型名 *数组名[数组长度];

类型名指定数组元素所指向的变量的类型。例如:

  1. int a[10];
  2. char *color[5];

上述代码定义了整型数组a和字符指针数组color。整型数组a有10个元素,可以存放10个整型数据;指针数组color有5个元素,元素的类型是字符指针,用于存放字符数据单元的地址。一个数组的元素均为指针型的数据。

指针数组初始化与应用
指针数组初始化与使用和数组差不多,不同的就是数组元素均为指针类型的数据。如下对指针数组的初始化:

  1. char *color[5] = {"aaa", "bbb", "ccc", "ddd", "fff"};
  2. color[0] = "abc";

指针数组初始化和数组一样,上述代码定义了一个字符型指针数组。字符指针可以指向字符串,因此字符型指针数组也可以存储字符串数据。
改变指针数组中的数据,和数组一样,根据数组的下标改变。

指针数组的简单使用

  1. #include<stdio.h>
  2. int main(){
  3. //初始化指针数组
  4. char *color[5] = {"aaa", "bbb", "ccc", "ddd", "fff"};
  5. //输出指针数组中内容
  6. for(int i = 0 ; i < 5; i ++){
  7. printf("%s\t", color[i]);
  8. }
  9. //改变指针数组中的第一个元素
  10. color[0] = "abc";
  11. printf("\n");
  12. for(int i = 0 ; i < 5; i ++){
  13. printf("%s\t", color[i]);
  14. }
  15. //把指针数组中第一个元素与最后一个元素交换位置
  16. char *str= color[0];
  17. color[0] = color[4];
  18. color[4] = str;
  19. printf("\n");
  20. for(int i = 0 ; i < 5; i ++){
  21. printf("%s\t", color[i]);
  22. }
  23. return 0;
  24. }

上述代码先定义了一个字符型指针数组并初始化,改变了指针数组的第一个值和交换指针数组中第一个和最后一个元素的位置操作。
运行效果如下:
在这里插入图片描述
通过上述案例可以看出使用字符型指针数组可以操作多个字符串。如果不用指针数组,操作多个字符串就只能使用二维数组来实现,因为每个字符串的长度可能不一样,所以使用二维数组来操作多个字符串,会造成内存单元的浪费。推荐使用指针数组去操作多个字符串。

指向指针的指针

定义指针是 类型名 *变量名; 定义的。定义指向指针的指针格式如下:
类型名 * *变量名;
指向指针的指针也称为二级指针。与一级指针相比,二级指针要相对复杂一些。

定义二级指针并初始化

  1. int a = 10;
  2. int *p = &a;
  3. int **pp = &p;

上述代码定义了3个变量a、p和pp并初始化。一级指针p指向整形变量a,二级指针pp指向一级指针p。如下如:
在这里插入图片描述
由于p指向a,所以p和&a的值一样,a和 *p代表同一个单元;由于pp指向p,所以pp和&p的值一样,p和 *pp代表同一个单元。由此可知&&a、&p和pp等价,&a、p和 *pp等价,a、*p和 **pp代表同一个单元,它们的值都相同。

二级指针的简单使用

  1. #include<stdio.h>
  2. int main(){
  3. //定义及初始化
  4. int a = 10, b = 20;
  5. int *x = &a, *y = &b;
  6. int **p = &x;
  7. printf("a = %d, *x = %d, *y = %d, **p = %d\n", a, *x, *y, **p);
  8. //改变一级指针x和y指向地址的数据值
  9. *x = 100;
  10. *y = 200;
  11. printf("a = %d, *x = %d, *y = %d, **p = %d\n", a, *x, *y, **p);
  12. //改变二级指针指向的地址
  13. p = &y;
  14. printf("a = %d, *x = %d, *y = %d, **p = %d\n", a, *x, *y, **p);
  15. //改变二级指针指向地址的数据值
  16. **p = 400;
  17. printf("a = %d, *x = %d, *y = %d, **p = %d\n", a, *x, *y, **p);
  18. return 0;
  19. }

定义并初始化指针,对指针地址及地址数据值进行修改操作,运行效果如下:
在这里插入图片描述

指向函数的指针

在C语言中,函数名代表函数的入口地址。可以定义一个指针变量,接收函数的入口地址,让它指向函数,这就是指向函数的指针,也称为函数指针。通过函数指针可以调用函数,它还可以作为函数的参数。

函数指针的定义

函数指针定义的一般格式为:
类型名 (*变量名) (参数类型表)
类型名指定函数返回值的类型,变量名是指向函数的指针变量名称。例如:

  1. int (*funptr) (int, int);

定义了一个函数指针funptr,它可以指向有两个整型参数且返回值类型为int的函数。

通过函数指针调用函数

在使用函数指针前,需要先对它赋值。赋值时,将一个函数名赋给函数指针,但该函数必须已定义或声明,且函数返回值的类型和函数指针的类型要一致。
假设函数fun(x, y)已定义,它有两个整型参数且返回一个整型数据,则:

  1. funptr = fun;

将函数fun()的入口地址赋给funptr,函数指针funptr指向函数fun()。

调用函数有两种方法,直接用函数名或通过函数指针。例如,调用上述函数fun()的两种方法。

  1. fun(3, 5);
  2. (*funptr)(3, 5);

通过函数指针调用函数的一般格式为:(*函数指针名)(参数表);

函数指针作为函数的参数
C语言的函数调用中,函数名或已赋值的函数指针也能作为实参,此时,形参就是函数指针,它指向实参所代表函数的入口地址。

函数指针的基本使用

计算a * a + a / b表达式的值,利用指针函数计算(题目很简单,熟悉一下函数指针的用法)。代码如下:

  1. #include<stdio.h>
  2. //声明函数,三个参数,一个函数指针参数,两个double类型的参数
  3. double calc(double(*funp)(double), double a, double b);
  4. double f(double x);
  5. int main(){
  6. double result;
  7. //定义函数指针
  8. double (*funp)(double);
  9. //函数名f作为函数calc的实参
  10. result = calc(f, 3.0, 5.0);
  11. printf("结果为:%.2f\n", result);
  12. //对函数指针funp赋值
  13. funp = f;
  14. //函数指针funp作为函数calc的实参
  15. result = calc(funp, 3.0, 5.0);
  16. printf("结果为:%.2f\n", result);
  17. return 0;
  18. }
  19. //函数指针funp作为函数的形参
  20. double calc(double(*funp)(double), double a, double b){
  21. double s;
  22. //调用funp指向的函数
  23. s = ((*funp)(a) + a / b);
  24. return s;
  25. }
  26. double f(double x){
  27. return x*x;
  28. }

运行效果如下:
在这里插入图片描述
指针作为函数的返回值

在C语言中,函数返回值的类型除了整型、字符型和浮点型等基本数据类型外,还可以返回指针类型,即函数可以返回一个地址。但不能返回函数内部定义的局部数据对象的地址。返回指针的函数一般都返回全局数据对象或主调函数中数据对象的地址。 如下案例:

输入一个字符串和一个字符,判断输入的字符串中是否包含输入的字符。

  1. #include<stdio.h>
  2. //函数声明
  3. char *match(char *s, char ch);
  4. int main(){
  5. char ch, str[80], *p = NULL;
  6. printf("请输入一个字符串:");
  7. gets(str);
  8. //输入一个字符
  9. printf("请输入一个字符:");
  10. ch = getchar();
  11. //调用函数match()
  12. if((p = match(str, ch)) != NULL){
  13. printf("字符串%s中包含字符%c", str, ch);
  14. }else{
  15. printf("字符串%s中不包含字符%c", str, ch);
  16. }
  17. return 0;
  18. }
  19. //函数返回值的类型是字符指针
  20. char *match(char *s, char ch){
  21. while(*s != '\0'){
  22. //如果在字符串s中找到字符ch,返回相应的地址
  23. if(*s == ch){
  24. return(s);
  25. }else{
  26. s++;
  27. }
  28. }
  29. //在s中没有找到ch,返回空指针
  30. return (NULL);
  31. }

效果如下:
在这里插入图片描述


由于初学C语言,上述内容如有错误地方,恳请各位大佬指出!

发表评论

表情:
评论列表 (有 0 条评论,407人围观)

还没有评论,来说两句吧...

相关阅读

    相关 指针数组函数指针

    > 写在前面:学习的第一门语言是Java,之前对C也了解一点,也只是了解一点,在加上长时间没有接触了,基本就只会一个Hello World了。现在由于准备升本考试,不得不从头开