C语言指针的学习(初学者自用!!)

内存

举个栗子,如果把计算机比作是一所大学,那么内存就相当于一栋一栋的学生宿舍楼,楼里的每个宿舍都有自己的房间号,假设宿舍是一个8人间,把宿舍里的每个学生比作成一个比特位(bit),那么每个房间就是一个字节(Byte),而房间号,当你想要“串门”的时候,通过房间号就可以快速找到该房间。

 bit      --- 比特位

 Byte   --- 字节             1Byte = 8bit

 KB      1KB = 1024Byte

 MB      1MB = 1024KB

 .........

可以想象:内存是一个巨大的空间,这个空间被均匀地划分成相同大小的内存单元,每个内存单元都有独属于自己的地址编号,称为地址,每个内存单元的大小是1个字节,每个字节能存放8个比特位。

 在计算机中,我们把内存单元的地址编号叫做地址,在C语言中,我们给地址起了一个新名字——指针。所以我们可以这样理解:

内存单元的编号 == 地址 == 指针

指针变量和地址

取地址操作符(&)

在学习指针变量之前,先认识一个新的操作符:&(取地址操作符)。当我们想要获取A的地址时,只需要在A的前面加上" & "即可。比如:

int a = 0;
&a;//这样就能获取a的地址了
printf("%p",&a);//打印地址用" %p "

指针变量

在C语言中,我们如何创建一个整型变量or字符型变量呢?很简单,是这样的:

int a = 0;
char ch = 'c';

同样的方法,我们可以这样创建:

int* ptr;//" * "表示ptr是指针变量,int则表示ptr指向的是int类型的对象
char* ptr_ch;//同理
double* ptr_dl;//同理

(注:int*  pa和int   *pa是一样的,看个人书写习惯)

指针变量前的int* 、char* 、double* 有什么意义呢?我们稍后再提。

学会指针变量的创建之后,我们就可以用指针来存放地址了。例如:

int a = 10;
&a;
int* pa = &a;//pa是指针的名字,想叫啥都行

我们把地址存入指针之后该怎样使用呢?这时就需要学习一个新的操作符“ * ”

解引用操作符(*)

解引用操作符(*)就像一把钥匙,能够打开房间门,从而找到房间里的东西。也就是说,“ * ”能够获取指针所存放的地址里面的值(有种套娃的感觉,一定要花时间思考)

int a = 10;//假设a的地址编号为0x0012ff40
int* pa = &a;//假设pa的地址为0x00126650,此时pa里放的就是a的地址
*pa;//对pa解引用就能打开a的地址,从而获得a的值
printf("%d\n",a);  //输出结果为10
printf("%d\n",*pa);//输出结果也为10

你也可以通过地址来改变变量的值,例如:

int a = 123;
int* pa = &a;
printf("%d\n",a);//输出为123
*pa = 456;//通过地址改变a的值
printf("%d\n",a);//输出为456

指针变量的大小

在32位机器中,指针变量的大小是固定的,为4个字节。在64位机器中,指针变量的大小也是固定的,为8个字节。不论这个指针变量的类型是int*、char*还是double*,它大小都是固定的。

指针变量类型的意义

现在回到我们之前没有解决的问题:指针变量前的int* 、char* 、double* 有什么意义呢?

我们前面提到,一个指针变量,比如:

int  a = 10;

int* pa = &a;

" * " 前面的 int 意思是pa指向的是int类型的对象。

现在学习一个新的知识点:

当指针在访问内存空间时,它有一个访问权限,而这个访问权限的大小就与指针变量的类型有关。

先说结论:指针变量的类型决定它一次性能访问多少个字节(或者说一次性能跳过多少个字节)。比如,int*的指针能一次性访问4个字节,char*的指针能一次性访问1个字节,double*的指针能一次性访问8个字节

直接上代码:

	int a = 123;
	int* pi = &a;
	char* pc = (char*)&a;//把a的地址强制类型转换为char*
	printf("a的地址是:%p\n",&a );
	printf("pi    =    %p\n", &a);
	printf("pi+1  =    %p\n", pi+1);
	printf("pc    =    %p\n", &a);
	printf("pc+1  =    %p\n", pc+1);

运行结果:

我们可以发现,int*类型的指针pi在+1后从E4变成了E8(跳过了4个字节),而char*类型的指针pc在+1后从E4变成了E5(跳过了1个字节)。

除此之外,还有一种void*类型的指针,留到以后再讲。

const修饰指针

const是什么呢?

const是C语言中的一个关键字,若A被const修饰了,那么A就具有了常属性(简单理解为不能被修改)。

const修饰指针的三种形式:

1、const放在“ * ”的左边

int a = 0;
const int *pa = &a;//常量指针

你需要理解:

这种叫作常量指针,我们可以认为 const 修饰的是 *pa ,而 *pa 表示的就是a存放的值0

当我们尝试通过*pa来修改a的值时:

我们会发现编译器报错了,因此,对于这种修饰,我们无法通过指针来改变指针所指对象的值

再看一组代码:

指针ptr一开始指向的是a的地址,当我们改变指针ptr的指向时,程序成功运行。

因此,我们可以得出结论:

常量指针能改变指针的指向,但不能改变指针所指对象的值

2、const放在“ * ”的右边

int a = 0;
int * const ptr = &a;

这种叫作指针常量,我们可以认为 const 修饰的是指针ptr,而ptr表示的就是一个存放a的地址的指针

当我们尝试改变指针ptr的指向时:

我们发现编译器报错了,因此,我们能够知道:这种修饰方式不能改变指针的指向

接下来再看一组代码:

指针始终指向的是a的地址,当我们尝试通过*ptr来改变a的值时,程序成功运行。

因此,我们能够得出结论:

指针常量能改变指针所指对象的值,但不能改变指针的指向

不难发现,常量指针和指针常量的作用是相反的

3、const放在“ * ”的两边

int a = 0;
const int * const pa = &a;

有了前两个例子的铺垫,我们能够很容易的知道:这种修饰方式使得指针的指向和指针所指对象的值都不能改变

指针的运算

指针的+、-

指针也是能进行加减法的,接下来我用数组举例:

	int i = 0;
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* ptr = arr;//数组名就是数组的首元素地址(即arr和&arr[0]的地址是一样的)
	//用指针遍历数组
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *ptr);
		ptr++;
	}

由于ptr是int*类型的,arr是int类型的,所以ptr++就会使指针向后移动4个字节,也就是向后移动一个元素(减法同理,但要小心不要访问不属于自己的内存空间)

指针加减指针

同样以数组为例:

	int i = 0;
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* left = &arr[2];//数组名就是数组的首元素地址(即arr和&arr[0]的地址是一样的)
	int* right = &arr[8];
	int ret = right - left;
	printf("%d\n", ret);
下标:012345678 9
元素:12345678910

运行结果:

发现结果就是left和right之间的元素个数

指针的关系运算

就是比较地址的大小(不想写了偷个懒)

野指针

 概念

野指针就是指针指向的地址是不可知的(随机的、不正确的、没有明确限制的)

野指针的成因

1、指针未初始化

int* ptr;//未初始化
*ptr = 10;

指针未初始化意思是指针在创建时没有给它一个地址。此时指针就会胡乱的指向任意的内存空间,我们无法去访问该空间,这是一种非法的(错误的)行为

2、指针访问数组时发生了越界

	int arr[3] = { 0 };
	int* ptr = &arr[0];
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		*(ptr++) = i;//这一步把 i的值赋给*ptr,然后再+1使ptr向后移动到下一个元素的位置
	}

i= 0 时,arr[0] = 0;i = 1 时,arr[1] = 1;i = 2时,arr[2] = 2 ,此时 i 赋值给*ptr后,ptr还要完成+1的操作循环才会结束,+1向后移动4个字节循环结束,此时的ptr指向的是arr[2]后的空间,超过了数组最后一个元素的空间,使得ptr指向了不属于数组的地址,这就是越界。

3、指针指向的内存空间被释放了

int* test(int n)
{
	return &n;//n作为函数test的形参,会向系统在栈区申请一块空间来存放n的值
}             //当test一结束,系统就会收回n申请的空间,此时n的地址对于这个程序就是非法的

int main()
{
	int* p = test(123);
	int* ptr = *p;//此时ptr接收的是一个非法的地址
	printf("%d", *ptr);
	return 0;
}

如何避免野指针

当我们创建好一个指针变量后,可以将其置为NULL

NULL是C语⾔中定义的⼀个标识符常量,值是0,0也是地址,这个地址是⽆法使⽤的,读写该地址 会报错。

  • 11
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值