前言一、指针是什么?二、指针和指针类型
2.1指针加或减上一个整数2.2指针的解引用 三、野指针四、指针运算五、指针和数组六、二级指针总结
前言
本文将简要介绍C语言指针如何使用以及使用中的一些注意事项,将围绕指针是什么、指针的类型、野指针、指针运算、指针和数组、二级指针等内容对C语言中遇到的指针进行介绍。
提示:以下是本篇文章正文内容,下面案例可供参考
一、指针是什么?指针理解的2个要点: 1. 指针是内存中一个最小单元的编号,也就是地址 2. 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量
那么,对于计算机的内存我们可以这样理解:整个内存被分成了若干个小单元,每个单元占一个字节的空间,而每个单元都有一个独有的地址编码。我们定义的变量就存储在内存中的这一个个小单元中。
指针变量:我们可以通过&(取地址 *** 作符)取出变量所在的内存的那个小单元的地址,把地址可以存放到一个变量中,这个变量就是指针变量。
观察下面的代码及注释以理解指针:
int main() { int a = 10;//在内存中开辟一块空间 int* p = &a; //这里我们对变量a,取出它的地址,可以使用& *** 作符。 //a变量占用4个字节的空间,这里是将a的4个字节的第一个字节的地址存放在p变量中,p就是一个指针变量。 //int* p中的*号表示p是一个指针变量,int则表示p所对应的那个变量a是一个整型变量 *p = 20; //这里的*号则表示解引用 *** 作符,*p则表示p这个地址所存储的内容,也就是变量a,*p = 20也就相当于a = 20 //注意定义和解引用时两个*的不同含义 return 0; }
总结:
指针变量,用来存放地址的变量。(存放在指针中的值都被当成地址处理)。
那这里的问题是:
一个小的单元到底是多大?(1个字节) 如何编址?
经过仔细的计算和权衡我们发现一个字节给一个对应的地址是比较合适的。
对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电平(低电压)就是(1或者0);
那么32根地址线产生的地址就会是:
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 ... 11111111 11111111 11111111 11111111
因此我们可以知道,32根地址线将有232种组合,因此就有了232个地址,每个地址标识一个字节,于是可以计算得:(232Byte == 232/1024 KB == 232/1024/1024 MB == 232/1024/1024/1024GB == 4GB),所以32位机器的最大内存是4GB.
同理64位机器因为有64根地址线,故而其理论上的内存可以达到128GB。
这里我们就明白:
在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以 一个指针变量的大小就应该是4个字节。
那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址。
总结:
指针是用来存放地址的,地址是唯一标示一块地址空间的。
指针的大小在32位平台是4个字节,在64位平台是8个字节。
二、指针和指针类型
上文我们提到,在定义一个指针变量时int* p,这个*表示p是个指针变量,int表示指针变量所指向的地址存放的内容是一个整型。那么int*其实就是指针变量的类型。
下面是一些指针定义的示例:
char *pc = NULL; int *pi = NULL; short *ps = NULL; long *pl = NULL; float *pf = NULL; double *pd = NULL;
从中我们可以发现,指针的定义方式是type *,不同type定义的指针表示该指针存放的变量的类型是相对应type。
char* 类型的指针是为了存放 char 类型变量的地址。
short* 类型的指针是为了存放 short 类型变量的地址。
int* 类型的指针是为了存放 int 类型变量的地址。
为了更好地理解指针类型及其指针类型的意义,我们先来看两个指针使用的实例:
2.1指针加或减上一个整数代码如下(示例):
int main() { int n = 10; char* pc = (char*)&n; int* pi = &n; printf("n的地址 :%pn", &n); printf("pc指针内容:%pn", pc); printf("pc指针+1 :%pn", pc + 1); printf("pi指针内容:%pn", pi); printf("pc指针+1 :%pn", pi + 1); return 0; }
运行结果:
从运行结果可以看出,不论是什么类型的指针变量,其内容确实是一个和所指向变量的地址相同的一个地址。
但当同样内容不同类型的指针加上一个整数1后,它们所指向的地址则发生了不同的变化:
char型指针+1后,地址只增加了1个字节;int型指针+1后,地址则增加了4个字节!
这说明:指针的类型决定了指针向前或者向后走一步有多大(距离)。
这就是指针类型的一个重要意义!
2.2指针的解引用代码如下(示例):
int main() { int n = 0x11223344; char *pc = (char *)&n; int *pi = &n; *pc = 0; //重点在调试的过程中观察内存的变化。 *pi = 0; //重点在调试的过程中观察内存的变化。 return 0; }
调试过程中的结果:
1.执行 int n = 0x11223344;后计算机给n分配了一个0x0053f944的地址,里面存了我们初始化的0x11223344:
2.pc和pi都是存放n地址的指针变量,下面依次执行*pc = 0;和*pi = 0。
3.这是执行*pc = 0;后地址0x0053f944内存放数值发生的变化,我们发现,在4个字节中只有1个字节发生了变化,变为0。
4.这是执行*pi = 0;后地址0x0053f944内存放数值发生的变化,我们发现,在4个字节中所有字节发生了变化,变为0。
总结:
指针的类型决定了,对指针解引用的时候有多大的权限(能 *** 作几个字节)。
比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节。
这是指针类型的另一个重要意义!
三、野指针
野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)。
那么为什么会出现野指针呢?
指针未初始化
int main() { int *p;//局部变量指针未初始化,默认为随机值 *p = 20; return 0; }
指针越界访问
int main() { int arr[10] = {0}; int *p = arr; int i = 0; for(i=0; i<=11; i++)//数组内只有10个元素,却循环了11次,指针变量访问了数组外的地址 { //当指针指向的范围超出数组arr的范围时,p就是野指针 *(p++) = i; } return 0; }
指针指向的空间释放
详见后续的动态内存管理,此处以局部变量举个例子
int* test()//因为返回的数据是一个地址,所以函数类型定义成一个指针int* { int n = 10; return &n; } int main() { int* p = test(); *p = 20; printf("%dn", *p); }
这里也可能出现野指针,因为n是一个在test()函数内的局部变量,test()函数返回的n的地址在出了test()函数之后就被销毁了。
基于以上野指针出现的原因,如何规避野指针呢?
指针初始化小心指针越界指针指向空间释放及时置NULL避免返回局部变量的地址指针使用之前检查有效性
int main() { int *p = NULL;//当你不知道*p放什么的时候,初始化成一个空指针NULL //但是初始化成一个空指针后指针是没法用的,所以如果有初始化成空指针的指针,需要在使用前检车有效性 int a = 10; p = &a; if(p != NULL)//指针使用之前检查有效性 { *p = 20; } return 0; }
四、指针运算
此处主要涉及三种运算:
1.指针± 整数
2.指针-指针
3.指针的关系运算
其中,指针±整数在前文已经提及,此处不再赘述。
2.指针-指针
两个同类型指针相减,得到的结果是两个指针之间的元素的个数。
下面是一个能够实现求字符串长度的函数,就是运用了指针-指针的特性。
int my_strlen(char *s) { char *p = s; while(*p != '' ) p++; return p-s; }
关于这个函数,可以参见下面博客中的方法三:
https://blog.csdn.net/qq_44875714/article/details/122648076?spm=1001.2014.3001.5502
3.指针的关系运算
int main() { int arr[10] = { 0 }; int i = 0; for (int* p = &arr[10]; p > &arr[0];) { i++; *--p = i; } }
以上的代码效果就是使得arr[]数组变为{10,9,8,7,6,5,4,3,2,1,},其中for语句中的判断部分就是使用了指针的关系运算,比较了p和arr[]中元素地址。
代码如果简化为下:
for(p = &arr[9]; p >= &arr[0];p--) { i++; *p = i; }
实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行。
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
五、指针和数组
我们现在已经知道,数组名其实就是数组首元素的地址。
但是有两种情况是例外,这两种情况如下:
sizeof(数组名),计算整个数组的大小,单位是字节。sizeof内部单独放一个数组名,数组名表示整个数组。&数组名,取出的是整个数组的地址。&数组名,数组名表示整个数组。但需要注意的是,整个数组的地址其实就是数组首元素的地址,因此数组的地址和数组首元素地址在值上是相同的,但意义不同
除此两种情况以外,数组名就是数组首元素的地址,因此其也可以存放在一个指针变量中,如:int arr[10] = {0};int *p = arr;既然可以把数组名当成地址存放到一个指针中,我们使用指针来访问数组元素就成为可能。
事实上,以下关于arr[]数组中第二个元素的表示都是等价的:
arr[2] == *(arr+2) == *(p+2) == *(2+p) == *(2+arr) == 2[arr] == p[2] == 2[p]
注意2[arr]和2[p]的表示方法,虽然看起来不可思议,但事实上是可行的。
六、二级指针
既然指针变量是存放其他变量的地址的变量,那指针变量必然也有一个地址,那是不是可以对指针变量定义一个指针来存放指针变量的地址呢?结论是肯定的,这就是指向指针的指针,也就是二级指针。
int main() { int a = 0; int* pa = &a; int** ppa = &pa;//二级指针 *pa = 10; printf("%dn", *pa); **ppa = 20;//二级指针的解引用 *** 作 printf("%dn", **ppa); }
**ppa = 20;
等价于*pa = 20;
等价于a = 20;
上面代码的输出结果:
总结
以上就是对指针的初步认识,有了这些认识,对于指针的初级使用会有一个大致的掌握。当然,指针绝不止这么简单,C语言的指针奥妙无穷,更为详细的笔记等我学了再记录。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)