论指针如何使用

简介: 字符指针,就是指向字符的指针,接收字符类型变量的地址,正常存放地址,并且拿出来使用是将这个变量的地址传给指针保护然后再通过解引用操作对变量进行访问。

该文章将详细的介绍指针更深入的知识,在这里你将对指针的理解更加更加全面,助你修炼内功,在编程的路上更上一层。当然有不足的地方望提出,必修改,希望该文章能帮助你更好的理解指针,加油!!!


1.字符指针


字符指针,就是指向字符的指针,接收字符类型变量的地址,正常存放地址,并且拿出来使用是将这个变量的地址传给指针保护然后再通过解引用操作对变量进行访问。


int main()
{
  char ch = 'a';
  char* pa = &ch;
  printf("%c", *pa);
  return 0;
}


不过还有一种使用方式:


int main()
{
  char* pa = "hello world";
  printf("%s", pa);
  return 0;
}


这是表示把"hello world"整个字符串的地址传给指针变量pa了吗?


当然不是,实质上它是把这个字符串的首字符的地址串给了pa,pa其实指向的是’h’。


597f202a3ebd41b98ac0546368e5d696.png


0686527b89e242588096ebd829ea1f24.png


打印字符串只需要起始地址就可以了,打印到\0会自动停止。


小试一题:


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


答案呢


16a5633652cd44ed80cf56f266328191.png


解析:


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


数组名表示首元素的地址,str1和str2这是两个数组虽然里面元素内容相同,但开辟的空间肯定不同,所以首元素地址不可能相同的。str3和str4分别都是接收字符串首字符的地址,"hello world"这个常量字符串是只有一种,在内存中没有必要再开辟一个空间给它所以首元素的地址是相同的也就是说str3==str4.


2.指针数组


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


当然是数组啦,是存放指针的数组


以下指针数组都表示什么呢?


int *arr1[10];//一个存放着10个整形指针的指针数组
char *arr2[5];//一个存放5个字符指针的指针数组
char **arr3[10];//一个存放10个二级字符指针的指针数组


3.数组指针


3.1数组指针的定义


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


答案:是指针


我们知道整形指针 是可以指向整形数据的指针


字符指针是可以指向字符类型的指针,那数组指针应该是可以指向数组的指针

int *p[10];
int (*p1)[10];
//p,p1分别指的是什么?


第一个表示一个指针数组,存放10个整形指针的数组


第二个表示数组指针,表示这个指针指向的是一个10个元素的整形数组。


解析:


int (* p1)[10];


首先看()里面的*与p结合,表面p是个指针变量,然后再和int [10]结合,表示指针指向的是一个大小为10个元素的数组,所以这个叫数组指针。


注意:因为[ ]的优先级比 * 高所以需要加上()来保证 * 与p先结合。


3.2&数组名VS数组名


数组名的意义:

  1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。
  2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址
  3. 除此之外所有的数组名都表示首元素的地址


看下面代码:


int main()
{
  int arr[5] = { 0,1,2,3,4 };
  printf("%p\n", arr);
    printf("%p\n", &arr);
  printf("%p", arr+1);
  printf("%p", &arr+1);
  return 0;
}

60338b63b9ec4bf085951956f8ed70d9.png


这里发现arr与&arr的地址是一样的那是不是说明&arr与arr是一样的呢?答案是:当然不是啦


&arr取出的是整个数组地址,而arr是首元素地址,只不过它们表示相同而已


本例中 &arr 的类型是: int(*)[10] ,是一种数组指针类型


数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40


接下来我们用图说


```c
int main()
{
  int arr[5] = { 0,1,2,3,4 };
  int sz = sizeof(arr);
  int* p = &arr;
  printf("sz=%d\n", sz);
  printf("%p", p);
  return 0;
}


36db6c68fd8041c5b735b889cb522c14.png

f4ac5340009042668cdbf24f7a537e16.png


8b1a93f73ce34b59883b7e6cd5ea6cf3.png


所以&arr取出的是整个数组而arr(数组名)是首元素地址。


3.3数组指针的使用


数组指针字母使用呢?


数组指针是指针,是一个指向数组的指针。


那么看下面代码:


#include <stdio.h>
int main()
{
  int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
  int(*p)[10] = &arr;//&arr取出的是整个数组的地址,用数组指针来接收
  return 0;//但很少这样使用
}


一个数组指针的使用:


void Print1(int arr[2][5], int row, int col)
{
  int i, j;
  for (i = 0; i < row; i++)
  {
    for (j = 0; j < col; j++)
    {
      printf("%d", arr[i][j]);
    }
    printf("\n");
  }
}
void Print2(int(*arr)[5], int row, int col)
{
  int i, j;
  for (i = 0; i < row; i++)
  {
    for (j = 0; j < col; j++)
    {
      printf("%d", arr[i][j]);
    }
    printf("\n");
  }
}
int main()
{
  int arr[2][5] = {1,2,3,4,5,6,7,8,9,10};
  Print1(arr, 2, 5);
  //arr数组名表示首元素的地址
  //但是二维数组中arr表示二维数组第一行中的地址也就是一维数组的地址
  //所以可以用数组指针来接收.
  Print2(arr, 2, 5);
  return 0;
}


学了指针数组和数组指针我们来一起下面代码是什么意思吧


int arr[5];//一维数组,五个整形元素
int *arr1[10];//指针数组,10个整形指针
int (*arr2)[10];//数组指针,指向一个10个整形的数组
int (*arr3[10])[5];
首先arr3与[10]结合成为数组,然后这个数组的类型是int(*)[5]这个是什么?,这个是数组指针呀,说明
这个数组有10个数组指针,这10个数组指针指向的是一个5个整形元素的数组。


如图:


13ab0d361a744d0eb81e16e2d95c396e.png


4.数组传参和指针传参


void test(int arr[])//数组传参,数组接收
{}
void test(int arr[10])//跟上面一样
{}
void test(int* arr)//数组传参,指针接收
{}
void test2(int* arr[20])//数组传参,指针接收
{}
void test2(int** arr)//数组传参,指针接收
{}
因为数组名就是首元素的地址,所以数组名传参,可以用数组来接收,也可以用地址来接收,只不过要注意接收的是一级指针还是二级指针。
int main()
{
  int arr[10] = { 0 };
  int* arr2[20] = { 0 };
  test(arr);
  test2(arr2);
  return 0;
}


4.2二维数组的传参


void test(int arr[3][5])//ok?二维数组传参,二维数组接收
{}
void test(int arr[][])//ok?这个可不行喔
{}
void test(int arr[][5])//ok?跟第一个一样


注意点:二维数组传参,函数参数的设计只能省略第一个[ ]的数字。


因为对于一个二维数组,可以不知道到多少行,但不能不知道一行多少个元素。这样才方便运算。


void test(int *arr)//ok?还记得上面说的吗,二维数组的数组名相当于第一行的数组地址所以是类型是数组指针类型的,应该用数组指针来接收
{}
void test(int* arr[5])//ok?不行这是个指针
{}
void test(int (*arr)[5])//ok?这个可以是数组指针,指针指向一个五个整形的数组
{}
void test(int **arr)//ok?不行,这个是二级指针
{}
int main()
{
 int arr[3][5] = {0};
 test(arr);
 }


4.3一级指针传参


//用一级指针来接收
void print(int *p, int sz)
{
 int i = 0;
 for(i=0; i<sz; i++)
 {
 printf("%d\n", *(p+i));
 }
}
int main()
{
 int arr[10] = {1,2,3,4,5,6,7,8,9};
 int *p = arr;
 int sz = sizeof(arr)/sizeof(arr[0]);
 //一级指针p,传给函数
 print(p, sz);
 return 0;
 }


想一想当函数参数为一级指针时,可以接收上面参数呢?


void test1(int * p)
{ }
//test1函数能接收什么参数?
void test2(char* p)
{ }
//test2函数能接收什么参数?


4.4二级级指针传参


void test(int** ptr)
{
 printf("num = %d\n", **ptr); 
}
int main()
{
 int n = 10;
 int*p = &n;
 int **pp = &p;
 test(pp);//pp是二级指针
 test(&p);//p是一级指针,&p就应该用二级指针来接收了
 return 0;
 }


当函数的参数为二级指针的时候,可以接收什么参数?


void  test(char**p)
{
}
int mian()
{
  char c='b';
  char *pc=&c;
  char**pcc=&pc;
  char *arr[10];
  test(&pc);//指针的地址,需要二级指针接收
  test(ppc)//二级指针,二级指针接收
  test(arr);//数组名表示首元素地址,每个元素的类型是char类型的指针,然后再取地址,当然可以用二级指针接收。
}


5.函数指针


先看下下面的代码:


void test()
{
  printf("hehe\n");
}
int main()
{
  printf("%p\n", test);
  printf("%p\n", &test);
  return 0;
}


输出的结果:


e42a05fb0d354990b40c510f6ed653a5.png


输出的是两个地址,这两个地址呢都是test函数的地址,从图中可以看出来,test和&test的地址是一样的。


那我们想一下如何存函数的地址的,存地址需要指针来存,那函数的地址也需要指针来存。


void test()
{
 printf("hello world\n");
}
//下面p1和p2哪个有能力存放test函数的地址?
void (*p1)();
void *p2();


首先要求存放地址就要求p1,p2是指针,那哪个是指针呢?


p1可以存放,*首先和p1结合,表明p1是一个指针,那这个指针指向一个void ();函数,这个函数参数为空,返回值为void,而p2先跟()结合成为一个函数,然后再和 *结合,成为一个嗯,,,不知道的东西。


理解两段有趣的代码:


//代码1
(*(void (*)())0)();
这个代码怎么看呢?
//代码2
void (*signal(int , void(*)(int)))(int);


代码1:


(* (void (*)()) 0 )();0前面有个括号,括号里面有void


()()这个是上面呢?如果不知道那这样呢?void(p)()这个是一个函数指针,而void ( * )()则是函数指针类型,所以0被强制转化成函数指针类型,这个函数参数为0,返回值为void,对这个函数指针解引用操作,来调用这个函数,调用这个函数的参数为0。


代码2:


首先看到signal没有与号结合所以不是个指针,那它就是函数名,这个函数里面的参数是int 和void( * )(int),既然函数名和函数参数都有了,那函数的返回值是什么呢?把signal(int ,void()(int))去掉,就剩void ( * )(int)说明这个函数的返回值也是个函数指针。


代码1:
(*(void (*)())0)();
  //这是个函数调用
  //调用0地址处的一个函数
  //首先将0强制类型转化成(void(*)())类型的函数指针2
  //然后*解引用调用0处地址的函数


代码2:
void (  *signal(int, void(*)(int))  )(int);
  //这是一个函数声明;
  //声明的函数名称叫signal
  //signal函数有两个参数,第一个是int类型,第二个是函数指针类型,该函数指针指向那个函数的参数是int,返回值为void
  //signal函数的返回值类型也是一个函数指针,该函数指针指向的函数的参数是int返回值是void
  这个代码实在难懂晦涩,有没有办法让它好看点呢?
  我们知道typedef是可以将数据类型重名的,所以我们可以将void(*)(int)这个函数指针类型重命名为 pdf
  typedef void(*)(int) pdf;//这种重命名的方式是错误的喔,对于函数指针类型的重命名我们需要将重命名的名字放在(里面)
  typedef void(*pdf)(int);
  代码 :void (  *signal(int, void(*)(int))  )(int);
  写成  pdf signal(int,pdf);


6.函数指针数组


我们知道指针数组是存放指针的数组;


int * arr[10]存放10int*类型的指针的数组


那把函数的地址存放到一个数组中,那这个数组就叫函数指针数组,那函数指针数组如何定义呢?


int (*parr1[10])();
//parr1先和[10]结合成为数组,然后这个数组的类型是int(*)()这个,所以这个数组接收的就是函数指针,这个数组就是函数指针数组。
int* parr2[10]();
//parr2先和[10]结合,成为数组,数组的类型是int*类型,所以这个数组是接收int*类型的,所以是指针数组
int (*)() parr3[10];
//这个书写就是错误的,不正确的书写格式,让人乍看是函数指针数组,但其实不是,parr3[10]应该放在(*里面)才是


函数指针数组有什么实际作用呢?


我们想数组里面放着函数的地址,那么我们就可以根据数组下标来找到函数的地址,然后解引用就可以调用函数,这种关系很妙,把这个用来 : 转移表


例子:(简易计数器)


#include <stdio.h>
int Add(int x,int y)
{
  return x + y;
}
int Sub(int x, int y)
{
  return x - y;
}
int Mul(int x, int y)
{
  return x * y;
}
int Div(int x, int y)
{
  return x / y;
}
void menu()
{
  printf("*************************\n");
  printf("****  1.Add  2.Sub   ****\n");
  printf("****  3.Mul  4.Div   ****\n");
  printf("*************************\n");
}
int main()
{
  int input;
  int x, y;
  int ret = 0;
  do
  {
    menu();
    printf("请选择:\n");
    scanf("%d", &input);
    switch (input)
    {
    case 1:printf("请输入操作数:\n");
      scanf("%d %d", &x, &y);
      ret = Add(x, y);
      printf("ret=%d\n", ret);
      break;
    case 2:printf("请输入操作数:\n");
      scanf("%d %d", &x, &y);
      ret = Sub(x, y);
      printf("ret=%d\n", ret); 
      break;
    case 3:printf("请输入操作数:\n");
      scanf("%d %d", &x, &y);
      ret = Mul(x, y);
      printf("ret=%d\n", ret); 
      break;
    case 4:printf("请输入操作数:\n");
      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;
}


这个计算器并没有使用函数指针数组,我们可以看出来这个代码有很多重复,浪费,而且固定不好再操作,让我们使用函数指针数组来看看。


使用函数指针数组来实现:


int main()
{
  int input=1;
  int x, y;
  int ret = 0;
  int(*pf[5])(int x ,int y )={0,Add,Sub,Mul,Div};//函数指针数组,存放着函数的地址可以通过下标来找到函数的地址
  while (input)
  {
    menu();
    printf("请选择:\n");
    scanf("%d", &input);
    if (input >= 1 && input <= 4)
    {
      printf("请输入操作数:\n");
      scanf("%d %d", &x, &y);
      ret=(*pf[input])(x, y);//函数调用
      printf("ret=%d\n", ret);
    }
    else
    {
      printf("输入错误\n");
    }
  }
}


7.指向函数指针数组的指针


是不是有点懵呀,哈哈哈。没事我们慢慢来理解


指向数组指针数组的指针


首先这个是指针,指针指针数组,这个数组的元素是函数指针数组。


那怎么定义呢?


void test(const char* str)
{
  printf("%s\n", str);
}
int main()
{
  test("hello,tao");
  //函数指针pf
  void(*pf)(const char*) = test;
  //函数指针数组pf1[]
  void(*pf1[5])(const char*) = { NULL,test };
  //指向函数指针数组的指针ppf
  void (* (*ppf)[5])(const char*) = &pf1;
首先ppf与*结合成为指针然后void (* (*) [5])(const char*)将这个指针拿走,看到的是这个指针指向的类型,指针指向的是一个函数指针数组类型,能看懂吧((*)和[5]结合成为数组,这个数组的类型呢又是void(*)(const char *)类型的,指的是这个数组的元素是函数指针存放的是函数的地址,所以一一理解就好了。)
  return 0;
}


8.回调函数


回调函数就是一个通过函数指针调用的函数。如果说你把函数的指针或地址作为参数传给一个函数,然后这个函通过指针调用其所指向的函数时,这个就是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进响应。


比较典型的是qsort函数的使用:


fac730e6c3154a19a6579a66792b3ad6.png


#include <stdlib.h>
//com_int是设计者自己根据比较什么类型的数据而设计的比较函数
int com_int(const void* e1,const void *e2)
{
  return *(int *)e1 - *(int *)e2;
}
int main()
{
  int arr[10] = { 10,9,8,7,6,5,4,3,2,1 };
  int sz = sizeof(arr) / sizeof(arr[0]);
  qsort(arr, sz, sizeof(arr[0]), com_int);//这里就是将函数地址传给qsort函数,而qsort函数内部又调用这个函数,这个就是回调函数
  int i;
  for (i = 0; i < sz; i++)
  {
    printf("%d ", arr[i]);
  }
  return 0;
}
相关文章
|
4月前
|
存储
如何使用指针
如何使用指针。
29 6
|
1月前
|
存储 C语言
指针基础
指针基础
24 0
|
4月前
|
存储 编译器 数据库
指针(1)--对于指针基本概念的介绍
指针(1)--对于指针基本概念的介绍
45 0
|
4月前
指针应用基础练习
指针应用基础练习
33 0
|
10月前
|
存储 安全 编译器
C++入门学习(4)引用 (讲解拿指针比较)
C++入门学习(4)引用 (讲解拿指针比较)
|
4月前
|
存储 C++
C++语言学习指针和引用应用案例
C++中的指针和引用用于高效操作内存。示例展示指针和引用的基本用法:指针`*p`存储变量`a`的地址,引用`&x`在函数调用中实现值交换而无需复制。此外,引用`update(&x)`可直接修改原变量,指针`p`在数组操作中用于遍历和访问不同部分。
29 2
|
4月前
|
存储 安全 数据处理
C++系列十一:指针
C++系列十一:指针
|
4月前
|
存储 安全 C++
第六章:C++中的指针和引用
第六章:C++中的指针和引用
37 1
|
11月前
指针的基础
指针的基础
44 0
|
存储 编译器 API
数组——参考《C和指针》
数组——参考《C和指针》
43 0