C语言入门——指针初阶(1)

在C语言中,有一种非常重要的数据类型——指针。指针用于存储变量的内存地址,并且允许你直接访问内存中的数据。同时,指针也是C语言的一大难点之一,我会尽量总结出一篇或几篇清晰易懂的博客,希望对各位读者有所帮助。

1.什么是指针

大家都知道,我们电脑的内存被划分成了许多1个字节大小的内存单元,就像我们每家每户的门牌号一样,每个内存单元都有一个唯一的编号,我们把这个编号叫做地址,也叫做指针。而平时我们说的指针通常指的是指针变量,也就是用来存放内存地址的变量。通过指针,你可以不使用变量名来直接读取或者修改内存中的数据。

指针的大小在32位平台是4个字节,在64位平台是8个字节。这是为什么呢?

我们的电脑中有着许多地址线,可以把电信号转化为数字信息,高电平定为0,低电平定为1,则32根地址线就有2的32次方种排列顺序,64根地址线就有2的64次方种排列顺序,将这样用0和1组成的二进制序列就作为内存空间编号。所以地址线越多,能够被编号的内存单元就越多,但是不代表64位的机器内存就一定比32位的大。

2.指针和指针类型

2.1 指针变量

2.1.1 取地址操作符

当我们使用取地址操作符&拿到了一个变量的地址之后,就需要使用指针变量把地址存储起来,指针变量是一种专门存放地址的变量,所以,存放在指针变量中的数值都会当成地址处理

​​​​#include <stdio.h>

int main()
{
    int a = 100;
    int *pa = &a;
    return 0;
}

其中,星号*说明pa是指针变量,int说明指针变量pa指向的对象是整型

2.1.2 解引用操作符

当我们将地址存储到指针变量中后,要想使用地址,就要用到解引用操作符*。

​​​​#include <stdio.h>

int main()
{
    int a = 100;
    int *pa = &a;
    *pa = 0;
    printf("%d", a);
    return 0;
}

例如这里,我们创建了一个整型变量a并赋值为100,然后在指针变量pa中存储了它的地址,接着使用解引用操作符直接使用指针变量pa修改内存中变量a的数值,最后的输出是

2.2 指针类型

变量有整型、浮点型等不同的类型,指针变量也有不同类型

​​​​char *pc = NULL;
int *pc = NULL;
long *pl = NULL;
float *pf = NULL;
double *pd = NULL;
short *ps = NULL;

如上,指针变量的类型是 type + *  ,且星号*的位置并没有严格的要求。

2.3 指针变量的大小

我们知道,指针变量是用来存放地址的,而地址的二进制位数由地址线来决定。32位平台中地址有32个bit位即4个字节,64位平台中地址有64个bit位即8个字节,这点我们可以通过实测来证明。

#include <stdio.h>

int main()
{
    printf("%zd\n", sizeof(char *));
    printf("%zd\n", sizeof(short *));
    printf("%zd\n", sizeof(int *));
    printf("%zd\n", sizeof(double *));
    return 0;
}

打开vscode,此时使用的是windows-gcc-x64的编译器,所以指针变量的大小是8个字节

打开vs2019,我们在x86的平台(32位)再测试一次,此时指针变量的大小变为了4。

总结:

  • 32位平台中地址是32个bit位,指针变量大小为4个字节
  • 64位平台中地址是64个bit位,指针变量大小为8个字节
  • 指针变量的大小和类型无关

2.4 指针类型的意义

既然指针变量的大小都是固定的,那为什么要分出这么多类别呢?实际上,除了可读性之外,指针变量的类型还有特殊的意义

调试以上两段代码,我们会发现前者只将a的第一个字节修改为0,后者可以将a的四个字节都修改为0。

这说明:指针的类型决定了其对指针解引用的时候有多大的权限,即使用解引用操作符进行操作的时候能修改几个字节。具体权限大小取决于sizeof(type),例如long long* 类型的指针解引用的时候就能修改8个字节。

2.5 指针+-整数

如上,变量n的地址为0x00DAFC78,pc是char*类型的指针变量,pi是int*类型的指针变量,我们会发现pc+1后原地址前进了1个字节,而pi+1后原地址前进了4个字节,这也是指针变量类型不同带来的差异。

这说明:指针变量的类型决定了指针进行加减操作的时候的步长(向前或向后走一步的距离)

3.野指针

概念:当一个指针指向的位置是不可知的、随机的、不正确的、没有限制的,这个指针就是野指针

3.1 野指针的成因

野指针的成因有多种,都是由于错误的或者不规范的操作而导致的。

3.1.1 指针未初始化

#include <stdio.h>

int main()
{
	int* p;
	*p = 10;
	return 0;
}

此处,我们创建了一个指针变量p,但是这个指针并没有进行初始化,也就是没有明确的指向一个地址。这里的指针p就是一个野指针,当我们试图运行这段代码的时候就会报错。

3.1.2 指针的越界访问

#include <stdio.h>

int main()
{
	int arr[10] = { 0 };
	int* p = arr;
	for (int i = 0; i < 12; i++)
	{
		*(p++) = i;
	}
	return 0;
}

以上代码就是指针越界访问的经典情况,我们创建了一个大小为10的arr数组,但是在循环中*p随着不断的++,指向的范围超出了数组的范围,此时p就是一个野指针。当我们试图运行这段代码的时候也会报错。

3.1.3 指针指向的空间被释放

我们都知道,变量有作用域和生命周期,当一个指针变量p指向了某个变量所占的一块空间,而这个变量在生命周期结束后就被销毁了,此时p也是野指针。

3.2 如何规避野指针

野指针虽然对程序的运行有危害,但是只要我们足够细心谨慎也是容易避免的。

3.2.1 指针初始化

当我们创建了一个指针变量,可以直接用地址初始化它。但是有时候我们创建指针变量后,不知道指针应该指向哪里(应该初始化什么值),这时就先初始化为NULL

#include <stdio.h>

int main()
{
	int a = 100;
	int* pi = &a;

	int* po = NULL;
	return 0;
}

这里的指针pi指向了a的地址,但是指针po我们不知道该指向哪,于是就用NULL给它初始化。这时的po是一个空指针,虽然避免了野指针,但此时的po没有指向任何有效的空间,不能直接使用。

NULL是C语言定义的一个常量,数值为0。虽然我们事实上的确可以用0来初始化指针,但是NULL可以让人一眼就知道我们在初始化指针,而0不行,这也是提升代码可读性的一个方法。

3.2.2 避免指针越界访问

这个没有什么好讲的,平时码代码的时候注意留心即可避免

3.2.3 指针指向的空间释放后及时置NULL

指针指向的空间释放后,这个指针就变成了一个野指针,好似一条野狗一样带着危险。但我们可以将这条野狗拴起来(赋值为NULL),就可以有效避免可能出现的问题。

但是就算将野狗栓了起来,当我们靠近它的时候还是有危险。同理,当我们将指针赋值为NULL时,虽然避免了野指针的产生,但是我们也是不能够使用这个指针的。所以在使用指针之前,先判断其是否为NULL,不是的话再去使用。

另外的,我们还要尽量避免去在函数中返回局部变量的地址,这可以帮助我们规避野指针。

4.指针的运算

4.1 指针+-整数

#include <stdio.h>

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* pi = &arr;
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", *pi + i);
	}
	return 0;
}

在上面的代码中,我们将arr的地址赋值给指针变量pi时,实际上是将数组的第一个元素的地址赋值给了pi。

接着我们在循环中用*pi+i,就可以用指针顺藤摸瓜找到后面所有的元素。运行代码试试吧

4.2 指针-指针

#include <stdio.h>

int main()
{
	int arr[10] = { 0 };
	int* p1 = &arr[9];
	int* p2 = &arr[0];
	printf("%d\n", p1 - p2);
	printf("%d\n", p2 - p1);
	return 0;
}

指针-指针得到的数值的绝对值,是两个指针之间的元素个数

我们试着运行一下这段代码看看结果是否符合上图

需要注意的是,指针与指针相减,前提是两个指针指向了同一块空间。

本篇到此结束,余下的讲解将会在下一章——指针初阶(2)中提到。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值