【C语言】指针进阶篇实用的指针

目录

前言

1.字符指针

2.注意区分指针数组和数组指针

1.指针数组

2.数组指针 

1.什么是数组指针

2.arr与&arr

3.指针参数的传递

1.一维数组传参

2.二维数组传参

4.函数指针

5.函数指针的使用之回掉函数*

6.函数指针数组

7.小结


前言

上一篇我们已经对指针有了初步的了解,你那么这一篇就让我们来学习更多更实用和有趣的指针使用方法吧。

1.字符指针

在指针类型中我们知道有一种指针类型char*

int main()
{
    char ch = 'w';
    char *pc = &ch;
    *pc = 'w';
    return 0;
}

还有一种写法我们可以将一个字符串放入其中

#include<stdio.h>
int main()
{
const char* arr="hello word";
printf("%s",arr);
return 0;
}

这种const修饰的字符串我们称其为常量字符串,不能修改.

当然不能修改并不意味着不能观察,就像银行柜台的钱只能看不能动,你甚至可以数一下柜台里有多少人,几台验钞机但是你不能去拿它。像下面这样一段代码就没有问题。不过要注意你可以把arr的内容放到arr2中,但不能将arr2放到arr中。更改常量字符串的内容是初学者很容易出现的错误

#include<stdio.h>
#include<string.h>
int main()
{
const char* arr="hello word";
char arr2[]="################";
strcpy(arr2,arr);
printf("%s",arr2);
return 0;
}

e4f4ff8e865a4fbb9037682066dd60cf.png

 而且注意arr并没有将他们全部存入进去,只是将字符串的首地址存放在了其中

0d20aae8cc3942129b4ab74bda904145.png

 下面我们可以看一道曾经出现过的面试题来排除一下认知上可能出现的错误

#include <stdio.h>
int main()
{
    char str1[] = "hello word.";
    char str2[] = "hello word.";
    const char *str3 = "hello word.";
    const char *str4 = "hello word.";
    if(str1 ==str2)
 printf("str1 and str2 are same\n");
    else
 printf("str1 and str2 are not same\n");
       
    if(str3 ==str4)
 printf("str3 and str4 are same\n");
    else
 printf("str3 and str4 are not same\n");
       
    return 0;
}

882a014551dd48a1aee2b76790ca3d49.png
这里str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当 几个指针。指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化 不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4不同。

实际上对比的是存储他们的内存空间是不是同一个地方。

2.注意区分指针数组和数组指针

1.指针数组

指针数组就是用来存放指针的数组,它本质上是一个数组,只不过里面存放的内容是相应类型的指针。

int* arr1[6]; //整形指针的数组
char *arr2[6]; //一级字符指针的数组
char **arr3[6];//二级字符指针的数组

2.数组指针 

1.什么是数组指针

数组指针就是是指向数组的指针,它本质上是一个指针。

不妨我们来判断一下下面这两个谁是数组指针把

1.int *p1[6];
2.int (*p2)[6];

这就牵涉到int和p谁先和*结合了,像1之中的*就先和int结合在了一起所以他是一个存放int*类型(整型指针)的数组。

而2里面我们加上括号让它先跟p2结合在了一起,说明p2是一个指针类型的变量,指向一个大小为6个整型的数组。

因为[ ]的优先级是高于*的所以如果我们不用()去限定它,它会有优先往数组的方向去判定定,而不是指针。

2.arr与&arr

int arr[10];

 我们学习过数组名表示的是首元素地址,在各种使用和传递的过程中传递的就是首元素的地址。

那么数组名arr和&arr有区别吗?

这就牵扯到我们上篇内容所讲的指针类型的问题了

不妨看一下下面这段代码

#include <stdio.h>
int main()
{
 int arr[10] = { 0 };
 printf("arr = %p\n", arr);
 printf("&arr= %p\n", &arr);
 printf("arr+1 = %p\n", arr+1);
 printf("&arr+1= %p\n", &arr+1);
 return 0;
}

运行结果982d553822e14679a34d5f27449042ba.png

 可以看到他们之中存放的都是首元素地址,但是当加1是arr跳过了4个字节也就是1个int的类型的大小,而&arr加上一个1则跳过了整个数组的大小.所以这个取地址arr实际上取出的是整个数组的地址它的实际指针类型是数组指针int(*)[10];

3.指针参数的传递

我们在使用指针的过程中经常会用到指针来传递或者接受参数,那么函数的参数该怎么设计呢

1.一维数组传参

int main()
{
	int arr[10] = {0};
    test(arr);
}

像这样我们创建一个一维数组arr,将它作为参数传递给我们创建的test函数,那么test又该用什么类型来接受它呢。

void test(int arr[])
{}
void test(int arr[10])
{}
void test(int *arr)
{}

作为一个一维数组,我们当然可以用一个一维数组数组类型来接受了。同时我们也知道数组名其实就是首元素的地址,因此用一个指针类型来接受也是可以的。

2.二维数组传参

int main()
{
 int arr[3][5] = {0};
 test(arr);
}

我们已经知道一维数组怎么传参了,那么一个二维数组呢。

void test(int arr[3][5])
{}
void test(int arr[][])//×
{}
void test(int arr[][5])
{}

如果我们已经学过数组了,那么应该也知道当我们在将二维数组传参给函数时接收它的数组行是可以省略的,但列不能省略。如果想用一个指针来接收该怎么实现呢

void test(int *arr)//1×
{}
void test(int* arr[5])//2×
{}
void test(int (*arr)[5])//3✓
{}
void test(int **arr)//4×
{}

如果我们用int *类型去接受它,只能得到第一行的地址,所以1是不对的。

int* arr[5]是一个五个元素大小的指针数组自然也不能用来接受我们的二维数组。

而int(*arr)[5]则是一个指向指向数组的指针,5说明它指向的数组每行有多少个元素,所以正确。

int**arr是一个二级指针,二级指针是用来存放指针变量的自然不能用来接收一个二维数组。

4.函数指针

#include<stdio.h>
void test()
{
	printf("hello");
}
int main()
{
	printf("%p\n", test);
	printf("%p\n", &test);
	return 0;
}

test和&test在使用时是没有区别的像下面这段代码,test和&test都是一样的。

#include<stdio.h>
void test(int a)
{
	printf("%d\n",a);
}
int main()
{
	test(5);
	void (*pt)(int) = test;
	pt(6);
	return 0;
}

 可以看到当我们想要创建一个函数指针时,首先要先告诉它函数返回值类型。然后写出指针变量名,再写出函数的接收值的类型。注意(*pt)的括号不能去掉如果去掉的话*会先跟void的结合表示一个返回值为void*的函数。

 同时也要注意我们在使用时直接使用*pt跟pt都是一样的都能顺利调用函数

你已经了解了函数指针那么来看一下这段代码解释一下它把。

(*(void (*)())0)();

其实这段代码并没有看起来那么“阴间”,我们一点一点的拆开来看,首先

void(*)( )我们可以很明白的看出它是一个函数指针,而在一个数前面加()的意思是强制类型转换

(void(*)())0也就是把0强制转换成void(*)( )类型

( *    (void (*)())   0 )   ()则是说明将0强类型转换之后再去调用0地址处的函数

当然我们在日常中一般是用不到这么“诡异”的代码的,而且在一些平台0的地址处是不能随便访问的。

void (*signal(int , void(*)(int)))(int);

我们在看这些代码时一步一步拆开来看即可首先void(* )(int)这样来看它就是一个普通的函数指针了,返回值类型为void接收一个int类型的形参。它说明了函数signal的返回类型是一个函数指针

signal(int , void(*)(int))

函数名为signal,参数为int类型和函数指针类型的函数。

 这一段其实跟int  test(int a);这样的代码没有本质上的区别都是一个函数声明,只不过返回类型和参数看起来比较复杂罢了。

我们的int  test(int a,char ch);声明了一个返回类型为int 参数为int和char类型的函数。

而上面这段代码则是声明一个返回类型为函数指针,参数为int和函数指针类型的函数。

如果你暂时理解不了也没有关系,在后面见到更多特殊的返回类型之后一般会慢慢理解。

5.函数指针的使用之回调函数*

讲了这么多,函数指针在实际的使用中又有什么作用呢。这里就牵扯到一个重要的内容回调函数

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个 函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数 的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的

假设我们需要一个能够加减乘除的计算器那么我们可以写下面一段代码

#include <stdio.h>
int add(int a, int b)
{
    return a + b;
}
int sub(int a, int b)
{
    return a - b;
}
int mul(int a, int b)
{
    return a * b;
}
int div(int a, int b)
{
    return a / b;
}
int main()
{
    int x, y;
    int input = 1;
    int ret = 0;
    do
    {
        printf("*************************\n");
        printf(" 1:add           2:sub \n");
        printf(" 3:mul           4:div \n");
        printf("*************************\n");
        printf("请选择:");
        scanf("%d", &input);
        switch (input)
        {
        case 1:
            printf("输入操作数:");
            scanf("%d %d", &x, &y);
            ret = add(x, y);
            printf("ret = %d\n", ret);
            break;
        case 2:
            printf("输入操作数:");
            scanf("%d %d", &x, &y);
            ret = sub(x, y);
            printf("ret = %d\n", ret);
            break;
        case 3:
            printf("输入操作数:");
            scanf("%d %d", &x, &y);
            ret = mul(x, y);
            printf("ret = %d\n", ret);
            break;
        case 4:
            printf("输入操作数:");
            scanf("%d %d", &x, &y);
            ret = div(x, y);
            printf("ret = %d\n", ret);
            break;
        case 0:
            printf("退出程序\n");
            break;
        default:
            printf("选择错误\n");
            break;
        }
    } while (input);

    return 0;
}

但是这段代码中却存在一些问题,我们在每一段都写有           

printf("输入操作数:");

 scanf("%d %d", &x, &y);

ret = add(x, y);

这种相似但重复的内容。

并且这些函数的返回值和参数类型都相同,我们可以写下面一段代码

void counter(int(*con)(int , int))
{
    int  x = 0, y = 0;
    printf("输入操作数:");
    scanf("%d %d", &x, &y);
    printf("ret = %d\n", con(x,y));
}

上面add,sub,mul,div这几个函数,它们的返回值类型都是int,参数类型都是(int,int)所以我们可以使用这样一个函数指针去接收他们,然后调用

int(*con)(int , int)

上面的计算器经过改造之后就变成了下边这样

#include <stdio.h>
int add(int a, int b)
{
    return a + b;
}
int sub(int a, int b)
{
    return a - b;
}
int mul(int a, int b)
{
    return a * b;
}
int div(int a, int b)
{
    return a / b;
}
void counter(int(*con)(int , int))
{
    int  x = 0, y = 0;
    printf("输入操作数:");
    scanf("%d %d", &x, &y);
    printf("ret = %d\n", con(x,y));
}
int main()
{
    int x, y;
    int input = 1;
    int ret = 0;
    do
    {
        printf("*************************\n");
        printf(" 1:add           2:sub \n");
        printf(" 3:mul           4:div \n");
        printf("*************************\n");
        printf("请选择:");
        scanf("%d", &input);
        switch (input)
        {
        case 1:
            counter(add);
            break;
        case 2:
            counter(sub);
            break;
        case 3:
            counter(mul);
            break;
        case 4:
            counter(div);
            break;
        case 0:
            printf("退出程序\n");
            break;
        default:
            printf("选择错误\n");
            break;
        }
    } while (input);
    return 0;
}

是不是简洁了许多,当然回掉函数的用处并不仅止于此。

我们有一种类型为void*类型的指针它可以接收任意类型的指针,当然在使用的时候要先转换为其它类型才能使用因为void是无类型的。

#include<stdio.h>
#include<string.h>
int test(void*x, void*y,const char* ch)
{
	if (strcmp(ch, "int") == 0)
	{
		return *(int*)x - *(int*)y;
	}
	if (strcmp(ch, "char") == 0)
	{
		return *(char*)x - *(char*)y;
	}
	return 0;
}
int main()
{
	int a = 1, b = 2;
	char c = 'b', d = 'a';
	int ret=test(&a, &b, "int");
	int bet = test(&c, &d,"char");
	printf("%d\n", ret);
	printf("%d\n", bet);
}

 这段代码编译器没有报错也没有警告当然写这样一段代码只是为了说明我们可以用void*接受任意类型并在使用的时候再强制类型转换成为我们所需要的类型。

而利用这样一个特性我们可以以较少的代码量完成一些功能比如写一个能够排序任意类型的排序函数。

当然这些只是回调函数的一些基本用法。

6.函数指针数组

函数指针数组,数组是一类相同元素的集合。而函数指针作为一种元素当然也能够放入一个数组之中。

例如上面我们写过的计算器代码add,sub,mul,div他们的返回类型,跟参数都相同也可以视为一类相同元素

int (*arr[5])(int x,int y)={0,add,sub,mul,div};

 既然是一个数组自然可以通过下标去调用。

#include <stdio.h>
int add(int a, int b)
{
           return a + b;
}
int sub(int a, int b)
{
           return a - b;
}
int mul(int a, int b)
{
           return a*b;
}
int div(int a, int b)
{
           return a / b;
}
int main()
{ 
     int x, y;
     int input = 1;
     int ret = 0;
     int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
     while (input)
     {
          printf( "*************************\n" );
          printf( " 1:add           2:sub \n" );
          printf( " 3:mul           4:div \n" );
          printf( "*************************\n" );
          printf( "请选择:" );
          scanf( "%d", &input);
          if ((input <= 4 && input >= 1))
         {
              printf( "输入操作数:" );
              scanf( "%d %d", &x, &y);
              ret = (*p[input])(x, y);
         }
          else
               printf( "输入有误\n" );
          printf( "ret = %d\n", ret);
     }
      return 0;
}

7.小结

指针的基本语法与使用在这里基本已经介绍完毕,当然也只是基本内容,更加深入的内容依赖于后续的使用需求和学习。 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值