• C语言学习之路(基础篇)—— 指针(上)


    说明:该篇博客是博主一字一码编写的,实属不易,请尊重原创,谢谢大家!

    概述

    1) 内存

    内存含义:

    • 存储器: 计算机的组成中,用来存储程序和数据,辅助CPU进行运算处理的重要部分。
    • 内存: 内部存贮器,暂存程序/数据——掉电丢失 SRAM、DRAM、DDR、DDR2、DDR3
    • 外存: 外部存储器,长时间保存程序/数据—掉电不丢ROM、ERRROM、FLASH(NAND、NOR)、硬盘、光盘

    内存是沟通CPU与硬盘的桥梁:

    • 暂存放CPU中的运算数据
    • 暂存与硬盘等外部存储器交换的数据

    2) 物理存储器和存储地址空间

    有关内存的两个概念:物理存储器 和 存储地址空间。

    物理存储器:实际存在的具体存储器芯片。

    • 主板上装插的内存条
    • 显示卡上的显示RAM芯片
    • 各种适配卡上的RAM芯片和ROM芯片

    存储地址空间:对存储器编码的范围。我们在软件上常说的内存是指这一层含义。

    • 编码:对每个物理存储单元(一个字节)分配一个号码
    • 寻址:可以根据分配的号码找到相应的存储单元,完成数据的读写

    3) 内存地址

    • 将内存抽象成一个很大的一维字符数组。
    • 编码就是对内存的每一个字节分配一个32位或64位的编号(与32位或者64位处理器相关)。
    • 这个内存编号我们称之为内存地址。

    内存中的每一个数据都会分配相应的地址:

    • char:占一个字节分配一个地址
    • int: 占四个字节分配四个地址
    • float、struct、函数、数组等
      在这里插入图片描述

    4) 指针和指针变量

    指针: 指针 === 地址 === 编号
    指针变量: 存放指针(地址)的变量

    • 内存区的每一个字节都有一个编号,这就是“地址”。
    • 如果在程序中定义了一个变量,在对程序进行编译或运行时,系统就会给这个变量分配内存单元,并确定它的内存地址(编号)
    • 指针的实质就是内存“地址”。指针就是地址,地址就是指针。
    • 指针是内存单元的编号,指针变量是存放地址的变量。
    • 通常我们叙述时会把指针变量简称为指针,实际他们含义并不一样。

    在这里插入图片描述

    指针基础知识

    1) 指针变量的定义和使用

    • 指针也是一种数据类型,指针变量也是一种变量
    • 指针变量指向谁,就把谁的地址赋值给指针变量
    • *”操作符操作的是指针变量指向的内存空间

    定义指针的三步骤:
    1、 *与符号结合代表是一个指针变量
    2、 要保存谁的地址,将他的定义形式放在此处
    3、 用*p替换掉定义的变量

    int a = 10;
    // 1、定义*p指针变量
    // 2、int a ---->  
    // 3、int a ----> int *p  // 指针变量p
    
    • 1
    • 2
    • 3
    • 4

    分析:
    1、与*结合代表这个一个指针变量
    2、p是变量,p的类型是将变量p本身拖黑,剩下的类型就是指针变量的类型 int *
    3、指针变量p用来保存什么类型数据的地址 ,将指针变量p和指针变量p最近的*一起拖黑,剩下什么类型就保存什么类型数据的地址

    int a = 10;
    int *p = &a;
    
    • 1
    • 2

    示例:

    #include 
    
    int main()
    {
    	int a = 10;
    	char b = 97;
    	printf("%p, %p\n", &a, &b); //打印a, b的地址
    
    	//int *代表是一种数据类型,int*指针类型,p才是变量名
    	//定义了一个指针类型的变量,可以指向一个int类型变量的地址
    	int* p;
    	p = &a; //将a的地址赋值给变量p,p也是一个变量,值是一个内存地址编号
    	printf("%d\n", *p); //p指向了a的地址,*p就是a的值
    
    	char* p1 = &b;
    	printf("%c\n", *p1); //*p1指向了b的地址,*p1就是b的值,%c打印字符
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    在这里插入图片描述

    在这里插入图片描述

    注意:&可以取得一个变量在内存中的地址。但是,不能取寄存器变量,因为寄存器变量不在内存里,而在CPU里面,所以是没有地址的。

    2) 通过指针间接修改变量的值

    #include 
    
    
    int main() {
    
    	int a = 10;
    	char b = 97;
    
    	int* p = &a;
    	char* p1 = &b;
    
    	printf("a=%d, *p=%d\n", a, *p);  // a=10, *p=10
    	printf("b=%c, *p1=%c\n", b, *p1);  // b=a, *p1=a
    	// 指针变量保存谁的地址就指向了谁
    	*p = 100; // 在使用时,*与p结合代表,取p指针所指向那块空间的内容
    	*p1 = 65;
    
    	printf("a=%d, *p=%d\n", a, *p);  // a=100, *p=100
    	printf("b=%c, *p1=%c\n", b, *p1);  // b=A, *p1=A
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    在这里插入图片描述

    重点:星花*与取地址& ,在给变量赋值时,等号两边的表达式类型应当要匹配一致;int* p = &a;亦是如此。

    在使用时,对一个表达式取*,就会对表达式减一级*,如果对表达式取&,就会加一级*

    int a = 10;
    //在使用时,对一个表达式取`*`,就会对表达式减一级`*`,如果对表达式取`&`,就会加一级`*`
    
    // 左边表达式p为int*类型;右边的表达式a为int类型那么加上`&`,对表达式取`&`,就会加一级`*,即int *类型;左右两边类型匹配
    int* p;
    p = &a;
    
    // 左边表达式p为int*类型,取`*`,则减一级`*`即int类型;右边100为int类型;左右两边类型匹配
    *p = 100;
    
    int* t;
    int** g;
    // 左边表达式g为int**类型;右边表达式t为int*类型,取`&`就会加一级`*`即为int**类型;左右两边类型匹配
    g = &t;
    // 左边表达式g为int**类型,取`*`,则减一级`*`即int*类型;右边表达式t为int*类型;左右两边类型匹配
    *g = t;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    3) 指针大小

    • 使用sizeof()测量指针的大小,得到的总是:48
    • sizeof()测的是指针变量指向存储地址的大小
    • 32位平台,所有的指针(地址)都是32位(4字节)
    • 64位平台,所有的指针(地址)都是64位(8字节)
    #include 
    
    int main() {
    	// 不管什么类型的指针,大小只和系统编译器有关系
    	char* p1;
    	char** p2;
    	short* p3;
    	int* p4;
    	int** p5;
    	float* p6;
    	double* p7;
    	printf("%d\n", sizeof(p1));
    	printf("%d\n", sizeof(p2));
    	printf("%d\n", sizeof(p3));
    	printf("%d\n", sizeof(p4));
    	printf("%d\n", sizeof(p5));
    	printf("%d\n", sizeof(p6));
    	printf("%d\n", sizeof(p7));
    	printf("%d\n", sizeof(long*));
    
    	return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    编译器X86运行结果:

    在这里插入图片描述

    编译器X64运行结果:

    在这里插入图片描述

    因为32位编译器内存地址编号范围是0x0000 0000 - 0xffff ffff如此,所以我们的指针变量占4个字节就可以存下;而64位编译器内存地址编号范围是0x0000 0000 0000 0000 - 0xffff ffff ffff ffff 这样的编号,需要8个字节才能存下,所以指针变量也需要8个字节

    4) 指针的宽度和步长

    • 不同类型的指针变量,取指针指向的内容的宽度
    • 指针的宽度 = sizeof(将指针变量与指针变量最近的*拖黑,剩下的类型;如char *p; sizeof(char);占一个字节; int **p; sizeof(int*);占4个字节)
    • 宽度也叫做步长
    • 步长:指针加1跨过多少个字节

    示例1:

    #include 
    
    int main(){
    
    	int num = 0x01020304;  // 刚好四个字节的数据
    	char* p1 = (char*)#  // 类型不匹配强转
    	short* p2 = (short*)# // 类型不匹配强转
    	int* p3 = #
    
    	//通过*取指针变量所指向那块空间内容时,取的内存的宽度和指针变量本身的类型有关
    	printf("p1=%x\n", *p1);  // 04  1个字节
    	printf("p2=%x\n", *p2);  // 0304  2个字节 
    	printf("p3=%x\n", *p3);  // 01020304  4个字节
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在这里插入图片描述

    在这里插入图片描述

    示例2:

    #include 
    
    int main() {
    
    	int num = 0x01020304;  
    	char* p1 = (char*)#  
    	short* p2 = (short*)# 
    	int* p3 = #
    
    	printf("p1=%u\n", p1);  // p1=7601028
    	printf("p2=%u\n", p2);  // p2=7601028
    	printf("p3=%u\n", p3);  // p3=7601028
    	printf("\n");
    	printf("p1=%u\n", p1+1);  // p1=7601029       p1是char类型的指针,+1跨过1个字节
    	printf("p2=%u\n", p2+1);  // p1=7601030       p2是short类型的指针,+1跨过2个字节
    	printf("p3=%u\n", p3+1);  // p1=7601032       p3是int类型的指针,+1跨过4个字节
    
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    在这里插入图片描述

    5) 野指针和空指针

    5.1 野指针

    指针变量也是变量,是变量就可以任意赋值,不要越界即可(32位为4字节,64位为8字节),但是任意数值赋值给指针变量没有意义,因为这样的指针就成了野指针,此指针指向的区域是未知(操作系统不允许操作此指针指向的内存区域)。所以,野指针不会直接引发错误,操作野指针指向的内存区域才会出问题

    错误示例:

    	int a = 100;
    	int* p;
    	p = a; //把a的值赋值给指针变量p,p为野指针, ok,不会有问题,但没有意义
    
    • 1
    • 2
    • 3
    	int* p;
    	p = 0x12345678; //给指针变量p赋值,p为野指针, ok,不会有问题,但没有意义
    
    • 1
    • 2
    	int* p;
    	*p = 1000;  //操作野指针指向未知区域,内存出问题,err
    
    • 1
    • 2

    野指针就是没有初始化的指针,指针的指向是随机的,不可以操作野指针。

    在这里插入图片描述
    在这里插入图片描述

    正确示例:
    指针p保存的地址一定是定义过的(向系统申请过的)。

    	int a = 100;
    	int* p = &a;
    	*p = 1000; //p指向了a的地址,*p就是a的值,给*p赋值1000,那么a的值也是1000
    
    • 1
    • 2
    • 3

    在这里插入图片描述
    在这里插入图片描述

    5.2 空指针

    野指针和有效指针变量保存的都是数值,为了标志此指针变量没有指向任何变量(空闲可用),C语言中,可以把NULL赋值给此指针,这样就标志此指针为空指针,没有任何指针。

    #include 
    
    
    int main() {
    	int a = 100; // 整型变量的初始化
    	// 将指针的值赋值为0 即0x00000000 =  NULL
    	int* p = NULL; // 因为p保存了0x0000的地址,这个地址是程序初始地址不可以使用的,非法
    
    	*p = 1000;
    	printf("%d", *p);
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这里插入图片描述

    在这里插入图片描述

    既然不能使用,为什么还要初始化为NULL呢,赋值为NULL主要用于标记,来判断该指针是否被使用,避免它成为一个野指针。

    #include 
    
    
    int main() {
    	int a = 100; // 整型变量的初始化
    	// 将指针的值赋值为0 即0x00000000 =  NULL
    	int* p = NULL; // 因为p保存了0x0000的地址,这个地址是程序初始地址不可以使用的,非法
    	// 如果p等于NULL,说明没有被使用,那么就可以进行赋值操作
    	if (p == NULL)
    	{
    		p = &a;
    		*p = 1000;
    	}
    	else
    	{
    		// 说明p有指向,被使用
    		// 养成好的习惯,每次使用完指针,就给赋值为NULL
    		// int* p = NULL;
    	}
    	printf("%d %d", *p, a);
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    在这里插入图片描述

    6) 万能指针void *

    void*万能指针可以指向任意变量的内存空间:

    错误示例:

    #include 
    
    int main() {
    
    	int a = 10;
    	void* p = (void*)&a;  // a为int类型,取&,加一级*,所以为int*类型,int*类型不匹配void*类型,所以要进行强转
    	printf("%d", *p);
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    运行以上代码,提示错误

    在这里插入图片描述

    导致以上错误原因是:我们不知道*p应该取多少个字节的数据

    在这里插入图片描述

    就好比定义void类型的变量一个道理,因为编译器不知道该给此变量类型分配多大的空间;但是定义void*类型没有问题,因为指针类型数据要么4个字节要么8个字节(取决于编译器)

    在这里插入图片描述

    目前程序上是知道*p指针变量应该取哪里的地址,只是不知道应该取多少个字节数据而已,那么我们可以通过转换类型的方式去获取地址数据。(如:*p 中的p指向a的地址,p的类型是void*,你要取多少个字节数据,就转为什么类型即可;我要取4个字节的数据,那么就将p转为int*类型即可)

    #include 
    
    int main() {
    
    	int a = 10;
    	//void b = 20; // error 不可以定义void类型的变量,因为编译器不知道给变量分配多大的空间
    	// 但是可以定义void* 类型, 因为指针类型数据要么4个字节要么8个字节(取决于编译器)
    	void* p = (void*)&a;  // a为int类型,取&,加一级*,所以为int*类型,int*类型不匹配void*类型,所以要进行强转
    
    	//printf("%d\n", *p); //error p是void*类型,编译器不知道取几个字节的大小
    
    	// *p 中的p指向a的地址,p的类型是void* 类型,你要取4个字节数据,那么将p转为int*类型即可解决
    	printf("%d\n", *(int*)p);
    	// 同理q的类型是void* 类型,我要取2个字节的数据,那么我就将q转为short*类型即可
    	printf("%d", *(short*)p);
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在这里插入图片描述

    7) const修饰的指针变量

    const是一个C语言的关键字,具有着举足轻重的地位。它限定一个变量不允许被改变,产生静态作用。使用const在一定程度上可以提高程序的安全性和可靠性。另外,在观看别人代码的时候,清晰理解const所起的作用,对理解对方的程序也有一定帮助。

    const修饰变量a后,不能再通过变量a去修改a所指向内存空间里面的内容

    	// const 修饰变量a
    	const int a = 10;
    	a = 100;  // error 修饰变量a后,不能再通过变量a去修改a所指向空间里面的内容
    
    • 1
    • 2
    • 3

    但可以通过指针变量*p去修改a地址的内容

    	// const 修饰变量a
    	const int a = 10;
    	int* p = &a;
    	*p = 100;
    
    • 1
    • 2
    • 3
    • 4

    在这里插入图片描述

    const修饰指针变量*p后,不能再通过*p去修改变量p所指向a空间里面的内容

    	int a = 10;
    	// 这里const修饰的是*,不能通过*p去修改p所指向空间的内容
    	const int* p = &a;
    	*p = 100;  // error 不能通过*p去修改变量p所指向a空间里面的内容
    
    • 1
    • 2
    • 3
    • 4

    在这里插入图片描述

    const修饰变量p后,变量p本身的值不能被更改

    	int a = 10;
    	int b = 20;
    	// const修饰的是变量p,p保存的地址不可以修改
    	int* const p = &a;
    	p = &b; // error 变量p本身的值不能被更改
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述

    const修饰的是变量p*p本身的指向不能改变,不能通过*p去修改p所指向空间的内容

    	int a = 10;
    	int b = 20;
    	// const修饰的是变量p和*,p本身的指向不能改变,不能通过*p去修改p所指向空间的内容
    	const int* const p = &a;
    	p = &b; // error 变量p本身的值不能被更改
    	*p = 100; // error 不能通过*p去修改变量p所指向a空间里面的内容
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    多级指针

    • C语言允许有多级指针存在,在实际的程序中一级指针最常用,其次是二级指针。
    • 二级指针就是指向一个一级指针变量地址的指针。
    • 三级指针基本用不着,但考试会考。
    #include 
    
    int main() {
    
    	int a = 10;
    	// *p > int a > int (*p) > int *p
    	int* p = &a;
    	// *q > int *p > int *(*q) > int **q
    	//如果*和&相遇,相抵消
    	// **q == *(*q) == *(p) ==  a
    	// **q == *(*q) == *(&a) ==  a
    	int** q = &p;
    	// *k > int **q > int **(*k) > int ***k
    	int*** k = &q;
    	// *符号结合,代表这个k是一个指针变量
    	// k是一个变量
    	// k的类型,将变量k拖黑,剩下的类型
    	// k用来保存谁的地址  将变量k和k最近的*一起拖黑,剩下什么类型
    	// 就保存什么类型数据的地址
    	
    	printf("%d\n", *p); //10
    	printf("%d\n", **q); //10
    	printf("%d\n", ***k); //10
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    在这里插入图片描述

    定义多级指针保存数据的地址时,定义的指针的类型只需要比要保持的数据的类型多一级*即可

    	// 定义多级指针保存数据的地址时,定义的指针的类型只需要比要保持的数据的类型多一级`*`即可
    	int******************* g;
    	int******************** f = &g;
    
    • 1
    • 2
    • 3

    指针和数组

    1) 数组名

    数组名字是数组的首元素地址,但它是一个常量:

    #include 
    
    int main() {
    
    	int a[10] = { 3,9,5,1,4,7,6,10,2,8 };
    	//a = 10; //err, 数组名只是常量,不能修改
    	printf("a = %p\n", a); // a = 0073F904
    	printf("&a[0] = %p\n", &a[0]); // &a[0] = 0073F904
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    2) 指针操作数组元素

    在没有学习指针之前,我们打印数组元素,是这样子打印的

    #include 
    
    
    int main() {
    
    	int a[10] = { 3,9,5,1,4,7,6,10,2,8 };
    	//a = 10; //err, 数组名只是常量,不能修改
    	//printf("a = %p\n", a); // a = 0073F904
    	//printf("&a[0] = %p\n", &a[0]); // &a[0] = 0073F904
    	for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
    	{
    		printf("%d ", a[i]); //先打印原始元素
    		a[i] = i + 1; // 后赋值
    	}
    	printf("\n");
    	for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
    	{
    		printf("%d ", a[i]); //打印赋值后的元素
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    在这里插入图片描述

    在学习指针后,我们可以通过指针来操作数组元素

    • 指针加1,跨过一个步长 如:int *p; 的步长为sizeof(int) = 4Byte
    • 要得到内存的数据,就该先得到数据的地址
    • *(地址) 得到的是地址里面的内容
      在这里插入图片描述
    #include 
    
    int main() {
    
    	int a[10] = { 3,9,5,1,4,7,6,10,2,8 };
    	//a 数组名,首元素的地址
    	int* p = a; // 指针p保存的是首元素的地址
    	for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
    	{
    		printf("%d ", *(p+i)); //先打印原始元素
    		*(p + i) = i + 1; // 后赋值
    	}
    	printf("\n");
    	for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
    	{
    		printf("%d ", a[i]); //打印赋值后的元素
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    输出结果
    3 9 5 1 4 7 6 10 2 8
    1 2 3 4 5 6 7 8 9 10
    
    • 1
    • 2
    • 3

    3) 指针加减运算

    3.1 加法运算

    • 指针计算不是简单的整数相加
    • 如果是一个int *+1的结果是增加一个int的大小
    • 如果是一个char *+1的结果是增加一个char的大小
    #include 
    
    int main()
    {
    	int a;
    	int* p = &a;
    	printf("%d\n", p); // 9435892
    	p += 2;//移动了2个int
    	printf("%d\n", p); // 9435900
    
    	char b = 0;
    	char* p1 = &b;
    	printf("%d\n", p1);	// 9435871
    	p1 += 2;//移动了2个char
    	printf("%d\n", p1); // 9435873
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    通过改变指针指向操作数组元素:

    #include 
    
    int main()
    {
    	int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    	int i = 0;
    	int n = sizeof(a) / sizeof(a[0]);
    
    	int* p = a;
    	for (i = 0; i < n; i++)
    	{
    		printf("%d, ", *p);
    		p++;
    	}
    	printf("\n");
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    输出结果
    1, 2, 3, 4, 5, 6, 7, 8, 9,
    
    • 1
    • 2

    两指针相加没有意义:单个指针加法运算如上面开头那种p += 2;没有问题;但是两个指针相加没有任何意义,比如p指针指向第一个元素地址内容,q指针指向最后一个元素地址内容,那么结果只是一个很大的数,无其他任务实质意义。

    #include 
    
    int main() {
    
    	// 整个数组的步长 = sizeof(int [10]) == sizeof(a) == 10*4 = 40;
    	int a[10] = { 3,9,5,1,4,7,6,10,2,8 };
    	int* p = a; // &a[0]
    	int* q = (int*)(&a + 1) - 1; // 等同于int *q = &a[9]
    
    	// 两指针相加没有意义
    	printf("%d\n", p + q);
    
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在这里插入图片描述

    3.2 减法运算

    示例1:通过改变指针指向操作数组元素

    #include 
    // 指针加法运算
    int main()
    {
    	int a[10] = { 3,9,5,1,4,7,6,10,2,8 };
    	int i = 0;
    	int n = sizeof(a) / sizeof(a[0]);
    
    	int* p = a + n - 1;  // p指向最后一个元素地址,*p则就是取最后一个元素的地址内容
    	for (i = 0; i < n; i++)
    	{
    		printf("%d, ", *p);
    		p--;
    	}
    	printf("\n");
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    输出结果
    8, 2, 10, 6, 7, 4, 1, 5, 9, 3,
    
    • 1
    • 2

    示例2:两指针(类型一致)相减,得到的是中间跨过多少个元素

    #include 
    
    int main() {
    	// 两指针相减
    	// 整个数组的步长 = sizeof(int [10]) == sizeof(a) == 10*4 = 40;
    	int a[10] = { 3,9,5,1,4,7,6,10,2,8 };
    	int* p = a; // &a[0]
    	// 通过地址取数组最后一个元素,那么&a+1则表示横跨整个数组,就是41,取地址则需要转为int*,最后才能得到41对应的地址,最后地址-1就得到最后一个元素地址
    	int* q = (int*)(&a + 1) - 1; // 等同于int *q = &a[9]
    	printf("%d\n", q-p); // 9
    	// 验证是q-p是否跨了9个元素,直接*取q的地址内容即可
    	printf("%d\n", *(p+9)); // 8
    	
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    输出结果
    9
    8
    
    • 1
    • 2
    • 3

    4) 方括号不是数组的专属

    • []并不是数组的专属
    • []实际上是*()的缩写

    示例1:

    #include 
    
    
    int main() {
    	// `[]`并不是数组的专属
    	int a;
    	int* p = &a;
    	// [] == *()
    	// p[0] == *(p+0) == *p
    	p[0] = 100;
    
    	printf("%d", a); // 100
    
    	return 0;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    示例2:

    #include 
    
    int main() {
    	// `[]`并不是数组的专属
    	int a;
    	int* p = &a;
    	// [] == *()
    	// p[0] == *(p+0) == *p
    	p[0] = 100;
    	p[1] = 200; // error 内存污染,p[1] == *(p+1)  p+1跨过一个元素,指向的是a后面的地址,
    	            //取这块地址里面的内容不能进行操作,即使显示没有问题,但是运行编译会出错
    
    	printf("%d", a);
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在这里插入图片描述

    示例3:

    int main() {
    	// `[]`并不是数组的专属
    	// [] == *()
    	int a[10] = { 3,9,5,1,4,7,6,10,2,8 };
    	int* p = a;
    	
    	for (int i = 0; i < sizeof(a)/sizeof(a[0]); i++)
    	{
    		// 第一种方式
    		printf("%d ", a[i]);
    		// 第二种方式
    		printf("%d ", *(p+i));
    		// 第三种方式
    		printf("%d ", p[i]); // p[i] == *(p+i)
    		// 第四种方式
    		printf("%d ", *(a+i)); // a[i] == *(a+i)  (a+i)首元素地址阔过第i个元素,等到该元素的地址,*取该元素地址的内容
    
    	}
    
    	return 0;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    输出结果
    3 3 3 3 9 9 9 9 5 5 5 5 1 1 1 1 4 4 4 4 7 7 7 7 6 6 6 6 10 10 10 10 2 2 2 2 8 8 8 8
    
    • 1
    • 2

    5) 指针数组

    整型数组,是一个数组,数组的每一个元素都是整型。
    指针数组,它也是数组,数组的每一个元素都是指针。

    示例1:通过指针数组保存多个变量的地址,并打印指向变量地址的内容

    #include 
    
    int main() {
    
    	int a = 10;
    	int b = 20;
    	int c = 30;
    	// int *p1 = &a; int *p2 = &b; int *p3 = &c;
    	int* num[3] = { &a, &b, &c };
    
    	for (int i = 0; i < sizeof(num)/sizeof(num[0]); i++)
    	{
    		printf("%d ", *num[i]); // *num[i]  []优先级高于*
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在这里插入图片描述

    输出结果
    10 20 30
    
    • 1
    • 2

    示例2:定义一个指针来保存数组num首元素的地址,并通过指针变量打印出指针数组中的所有元素

    #include 
    
    int main() {
    
    	int a = 10;
    	int b = 20;
    	int c = 30;
    	// int *p1 = &a; int *p2 = &b; int *p3 = &c;
    	int* num[3] = { &a, &b, &c };
    	// 定义一个指针用来保存数组num首元素的地址
    	// num首元素地址 num == &num[0]
    	// 定义指针的类型 int **  首先num[0]是int *类型,要保存int  *类型的地址,就需要比它多一级*
    	int** k = &num[0];
    	printf("%d\n", **k);
    	for (int i = 0; i < sizeof(num)/sizeof(num[0]); i++)
    	{
    		printf("%d ", **(k+i));  //这里的括号不能去除,去除后就变成了10+i了
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    在这里插入图片描述
    在这里插入图片描述

  • 相关阅读:
    计算机网络初识
    共享盘的文件删除后能找回吗
    代码随想录训练营第III期--011--python
    sqrt函数的实现
    【C++】线程池(有点乱待补充)
    Tars-Java网络编程源码分析
    使用frp搭建内网穿透服务
    5. hdfs的界面详解
    Centos安装Rclone,操作Minio基本命令
    MATLAB | 实用(离谱)小技巧大合集:仅隐藏轴线 | 复杂公式刻度标签 | 渐变背景 | 半透明图例... ...
  • 原文地址:https://blog.csdn.net/qq_41782425/article/details/127814460