2020第三次软件培训——指针

指针

指针的概念(地址和内存)

计算机中所有的数据都必须放在内存中,不同类型的数据占用的字节数不一样,例如 int 占用 4 个字节,char 占用 1 个字节。为了正确地访问这些数据,必须为每个字节都编上号码,就像门牌号、身份证号一样,每个字节的编号是唯一的,根据编号可以准确地找到某个字节。
下图是 4G 内存中每个字节的编号(以十六进制表示):
在这里插入图片描述

我们将内存中字节的编号称为地址(Address)或指针(Pointer)。

定义指针变量

int *a; //定义整型指针变量a

char *b; //定义char类型指针变量

int **c; //定义(int *)类型指针变量,所谓的二级指针

char ***d; //定义(char **)类型的指针变量,所谓的三级指针

//特殊的指针,void型指针,可以指向任何数据类型

void* ptr = a; //OK 指向int类型的数据

ptr = b; //OK 指向char类型的数据

指针变量的值中有一个非常特殊的值: NULL,它不指向系统中的任何变量或者函数。一般,我们使用它作为一个标志(返回指针的函数没有正确执行、到达链表末尾等等)

附其他类型定义:

int *p[3]; //首先从P 处开始,先与[]结合,因为其优先级比*高,所以P 是一个数组,然后再与*结合,说明数组里的元素是指针类型,然后再与int 结合,说明指针所指向的内容的类型是整型的,所以P 是一个由返回整型数据的指针所组成的数组

int (*p)[3]; //首先从P 处开始,先与 * 结合,说明P 是一个指针然后再与[]结合(与"()"这步可以忽略,只是为了改变优先级),说明指针所指向的内容是一个数组,然后再与int结合,说明数组里的元素是整型的.所以P 是一个指向由整型数据组成的数组的指针

int p(int); //从P 处起,先与()结合,说明P 是一个函数,然后进入()里分析,说明该函数有一个整型变量的参数,然后再与外面的int 结合,说明函数的返回值是一个整型数据

Int (*p)(int); //从P 处开始,先与指针结合,说明P 是一个指针,然后与()结合,说明指针指向的是一个函数,然后再与()里的int 结合,说明函数有一个int 型的参数,再与最外层的int 结合,说明函数的返回类型是整型,所以P 是一个指向有一个整型参数且返回类型为整型的函数的指针

& *运算符

这里&是取地址运算符,*是间接运算符。
&a 的运算结果是一个指针,指针的类型是a 的类型加个*,指针所指向的类型是a 的类型,指针所指向的地址嘛,那就是a 的地址。
*p 的运算结果就五花八门了。总之*p 的结果是p 所指向的东西,这个东西有这些特点:它的类型是p 指向的类型,它所占用的地址是p所指向的地址。
例六:

int a=12; int b; int *p; int **ptr; 

p=&a; //&a 的结果是一个指针,类型是int*,指向的类型是int,指向的地址是a 的地址。 

*p=24; //*p 的结果,在这里它的类型是int,它所占用的地址是p所指向的地址,显然,*p 就是变量a。 

ptr=&p; //&p 的结果是个指针,该指针的类型是p 的类型加个*,在这里是int **。该指针所指向的类型是p 的类型,这里是int*。该指针所指向的地址就是指针p 自己的地址。 

*ptr=&b; //*ptr 是个指针,&b 的结果也是个指针,且这两个指针的类型和所指向的类型是一样的,所以用&b来给*ptr 赋值就是毫无问题的了。 

**ptr=34; //*ptr 的结果是ptr 所指向的东西,在这里是一个指针,对这个指针再做一次*运算,结果是一个int 类型的变量。 

指针的运算

指针变量保存的是地址,而地址本质上是一个整数,所以指针变量可以进行部分运算,例如加法、减法、比较等,请看下面的代码:

#include <stdio.h>

int main(){
	int  a = 10,  *pa = &a, *paa = &a;
	double b = 99.9, *pb = &b;
	char  c = '@', *pc = &c;
	//最初的值
	printf("&a=%#X, &b=%#X, &c=%#X\n", &a, &b, &c);
	printf("pa=%#X, pb=%#X, pc=%#X\n", pa, pb, pc);
	//加法运算
	pa++; pb++; pc++;
	printf("pa=%#X, pb=%#X, pc=%#X\n", pa, pb, pc);
	//减法运算
	pa -= 2; pb -= 2; pc -= 2;
	printf("pa=%#X, pb=%#X, pc=%#X\n", pa, pb, pc);
	//比较运算
	if(pa == paa)
	{
		printf("%d\n", *paa);
	}
	else
	{
		printf("%d\n", *pa);
	}
return 0;
}

运行结果:

&a=0X28FF44, &b=0X28FF30, &c=0X28FF2B
pa=0X28FF44, pb=0X28FF30, pc=0X28FF2B
pa=0X28FF48, pb=0X28FF38, pc=0X28FF2C
pa=0X28FF40, pb=0X28FF28, pc=0X28FF2A
2686784

从运算结果可以看出:pa、pb、pc 每次加 1,它们的地址分别增加 4、8、1,正好是 int、double、char 类型的长度;减 2 时,地址分别减少 8、16、2,正好是 int、double、char 类型长度的 2 倍。
这很奇怪,指针变量加减运算的结果跟数据类型的长度有关,而不是简单地加 1 或减 1,这是为什么呢?
以 a 和 pa 为例,a 的类型为 int,占用 4 个字节,pa 是指向 a 的指针,如下图所示:
在这里插入图片描述

刚开始的时候,pa 指向 a 的开头,通过 *pa 读取数据时,从 pa 指向的位置向后移动 4 个字节,把这 4 个字节的内容作为要获取的数据,这 4 个字节也正好是变量 a 占用的内存。
如果pa++;使得地址加 1 的话,就会变成如下图所示的指向关系:
在这里插入图片描述

这个时候 pa 指向整数 a 的中间,*pa 使用的是红色虚线画出的 4 个字节,其中前 3 个是变量 a 的,后面 1 个是其它数据的,把它们“搅和”在一起显然没有实际的意义,取得的数据也会非常怪异。
如果pa++;使得地址加 4 的话,正好能够完全跳过整数 a,指向它后面的内存,如下图所示:
在这里插入图片描述

指针的应用

观察下面两种方法的输出结果有何不同

例1、 值传递

#include <stdio.h>

void swap(int a, int b) {
	int c = a;
  	a = b;
 	b = c;
}

int main() {
	int a = 2, b = 3;
	swap(a, b);
	printf("a=%d\tb=%d\n", a, b);
	return 0;
}

例二、指针传递

#include <stdio.h>

void swap(int* pa, int* pb) {
	int c = *pa;
	*pa = *pb;
	*pb = c;
}

int main() {
	int a = 2, b = 3;
  	swap(&a, &b);
 	printf("a=%d\tb=%d\n", a, b);
  	return 0;
}

值传递只是拷贝了一份变量的值到新的内存空间,对新内存空间中的变量进行操作,而不会对原有变量造成任何影响。指针操作则直接对原有变量内存空间中的数据进行了修改,从而正确地完成了交换数据的功能。

指针与数组的关系

#include <stdio.h>

int main() {
  	int array1[10];
  	char array2[10];
  	printf("数组元素中,第0个元素的地址 %d\n", &array1[0]);
  	printf("整型数组的首地址是 %d\n数组中第一个元素的地址是 %d\n", array1, &array1[0]);
  	//使用指针加1的效果:
  	printf("int型指针加1: %d\n", array1 + 1);
  	printf("字符数组的首地址是 %d\n数组中第一个元素的地址是 %d\n", array2, &array2[0]);
  	//使用指针加1的效果:
  	printf("char型指针加1:%d\n", array2 + 1);
  	return 0;
}

C\C++使用指针的类型来确定一次指针变量 +1 需要在内存空间向后移动多少个字节,同时
指针的类型也使得程序能够正确地完成指针–>值的转换(这里的"值"指的是指针指向的内存
空间中储存的值)。

其次,上面的程序也证明了数组的首地址与数组名中储存的地址相同!

下图是一个字符数组在内存空间内的存储结构图,对于字符数组,其指针每加“1”在内存空间内就是将指向的地址向后移动了一个字节(8位)的长度。
在这里插入图片描述

下图是一个整型数组在内存空间内的储存结构图,对于整型数组,其指针每加“1”在内存空间内就是将指向的地址向后移动了四个字节(32位)的长度。
在这里插入图片描述

二维数组的指针

#include <stdio.h>

int main() {
    int array[3][10];
  	printf("二维数组首地址 %d\n", array);
  	printf("第一行一维数组首地址 %d\n", *array);
  	printf("二维数组第二行地址 %d\n", array + 1);
  	printf("第二行一维数组首地址 %d\n", *(array + 1));
  	return 0;
}

是不是觉得二维数组的指针还是挺好理解的
那下面来看看提高部分吧

#include <stdio.h>

int main() {
  	int array[3][10] = {};
  	int **p = array;
  	printf("第1行第0列的值 %d\n", **(p1+1));
  	return 0;
}

好吧,这样居然就报错!我强制转换总行了吧!

#include <stdio.h>
 
int main() {
  	int array[3][10] = {};
  	int **p = (void**)array;
  	return 0;
}

还错,那怎样才正确嘛!

#include <stdio.h>
 
int main() {
  	int array[10][10] = {};
  	int(*p1)[10] = array;
  	printf("第1行第0列的值 %d\n", **(p1+1));
  	return 0;
}

二维数组指针总结

二维数组指针和二级指针的差异来自于内存中储存数组方式的不同
理想的情况下,二维数组储存的方式如下图所示

在这里插入图片描述

而在计算机的内存中,考虑到内存空间的总量有限,二维数组是以下图的形式储存的
也就是说,在C\C++中,二维数组是一个伪二维数组,它本质上就是一个一维数组
在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
指针是C编程语言中非常重要的概念之一,对于初学者来说可能会感到害怕和困惑。但只要理解了指针的原理和用法,就会发现它其实并不可怕。 首先,指针是一个存储变量地址的变量,它可以指向任何数据类型的变量,包括整型、浮点型、字符型等等。我们可以通过指针访问变量的值,也可以通过指针修改变量的值,这是指针的一大优势。 其次,理解指针的应用场景能够帮助我们更好地使用它。比如,当我们需要在函数之间传递大量的数据时,通过传递指针可以提高程序的执行效率。另外,在动态内存分配和释放中,指针也是一个必不可少的工具。 理解指针的用法也是很重要的。首先,我们需要理解指针的声明和初始化。指针的声明使用“类型 * 变量名”的语法,例如 int *ptr; 表示声明了一个指向整型变量的指针指针的初始化可以通过给指针赋值一个变量的地址或者通过取地址符&获取变量的地址。 然后,我们需要了解指针的运算。指针可以进行四种基本的运算:取地址运算符&,取值运算符*,指针加法和指针减法。取地址运算符&用于获取变量的地址,取值运算符*用于获取指针所指向的变量的值。指针加法和指针减法用于指针地址的增加和减少,不同数据类型的指针相加或相减会有不同的结果。 最后,我们需要注意指针的安全性。在使用指针的过程中,需要特别小心避免空指针、野指针等问题的出现,因为这些问题容易引发程序的崩溃或者产生不可预知的结果。 总结来说,指针作为C语言中的重要概念,我们应该尽早学习和掌握。只要理解指针的原理和用法,我们就能够更加灵活地操作内存,提高程序的效率和功能。通过不断的实践和学习,我们可以逐渐摆脱对指针的恐惧,让指针成为我们编程的得力助手。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值