【C语言】一级、二级指针详解

一、一级指针

1.作用

用来存放变量在内存中的地址
使用指针可以间接访问地址中的变量值

2.定义指针

类型名 *指针的名字;

指针本身也是个变量,只是该变量比较特殊,它里面存放别人的地址
下面是初始化指针,以及给指针赋值

int a = 1;
int b = 2;
int *p = &x;  //定义了指针p,p里面存放变量a在内存中地址(p指向a的地址,p指向a) 
p = &y;
int *q;
q = &a;

3.使用指针

解引用:获取地址中保存的内容
取地址:获取变量在内存中的地址
解引用和取地址互为逆运算

第一种:通过指针访问变量的值 --》解引用
第二种:通过指针修改变量的值

野指针

定义了指针,但是指针没有明确的指向(指针指向哪里不知道),这种指针就叫做野指针
危害:段错误

解决方法:
方法一:定义指针的时候给指针初始化

int a = 1;
int *p;  指针悬空
p = &a;

方法二:定义指针的时候先让指针指向NULL,后面再改变指针的指向

  int a = 1;
  int *p = NULL; //NULL里面是不可以存放任何数据的  不太让指针悬空
  p = &a;

方法三:定义指针的时候给指针分配堆空间(动态分配内存)

栈空间(栈内存)
堆空间(堆内存)
内存泄漏:一直申请堆内存,但是都没有释放,导致堆内存越来越少,最后没有可以使用的堆内存
堆内碎块(磁盘碎片):频繁地分配和释放不同大小的堆空间会导致堆内碎块

  #include <stdlib.h>
  void *malloc(size_t size); //分配堆空间
         返回值:分配的堆空间的首地址
           参数:size --》需要分配多少字节的堆空间
  void free(void *ptr);  //释放堆空间
           参数:ptr --》之前申请的堆空间首地址
  void *calloc(size_t nmemb, size_t size); //申请分配nmemb块内存,每个的大小是size
         返回值:分配堆空间的首地址
           参数:nmemb --》申请多少块内存区域
                 size --》申请的内存区域每一个的大小
  void *realloc(void *ptr, size_t size);  //重新分配堆内存
         返回值:分配的新的堆空间的首地址
           参数:ptr --》原来分配的堆空间首地址
                 size --》新的堆空间的大小
  int *p = malloc(10*sizeof(int))
  int *q = calloc(10,sizeof(int));  //申请10个内存,每个大小是4个字节
  free(p);

NULL介绍(空指针)

#define NULL  0x0000 0000 
NULL表示的内存中的0x0000 0000

4.指针的大小

跟指针的类型没有任何关系,跟操作系统的位数有关

在32位系统上,所有的指针大小都是4个字节
在64位系统上,所有的指针大小都是8个字节

指针用来存放变量的首地址,而在64位系统上所有的地址长度大小都是64位

5.指针的运算

  • 取地址
  • 解引用
  • 加法

一个指针加上另外一个指针没有任何实际意义
用指针去加一个整数,指针做加法,加的是类型的大小

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
    int n1 = 11;
    int n2 = 22;
    double n3 = 12.3;
    char n4 = '@';
    int *p1 = &n1;
    int *p2 = &n2;
    double *p3 = &n3;
    char *p4 = &n4;

    //p1+p2; //编译没有错,但是毫无意义,就是把地址值当成数字相加
    //有意义:指针加法
    //printf("p1原本存放的地址值是: %p\n",p1);
    //printf("p2原本存放的地址值是: %p\n",p2);
    printf("p3原本存放的地址值是: %p\n",p3);
    printf("p4原本存放的地址值是: %p\n",p4);
    
    //printf("p1+1存放的地址值是: %p\n",p1+1);
    //printf("p2+3存放的地址值是: %p\n",p2+3);
    printf("p3+1存放的地址值是: %p\n",p3+1);
    printf("p4+1存放的地址值是: %p\n",p4+1);
    return 0;
}

/*
执行结果:
    p3原本存放的地址值是: 0x7fffce42acf0
    p4原本存放的地址值是: 0x7fffce42ace7
    p3+1存放的地址值是: 0x7fffce42acf8
    p4+1存放的地址值是: 0x7fffce42ace8
*/
  • 减法

一个指针减去另外一个指针有实际意义

公式: p2-p1结果是: (p2-p1)/sizeof(指针类型)
本质上p2-p1表示p2和p1之间间隔了多少个元素

用指针去减一个整数,指针做减法,减的是类型的大小

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
    //一个指针减去另外一个指针有实际意义
    //跟数组结合,指针减去另外一个指针有意义
    int buf[5]= {33,44,55,66,777};
    double buf1[5] = {56.3,56.9,73.1,58.9,3656.8};
    char buf2[10] = "nihao";
    
    //定义两个指针,分别指向数组不同元素的首地址
    int *p1 = &buf[0];
    int *p2 = &buf[4];
    
    printf("p1存放的地址值是: %p\n",p1);
    printf("p2存放的地址值是: %p\n",p2);
    
    printf("p2-p1 is: %ld\n",p2-p1);
    printf("p1-1存放的地址值是: %p\n",p1-1);
    
    // double *p1 = &buf1[0];
    // double *p2 = &buf1[4];
    
    // printf("p1存放的地址值是: %p\n",p1);
    // printf("p2存放的地址值是: %p\n",p2);
    
    // printf("p2-p1 is: %ld\n",p2-p1);
    // printf("p1-1存放的地址值是: %p\n",p1-1);
    
    // char *p1=&buf2[0];
    // char *p2=&buf2[8];
    
    // printf("p1存放的地址值是: %p\n",p1);
    // printf("p2存放的地址值是: %p\n",p2);
    
    // printf("p2-p1 is: %ld\n",p2-p1);
    // printf("p1-1存放的地址值是: %p\n",p1-1);
        
    return 0;
}
  • ++和解引用配合使用

*p++ 先解引用p,然后p加1

(*p)++ 先解引用p,然后把解引用的值加1
*++p 先将++用于p,然后解引用
++*p 先解引用,然后把结果加1

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
    int buf[5]={345,75,576,45,78};
    int *p = &buf[0];
    
    //*p++; //拆分两步  第一步 *p  第二步:p=p+1
    //printf("没有计算之前p存放的地址: %p\n",p);
    //printf("验证*p++计算过程: %d\n",*p++);
    //printf("计算之后p存放的地址: %p\n",p);
    
    //(*p)++; //拆分两步  第一步 *p  第二步:*p=(*p)+1
    // printf("没有计算之前p存放的地址: %p\n",p);
    // printf("验证(*p)++计算过程: %d\n",(*p)++);
    // printf("计算之后p存放的地址: %p\n",p);
    // printf("buf[0] is: %d\n",buf[0]);
    
    //*++p; //拆分两步  第一步 p=p+1  第二步:*p
    // printf("没有计算之前p存放的地址: %p\n",p);
    // printf("验证*++p计算过程: %d\n",*++p);
    // printf("计算之后p存放的地址: %p\n",p);
    
    //++*p; //拆分两步  第一步 *p  第二步:  ++第一步的值
    printf("没有计算之前p存放的地址: %p\n",p);
    printf("验证*++p计算过程: %d\n",++*p);
    printf("计算之后p存放的地址: %p\n",p);
    printf("buf[0] is: %d\n",buf[0]);
    return 0;
    
}
  • 指针比较大小

用来判断两个指针是否指向同一块内存区域

#include <stdio.h>
 
int main()
{
    int n1 = 45;
    int n2 = 45;
    
    int *p1 = &n1;
    int *p2 = &n2;
    
    if(p1 == p2) //判断p1和p2存放的地址值是否一样
    {
        printf("地址相同!\n");
    }
    else
    {
        printf("地址不相同!\n");
    }
        
    if(*p1 == *p2) //判断p1和p2地址中对应的值是否相同
    {
        printf("值是相同的!\n");
    }
    
    if(p1 > p2)
        printf("n1存储的位置是在n2的靠后面!\n");
    else if(p1 < p2)
        printf("n1存储的位置是在n2的靠前面!\n");
    
    return 0;
}

6.void *类型的指针

万能指针,通用类型的指针
特点:可以跟任何其它类型的指针强转
比如:应用举例:C语言的库函数的使用
例子一:void *作为函数的返回值

 void *malloc(size_t size);   //通用性,随便你定义什么指针都可以
 char *malloc(size_t size);  //瞎编的,局限性,返回char类型指针
 int *malloc(size_t size);   //瞎编的,局限性,返回int类型指针
 char *p=(char *)malloc(10);
 double *q=(double *)malloc(80);

例子二:void *作为函数的参数

 ssize_t read(int fd, void *buf, size_t count);
           参数:buf --》存放读取到的内容
           你从文件读取数据,是不是有可能读取到各种各样不同类型的数据
                比如:读取文件数据是个整数--》应该保存到整型变量的地址中
                            int a;
                            read(int fd, &a, size_t count);
                      读取文件数据是个字符串--》应该保存到数组
                            char buf[20];
                            read(int fd, buf, size_t count);
                      读取文件数据是个小数--》应该保存到浮点型变量的地址中
                            float b;
                            read(int fd, &b, size_t count);

总结:好处在于把指针类型的选择权留给了程序员

二、二级指针

1.概念

也是个指针,该指针用来存放另外一个一级指针在内存中的地址
二级指针解引用一次,变成一级指针

2.定义二级指针

   int a=88;
   int *p=&a;
   int **q=&p; 
   char b='#';
   char *p=&b;
   char **q=&p;

3.使用二级指针

   *q : 二级指针解引用一次,结果是*&p-->p
   **q: 二级指针解引用两次,结果是**&p-->*p

4.二级指针的运算(考察最多的是一级指针的运算,偶尔会出现二级指针的运算)

加法:二级指针加一个整数

   char **p;  p+1;
   int **q;   q+1;

减法:二级指针减一个整数

二级指针做加减法,加减的是指针(一级指针)的大小(64位系统加减8的倍数 32系统加减4的倍数)

5.数组名需要注意的地方

第一点:数组名是个指针,但是sizeof求大小又不当成指针

   int buf[10];
   sizeof(buf);  //40字节

第二点:数组名前面取地址,是个数组指针

char buf[10]="hello";
char *p1=buf;
char **q1=&p1;  //写成char **q1=&buf; 

第三点: 数组名不可以作为左值,但是普通指针可以左值

 char buf[10]="hello"
 char *p=buf;
 p=p+1;     //没有问题
 buf=buf+1; //有问题
  • 25
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值