自动指针(通用12篇)
自动指针 篇1
0 引 言
指针式仪表结构简单、使用方便、价格便宜,目前仍大量应用于工农业生产和科学试验中。根据国家规定,每年都要对这些仪表进行检定。目前,指针式仪表的自动检定一般采用光电法和图像处理法两种。光电法是根据指针通过被检测表盘的受检点时该点反射光强度的变化,利用光电效应来产生触发信号,经比较计算得出仪表的指示精度。该法实时性好,但由于反射光强度受表盘的反射系数的影响较大,故稳定性不好。图像处理方法是利用数字图像处理技术,对视觉信号进行处理,完成图像采集、图像转换、偏差检测、结果显示等操作,实现检定过程的自动化。该方法可保证检测速度和精度并且检测方便。本文基于图像处理技术,根据指针式仪表的表盘特征,提出了一种适合实际应用的识别方法。
1 检定系统总体结构
指针式仪表的自动检定装置可以采用不同的图像采集系统进行图像处理和识别。本文研究一种基于机器视觉的指针式仪表自动检定技术,采用数字图像传感器OV7620将指针式仪表表盘图像采集到数字信号处理器DSP中,以DSP作为检定系统的核心,通过图像处理方法分别获取各个基准信号下仪表指针的位置,从而反映仪表的示值读数,最后获取仪表的误差。系统的总体框图如图1所示。图1中:
(1) 被测仪表固定部分。使装置获得清晰稳定的表盘图像来源。
(2) 图像采集部分。采集指针式仪表图像,包括镜头和图像传感器两部分。
(3) 微处理器部分。该指针式仪表识别系统的处理单元以DSP为中心,DSP采用I2C总线方法对图像传感器进行初始化,通过I/O口获得图像数据,对图像数据进行处理,识别指针式仪表的读数。同时DSP发出数字信号,该信号经D/A转换装置转换成对应的识别量,用来作为待测表的标准源[1]。
2 指针式仪表的自动识别系统
指针式仪表的图像识别系统是自动检定装置的核心, 其工作流程如图2所示。
根据指针式仪表的表盘特征,通过坐标平移、坐标翻转和坐标系转换,将指针式仪表的表盘图像从直角坐标系中转换的极坐标系中,根据直角系中圆心为原点半径为R的圆,变换到极坐标系中为r=R的一条直线,如图3所示。到实现圆形、扇形表盘图像转化为矩形表盘图像,利用矩形表盘图像进行由上至下的直线扫描获得刻度线及指针的位置信息,通过位置信息的比较确定仪表读数[2]。
2.1 图像预处理
一般情况下,通过各类图像采集系统获取的图像与希望得到的图像总存在较大的偏差,图像无法直接分析和判别,必须用一定的方法对图像的质量加以提高。这一类改善图像质量的方法和技术统称为图像的预处理。图像预处理是将图像中用户感兴趣的特征有意识地突出,而衰减不需要的特征。预处理的主要目的是提高图像的可读性和便于计算机对图像分析、处理、识别和理解。
由于仪表表盘是属于“明背景,暗目标”的灰度图,指针信息集中在低灰度区,指针周围附近背景处于中间灰度区。因此,要将指针信息加强,从背景中分离出来,必须扩展低灰度区的信息,分段线性灰度变换很容易实现这一目的。同时,因为现场表盘图片质量不一致,在采用分段线性灰度变换时,各图像分段线性区间是十分离散、无法统一处理的,要计算机自动确定一个适当的分段区间有一定困难,为此本文提出基于灰度期望的分段线性变换来实现指针的对比度增强,突出目标区[3]。因此,构成如下基于灰度期望的分段线性变换算法来自适应地增强不同质量的图像:
(1) 计算图像的灰度期望值α,判别图像是否需要变换灰度期望是表盘图像这一灰度图的灰度统计平均值,用α表示灰度期望,则灰度期望由下式计算:
式中:N为图像中灰度值的总个数;L1,L2,…,LN为图像中灰度可取值;P(Li)为图像中灰度Li出现的次数频率;h(Lm)为图像中灰度Lm出现的次数[4]。
灰度期望描述了图像明暗的灰度平均水平,灰度期望值越小,图像越暗;反之,越亮。
(2)基于灰度期望确定变换区间。设从图像中取一像素点,灰度值为Lj,则该点周围背景的平均灰度应依随其统计规律趋近于灰度期望α。
对于指针目标来说,由于指针为低灰度,当灰度期望值小时,指针与背景的对比度小。当期望小到接近指针灰度时,指针被背景完全湮没,因此,根据灰度期望的大小,可以将图像f(x,y)分成3种状态:湮没、增强、显著。若用-1,0,1分别表示这三种状态,则仪表图像的状态可记为:
设定两个参数:湮没阈值为B和显著阈值W。则有:
当S[f(x,y)]=-1时,指针与背景不可分离,不能进行处理和识别,为废图像,应舍去;当S[f(x,y)]=1时,指针与背景对比度显著,无需作增强处理;当S[f(x,y)]=0时,需对图像做对比度增强处理,进行灰度变化。
(3) 利用灰度期望构成线性变换函数,对灰度区间[0,α]进行线性变换。对指针式仪表图像做灰度变换,其目的是为了增强指针与背景的对比度,而灰度期望这一描述指针式仪表图像的平均灰度的参数,将其作为变换区间的界线是合适的。即选定低灰度区间[0,α]和高灰度区间[α,1]作为灰度变换区间,将[0,α]增强扩张到[0,1],而将[α,1]的灰度压缩为1。这样低灰度信息即指针信息得到加强。即数学表达式为[5]:
式中以灰度期望α的倒数作为线性变换的斜率,图像越暗,灰度期望α越小,变换后对比度也就越大[6]。
2.2 图像分割的理论和算法
图像分割是图像处理与机器视觉的基本问题之一,其要点是:把图像划分成若干互不交迭区域的集合,这些区域要么对当前的任务有意义,要么有助于说明它们与实际物体或物体的某些部分之间的对应关系。目前,常用的图像分割方法有Hough变换法和灰度图像的二值化法两种。
图像分割没有一个万能的方法,必须具体问题具体分析。指针式仪表表盘图像呈扇形,指针和刻度线都为直线,因此,在分割图像提取指针和刻度线时,可以使用Hough变换法提取指针和刻度线。但由于精密的仪表刻度线较多,使用Hough变换的方法计算量大,占用内存多,不适用硬件装置。根据仪表表盘图像中仪表指针与刻度线的共性为灰度值较小,因此使用二值化的方法比较合适。
由于该系统中的判断目标是指针和刻度线。在实际判读中,随着被测表的环境亮度、表盘背景、表壳材质和表壳曲率的不同,将会导致图像亮度发生变化,若选用固定的阈值进行二值化处理,不能适应这种变化,因此选用动态阈值法。同时,最大类间方差法利用图像的灰度直方图,以目标和背景的方差最大来动态地确定图像分割阈值,具有很强的适应性,因此,采用最大类间方差法来实现二值化处理[7]。
最大类间方差法依据的原理是:对图像I,记T为前景与背景的分割预值,前景点个数占图像比例为ω0,平均灰度为u0;背景点个数占图像比例为ω1,平均灰度为u1。图像总的平均灰度为:uT=ω0×u0+ω1×u1。从最小值到最大值遍历T,当T使得方差σ2=ω0×(u0-uT)2+ω1×(u1-uT)2最大时,T即为分割的最佳阈值,并以此对图像进行二值化处理。方差是灰度分布的均匀性的一种度量,方差值越大,说明构成图像的两部差别越大,当部分前景错分为背景或部分背景错分为前景都会导致两部分差别变小,因此使方差最大的分割意味着错分概率最小。这样即使表壳亮度发生变化也不会影响判读准确度,大大提高了系统的适用性,降低了系统对使用环境的要求[8]。
用以上最大类间方差法进行了实际应用实验,得到的二值化前、后的图像分别见图4和图5。结果表明,该算法可以实现有效分割。
2.3 表盘图像归一化的算法
指针式仪表表盘图像一般呈圆形或扇形。在实际应用中扫描圆心的横纵坐标会同时出现误差,使圆扫描无法与全部刻度线同时相交而获得准确的刻度线信息。通过坐标平移、坐标翻转和坐标系转换,实现圆形、扇形表盘图像转化为矩形表盘图像,就可以有效地解决偏心误差所带来的问题,然后利用矩形表盘图像进行直线扫描获得刻度线及指针的位置信息。因此表盘图像归一化是直线扫描识别仪表读数的一个重要环节。
从图像传感器OV7620所获得的表盘图像数据是以矩阵的形式存在,矩阵对应到直角坐标系中的形式为I(x,y),因此要将图像从直角坐标系变换到极坐标系中。根据表盘图像特征,先将表盘图像I(x,y)进行坐标平移,指针的回转中心为(X,Y),使指针的回转中心为坐标原点,平移后的图像为I′(x-X,y-Y),将平移后的图像进行翻转得到图像I″(-(x-X),y-Y),再经坐标系变换,变换到极坐标系中的图像I′″(θ,ρ)[9]。
2.4 直线扫描法
将圆形或扇形表盘图像通过坐标平移、坐标翻转和坐标系转换,实现圆形、扇形表盘图像归一化为矩形表盘图像,利用矩形表盘图像进行由上至下的直线扫描获得刻度线及指针的位置信息,通过位置信息的比较确定仪表读数。该方法可以有效的解决圆扫描中扫描圆心偏心所带来的稳定性低的问题。
直角坐标系中表盘图像两侧的刻度线像素点的y值较大,而中间部分刻度线的像素点的y值较小,根据(y-Y)2-(y-Y+ΔY)2=ΔY(2y-Y),其中ΔY为扫描圆心Y的误差,可以看出,在同一误差下,y值越大对ρ的影响越大,变换后的表盘图像会出现上凸、下凹的情况,而当圆心的横坐标X出现误差时,直角坐标系中表盘图像的刻度线像素点x值是逐渐增大的,X的误差对ρ的影响是由大变小的,因此变换后的表盘图像会出现倾斜的情况。由于扫描圆心的误差对θ的影响很小,刻度线总是垂直于横坐标轴,因此对变换后的表盘图像进行由上至下的列直线扫描可以获得表盘准确的刻度线信息。
2.5 实验结果
实验选用的测量仪表型号为85C1电压表,精度等级为2.5,量程为0~5 V。选用的传感器型号为OV7620,分辨率为640×480,被测电压表的最小刻度值对应的像素数为640/50=12.8像素/0.1 V。在指针式仪表的实际应用中,读数时需要对仪表最小值的下一位进行估读,即640/500=1.28像素/0.01 V大于1像素,估读位有效,使用该分辨率的传感器可以进行准确识别该精度仪表[10]。人工判读和采用上述方法自动识别的实验结果见表1。
3 结 语
本文针对模拟指针式仪表的检定问题,提出了基于图像处理的技术方案,对图像处理技术应用于指针式仪表示值自动判读的原理和实现算法进行了研究,提出了一种新的识别方法。该方法的检定精度高,准确性好,为后续更高难度的指针式仪表读数工作奠定了基础。根据实验证明该算法简单、稳定且识别误差小,可改善仪表检定系统的庞大和复杂应用,具有一定的应用意义。
摘要:将机器视觉技术应用到指针式仪表的检定系统中,可以改变传统的靠人工读数进行检定的方法,提高了指针式仪表检定的效率与精度。指针式仪表的识别与检定,关键就是通过指针示值的动态识别,来实现仪器仪表的读数和自动检定。针对指针式仪表示值的自动判读,提出了一种新的图像识别方法。该方法适合实际应用,检定精度高,准确性好。经实验数据证明,该识别方法简单、稳定且识别误差小。
关键词:指针式仪表,图像传感器,图像识别,坐标变换,直线扫描
自动指针 篇2
先看下面的代码,注意看代码中的注解!
#include
#include
usingnamespacestd;
voidprint_char(char* array[],intlen);//函数原形声明
voidmain(void)
{
//-----------------------------段1-----------------------------------------
char*a[]={“abc”,“cde”,“fgh”};//字符指针数组
char* *b=a;//定义一个指向指针的指针,并赋予指针数组首地址所指向的第一个字符串的地址也就是abc�字符串的首地址
cout<<*b<<“|”<<*(b+1)<<“|”<<*(b+2)
//-----------------------------段2-----------------------------------------
char* test[]={“abc”,“cde”,“fgh”};//注意这里是引号,表示是字符串,以后的地址每加1就是加4位(在32位系统上)
intnum=sizeof(test)/sizeof(char*);//计算字符串个数
print_char(test,num);
cin.get;
//-------------------------------------------------------------------------
}
voidprint_char(char* array[],intlen)//当调用的时候传递进来的不是数组,而是字符指针他每加1也就是加上sizeof(char*)的长度
{
for(inti=0;i{
cout<<*array++<}
}
下面我们来仔细说明一下字符指针数组和指向指针的指针,段1中的程序是下面的样子:
char*a[]={“abc”,“cde”,“fgh”};
char* *b=a;
cout<<*b<<“|”<<*(b+1)<<“|”<<*(b+2)<
char *a[]定义了一个指针数组,注意不是char[], char[]是不能同时初始化为三个字符的,定义以后的a[]其实内部有三个内存位置,分别存储了abc�,cde�,fgh�,三个字符串的起始地址,而这三个位置的内存地址却不是这三个字符串的起始地址,在这个例子中a[]是存储在栈空间内的,而三个字符串却是存储在静态内存空间内的const区域中的,接下去我们看到了char* *b=a;这里是定义了一个指向指针的指针,如果你写成char *b=a;那么是错误的,因为编译器会返回一个无法将char* *[3]转换给char *的错误,b=a的赋值,实际上是把a的首地址赋给了b,由于b是一个指向指针的指针,程序的输出cout<<*b<<“|”<<*(b+1)<<“|”<<*(b+2)<结果是>
abc
cde
fgh
可以看出每一次内存地址的+1操作事实上是一次加sizeof(char*)的操作,我们在32位的系统中sizeof(char*)的长度是4,所以每加1也就是+4,实际上是*a[]内部三个位置的+1,所以*(b+1)的结果自然就是cde了,我们这时候可能会问,为什么输出是cde而不是c一个呢?答案是这样的,在c++中,输出字符指针就是输出字符串,程序会自动在遇到�后停止.
我们最后分析一下段2中的代码,段2中我们调用了print_array()这个函数,这个函数中形式参数是char *array[]和代码中的char *test[]一样,同为字符指针,当你把参数传递过来的时候,事实上不是把数组内容传递过来,test的首地址传递了进来,由于array是指针,所以在内存中它在栈区,具有变量一样的性质,可以为左值,所以我们输出写成了,cout<<*array++<
到这里这两个非常重要的知识点我们都说完了,说归说,要想透彻理解希望读者多动手,多观察,熟能生巧!
420){this.width=420}“ alt=”“ />
内存结构示意图!
//程序作者:管宁
//站点:www.cndev-lab.com
指针的速度 篇3
每一秒的蹦跳显示着极限科技的无穷魅力。
手表,这一简单而寻常的物品
却将速度、科技、时尚等令人心跳血压加快升高的因素融为一体。
2007年精工(SEIKO)sportura系列产品设计的重点在工程的细部处理上。每个表冠,刻度和各个小表盘的设计灵感都来自于高性能汽车运动的特点,所有的表壳和表带都更具流线形,更能够完美地贴合手腕。2007年精工(SEIKO)sportura的空气动力学设计更符合人类工程学原理。
精工本田F1车队装备的1/00秒
飞返显示式计时码表
本田F1车队开始了他们的新赛季,精工(SEIKO)两款新的sportura也加入了他们的车队。1/100秒指针显示计时码表穿上了本田的赛车服,新型的橡胶材质表带,其光滑设计模仿了车队在雨天使用的车胎纹路。
精工Kinetic Chronograph
(人动电能计时码表)极限款
然而2007年本田系列中最耀眼的无疑还是Kinetic chronograph(人动电能计时码表)极限款。它是5个独立表盘显示的构造,9T82计时机芯非常独特。每种计时功能都是通过各个小表盘单独显示,因此时间和计时都可以在高速下轻松即时读取。表壳为钢质,黑色离子表面,表带为钢和碳纤维材料制造。
在蓝宝石水晶表镜下,每一个小表盘都有完全独立的水晶小表镜(此水晶用作放大小表盘的读数,使用即使在高速下仍能清晰可见),该设计灵感来自世界先进赛车的车头灯透过蓝宝石表背,机芯的摆陀清晰可见,刻以和高性能运动汽车制动盘形状和外观很相似的标记。
这款计时码表是SEIKO(精工)的全球形象大使Jenson Button(简森·巴顿)的选择全球限量发行750只,每只都配证书,由精工SEIKO公司总裁和本田F1车队总裁签名。
豪雅SLR Mercedes-Benz
该款TAG Heuer SLR for Mercedes-Benz限量发行3500只既为庆祝Mercedes-Benz成为全世界动力最为强劲,最尊贵不凡的跑车之一,更为纪念豪雅表与Mercedes-Benz之间的成功携手。这款全新计时码表保留了上款SLR“梦之车”,中最为独特的设计元素——著名的SLR722.650hp发动机,最高时速可达337km/h,在黑色喷漆碳纤维前扰流板和钯灰车轮轮圈的灵感激发下,码表采用黑色碳化钛准距仪表圈,同时红色指针又与红色SLR标志和红色喷漆制动卡钳相得益彰。
SLR创新传统继续体现在其倾斜45°的按钮上(灵感来自SLR跑车的翘板变速开关),充分考虑人体工学,即便在驾驶中也可轻松操纵码表功能。内部双向旋转式表圈,由9点钟位置的第二枚表冠上紧发条,使驾驶者可以测量比赛时限。
豪雅全新橙色F1计时码表
豪雅表的发展历程与一级方程式赛式密不可分。2007年,豪雅表推出了全新橙色F1(Formula1)计时码表。ETAG10.711石英机芯,专业的计时码表功能,3个计时圈。41mm的表盘直径,手工镶嵌时间刻度,精确到1/10秒的计时秒针,并附带日期,充分满足您对精密计算时间的要求。绚丽张扬的橙色表面,雾面处理的不锈钢表壳,立体的时针与分针特别的秒针,彩色的豪雅表品牌标志,打造出更加时尚前卫的造型。
这款F1计时码表还具备了豪雅表一贯的专业特性:“易旋”旋入式表冠,凸起绒面数字刻划的碳化钛膜单向旋转式表圈,抗磨钛金属碳化不锈钢护桥,环型绒纹饰纹的后底盖,200米防水深度,夜光时标和指针,防划蓝宝石水晶镜面和潜水用延长带节的双安全带扣。精湛的工艺,更强化了F1计时码表的现代感和实用性,使之成为计时码表设计和性能领域的佼佼者。
天梭驰100系列
女士运动计时码表
天梭娉驰100女士精钻计时码表,独具跃动的计时功能和清朗的表壳设计,特别选用60颗共重0.2760克拉的顶级威塞尔顿钻石,每颗均达到VS/SI净度等级,配合32/24全切割,令腕间风采越发流溢,在欧洲被誉为世上最性感的运动腕表。
“性感”源自于它亮彩的贝母表盘,以及熠熠生辉的顶级钻石。其中最令人垂涎的是红色表盘款式,无论配合红色皮带,亦或是精钢表链,都显得娇艳欲滴,充满诱人的魅力。十二边形设计经典而富有力量感,防水能力达100米,螺旋表冠配有表冠保护装置,令腕表更能承受各种运动的撞击;同时,配备有精确到1/10秒计时码表功能:外圈装有速度标尺,可以测量最快400公里的时速。位于2点、10点位置的计时盘,采用银色盘面,简洁、清晰,让人想到运动赛车上的仪表盘,非常富有动感,使女人散发着完美的魅惑气质。
天梭MotoGP自动
多功能限量版
自动指针 篇4
关键词:自动校时,机械传动,齿轮啮合
一、存在的问题
1.对时间统一性和精确性要求比较高的控制系统, 例如动车系统, 需要定点统一校时, 而一般的人工校时无法达到此系统的要求。
2. 普通钟表由于机芯的机械设计的局限性和使用材料的优劣程度导致无法达到高精确走时。
二、设计思路
为了解决上述存在的问题, 我们设计了一个无线电自动校时指针式电子表, 此指针式电子表由五部分组成:无线电接收装置, 驱动装置, 钟表机芯, 机械传动装置, LED显示屏。
无线电自动校时指针式电子表的工作原理是:利用无线电接收装置接收统一的标准时间的无线电波, 将无线电信号进行解调, 同时把标准时间显示到显示屏上, 然后将该信号转化为单片机能够识别的数字信号, 并由单片机控制驱动装置, 利用驱动装置控制机械传动装置, 使传动装置中的齿轮与钟表机芯中控制分针转动的齿轮啮合, 并根据数字信号和此时钟表显示时间的差值计算出分针所需转动的角度进而推算出机械传动装置的齿轮转动的圈数并带动钟表机芯中的啮合齿轮, 达到自动校时、消除钟表系统积累误差的目的, 实现了时间的精准性。
无线电自动校时指针式电子表使用了STC51单片机和无线模块、电机等设计硬件和软件以及机械部分来实现自动校时功能。以下我们详细介绍无线电自动校时指针式电子表的机械传动装置。
三、设计方案
上图是一个普通指针式电子表的内部结构, 我们对其进行了改装, 通过调节上图中的蓝色示齿轮来校对分针和时针的示数。我们的机械设计目的是制作一个带齿轮的机械传动装置, 利用这个齿轮与蓝色齿轮进行啮合传动, 并对这个机械装置进行自动化控制。由于此无线电自动校时指针式电子表在非校对时段是进行正常工作的, 不需要校时, 因此在这段时间里机械传动装置中的齿轮与蓝色齿轮处于非啮合状态。
1. 自动调节分针部分的设计
把钟表机芯手动旋钮部分拆除, 将电机的轴加长, 并在尾端铸造一个齿轮 (该齿轮的模数、压力角与钟表机芯手动旋钮部分底部的齿轮和钟表调节分针的齿轮相等) , 然后把齿轮安装在与钟表机芯中的齿轮相啮合的位置, 再将电机跟钟表机芯共同放入所设计的固定架中, 以达到电机齿轮与钟表调节分针的齿轮正确啮合的目的, 从而驱动电机转动来带动分针转动, 完成分针自动校时的功能。
2. 无线电自动校时指针式电子表正常走时状态和调节分针状态的相互转换设计
首先, 将装入钟表机芯的固定架底部截取成抽屉式的形状, 并在底部槽和装钟表机芯的抽屉式模块之间空出2~3mm的空隙, 使得抽屉式模块能够左右移动。接着在抽屉式模块的侧面打孔攻螺纹, 然后在所设计的固定架的侧部固定一个直流电机, 在直流电机轴上固定一定长度的带有外螺纹的轴, 将抽屉式模块侧边的孔与电机轴进行螺纹联接, 随后驱动电机, 控制电机分别进行正转或反转, 使的抽屉式模块前进或后退。抽屉式模块到达靠近电机侧面的位置即电机齿轮与控制分针转动齿轮处于非啮合状态时, 是钟表正常走时的状态, 而贴近另一个侧面的位置即机齿轮与控制分针转动齿轮处于啮合状态时, 是调节分针的状态。
四、性能分析
无线电自动校时指针式电子表具有以下优点:
(1) 无线电自动校时指针式电子表接收标准时间的无线电波, 来达到钟表自动校时的功能。通过定时的自动校时来消除钟表系统的积累误差, 实现时间的精准性。
(2) 无线电自动校时指针式电子表能够使在特定区域内安装有此类装置的钟表获得一个统一的时间, 以此达到例如动车系统的时间一致性。
参考文献
[1]邱宣怀, 郭可谦, 吴宗泽, 汤绍模, 郭芝俊, 黄纯颖, 杨景蕙.机械设计[M].高等教育出版社, 2011
自动指针 篇5
总结:
单链表是一种使用指针来存储值的数据结构。链表中的每个节点包含一个字段,用于指向链表的下一个节点。
有一个独立的根指针指向链表的第1个节点。单链表只能从一个方向遍历。
如何insert单链表:1、新节点的link字段必须设置为指向它的后面节点。2、前一个节点的link字段必须指向这个新节点。
为了防止可能会插入链表的起始位置这种情况,在C中,可以保存一个指向必须进行修改的link字段的指针,而不是保存一个指向前一个节点的指针。
双链表中的每个节点包含两个link字段:其中一个指向链表的下一个node,另一个指向前一个node。
双链表有两个根指针,一个指向第一个node,另一个指向最后一个node。因此遍历的过程中可以从任何一端开始,而且在遍历过程中够可以改变方向。
为了把一个新节点插入到双链表中,我们必须修改4个指针。新节点的前向和后向link字段必需被设置,前一个节点的fwd和后一个节点的bwd也要修改,指向新节点。
警告:
1、落到链表尾部的后面。
2、使用指针时应该格外小心,因为C并没有对他们的使用提供安全网。
3、从if语句中提炼语句可能会改变测试结果。
编程提示:
1、消除特殊情况使代码更易于维护。
2、不要轻易的进行提炼语句,这样会使你的语句更难维护。
编程实例:(本章就不再弄习题了,关于数据结构这块会有大量代码进行训练)
1、提炼后的单链表插入操作
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include “stdlib.h”
typedef struct NODE
{
struct NODE *link;
intvalue;
} Node;
int sll_int(register Node **linkp, int new_value)
{
register Node *current;//指向当前节点
register Node *new_node;//指向插入节点
/*
** 寻找正确插入位置,按顺序访问链表,直到有个值大于或等于新值
*/
while ((current = current->link) != NULL && current->value < new_value)
{
linkp = ¤t->link; //移动linkp指向下一个Node的link
}
/* 分配新的内存,并存到新节点去 */
new_node = (NODE *) malloc (sizeof(NODE));
if (new_node == NULL)
{
return 0;
}
new_node->value = new_value;
/* 插入新节点 */
new_node->link = current;
*linkp = new_node;
return 1;
}
2、双链表插入操作
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include “stdlib.h”
typedef struct NODE
{
struct NODE *fwd;
struct NODE *bwd;
int value;
}Node;
int dll_insert(Node *rootp, int value)
{
/* 把一个值插入到一个双向链表中,rootp是一个指向根节点的指针
value 是插入的新值
返回值:如果已经存在链表中,返回0
如果内存不足导致无法插入,返回-1,成功返回1;
*/
Node *this_node;
Node *next_node;
Node *new_node;
for (this_node = rootp; next_node != NULL; this_node = next_node )
{
if (next_node->value == value)
return 0;
if (next_node->value < value)
break;
next_node = next_node->fwd;
}
/* 为新节点申请内存空间*/
new_node = (Node *) malloc (sizeof(Node));
if (new_node == NULL)
return -1;
new_node->value = value;
/*
插入节点
if 不在链表尾部 then 不在链表起始位置 or 位于链表起始位置
else 在链表尾部 then 不在链表起始位置 or 位于链表起始位置(空链表)
*/
if (next_node->fwd != NULL)
{
/*不在链表尾部*/
if (this_node != rootp)
{
/* 不在链表的头部 */
this_node->fwd = new_node;
next_node->bwd = new_node;
new_node->bwd = this_node;
new_node->fwd = next_node;
}
else
{
/* 在链表的头部*/
rootp->fwd = new_node;
next_node->bwd = new_node;
new_node->bwd = rootp;
new_node->fwd = next_node;
}
}
else
{
/*在链表尾部*/
if (this_node->bwd != rootp)
{
/* 不在链表的头部 */
new_node->fwd = NULL;
new_node->bwd = this_node;
this_node->fwd = new_node;
rootp->bwd = new_node;
}
else
{
/* 在链表的头部*/
new_node->fwd = NULL;
new_node->bwd = NULL;
rootp->bwd = new_node;
rootp->fwd = new_node;
}
}
移动设备的隐形指针 篇6
今天,与手机交流的方法是在其键盘或屏幕上指点江山。而研究人员们正在开发更加自由无界的移动设备操作方式。
德国波茨坦哈索普列特纳学院的计算机科学教授帕特里克·伯蒂奇(Patrick Baudisch)和他的研究生肖恩·古斯塔夫森(Sean Gustafson)正在开发一个手机的原型界面,无需使用触摸屏、键盘或者其它任何物理输入设备。一枚小型摄像机和附在衣服上的微处理器就能捕捉并分析人们的手势,将每个手势的轮廓送到电脑显示器上。
该设想希望让使用者能经由一个“虚拟界面”来追踪他们手指在空中画出的形状,从而增强手机对话。伯蒂奇和古斯塔夫森制造了一个带有钻头大小摄像头的原型设备,但他们预测在几年内零件就可以缩小,以便制造更小的系统。
通过手势与电脑交流的想法早已不新鲜。索尼公司已经在销售其称作EyeToy的摄像头及软件,能在PlayStation游戏主机上捕捉手势。微软则为Xbox 360游戏主机开发了一套更为复杂的手势感应系统,叫做“诞生计划”(Project Nail)。麻省理工学院的教授帕蒂·梅斯(Pattie Maes)和她的学生普拉纳·米斯特里(Pranav Mistry)开发了一个基于手势的研究项目,叫做“第六感”(SixthSense),采用可穿戴的摄像头来记录个人手势,并用一个小型投影仪在任何表面创建一个临时的显示界面。
伯蒂奇和古斯塔夫森表示,他们的系统比“第六感”简单,所需零件更少,因此可以更廉价。用左手或者右手做出一个L形状,使用者就能“开启”这个界面。这个动作创建了一个二维空间表面,为即将出现的手指轨迹划定边界。伯蒂奇说使用者能利用这块区域来阐述空间情境,例如怎样从一个地方到达另一个地方。“用户在空中开始比划,”他说,“无需设置工作,也不用掏出设备或者手写笔。”研究人员还发现,由于视觉记忆作用,用户甚至可以回到之前的虚拟草稿添加内容或注释。
关于设置和用户研究详情的报告将在2010年10月纽约举行的用户界面软件与技术(User Interface Software and Technology)研讨会上发表。
一款名为“表面”的实验性触摸屏桌面的开发者,微软研究员安迪·威尔逊(Andy Wilson)说,这项成果是未来产品的标志。“我认为当设备被缩减为零一甚至连显示器都没有——的时候,这种技术作为终极想法是非常有趣的。”他说。
指针的学习与应用 篇7
关键词:内存,指针,单星指针,双星指针,动态数组
1 问题提出
C语言程序中指针的广泛使用严重地影响了数据流分析的精确程度[1]。文献[2]使用安全程序的设计和证明框架,为类C语言一个子集设计指针逻辑系统,用推理规则来表达每一种语句引起指针信息的变化情况,以支持程序的安全性验证。文献[4]提出了在C++编程环境中把对象内的指针区别对待的思想。文献[3,5,6]认为教师可以通过抓住数据在内存中的存储和变化这条教学主线来进行指针教学,并给出了一些尝试。
为了区别整数与存储单元的地址,C语言引入了指针数据类型,定义指针变量以便处理存储单元的地址。因此指针变量其实是比较简单的变量,专门用来存放存储单元的地址,通常是变量的地址。当一个指针变量存储了一个变量的地址,就可以通过指针变量来访问该变量的存储单元。
例1:指针变量的定义和用法
2 变量的存储结构
计算机的内存由若干字节组成,每个字节由8位构成,每个字节都有唯一的编号,以便CPU能读写任何一个字节的内容。字节的编号也称地址。
3 指针概念
实体的地址也称指针。指针不能用一般的变量存储,需要用一种特殊的变量来存储,这种特殊的变量就是指针变量。
定义变量必须使用数据类型,定义指针变量使用的就是指针数据类型。在C语言中,数据类型结合一个或多个*来表示指针数据类型。用指针数据类型定义变量时,变量名应放在*后面。
例2:指针变量的定义和用法
下面给出指针的一个重要概念和3个运算符:
(1)在C语言中,如果一个指针变量p存储了另一个变量x的地址,则称p指向x。例2的指向关系如图1所示。
(2)&是取地址运算符,用来取出变量的地址。
(3)*是间接引用运算符,作用于一个指针变量,访问该指针所指向的内存数据。只有指针变量指向了某个变量,才能使用该运算符,否则将引起异常。当指针变量p指向一个变量时,可以用*p代表该变量。
(4)[]是下标运算符,当指针变量p指向一个变量时,可以用p[0]代表该变量。这是把存储空间当做只有一个下标变量的数组来使用。
4 指针类型
基本数据类型、非基本数据类型和函数,都有相应的指针类型,如,int*,int**,char*,char**,int(*)[4],int(*)()等。定义的指针变量用于指向对应的数据类型。简单地说,指针变量只能存储同类型实体的地址,通过指针变量访问实体。
用一般数据类型加*定义的指针变量称为单星指针变量,加**定义的指针变量称为双星指针的指针。指针变量,加***定义的指针变量称为三星指针变量,依次类推。双星指针变量也称为指向
例3:双星指针变量
C语言规定,指针变量的地址,要用更高级别的指针变量来存储。
fpp是双星指针变量,可存储单星指针变量的地址。fp指向a,fpp指向fp。
5 一维数组与指针
如果定义了字符数组c,整型数组a。char c[5];int a[3]。
数组在内存中占据连续的存储单元,每个下标变量占有相同个数的字节。数组名是数组的起始地址,是常量,c[0]的地址可用c表示,为1000,c[1]的地址可用c+1表示。一般地,c[i]的地址可用c+i表示。
a[0]的地址也是a,为2000,a[1]的地址可用a+1表示,a[i]的地址可用a+i表示。
从这里可以看到,指针加1有特殊的作用,是指向下一个下标变量。不能简单地理解为地址加1,地址增加的数量由数据类型决定。
指针加1指向下一个下标变量,也就是下一个存储单元。
访问下标变量有两程方法:
a[i]——下标法
*(a+i)——地址法
另外,还可以定义一个指针变量,指向一个数组元素。如定义一个指针变量p,使它的值等于某一元素的地址,这样*p就是该元素。也可用p[0]代表该元素。
例4:分别用下标法、地址法访问数组元素。
一维数组名是单星指针,而二维数组名不是双星指针。
6 一维动态数组
需要使用一维动态数组来解决某些实际问题。实现一维动态数组需要定义单星指针变量,用来存储所分配空间的首单元的地址。
例5:使用一维动态数组。
7 二维动态数组
在软件开发中经常需要实现二维动态数组,以解决另外一些实际问题。实现二维动态数组需要定义双星指针变量。
例6:一个班有n人,m门成绩,编写程序进行简单成绩处理。
分析:第5行程序pp=(float**)malloc(n*sizeof(float*))先建立一维float指针数组,有n个下标变量,每个下标变量都是指针变量;第8、9、10及11行程序依次建立了n个一维float数组,首单元的地址分别送给了指针数组的各下标变量。每个一维数组有m+1个下标变量,前m个分别用来存一人的m门成绩,最后一个变量用来存该人的平均分。
8 数的存储结构
这里要把内存中的结构读出来,以帮助理解数的存储结构。
例7:显示短整数10和-10在内存中的存储内容。
说明:
(1)用unsigned char指针读取内存字节中的内容,系统不会把首位当符号位进行处理,因而能准确地显示该字节中的每一位内容。
(2)输出结果第3,4行,输出了两个字节的地址和内容(十六进制0和a),由此可知内存中的存储形式如下:
地址:12450531245052
字节内容:0000 0000 0000 1010
(3)输出结果第7,8行,输出了两个字节的地址和内容(十六进制ff和f6),由此可知内存中的存储形式如下:
地址:12450411245040
字节内容:1111 111111110110
这正是-10的补码。
(4)从例7可以看到,整数字节的逻辑顺序与物理顺序一致,属于little endian。
9 结语
文中提出了新的指针概念单星指针、双星指针和多星指针从变量在内存中的结构来引入和介绍指针的概念,通俗易懂,并给出了指针深层应用的例子。
参考文献
[1]刘强.考虑指针别名的过程间分析技术研究.中国科学院研究生院(计算技术研究所),1998.
[2]陈意云,华保健,葛琳,王志芳.一种用于指针程序安全性证明的指针逻辑.计算机学报,2008,31(3):272-280.
[3]顾晓燕.C语言中的指针和指针教学.福建电脑,2008,(1):206,166.
[4]江汉大学学报(自然科学版).2008,36(1):41-43.
[5]陈无兴,段昌盛.教学主线的研究与实践[J].恩施职业技术学院学报,2006,(2).
C语言指针探究 篇8
1 C语言能直接访问内存———通过指针机制实现
高级语言编程是与硬件无关的[1]。语言中的许多对象符号就是对硬件的抽象。比如, 变量名称就是对内存单元的抽象, 函数名、数组名也都是对相应内存单元的抽象代表。
我们都知道, 程序中首先要定义变量、数组, 这是为编译程序服务的。
例如:定义变量、数组语句如下:
int a, x[10];
在程序编译时, 编译程序就会分配内存单元给变量及数组, 以便日后数据存入变量和数组中。所以变量名、数组名就是内存单元的代名词, 正像身份证号码就是某人的代号一样。但是, 如果想直接对内存单元读写数据可以吗?高级语言能做到吗?回答是, C语言可以。进一步讲, C语言是通过指针机制实现的。同时, 也可以说, C语言通过指针机制做到编程中可以直接和内存这样的硬件打交道。也就是直接访问内存。接下来就对指针机制访问内存做详尽的说明。
2 指针变量做函数形参———能完成一石三鸟的功能
高级语言编写的程序, 需要翻译成二进制代码程序, 并装入内存单元中, 才能运行。编译程序做翻译工作, 链接程序和装入软件做链接和装入代码到内存单元的工作。翻译程序还有一个非常重要的工作, 就是把程序中所有的名字和内存单元对应起来, 形成一一映射关系。举例说明如下。
程序中定义变量如下:
假设, 程序在编译处理时, 给变量a分配的内存单元是2字节, 从2000H到2001H这2个字节单元;数组x, 编译程序给它分配的内存单元从3 000H~3 013H共40个字节单元;针对函数max, 编译程序给它分配的内存单元从5 000H开始的一系列单元。显然, 内存分配工作是编译程序来做的工作, 与编程者没有关系, 对编程者是透明的。但是, C语言可以和内存直接打交道, 具体如下:
int*p, a;
p=&a;
*p=12;
上述语句定义两个变量:p, a。
p:是指针变量, 用它存储变量a的内存地址。
a:是整型变量, 用它存储一个整数。
上述语句有两个关键的运算符:&, *。
&:是取地址运算符。&a, 代表变量a的内存地址。
*:是取值运算符。*p, 代表p的内容 (&a) 所标识的内存单元, 即a。
这里, 有三个值要区分。分别是:变量地址、变量值、指针变量的值。用下图表达其中的关系。
图1中, 矩形框代表变量的单元, 假设, p变量的内存单元地址是:&p (5 000H) , a变量的内存单元地址是:&a (2 000H) 。p的值是:&a, a的单元地址是:&a, a的值是12, 也是:*p。所以, 取值运算符*, 就是获取地址对应单元的数据。在这里, 运算符&, *是功能互逆的。所以*p就是a。因为, *p等于*&a即a。
使用指针变量p可以间接访问a的数据。因为, a变量的核心信息———内存单元地址, 已经在p内存单元中保存了, 所以访问a, 完全可以通过p变量实现。为什么要这样曲线使用a的信息呢?请看以下例子。
程序代码:
分析以上程序, 程序功能是交换主函数中变量a, b的值。函数swap的形参是指针类型变量, 为什么要这样?因为, 主函数中的变量a, b, 要在swap函数中使用, 直接写成如下形式是不行的。
因为, 第一种情况是, 主函数中变量a, b的作用域仅在主函数中, 不能越权在swap函数中使用它们。第二种情况下, swap函数中使用的变量a, b是自己的形参变量, 也是函数swap内的变量, 并不是主函数中定义的变量a, b。
所以, 以上两种代码都不能完成主函数中变量a, b的交换任务。但是, C语言提供了解决此问题的方法, 那就是指针。通过获取主函数中变量a, b的指针 (地址) , 并把它以实参的形式传递给函数swap, 函数swap就可以得到主函数中a, b变量的信息, 以便于使用它们。这就是用指针的形式间接访问主函数中变量a, b的手段。不能直接访问就采用迂回的、间接的指针方法。下面再举一例说明指针还可以用在什么地方。
程序代码如下:
程序说明:
程序中, 排序的数据希望是主函数中x数组中的100个整数, 但, 排序工作是sort函数来实现。这就面临两个问题。其一, 主函数中的数据要在另一个函数sort中使用和访问, 面临作用域超界问题。同时, 其二, 是100个整数要一一传递给sort函数, 显然, 传递工作量太大, 会消耗许多系统时间, 完成数据的传递工作。但是, C语言提供了解决方法, 还是指针。用指针变量做形参, 传递实参数据时, 把主函数中数组的首地址传过来即可解决上述两个问题。即, 用传首地址代替传100个数值;用传首地址的方法, 就可以间接访问主函数中数组x中的100个数据, 避开作用域限制[2]。显然, 用指针做函数sort的形参, 可以起到一石二鸟的作用。也解决并弥补了C语言用return语句只能返回一个数据的不足。所以在本环境下, 用指针做函数sort的形参, 可以说是一石三鸟。
3 指针和文件操作
通过文件指针操作文件, 大大方便编程者。
下面再举一例, 说明指针在文件操作中的应用。
程序如下:
程序解释如下:
程序功能是打开文件, 读取文件中的一个个字符, 并显示到屏幕上。在这里, 文件指针fp是编程者抽象访问文件内容的关键。从而使程序与硬件 (磁盘) 脱钩, 为编写程序提供了大大的便利。在这里文件指针就是硬盘中文件的抽象, 也是访问文件的武器。所有的文件操作细节交由库函数承担, 库函数借助文件指针抽象操作文件内容, 编程者工作量大大减轻。
4 指针带来程序效率
另外, 指针在链表中的应用, 使得在批量数据中删除、插入操作, 效率大大提高。不会出现顺序存储数据时大量数据移动现象[3]。
5 指针小结
指针就是内存单元的地址编号, 指针变量是用来存储其他对象地址的特殊变量。用指针可以间接访问变量, 可以突破变量作用域的限制, 去访问其他函数中定义的变量。同时也是提高程序效率的方法之一[4]。也是函数处理批量数据的解决手段。真正要用好指针, 解决实际问题, 需要掌握指针理论和多编程实践。
参考文献
[1] (美) Stephen Prata.C Primer Plus[M].第5版.云巅工作室译.北京:人民邮电出版社, 2013.
[2] (美) Ivor Horton.C语言入门经典[M]第4版.杨浩, 译.北京:清华大学出版社, 2010.
[3] (美) Kenneth A.Reek.C和指针POINTERS ON C[M].第2版.徐波, 译.北京:人民邮电出版社, 2008.
怎样正确输出指针类型数据 篇9
这里,作者用了“%d”格式转换输出a和*a这两个指针类型表达式的值。
实际上,这种做法是错误的。但是由于这种错误相对于代码语法错误来说不是那么直截了当而是比较隐晦,所以往往容易被视而不见,甚至被误以为是正确的写法。
1 为什么用“%d”输出指针是错误的
除了使用“%d”这种错误的格式输出指针类型数据,使用“%o”、“%x”(或“%X”)及“%u”等几种错误转换格式的情况也很常见。例如参考文献[4]的第224页:
作者认为这条语句的“作用是以八进制形式输出指针变量p的值”。
由于这几种错误的性质类似,所以这里也一并讨论。
首先,根据参考文献[1-3],“%d”这种格式只用于输出int类型的数据,输出的结果为十进制整数形式的字符序列——“[-]dd…d”,因此,在一定条件下将输出一个负的十进制整数。仅此一点就足以断定用“%d”格式输出指针是错误的。因为指针数据类型并不等同于int数据类型;指针数据类型的值表示地址,然而地址不可能是负值。
既然地址不可能是负值,而“%o”、“%x”(或“%X”)、“%u”这几种格式的输出结果都不是负值,那么用这几种格式输出指针类型的值是否可以呢?同样不可以。
根据参考文献[2-3],“%o”、“%x”(或“%X”)、“%u”这三种格式都只用于输出unsigned类型的数据。unsigned数据类型和指针数据类型是截然不同的数据类型。C语言并没有规定指针类型数据的内部表示应该和unsigned类型一致,甚至没有规定这两种类型数据在机器内部应该如何表示,而且这两种数据的尺寸也未必相同。事实上,C语言自C89开始,就要求编译器应提供“stddef.h”并在其中提供“ptrdiff_t”类型的定义。“ptrdiff_t”类型这种类型是两个指针做减法运算得到的结果的类型,这间接地说明了指针数据类型并不必然等同于整数类型的尺寸。因此使用“%o”、“%x”(或“%X”)及“%u”输出指针毫无依据可言,因而是错误的用法。
参考文献[3](§7.19.6.1,p280)为此特意指出,“If any argument is not the correct type for the corresponding conversion specification,the behavior is undefined.”。这表明使用“%d”、“%o”、“%x”(或“%X”)、“%u”输出指针数据是一种未定义行为(undefined behavior)。未定义行为本质上就是程序的一种错误。因为编译器此时有任意的处理方式,都不违背语言标准。从代码的角度来说,使用“%d”、“%o”、“%x”(或“%X”)、“%u”都是没有明确意义的代码,没有明确意义的代码当然是错误的代码。
许多使用“%d”、“%o”、“%x”(或“%X”)及“%u”这几种格式输出指针的人往往有一个误区,这个误区来自于经验,那就是使用这种格式输出指针并没有出现错误,因而他们认为可以使用这些格式输出指针。
然而,这种想法是根本站不住脚的。仔细推敲一下就不难发现,这种推理的基础是基于使用个别编译器的经验而已。个别编译器当然不代表所有编译器。这个道理就如同在某个编译器上int类型的尺寸是2B,但绝不能说C语言的int数据类型的大小就是2B一样。
因此,在个别编译器上,指针尺寸的大小和表示方法可能确实与某种整数类型相同,但这绝不能说明在所有的编译器上指针的大小和表示方法都和某种整数类型相同。
如果考察的范围广些,不难发现,指针就其一般而言,和整数类型大小不同的例子很多。在这种情况下,参考文献[4]第248页中的程序就会产生错误。例如,在针对DOS操作系统的编译器MSC 6和TC在以大内存模式编译时,这段程序就会得到错误的行为;此外,在不少64位机器环境下的编译器中,以“%d”、“%o”、“%x”(或“%X”)及“%u”这几种格式输出指针类型的值也显然会发生错误。原因就在于,错误地假设了整数类型与指针类型具有相同的表示和尺寸。
2 输出指针正确的转换说明
由于由于在各种不同环境下,指针的尺寸未必和任何整数类型相同,因为实现可能支持多种尺寸的指针。所以无论是K&R的经典名著[1],还是国家标准C90[2],以至于目前最新的国际标准C99[3],都明确指出调用格式化函数(如printf()、fprintf()等)输出指针类型数据的值应该使用转换说明符p,此时,对应的“实参应为指向void的指针。该指针的值将以实现定义的方式转换为一系列可印刷字符”。具体的输出的结果显然和具体实现有关。
虽然格式化输出函数只能输出void*类型的指针,但由于printf()函数的函数原型为:
C语言规定,与“...”部分相对应的指针类型的实参,在调用时都将被按照隐式类型转换的规则一律转换为“void*”类型的指针,因此,%p这种转换输出格式实际上同样适合于输出其他类型指针的值。
由此,不难得出结论,调用printf()函数输出指针类型的值,应该使用%p格式转换声明。所以,参考文献[4]第248页的代码,正确的写法分别应该是:
当然,由于输出的结果是“实现定义的”,所以在不同的实现中的输出结果的形式可能并不相同。
3 结束语
根据前面的分析和讨论,可以得到如下的结论:
指针数据类型并不等同于任何整数类型。
用“%d”、“%o”、“%x”(或“%X”)及“%u”这几种格式输出指针类型的值是错误的未定义行为。
应该用“%p”转换格式输出指针类型数据的值。
本文指出的错误并非是今天才出现的,事实上二十年时间前出版的C语言教材[5]就已经存在这两种错误。作为教材,应该遵循标准,教给学生具有一般性的通用性的知识。然而,近二十年间这个错误竟然没有得到改正,这是非常令人震惊的事情。在此期间,不少以文献[4-5]为参考编写的C语言教材或书籍中同样也存在类似的错误,可见这两个错误的影响之广泛及深远。
为此本文正式指出这个错误并予以更正,希望这个错误不至于再以讹传讹地流传下去。
参考文献
[1]Kernighan B W,Ritchie D M.C程序设计语言[M].北京:清华大学出版社,1998.
[2]国家技术监督局,GB/T15272-94程序设计语言C[S].1994.
[3]International Organization for Standardization,ISO/IEC9899:1999.[S].
[4]谭浩强.C程序设计[M].4版.北京:清华大学出版社,2010.
C语言的指针解析 篇10
1.1 指针
C语言中, 任何数据都会占用内存单元的。计算机内存的每个存储位置都对应唯一的存储地址, 而变量名作为变量数据存储区域所取的名字, 代表的是一个内存编号, 而这个内存编号对于用户来说是未知的。如定义:int a=12。
变量名a代表某个内存单元, 而变量值12则是这块内存单元里面的内容。在整个程序运行过程中, 通过变量名来访问变量, 变量名a所代表的这块内存单元不变, 所改变的只是这块内存单元里面的值。
C语言也支持使用变量存储地址来对变量进行存取操作, 要取得变量的存储地址, 可使用取地址运算符&, 如&a表示变量a的存储地址;而变量的存储地址就是指针。
1.2 指针变量
指针类型就是C语言中用于表示存储地址的数据类型, 而专门用来存放变量存储地址的变量则称为指针变量。
格式:数据类型 *指针变量名。跟普通变量不同的是, 所有指针变量占用的内存单元大小是一样的, 前面的数据类型代表的是指针变量所指向变量的数据类型。如以下代码:
1.3 二级指针变量
在C语言中, 任何值都有所对应的内存地址, 上述介绍的指针变量是指向普通变量的, 也就是普通的指针, 称为一级指针变量。
还有一种指针变量所指向的变量不是普通变量反而还是一个指针变量, 这种变量称为二级指针变量。如:
int a=12;//普通变量a
int *b=&a;//一级指针变量b, 指向普通变量a
int *c=&b;//二级指针变量c, 指向指针变量b
而这时, *c只是代表取c所指向的变量b里面的值:&a, 而a的地址又是未知的。所以, 对于二级指针变量c, 要想取出a的值, 必须需要两个间接访问运算符**, 如**c=** (&b) =*b=* (&a) =a。
2 指针与数组的关系
2.1 指针与一维数组
数组元素在内存中是顺序存放的, 并且数组名代表的是这一块内存单元的首地址, 数组其他元素的地址可以根据每个元素所占用的内存单元依次推知。因此, 一维数组的数组名实际上就是该数组的指针。如以下定义:
int a[10]={1, 2, 3, 4, 5};int *p=a;
此时, p指向数组中的第1个元素 (a[0]) , p+1指向第2个元素 (a[1]) , 依次类推。当然, p最开始也可以指向其他元素, 如指向第4个元素:p=&a[3];
在指针的运算中, 经常会跟自增或自减运算符结合起来使用, 如以下代码:
上述代码就是指针与数组之间关系的一个典型示例, 通过自增或自减运算符不光改变p的指向, 也可以改变p所指向的内存单元里面的内容 (也就是数组元素的内容) 。
2.2 指针与二维数组
由于二维数组可以看成是多个一维数组, 如以下定义:int a[3][4];可以把二维数组a看成3个长度为4的一维数组a[0], a[1], a[2] (假定二维数组按行存储) ;a[0]为第一个一维数组的数组名, 里面有4个元素:a[0][0], a[0][1], a[0][2], a[0][3];a[1]和a[2]依次类推。此时, 虽然二维数组名a和&a[0][0]表示的是同一个地址, 但是指向的却是完全不同的两种存储区域。a指向的是一维数组a[0]中4个元素的存储区域的首地址, &a[0][0]指向第一个元素a[0][0]的地址。此时的a相当于二级指针, a等价于&a[0], 而a[0]又等价于&a[0][0], 所以**a才等价于a[0][0], *a只是a[0], 而a[0]还是地址, 此值对于用户来说是未知的。如以下代码:
2.3 字符串指针
对于一维字符数组指针来说, 如前所述, 比较简单一些, 如以下代码:
但是对于二维字符数组指针, 相对来说复杂一些, 如以下代码:
3 指针作为函数参数
3.1 值传递与地址传递
在C语言中, 函数的参数传递有两种:值传递和地址传递。所谓值传递是指在函数调用时, 给形参分配内存单元, 把实参的值一一传递给对应的形参。但在函数执行中, 只是对形参变量进行处理, 形参值的改变对实参没有影响。而地址传递是指在函数调用时, 实参把数据的存储地址传递给形参, 使其指向同样的存储区域, 这样, 在函数执行中, 对形参的处理实际上就是对实参的处理, 形参值的改变对实参有影响。在C语言中, 地址传递一般通过指针或数组名来作为函数的参数。如以下代码:
在上述代码中, 函数swap1就是值传递, swap2就是地址传递, 调用swap1函数实参值不会做交换, 只有swap 2作交换, 代码的输出结果为:3, 5, 5, 3。
3.2 指针变量作为函数参数
指针变量作为函数形参也经常应用在数组作为实参的函数调用中。如以下代码:
针对数组或指向数组的指针, 各有两种表现形式:数组形式或指针形式。如:int a[10];数组的引用可以有常用的数组形式:a[2], 也可以有指针形式:* (a+2) 。同样的对于指向数组的指针, 如int*p=&a[2], 也有指针形式:* (p+2) 和数组形式p[2], 都等价于a[4]。
4结语
本文在简要介绍指针的概念和指针变量的基础上, 着重介绍了指针与数组的关系, 通过示例说明了指向一维数组的指针以及二维数组的指针, 特别是二维数组名作为二级指针的引用和字符串指针的应用;最后介绍了值传递与地址传递以及指针作为函数参数的应用。
摘要:C语言作为计算机类专业一门重要的基础课程, 是学生学习程序设计的开端, 后续专业课程大多以此作为前提。而在C语言的学习中, 指针又是一个重点以及难点。文章首先介绍了指针的概念以及指针变量的定义, 其次重点介绍了指针与数组之间的关系, 最后介绍了值传递以及地址传递的区别以及指针变量作为函数参数的应用。
关键词:C语言,指针,指针变量,地址传递
参考文献
[1]吉顺如.C语言程序设计教程[M].北京:机械工业出版社, 2011.
[2]李师贤, 译.C++Primer中文版:第4版[M].北京:人民邮电出版社, 2006.
拨正我们的信仰指针 篇11
从起朱楼、宴宾客,到楼塌了、被关了,吴泽衡与王林的最终命运,应交由法律裁决。与此同时,也该追问,两人与江湖术士无异,却拥有大批弟子,且不乏高学历者、知名人士,难道他们真有超人之能?那些在娱乐圈或官场深耕多年之人,何以被王林迷得团团转?
先可确定,智商高不等于情商高,情商高不等于无软肋;有学识不等于有见识,有见识不等于无所不能。日本臭名昭著的奥姆真理教,曾制造震惊世界的东京地铁毒气事件,具体实施的几名信徒全都受过高等教育。该教创始人麻原彰晃之所以能够俘获那么多信徒,一开始也是因为他宣扬自身拥有特异功能,用“道理”取代真理,又用所谓的“神谕”取代道理,引得不少人顶礼膜拜。
但凡搞邪教、行怪力乱神之人,所擅长的,无非一骗二吓三诱惑。对于吴泽衡、王林来说,“特异功能”只是招徕资源的敲门砖,他们更像是高明的心理学大师,看懂了慕名者的心理,深知求助者的人性弱点。那些心中有非分之想、渴求通过“神迹”谋取利益的人,最容易中了吴泽衡们的道。那些不拜苍生拜鬼神的官员们,深陷其中,理与此同。
从更广的视野观之,吴泽衡、王林们大行其道,一与公民科学素养的缺失有关——2013年12省市抽样调查结果显示,我国公民的科学素质整体水平达到4.48%,但与发达国家仍有较大差距;二与社会快速转型带来的不适有关——正如有学者所称,现代化引起的巨变除了使人们感到新鲜好奇之外,也使千千万万的人患上了现代化综合征:伤感怀旧,焦灼浮躁,孤独冷漠。多元文化、多元思想、多元价值观之下,各种思潮涌起,人们无所适从,便容易出现信仰迷茫和道德失范。
“篱笆扎得紧,野狗野猫钻不进”。精神缺乏定力,就容易失魂落魄;目光缺乏穿透力,最容易为歪理邪说所蛊惑。诗人惠特曼说,“没有信仰,则没有名副其实的品行和生命”。清醒坚定的正确信仰,才是应对繁杂社会的定海神针,也是帮助我们洞穿迷雾妖气的火眼金睛。
健康的信仰从哪里来?它体现在对优秀传统价值的认可,比如诚信友善;体现在对人类美好价值的向往,比如自由平等;也体现在对当下社会共识的践行,比如公正法治。社会主义核心价值观并不抽象,它承接传统美德,又对接现代文明;它滋润着我们的心灵世界,又渗透在每个人的生活轨迹中。以它为精神导航,才不会迷失;时时深味其精髓,人生才不会触礁。
“人生最可怕的不是身体失去自由,而是精神不能自主,这种痛楚无时无刻不在折磨着自己。”一位被吴泽衡蒙骗的人如此忏悔。“但立直标,终无曲影”,要想不在时代的滚滚洪流中随波沉沦,不被歪门邪道所诱骗蛊惑,拨正信仰的指针是不二法门。
编辑提点:李跃
越是在物质发达的时代,越需要精神的家园;越是价值多元化的时代,越需要正确的信仰。
卢梭说,没有信仰,就没有真正的美德。当下,人人在生活中都不断地寻找自己的信仰。有人信仰金钱,认为金钱才能给人安全感;有人信仰权势,认为有权势就拥有了一切……其实,我们并不缺乏信仰,缺乏的只是真实健康的信仰。那么,健康的信仰从哪里来?要有正确的价值观,要承接传统美德,对接现代文明,等等。坚持这种正确的信仰,会滋润我们的心灵世界,使我们在生活的轨迹中不迷失方向。
C++指针应用的四大误区 篇12
继承自C语言的C++语言保持了程序运行的高效性,指针的存在是高效性的保证,但同时也给程序的设计与开发带来了不安全因素。文献[1][2]对指针的应用进行了较全面的介绍。从常见的指针错误出发,结合实际编程经验,说明指针应用的4大误区,并给出相应的错误规避方法。
1 操作空指针
C++语言中,使用关键字NULL表示未指向任何内存位置的指针,从程序错误的角度将空指针定义为未指向本程序可控地址范围的指针,实际应用中可表现为3种形式。
(1)指针为空。NULL指针表示未将指针指向任何地址,相当于整数中的0,表示空指针。对NULL指针指向的内存进行的读写操作,均会引起程序的直接终止,属于致命错误。所以,在使用一个指针———特别是由其它程序模块传递的指针参数之前,需要对指针进行判断,杜绝对空指针的操作,如:
if(p!=NULL){//do sth…}else{//error}
(2)指针变量未赋初值。C++编译器对于未进行初始化的指针变量,并不自动赋值为NULL。因此,对于NULL指针的检测将失效,无法判断指针是否指向了合理的内存空间。因此,需要在程序编写时养成良好的习惯,定义指针变量时即时其进行初始化操作,在没有地址空间可指向的情况下,赋值为NULL,以便后继程序的错误检测;
(3)指针越界。最长见的越界错误发生在数组元素读写时,由于对数组的长度未做检测,会操作数组空间后的连续内存区域,而这些区域已经不属于本程序的控制范围,与前两种指针错误不同,指针越界错误通常不会造成直接的程序终止,而隐藏较深,对程序后期的调试带来不可重现的错误。所以,在操作数组结构时,务必注意对长度的判断,可避免此类指针越界错误。
2 内存泄露
Java语言与C#语言均带有自动垃圾回收(Garbage Collection)机制,虚拟机会释放已经无用的内存。C++语言将内存申请与释放的权限完全交付程序员控制,在保证程序高效的同时,也为编程过程带来了不可避免的负担———程序员必须回收自己申请且已经无用的内存,否则,即出现内存泄露。
在此,存在三处易出错语句。语句1处于类MyClass的析构函数之中,由于构造函数中进行了动态内存分配,故必须在析构函数中对此内存进行释放,否则将造成内存泄露;语句2对应main函数中的new操作,如遗漏语句2则造成直接的内存泄露,而遗漏语句1时,语句2的操作将造成pMy所指向的MyClass对象的局部析构;语句3与内存泄露无关,但十分重要。一旦失去此语句且对MyClass对象进行了默认构造,则在对象析构时会出现指针变量未赋初值的错误,造成程序异常终止。
值得注意的是,内存泄露错误并不会像操作空指针一样会使程序异常退出,而是仅消耗了系统内存[3]。所以,对于一次启动长期运行的大型程序而言,防止内存泄露是十分重要的。同时,常见操作系统,例如Windows,当程序退出时会回收此程序占用的所有内存,此特征可看做对内存泄露后果的一种弥补,但并非所有操作系统都具有此功能。
3 重复释放
new运算符与delete运算符应“成对”出现,仅申请内存而不释放内存,会造成内存泄露。反之,若对于已申请的内存进行了重复性释放,同样会使可执行程序产生致命错误。
在此给出内存重复释放的典型可能错误场景。函数Fun1内进行了内存申请,并将指针传递给Fun2。Fun2内部进行条件判断,若满足条件则不再需要此内存,立即进行内存释放,否则,继续将内存指针传递给Fun3。Fun3与Fun2逻辑类似,要么释放,要么继续传递给其他函数。实际问题中,判定是否需要释放内存的逻辑往往比较复杂,程序设计时,若考虑不周,出现condition1∩condition2≠Φ的情况,则会重复执行delete p语句,造成内存的重复释放。
重复释放内存会使程序异常终止,然而再谨慎的代码设计与编写也无法完全杜绝重复释放问题,所以,可以在释放内存后,同步对其指针进行赋空操作,而在释放之前检测指针是否为空,不为空则释放内存。
4 指针常量与常量指针
指针常量与常量指针是一对比较容易混淆的概念,特别是在进行函数参数传递的时候,需要注意二者的区别和应用习惯。
(1)指针常量说明指针本身是不允许修改的,为一个常量地址。最常见的指针常量有两种:数组名称与引用。C++语法沿用了C语言的特点,数组名即为指向数组地址空间的首指针[4]。这个指针自身为常量,不许修改,也就是不能将数组名进行赋值,指向其它地址空间。否则,数组地址空间无法被程序表示和控制。任何改变数组名称指向的操作均会引起编译错误。例如:
int a[10];int m;a=&m;//compiling error
引用作为C++相比C语言特有的语法,其核心目的是实现一种安全的指针,而引用自身可以看做被引用对象的常量地址描述,不许更改。
(2)常量指针的意思是指针所指向的地址空间为常量,而指针自身是可以修改的。常见的常量指针也有两种场景:常量字符串与参数传递。
语句1试图修改str1指针所指向的地址空间内的内容,由于str1是常量指针,所以这个操作时不允许的。遗憾的是,大多数编译器不会检测到这个错误,所以会造成运行时的程序异常。语句2是将str1指向的其他地址空间,由于str1本身是变量,所以这个操作不会引起任何错误,这与指针常量是不同的。
void Fun(const int*p1,int*p2);
在此给出一个函数声明方式,形参p1使用关键字const修饰,意味着p1是常量指针,不能够在函数体内改变p1地址空间内的内容。而p2未作修饰,意味着p2所指向的内容可以修改。
5 结语
正确地使用指针是成功构建C++程序的基础,从4个方面探讨了应用指针的编程误区,并给出可能的解决方案与错误避免方法。
参考文献
[1]王美荣.C语言指针的应用[J].电脑知识与技术,2009,(5)35:982-983.
[2]杨井荣,赵春雨.C语言指针综述[J].电脑编程技巧与维护,2009:101-102.
[3]Prata.S.著,孙建春,韦强译.C++Primer Plus中文版[M].第5版,人民邮电出版社,2005.