保姆级指针进阶教程——【C语言】

简介: 在之前的博客中,我简单的介绍了什么是指针、指针的大小、运算、二级指针等等一些指针的基础知识,接下来我将带大家深入的了解一下指针,保证让大家对指针有更深刻的理解。

在之前的博客中,我简单的介绍了什么是指针、指针的大小、运算、二级指针等等一些指针的基础知识,接下来我将带大家深入的了解一下指针,保证让大家对指针有更深刻的理解。


字符指针


在指针的类型中,我们见过一种类型的指针char*,这个就是字符指针,这类指针指向的类型对象就是字符;


一般使用方法:


#include<stdio.h>
int main(void)
{
    char ch = 'w';
    char *pc = &ch;//*pc就是字符指针
    *pc = 'w';
    return 0;
}


我们可以通过一个例子更好的理解字符指针:当我们不方便使用ch修改其中的值时,我们就可以使用字符指针来间接修改其中的内容,类似于借刀杀人。


但是字符指针还有一种特殊的用法:可以用字符指针直接创建一个常量字符串,例如char*p = "abcd";原本我们是使用一个字符数组进行创建一个字符串的,但是字符指针也是可以创建字符串的;但是二者有一点不同的区别:*p指向的字符串是存放在代码区里,而字符数组存放的地方在栈区。


int main(void)
{
  char arr[] = "abcdef";
  const char* p = "abcdef";//常量字符串
  printf("%s\n", p);
  printf("%c\n", *p);
  return 0;
}


当我们使用字符指针p打印整个字符串和解引用p时所打印的结果如下:


7d83598e9eda48e881c46c36a190cab0.png


由次我们可以得出字符指针p指向的是字符串的首元素的地址,并不是整个字符串。


c618945942574f37abad0a815edc5ae8.png


通过一个典型的例题我们就可以看出使用数组创建字符串和使用字符指针创建字符串的区别:


#include <stdio.h>
int main()
{
  char str1[] = "hello world.";
  char str2[] = "hello world.";
  const char* str3 = "hello world.";
  const char* str4 = "hello world.";
  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;
}


上面的一串程序所输出的结果应该是怎么样的呢?


1fc929360eda4031956b61f9292c48c7.png


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


这就是关于字符指针的一些知识。


指针数组


什么是指针数组呢?我们可以先进行几个类比。整型数组:是存放整型的数组、字符数组:存放字符的数组;那指针数组顾名思义就是存放指针的数组。


int* arr1[10]; //整形指针的数组


char *arr2[4]; //一级字符指针的数组


char **arr3[5];//二级字符指针的数组


这些都叫做指针数组。


指针数组最常用的功能就是模拟实现二维数组:


int main(void)
{
  int arr1[] = { 1,2,3,4,5 };
  int arr2[] = { 2,3,4,5,6 };
  int arr3[] = { 3,4,5,6,7 };
  int* arr[] = { arr1,arr2,arr3 };
  for (int i = 0; i < 3; i++)
  {
    for (int j = 0; j < 5; j++)
    {
      printf("%d ", arr[i][j]);
    }
    printf("\n");
  }
  return 0;
}


将三个相同类型的一维数组放入到指针数组中去,利用指针数组进行访问即可模拟实现出二维数组。因为数组名就是数组首元素的地址,如果有多个数组,我们就可以将多个数组放入指针数组中进行处理,将这些数组一一打印出来。程序运行结果如下:


e992bff9eac34d5aa14275689d55c4cd.png


这就是指针数组的一些内容。


数组指针


数组指针是指针?还是数组?


答案是:指针。


我们都知道整形指针: int * pint; 能够指向整形数据的指针。 浮点型指针: float * pf; 能够指向浮点型数据的指针。 那数组指针应该是:能够指向数组的指针。


那下面代码中那个是数组指针呢?


int* p1[10];


int (*p2)[10];


p1和p2分别是什么?


答案:p1是指针数组,p2是数组指针


因为[]的优先级要高于*号,p2先和*结合,说明p2是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p2是一个指针,指向一个数组,叫数组指针。


数组名VS&数组名


对于int arr[10];这个数组,其中arr代表什么,&arr又代表什么呢?


我们知道arr是数组名,代表数组的首元素的地址。那&arr是多此一举吗?我们先来看一段代码:


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

a9d3dc5b170249ed886f6c6dfc865bf2.png


可见数组名和&数组名打印的地址是一样的,但是arr和&arr真没有区别吗?答案是有区别。


862ad98d319647418d3ddda19d3c99c3.png


arr的类型为int*而&arr的类型为int(*)[10];


#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;
}

104777e39c2845a094160bbf08ad8dbe.png


根据上面的代码我们发现,其实&arr和arr,虽然值是一样的,但是意义应该不一样的。


实际上: &arr 表示的是整个数组的地址,而不是数组首元素的地址。&arr的数据类型为int (*)[10],是数组指针类型并不是int*。而数组+1跳过的也是整个数组的大小,所以&arr+1和arr+1相差40.


数组指针的使用方法


那数组指针应该怎么使用呢?


数组指针是指向的是数组,那数组指针中存放的应该是数组的地址,一般在二维数组上使用比较方便。


#include <stdio.h>
void print_arr1(int arr[3][5], int row, int col)
{
 int i = 0;
 for(i=0; i<row; i++)
 {
 for(j=0; j<col; j++)
 {
 printf("%d ", arr[i][j]);
 }
 printf("\n");
 }
}
void print_arr2(int (*arr)[5], int row, int col)
{
 int i = 0;
 for(i=0; i<row; i++)
 {
 for(j=0; j<col; j++)
 {
 printf("%d ", arr[i][j]);
 }
 printf("\n");
 }
}
int main()
{
 int arr[3][5] = {1,2,3,4,5,6,7,8,9,10};
 print_arr1(arr, 3, 5);
 print_arr2(arr, 3, 5);
 return 0;
}


我们分别使用了二维数组接收和数组指针接收,这两个都可以实现二维数组的传参,但本质其实都是指针。


对于上面的程序中的数组名arr,表示首元素的地址,但是二维数组的首元素是二维数组的第一行,所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址,可以用数组指针来接收。


31a35f2cf53649b294c764f82fdc7ffd.png


函数指针


顾名思义就是指向函数的指针就是函数指针。


函数有地址吗?如果有那我们一定可以使用指针来指向函数的地址。我们先来看一段代码:


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


输出结果:


496c09d8ce6449adaedd1b38c00493e9.png


输出了两个地址,这两个地址都是test函数的地址 ,证明函数是有地址的,那一定就有函数指针的存在。并且我们知道了在函数指针中函数名和&函数名表示的意义是相同的,这里可以与数组哪里区分一下。


那我们应该用什么样的指针来接收函数指针呢?这是我们可以类比数组指针的写法来写:


当定义的函数已经确定时,我们就要使用自定义函数的返回值和参数来进行函数指针的仿写。举个例子:


int add(int x, int y)
{
        return x+y;
}
//定义 int (*pf)(int, int) = &add;或者 int (*pf)(int, int) = add;
void test()
{
       .......
}
//定义 void(*pd)() = &test或者 void(*pd)() = test;


当我们调用函数时我们可以使用最原始的方法,直接写出函数将其调用,也可以使用函数指针调用函数。


int add(int x, int y)
{
  return x + y;
}
int main(void)
{
  int (*pf)(int,int) = add;
  int r = add(3, 5);
  int m = pf(4, 5);
  printf("%d %d\n", r, m);
  return 0;
}


此程序使用了两种方法调用add函数,都是可以成功调用的,结果如下:


a397566512634a46840ee1882323562a.png


这就是函数指针,我们一般在回调函数中使用,如果不了解回调函数可以访问作者主页,里面有回调函数的讲解!!!


函数指针数组


数组是一个存放相同类型数据的存储空间,上面讲解了指针数组,那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?


//模板
int (*parr1[10])();
parr1 先和 [] 结合,说明 parr1是数组,数组的内容是什么呢? 是 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;
}
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;
}


这样比用switch case语言更清晰,而且使代码简洁明了。


指向函数指针数组的指针


指向函数指针数组的指针是一个指针指针指向一个数组 ,数组的元素都是函数指针;那该如何定义呢?


为了防止混淆,我将上面两个模块定义和指向函数指针数组的指针进行区分:


void test(const char* str)
{
 printf("%s\n", str);
}
int main()
{
 //函数指针pfun
 void (*pfun)(const char*) = test;
 //函数指针的数组pfunArr
 void (*pfunArr[5])(const char* str);
 pfunArr[0] = test;
 //指向函数指针数组pfunArr的指针ppfunArr
 void (*(*ppfunArr)[5])(const char*) = &pfunArr;
 return 0;
}


其实我们可以加入数组和指针继续往下套,这样也就越来越复杂,但是如果感兴趣可以继续往下探索,这里我就不多说了。


以上就是指针进阶的全部内容,感谢大家观看,希望可以指出我的不足和错误,我将在你们的支持下不断完善自己!!!  

目录
相关文章
|
16天前
|
C语言
C语言:数组和指针笔试题解析(包括一些容易混淆的指针题目)
C语言:数组和指针笔试题解析(包括一些容易混淆的指针题目)
|
1月前
|
存储 程序员 编译器
爱上C语言:指针很难?来来来,看看这篇(基础篇)
爱上C语言:指针很难?来来来,看看这篇(基础篇)
|
2月前
|
存储
指针进阶详解(下)
指针进阶详解(下)
22 0
|
5天前
|
C语言
c语言指针总结
c语言指针总结
12 1
|
5天前
|
搜索推荐 C语言
详解指针进阶2
详解指针进阶2
|
16天前
|
存储 C语言
指针深入解析(C语言基础)带你走进指针,了解指针
指针深入解析(C语言基础)带你走进指针,了解指针
|
16天前
|
C语言 C++
C语言:指针运算笔试题解析(包括令人费解的指针题目)
C语言:指针运算笔试题解析(包括令人费解的指针题目)
|
18天前
|
安全 C语言
指针与字符串:C语言中的深入探索
指针与字符串:C语言中的深入探索
15 0
|
19天前
|
存储 监控 C语言
c语言的指针
c语言的指针
22 0