C语言玩转指针之指针的高阶玩法

目录
  • 前言
  • 一、字符指针
  • 二、指针数组和数组指针
    • 1.指针数组
    • 2.数组指针
      • 2.1.数组指针是什么?
      • 2.2.&数组名和数组名的区别
      • 2.3.数组指针的使用
  • 三、数组参数与指针参数
    • 1.一维数组参数
    • 2.二维数组参数
    • 3.一级指针传参
    • 4.二级指针传参
  • 四、函数指针
  • 五、函数指针数组
  • 六、指向函数指针数组的指针
  • 七、回调函数
  • 总结

前言

指针第一篇,万人浏览:

【C语言】玩转指针——关于指针,你需要掌握的基础知识!

指针的主题,我们在初级阶段的《指针》章节已经接触过了,我们知道了指针的概念:

1.指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
2. 指针的大小是固定的4/8个字节(32位平台/64位平台)。
3. 指针是有类型,指针的类型决定了指针的±整数的步长,指针解引用操作的时候的权限。
4. 指针的运算

这个章节,我们继续探讨指针的高级主题

一、字符指针

在指针的类型中我们知道有一种指针类型为字符指针 char* 。

思考以下代码,pc和p分别指向何方?

int main()
{
	char ch = 'www';
	char *pc = &ch;//pc是指向一个字符变量的
	const char* p = "hello boy";//"hello boy"是一个常量字符串
	//上面表达式的作用是:把常量字符串“hello bit”的第一个字符h的地址赋给p(指向首字符地址)
	return 0;
}

【注意】

代码 char* pstr = “hello bit.”; 特别容易让同学以为是把字符串 hello boy 放到字符指针
p里了,但是/本质是把字符串 hello boy首字符的地址放到了p中。

思考下面代码,输出的是什么?

int main()
{
	char str1[] = "hello boy.";
	char str2[] = "hello boy.";
	//两个字符数组,独立开辟空间。
	//数组名是数组首元素地址
	char *str3 = "hello boy.";
	char *str4 = "hello boy.";
	//二者都是指向常量字符串,(常量字符串,是不能修改的)
	//二者指向的是同一个地址

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

所以结果如下图

二、指针数组和数组指针

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

答案是:数组。

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

答案是:指针。

举个例子:

int *p1[5];  //指针数组
int (*p2)[5];  //数组指针

二者形式很相似,那么我们如何区分呢?

1.指针数组

【指针数组】

首先它是一个数组,数组的元素都是指针,数组占多少个字节由数组本身 决定。它是“储存指针的数组”的简称。

指针数组是数组元素为指针的数组(例如 int *p[5],定义了p[0],p[1],p[2],p[3],p[4]五个指针),其本质为数组。

int *p[5];

这里涉及到一个优先级的问题。

我们知道数组下标的优先级比取值运算符的优先级高。所以,p先被定义为具有5个元素的数组。它的类型是int*,所以它是指向整型变量的指针。

【结论】指针数组是一个数组,每个数组元素存放一个指针变量。

指针数组如何初始化呢?

int main()
{
	//char *arr[5];//arr是存放字符指针的数组
	//int * arr2[4];//arr2是存放整型指针的数组

	int a = 10;
	int b = 20;
	int c = 30;
	int d = 40;
	                //int* int* int* int*
	int * arr2[4] = { &a, &b, &c, &d };//arr2就是整型指针的数组
	printf("%d\n", *arr2[0]);//取出第一个地址的内容
	int i = 0;
	for (i = 0; i < 4; i++)
	{
		printf("%d\n", *(arr2[i]));
	}
	return 0;
}

数组指针中&a,&b,&c,&d分别指向10,20,30,40

大家有没发现,如果这样定义的话,会有些繁琐。

所以我们可以采用以下的方法:

int main()
{
	const char* arr[5] = { "abcedf", "bcedfg", "hehe" ,"hhh","zhangsan"};
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%s\n", arr[i]);
	}
	return 0;
}

2.数组指针

2.1.数组指针是什么?

【数组指针】

首先它是一个指针,它指向一个数组。在 32 位系统下永远是占 4 个字节,
至于它指向的数组占多少字节,不知道。它是“指向数组的指针”的简称。

数组指针是指向数组地址的指针,其本质为指针

int (*p)[5];

在上面代码中,圆括号和数组下标位于同一优先级队列,所以从左到右执行。

因此,p先被定义为一个指针变量,后边[5]表示的是一个具有5个元素的数组,p指向的就是这个数组。

由于指针变量的类型事实上就是它所指向的元素的类型,所以这个int定义数组元素的类型为整型。

通过下面一个例子来加深理解

int main()
{
	int a = 10;
	int*pi=&a;//整型的地址存放到整型指针中
	char ch = 'w';
	char* pc=&ch;//字符的地址存放在字符指针中

	int arr[10] = { 0 };
	int*p = arr;//arr-是数组首元素的地址

	//int* parr[10]; //这样写是数组
	int (*parr)[10]=&arr;//取出的是数组的地址,应该存放到数组指针中
	return 0;
}

那么我们如何进行初始化呢?

我们在学习指针的时候,是将指针指向数组名,因为数组名是数组首元素地址,知道了第一个元素的地址,后面的元素就可知道。如下:

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

所以,上面的指针p是一个指向整型变量的指针,它并不是指向数组的指针。而数组指针,才是指向数组的指针。

所以,在初始化的时候,应该将数组的地址传递给数组指针,而不是传递数组第一个元素的地址。它们值虽然相同,但含义不一样。

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

2.2.&数组名和数组名的区别

我们以arr和&arr来举例说明:

a,&a 的值是一样的。
但意思不一样,
a 是数组首元素的首地址,也就是 a[0]的首地址。
&a 是数组的首地址,表示的是数组的地址。

例如:

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

可以看到,它们的值是一样的。

但是,如果它们+1呢?

如下:

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

可以看到,+1后的结果就不一样了。

那么为什么呢?

a 是数组首元素的首地址,也就是 a[0]的 首地址。
&a 是数组的首地址。
a+1 是数组下一元素的首地址,即 a[1]的首地址。
&a+1 是下一 个数组的首地址。

2.3.数组指针的使用

数组指针指向的是数组,存放的是数组的地址

那怎么使用,举个例子:

#include <stdio.h>
int main()
{
    int arr[10] = {0};
    int (*p)[10] = &arr;//把数组arr的地址赋值给数组指针变量p
    //这里*先与p结合,再与 [ ] 结合,由于上面定义的数组是int类型,所以取地址的类型也是int类型。
    return 0;
}

来看下面代码,思考我们如何利用数组指针打印我们想要的结果呢?

void print(int (*parr)[10], int sz)//传上来地址,用数组指针接受
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
	//以下3种方式都能打印
		//printf("%d ", parr[0][i]);//把一维数组当成二维数组,[0]表示第一行,[i]表示遍历元素
		//printf("%d ", (*(parr + 0))[i]);//*(parr + 0)解引用首元素地址
		printf("%d ", (*parr)[i]);//(*parr) 相当于 parr指向的数组的数组名
	}
}
int main()
{
	int arr[10] = {1,2,3,4,5,6,7,8,9,10};
	int sz = sizeof(arr) / sizeof(arr[0]);
	print(&arr, sz);//&arr把数组的首元素地址传上去函数

	return 0;
}

三、数组参数与指针参数

我们都知道参数分为形参和实参。

形参是指声明或定义函数时的参数
实参是在调用函数时主调函数传递过来的实际值。

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

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

3.一级指针传参

首先,用一级指针传参,那就用一级指针接收

#include <stdio.h>
void print(int *p, int sz) //一级指针接收int* p
{
	 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 test(int *p)//可以接收什么?
{}
int main()
{
	int a = 10;
	int* p1 = &a;
	int arr[10] = {0};
//怎样传给函数?
	return 0;
}

其实我们可以有下面的方式:

void test(int *p)
{}
int main()
{
	int a = 10;
	int* p1 = &a;
	int arr[10] = {0};

	test(&a);//传地址上去可以
	test(arr);//传个数组名过去可以
	test(p1);//传个指针也可以
	test(NULL);//传空指针也行,考虑清楚,因为传空指针就是传0,并且空指针不能解引用,不支持访问空间

	return 0;
}

4.二级指针传参

如果是二级指针怎么传参呢?

同样的,我们可以有下面的方法。

void test(int **ppa)
{}

int main()
{
	int a = 10;
	int* pa = &a;
	int** ppa = &pa;

	int* arr[5];

	test(ppa);
	test(&pa);
	test(arr);

	return 0;
}

四、函数指针

函数指针是是什么?

我们说,数组指针就是数组的指针。是指向数组的指针。

同理

函数指针就是函数的指针。它是一个指针,指向一个函数

我们思考一下下面三个代码:

char * (*fun1)(char * p1,char * p2);
char * *fun2(char * p1,char * p2);
char * fun3(char * p1,char * p2);

什么意思?

char * (*fun1)(char * p1,char * p2);

char *fun2(char * p1,char * p2);
//fun2是函数名,p1,p2 是参数,其类型为 char *型,函数的返回值为 char *类型。

char ** fun3(char * p1,char * p2);
//与 第二个表达式相比,唯一不同的就是函数的返回值类型为 char**,是个二级指针。

那么第一个代码是什么意思?

这里 fun1 不是什么函数名,而是一个

指针变量,它指向一个函数。这个函数有两个指针类型的参数,函数的返回值也是一个指针。

那么我们如何使用函数指针呢?

#include <stdio.h>
#include <string.h>
char * fun(char * p1, char * p2)
{
	int i = 0;
	i = strcmp(p1, p2);
	if (0 == i)
	{
		return p1;
	}
	else
	{
		return p2;
	}
}
int main()
{
	char * (*pf)(char * p1, char * p2);
	pf = &fun;
	(*pf) ("aa", "bb");
	return 0;
}

我们使用指针的时候,需要通过钥匙(“*”)来取其指向的内存里面的值,函数指针使用也如此。通过用(*pf)取出存在这个地址上的函数,然后调用它。

给函数指针赋值时,可以用&fun 或直接用函数名 fun。这是因为函数名被编译之后其实就是一个地址,所以这里两种用法没有本质的差别。

接下来看一下下面这个代码什么意思?

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

第一步:void(*) (),可以明白这是一个函数指针类型。这个函数没有参数,没有返回值。

第二步:(void(*) ())0,这是将 0 强制转换为函数指针类型,0 是一个地址,也就是说一个函数存在首地址为 0 的一段区域内。

第三步:((void() ())0),这是取 0 地址开始的一段内存里面的内容,其内容就是保存在首地址为 0 的一段区域内的函数。

第四步:((void() ())0)(),这是函数调用。

五、函数指针数组

把函数的地址存到一个数组中,那这个数组就叫函数指针数组

char * (*pf[3])(char * p);//一个函数指针数组,pf为数组名,类型是char*(*)()
//pf先于[3]结合,说明是一个数组,数组内存储了3个指向函数的指针
//指针再与*结合,说明是一个函数指针数组

六、指向函数指针数组的指针

看起来很复杂,其实仔细分析也不难。

这里的函数指针数组指针不就是一个指针嘛。只不过这个指针指向一个数组,这个数组里面存的都是指向函数的指针。仅此而已。(套娃)

那如何定义呢?下面代码介绍

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

七、回调函数

根据维基百科的解释:

把一段可执行的代码像参数传递那样传给其他代码,而这段代码会在某个时刻被调用执行,这就叫做回调。如果代码立即被执行就称为同步回调,如果在之后晚点的某个时间再执行,则称之为异步回调

比如:

函数 F1 调用函数 F2 的时候,函数 F1 通过参数给函数 F2 传递了另外一个函数 F3 的指针,在函数 F2 执行的过程中,函数F2 调用了函数 F3,这个动作就叫做回调(Callback),而先被当做指针传入、后面又被回调的函数 F3 就是回调函数。

举个例子:

int Add(int x, int y)
{
	return x + y;
}

int Sub(int x, int y)
{
	return x - y;
}
void Cale(int(*pf)(int, int))//通过指针传地址
{
	int ret = pf(3, 5);
	printf("%d\n", ret);
}

int main()
{
	//Cale(Add);
	Cale(Sub);//调用函数
	return 0;
}

总结

本文断断续续写了好几天,一是自己本身半桶水都没有,很多知识限于理论,不能实践。二是高阶指针确实难理解,就算现在写完了,理解依旧不透彻。参考了《C语言深度剖析》一书,如果大家想更加深入理解的话,建议去找这本书看看,收获会更多。

到此这篇关于C语言玩转指针之指针高阶玩法的文章就介绍到这了,更多相关C语言指针高阶玩法内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 详解C语言结构体中的函数指针

    结构体是由一系列具有相同类型或不同类型的数据构成的数据集合.所以,标准C中的结构体是不允许包含成员函数的,当然C++中的结构体对此进行了扩展.那么,我们在C语言的结构体中,只能通过定义函数指针的方式,用函数指针指向相应函数,以此达到调用函数的目的. 函数指针 函数类型 (*指针变量名)(形参列表):第一个括号一定不能少. "函数类型"说明函数的返回类型,由于"()"的优先级高于"*",所以指针变量名外的括号必不可少.  注意指针函数与函数指针表示

  • C语言 指针与二维数组详解

    二维数组在概念上是二维的,有行和列,但在内存中所有的数组元素都是连续排列的,它们之间没有"缝隙".以下面的二维数组 a 为例: int a[3][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11} }; 从概念上理解,a 的分布像一个矩阵: 0   1   2   3 4   5   6   7 8   9  10  11 但在内存中,a 的分布是一维线性的,整个数组占用一块连续的内存: C语言中的二维数组是按行排列的,也就是先存放 a[

  • C语言 结构体和指针详解及简单示例

    指针也可以指向一个结构体,定义的形式一般为: struct 结构体名 *变量名; 下面是一个定义结构体指针的实例: struct stu{ char *name; //姓名 int num; //学号 int age; //年龄 char group; //所在小组 float score; //成绩 } stu1 = { "Tom", 12, 18, 'A', 136.5 }; //结构体指针struct stu *pstu = &stu1; 也可以在定义结构体的同时定义结构

  • c语言指针之二级指针示例

    二级指针的概念 首先任何值都有地址,一级指针的值虽然是地址,但这个地址做为一个值亦需要空间来存放,是空间就具有地址,这就是存放地址这一值的空间所具有的地址,二级指针就是为了获取这个地址,一级指针所关联的是其值(一个地址)名下空间里的数据,这个数据可以是任意类型并做任意用途,但二级指针所关联的数据只有一个类型一个用途,就是地址,指针就是两个用途提供目标的读取或改写,那么二级指针就是为了提供对于内存地址的读取或改写指针的表现形式是地址,核心是指向关系指针运算符"*"的作用是按照指向关系访问

  • C语言入门之指针用法教程

    本文针对C语言初学者详细讲述了指针的用法,并配以实例进行说明.具体分析如下: 对于C语言初学者来说,需要明白指针是啥?重点就在一个"指"上.指啥?指的地址.啥地址?内存的地址. 上面说明就是指针的本质了. 这里再详细解释下.数据存起来是要存在内存里面的,就是在内存里圈出一块地,在这块地里放想放的东西.变量关心的是这块地里放的东西,并不关心它在内存的哪里圈的地:而指针则关心这块地在内存的哪个地方,并不关心这块地多大,里面存了什么东西. 指针怎么用呢?下面就是基本用法: int a, b,

  • C语言指针学习经验总结浅谈

    这篇C语言指针学习经验总结主要是我入职以来学习C指针过程中的点滴记录.文档里面就不重复书上说得很清楚的概念性东西,只把一些说得不清楚或理解起来比较费解的东西做一下讲解,希望能达到以下三个目的 1.通过写这些东西,把我脑袋中关于C的模糊的知识清晰化.2.给初转C的同事们一点提示和帮助.3.也希望各位前辈检查一下文档中是否有理解偏差的地方.1 指针的概念分解      指针是一个特殊的变量,它里面存储的数值被解释成为内存里的一个地址. 要搞清一个指针需要搞清指针的四方面的内容: 1.指针的类型2.指

  • C语言的指针类型详细解析

    指针存储了内存的地址,同时指针是有类型的,如int*,float*,那么,一个自然的猜想就是指针变量应该存储这两方面的信息:地址和指针类型,比如,就像下面的结构体: 复制代码 代码如下: struct pointer{    long address;    int type;} 举个例子:打印sizeof(int*),值为4,可见4字节是存储内存地址用的,反过来就说明指针并没有存储类型信息的地方,那么指针的类型信息存放在哪儿呢?下面剖析一段简单的代码. 复制代码 代码如下: // ma.cpp

  • C语言指针的长度和类型深入分析

    指针是C语言的精髓,本文就以实例的形式详细分析了C语言的长度和类型.对于初学者深入理解C语言程序设计有很好的参考价值.具体分析如下: 一般来说,如果考虑应用程序的兼容性和可移植性,指针的长度就是一个问题,在大部分现代平台上,数据指针的长度通常是一样的,与指针类型无关,尽管C标准没有规定所有类型指针的长度相同,但是通常实际情况就是这样.但是函数指针长度可能与数据指针的长度不同. 指针的长度取决于使用的机器和编译器,例如:在现代windows上,指针是32位或是64位长 测试代码如下: #inclu

  • C语言中常量指针与指针常量区别浅析

    常量指针是指--指向常量的指针,顾名思义,就是指针指向的是常量,即,它不能指向变量,它指向的内容不能被改变,不能通过指针来修改它指向的内容,但是指针自身不是常量,它自身的值可以改变,从而指向另一个常量.指针常量是指--指针本身是常量.它指向的地址是不可改变的,但地址里的内容可以通过指针改变.它指向的地址将伴其一生,直到生命周期结束.有一点需要注意的是,指针常量在定义时必须同时赋初值.注:也有人将这两个名称的定义与含义反过来认为:"指针常量:顾名思义它的中心词是"常量"这是重点

  • C语言指针详解及用法示例

    新手在C语言的学习过程中遇到的最头疼的知识点应该就是指针了,指针在C语言中有非常大的用处.下面我就带着问题来写下我对于指针的一些理解. 指针是什么? 指针本身是一个变量,它存储的是数据在内存中的地址而不是数据本身的值.它的定义如下: int a=10,*p; p=&a int a=10; int *p=&a; 首先我们可以理解 int* 这个是要定义一个指针p,然后因为这个指针存储的是地址所以要对a取地址(&)将值赋给指针p,也就是说这个指针p指向a. 很多新手都会对这两种定义方法

随机推荐