C语言指针全归纳-进阶版

进阶的内容比较庞大,但请各位耐心看完,我相信一定会有所收获,如果我有错误的地方希望大家可以指正我的错误,万分感谢

防止有些博友没有看过前一篇我的博客,在此简单概括一下那篇博客的内容有哪些。

  1. 指针变量是用来存放地址的,对其解引用就可以找到该地址唯一标识的内存空间
  2. 指针大小在32位平台上为4字节,在64位平台上为8字节
  3. 指针具有类型之分,指针类型决定了其进行解引用操作时的访问权限及+/-整数的步幅
  4. 指针的几种运算(指针加减整数,指针减指针,指针关系运算)

本文章主要内容

  • 1.字符指针
  • 2.指针数组
  • 3.数组指针
  • 4.数组传参和指针传参
  • 5.函数指针
  • 6.函数指针数组
  • 7.指向函数指针数组的指针
  • 8.回调函数

1.字符指针

我们知道字符指针的使用方法为

#include
int main()
{
     
    char a='0';
    char *p=&a;
    *p='a';
    return 0;
}

那下面这种是什么意思呢

#include
int main()
{
     
    char *p="Thanks for browsing";
    return 0;
}

我们知道char*只能存一个元素的地址,但怎么可以和一个字符串相等呢?
C语言指针全归纳-进阶版_第1张图片

有次我们看到,原来上面这个代码的意思是存放该字符串首元素的地址(即‘T’的地址存放在p中)
那么我们就可以这样使用它了
C语言指针全归纳-进阶版_第2张图片
但是这样写有个小问题,这里的"Thanks for browsing"和存放于数组中的字符串不同,是不可更改的,被称为常量字符串,它在内存里只会存储一份。
不理解只存一份的可以点我哦

2.指针数组

初阶已介绍指针数组,即存放指针的数组就是指针数组
在此就不再赘述,主要是为了区分下面的数组指针

int *arr1[10];//存放一级整形指针的数组
int **arr2[10];//存放二级整形指针的数组

3.数组指针

数组指针重点说的是指针
例如整形指针是指向整形的指针、字符指针是指向字符的指针
那么数组指针就是指向数组的指针

int arr[10]={
     0};
int (*p1)[10]=&arr;
int *p2=arr;
//*p代表这是个指针,因为[]比*的结合性要高,会与指针数组产生歧义,所以需要加上()
//即int (*p1)[10]=&arr的完整解释为p是一个指向有十个整形元素的指针

&arr是指整个数组的首地址
arr是指首元素的地址
两者虽数值上相同,但加1后移动的步幅不同!(如下图)

C语言指针全归纳-进阶版_第3张图片
数组名只有在两种情况下是指整个数组的首地址

  1. &arr
  2. sizeof(arr)

除此之外数组名均指代数组首元素地址

接下来我们来介绍一下数组指针的使用方法

#include
int main()
{
     
	int arr[10] = {
      0 };
	int i = 0;
	int(*p)[10] = &arr;
	for (i = 0; i < 10; i++)
	{
     
		printf("%d ", (*p)[i]);
		printf("%d ", *((*p)+i));
		printf("%d ", (*p)[0][i]);
		//以上三种均可找到数组中的元素
		//*p等价于arr,即arr[i]==(*p)[i]
		//&具有‘升维’的作用,*具有‘降维’的作用
	}
	return 0;
}

C语言指针全归纳-进阶版_第4张图片

#include
void print(int (*p)[5],int r,int c)
{
     
	int i = 0;
	for (i = 0; i < r; i++)
	{
     
		int j = 0;
		for (j = 0; j < c;j++)
		{
     
			printf("%d   ", *(*(p+i)+j));
			//p+i相当于&arr+i  第i行的地址
			//*(p+i)+j相当于arr+j  第j个的地址
		}
		printf("\n");
	}
}
int main()
{
     
	int arr[3][5] = {
      {
      1, 2, 3, 4, 5 },
					{
      6, 7, 8, 9, 10 },
					{
      11, 12, 13, 14, 15 } };
	print(arr, 3, 5);//二维数组传参,传的是第一行的地址
	//传参数也会发生降维
	//传二维数组,函数得到的为第一行的地址
	//传一维数组,函数得到的为首元素地址
	return 0;
}

这里做一个小练习,可以测试一下自己是否真的明白了指针数组和数组指针

int arr[5];                 有五个整形元素的数组
int *p[5];                  有五个整形指针的数组
int (*p)[5];                指向有五个整形元素的数组的指针
int (*p[5])[10];            存放五个指向有十个整形元素的数组的指针的数组 
//p先和[]结合成为数组,然后拆解为int (*)[10],
//现在即使数组中的元素类型,每个元素为指向有十个整形元素的数组指针         

4.数组传参和指针传参

一维数组传参
void test1(int arr[])
{
     }//该方法就是普通的数组传参
void test2(int arr[10])
{
     }//该方法与test1相同,10并没有实际意义,只是形式上感觉上下匹配
void test3(int *p)
{
     }//该方法就是利用了数组传参会发生降维,就利用整形指针接收
void test4(int *p[10])
{
     }//同test2
void test5(int **p)
{
     }//传的元素为一级指针,所以可以用二级指针接收
int main()
{
     
	int arr[10] = {
      0 };
	int *p[10] = {
      0 };
	test1(arr);
	test2(arr);
	test3(arr); 
	test4(p); 
	test5(p);
	return 0;
}
二维数组传参
void test1(int arr[3][5])
{
     }//该方式可行
void test2(int arr[][5])
{
     }//该方法可行
void test3(int arr[][])
{
     }//该方法不可行,对二维数组而言,可以不知道行数,但必须知道列数
void test4(int *arr)
{
     }//该方法不可行经降维后得到是一维数组的地址,而此处却用整形指针接收
void test5(int* arr[])
{
     }//该方法不可行,这里用的是指针数组接收,要想用数组接收只能用上面两种
void test6(int (*p)[5])
{
     }//该方法可行,利用数组指针指向一个一维数组
void test7(int **p)
{
     }//该方法不可行,该方法是为了接收一级指针。利用指针接收只能用上面这种
int main()
{
     
	int arr[3][5] = {
      0 };
	test1(arr);
	test2(arr);
	test3(arr);
	test4(arr);
	test5(arr);
	test6(arr);
	test7(arr);
	return 0;
}
我们知道一级指针传值,可以用一级指针接收
那么,反过来想想,都有哪些传过去可以用指针接收呢?
void test(int *p)
{
     }
int main()
{
     
	int a = 10;
	int *pa = &a;
	int arr[5] = {
      0 };
	test(&a);
	test(pa);
	test(arr);
	//传参基本原则:传过去的参数和形参类型相同
	return 0;
}
同理,二级指针呢?
void test(int **p)
{
     }
int main()
{
     
	int a = 10;
	int *pa = &a;
	int **ppa = &pa;
	int *arr[10] = {
      0 };
	test(ppa);
	test(&pa);
	test(arr);
	return 0;
}

5.函数指针

#include
int test(int x,int y)
{
     return x*y;}
int main()
{
     
	int a=0;
	int *pa=&a;//指向整形的指针
	
	int arr[10]={
     0};
	int (*parr)[10]=&arr;//指向数组的指针

	int    (*pf)(int ,int)=&test;//指向函数的指针
//函数返回值类型   参数类型   函数名取地址即可获得函数地址
test==&test 两者相同,上面可替换,这里可没有函数首元素一说
     函数指针如何调用函数呢?
    int b=(*pf)(2,3);//和正常使用指针是类似的,只需解引用即可使用
    return 0;
}

但上面已经写过,将test赋给pf,那么是否意味着pf也可以和test一样直接调用函数呢?
答案是肯定的,可以。pf前面加星号是为了初学者可以更快了解C语言语法,其实 并未起到解引用的用处,但如果写成(pf)(2,3)的形式就一定要带括号,否则意思就变成了对调用函数的结果解引用
C语言指针全归纳-进阶版_第5张图片
现在我们已经初步认识函数指针,接下来我们来看看两句很“有趣”的代码

1.(*(void(*)())0)()
  1. 这个的确很不好看明白,但首先我们知道void(*)()是一个不需要参数且返回值为空的函数指针类型,而 ()0就是将0强制类型转换为函数指针类型,接着就是解引用调用0地址处的函数,该函数无参数,返回类型为void。
    (该代码源自C陷阱和缺陷)
    C语言指针全归纳-进阶版_第6张图片
2.void(* signal(int, void(*)(int) ) )(int);
  1. 首先要肯定的是这是一个函数声明,signal是函数名,需要整形和函数指针(该函数指针参数为int,返回值类型为void)两个参数,signal的返回值类型为void(* )(int)(该函数指针参数为int,返回值类型为void)。
上面这个其实不容易理解,所以我们可以利用typedef让它变得好看一点
typedef void(*new_pf)(int) ;
注意new_pf为新名字,void(*)(int)为旧名字
这样写是语法规定,不可以写成typedef void(*)(int) new_pf;
所以上面的函数声明就可以写成  new_pf signal(int,new_pf);

6.函数指针数组

这个应该可以举一反三了吧,函数指针数组即存放函数指针的数组

#include
void test1()
{
     
	printf("hello\n");
}
void test2()
{
     
	printf("world\n");
}
int main()
{
     
	void(*pt1)() = test1;
	void(*pt2)() = test2;
	void(*parr[2])() = (test1, test2);
	//函数指针数组其实就是在函数指针的名字后面加 [元素个数]
	return 0;
}

运用函数指针数组很经典的案例函数指针数组实现计算器(可以点进来了解一下)

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

函数指针数组终归是个数组,既然是数组,那就可以用指针指向它

int (*p)(int ,int);//函数指针
int (*parr[5])(int ,int);//函数指针数组
int (*(*pparr)[5])(int ,int)= &parr;//指向函数指针数组的指针
(*pparr)证明是个指针,[5]说明指向的是个数组,元素类型为int (*)(int ,int)

8.回调函数

注意重点来了

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

举个例子
void test1()
{
     
     printf("hello");
}
void test2(void (*p)())
{
     
	 p();
}
int main()
{
     
    test2(test1);
    并没有主动调用test1,而是将test1地址传给test2,在适当情况下test2调用test1
    此时test1被称为回调函数
    return 0;
}

如果想加深一下对回调函数的理解可以点下面两个链接
回调函数计算器
快速排序的使用(qsort就是一个很好的回调函数)

大家好,上次的初阶版让我第一次感受到了写博客喜悦,原来可以有这么多人看我的博客呀,非常感谢大家对我的支持与鼓励,我一定会再接再厉,继续创作出优质的内容来向大家分享知识。——2021.3.23

今天终于把它磨出来了,虽然有些小细节还是可能还需更改,但内容上还是很充实的,非常感谢点进来的每个人。——2021.4.4

你可能感兴趣的:(#,C语言知识汇总,指针,字符串,c语言,经验分享)