NULL的含义(空指针)

NULL的本质是零,但这个零不代表数字零,表示的是内存单元的编号零

  • 计算机规定了,以零为编号的内存单元的内容不可读,不可写

    指针的重要性

  • 表示一些复杂的数据结构;

  • 快速地传递数据;
  • 使函数返回一个以上的值(函数中的return只能返回一个值);
  • 能够直接访问硬件;
  • 能够方便地处理字符串;
  • 是理解面向对象语言中引用的基础。

    指针的定义

    地址

  • 内存单元的编号;

  • 从零开始的非负整数

    指针

    指针就是地址,地址就是指针,地址是内存单元的编号
    指针变量是存放地址的变量指针和指针变量是两个不同的概念。但通常我们叙述时,会将指针变量简称为指针,实际它们的含义并不一样。
    指针的本质是一个操作受限(地址间只能相减)的非负整数

    指针的分类

    基本类型指针(重要)

    1. int main(void)
    2. {
    3. int *p; //p是变量的名字,int *表示p变量存放的是int类型变量的地址
    4. int i = 3;
    5. p = &i; /*
    6. 1.p变量保存了变量i的地址,因此p指向了i;
    7. 2.p变量不是i变量,i变量也不是p,修改p的值不会影响i的值,修改i的值也不会影响p的值
    8. 3.如果一个指针变量指向了某个普通变量,则
    9. *指针变量 等价于 普通变量
    10. ==>即*p 等价于 i
    11. *p就是取p所指向的地址的值,其数据类型为int。
    12. */
    13. return 0;
    14. }

    基本类型指针常见错误

    1. int main(void)
    2. {
    3. int i = 5;
    4. int *p;
    5. int *q;
    6. p = &i;
    7. //*q = p; //*q的数据类型为int,p的数据类型为int *
    8. //*q = *p; //q为垃圾值,程序无权限读写*q的值
    9. p = q; //q是垃圾值,q赋值给p,p也成为垃圾值,当读写*p时,程序也会报错
    10. printf("%d\n",*q);
    11. /* q的空间是属于本程序的,所以本程序可以读写q的内容,
    12. 但是如果q内部是垃圾值,则本程序不能读写*q的内容,
    13. 因为此时*q所代表的内存单元的控制权限并没有分配给本程序,
    14. 所以会执行printf时会报错
    15. */
    16. return 0;
    17. }

    * 星号的三种含义

  • 乘法运算符;

  • 定义指针变量,如:int * p;
  • 指针运算符,即*p,表示以指针变量p所指向的地址所对应的变量的值

    通过被调函数修改主调函数普通变量的值

    ```c
  1. 实参必须为该普通变量的地址;
  2. 形参必须为指针变量;
  3. 在被调函数中通过
    1. *形参名 = (修改值);
    2. 的方式修改主调函数相关变量的值。
    ```

    指针和数组

    指针和一维数组

    一维数组名

  • 一维数组名是个指针常量;
  • 存放的是一维数组第一个元素的地址

    下标和指针的关系

    如果p是个指针变量,则p[i]永远等价于*(p + i)

    确定一个一维数组需要的参数及其原因

    ——即如果一个函数要处理一个一维数组,需要接收该数组的哪些信息。 ```c /一个函数输出任何一个一维数组的内容/ void f(int * pArr,int len) { for(int i = 0;i < len;i++) {
    1. printf("%d",pArr[i]);
    } }

/一个函数输出任何一个字符串的内容/ void f(char pArr) { while(pArr != ‘\0’) { printf(“%c”,*pArr); pArr++; } }

/**原因如下*/

  1. 要输出一个数组,首先需要知道它的首地址;
  2. 数组没有特殊的结束符,因此完整输出时,需要知道它的长度;
  3. 字符串具有特殊的结束符’\0’,因此只需要知道它的首地址即可。
  1. <a name="WkmSv"></a>
  2. #### 指针变量的运算
  3. - 指针变量不能相加、不能相乘、也不能相除;
  4. - **如果两个指针变量指向的是同一块连续空间中的不同存储单元(即数组),这两个指针变量相减才有意义**。
  5. <a name="AhrJQ"></a>
  6. #### 指针变量的大小
  7. - **变量的地址是该变量所占内存空间的首地址**,所占空间大小由系统根据其数据类型分配;
  8. - **一个指针变量,无论它所指向的变量占几个字节,该指针变量本身都占用同样大小的字节空间**,具体大小视硬件平台而定。
  9. <a name="D77xN"></a>
  10. ### 指针和二维数组
  11. <a name="YLVrC"></a>
  12. ## 指针和结构体(重要)
  13. <a name="wN4yl"></a>
  14. ## 多级指针
  15. ```c
  16. #include <stdio.h>
  17. int main(void)
  18. {
  19. int i = 10;
  20. int* p = &i;
  21. int** q = &p;
  22. int*** r = &q;
  23. r = &p; //error,因为r是int***类型,r只能存放int**类型变量的地址
  24. printf("i = %d\n",***r);
  25. return 0;
  26. }

专题

动态内存分配(重点难点)

传统数组的缺陷

  • 数组长度必须实现指定,且只能是常整数,不能是变量

    1. 例子:
    2. int a[5]; //OK
    3. int len = 5;int a[len]; //error
  • 传统形式定义的数组,该数组的所占内存空间无法手动释放

    • 在一个函数运行期间,系统为该函数中的数组所分配的空间会一直存在,直到该函数运行完毕后,数组的空间才会被系统释放。
  • 一个数组的长度一旦定义,其长度无法再更改
    • 数组的长度不能在函数运行的过程中动态的扩大或缩小。
  • 传统方式定义的数组不能跨函数使用

    • A函数定义的数组,在A函数运行期间,可以被其它函数使用,但A函数运行完毕后,A函数中的数组将被释放,无法被其它函数使用。

      为什么需要动态分配内存

  • 动态数组(动态分配内存)很好的解决了传统数组的4个缺陷

  • 传统数组也叫静态数组

    malloc函数

    ```c 头文件:#include “stdlib.h”

原型: void* malloc(size_t size);

例子:char ptr = (char )malloc(10); // 分配10个字节的内存空间,用来存放字符

  1. - **size 为需要分配的内存空间的大小,以字节(Byte)计**;
  2. - malloc() 在**堆区**分配一块指定大小的内存空间,用来存放数据。**这块内存空间在函数执行完成后不会被初始化,它们的值是未知的**;
  3. - **分配成功时,返回指向该内存的首地址(每一份地址所占空间视数据类型而定),失败则返回 NULL**;
  4. - **函数的返回值类型是 void *,void 并不是说没有返回值或者返回空指针,而是返回的指针类型未知。所以在使用 malloc() 时通常需要进行强制类型转换**;
  5. - 由于申请内存空间时可能有,也可能没有,所以需要自行判断是否申请成功,再进行后续操作;
  6. - 如果 size 的值为 0,那么返回值会因标准库实现的不同而不同,可能是 NULL,也可能不是,但返回的指针不应该再次被引用;
  7. - 如果希望在分配内存的同时进行初始化,请使用 calloc() 函数;
  8. - **由于malloc()函数在堆区创建的,没有函数调用的入栈出栈过程,所以使用完后,动态分配的空间不会自动释放,当需要释放动态分配的内存时,调用free(p);即可**。
  9. ```c
  10. free(p); //表示把p所指向的内存给释放掉,而p本身的内存是静态的,只能在函数执行结束后由系统释放

动态数组(动态内存分配)的构造

  1. #include <stdio.h>
  2. int main(void)
  3. {
  4. int len;
  5. int* pArr;
  6. int i;
  7. pArr = (int*)malloc(4 * len); //动态构造了一个一维数组pArr,长度为len,类似于int pArr[len]
  8. for(i = 0;i < len;i++)
  9. {
  10. printf("%d\n",pArr[i]);
  11. }
  12. return 0;
  13. }

静态内存和动态内存的比较

静态内存

  • 静态内存是由系统自动分配,由系统自动释放;
  • 静态内存是在栈分配的。

    动态内存

  • 动态内存是由程序员手动分配,手动释放;

  • 动态内存是在堆分配的。

    跨函数使用内存的问题

    静态内存不能跨函数使用

    ```c

    include

void f(int* q) { int i = 5; q = &i; //将i的值赋给p }

int main(void) { int p; f(&p);
printf(“%d\n”,
p); //语法没有问题,但调用完f()函数后,p存储的是i的值, //而此时i的内存已经释放,printf()函数再次调用p时, //p的执行超出了函数该有的权限范围

  1. return 0;

}

//Note:动态内存分配由于使用的是堆分配,不存在入栈出栈的过程, //在函数执行后,不会自动释放,所以可以跨函数使用 ```