浅谈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


目录
相关文章
|
存储 网络协议 文件存储
云计算——常见存储类型
云计算——常见存储类型
1071 0
|
安全 前端开发 关系型数据库
单机手动部署OceanBase集群
单机手动部署OceanBase的实验步骤,有详细截图
1413 0
|
5月前
|
机器学习/深度学习 人工智能 算法
大型多模态推理模型技术演进综述:从模块化架构到原生推理能力的综合分析
该研究系统梳理了大型多模态推理模型(LMRMs)的技术发展,从早期模块化架构到统一的语言中心框架,提出原生LMRMs(N-LMRMs)的前沿概念。论文划分三个技术演进阶段及一个前瞻性范式,深入探讨关键挑战与评估基准,为构建复杂动态环境中的稳健AI系统提供理论框架。未来方向聚焦全模态泛化、深度推理与智能体行为,推动跨模态融合与自主交互能力的发展。
294 13
大型多模态推理模型技术演进综述:从模块化架构到原生推理能力的综合分析
|
4月前
|
机器学习/深度学习 人工智能 算法
人机融合智能 | 以人为中心人工智能新理念
本文探讨了“以人为中心的人工智能”(HCAI)理念,强调将人的需求、价值和能力置于AI设计与开发的核心。HCAI旨在确保AI技术服务于人类,增强而非取代人类能力,避免潜在危害。文章分析了AI的双刃剑效应及其社会挑战,并提出了HCAI的设计目标与实施路径,涵盖技术、用户和伦理三大维度。通过系统化方法,HCAI可推动AI的安全与可持续发展,为国内外相关研究提供重要参考。
242 3
|
前端开发 数据可视化 JavaScript
探索前端可视化开发:低代码平台原理与实践
【4月更文挑战第7天】本文探讨了低代码平台在前端开发中的应用,介绍了其模型驱动、组件化和自动化部署的原理,强调了提升效率、降低技术门槛、灵活适应变更和保证一致性等优势。建议开发者明确适用场景,选择合适平台,并培养团队低代码技能,同时规划与现有技术栈的融合,实施持续优化治理。低代码平台正改变开发格局,为业务创新和数字化转型提供新途径。
510 0
|
数据采集 算法 搜索推荐
R语言营销数据分析:使用R进行客户分群的实践探索
【9月更文挑战第1天】R语言以其强大的数据处理和统计分析能力,在金融数据分析、营销数据分析等多个领域发挥着重要作用。通过R语言进行客户分群,企业可以更好地理解客户需求,制定精准的营销策略,提升市场竞争力和客户满意度。未来,随着大数据和人工智能技术的不断发展,R语言在营销数据分析中的应用将更加广泛和深入。
|
监控 安全 物联网
智能家居安全漏洞的检测与防护策略
随着物联网技术的飞速发展,智能家居系统已逐渐融入人们的日常生活。然而,随之而来的安全威胁也日益凸显。本文将探讨智能家居系统中存在的安全漏洞,分析其成因,并提出有效的检测和防护措施。通过技术手段和管理策略的双重保障,旨在为智能家居用户打造一个更加安全可靠的生活环境。
|
存储 算法 异构计算
这就是算法:日常生活中的算法应用
这就是算法:日常生活中的算法应用
857 3
|
机器学习/深度学习 人工智能 监控
构建未来:AI在个性化学习路径设计中的应用
【4月更文挑战第29天】 随着人工智能(AI)的飞速发展,教育领域正经历一场由数据驱动的变革。本文聚焦于AI技术在个性化学习路径设计中的应用,探讨其如何通过精准分析学习者的行为和表现来优化教学策略和内容。我们展示了利用机器学习模型来预测学习成果、识别学习障碍以及提供即时反馈的潜力。文章还讨论了实施个性化学习所面临的挑战,包括数据隐私保护和技术整合问题,并提出了相应的解决策略。
|
程序员
深入解析:分布式一致性的终极解决方案——XA协议
本文介绍了分布式系统中的两种一致性协议:2PC(两阶段提交)和3PC(三阶段提交)。2PC分为准备和提交两个阶段,确保所有参与者在提交前达成一致。3PC则在2PC基础上增加了一个CanCommit阶段,提高容错性和可用性,参与者在超时后可自行中断事务。选择协议需依据业务需求和系统特点,高一致性要求可选3PC,注重性能则选2PC。
204 0