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、p是变量,p的类型是将变量p本身拖黑,剩下的类型就是指针变量的类型 int *
3、指针变量p用来保存什么类型数据的地址 ,将指针变量p和指针变量p最近的*一起拖黑,剩下什么类型就保存什么类型数据的地址

int a = 10;
int *p = &a;

示例:

#include <stdio.h>

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;
}

在这里插入图片描述

在这里插入图片描述

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

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

#include <stdio.h>


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;
}

在这里插入图片描述

重点:星花*与取地址& ,在给变量赋值时,等号两边的表达式类型应当要匹配一致;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;

3) 指针大小

  • 使用sizeof()测量指针的大小,得到的总是:48
  • sizeof()测的是指针变量指向存储地址的大小
  • 32位平台,所有的指针(地址)都是32位(4字节)
  • 64位平台,所有的指针(地址)都是64位(8字节)
#include <stdio.h>

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;
}

编译器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 <stdio.h>

int main(){
    
    

	int num = 0x01020304;  // 刚好四个字节的数据
	char* p1 = (char*)&num;  // 类型不匹配强转
	short* p2 = (short*)&num; // 类型不匹配强转
	int* p3 = &num;

	//通过*取指针变量所指向那块空间内容时,取的内存的宽度和指针变量本身的类型有关
	printf("p1=%x\n", *p1);  // 04  1个字节
	printf("p2=%x\n", *p2);  // 0304  2个字节 
	printf("p3=%x\n", *p3);  // 01020304  4个字节
	return 0;
}

在这里插入图片描述

在这里插入图片描述

示例2:

#include <stdio.h>

int main() {
    
    

	int num = 0x01020304;  
	char* p1 = (char*)&num;  
	short* p2 = (short*)&num; 
	int* p3 = &num;

	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;
}

在这里插入图片描述

5) 野指针和空指针

5.1 野指针

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

错误示例:

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

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

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

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

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

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

5.2 空指针

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

#include <stdio.h>


int main() {
    
    
	int a = 100; // 整型变量的初始化
	// 将指针的值赋值为0 即0x00000000 =  NULL
	int* p = NULL; // 因为p保存了0x0000的地址,这个地址是程序初始地址不可以使用的,非法

	*p = 1000;
	printf("%d", *p);

	return 0;
}

在这里插入图片描述

在这里插入图片描述

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

#include <stdio.h>


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;
}

在这里插入图片描述

6) 万能指针void *

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

错误示例:

#include <stdio.h>

int main() {
    
    

	int a = 10;
	void* p = (void*)&a;  // a为int类型,取&,加一级*,所以为int*类型,int*类型不匹配void*类型,所以要进行强转
	printf("%d", *p);

	return 0;
}

运行以上代码,提示错误

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

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

#include <stdio.h>

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;
}

在这里插入图片描述

7) const修饰的指针变量

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

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

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

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

	// const 修饰变量a
	const int a = 10;
	int* p = &a;
	*p = 100;

在这里插入图片描述

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

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

在这里插入图片描述

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

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

在这里插入图片描述

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空间里面的内容

多级指针

  • C语言允许有多级指针存在,在实际的程序中一级指针最常用,其次是二级指针。
  • 二级指针就是指向一个一级指针变量地址的指针。
  • 三级指针基本用不着,但考试会考。
#include <stdio.h>

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;
}

在这里插入图片描述

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

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

指针和数组

1) 数组名

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

#include <stdio.h>

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;
}

2) 指针操作数组元素

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

#include <stdio.h>


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,跨过一个步长 如:int *p; 的步长为sizeof(int) = 4Byte
  • 要得到内存的数据,就该先得到数据的地址
  • *(地址) 得到的是地址里面的内容
    在这里插入图片描述
#include <stdio.h>

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;
}
输出结果
3 9 5 1 4 7 6 10 2 8
1 2 3 4 5 6 7 8 9 10

3) 指针加减运算

3.1 加法运算

  • 指针计算不是简单的整数相加
  • 如果是一个int *+1的结果是增加一个int的大小
  • 如果是一个char *+1的结果是增加一个char的大小
#include <stdio.h>

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;
}

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

#include <stdio.h>

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,

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

#include <stdio.h>

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;
}

在这里插入图片描述

3.2 减法运算

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

#include <stdio.h>
// 指针加法运算
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;
}
输出结果
8, 2, 10, 6, 7, 4, 1, 5, 9, 3,

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

#include <stdio.h>

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;
}
输出结果
9
8

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

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

示例1:

#include <stdio.h>


int main() {
    
    
	// `[]`并不是数组的专属
	int a;
	int* p = &a;
	// [] == *()
	// p[0] == *(p+0) == *p
	p[0] = 100;

	printf("%d", a); // 100

	return 0;

}

示例2:

#include <stdio.h>

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;
}

在这里插入图片描述

示例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;

}
输出结果
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

5) 指针数组

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

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

#include <stdio.h>

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;
}

在这里插入图片描述

输出结果
10 20 30

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

#include <stdio.h>

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;
}

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

猜你喜欢

转载自blog.csdn.net/qq_41782425/article/details/127814460