当前位置 博文首页 > CW_qian的博客:7月27日笔记C语言基础指针0

    CW_qian的博客:7月27日笔记C语言基础指针0

    作者:[db:作者] 时间:2021-08-25 21:45

    内存地址

    • 字节:字节是内存的容量单位,英文称为 byte,一个字节有8位,即 1byte = 8bits
    • 地址:系统为了便于区分每一个字节而对它们逐一进行的编号,称为内存地址,简称地址。

    基地址

    • 单字节数据:对于单字节数据而言,其地址就是其字节编号。
    • 多字节数据:对于多字节数据而言,地址是其所有字节中编号最小的那个,称为基地址。

    取址符

    • 每个变量都是一块内存,都可以通过取址符 & 获取其地址??%p
    • 例如:

    int?a = 100;

    printf("整型变量 a 的地址是: %p\n", &a);

    char?c = 'x';

    printf("字符变量 c 的地址是: %p\n", &c);

    double?f = 3.14;

    printf("浮点变量 f 的地址是: %p\n", &f);

    • 注意:
      • 虽然不同的变量的尺寸是不同的,但是他们的地址的尺寸确实一样的。
      • 不同的地址虽然形式上看起来是一样的,但由于他们代表的内存尺寸和类型都不同,因此它们在逻辑上是严格区分的。

    指针基础

    • 指针的概念:
      • 地址。比如 &a 是一个地址,也是一个指针,&a 指向变量 a。
      • 专门用于存储地址的变量,又称指针变量。
    • 指针的定义:

    int????*p1; // 用于存储 int ?型数据的地址,p1 被称为 int ?型指针,或称整型指针

    char???*p2; // 用于存储 char 型数据的地址,p2 被称为 char 型指针,或称字符指针

    double?*p3; // 用于存储double型数据的地址,p3 被称为 double 型指针

    • 指针的赋值:赋给指针的地址,类型需跟指针的类型相匹配。

    int?a = 100;

    p1 = &a; // 将一个整型地址,赋值给整型指针p1

    char?c = 'x';

    p2 = &c; // 将一个字符地址,赋值给字符指针p2

    double?f = 3.14;

    p3 = &f; // 将一个浮点地址,赋值给浮点指针p3

    • 指针的索引:通过指针,取得其指向的目标

    *p1 = 200; // 将 p1 指向的目标(即a)修改为200,等价于 a = 200;

    *p2 = 'y'; // 将 p2 指向的目标(即c)修改为'y',等价于 c = 'y';

    *p3 = 6.6; // 将 p3 指向的目标(即f)修改为6.6,等价于 f = 6.6;

    • 指针的尺寸
      • 指针尺寸指的是指针所占内存的字节数
      • 指针所占内存,取决于地址的长度,而地址的长度则取决于系统寻址范围,即字长
      • 结论:指针尺寸只跟系统的字长有关,跟具体的指针的类型无关

    void型指针

    • 概念:无法明确指针所指向的数据类型时,可以将指针定义为 void 型指针
    • 要点:
      1. void 型指针无法直接索引目标,必须将其转换为一种具体类型的指针方可索引目标
      2. void 型指针无法进行加减法运算
    • void关键字的三个作用:
      1. 修饰指针,表示指针指向一个类型未知的数据。
      2. 修饰函数参数列表,表示函数不接收任何参数。
      3. 修饰函数返回类型,表示函数不返回任何数据。
    • 示例:

    // 指针 p 指向一块 4 字节的内存,且这4字节数据类型未确定

    void?*p = malloc(4);

    // 1,将这 4 字节内存用来存储 int 型数据

    *(int?*)p = 100;

    printf("%d\n", *(int?*)p);

    // 2,将这 4 字节内存用来存储 float 型数据

    *(float?*)p = 3.14;

    printf("%f\n", *(float?*)p);


    野指针

    • 概念:指向一块未知区域的指针,被称为野指针。野指针是危险的。
    • 危害:
      1. 引用野指针,相当于访问了非法的内存,常常会导致段错误(segmentation fault)
      2. 引用野指针,可能会破坏系统的关键数据,导致系统崩溃等严重后果
    • 产生原因:
      1. 指针定义之后,未初始化
      2. 指针所指向的内存,被系统回收
      3. 指针越界
    • 如何防止:
      1. 指针定义时,及时初始化
      2. 绝不引用已被系统回收的内存
      3. 确认所申请的内存边界,谨防越界

    空指针

    ????????很多情况下,我们不可避免地会遇到野指针,比如刚定义的指针无法立即为其分配一块恰当的内存,又或者指针所指向的内存被释放了等等。一般的做法就是将这些危险的野指针指向一块确定的内存,比如零地址内存。

    • 概念:空指针即保存了零地址的指针,亦即指向零地址的指针。
    • 示例:

    // 1,刚定义的指针,让其指向零地址以确保安全:

    char?*p1 = NULL;

    int??*p2 = NULL;

    // 2,被释放了内存的指针,让其指向零地址以确保安全:

    char?*p3 = malloc(100); // a. 让 p3 指向一块大小为100个字节的内存

    free(p3); ??????????????// b. 释放这块内存,此时 p3 相当于指向了一块非法内存

    p3 = NULL; ?????????????// c. 让 p3 指向零地址


    指针运算

    • 指针加法意味着地址向上移动若干个目标
    • 指针减法意味着地址向下移动若干个目标
    • 示例:

    int??a = 100;

    int?*p = &a; // 指针 p 指向整型变量 a

    int?*k1 = p + 2; // 向上移动 2 个目标(2个int型数据)

    int?*k2 = p - 3; // 向下移动 3 个目标(3个int型数据)


    数组名涵义

    • 数组名有两个含义:
      • 第一含义是:整个数组
      • 第二含义是:首元素地址
    • 当出现以下情形时,那么数组名就代表整个数组:
      • 在数组定义中
      • 在 sizeof 运算表达式中
      • 在取址符&中
    • 其他任何情形下,那么数组名就代表首元素地址。即:此时数组名就是一个指向首元素的指针。
    • 示例:

    int?a[3]; ?????????????????// 此处,a 代表整个数组

    printf("%d\n", sizeof(a)); // 此处,a 代表整个数组

    printf("%p\n", &a); ???????// 此处,a 代表整个数组,此处为整个数组的地址

    int?*p = a; ??????// 此处,a 代表首元素 a[0] 的地址,等价于 &a[0]

    p = a + 1; ???????// 此处,a 代表首元素 a[0] 的地址,等价于 &a[0]

    function(a); ?????// 此处,a 代表首元素 a[0] 的地址,等价于 &a[0]

    scanf("%d\n", a); // 此处,a 代表首元素 a[0] 的地址,等价于 &a[0]

    ????????C语言只有在第一含义的场合下表现为数组,其他大部分场合都表现为首元素的地址,当数组表现为首元素地址时,实际上就是一个指向其首元素的指针。数组运算实际上就是指针运算。


    数组下标

    • 数组下标实际上是编译系统的一种简写,其等价形式是:

    ????????????????????????a[i] = 100; ?等价于 ?*(a+i) = 100;

    • 根据加法交换律,以下的所有的语句均是等价的:

    ??a[i] = 100;

    *(a+i) = 100;

    *(i+a) = 100;

    ??i[a] = 100;

    • 数组运算,等价于指针运算。

    字符串常量

    • 字符串常量在内存中的存储,实质是一个匿名数组
    • 匿名数组,同样满足数组两种涵义的规定
    • 示例:

    printf("%d\n", sizeof("abcd")); // 此处 "abcd" 代表整个数组

    printf("%p\n", &"abcd"); ???????// 此处 "abcd" 代表整个数组

    printf("%c\n", "abcd"[1]); // 此处 "abcd" 代表匿名数组的首元素地址

    char?*p1 = "abcd"; ????????// 此处 "abcd" 代表匿名数组的首元素地址

    char?*p2 = "abcd"?+ 1; ????// 此处 "abcd" 代表匿名数组的首元素地址


    char型指针

    ????????char型指针实质上跟别的类型的指针并无本质区别,但由于C语言中的字符串以字符数组的方式存储,而数组在大多数场合又会表现为指针,因此字符串在绝大多数场合就表现为char型指针。

    • 定义:

    ????????????????char?*p = "abcd";


    多级指针

    • 如果一个指针变量 p1 存储的地址,是另一个普通变量 a 的地址,那么称 p1 为一级指针
    • 如果一个指针变量 p2 存储的地址,是指针变量 p1 的地址,那么称 p2 为二级指针
    • 如果一个指针变量 p3 存储的地址,是指针变量 p2 的地址,那么称 p3 为三级指针
    • 以此类推,p2、p3等指针被称为多级指针
    • 示例:

    int?a = 100;

    int???*p1 = &a; ?// 一级指针,指向普通变量

    int??**p2 = &p1; // 二级指针,指向一级指针

    int?***p3 = &p2; // 三级指针,指向二级指针


    指针万能拆解法

    • 任意的指针,不管有多复杂,其定义都由两部分组成。
      • 第1部分:指针所指向的数据类型,可以是任意的类型
      • 第2部分:指针的名字

    • 示例:

    char???(*p1); ?????// 第2部分:*p1; 第1部分:char;

    char??*(*p2); ?????// 第2部分:*p2; 第1部分:char *;

    char?**(*p3); ?????// 第2部分:*p3; 第1部分:char **;

    char???(*p4)[3]; ?// 第2部分:*p4; 第1部分:char [3];

    char???(*p5)(int, float); // 第2部分:*p5; 第1部分:char (int, float);

    • 注解:
    1. 上述示例中,p1、p2、p3、p4、p5本质上并无区别,它们均是指针
    2. 上述示例中,p1、p2、p3、p4、p5唯一的不同,是它们所指向的数据类型不同
    3. 第1部分的声明语句,如果由多个单词组成,C语言规定需要将其拆散写到第2部分的两边

    const 型指针

    下一篇:没有了