关于所有指针类型的那些运算

对于一些教材上说:

  • 指针的指针就是指向指针的指针
  • 结构指针就是指向结构的指针
  • 数组指针就是指向数组的指针
  • 函数指针就是指向函数的指针

    当你看到函数指针时,指针之间的运算你就已经突破了。

对这些说法展开的讨论:

那这个定义这么写的话,是不是就可以问:指针的指针就一定要指向一个指针,结构指针就一定要指向一个结构.....

1.先看指针的指针,就是二级指针。

指针是一种新的数据类型,既然是数据类型,就要知道他的宽度。到目前为止,在32位系统下只要是 * 类型即时4字节,别急,往下看,先来看二级指针的写法:

int  a = 1;     //声明一个变量a
int *p = &a;    //声明一个指针p指向a的地址;
int **pp=&p;    //声明一个二级指针指向p的地址,
此时,如果要打印**pp指向的值,那就是1;

在来看一下指针间的运算,运算要在类型确定的情况下才能够进行运算;a - b 的值就等于(a - b)的值 / 类型去掉一个 * 号后类型的大小;

指针加数字/指针减数字

char* p = (char*)10;
printf("%d",p+1);

char* p=(char*)10;
printf("%d",p-1);

打印出来的就是11,char* 类型去掉一个 * 之后的大小就是1;那如果p的类型是int* 类型,那打印出来的就是14;减法是同样的道理,

指针减指针:

 int arr[10] = { 0 };
 int *p = &arr[1];     //x+4
 int *q = &arr[6];     //x+24
 printf("%d\n", p - q);//-5

计算时,要先计算出被减数和减数之间有多少字节,然后用这个字节在除以类型去掉一个 * 之后的大小;所以计算完是-5;那能做减法,能做加法吗?  对于指针来说,地址是编译器分配的,不能确定俩个指针相加完之后的值在不在有效内存上,如果强行让其相加的话,也是可以的,但是没有意义,你的工作是为了利用计算机解决问题,现在好了,你用问题把计算机解决掉了!(补充一点知识,从汇编指令上看,强制转换并不产生实际的计算指令,感兴趣可以去研究一下)

现在来提升难度,感受被指针支配的恐惧;

char* a1;
char** a2;
char*** a3;
printf("%d\n",*(a1+2));//+2
printf("%d\n",a1[2]);    //+2

打印的俩种语句的反汇编是一样的,可以自己打印一下,这就说明,数组的计算和指针的技术方式是一样的;他们都是加二,如果把a1替换成a2呢,先char**减去一个 * ,类型大小时4,加2就是加俩个大小为4的类型,就是+8;再来看一种:

printf("%d\n",*(*(a2+2)+3));

难度上升了一个维度,a2+2加的是8,上一句已经解释了为什么,那来看+3,现在*(a2+2)整体的类型是什么,a2的类型是char** ,那*(a2+2)的类型就是char*类型,char*类型的值+3就是将char*减去一个*之后的类型大小,为1,所以就是+3,最后的大小就是a1+8+3;

printf("%d\n",*(*(*(a3+3)+4)+5));

难度看似有上升了,真的是这样吗?和上边的计算方法如出一辙,它最后的运算是+12+16+5;

相信我,当你已经不再害怕看到这些东西的时候,那你指针的瓶颈就已经突破了;当你掌握了方法,再多的括号都不是事;

那现在来回头看上面的代码,仔细看第一步的俩个printf,俩种语句汇编是一样的,所以,下边的俩个printf分别是a2[2][3],a3[3][4][5],到现在,你已经学完了一维数组,二维数组,三维数组,那在高维的计算还能不会吗,事实上也不会有人回去声明3维以上的数组的;这些维度的数组计算方式一样的原因是  计算机在实现这些多维数组时,在底层都是一维数组的声明方式,所谓的维度完全是为了方便开发人员,和计算机无关;

2.结构体指针

struct str{
int a;
int b;
}
int main()
{
    str* p;//结构体指针
    p++;
}

结构体用好几种不同的声明类型,文章只介绍结构体类型的运算,不包括内存对齐,声明类型这些东西:

可以计算结构体指针加数字或者减数字,先算出结构体类型的大小,和上别的指针加减法师一样的,str结构体大小为8,那计算就以8为单位计算;

3.数组指针

声明类型为int (*p)[ 5 ],这个要和指针数组区分开,指针数组声明方式是int* p[5],因为[]运算符的优先级大于 * ,所以它的重点是数组;而()的优先级大于[],重点强调的是指针,所以前一种类型就是数组指针;

int main(){
    int (*px)[5];
    printf("%d\n",sizeof(px));
    px=10;
    px++; 
    printf("%d",px);
}

来看sizeof(px)会输出多少,想要知道输出多少,就要知道px的数据类型,每种变量,最重要的就是它的类型。px的类型为int (*)[5],那它的类型大小就是20,在看下一行的赋值语句,这一行是通不过编译 的,因为10的类型和px的类型不一样,需要强制转换一下:

px=(int (*)[5])10;这样才能通过编译;再下一行px++,相当于px+1,上边的指针运算,int (*)[5]类型去掉一个 * 之后是int[5],类型大小为20,所以最后输出的px位30;换成char类型也是同样的道理,就是类型大小编程了1,;

那现在提升难度,多维数组指针,写法是int (*p)[5][5];如果已经了解了多维指针和数组指针,那这个就可以理解为这两种指针类型的结合体,计算方式也八九不离十。直接上题:

char str1[]={0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,
            0x19,0x11,0x21,0x34,0x23,0x75,0x21,0x33,0x12,0x23};
int main(){
    int (*p1)[5];                //一维数组指针
    p1 = (int (*)[5])str1;        //给p1赋值
    printf("%x\n",*(*(p1+2)+3));

    char (*p2)[3][4];            //二维数组指针声明
    p2 = (char (*)[2][3])str1;   //给p2赋值
    printf("%x\n",*(*(*(p2+2)+3)+4));
}

那就直接说答案了:第一个输出的是4*2*5*+4*3*5 = .....不好意思!越界了,数有点大;算术中的4是int的类型大小。就假装得到的值是20,在数组中数的时候,因为是char类型,但是p1是int类型,输出的是 23123321,这里还有一个知识点,就是现在大部分的windows设备都是采用小端存储,就是低地址在高位;

在来看下边的p2:计算方式是:1*2*3*2+1*2*3*3+1*2*3*4 = ......还是比较大,算术中的1代表char类型大小,紧跟着的2*3是数组的[2][3],2就是+2了,后边也是一样的计算方式;

从代码中还可以总结到,一维数组想要取值需要俩个 * ,二维数组想要取值需要三个 *;

 

4.函数指针

先来看函数指针的写法:int (*pFuntion)(int,int);最重要的 它的宽度是  4。

int main(){ 
    int (*pFunction)(int,int);
    pFunction=(int (__cdecl *(int,int)))10;//赋值语句;
}

赋值需要转换的类型就是pFunction的类型,这里又有一个小知识,__cdecl是编译器默认的调用约定,参数传递是从右向左,默认的,所以不写也可以;

也可以这样赋值:

int func(int a,int b){
    return a+b;

}
int main(){ 
    int (*pFunction)(int,int);
    pFunction=func;//赋值语句;
}

这样应该也能够看懂吧,func的类型正好是(int (*(int,int)))类型,所以不需要强制转换;

函数指针运算

加法:进行运算时,要先去掉一个 * 在进行运算,但是,在去掉之后还能确定函数宽度吗?函数的宽度可能是变化的,所以不能进行加法运算,

减法:emmmmm......让你们失望了,他也不能做减法运算,道理是一样的;又有一个小知识,函数也是全局变量;和找全局变量的 方式是一样的;

但是,看着这个函数指针确有点鸡肋,不知道什么时候用它,我来总结一下他一般用在什么地方:

#include <stdio.h>  
   
//Calculate用于计算积分。一共三个参数。第一个为函数指针func,指向待积分函数。二三参数为积分上下限  
double Calculate(double(*func)(double x), double a, double b)  
{  
    double dx = 0.0001;//细分的区间长度  
    double sum = 0;  
    for (double xi = a+dx; xi <= b; xi+=dx)  
    {  
       double area = func(xi)*dx;  
       sum +=area;  
    }  
    return sum;  
}  
   
double func_1(double x)  
{  
    return x*x;  
}  
   
double func_2(double x)  
{  
    return x*x*x;  
}  
   
void main()  
{  
    printf("%lf\n", Calculate(func_1, 0, 1));  
    printf("%lf\n", Calculate(func_2, 0, 1));  
}  

这个是我看别人的.....,原文链接在这里

https://blog.csdn.net/wudidajundui/article/details/80411594?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522161779085016780274117123%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=161779085016780274117123&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_v2~rank_v29-2-80411594.pc_search_result_cache&utm_term=%E5%87%BD%E6%95%B0%E6%8C%87%E9%92%88%E7%94%A8%E5%A4%84&spm=1018.2226.3001.4187

这个例子确实很经典,但是还有一个就是在加载dll的时候,动态调用dll中的函数可以保证和 dll 中需要调用的函数申明一致;

  • 5
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Aaronpack

你的鼓励是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值