十三、指针
1.内存
要理解指针,我们首先要理解内存,那什么是内存呢?内存是计算机上的一种存储空间,我们的电脑中常见的由8G/16G内存,程序在运行的时候会载入内存,程序如果有数据需要存储也会申请内存空间。那么如何有效的使用内存呢?
在生活中,我们有很多的空间,很多的楼,为了方便我们使用这些空间,我们会对其进行编号,比如西安邮电大学长思四101,102,103等等。这就是一种一个门牌号对应一个房间的关系,有了这些门牌号,我们可以很方便的找到对应的空间。
而我们计算机也是采用了这一种方式,我们假设内存是一个大长方形,然后我们对其进行等分,如下图所示(笔者画图水平不高,还望包涵)
然后我们将每一个小格子称作内存单元,我们可以对每一个内存单元进行编号,如下图所示,这样未来在我们可以通过编号很快的找到我们的每一个内存单元
中间的每一个格子叫做内存单元,实践中,每一个内存单元的大小是一个字节,我们将右边的称作内存单元编号
由于在现实生活中我们将西安邮电大学a栋教学楼a101也叫做地址,通过这些地址可以找到所需要的空间,事实上我们也将内存单元编号称作地址,而在c语言中我们也将地址叫做指针。
那我们又有了一个新问题,如果访问一个内存单元,那内存单元的地址(指针)该如何产生呢?
我们的电脑机器常见的就是32位,64位(这里的位指的是比特位)。当然,现在主流的都是64位机器,但是我们为了能形象一点,我们这里以32位机器为例子
我们的32位机器,有32根地址线,就是我们物理中的哪些电线,地址线如果通电,那我们就会有电信号,就会有高电平,低电平。转换成数字信号就是1和0(如果大家想了解数字逻辑电路,数字信号等相关知识的话,可以在评论区留言,有时间可以出一些博客来讲解一下这些内容),也就是我们的二进制0和1。
那如果32根地址线全部通电呢?这样就会产生32位二进制序列,如下图所示,而这些二进制序列恰好对应于上面的每一个内存编号,因为二进制与上面的十进制的内存编号是可以互相转换的,这在进制转换这篇外传中已经详细讲过了。
因此,我们将这些二进制序列与内存编号一一对应得到如下
这样的话,每一个由32根地址线决定的二进制序列就可以管理我们的内存,而这些二进制序列就称作我们这每一个内存单元的地址。
而且我们这32根地址线最多产生,2的32次方个地址,每一个地址对应一个内存单元,而每一个内存单元是一个字节的大小,也就是说,最多可以管理2的32次方个字节的内存大小。
2的32次方字节内存大小我们换算一下
1KB=2的10次方字节
1MB=2的十次方KB
1GB=2的十次方MB
因此2的32次方字节等于4GB
也就是说32位机器最多拥有4GB内存,这也就是为什么我们以前最常见到的内存是4GB的一个原因。这是由于硬件所决定的最大内存
而我们现在随着硬件设施的发展,现在已经拥有64位机器,这样我们最多可以控制4*2的32次方个GB,这已经是相当大了。当然我们日常中最常见到的就是8GB/16GB了
经过上面的讲解,相信大家已经较为深刻的理解内存了,内存单元编号就是我们的地址也就是c语言中的指针。通过内存单元编号,也就是指针可以去找到我们这个内存里的数据。这叫做访问,通过一个指针去访问。
我们看下面一段代码
#include<stdio.h> int main() { int a = 5;//向内存申请四个字节的空间 printf("%d", sizeof(a)); return 0; }
不难得知结果是4,也就是a向内存申请了四个字节的空间,如下图所示,假设橙色区域即为a所申请的四个字节空间。
在这里我们有个习惯,因为使用32个二进制位(也就是比特位)表示内存单元的编号,太过于冗杂,由于四位二进制数等于一位十六进制数,于是我们采用十六进制数来表示内存,这样我们就只需要八个数字即可清晰的表示出地址,也就是我们c语言中的指针。
这里有一点需要注意,我们c语言中的十六进制数需要加上前缀0x,八进制数加上前缀0。
我们打印一下a的地址
这段代码需要注意的是,&a的意思是取出a的地址,%p是打印地址的意思。可以跟%d是打印整型类比。
#include<stdio.h> int main() { int a = 5;//向内存申请四个字节的空间 printf("%p", &a); return 0; }
运行后,我们会发现,只打印出来了一个地址,可是,我们的a占了四个字节啊,也就是四个内存单元,应该占据了四个地址,那我们这个地址是这四个地址中的哪一个呢?
注:有的人打印出来的地址和我这个不太一样,他们的不只是8个十六进制数,而是16个十六进制数,这其实是因为你们的编译器目前是64位环境下,而我的编译器是32位环境。下面给出如何将编译器切换成32位环境下的方法。
我们的visual stido环境下,那里有一个x86和x64的选项,其中x64是64位环境,x86是32位环境,想要切换环境,直接选择即可。而且当你运行完毕以后你可能会发现,每次打印出来的a的地址都不一样,这是正常的,每次编译后,编译器都会重新为其分配地址。
想要直到到底占用了哪一个地址,我们调试打开内存窗口,按住crtl +fn+f10(有的电脑直接ctrl+f10),然后点击调试,点击窗口,点击内存,内存1,2,3,4随便选择一个即可。
我们选择内存1,先将下面改成4
变成如图所示
这是a的地址,0x0093FCE8,当然这个显示是四个字节中a的地址,但是我们此时已经得知a的地址了。
然后我们将列改为1
我们发现有四个地址,其中最小的那个地址刚好就是a的地址
于是我们得出结论,a的地址为它所占的几个内存单元中最小的地址,也就是第一个字节的地址,这是一个很重要的结论。
用图片来表示即为
a的地址为对应内存单元最小的一个,也就是第一个字节的地址
那么现在我们讲了这么多的内容,那么指针怎么用呢?先阅读下面一段代码,我们来讲解一下
#include<stdio.h> int main() { int a = 5;//向内存申请四个字节的空间 int* pa = &a; printf("%p", pa); printf("%p", &a); return 0; }
这段代码中,我们创建了a,并申请了四个字节的空间,然后我们使用一个变量pa来存放a的地址,这个变量pa就叫做指针,pa因为也是一个变量,所以我们习惯上叫它为指针变量。pa前面的那颗*表示pa是一个指针,而前面的int代表着pa这个指针(地址)指向的是一个int类型,所以合起来pa的类型我们称作int*,也就是所谓的指针类型。
这段代码运行结果为上图所示,也就是说&a,和pa其实是一回事。
那么我们使用指针变量可不仅仅只是记录一下a的地址什么的,我们是需要通过这个地址来改变它里面的数据。这才是指针的用处。
#include<stdio.h> int main() { int a = 5;//向内存申请四个字节的空间 int* pa = &a; *pa = 20; printf("%d\n",a); return 0; }
如图所示,这个*pa中,*的意思是解引用,也就是说这个操作可以使我们通过pa这个地址找到a的值。然后我们对其赋值,从而篡改了a的值,此时a的值已经变成了20
2.指针变量的大小
我们运行一下下面这段代码
#include<stdio.h> int main() { int* a; char* b; printf("%d\n", sizeof(a)); printf("%d\n", sizeof(b)); return 0; }
32位机器下结果应该是两个4,64位机器结果是两个8
可见指针变量的大小与其所指向类型无关,只与机器是多少位有关系。
为什么会产生这种情况?
因为指针变量存放的是一些比特位
32位环境下,一共有32个比特位,也就是4个字节。
64位环境下,一共有64个比特位,也就是8个字节
十四、结构体
在c语言中我们已经见过许多的类型了,有int,char,short,long,long long ,double ,float等等这些都是c语言中的内置类型。除此之外,我们c语言中还有一种类型是自定义类型,自定义类型分为三种,结构体,联合体,枚举。这三种类型之中,我们已经初步了解过枚举类型了,那么我们现在来了解一下结构体。
当我们在描述一个人的时候需要描述它的身高,体重,姓名性别,年龄等等。当我们需要了解一本书的时候,需要描述它的书名,作者,出版社,定价等。这些都是一种比较复杂的类型。这时候就需要结构体了。
结构体的关键词是struct,那这个该怎么用呢?我们定义一个学生类型
struct Stu { char namr[20]; char sex[5]; int age; };
我们使用一下这个结构体
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> struct Stu { char name[20]; char sex[5]; int age; }; int main() { struct Stu s = { "zhangsan","男",18 }; struct Stu s2 = { "如花","女",20 }; printf("%s\n", s2.name); printf("%s\n", s2.sex); printf("%d\n", s2.age); return 0; }
在main函数中,我们定义两个结构体变量,然后对其初始化,最后打印出来。值得注意的是这个.操作符,是通过结构体变量来找到成员的。
结构体变量.成员名
除此以外我们还可以采用函数的方式来打印结构体,具体代码实现如下
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> struct Stu { char name[20]; char sex[5]; int age; }; void Printf(struct Stu* ps) { printf("%s %s %d\n", (*ps).name, (*ps).sex, (*ps).age); printf("%s %s %d\n", ps->name, ps->sex, ps->age); } int main() { struct Stu s = { "zhangsan","男",18 }; struct Stu s2 = { "如花","女",20 }; Printf(&s); /*printf("%s\n", s2.name); printf("%s\n", s2.sex); printf("%d\n", s2.age);*/ return 0; }
运行结果如下
这里有两种打印方式,一种是将结构体指针解引用后使用.操作符。
另外一种是使用->操作符。也就是结构体指针->结构体成员。
总结
本节我们讲解了指针与结构体。我们需要对指针有一个深刻的理解。
本站结束,下一站,分支和循环