@toc 编程
在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向 points to)存在电脑存储器中另外一个地方的值。因为经过地址能找到所需的变量单元,能够 说,地址指向该变量单元。所以,将地址形象化的称为“指针”。意思是经过它能找到以它为地址的内存单元数组
那咱们就能够这样理解:markdown
内存:编程语言
指针ide
指针是个变量,存放内存单元的地址(编号)。函数
那对应到代码:学习
#include <stdio.h> int main() { int a = 10;//在内存中开辟一块空间 int *p = &a;//这里咱们对变量a,取出它的地址,可使用&操做符。 //将a的地址存放在p变量中,p就是一个之指针变量。 return 0; }
总结∶指针就是变量,用来存放地址的变量。(存放在指针中的值都被当成地址处理)。3d
思考如下的问题∶
(1)一个小的单元究竟是多大 ? (1个字节)指针
(2)如何编址 ?调试
通过仔细的计算和权衡咱们发现一个字节给一个对应的地址是比较合适的。
对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的是产生一个电信号正电 / 负电(1或者0)
那么32根地址线产生的地址就会是∶
00000000 00000000 00000000 00000000
0000000 00000000 00000000 00000001
…….
11111111 1111111111111111 11111111这里就有2的32次方个地址。 每一个地址标识一个字节,那咱们就能够给(2 ^ 32Byte == 2 ^ 32 / 1024KB == 2 ^
32 / 1024 / 1024MB == 2 ^ 32 / 1024 / 1024 / 1024GB == 4GB)4G的空闲进行编址。
一样的方法,那64位机器,若是给64根地址线,那能编址多大空间,本身计算。
这里咱们就明白 :
(1)在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,因此一个指针变量的大小就应该是4个字节。
(2)那若是在64位机器上,若是有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址。
总结 :
(1)指针是用来存放地址的,地址是惟一标示一块地址空间的。
(2)指针的大小在32位平台是4个字节,在64位平台是8个字节。
咱们先看这样一段代码的执行效果:
在32位 (x32)系统下
:
能够看到无论什么类型的指针,大小都是4,这时候咱们内心可能产生疑问,为何大小都是4,还要区分不一样类型的指针,那这种区分是否是没有意义的?不一样类型的指针是否是能够互用?为了验证这些问题,请看如下代码及运行结果。
咱们能够先屏蔽其它代码内容,保留前三行内容,再加上 * p = 0; 即调用指针来更改指针指向地址中的内容。经过F10调试,打开窗口-- - 内存监视器(相似变量监视窗口),能够看到a的内容确实改变了
如今咱们*将char pc来重复上面的操做,看可否实现相应变动指针指向地址内容的功能。
经过F10调试,打开窗口-- - 内存监视器(相似变量监视窗口),咱们发现这里仅前两个变成了00,后面几位没有变化。**
经过这个咱们能够发现指针的类型仍是意义的,其意义在于解引用操做时,对字节的操做数量不一样。
好比int 类型的指针,能够改动4个字节的内容,而char 类型的指针只能更改一个字节的内容。
指针的意义1:指针的解引用
总结:指针类型决定了指针进行解引用操做的时候,可以访问空间的大小
举例:
int p; p可以访问4个字节
char p; p可以访问1个字节
double p; p可以访问8个字节
指针的意义2:指针 + - 整数
看下面这个例子:pa + 1 实际地址 + 4, pc + 1 实际地址 + 1
总结:指针的类型决定了指针向前或者向后走一步有多大(距离、指针的步长)。
举例:
int p; p + 1 – > 日后跳4字节
char p; p + 1 – >日后跳1字节
double* p; p + 1 – > 日后跳8字节
思考:那么指针这两个意义的价值是什么呢?
把arr[10]数组全部元素依次加1 :↓↓↓↓
若是咱们将int p = arr; 改为char pc = arr;会发生什么呢?
是否是只会将10个字节改为1,而咱们数组int arr[10]有40个字节,10个字节的大小至关于2个半int类型的大小,咱们能够打开内存窗口调试,看一下是否是这样的状况。
只改了前十个字节
野指针概念:野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
野指针成因:
1.指针未初始化
#include<stdio.h> int main() { int a;//局部变量不初始化,默认是随机值 int* p;//局部的指针变量不初始化,默认也会给随机值 *p = 20;//这时候对指针进行解引用操做,实际上并不知道更改的内存是在哪里 return 0; }
2.指针越界访问
#include<stdio.h> int main() { int arr[10] = { 0 }; int* p = arr; int i = 0; for (i = 0; i < 12; i++) { //当指针指向的范围超出数组arr的范围时,就越界访问了,此时p就是野指针 *(p++)=1; } return 0; }
3.指针指向的空间释放
#include<stdio.h> int* test() { int a = 10; return &a; } //1.a 建立的空间在函数结束的时候返回给系统了,不属于当前程序的内容 int main() { int* p = test(); //2.这时候经过*p 去访问一个返还系统的空间,该空间有可能已经存放其它内容了 //3.因此p是一个野指针 *p = 20; return 0; }
那么咱们如何可以避免野指针呢?
1.指针初始化
2.当心指针越界
3.指针指向空间释放,即置为NULL
int*pa=NULL; //空指针
4.指针使用以前检查有效性
指针运算有三种,分别为:
①指针 + -整数
②指针 - 指针
③指针的关系运算
下面对这三种运算进行详细的讲解:
#include<stdio.h> int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,10}; int i = 0; int sz = sizeof(arr) / sizeof(arr[0]); int* p = arr; for (i = 0; i < sz; i++) { printf("%d ", *p); //p = p + 1; //指针 + 整数 p++; } return 0; }
这里咱们使用的时指针 + 1,那么指针 + 2或指针 + 3是什么样子的呢?请看下面的举例;
指针除了能够 + 整数,也能够 - 整数,好比:
int*p=arr[sz-1]-->访问下标
练习:
解析:
咱们知道指针变量是用于存放地址的,那么指针 - 指针 就是 地址 - 地址,那指针 - 指针的结果又是什么呢?请看下面的例子:
打印 & arr[9] - &arr[0] 结果为9,表明从arr[0]到arr[9], 其中间有9个元素。
总结:指针 - 指针获得的结果是中间元素的个数
固然若是咱们将 & arr[9] - &arr[0] 的顺序调换如下,变成 & arr[0] - &arr[9],获得的又是什么呢?
结果是: - 9
因此若是咱们要获得元素的个数,应该用大地址 - 小地址。
看下面这个 错误作法:
指针 + - 要使他们都指向一个空间
练习:模拟strlen的功能函数 (第三种方法)-->见' 递归 '
int my_strlen(char* str) { char* start = str; char* end = str; while (*end != '\0') { end++; } return end - start; } int main() { char arr[] = "hello,world"; int len = my_strlen(arr); printf("%d", len); return 0; }
关系运算,简单来讲就是比较大小,有 > 、 >= 、 == 、 != 、 < 、 <=
看下面例子:
#define N_VALUES 5 float values[N_VALUES]; //表明右5个元素 float *vp; //指针+-整数;指针的关系运算 for (vp = &values[N_VALUES]; vp > &values[0];) { *--vp = 0; }
将上面代码改造一下↓↓↓↓↓
for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--) { *vp = 0; }
实际在绝大部分的编译器上是能够顺利完成任务的,然而咱们仍是应该避免这样第二种写,由于标准并不保证它可行。
标准规定∶
容许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,可是不容许与指向第一个元素以前的那个内存位置的指针进行比较。
这句话看到这里,仍是不太明白,因此就画个图来演示一下:
(画图演示很重要)
数组名是什么?
在以前的学习中,咱们可能已经知道了数组名就是首元素的地址,这里咱们验证一下这个说法是否正确,请看下面这个例子:
能够看到 arr 和& arr[0] 结果是同样,说明 “数组名就是首元素的地址” 是正确的!
可是,数组名就一直是首元素地址吗?
数组名在绝大多数状况下是首元素地址,但有两个例外:
一、& arr --- &数组名 - 数组名不是首元素的地址,数组名表示整个数组 &
数组名 , 取出的是整个数组的地址。
整个数组的地址和首元素地址有什么区别呢?
二、sizeof(arr)----sizeof(数组名)-- - 数组名表示的整个数组–sizeof(数组名)计算的是整个数组的大小。
咱们回到刚刚说的问题:整个数组的地址和首元素地址有什么区别呢?
在打印的时候咱们发现打印出来的值是相等的。
解析:
举例:咱们让它们都进行 + 1, 首元素地址加1就到了第二个元素的地址,而数组地址 + 1则是直接跳过该数组
上面咱们说了 数组名就是首元素的地址,那么咱们经过使用指针来访问数组就成为了可能:
#include<stdio.h> int main() { int arr[] = { 1,2,3,4,5,6,7,8,9,10 }; int* p = arr; int i = 0; int sz = sizeof(arr) / sizeof(arr[0]); for (i = 0; i < sz; i++) { printf("&arr[%d] = %p <===> p+%d = %p\n", i, &arr[i], i, p + i); } return 0; }
p + i其实计算的是数组arr下标为i的地址。那么咱们就能够直接经过指针来访问数组,好比:
指针变量也是变量,是变量就有存储空间,就有地址,那么指针变量的地址存放在哪里那?
这就是二级指针,指向内容是存放地址的指针。
一般咱们所说的指向数组的指针是一级指针。
#include<stdio.h> int main() { int a = 10; int* pa = &a;//pa 一级指针 int** ppa = &pa;//ppa 二级指针 ......后面还有 三级指针、四级指针....n级指针 return 0; }
这里咱们进行画图演示:
好了,如今咱们对二级指针应该理解清楚了,可是二级指针怎么应用呢?
若是咱们对 ppa 进行解引用操做 * ppa就 能找到 pa 也就是a的地址,再次进行解引用操做** ppa,就能找到a了!
以下图:
在咱们学习指针和数组的概念时,会接触到两个这样的概念
1.指针数组-- - 数组-- - 存放指针的数组
2.数组指针-- - 指针
那么咱们怎么来理解1.指针数组呢?咱们来看下面的这个问题。
#include<stdio.h> int main() { int a = 10; int b = 20; int c = 30; int* pa = &a; int* pc = &c; //这里只有三个变量,咱们建立了三个指针分别存放它们的地址 //假若有10个变量,咱们要一口气建立10个指针来存放它们的地址吗? return 0; }
这个时候咱们就会思考,这10个指针变量能不能利用数组的方式来建立呢,就跟10个int 类型的变量同样,经过建立一个 int arr[10]就能实现。
整型数组-- - 存放整型
字符数组-- - 存放字符
指针数组-- - 存放指针
按照这个思路,咱们建立一个指针数组 int* arr[3] = { &a,&b,&c };
咱们能够经过指针数组访问其内容并打印: