05_指针

1 指针的概述

1.1 指针的意义

1.1.1 指针的概念

        程序在计算机中运行的过程,称为进程;在Linux系统中的进程是资源管理的最小单位。在32位的系统中构建一个4G的虚拟内存空间,用于存储程序运行的所有数据,包含程序和变量、常量等数据,并且数据存储的最小单位是以字节(8位数据)为单位。每一个字节都设置序号:0 ~ 4G(0XFFFF FFFF),此时的序号就称为地址,也称为指针。

1.1.2 指针访问的意义

  1. 通过指针提高对内存空间的访问效率;
  2. 通过指针引用连续存储空间可以简化代码逻辑。
  3. 指针作为函数的返回值和参数的时候,实现多个数据的输入和输出等;
  4. 使用指针实现对物理寄存器进行访问,实现硬件设备编程。

1.2 指针的认识

1.2.1 指针的理解

1.2.2 指针的定义

        所谓的指针,指的是数据在内存空间存储的起始地址,也就是数据的指针;此时的指针指向整个数据的内存空间

        对于一个变量可以使用取地址运算符&得到整个变量的地址(指针),此时的指针是指针常量,作为指针常量可以定义相同类型的指针变量存储其指针常量的值

1.指针变量定义的语法格式

        存储类型 数据类型 * 指针变量名;

        数据类型:不表示指针变量的数据类型,表示的是指针指向空间的数据类型。

        存储类型:修饰的是指针变量本身的存储属性,可以用于存储其它任意属性的变量的地址。

        *:在定义指针变量的时候,是一个特殊的修饰符号,表示所定义的变量为指针变量。

2.指针的使用:

        运算符:

        &:取地址运算符,得到变量(存储空间)的地址;

        *: 作为运算符的时候,地址引用访问运算符,得到指针指向空间的数据;

        指针的值:空间起始地址编号

        指针指向的数据类型:数据的存储空间的大小和数据的表示形式。

#include <stdio.h>

int main()
{
        int a = 0x12345678;
        
        /* 在定义int类型的指针,并将指针的值初始化为&a; */
        int *p = &a;               /* *是特殊说明符号,表示的是指针变量的定义 */ 
            
        printf("a = %x, *(&a) = %x, *p = %x\n", a, *(&a), *p);
        *p = 0x87654321;            /* *是地址引用访问符,引用指针目标空间的数据 */
        printf("a = %x, *(&a) = %x, *p = %x\n", a, *(&a), *p);

        char *q = (char *)&a;
        printf("p = %p, q = %p\n", p, q);     /* 指针变量p和指针变量q的值相同,访问的是同一个地址 */
        printf("*q = %x, *p = %x\n", *q, *p);    /* 访问的数据内容不同 */

        /* 结论:由于指针变量p和指针变量q指向的数据类型不同,相同指针值访问到的数据不相同 */
        return 0;
}

2. 指针的运算

        对于指针运算的前提:只有在连续存储空间中的相同数据类型的指针之间运算才有意义,其余情况下的指针运算是没有意义的。

2.1 指针的算术运算

1.指针的加法运算

        指针 + 整型数据n:指针向高地址方向偏移n个元素,地址值的差值:sizeof(指针指向空间的数据类型) * n;

#include <stdio.h>

int main()
{
        int a = 123;
        char c = 'a';

        int *p = &a; 
        char *q = &c; 

        printf("p : %p, p+4 : %p\n", p, p+4);    /* int类型指针+4 ,差值相差16 */
        printf("q : %p, q+4 : %p\n", q, q+4);    /* char类型指针+4,差值相差4 */

}

2.指针的减法运算

        a.指针 - 整型数据n:指针向低地址方向偏移n个元素,地址差值:sizeof(指针指向空间的数据类型) * n;

        b.指针-指针:表示两个指针之间相差元素的个数;

3.++运算符

        a.后加加(p++):先取指针值,再对指针自加1运算(向高地址方向偏移1个元素);

                *(p++)  => *p++ :先去地址引用 *p,再对指针自加1运算 p++

        b.前加加(++p):先对指针自加1运算,再取指针值

4. --运算符

        a.后减减(p--):先取指针值,再对指针减1运算(向低地址方向偏移1个元素);

        b.前减减(--p):先对指针自减1运算,再取指针的值

2.2 指针的关系运算和逻辑运算

        对于两个指针的关系运算,可以用来判断两个指针空间的位置

                >、>=、<、<=、==、!=

                !、&&、||

if(p)     /* p != NULL 结果为true,否则p == NULL为false*/
if(!p)    /* p == NULL 结果为true,否则p != NULL为false */

3. 数据在内存存储

3.1 数据存储结构图

        数据在内存中的存储,在不同的系统中存储方式不同:由于内存的地址空间从低地址到高地址连续的,数据有高低位之分;根据数据在内存中存储顺序主要分为小端存储和大端存储两种方式:

        1.小端存储:数据的低位存储在内存的低地址端,数据的高位存储在内存的高地址端。

        2.大端存储:数据的低位存储在内存的高地址端,数据的高位存储在内存的低地址端。

数据存储实例图

 3.2 计算机内存存储方式判断

#include <stdio.h>

int main()
{
        int i;
        int a = 0x12345678;
        char *p = &a; 
    
        for (i = 0; i < sizeof(a); i++) {
                printf("0x%x\n", *p++);
        }   
}
注意:当结果先输出的是0x78说明计算机采用小端存储结果;当结果先输出的是0x12说计算机采用的大端存储结构;

4. 指针和数组

        对于数组在内存中开辟连续存储空间顺序存储数组中的每个元素;每一个元素的存储空间都有起始地址。所以使用指针对数组元素的访问。

4.1 一维数组和指针

4.1.1 一维数组和指针分析

#include <stdio.h>

int main()
{
        int i;
        int a[5] = {1,2,3,4,5};
        int *p = a;
    
        printf("a[i] : ");
        for (i = 0; i < 5; i++) {
                printf("%d", a[i]);
        }
        printf("\n");

        printf("*(a+i) : ");
        for (i = 0; i < 5; i++) {
                printf("%d", *(a+i));
        }
        printf("\n");
        
        printf("*(p+i) : ");
        for (i = 0; i < 5; i++) {
                printf("%d", *(p+i));
        }
        printf("\n");

        printf("p[i] : ");
        for (i = 0; i < 5; i++) {
                printf("%d", p[i]);
        }
        printf("\n");
        
        printf("*p : ");
        for (i = 0; i < 5; i++) {
                printf("%d", *p++);     /* 指针变量p可以自加运算,不能替换为指针常量arr */
        }
        printf("\n");
        return 0;
}

4.1.2 一维数组和指针练习

1.练习1

        以下程序运行结果为8,8

2.练习2

4.2 二维数组和指针

4.2.1 行指针的理解

在二维数组中,数组名表示的是数组首行元素的地址,指针指向的空间是一维数组,将其称为行指针。

所谓的行指针,指的是在二维数组中,指向一行元素的指针,就称为行指针。一行元素是一个数组,所以也称为数组指针。

行指针的定义:

int a[3][4];
    由于a是二维数组的数组名,表示的二维数组首行元素的地址(是指针常量);
此时可以定义相同类型的指针变量存储指针常量的值:
int (*p)[4] = a;    /* 采用右左原则分析:指针,数组(有4个元素,元素类型为int) ==> p是一个指向有4个int类型元素的数组的数组指针   */

数组指针(行指针)的定义

        存储类型 数据类型 (* 指针变量名)[常量表达式];

                存储类型:修饰指针变量

                数据类型:修饰数组元素的数据类型

4.2.2 列指针的理解

#include <stdio.h>

int main()
{
        int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
        
        int *q = &a[0][0];
        int i;
        int j;
        for (i = 0; i < 3; i++) {
                for (j = 0; j < 4; j++) {
                        printf("%d ", *(q+i*4+j));
                }
                printf("\n");
        }
        return 0;
}

4.3 指针数组

4.3.1 指针数组的理解

        所谓的指针数组,指的是有多个相同数据元素类型指针的数组集合,称为指针数组。

        实质是数组(满足数组定义的规则),数据元素类型为指针。

                定义的语法规则:

                        存储类型 数据类型 * 数组名[常量表达式]

                        存储类型:修饰数组存储空间的属性;

                        数据类型:修饰数组数据元素指向空间的数据类型;

               指针数组元素的访问:满足数组元素访问规则。

                        数组名[下标] 逐一元素访问

4.3.2 指针数组元素的初始化

1.先定义,在初始化

        a.全局变量和static修饰的局部变量,在定义的时候未设置初始值默认初始值为0值(NULL);

        b.未被static修饰的局部变量,在定义的时候未设置初始值默认初始值为随机值。

2.在定义的时候设置初始值

        a.全部初始化

                所有数组元素都是设置的初始地址值。(初始化数据个数等于常量表达式;省略常量表达式)。

        b.部分初始化

                初始元素部分未初始值,未初始化的元素部分为0值(NULL);

#include <stdio.h>

int a = 345;        /* 静态存储区 */

//int *ptr[3];
int main()
{
        int i;
        int b = 123;        /*  栈区 */
        int arr[3] = {1,2,3};
        //static int *ptr[3];
        int * ptr[3] = {&a, &b, arr};        
        for (i = 0; i < 3; i++) {
                printf("ptr: %d\n", *ptr[i]);
        }
        return 0;
}

 

3.在实现指针数组元素值设计的时候注意:

        数据元素指针指向的空间大小尽可能具有相同的属性。要么是相同类型的变量,也可以是同样大小的数组空间。

5. 多级指针

所谓的多级指针,指的是指针的地址,在引用的时候,也需要多次地址引用访问。

一般情况,二级指针就是多级指针的一种,指针的级数可以在递增。

#include <stdio.h>

int main()
{
        int a = 123;
        printf("a = %d\n", a); 
        

        int *p = &a; 
        printf("a = %d, *p = %d\n", a, *p);

        int **q = &p;
        printf("a = %d, *p = %d, **q = %d\n", a, *p, **q);    /* 数据变量直接访问、一级指针访问、二级指针访问 */

        **q = 111;
        printf("a = %d, *p = %d, **q = %d\n", a, *p, **q);
        return 0;         
}

6.特殊指针

6.1 void指针

        在C语言中,有一个特殊的数据类型关键字void,不能用来修饰变量

                1.可以修饰函数的参数和返回值的数据类型:表示函数没有参数和返回值;

                2.可以用来修饰指针:有重要的意义,用来表示指针指向任意的数据类型。

void * 指针变量名;
意义:对于void类型指针是指向任意数据类型的指针,可以是同一个指针在不同时间指向不同的数据类型。
    解决不同数据类型数据在完成相同逻辑运算功能。
使用注意:
    在对于void类型指针访问,需要根据时间的数据类型访问(在读写访问的时候需要根据时间数据类型强制转换实现)。

void指针实例

#include <stdio.h>

int main()
{
        int a = 23; 
        char c = 'a';
        void *p; 

        p = &a;         /* void指针指向int类型的空间 */
        printf("%d\n", *(int *)p);    
            
        p = &c;         /* void指针指向char类型的空间 */
        printf("%c\n", *(char *)p);
}

6.2 NULL指针和野指针

6.2.1 NULL指针

        所谓的NULL指针,其实质是指针的零值:在标准C库中所定义的标识符常量,其值为(void *)0,也称为空指针。

意义:

        1.可以用于指针的初始化,在定义的时候,如果指针的指向不明确的时候,可以将指针的初始值设置为NULL,表示指针没有指向。

        2.用于函数的返回值,表示函数异常返回;

        3.确保指针的正常访问,对于没有明确指向的指针,使用NULL进行判断。

对于指针p和零值的比较:

if(p == NULL) {        /* 相反的比较:if(p != NULL) p不为零值条件成立;否则p为零值条件不成立 */
    /* 指针p为零值,执行该部分 */
} else {
    /* 指针p不为零值,执行该部分 */
}
if(p) {                /* 相反的比较:if(!p) p为零值条件成立;否则p不为零值条件不成立 */
    /* 指针p不为零值,执行该部分 */
} else {
    /* 指针p为零值,执行该部分 */
}

6.2.2 野指针

所谓的野指针,所定义的指针变量所指向的空间没有开辟

        1.一般情况下野指针产生:

                a.在定义指针的时候,没有给指针设置初始值的时候,所定义的指针就是野指针;

                b.指针在使用的过程中,将指针的值做了随意的修改,指向的空间并没有开辟;

                c.如果指针指向的空间是动态开辟的空间,在空间释放后,指针的值没有修改。

        2.对于野指针的访问会产生未知的异常:

                a.程序出现段错误,野指针所指向的空间不具备读写访问的权限;

                b.程序能够正常运行,数据发生异常。

                c.整个程序的运行没有任何异常。

        3.避免野指针的出现:

                a.少使用或者不使用指针。

                b.在定义指针的时候,需要初始化指针指向的空间(开辟存储空间)。

                c.在指针指向不明确的时候,将指针的值设置为NULL。

#include <stdio.h>

int main()
{
        int *p = NULL;    /* 在指针定义的时候,由于指向不明确,初始设置为NULL */

        if (p == NULL) {    /* 在访问指针之前,对其判断指针是否有指向,有指向才访问指针,否则不访问指针 */
                return -1;
        }
        printf("*p = %d\n", *p);
        return 0;
}

6.3 const指针

        所谓的const指针,指的是使用const修饰的指针变量;

1.const变量

        所谓的const变量,指的是在C语言中,定义的变量使用const修饰,那么此时的变量为只读变量,不能被直接修改。

#include <stdio.h>

int main()
{    
        const int a = 123;        /* const修饰的变量a为只读变量,不能直接修改 */
        int *p = &a;

        printf("a = %d, *p = %d\n", a, *p);
        //a = 321;                /* error,只读变量a的值不能直接修改 */
        *p = 321;
        printf("a = %d, *p = %d\n", a, *p);
}

2.const修饰指针指向空间的内容

                const int *p;

               int const *p;

        const修饰的指针的目标(指向空间的数据,指针的引用值)不能被修改,但是指针的指向(指针的值)可以被修改;

        在定义的时候,需要初始化指针的目标内容;指针的指向可以使用之前设置。

int main()
{
        const int a = 123;
        const int b = 22;
        const int *p = &a;    /* const修饰的指针目标空间的数据,不能修改指针引用的值 */
//      int const *p = &a;    /* 等价于:const int *p = &a */        
        printf("p: %p, &a = %p, &b : %p, a = %d, b = %d, *p = %d\n", p, &a, &b, a, b, *p);
        //*p = 321;           /* error,指针目标为const修饰,不能被修改 */
        p = &b;                
        printf("p: %p, &a = %p, &b : %p, a = %d, b = %d, *p = %d\n", p, &a, &b, a, b, *p);
}

3.const修饰指针的指向

        int * const p;

        const修饰的是指针的指向,也就是指针指向空间的起始地址不会改变。但是指针的目标(指向空间的数据内容)可以改变。

        在定义的时候,需要初始化指针变量的指向(指针的值)。

int main()
{
        int a = 123;
        int * const p = &a;    /* const修饰的指针变量p的指向,指针的起始地址值不能被修改 */


        printf("a = %d, *p = %d\n", a, *p);
//      p = &a;                /* error,指针的指向由const修饰,不能被修改 */
        *p = 321;
              
        printf("a = %d, *p = %d\n", a, *p);
}

4.const修饰指针的指向和指针的目标

        const int * const p;

        第1个const修饰的指针的目标,指针指向空间的内容不能被修改,在定义的时候需要初始化指针目标内容。

        第2个const修饰的指针的指向,指针指向空间的起始地址值不能被修改,在定义的时候需要初始化指针变量的指向。

int main()
{
        const int a = 123;
        const int b = 333;
        const int * const p = &a;

        printf("a = %d, *p = %d\n", a, *p);
        //p = &b;            /* error,指针的指向是有const修饰,不能改变指针变量值 */
        //*p = 111;
          /* error, 指针的目标是由const修饰,不能改变指针目标空间的数据 */
}

  • 13
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值