浅谈C之精华---指针
今天是2016年的第一天,祝大家元旦快乐!哎,今天有点倒霉,代码写到一半,突然机子就没电了,幸好有保存,否则今天没有这篇日志的出现。
好了,今天以我个人的角度来深度剖析一下C语言中关于指针的用法以及注意事项,曾经我也是被指针坑得不要不要的,当然现在依然还是再被指针坑,因为指针用起来有很多细节的地方要注意。好了,废话不多说,我们来看看指针到底什么地方难!!!
指针到底是什么?
要说起这个概念,我们可以来理解信件和地址的概念。每个家庭都有一个固定的地址,当你从远方收到客人给你发信件,那么这个具体的地址就只有一个,信件就必须送到这个地址上来,我们才能收得到信件。我们就可以把指针理解成具体的地址,给指针一个指向也就是将信件送到具体的地址,可能我的说法比较抽象,不急,我们来看看图:
在32位操作系统中,只要是指针变量,永远都是占4个字节。
我们常常听人家说,数组是指针,指针就是数组。是吗?错,数组就是数组,指针就是指针,它们在某些情况下看起来很像,其实是穿着同一件衣服在欺骗使用它们的人。
我们也常听很多人说,数组的首地址就是数组的第一个元素,可以这么说,但是,它们之间实质是不能等效的,为什么这么说?
数组的首地址和数组的首元素的首地址是完成不同的概念,看着名字很像,其实有所区别。数组名是个左值,但不是可修改的左值,而数组首元素的首地址,也就是0地址,你可以对它进行赋值,就相当于给数组的元素赋值。所以数组的首地址和数组的首元素的首地址是不相同的概念,请那些所谓的程序员不要误导初学者。
那数组和指针到底有什么区别?
指针是间接寻址,数组是直接寻址,这就是两者在访问数据时的区别。指针的值是运行时从内存取得的,数组的值是编译时已经确定的。所以切记不要将两者混淆。我们只能说数组具有指针的特性,却不能说数组就是指针。
在嵌入式开发中,指针的运用是非常平凡的,比如说寄存器,我相信搞过单片机的人一定不会陌生对于寄存器的概念,网上对于寄存器的概念很详细,可以去看看,我这里简单解释一下,其实可以这么理解,寄存器就可以理解成为是一个储物柜,你想把东西存进去,那么就给它赋一个值。在ARM-v7架构的汇编上,我们可以看到这样的代码:
Loop :
Mov r0 , #1
Mov r1 , r0
Sub r2 , r1 , r0
Cmp r2 , r1
B loop
这里的r0 ,r1 ,r2就是寄存器,在汇编语言上,就是对一些数据进行简单的赋值操作,比如我们在C语言上定义一个整形变量的时候 : int i = 100 ;
在C语言经过预处理等阶段翻译成汇编,那么在计算机中会进行一系列的处理,先保护现场,然后向下偏移到具体的地址,将值赋给寄存器,在写到对应的地址上去,接着恢复现场。这是一个挺复杂的过程,用通俗的话来说,你定义一个变量,给变量赋值,但是计算机并不认识啊,计算机只认识二进制代码,那么汇编最后又用翻译成二进制代码,计算机就可以识别啦!所以计算机它要知道你定义这个变量,要用到哪个寄存器?要把什么值给寄存器?然后你想存到内存的什么位置?最后存到对应的位置。
能够理解寄存器,对理解指针是非常有帮助的,因为你在使用指针,也就相当于在操作内存,而操作内存,也就要联系到相关的寄存器。
在51单片机上,P0 , P1 ,P2 ,P3这些IO口,TMOD ,TCON,SCON这些其实就是寄存器,我们知道学习单片机第一步就是点亮LED灯,假设LED接在P0.0口,高电平点亮,那么要点亮LED,也就是给IO口写一个1,LED灯就点亮了。
P0 = 0x01 ;
其实给P0口赋值0x01--------- > 0000 0001 低4位的第一位被置为1,那么就点亮了。这是最简单的一个实例。
在嵌入式开发中,有区别于单片机,嵌入式其实就是软硬可裁剪,可带操作系统的一种设备,我们日常用的手机,电脑,平板等等电子产品,属于嵌入式产品。我举个例子,在tiny4412上,你想点亮LED灯,那可就不是像51那么方便了,首先你要告诉ARM,你要配置那个IO口,要什么状态,有上拉,推挽输出,下拉,中断等等。然后再给状态寄存器赋值,LED灯才能点亮。
说了这么多,我们还是直接上代码,有一些概念我以前写过了,不再重复。我们来看看指针有什么常用的技巧和方法。
#include <stdio.h> #include <stdlib.h> #define Name "Yangyuanxin" /*打印规定长度的数组*/ void print_array(int len , void *buffer) ; /*从前后往查找字符串中的字符*/ char * find_char(const char * s, int c); /*字符串拷贝*/ char * my_strcpy(char *dest , const char *src) ; /*实现两数求和*/ int add(int x , int y) ; //定义一个结构体,放着相关的指针成员 struct node { char *p ; int (*fun)(int , int); void (*func)(int , void *); }; //初始化结构体成员 struct node pointer = { p:Name , //:相当于.p = Name fun:add , func:print_array , }; int main(void) { printf("%p",printf); printf("\n--------------------------指针1:-------------------------\n"); int *a = NULL; //为了防止产生野指针,所以一般指针在定义初始化之前要先让它指向NULL a = malloc(1); //如果去掉这条语句的话那么整个代码会因此而崩溃,因为指针定义的时候并没有空间。 *a = 12 ; //a相当于地址,*a就是地址上相应的值。这有点像链表的特性,有一个数据域和一个数据域 printf("a = %d\n*a = %d\n\n",a,*a); printf("\n--------------------------指针2:-------------------------\n"); int i = 0; int *p = &i ; //指针p获取了i的地址 //以下这种做法在嵌入式开发中非常普遍,前面相当于寄存器,后面就是给寄存器赋值啦,这就是指针在开发中的运用。 *(volatile unsigned int *)0x29FECC = 100; //0x29FECC这个地址其实就是变量i地址,所以i的值此时为100 printf("i = %d\n\n" , i); printf("\n--------------------------指针3---->指针的数组特性:-------\n"); int buffer[10] = {2,3,4,5,6,7,8,10 ,11,12}; print_array(10 , buffer);//传入这个函数,长度为10 , 传入buffer这个数组 int *ppp = buffer ; //定义一个指针接受一个数组,此时指针就相当于有了空间 for(i = 0 ; i < 10 ; i ++){ printf("ppp[%d]:%d\n",i ,ppp[i]); } //还可以这么来,利用数组的自动计数功能 #define NR(x) (sizeof(x)/sizeof(x[0])) putchar('\n'); for(i = 0 ; i < NR(buffer) ; i ++){ printf("ppp[%d]:%d\n",i ,ppp[i]); } printf("\n--------------------------指针4--->字符串指针\n"); char *q = "HELLO"; printf("%s\n",find_char(q , 'H')); //查找字符串中的字符.从前往后 char buf[20]; my_strcpy(buf , q); //将字符串拷贝到buf数组里 printf("buf = %s\n",buf); //打印出buf printf("\n--------------------------指针5---->回调函数:-----------------\n"); int (*fun)(int , int) = add; //回调函数fun取得add函数的首地址,此时回调函数fun就等效于add printf("fun = %d\n",fun(1 , 2)); //所以你可以对fun传值 void (*func)(int , void *) = print_array ; //定义一个回调函数指向print_array这个函数 func(10 , buffer); //此时你可以看到数组的长度和数组的元素被打印出来 typedef int (*pfce)( const char*, ... ) ; //重定义一个函数指针pfce,函数指针的类型可变参 pfce pri = printf ; //定义一个函数指针变量pri指向printf pri("Hello world!\n"); //这时候pri就是printf了,哈哈 typedef int (*put)(int ch); //重定义一个回调函数 put myputchar = putchar ; //让回调函数变量指向putchar myputchar('A'); //于是myputchar就是putchar啦,你也可以用它来putchar啦!!! myputchar('\n'); //字符串黏贴的用法## #define XNAME(n) x##n #define PRI(n) printf("x"#n" = %d\n",x##n) int XNAME(1)=12; //int x1=12; PRI(1); printf("\n--------------------------指针6---->结构体指针:---------------\n"); printf("pointer.p = %s \n",pointer.p); printf("pointer.fun = %d\n",pointer.fun(1 , 2)); return 0 ; } void print_array(int len , void *buffer) { printf("打印出传进来的数组:\n"); int *p = (int *)buffer ; //这一步操作其实就是用指针p来等效于buffer,此时指针就具有了数组的特性。 int i ; for(i = 0 ; i < len ; i ++) { printf("buffer[%d]:%d\n",i , p[i]);//将传进来的数组和数组的长度都打印出来 } putchar('\n'); } char * find_char(const char * s, int c) { for(; *s != (char) c; ++s) if (*s == '\0') //如果传进来的字符串=='\0',那么就返回一个NULL值 return NULL; return (char *) s; //否则就从找到的那个字符c的地方开始从前往后遍历,直到'\0' } char * my_strcpy(char *dest , const char *src) { char *tmp = dest ; while((*dest++=*src++)!='\0'); return dest ; } int add(int x , int y) { int z; z = x+y; return z ; } 运行结果:
因为指针是C语言的精髓,我也不能在一篇日志上将指针剖析完整,我也在不断的学习和应用中,如果要深入的去理解它,那么还是要多看看书,多去应用才能明白,或许我这篇日志也可能有不对的地方,但人总是与时俱进,到了什么时候,就会有什么能力的呈现,理解能力也会更加深刻,编程是需要不断总结和不断应用的,只运用不总结,那么就算有经验了,一些东西还是只能看了才会用,但是经常总结,对知识的熟练程度才会慢慢加强!好了,今天到此为止,改天有机会再和大家一起探讨交流技术问题!同时言论有失误的地方,也请高手指正。
我的CSDN博客: http://blog.csdn.net/morixinguan/article/month/2015/12
浅谈C之精华---指针的更多相关文章
- 浅谈C中的指针和数组(一)
本文转载地址:http://www.cnblogs.com/dolphin0520/archive/2011/11/09/2242138.html 在原文的基础上加入自己的想法作为修改. 指针是C/C ...
- 浅谈c语言的指针
对于非计算机专业的同学,c语言的指针往往就是老师的一句“指针不考“就带过了.c语言的指针号称是c语言的灵魂,是c语言中最精妙的部分. 指针本质上也是变量,也就是一段内存,只是他的特殊之处是他存储的数据 ...
- 浅谈C++的this指针
之所以写这篇文章,主要是为了回答网友 zhancaihua123同学的下面几个问题: father* p=new son;p->disp(...);father是父类,son是子类.disp是一 ...
- 浅谈 “空指针、野指针、void*”
Author: JW. Zhou Date: 2014/7/2 一.空指针(0/NULL) 返回NULL和返回0是完全等价的,因为NULL和0都表示空指针,换句话说:空指针是什么,就是 ...
- 浅谈C中的指针和数组(七)
现在到揭露数组名本质的时候了,先给出三个结论: (1)数组名的内涵在于其指代实体是一种数据结构,这种数据结构就是数组: (2)数组名的外延在于其可以转换为指向其指代实体的指针,而且是一个指针常量: ( ...
- 浅谈C中的指针和数组(六)
数组和指针,原本不想在写了,觉得这部分差不多了,但是自己在写程序的时候还是发现了一个错误.首先说一下我的要求: 给一个函数传递一个二维数组,然后我想在这个函数里面计算这个数组的行数. 写个类似的错误D ...
- 浅谈C中的指针和数组(五)
前面写了一些C指针和数组的一些知识,但是还有一些很重要的知识没有交代,这里做一个补充. 首先看一下,普通变量(指针也是变量)和数组名查看地址的方式是不同的. 查看数组变量的地址,不需要使用 & ...
- 浅谈C中的指针和数组(四)
原文转载地址:http://see.xidian.edu.cn/cpp/html/476.html 在原文的基础上增加自己的思想作为自己的修改 指针数组和数组指针的内存布局 初学者总是分不出指针数组与 ...
- 浅谈C中的指针和数组(二)
原文转载地址:http://see.xidian.edu.cn/cpp/html/475.html 在原文的基础上增加自己的想法作为修改 很多初学者弄不清指针和数组到底有什么样的关系.我现在就告诉你: ...
随机推荐
- Hive基本原理及环境搭建
今天我主要是在折腾这个Hive,早上看了一下书,最开始有点凌乱,后面慢慢地发现,hive其实挺简单的,以我的理解就是和数据库有关的东西,那这样的话对我来说就容易多啦,因为我对sql语法应该是比较熟悉了 ...
- Android初级教程:如何自定义一个状态选择器
有这样一种场景:点击一下某个按钮或者图片(view),改变了样式(一般改变背景颜色).这个时候一种解决方案,可能就是状态选择器.接下来就介绍如何实现状态选择器: 步骤: 一.新建这样的文件夹:res/ ...
- iOS日历中给一个事件添加多个提醒
大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请多提意见,如果觉得不错请多多支持点赞.谢谢! hopy ;) iOS自带的日历应用中,我们最多只能给一个事件设置2个提醒,但 ...
- Django开发自己的博客系统
好久之前就想做一下自己的博客系统了,但是在网上查了查好像是需要会一些Node.js的相关知识,而且还要安装辣么多的库什么的,就不想碰了.但是我遇到了Django这么一款神器,没想到我的博客系统就这么建 ...
- android 选项卡TabHost
选项卡主要有TabHost.TabWiget和 FramentLayout3个组件组成,用于实现一个多标签的用户界面,通过他可以将一个复杂的对话分隔成若干个标签页,实现对信息的分类显示和管理.使用给组 ...
- Day 21:Docker 入门教程
几个月以前,红帽(Red Hat)宣布了在 Docker 技术上和 dotCloud 建立合作关系.在那时候,我并没有时间去学习关于 Docker 的知识,所以在今天,趁着这个 30 天的挑战,我决定 ...
- MVPHelper更新日志 --- 新增常规分包模式
MVPHelper是一款可以自动生成MVP接口以及实现类的android studio插件,彻底解放双手! MVPHelper更新版本啦. 由于之前只支持contract模式,不是很符合大众口味 所以 ...
- Gradle 1.12 翻译——第九章 Groovy快速入门
由于时间关系,没办法同时做笔记和翻译,关于Gradle的用户指南,本博客不再做相关笔记,而只对未翻译章节进行翻译并在此发表. 有关其他已翻译的章节请关注Github上的项目:https://githu ...
- 不窃取用户隐私的搜索引擎: DuckDuckGo
不窃取用户隐私的搜索引擎: DuckDuckGo https://duckduckgo.com/ 最近goggle不给力, baidu搜出来的很多都是垃圾, bing用久了很烦. 于是用上了DuckD ...
- ZooKeeper 实现分布式队列
使用场景 在传统的单进程编程中,我们使用队列来存储数据结构,用来在多线程之间共享或者传递数据.在分布式环境下,同样需要一个类似单进程的组件, 用来实现跨进程.跨主机.跨网络的数据共享和数据传递.这就 ...