浅谈指针

1. 什么是指针

指针是一种数据结构(代表内存地址的无符号整数),使用它定义的变量叫做指针变量。打个比方:有个人让你去麦当劳帮我买个雪糕,那么麦当劳是这个变量a,但是那个人现在把麦当劳所在的地址写在纸上给你,那么这张纸就可以看做一个指向麦当劳的指针。

2. 为什么使用指针,什么情况下使用指针

  • 函数之间无法通过传参共享变量
    函数的形参变量属于被调用者,实参属于调用者,函数之间名字看见互相独立可以重名,函数之间的数据传递都是值传递(幅值、内存拷贝)。

  • 使用指针可以优化函数之间传参的效率
    使用指针可以不用进行数据的拷贝,直接使用内存的数据

  • 堆内存无法与标识符建立联系,只能配合指针

3. 如何使用指针

定义:类型* 变量名p;
  • 指针变量与普通变量使用方法有很大区别,一般以p结尾,与普通变量区分开。

  • 表示此变量是指针变量,一个*只能定义出一个指针变量,不能连续定义。

     int* p1,p2,p3; // p1是指针,p2,p3是int变量
     int *p1,*p2,*p3; // 三个指针变量
    
  • 类型表示的是存储是什么类型变量的地址,它决定当通过地址访问这块内存时访问的字节数。

  • 指针变量的默认值也是不确定,一般初始化为NULL(空指针)。

4. 使用指针要注意的问题

  • 空指针

     指针变量的值为NULL(大多数是0,也有特殊情况是1),这种指针变量叫空指针,空指针不能进行解引用(*指针变量),NULL被操作系统当作复位地址了(存储了系统重启所需要的数据),当操作系统察觉到程序访问NULL位置的数以的时就会向程序发送段错误的信号,程序就会死亡。
     空指针还被当作错误标志,如果一个函数的返回值是指针类型,实际返回的值是NULL,则说明函数执行失败或出错。
     在C语言代码中应该杜绝对空指针进行解引用,当使用来历不明的指针(调用者提供的)前应该先判断是否为NULL。
     #define NULL ((void*)0)
     if(NULL == p)
     {
     
     }
    
  • 野指针:指针变量的值是不确定的或都是无效的,这种指针叫野指针。

     使用野指针不一定会出问题,可能产生的后果如下:
     1、一切正常
     2、段错误
     3、脏数据
     虽然野指针不一定会出错,但野指针比空指针的危险更大,因为野指针是无法判断出来、也无法测试出来,也就意味着一旦产生无法杜绝。
     虽然野指针无法判断也无法测试出来,但是所有的野指针都是人为制造出来的,最好方法就是不生产野指针:
     1、定义指针变量时要初始化。
     2、不返回局部变量的地址。
     3、资源释放后,指向它的指针及时置空。
    

5. 指针和数组的关系

数组名就是个指针(常指针),数组名与数组首地址是映射关系,而指针是指向关系。
由于数组名就是指针,所以数组名可以使用指针的解引用运算符,而指针也可以使用数组的[]运算符。
使用数组当函数的参数时,数组会蜕变成指针,长度也就丢失,因此需要额外添加一个参数用来传递数组的长度。

6. 指针的算术运算

指针的本质就是个整数,因此从语法上来说整数能使用的运算符它都能使用。
不是所有的运算符对指针运算都是有意义的。
指针+整数 <=> 指针+宽度乘整数 向右移动
指针-整数 <=> 指针-宽度乘整数 向左移动
指针-指针 <=> 指针-指针/宽度 计算出两个指针之间相隔多少个元素。

7.指针的&和*

这里&是取地址运算符,*是间接运算符。
&a 的运算结果是一个指针,指针的类型是a 的类型加个*,指针所指向的类型是a 的类型,指针所指向的地址嘛,那就是a 的地址。
*p 的运算结果就五花八门了。总之*p的结果是p 所指向的东西,这个东西有这些特点:它的类型是p 指向的类型,它所占用的地址是p所指向的地址。

int a=12; int b; int *p; int **ptr;  
p=&a; //&a 的结果是一个指针,类型是int*,指向的类型是int,指向的地址是a 的地址。  
*p=24; //*p 的结果,在这里它的类型是int,它所占用的地址是p 所指向的地址,显然,*p 就是变量a。  
ptr=&p; //&p 的结果是个指针,该指针的类型是p 的类型加个*,在这里是int **。该指针所指向的类型是p 的类型,这里是int*。该指针所指向的地址就是指针p 自己的地址。  
*ptr=&b; //*ptr 是个指针,&b 的结果也是个指针,且这两个指针的类型和所指向的类型是一样的,所以用&b 来给*ptr 赋值就是毫无问题的了。  
**ptr=34; //*ptr 的结果是ptr 所指向的东西,在这里是一个指针,对这个指针再做一次*运算,结果是一个int 类型的变量。 
  • 赋值:指针变量 = 地址

     栈地址赋值:
     	int num = 0;
     	int* p = NULL;
     	p = &num;
     堆地址赋值:
     	int* p = NULL;
     	p = malloc(4);
    
  • 解引用(根据地址访问内存):*指针变量名 <=> 变量

     1、根据变量中存储的内存编号去访问内存中的数据,访问多少个字节要根据指针变量的类型。
     2、如果指针变量中存储的地址出错,此时可能发生段错误(这是赋值产生的错误)。
    

8. 指针和const配合

const int* p; 保护指针指向的数据,不能通过指针解引用修改内存的值。
int const *p; 同上
int * const p; 保护指针变量,指针变量初始化之后不能再显式的赋值。
const int *const p; 既不能修改指针的值,也不能修改内存的值。
int const * const p; 同上。

9. 什么是二级指针,什么情况下使用

二级指针就是指向指针的指针,就是地址的地址,打个比方,你有一个箱子,里面有你要的东西,你的钥匙能打开箱子,钥匙就是一级指针,你要用钥匙打开别的箱子去拿到开你要的东西的箱子的钥,那你手上的钥匙就是二级指针,如此类推…
在这里插入图片描述

那什么时候需要用到二级在呢,一般来说,当你需要共享一级指针变量或者函数时你就需要用到二级指针来进行操作,或者你需要传递一级指针数组或者其他类型的数据结构时就需要用到二级指针来进行操作。总的来说和一级指针用法类似。

10. 函数指针

可以把一个指针声明成为一个指向函数的指针。
int fun1(char *,int);
int (*pfun1)(char *,int);
pfun1=fun1;
int a=(*pfun1)(“abcdefg”,7); //通过函数指针调用函数。
可以把指针作为函数的形参。在函数调用语句中,可以用指针表达式来作为实参。

    int fun(char *);  
    inta;  
    char str[]="abcdefghijklmn";  
    a=fun(str);  
    int fun(char *s)  
    {  
        int num=0;  
        for(int i=0;;)  
        {  
            num+=*s;s++;  
        }  
        return num;  
    }  

这个例子中的函数fun 统计一个字符串中各个字符的ASCII 码值之和。前面说了,数组的名字也是一个指针。在函数调用中,当把str作为实参传递给形参s 后,实际是把str 的值传递给了s,s 所指向的地址就和str 所指向的地址一致,但是str 和s 各自占用各自的存储空间。在函数体内对s 进行自加1 运算,并不意味着同时对str 进行了自加1 运算。

11. 数组指针

int (*p)[3]; //首先从P 处开始,先与*结合,说明P 是一个指针然后再与[]结合(与"()"这步可以忽略,只是为了改变优先级),说明指针所指向的内容是一个数组,然后再与int 结合,说明数组里的元素是整型的.所以P 是一个指向由整型数据组成的数组的指针  

数组指针,指的是数组名的指针,即数组首元素地址的指针。即是指向数组的指针。

int array[10]={0,1,2,3,4,5,6,7,8,9},value;  
value=array[0]; //也可写成:value=*array;  
value=array[3]; //也可写成:value=*(array+3);  
value=array[4]; //也可写成:value=*(array+4);

上例中,一般而言数组名array 代表数组本身,类型是int[10],但如果把array 看做指针的话,它指向数组的第0 个单元,类型是int* 所指向的类型是数组单元的类型即int。因此array 等于0 就一点也不奇怪了。同理,array+3 是一个指向数组第3 个单元的指针,所以(array+3)等于3。其它依此类推。

12. 指针数组

优先级:()>[]>*

int *p[3]; //首先从P 处开始,先与[]结合,因为其优先级比*高,所以P 是一个数组,然后再与*结合,说明数组里的元素是指针类型,然后再与int 结合,说明指针所指向的内容的类型是整型的,所以P 是一个由返回整型数据的指针所组成的数组  

数组元素全为指针的数组称为指针数组。

13. 结构体指针

1. 结构体指针就是指向结构体变量的指针;
2. 如果一个指针变量中保存了结构体变量的首地址,那么这个指针变量就指向该结构体变量.
3. 如果一个指针变量中保存了结构体变量的首地址,那么这个指针变量就指向该结构体变量.
4. 通过结构体指针即可访问该结构体变量,这与数组指针和函数指针的情况是相同的 结构指针变量说明的一般形式为:

  • struct 结构体名 *结构体指针变量名
  • struct student *p = &Boy; //假设事先定义了 struct student Boy;
    struct MyStruct  
    {  
        int a;  
        int b;  
        int c;  
    };  
    struct MyStruct ss={20,30,40};  
    //声明了结构对象ss,并把ss 的成员初始化为20,30 和40。  
    struct MyStruct *ptr=&ss;  
    //声明了一个指向结构对象ss 的指针。它的类型是MyStruct *,它指向的类型是MyStruct。  
    int *pstr=(int*)&ss;  
    //声明了一个指向结构对象ss 的指针。但是pstr 和它被指向的类型ptr 是不同的。  

ptr->a; //指向运算符,或者可以这们(*ptr).a,建议使用前者
ptr->b;
ptr->c;
*pstr; //访问了ss 的成员a。
*(pstr+1); //访问了ss 的成员b。
*(pstr+2) //访问了ss 的成员c。

14. 结构体成员指针

当定义一个结构体,结构体成员中有指针的时候,那个成员就叫做结构体成员指针。
含有指针成员的结构体初始化的时候,必须给指针成员给一个明确的地址。注意:给指针成员初始化的时候,要么给其一个地址,比如是数组数组地址的时候,就可以通过指针来操作数组,也可以一个变量的地址;还可以给其新建一段内存,就是malloc一段内存,内存的地址由系统决定。

15. 指针和堆内存配合

什么是堆内存呢
堆:数据无序的顺序存储在内存中,它的申请和释放受程序员的控制。
堆内存的大小理论上等于物理内存的大小。
堆内存只能与指针配合使用(无法取名字)。
堆内存的释放是受控制的,一般程序中长期使用的数据都会从数据库、文件中读取到堆内存中。
堆内存归程序员管理,如果程序员的水平有限可能会出现内存泄漏、碎片等问题。
堆内存使用麻烦,需要借助标准库函数(头文件),申请(计算大小)、使用(与指针配合)、释放(不能重复放、指针要置空)。

指针需要和堆内存配合使用,如果指针没有申请内存,那它只能继续指向而不能进行输入,关于指针我们需要知道:

  1. 指针变量也是一种变量,占有内存空间,用来保存内存地址
  2. 指针与内存紧密相连,没有内存指针没有意义
  3. 对指针的操作实际上是对内存间接操作
  4. 指针变量和它指向的内存块是两个不同的概念

当使用malloc首次申请内存时,malloc会向操作系统提出申请,操作系统会一次向malloc分配33页(4096byte)内存给它管理,malloc会再分配若干字节给程序,接下来再分配内存时,malloc会从剩余的内存中分配给程序。
当获取到malloc分配的内存的地址时,理论上来说是可以越界的,直到超过33页。
malloc在管理内存时记录一些维护信息,就在每块申请到的内存的后面的8个字节(两个指针,一个指向前一块内存,一个指向接下来要分配的内存),这些维护停下被破坏后不会立即出错,但是会影响后面的申请和释放。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值