c语言高级指针理解及应用(下)

本章主要介绍c语言指针中,指针和数组的对应关系,指针的应用如数组指针,指针数组,函数指针,函数指针数组等比较难以理解的内容,本人希望能结合我在学习中遇到的问题用通俗易懂的语言给大家介绍这些知识,若有错误,恳请斧正。

本文大纲

  1. 一维数组
  2. 一级指针
  3. 二维数组
  4. 二级指针
  5. 指针数组
  6. 数组指针
  7. 指针和数组的定义、声明
  8. 数组参数、指针参数
  9. 函数指针
  10. 函数指针数组、函数指针的数组的指针
  11. 回调函数

    *先把最重要的结论放最前面,数组就
    是数组,指针就是指针,它们是完全不同的两码事!他们之间没有任何关系,只是经常穿
    着相似的衣服来迷惑你罢了。*

一维数组、一级指针、二维数组、二级指针
相信大家都学习过一维数组、一级指针、二维数组、二级指针,这里不做过多赘述,仅作复习。

//一维数组
int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(a+0));
printf("%d\n",sizeof(*a));
printf("%d\n",sizeof(a+1));
printf("%d\n",sizeof(a[1]));
printf("%d\n",sizeof(&a));
printf("%d\n",sizeof(&a+1));
printf("%d\n",sizeof(&a[0]));
printf("%d\n",sizeof(&a[0]+1));
//字符数组
char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr+0));
printf("%d\n", sizeof(*arr));
printf("%d\n", sizeof(arr[1]));
printf("%d\n", sizeof(&arr));
printf("%d\n", sizeof(&arr+1));
printf("%d\n", sizeof(&arr[0]+1));
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr+0));
printf("%d\n", strlen(*arr));
printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr+1));
printf("%d\n", strlen(&arr[0]+1));
char *p = "abcdef";
printf("%d\n", sizeof(p));
printf("%d\n", sizeof(p+1));
printf("%d\n", sizeof(*p));
printf("%d\n", sizeof(p[0]));
printf("%d\n", sizeof(&p));
printf("%d\n", sizeof(&p+1));
printf("%d\n", sizeof(&p[0]+1));
printf("%d\n", strlen(p));
printf("%d\n", strlen(p+1));
printf("%d\n", strlen(*p));
printf("%d\n", strlen(vp[0]));
printf("%d\n", strlen(&p));
printf("%d\n", strlen(&p+1));
printf("%d\n", strlen(&p[0]+1));
//二维数组
int a[3][4] = {0};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(a[0][0]));
printf("%d\n",sizeof(a[0]));
printf("%d\n",sizeof(a[0]+1));
printf("%d\n",sizeof(a+1));
printf("%d\n",sizeof(&a[0]+1));
printf("%d\n",sizeof(*a));
printf("%d\n",sizeof(a[3]));

指针数组
来判断下哪个是指针数组

A),int *p1[10];
B),int (*p2)[10];

很明显是A,因为[]的优先级高,p1先和[]结合构成数组,A定义了一个数组,其内容是包含十个指向int类型的指针,即指针数组。至于p2 就更好理解了,在这里“()”的优先级比“[]”高,“*”号和p2 构成一个指针的定义,指针变量名为p2,int 修饰的是数组的内容,即数组的每个元素。数组在这里并没有名字,是个匿名数组。那现在我们清楚p2 是一个指针,它指向一个包含10 个int 类型数据的数组,即数组指针。
这里写图片描述

数组指针的使用
来,看代码

int arr[10] = {0};
&arr;//表示数组的地址 //具体差异在哪里?
printf(“%p\n”, arr);
printf(“%p\n”, &arr+1);
//我们可以看出产生的结果是截然不同的
//这里是因为数组的地址和数组首元素的地址值是相同的,但是意义不同

arr只是表示数组首元素的地址,那数组的地址该怎么来存储

int arr[10] = {0};
int*p1 = &arr;//ok?
int (*p2)[10] = &arr;//ok?

显然p2是比较合适的选择
比如:
那我们要将一个二维数组传参,我们怎么做?

//测试
void test()
{
int arr[3][5] = {0};
print(arr);//这里print函数的参数如何设计?
}
void print(int arr[3][5])//ok?
{}
void print(int arr[][5])//ok?
{}
void print(int **arr)//ok?
{}
void print(int (*arr)[5])//ok?
{}

指针和数组的定义、声明
我们已经知道:定义是不存在的时候的要让他存在,而声明是不知道的是让他被知道。

//test.c
//数组的定义
char arr[] = "abcdef";
//指针的定义
char* p = "abcdef";
//main.c
extern char arr[];
extern char* p;
int main()
{
printf("%s\n", arr);//输出什么?
printf("%s\n", p);//输出什么?
return 0;
}

在main.c中的extern 表示arr和p是外部文件定义的变量,在使用的时候去其他模块查
找。也可以理解为声明了一个外部的变量。通过结果我们可以分析到:声明的其实就是
定义的变量本身。
但是我们换一种方式写代码:

//实例1
//test.c
//数组的定义
char arr[] = "abcdef";
//main.c
extern char* arr;
int main()
{
printf("%s\n", arr);//输出什么?
return 0;
}

结果?

//实例2
//指针的定义
char* p = "abcdef";
//main.c
//
extern char p;
int main()
{
printf("%s\n", p);//输出什么?
return 0;
}

结果?

a和&a之间的区别

int main()
{
char a[5]={'A','B','C','D'};
char (*p3)[5] = &a;
char (*p4)[5] = a;
return 0;
}

上面对p3 和p4 的使用,哪个正确呢?p3+1 的值会是什么?p4+1 的值又会是什么?
毫无疑问,p3 和p4 都是数组指针,指向的是整个数组。&a 是整个数组的首地址,a
是数组首元素的首地址,其值相同但意义不同。在C 语言里,赋值符号“=”号两边的数据
类型必须是相同的,如果不同需要显示或隐式的类型转换。p3 这个定义的“=”号两边的数
据类型完全一致,而p4 这个定义的“=”号两边的数据类型就不一致了。左边的类型是指
向整个数组的指针,右边的数据类型是指向单个字符的指针。在Visual C++6.0 上给出如下
警告:warning C4047: ‘initializing’ : ‘char ()[5]’ differs in levels of indirection from ‘char ‘。还好,
这里虽然给出了警告,但由于&a 和a 的值一样,而变量作为右值时编译器只是取变量的值,
所以运行并没有什么问题。不过我仍然警告你别这么用。
既然现在清楚了p3 和p4 都是指向整个数组的,那p3+1 和p4+1 的值就很好理解了。
但是如果修改一下代码,会有什么问题?p3+1 和p4+1 的值又是多少呢?

int main()
{
char a[5]={'A','B','C','D'};
char (*p3)[3] = &a;
char (*p4)[3] = a;
return 0;
}
//甚至还可以把代码再修改:
int main()
{
char a[5]={'A','B','C','D'};
char (*p3)[10] = &a;
char (*p4)[10] = a;
return 0;
}

这个时候又会有什么样的问题?p3+1 和p4+1 的值又是多少?
理解了上述的问题,接下来我们看下数组参数、指针参数的问题
一维数组传参

#include <stdio.h>
void test(int arr[])//ok
{}
void test(int arr[10])//ok
{}
void test(int *arr)//ok
{}
void test2(int *arr[20])//ok?
{}
void test2(int **arr)//ok?
{}
int main()
{
int arr[10] = {0};
int *arr2[20] = {0};
test(arr);
test2(arr2);
}

二维数组传参

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

一级指针传参

#include <stdio.h>
void print(int *p, int sz)
{
int i = 0;
for(i=0; 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;
}

二级指针传参

#include <stdio.h>
void test(int** ptr)
{
printf("num = %d\n", **ptr);
}
int main()
{
int num = 10;
int*p = #
int **pp = &p;
test(pp);
test(&p);
return 0;
}

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

void test(char **p)
{
}
int main()
{
char c = 'b';
char*pc = &c;
char**ppc = &pc;
char* arr[10];
test(&pc);
test(ppc);
test(arr);//Ok?
return 0;
}

函数指针
顾名思义,函数指针就是函数的指针。它是一个指针,指向一个函数。看例子:

A),char * (*fun1)(char * p1,char * p2);
B),char * *fun2(char * p1,char * p2);
C),char * fun3(char * p1,char * p2);

看看上面三个表达式分别是什么意思?
C):这很容易,fun3 是函数名,p1,p2 是参数,其类型为char 型,函数的返回值为char
类型。
B):也很简单,与C)表达式相比,唯一不同的就是函数的返回值类型为char**,是个
二级指针。
A):fun1 是函数名吗?回忆一下前面讲解数组指针时的情形。我们说数组指针这么定
义或许更清晰:
int (*)[10] p;
再看看A)表达式与这里何其相似!明白了吧。这里fun1 不是什么函数名,而是一个
指针变量,它指向一个函数。这个函数有两个指针类型的参数,函数的返回值也是一个指
针。

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

int (*parr1[10])();

答案是:parr1
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;
while (input)
{
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);
break;
case 2:
printf( "输入操作数:" );
scanf( "%d %d", &x, &y);
ret = sub(x, y);
break;
case 3:
printf( "输入操作数:" );
scanf( "%d %d", &x, &y);
ret = mul(x, y);
break;
case 4:
printf( "输入操作数:" );
scanf( "%d %d", &x, &y);
ret = div(x, y);
break;
default:
printf( "选择错误\n" );
break;
}
printf( "ret = %d\n", ret);
}
return 0;
}

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

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

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

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)[10])(const char*) = &pfunArr;
return 0;
}

回调函数

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

使用回调函数,模拟实现qsort(采用冒泡的方式)。

#include <stdio.h>
int int_cmp(const void * p1, const void * p2)
{
return (*( int *)p1 > *(int *) p2);
}
void _swap(void *p1, void * p2, int size)
{
int i = 0;
for (i = 0; i< size; i++)
{
char tmp = *((char *)p1 + i);
*(( char *)p1 + i) = *((char *) p2 + i);
*(( char *)p2 + i) = tmp;
}
}
void bubble(void *base, int count , int size, int(*cmp )(void *,
void *))
{
int i = 0;
int j = 0;
for (i = 0; i< count - 1; i++)
{
for (j = 0; j1; j++)
{
if (cmp ((char *) base + j*size , (char *)base + (j +
1)*size) > 0)
{
_swap(( char *)base + j*size, (char *)base + (j +
1)*size, size);
}
}
}
}
int main()
{
int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
//char *arr[] = {"aaaa","dddd","cccc","bbbb"};
int i = 0;
bubble(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_c
mp);
for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++)
{
printf( "%d ", arr[i]);
}
printf("\n");
return 0;
}

参考资料《c语言深度解剖》

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值