文章更新,更加详细的介绍请看这篇:http://www.javashuo.com/article/p-bpizmwmt-go.htmlhtml
不少人不敢讲C的指针,有些人讲不清,有些人怕讲错。初生牛犊不怕虎,就让我讲讲。java
下面开始。程序员
1、指针的定义 数组
指针是内存单元的编号。内存单元是以字节为单位的。因此指针就是字节的编号。 函数
好比咱们的我的电脑,内存通常4GB吧,那么一共就有 : 4*1024*1024*1024 = 4294967296 字节,也就是4294967296个编号。一个字节拥有一个编号,spa
范围从 0 ~ 4294967296-1 。指针
画个图表示:(注意字节由8位bit组成,为了直观我没画出来)code
可是呢,通常咱们是用16进制来表示的这些编号,但效果都同样。orm
2、变量与内存的关系htm
如今咱们来看C 变量在内存中的样子。咱们使用自动变量(局部变量)来说解。
int a = 5; //假设a存储在编号为0x02开始的位置
说明:
内存能够存储数据,因此咱们把每一个字节当作是一个“箱子”。数据存入内存就比如在箱子里面放数据
可是C语言的不一样数据类型占用的字节数是不都同样的,因此,每种数据类型占的”箱子”的个数不都同样。
好比char型,只要一个字节就够了,因此一个字符只需一个“箱子”。
而int型须要4(通常是4个字节)个“箱子”才放得下。
double型则须要8个“箱子”。
来分析上图中用橙色框起来的4个字节的内存块,这里就存储了a这个变量。
咱们从4个方面去讨论这个内存块:
一、内存的数据
咱们的变量赋值为5,因此内存的数据就是 0000 0000 0000 0000 0000 0000 0000 0101 (大端模式)
每一个字节地址: 0x02 0x03 0x04 0x05
二、*内存的名字 (对于咱们的程序使用的内存来讲,并非每个内存块都有名字)
名字就是变量名a
三、内存的地址
变量a占用了4个字节,那么,哪个字节的地址,才是变量a的地址呢?答:第一个低地址字节的地址,也就是0x02
四、内存的宽度
这里的a变量占用了4个字节,这就是他的宽度。
这里提一下,为后面讲指针的宽度作铺垫 :)
3、定义和使用指针变量
什么是指针变量。咱们知道int类型变量用来存储整形值,十二、100三、9823......
那么,一样的道理,指针变量就是用来保存地址的变量。
定义指针变量:在指向的变量的类型上加个* ,以下:
int* p_int; //指向int类型变量的指针 double* p_double; //指向idouble类型变量的指针 int(*p_func)(int,int); //指向返回类型为int,有2个int形参的函数的指针 int(*p_arr)[3]; //指向含有3个int元素的数组的指针 struct Student *p_struct; //结构体类型的指针 int** p_pointer; //指向 一个整形变量指针的指针
既然咱们已经定义指针变量了,那么接下来就用指针变量来存储其它变量的地址。
取地址符号 &
#include<stdio.h> int Add(int a,int b); struct Student { int age; double score; char name[31]; }; int main(void) { int val_int =100 ; double val_double = 12.00; int arr[3]={1,2,3}; struct Student stu={ 19, 98.00, "Jack" }; int*p = NULL; /**********************************************************/ int* p_int = &val_int; double* p_double = &val_double; int(*p_func)(int,int) = &Add; //or =Add int(*p_arr)[3] = &arr; struct Student *p_struct = &stu; int** p_pointer = &p; return 0; } int Add(int a,int b) { return a+b; }
4、指针的属性和使用
我认为指针有2个属性: ①指针的值 ②指针的宽度
指针的值很好理解,好比第一个例子 int a = 5; int*p = &a; 那么p的值就是a的地址0x02
指针的宽度:由指针的类型决定。
若是说指针的值标记了某个数据在内存的起始位置,那么,指针的宽度就决定了从起始地址
对应的字节开始,日后还有多少个字节,也是属于这个内存数据的。
生活情景:“老王,去帮我去仓库拿个货,个人货从78号箱开始,而且有2个。” 因而老王取出 78和79号箱子的货物递给你
假如你对老王说:“老王,帮我去仓库取个人货,个人从78号箱开始,一共
有几个箱子,我也记不住了” “那我怎么取,取少了你就有损失了,取多了别人也不干了,你不要难为我啊”
这点个人另外一篇随笔也讲到了 .
#include<stdio.h> int main(void) { int a =256 ; int* p=&a; printf("%d\n", *((unsigned char*)p) ); //读取内存数据时,取的宽度的比存时的宽度小,数据缺失 return 0; }
#include<stdio.h> int main(void) { int arr[2] = {1,2}; arr[2] = 100; //ops! 访问越界 return 0; }
前面咱们讲了内存块的4点要素,第4点就是内存块的宽度,他是和指针的宽度是一一对应的。一个指针变量指向了这个内存块,
那么指针的宽度就是这个内存块的宽度。这也代表,咱们在使用指针的时候,不要越界,也不要取少,取少了取出的数据会不完整,
越界就更严重了,程序会挂掉的,由于你访问了不属于你的内存。
扩展:有木有 没有宽度的指针呢? 答:这个能够有 void* p;
void* 类型的指针对应与C# or java中的Object类型变量,它能够指向任何类型变量。 不过他只保存了内存的起点,没有保存宽度信息,若是你想
取出原来的数据,你必须作出正确的强制转换。
五:一对相反操做的运算符 * 和 &
& 取地址符 &a 就获取了a的指针,而后咱们就能够用int*p变量出承接这个地址 p = &a; 咱们就说p指向了a
* 解地址符 *操做符有一个很形象的动词 :解 。p保存了a的起始地址和延续宽度,那么,*p则是肯定起始地址,量出宽度,
获取这个内存块。 所以咱们能够经过a 的地址p去操做(读/写)这个内存块了。
p也是一个变量,他本身也有地址,这就长产生了指针的指针。
注意:我所说的指针的宽度并非说指针变量占用的内存大小,而是经过这个指针指向的内存块的宽度。指针的占用的字节数是必定的,通常状况下,指针变量都占用4个字节。上面图中我也画了4个字节。
6、为何要使用指针
你可能会问:我有变量名,为何还要绕一转,用指针去使用内存数据?
但并非这样,下面是2个例子。
①经过动态申请的内存是匿名的,没有名字,只能经过指向这块内存的指针去使用。
malloc() realloc() calloc() 这里不扩展了。
②一个经典的题目:用函数交换2个变量的值。
#include<stdio.h> void swap_2b(int a,int b); void swap_hack(int *pa,int *pb); void swap_normal(int*pa,int*pb); int main() { int a = 5; int b = 3; swap_2b(a,b); //Can`t swap; swap_normal(&a,&b); //OK swap_hack(&a,&b); //OK return 0; } //2b青年写的函数 void swap_2b(int a,int b) { int t; t=a; a=b; b=t; } //程序员写的函数 void swap_normal(int*pa,int*pb) { int t; t=*pa; *pa=*pb; *pb=t; } //黑客写的函数 void swap_hack(int *pa,int *pb) { *pa = *pa^*pb; *pb = *pa^*pb; *pa = *pa^*pb; }
咱们发现只有2b青年没用指针哈。
缘由很简单,C语言函数是按值传递的,2b青年写的代码之因此达不到效果是由于它操做的内存块错了。
不管他在函数里面怎么换a和b的值,main函数中的a和b都不会变。由于swap函数中的a和b是随函数调用新生成的变量,是副本,而不是源对象。
可是传递指针就不同了,由于内存数据的指针是独一无二的。一我的有惟一的身份证同样。
7、野指针和NULL指针
指针变量在使用前或者使用完,好的习惯是赋值为NULL ,NULL 是编号为0的字节的地址。指向NULL表示不指向任何变量。
NULL就像剑鞘,野指针就像暗箭,若是你不像被暗箭所伤,就让他归鞘。
最后:因为内容比较多,可能有许多地方表达不当,或有疏漏,错误,但愿你们及时指出,谢谢 : )