C语言指针的进阶内容
文章目录
前言
上一篇文章讲解了<指针>的基础内容,建议大家先打牢地基,再来阅读这篇进阶文章
本篇主要讲解了比较复杂难懂的指针类型如何定义,使用方法以及应用场景;
内容较为复杂,各种操作符嵌套的较为复杂,希望大家可以细品这篇文章!
本人是一个刚刚上路的IT新兵!分享一点自己的见解,如果有错误的地方欢迎各位大佬莅临指导,如果这篇文章可以帮助到你,劳请大家点赞转发支持一下! 下面图片的双重水印是我对这篇文章进行了修改后二次上传的图片,请大家放心,不是偷的,大家放心观看!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 下面就要发车喽!!
一、字符指针
在指针的类型中我们知道有一种指针类型为字符指针 char* ;
int main()
{
char ch = 'w';
char *pc = &ch;
*pc = 'w';
return 0;
}
这是一种使用情况:指针指向一个字符
还有一种使用情况:指针指向一个字符串
#include<stdio.h>
int main()
{
const char* p0 = "hello";
printf("%c", *p0);
return 0;
}
指针只有一个字节的大小,"hello"这个字符串,再加上’\0’,一共6个字节;肯定不能全都存进去;那么这个指针里存的是什么呢?
实际上指针里存储的是首字符的地址;可以通过指针来打印整个字符串;
#include <stdio.h>
int main()
{
char arr1[] = "hello";
char arr2[] = "hello";
const char* arr3 = "hello";
const char* arr4 = "hello"
if (arr1 == arr2)//如果arr1与arr2相等
{ //返回"arr1==arr2"
printf("arr1==arr2\n");
}
else if(arr1!=arr2)//不相等返回"arr1!=arr2"
{
printf("arr1!=arr2\n");
}
if (arr3 == arr4) //同理
{
printf("arr3==arr4\n");
}
else if(arr3!=arr4)//同理
{
printf("arr3!=arr4\n");
}
return 0;
}
运行结果:
为什么呢?
这里arr3和arr4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当几个指针。指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以arr1和arr2不同,arr3和str4相同。
内存就好像是一个自动贩卖机;
字符串常量是自动贩卖机里的一个货物;
指针是指向货物的地址,也就是指针知道去哪可以买到,所以指针相同,都指向贩卖机里的那个货物
字符串是切切实实的买到了这个货物,虽然都是一样的货物,但是被两个不同的人买去,一个是我的货物,一个是你的货物,就不同了,所以字符数组不同;
二、数组指针
1.数组指针的定义
在指针的基础内容里讲到了指针数组
int* arr1[10]; //整形指针的数组
那是存放指针的数组;
整形指针是存放整形变量的地址的;
字符指针是存放字符变量的地址的;
那么数组指针是什么呢?
答案是:存放数组地址的指针
指针数组,数组指针;
他俩名字相似,定义方式也能相似
int *p1[10];
int (*p2)[10];
p1, p2分别是什么?
p1是指针数组,上篇已经讲过了;
p2是数组指针,这里就涉及到了优先级的问题,[]是比*的优先级高的,所以如果没有括号,p2会先与[10]结合组成数组p2[10]类型是int *;就又变成指针数组了;
所以要用括号()将*和p2括起来,保证*和p2先结合成一个指针,类型是int [10];这才是数组指针;
2.数组名的两种用法
那么数组指针肯定是要存放整个数组的地址;
上篇讲过数组名就是首元素地址;
在sizeof()里与(&数组名)时数组名就表示整个数组,计算的整个数组的大小,单位是字节
那么arr与&arr有什么区别呢?
他们两个打印出来的地址相同;那他们难 道一样吗?
由此可见并不一样;
arr+1;只跳过了四个字节
&arr+1;跳过了四十个字节,也就是整个arr数组;
虽然arr与&arr打印出来的地址一样;
但是意义不同,
arr就只是arr[0]的地址;
&arr虽然打印出来是也是arr[0]的地址,但是他取出的是整个数组的地址;
但是,假设pa是一个数组指针,存储了整个数组arr的地址;
*pa就相当于数组名即首元素地址;(*pa==arr);
3.数组指针的用法
*pa==arr所以,他用在打印一维数组就和直接应用数组名或一级指针没什么区别,甚至还要繁琐一点;所以一般数组指针应用在二维数组上;
![在这里插入图片描述](https://img-blog.csdnimg.cn/1a44a753d2ce493b8137a30926b75ed0.png
应用在二维数组的方法:
可以把二维数组arr[n][]看成是n个一维数组构成的;
假设第一个一维数组名为arr1;
…
假设第i个一维数组名为arri;
pa是第一个一维数组的数组名(arr1)的地址; 友情提示:数组名是数组首元素地址!!!
pa+i是第i个一维数组的数组名(arri)的地址;
*pa得到第一个一维数组的数组名(arr1);
*(pa+i)得到第i个一维数组的数组名(arri);
*(pa+i)+j是第一个一维数组的第j个元素的地址;
所以要得到第i行第j个元素要怎么做呢?
答案是:*(*(pa+i)+j);
在这里告知大家一个错误的使用方法:希望大家可以避开;
主要错误原因是不了解二维数组在内存中的存储方式和数组指针的概念;
咱们先来讲二维数组的存储方式:
二维数组在内存中的存储方式是第二种,并不是第一种!!!
那么指针是如何存储二维数组数据的呢?
因为上述代码定义的数组指针的数组元素个数为5;
所以不管你是第几行的,都会强行存储5个元素,
而输出就只输出了这个指针数组里的前四个元素,
就跳到下一组的五个元素了;所以会造成数据丢失,
也导致了后面的数据不够存储最后一组前四个,而导致了越界访问,生成随机数;
三、函数指针
1.函数名的意义
各种类型的变量,一二维数组,就连自定义的结构体都有指针,那函数肯定有啊,肯定要雨露均沾啊;
咱们也先从函数名的意义开始讲;
函数就没数组他们那么讲究;函数名和&函数名是等价的;即test==&test
在传参的时候要传函数地址,就传函数名就ok
2.函数指针的定义
那么函数指针的书写格式是什么呢;
咱们先来看一下其他类型的指针是如何定义的;
intp;
charp;
…
(struct Stu)*p;(结构体指针)
int (*p)[5];(整形数组指针)
总结一条规律:
除了*p其他的结合到一起就是指针的类型;
函数指针也不例外;
定义函数指针的公式是:xx(*n)(i,j,…);
3.函数指针的使用方法
四、数组参数,指针参数
在写代码时,肯定会将数组或者指针等参数传给函数,那函数的该用什么来接受呢?
1.一维数组传参
1.接收普通数组
传数组名就是传过去了首元素的地址;
我可以用指针接收
我也可以用数组去接收:
为什么放数也许不放数也行
你传过来的只是首元素的地址,你接收数组首元素地址时,内存不会再专门为你开辟一块空间去存放元素,他只会根据你传过来的地址去寻找相应的元素;
这时arr[i]==*(p+i);
也就是这个数组只是用来接收首元素地址的,不是用来存储数据的;
2.接收指针数组
与上述相同,你传给我指针数组,我就可以用指针数组接收
因为指针数组,里面存放的都是指针,传过来的是首元素的地址即指针的地址,所以要用一个二级指针去接收
*(p+1)==arr[1];*(p+i)==arr[i];
**(p+1)==*arr[1]==2;
2.二维数组传参
1.用二维数组去接收比如arr[i][j]
这里要注意i可以不填但是j一定要填上即arr[i][],arr[i][j]这两种;
二维数组可以不知道有多少行,但是一定要知道一行有多少个元素;
2.用指针去接收
二维数组的数组名是首行的地址,因此不能用一级指针去接收,要用数组指针去接收;
具体方法请转到本文数组指针的用法
3.一级指针传参
用一级指针去接收一级指针
p0+i==下标为i的元素的地址.
一级指针可以接收哪些参数呢?
答:地址,一级指针变量;
4.二级指针传参
#include <stdio.h>
void print(int** p0)
{
printf("%d\n", **p0);
}
int main()
{
int a = 6;
int* p = &a;
int** pp = &p;
print(pp);
print(&p);
return 0;
}
二级指针可以接收哪些参数呢?
答:一级指针变量的地址,一级指针数组的数组名,二级指针变量;
5.函数指针传参
#include <stdio.h>
int test(int n, int m)
{
return n + m;
}
void print(int i, int j, int (*test)(int, int))//或者直接用函数接收int test(int,int)
{
int c = test(i, j); //如果是用函数指针接收,可以解引用(*test)(i,j)
printf("%d", c);//也可以不解引用直接正常使用test(i, j)
}
int main()
{
int a = 1, b = 2;
print(a, b, test);
return 0;
}
函数指针就只能接收函数的地址了(函数名==函数的地址)
五、函数指针数组
int*arr[5]
数组名是arr;类型是int*;元素个数为5;
先让arr(数组名)和[5]结合组成数组,然后再去填类型
函数指针数组,类型肯定是函数指针;
定义函数指针的公式是:xx(*n)(i,j,…);
拿int(*add)(int,int)这个函数指针举例,返回类型是int,指针名是add,接收两个int类型的参数;
int(*add[5])(int,int)
add先与[5]组成数组,那剩下的就是该数组的类型了;即int(*)(int,int)是数组add[5]的类型
函数指针数组的定义公式:()(*N[])()
使用方法:
#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(" 0:退出 \n");
printf("*********************** \n");
printf("请选择:");
scanf("%d", &input);
if ((input <= 4 && input > 0))
{
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = (*p[input])(x, y);
printf("ret = %d\n", ret);
}
else if(input >5&&input<0)
{
printf("输入有误\n");
}
}
return 0;
}
六、指向函数指针数组的指针
如何定义:
函数指针数组:int(*add[])(int,int)
以这个函数指针数组为例;
也就是说除了函数名add之外其他的东西就是该函数指针数组的类型;
那么这个指向函数指针数组的指针的类型就是: int(*[])(int,int)
所以指向函数指针数组的指针是int(*(*p)[])(int,int);
*p声明p是一个指针,剩下的就是p的类型;
定义公式:
#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 (*p1)(int, int) = &add;//函数指针
int (*p2)(int, int) = ⊂
int (*p3)(int, int) = &mul;
int (*p4)(int, int) = &div
int (*pp[5])(int, int) = { p1,p2,p3,p4 };
int(*(*pp0)[5])(int, int) = &pp;
//pp0+i==pp[i]==p1/p2/p3/p4==&add/&sub/&mul/div
//(*pp0+i)==*pp[i]==*p1/*p2/*p3/p4==add/sub/mul/div;
printf("%d\n", (*(*pp0 + 0))(9, 3));
printf("%d\n", (*(*pp0 + 1))(9, 3));
printf("%d\n", (*(*pp0 + 2))(9, 3));
printf("%d\n", (*(*pp0 + 3))(9, 3));
return 0;
}
七、回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
#include <stdio.h>
int add(int n, int m)
{
return n + m;
}
void print(int i, int j, int (*pf)(int, int))
{
int c = pf(i, j);
printf("%d", c);
}
int main()
{
int a = 1, b = 2;
print(a, b, add);
return 0;
}
这就是回调函数;
总结
以上就是今天要讲的内容,本文仅仅简单介绍了指针的进阶内容,而指针是很复杂精妙的知识,需要大家去理解去反复咀嚼;
欢迎大家批评指正,也希望大家动动小手点赞转发支持一下!
知识需要咀嚼_沉淀