您当前的位置:首页 > 好词好句 > 正文

初识指针和指针变量的关系(初识指针和指针变量)

初识指针和指针变量的关系(初识指针和指针变量)

说到指点,估计还有很多小伙伴还在云里雾里,有点意识到这一点,但唐不知道为什么。不过不得不说,只有学会了指针,C语言才能算是入门。指针是精华C语言的。可以说指针的掌握程度直接决定了你的C语言编程能力。

在我们谈论指针之前,让让我们首先了解变量是如何存储在记忆。

如果你在程序中定义了一个变量,系统会分配相应的大小内存空间取决于你在程序编译时定义的变量类型。然后,如果你想使用这个变量,你只需要用变量名来访问它。

这是一个相对安全通过名称访问变量的方法。只有定义了它,才能访问相应的变量。这是记忆的基本认知。但是,如果你只知道这些,其实你还是不你不知道内存是如何存储变量的,因为你仍然不知道。我不知道底层是如何工作的。

所以如果你想更进一步,你需要找出变量在内存中真正的样子。内存的最小索引单位是1字节,所以实际上可以把内存比作一个超大的字符数组。正如我们在上一节中所说的,数组有下标,我们通过数组名和下标来访问数组中的元素。那么内存是一样的,除了我们给它一个新的名字:地址。每个地址可以容纳1字节所以如果我们需要定义一个整型变量,我们需要占用4个内存单元。

那么,这里你可能就明白了:其实在程序运行的过程中,是不需要变量名参与的。变量名只是方便我们编写和读取代码。只有程序员和编译器知道这个东西的存在。编译器也知道内存地址对应于具体的变量名,我们不我不知道,所以编译器就像一座桥。读取变量时,编译器会找到变量名对应的地址,读取对应的值。

熟悉指针和指针变量

所以让现在让我们言归正传。什么是指针?

所谓指针就是内存地址(以下简称地址)。在C中,设置了一个特殊的指针变量来存储指针。与普通变量不同,指针变量存储地址。

定义指针

指针也有类型,这实际上取决于地址所指向的值的类型。那么如何定义指针变量呢:

简单:类型名*指针变量名

char * pa//定义一个指向字符变量的指针,名称为pa int * pb//定义一个指向名为pb float*pc的整型变量的指针;//定义指向名为pc的浮点变量的指针

注意指针变量必须和被指向的变量类型相同,否则不同的类型可能会在内存中占据不同的位置,错误的定义可能会导致错误。

地址运算符和值运算符

使用地址获取运算符获取变量的地址,例如:

char * pa=a;int * Pb=f;

另一方面,如果您想要访问指针变量所指向的数据,那么您必须使用值运算符*,例如:

printf(%c,% d ,*pa,* Pb);

在这里,你可能会发现*在定义指针时也被使用,它属于重用符号。也就是说,这个符号在不同的地方有不同的含义:它的意思是定义指针变量定义时,它被用来获取指针变量在其他时候。

通过变量名访问变量值称为直接访问,通过指针形式访问称为间接访问,所以值运算符有时也称为间接运算符。

例如:

//Example01 //代码来自网络,非个人原创# include int main(void){ chara=福intf=123char * pa=a;int * pf=f;printf(a=% c ,* pa);printf(f=% d ,* pf);* pa=c * pf=1;printf(现在,a=% c ,* pa);printf(现在,f=% d ,* pf);printf(izeofpa=% d sizeof(pa));printf(izeofpf=% d ,sizeof(pf));printf(地址是:% p ,pa);printf(地址是:% p ,pf);return0}

该计划的实施如下:

//结果01 a=f f=123 now,a=c now,f=124 sizeof pa=4 sizeof pf=4的地址是:00 eff 97 f f的地址是:00EFF970

避免访问未初始化的指针。

voidf(){ int * a;* a=10}

像这样的代码非常危险。因为我们不I don’我不知道指针指向哪里。就像访问未初始化的普通变量一样,它会返回一个随机值。但是,如果它在指针中,它可能会覆盖其他内存区域或者甚至关键领域这是非常危险的。但是,在这种情况下,系统一般会拒绝程序的运行,并且程序会暂停并将报告一个错误。如果你赢得了奖金,并涵盖了一个合法的地址,那么下面的分配将导致一些有用的数据被莫名其妙的修改。这样的bug很难排除,所以使用指针时一定要注意初始化。

指针和数组。

有些读者可能会有点奇怪。指针和数组有什么关系?这两种商品没有任何关系。唐别担心,继续读下去,你的观点可能会改变。

数组的地址

就像我们刚才说的,指针实际上是一个变量在内存中的地址,所以如果有细心的朋友可能会想,像数组这样的一大组变量的地址是什么?

我们知道,当我们从标准输入流中读取一个值到一个变量中时,我们使用scanf函数,它通常似乎是添加在后面的。这实际上是地址提取运算符我们刚说过。如果你存储的位置是一个指针变量,你不用我不需要它。

//example 02 int main(void){ inta;int * p=a;printf(请输入一个整数:);scanf(% d ,a);//printf(a=% d ,此处a)是必需的;printf(请输入另一个整数:);scanf(% d ,p);//printf(a=% d ,a)这里不需要;return0}

该程序运行如下:

//共识02请输入一个整数:1 a=1。请输入另一个整数:2 a=2

在读取一个普通变量时,程序需要知道变量在内存中的地址,所以需要获取地址来完成这个任务。至于指针变量,它是地址信息另一个普通变量,所以直接给指针值就够了。

想象一下,当我们使用scanf功能时,有没有一些时候我们没有我不需要用它?是在阅读字符串:

//example 03 # include int main(void){ charurl[100];URL[99]='printf(请输入TechZone的域名:);scanf(% s ,网址);//printf(您输入的域名是:% s ,url)在这里也不用;return0}

该程序执行如下:

//共识03请输入TechZone的域名:www.techzone.ltd您输入的域名是:www.techzone.ltd。

因此,很容易推理:数组的名称实际上是一个地址信息,实际上是第一个元素数组的。让让我们尝试将第一个元素的地址与数组的地址进行比较:

//example 03 v2 # include int main(void){ charurl[100];printf(请输入TechZone的域名:);URL[99]='scanf(% s ,网址);printf(您输入的域名是:% s ,网址);Printf(的地址网址是:% p ,网址);Printf(的地址URL [0]是:% p ,网址[0]);if(URL==URL[0]){ printf(两者是一致的!');} else { printf(两者不一致!');} return0}

程序的运行结果是:

//comsequence03v2请输入TechZone的域名:www.techzone.ltd您输入的域名是:www.techzone.ltd网址是:0063F804 url[0]地址是:0063F804。两个人都是一致的!

所以,应该是真锤子。然后把数组后面的元素依次放回去,有兴趣的可以自己写代码试着输出。

指向数组的指针

我们刚刚验证了数组的地址是数组第一个元素的地址。那么自然有两种方法来定义指向数组的指针:

.char * p;//方法1p=a;//方法2p=a[0];

指针操作

当指针指向一个数组元素时,指针变量可以加减,n表示下n个元素指针指向的元素的-n表示最后n个元素指针指向的元素的。它不是在地址上加1。

比如:

//example 04 # include int main(void){ inta[]={ 1,2,3,4,5 };int * p=a;printf(*p=%d,*(p 1)=%d,*(p 2)=% d *p,*(p 1),*(p2));printf(*p-%p,*(p 1)-%p,*(p 2)-% p p,p 1,p 2);return0}

执行结果如下:

//结果04 *p=1,*(p 1)=2,*(p 2)=3 *p - 00AFF838,*(p 1) - 00AFF83C,*(p 2) - 00AFF840

有些朋友可能会疑惑,编译器怎么知道要访问下一个元素,而不是直接在地址上加1?

事实上,当我们定义指针变量时,我们已经告诉了编译器。如果我们定义一个指向整型数组的指针,指针加1实际上就是加了一个sizeof(int)的距离。与标准下标访问相比,使用指针间接访问数组元素的方法称为指针方法。

实际上,没有必要定义一个单独的指针变量来使用指针方法访问数组的元素,因为数组名本身就是指向第一个元素所以指针方法可以直接作用于数组名:

.printf(p-%p,p 1-%p,p 2-% p a,a 1,a 2);printf(a=%d,a 1=%d,a 2=% d ,*a,*(a 1),*(a2));

执行结果如下:

p-00AFF838,p 1-00AFF83C,p 2-00AFF840 b=1,b 1=2,b 2=3

现在,你觉得数组和指针有点像吗?但是,我要提醒你,数组和指针虽然很像,但绝对不是一回事。

你甚至可以直接用指针定义一个字符串,然后用下标方法读取每个元素:

//Example05 //代码来自网络# include # include int main(void){ char * str=ilovetechzone!'inti,长度;length=strlen(str);for(I=0;ilength,I){ printf(% c ,str[I]);} printf(');return0}

该程序运行如下:

//后果05我爱TechZone!

在刚才的代码中,我们定义了一个字符指针变量,并将其初始化为指向一个字符串。以后,你不仅可以用字符串处理函数&quot但是你也可以用下标方法访问字符串中的每个字符。

当然,将循环部分写成这样并没有错:

.for(i=0,ilength,I){ printf(% c ,*(str I));}

这相当于使用指针方法来读取。

指针和数组的区别

刚才我提到了很多指针和数组替换的例子,有些朋友可能会说,阿伦这两种商品不是一回事吗?"

随着你对指针和数组的了解越来越多,你会发现C语言的创始人并不是那么无聊的去创造两个名字不同的一模一样的东西。指针和数组是不同毕竟。

比如,笔者之前看过一个例子:

//Example06 //代码来自网络# include int main(void){ charstr[]=ilovetechzone!'int count=0;while(*str!=''){ count} printf(总共有%d个字符。'计数);return0}

当编译器报告一个错误时,你可能开始怀疑你学了假的C语言语法:

//示例中的错误06 error (active) E0137表达式必须是可修改的左值error C2105 "需要左值。

我们知道,*str!=''是一个复合表达式,所以我们应该遵循运算符优先级。详情请咨询《C语言运算符优先级及ASCII对照表》。

Str有一个更高优先级高于*str,但自动递增运算符要到下一条语句时才生效。所以对这个语句的理解是,先把str指向的值拿出来,确定是不是,如果是,跳出循环,然后str指向下一个字符的位置。

好像没什么问题,但是看看编译器告诉我们的:表达式必须是可修改的左值。

的操作对象是str,所以str 左值还是没有?

如果是左值,那么左值的条件必须满足。

具有用于识别和定位存储位置的标识符。

可以修改存储的值。

首先,可以满足数组名str,因为数组名实际上是数组第一个元素的位置。但是第二点还不够。数组名实际上是一个地址,地址不能修改。它是一个常数。如果您必须使用上述想法来实现它,您可以将代码更改为:

//Example06V2 //代码来自网络# include int main(void){ charstr[]=ilovetechzone!'char * target=strint count=0;while(*目标!=''){ count} printf(总共有%d个字符。'计数);return0}

因此它可以正常执行:

//后果06 V2一共有16个字符。

这样我们就可以断定,数组名只是一个地址,指针是一个左值。

指针数组?数组指针?

看下面这个例子,你能分辨出哪个是指针数组,哪个是数组指针吗?

int * P1[5];int(* p2)[5];

我们都可以单独判断,但是综合起来就有些困难了。

回答:

int * P1[5];//指针数组int(* p2)[5];//数组指针

让让我们逐一分析。

点阵

数组下标[]具有最高优先级,因此p1是一个数组拥有五行。那么这个数组是什么类型的呢?答案是int*,一个指向整型变量的指针。因此,这是一个指针数组。

那么这样的数组应该如何初始化呢?

你可以定义五个变量,然后用地址逐个初始化。

然而,这太繁琐了,但这并不这并不意味着指针数组是无用的。

例如:

//example 07 # include int main(void){ char * P1[5]={ 人生苦短,我用Python。''PHP是世界上最好的语言!''还有一件事.''一个好的程序员应该是那种过单行道的时候要看两边的人。'c语言很容易让你犯错;c看起来更好,但是当你用的时候,你会发现你会死的更惨。'};intifor(I=0;i5;I){ printf(% s ,P1[I]);} return0}

结果如下:

//后果07人生苦短,我用Python。PHP是世界上最好的语言!还有一点.一个好的程序员应该是那种过单行道的时候要看两边的人。c语言很容易让你犯错;c看起来更好,但是当你用的时候,你会发现你会死的更惨。

这是不是比二维数组更直接,更通俗?

数组指针

()和[]属于同级在优先,所以进行在优先顺序。

Int(*p2)将p2定义为指针,后跟一个数组5个元素,p2指向这个数组。因此,数组指针是一个指针指向一个数组。

但是,如果要初始化数组指针,要小心,比如:

//example 08 # include int main(void){ int(* p2)[5]={ 1,2,3,4,5 };intifor(I=0;i5;I){ printf(% d ,*(p2i));} return0}

Visual Studio 2019报告了以下错误:

//示例08中的错误和警告错误(活动)E0146初始值设定项有太多值错误C2440 初始化&quot:无法从初始化列表到int(*)[5]警告C4477"printf &quot:格式字符串% d 需要类型为的参数int ,但是变量argument 1有一个类型。

其实这是一个非常典型的误用指针的案例。编译器提示在将整数敬一杯指针变量给你。因为p2在最后分析中仍然是一个指针,所以您应该将一个地址。改变它:

//example 08 v2 # include int main(void){ int temp[5]={ 1,2,3,4,5 };int(* p2)[5]=temp;intifor(I=0;i5;I){ printf(% d ,*(p2i));} return0}//示例中的错误和警告08v2错误(活动)E0144 int * 类型值不能用于初始化类型的实体int(*)[5]错误C2440初始化&quot:无法从int[5]到int(*)[5]警告C4477"printf

但是为什么还是有问题呢?

让让我们回顾一下指针是如何指向数组的。

inttemp[5]={1,2,3,4,5 };int * p=temp

本来我们以为指针P是指向数组的指针,其实不是。如果你仔细想想,你会发现这个指针其实是指向第一个元素而不是指向数组。因为数组中的所有元素都存储在内存中,所以只需知道第一个元素的地址就可以访问后面的所有元素。

但是,这样一来,指针P指向的是整型变量,而不是数组。我们刚刚使用的数组指针是指向数组的指针。因此,应该将数组的地址传递给数组指针,而不是第一个元素的地址。尽管它们具有相同的值,但含义确实不同:

//example 08 v3//example 08 v2 # include int main(void){ int temp[5]={ 1,2,3,4,5 };int(* p2)[5]=temp;//这里取地址intifor(I=0;i5;I){ printf(% d ,*(* p2 I));} return0}

该程序运行如下:

//结果08 1 2 3 4 5

指针和二维数组。

在上一节《C语言之数组》中,我们谈到了二维数组,我们也知道C语言中的二维数组实际上是线性存储在记忆中。

假设我们定义:int array[4][5]

排列

作为数组的名称,array应该清楚地表明第一地址数组的。因为二维数组实际上是一维数组的线性扩展,所以数组应该是一个包含五个元素的数组的指针。

如果使用sizeof()来测试array和array 1,就可以检验这个结论。

*(数组1)

首先,从上一个问题我们可以得出,array 1也是一个包含五个元素的数组的指针,所以*(array 1)等价于array[1],正好等价于array[1][0]的数组名。因此*(数组1)指的是第二行子数组的第一个元素的地址。

*(*(数组1) 2)

有了正义的结论,我们就不难推导出这其实是array[1][2]。是不是感觉很简单?

综上所述,以下是结论。只要记住它们,它理解它们当然更好:

*(array I)==array[I]*(*(array I)j)==array[I][j]*(*(*(array I)j)k)==array[I][j][k].

数组指针和二维数组

正如我们在上一节中所说的,在初始化二维数组时,您可能会偷懒:

intarray[][3]={ {1,2,3},{4,5,6 } };

正如我们刚才所说,定义一个数组指针看起来像这样:

int(* p)[3];

那么结合是什么意思呢?

int(* p)[3]=array;

从刚才的解释中,我们可以知道数组是一个指针到一个三个元素的数组,所以这里我们可以把数组的值赋给p。

其实C语言的指针非常灵活。同样的代码,如果从不同的角度解读,可以有不同的应用。

那么如何使用指针访问二维数组呢?那没错,它使用数组指针&quot:

//example 09 # include int main(void){ int array[3][4]={ { 0,1,2,3},{4,5,6,7},{8,9,10,11 } };int(* p)[4];inti,j;p=数组;for(i=0,i3,i ) { for(j=0,j4,j){ printf(-'*(*(p I)j));} printf(');} return0}

运行结果:

//结果09 0 1 2 3 4 5 6 7 8 9 10 11

空指针

Void实际上是无类型的意思。如果你试图用它定义一个变量,编译器肯定会报告错误因为不同类型占用的内存可能不同。但是如果定义了指针就好了。void类型中的指针可以指向任何类型的数据,也就是说,任何类型的指针都可以赋给void指针。

将任何类型的指针转换成void都没有问题。但是如果你想反过来,你需要cast 。此外,唐 取消引用void指针,因为编译器实际上不我不知道空指针将保存什么类型。

//example 10 # include int main(void){ int num=1024;int * pi=# char * ps=TechZone void * pvpv=piprintf(pi:%p,PV:% p pi,PV);printf(* PV:% d * PV);pv=psprintf(ps:%p,PV:% p ps,PV);printf(* PV:% s * PV);}

这将报告一个错误:

//示例10中的错误错误C2100非法间接寻址错误C2100非法间接寻址

如果你必须这样做,你可以用cast :

//example 10 v2 # include int main(void){ int num=1024;int * pi=# char * ps=TechZone void * pvpv=piprintf(pi:%p,PV:% p pi,PV);printf(* PV:% d *(int *)PV);pv=psprintf(ps:%p,PV:% p ps,PV);printf(* PV:% s PV);}

当然,使用void指针时一定要小心。因为空指针可以全部吃掉几乎所有类型,它间接使得不同类型的指针转换合法。如果代码中有不合理的转换,编译器不会报错。

所以,不管void指针能不能用,后面讲功能的时候可以解锁更多新游戏。

空指针

在C语言中,如果指针不不指向任何数据,它被称为空指针用null 。NULL实际上是一个宏定义:

#defineNULL((void*)0)

在大多数操作系统中,地址0通常是一个未使用的地址,所以如果一个指针指向NULL,就没有任何意义。为什么指针指向NULL?

其实这是一个值得推荐的编程风格的比较。3354当你不如果还不知道指向哪里,就让它指向NULL,这样就不会有问题了将来不要太麻烦,比如:

//example 11 # include int main(void){ int * P1;int * p2=NULLprintf(% d ,* P1);printf(% d ,* p2);return0}

第一个指针没有初始化。在一些编译器中,未初始化的变量被赋予随机值就这样。这个指针叫做丢失的指针,野生指针或者浮动指针。如果下面的代码解引用了这种指针,而这个地址恰好是合法的,那么就会产生莫名其妙的结果,甚至导致程序的崩溃。所以养成一个好习惯,在暂时不清楚的时候使用NULL,可以为后期调试节省很多时间。

指针对指针

它是玩洋娃娃的时候了。事实上,只要你理解指针的概念,它没什么大不了的。

//example 12 # include int main(void){ int num=1;int * p=# int * * pp=pprintf(号码:% d ,num);printf(* p:% d * p);printf(* * p:% d * * PP);printf(p:%p,PP:% p p,PP);printf(num:%p,p:%p,* PP:% p num,p,* PP);return0}

程序结果如下:

//结果12 num: 1 *p: 1 **p: 1 p: 004FF960,pp: 004FF960 num: 004FF96C,p: 004FF96C,*pp: 004FF96C

当然,你也可以无限玩娃娃,不停指指点点。但是,这会使代码的可读性差。过一段时间,你可能就看不懂自己写的代码了。

数组和指向指针的指针

那么,什么指针对指针有什么用?

它这不是为了创造混乱的代码。在一个经典的例子中,你可以意识到它的用处:

char * Books[]={ 《C专家编程》 ' '《C和指针》 ' '《C的陷阱与缺陷》 ' '《CPrimerPlus》 ' '《Python基础教程(第三版)》 '};

然后我们需要对这些书进行分类。我们发现其中一个是用Python写的,其他的都是用C语言写的。这时候指针的指针就派上用场了。首先,我们刚刚定义了一个指针数组,也就是说,里面所有的元素都是指针,但是数组名可以以指针的形式访问,所以我们可以用指针对指针的方式指向指针数组:

.char * * Pythonchar * * CLang[4];python=Books[5];CLang[0]=Books[0];CLang[1]=书籍[1];CLang[2]=书籍[2];CLang[3]=书籍[3];

因为字符串的地址值实际上是它的第一地址,即一个指向字符的指针指针,可以这样分配。

这样,我们使用指针的指针来完成图书的分类,既避免了浪费冗余内存,又提高了图书书名只需更改一次时代码的灵活性和安全性。

和常量指针。

常数,在我们现在的认知中,应该是这样的:

520, '一

或者像这样:

# defineMAX1000 # defineB b

常量和变量最大的区别是前者不可修改,而后者可以。然后在C语言中,变量可以改成类似常量的特性,可以用const。

constintmax=1000constchara=一

在const关键字的作用下,变量将失去它们原来的可修改属性变成只读属性。

指向常数的指针

当然,强大的指针也可以指向const修改的变量,但这意味着它引用的值可以不要被指针修改。总结起来,就是以下四点:

指针可以修改为指向不同的变量。

指针可以修改为指向不同的常数

可以通过解引用来读取指针所指向的数据。

你可以不要通过取消引用来修改指针所指向的数据。

常数指针

指向非常数常量指针。

指针本身,作为变量,也可以修改。因此,指针也可以被const修改,但位置略有变化:

.int*constp=#.

这种指针具有以下特征:

指针本身不能修改。

指针指向的值可以修改。

指向常量的常量指针

在定义普通变量时,也用const修饰,得到这个指针。然而,由于太多的限制,它很少被使用:

.intnum=100constintcnum=200constint * constp=cnum.


声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,谢谢。

上一篇: 华为mate30pro屏幕碎了换一个多少钱(mate30pro换屏幕多少钱)

下一篇: 华硕x550dp笔记本(请问华硕笔记本X550DP这一款电脑的内存条该怎么加,我拆开后没有找到内存条的卡槽,跪求帮助在线等)



推荐阅读