指针是C语言的灵魂,涉及编程应用无处不在。同时它也是C语言程序难读、难理解的地方,在此结合自己的应用体会,本章特别将C语言的指针知识进行要点总结。

一、指针是什么

指针就是内存地址。

哪怕是定义再复杂的指针,最终它的含义都只有这点,无它。

那为什么叫指针呢?这是因为,计算机内存中每个地址都会存放数据,所以逻辑意义上讲就是一个地址始终指向了一个内存单元。根据人们的感官意识,就把这个地址数据叫指针。我画个图就能说明一切:

时钟指针
内存指针

所以,在32位的系统中,指针最终的结果无非就是一个32位的数字:0x_XXXX_XXXX。

二、指针总结

(一) 指针类型

1.普通指针

既然指针是一个32位的数字,那就应该属于“int”类型啊,那怎么会有很多种指针类型呢?这其实是C语言编译器的聪明之处,它把指针这个32位的地址数据,做了一个上层的抽象优化,它使得用指针的人在编程应用的时候变得非常的方便。

比如,现在有个10个数字,它们的数据范围都在0-255范围内,那我们只需分配10个字节的内存单元就可以了。如果从内存0x0000_1000地址开始,那结束地址应该是0x0000_1009。那我现在如果要得到第n个数字的内存地址,计算方法是:0x0000_1000+n。

但是,如果这10个数字是int类型的,那么我们就需要4*10个字节来存放,现在要得到第n个数字的内存地址,计算方法是:0x0000_1000+n*4。

进一步,如果这10个存储的对象是自定义数据类型,每个对象的长度是m字节,现在要得到第n个数字的内存地址,计算方法是:0x0000_1000+n*m。

这样一来,计算过程就会很复杂,因为你必须时刻要搞清楚每个储存对象的类型是什么,才能知道每单元存储对象的字节空间。

所以,C语言就干脆规定:你必须先定义好存储对象的数据类型是什么?也即用一段连续的内存是用来存储的是什么数据类型,那么这段内存地址就叫该数据类型的指针。比如前面的10个对象,我们首先定义它们存储的对象是int类型,首地址用指针来定义就是:

int *p=(int *) 0x0000_1000;

那现在要得到第n个对象的内存地址,就不用再考虑对象每个单元的长度了,直接计算出:

p+n。

而且每当出现类似p++的时候,p的值就不是简单+1,而是 p=p+sizeof(指向的数据类型)。

稍微解释下,就是定义某种类型指针的固定方法是:在此数据类型变量定义方法定义出来的变量前面加一个标记*号即可。比如定义一个int类型数据类型变量是: int p;那现在只需要在变量p前面加一个*就OK。

接着说:(int *) 0x0000_1000;

由于C语言规定,变量赋值必须要保证是相同类型。因此p已经定定义成了指针,那么等号右边的也必须是同等类型的变量。由于0x0000_1000是个立即数,它不属于任何类型的变量,因此我们首先必须把它进行强制转换。强制转换成指向int型的指针需要用:(int *) 。

所以当你再次看到这样初始化指针的时候,就不会感到奇怪和陌生了:

struct mydefine *point=(struct mydefine *) address;

mydefine是我们自定义的一种数据类型,指向它的指针定义和初始化与前面的int型指针并无差异。

2.函数指针

如果说普通变量型指针是内存地址还可以理解的话,那指向函数的指针怎么理解呢?难道函数也会有内存地址吗?是的!我们来看一下C语言程序的运行过程就明白了。

比如以下求和函数:

int d;
d=add(1,2);int add(int a,int b)
{
int  c;
c=a+b;
return c;
}

它经过编译之后,会变成如下格式的汇编程序:

pop 2
pop 1
call  addadd:
mov  eax,[esp+8]
add  eax,[esp+4]    ;eax作为函数调用返回值
ret

可以看到,C语言函数调用在经过汇编之后,就变成了过程调用,汇编程序中每个过程都会有一个标号,具体到上面的汇编程序就是:call 函数标号(add)。而这个函数标号最终在生产机器代码的时候,其实就是一个偏移值。所以C语言的函数指针就相当于汇编程序中的标号偏移,每个C函数经链接程序生成完整的机器代码后,对应的标号偏移其实就是一个逻辑意义上个的内存地址(它是一个相对偏移量的内存地址而不是绝对物理地址),可以表征函数放置在内存中的位置。

函数指针的定义比较特别:函数返回值类型 (* 指针变量名) (函数参数列表);它其实就是将普通“函数声明”中的“函数名”改成“(*指针变量名)”。

如 int (*p)(int, int); 代表含义:定义了一个指针变量 p,该指针变量可以指向返回值类型为 int 型,且有两个整型参数的函数。p 的类型为 int (*)(int,int)。

当定义了这个函数指针p之后,就可以用一个对应的真实函数来对它进行赋值,那么这个p就将取得这个真实函数的首地址,进而指向了具体的函数,后面对这个函数的任何调用都可以通过*p()来运行。

那么,究竟函数指针有什么用呢?一般初级编程者很少用到。函数指针由于指向的是函数,因此它最大的应用之处在于可以作为函数的参数:也即一个把函数本身作为目标函数的参数。

比如,我们有这样一个需求:写出一个函数destfunc,要求是当某值select=1时需执行函数:function1,否则需执行函数:function2。普通编写逻辑应该如下:

destfunc(1);/*调用*/void destfunc(char select)
{ if (select==1){  funciton1();}else {  funciton2();}
}void funciton1(void)
{ ...}void funciton2(void)
{ ...}

我们可以进一步优化这种写法,即把函数本身当成参数,由于函数本身是一长串可执行代码,因此要把它做为参数,就只有用各自的指针来引用:

void  (*funcp1)(void);
void  (*funcp2)(void); funcp1=&funciton1;
funcp2=&funciton2;destfunc(1,funcp1,funcp2);/*调用*/void destfunc(char select,void  (*)(void) funcp1,void  (*)(void) funcp2)
{ if (select==1){  *funcp1();}else {  *funcp2();}
}

或者这种方式应该也行:

void  (*funcp1)(void);
void  (*funcp2)(void); funcp1=funciton1;
funcp2=funciton2;destfunc(1,funcp1,funcp2);/*调用*/void destfunc(char select,void  (*)(void) funcp1,void  (*)(void) funcp2)
{ if (select==1){  funcp1();}else {  funcp2();}
}

请注意写法有点复杂,再次感叹,C语言最大的问题就是相当的绕......为此,我们只有想办法尽量把程序写得不那么绕,用typedef可以把上面的程序格式进行优化:

typedef int (*func_point)(void);func_point  funcp1=funciton1;
func_point  funcp2=funciton2;destfunc(1,funcp11,funcp2);   /*调用*/void destfunc(char select,func_point fp1,func_point fp2)
{ if (select==1){  fp1();}else {  fp2();}
}void funciton1(void)
{ ...}void funciton2(void)
{ ...}

函数指针对于功能相近、但是有细微差别的函数区分调用十分有用。下面我们来看一个非常经典的案例:根据函数指针变量operation指向不同的运算函数可实现加法运算、减法运算、乘法运算、除法运算。

设计如下函数:

int calculate(int a, int b, fun_t operation)
{int result;result = operation(a, b); // 运算return result;
}

其中,fun_t是一个函数指针,其定义为:

typedef int (*fun_t)(int, int);

该函数指针fun_t指向一个带两个int类型的形参、int类型的返回值的函数。使用关键字typedefint (*)(int, int)进行重命名(封装)为fun_t

最终,函数指针变量operation调用不同函数的实现方法如下,可以看到函数名本身就是一个指向自己的指针

int main(void)
{int result;int a = 192, b = 48;/* 两个数相加的操作 */result = calculate(a, b, add2);printf("加法运算: %d+%d = %dn",a, b, result);/* 两个数相减的操作 */result = calculate(a, b, sub2);printf("减法运算: %d-%d = %dn",a, b, result);/* 两个数相乘的操作 */result = calculate(a, b, mul2);printf("乘法运算: %d*%d = %dn",a, b, result);/* 两个数相除的操作 */result = calculate(a, b, div2);printf("除法运算: %d/%d = %dn",a, b, result);return 0;
}

实现运算的4个函数很简单,如下。这些函数就具备功能相近、但是有细微差别的特征。

int add2(int a, int b)
{return a+b;
}int sub2(int a, int b)
{return a-b;
}int mul2(int a, int b)
{return a*b;
}int div2(int a, int b)
{return a/b;
}

函数指针中,还有一个大名鼎鼎的应用:回调函数。

3.指针函数

既然指针是一种数据类型,那么就可以用函数来对它进行返回,包括上面的指向函数的指针。指针函数的定义格式就相当简单了,和指针变量一样,就是在普通函数基础上加一个*符号。

int *p(int, int);

所以,你现在应该能搞清楚这两者之间的区别了:
int (*p)(int, int) VS int *p(int, int)

但是说实话,这种极其细微的语法方式会带来这么大的区别,编程者很容易忘记和混淆。所以,这也是C语言这方面不友好的地方。

(二) 多重指针

前面说到定义某种类型指针的固定方法是:在此数据类型变量定义方法定义出来的变量前面加一个标记*号即可。最重要的是,这个方法可以扩展,而且能扩展到你哭为止:

比如现在p已经是一个int型指针变量,定义该变量的方式是int *p。那么我们再定义一个指向“int型指针”类型的指针,继续使用老方法---在此数据类型变量定义方法定义出来的变量前面加一个标记号: int **p。这个时候,p变量就变成了传说中的指向指针的指针。

还没有完,继续用这种方法往下扩展:int ***p,对它可以叫做"指向指针的指针的指针"。其实也没有必要那么复杂,就叫指向指针的指针就可以了:int ***p这种方式是只是在炫技,从实际应用上讲它没有太大意义,因为就用int **p就已经可以表示指向指针的指针了。

指针这样无限拓展下去,就会变成如下逻辑结构:

指向指针的指针形成链表

(三) 指针数组

指针只是一种数据类型而已,C语言中同种类型的数据就可以组成数组。而数组在内存中和上面的链表不同,它是连续分配空间的:

定义指针数组也是在传统数组的基础上加一个*符号:

int *p[10];

c语言int类型乘法溢出_【原创】C语言指针自我总结相关推荐

  1. C语言int类型数值溢出会怎么样

    c语言int类型数值占四个字节,就是32位.有符号能表示的范围为-2147483648 ~ 2147483647,无符号数表示的范围0~4294967295. 大于0的数: #include<s ...

  2. 用bool函数判断int类型相加溢出_Go是强类型语言,不支持隐式类型转换,那该怎么办?...

    Go语言中strconv包实现了基本数据类型和其字符串表示的相互转换. strconv包 strconv包实现了基本数据类型与其字符串表示的转换,主要有以下常用函数:Atoi().Itia().par ...

  3. C 语言 int 型乘法溢出问题

    2019独角兽企业重金招聘Python工程师标准>>> long l; int a, b; l = a*b; 因为 a*b 的结果仍然以 int 型保存, 所以即使 l 为long, ...

  4. INT 类型长度溢出缺陷

    读程序,猜答案吧! public class IntOut {/** 纳秒->毫秒转换 */private static final int NANOTIME = 1000000;/** 轮询最 ...

  5. request获取int类型的值_获取通话记录或通讯录的数量为负值

    安卓源码避坑指南5--获取通话记录或通讯录的数量为负值 通过蓝牙PBAP协议同步通讯录.通话记录时,想必对其数量大小也是很感兴趣的,因此一般的设计思路都是先获取到同步对象的总大小,然后再同步该对象的具 ...

  6. Go语言-int类型取值范围

    相比于C/C++语言的int类型,GO语言提供了多种int类型可供选择,有int8.int16.int32.int64.int.uint8.uint16.uint32.uint64.uint.文章目录 ...

  7. c语言 int类型转换为string类型

    1. int类型 12345 转换为char类型 12345: #include "stdio.h" /** *int类型转换为string类型 *Int_i: 要转换的int类型 ...

  8. C语言int 类型的表示范围 sizeof()函数

    一 . 在如今32位和64位的计算机系统中,int类型占32位,其中一位为符号位.占32位int的取值范围为-2147483648-2147483647(). 一些类型的输入输出符号: 1.%d有符号 ...

  9. python语言的类型是_Python到底是强类型语言,还是弱类型语言?

    0.前言 我在上一篇文章中分析了 的话题,在文章发布后,有读者跟我讨论起了另一个关于类型的问题,但是,我们很快就出现了重大分歧. 我们主要的分歧就在于:Python 到底是不是强类型语言?我认为是,而 ...

最新文章

  1. Python Data Structures
  2. linux设置数据库定时备份,linux中使用计划任务进行数据库定期备份
  3. k8s网络之Calico网络
  4. The Castle(信息学奥赛一本通-T1250)
  5. (49)Xilinx Subtracter IP核配置(十)(第10天)
  6. 2016年3月8日----Javascript的函数
  7. pycharm使用总结
  8. Play Framework + ReactiveMongo 环境搭建
  9. 计算机体系结构----指令流水线吞吐率、效率计算
  10. python运维自动化老男孩_Day1 老男孩python自动化运维课程学习笔记
  11. mysql获取当前时间+1天_mysql获取当前时间,前一天,后一天
  12. 推荐一大波让你直呼哇塞的Canvas库
  13. 今日头条信息流 - 橙子建站
  14. c语言ab43错误的是,求助,AB+没法玩下去了,详情请看报错代码
  15. sql查询_SQL查询
  16. 阿里云物联网平台搭建
  17. 自己动手学TCP/IP--ICMP(ping报文)
  18. 7485设计8位比较器
  19. 2023河北工业大学计算机考研信息汇总
  20. 数据分析师需要学习哪些技能?

热门文章

  1. 60-10-060-命令-kafka-run-class.sh
  2. 阿里技术面:ReadWriteLock读写之间互斥吗?
  3. java 自动装箱自动拆箱,java自动装箱、自动拆箱和正常情况性能比较
  4. mysql索引条件下推_MySQL 索引条件下推优化
  5. mac下electron始终安装不成功解决办法
  6. 实现Runnable接口
  7. Java并发编程-常用的辅助类
  8. Python3基础3——List列表的增删改和内建函数的用法
  9. BZOJ 3282 Link Cut Tree (LCT)
  10. 004:2的幂次方表示