C语言指针介绍(一)


本篇文章将介绍指针的一些基础知识,包括指针的概念,指针变量的类型及其意义和指针运算等相关知识。

1.指针是什么

计算机中,内存被划分为一个个的内存单元,每个内存单元大小为一个字节,要想快速的找到一个内存单元,对每个内存单元进行编号是比较好的方法,这个内存单元的编号就被称为地址,而在c语言中,地址有一个新的名字——指针。

2.指针变量

2.1 取地址操作符

我们平时在创建变量的时候,其实就是向内存申请空间,那我们如何拿到变量的地址呢?这就需要一个操作符:&——取地址操作符。它的用法是&+变量名。

2.2 指针变量的创建及解释

取出来的地址是一个数值,那这个值放哪呢?指针变量中。
在这里插入图片描述这里的pa为创建的指针变量,* 的意思是表示pa为指针变量,int 表示指向的对象为整型。

2.3 解引用操作符

我们把地址存到了指针变量中,以后要想使用的话,就需要用解引用操作符 * 。通过* pa访问pa里面存的地址所指向的空间,而这块空间里放的是谁呢,是a,所以我们可以通过* pa来对a进行操作。
解引用前解引用后
上图中,使用* pa将a的值修改为5,* pa其实就相当于变量a。
另外,我们平常使用的时候,通常直接说pa是个指针,其实它是指针变量,注意一下这个常用的说法就好。

2.4 指针变量的大小

指针变量的大小取决于地址的大小。在32位平台下,地址是32个bit位,占4个字节;在64位平台下,地址是64个bit位,占8个字节。
注意,指针变量的大小和指针类型是没有关系的,只要是指针变量,就是4或8个字节。

3.指针变量的类型

3.1 指针的解引用

下面我们通过一个例子来看指针变量类型的意义。
在这里插入图片描述这里使用int*类型的指针来对a的值进行修改,注意到整形变量a的四个字节全被修改为0。
在这里插入图片描述在这个代码中,我们使用char * 类型的指针对变量a进行操作,只有一个字节被修改为了0。
其实,指针变量的类型决定了对指针解引用的时候有多大权限,即一次能访问多少个字节。比如char * 的指针一次能访问1个字节,int * 类型的指针可以一次访问4个字节。

3.2 指针±整数

同样,和指针的解引用一样,指针类型不同,指针+1所跳过的字节数目就不同,比如char *类型的指针+1跳过一个字节,int * 的指针+1跳过四个字节。

3.3 void * 类型的指针

void *是一种特殊类型的指针,可以理解为没有具体类型,他可以接收任意类型的地址,不过,void *指针不可以直接进行解引用和加减整数。
void *类型的指针一般用在函数的参数中,用来接收不同类型的数据的地址。

4.const修饰指针

变量的值是可以被修改的,但如果我们不希望变量的值被修改,那就要用到const。
在这里插入图片描述
在这里插入图片描述
看,当我们用const修饰指针变量的时候,通过 *pa来修改变量的值就不再起作用了。再想一想,这里const修饰的是 * pa,那pa能不能被修改呢?我们来试一下
在这里插入图片描述在这里插入图片描述
注意到,此时编译程序并没有报错,显然pa是可以修改的。
同样,如果是

int* const pa = &a;

这样const放在 *后面,限制的是pa不能被修改,而 *pa则不受限制,如果我们想让 *pa和pa都不可以修改,那么可以

	const int* const pa = &a;

5.指针运算

5.1 指针加减整数

5.2 指针减指针

当我们做指针-指针的运算时,得到的是元素个数。注意,指针减指针需要在指针类型相同时才可以进行运算。
如我们模拟strlen的实现。

#include <stdio.h>
int main()
{
	char arr[] = "abcdef";
	printf("%d\n", my_strlen(arr));
	return 0;
}
int my_strlen(char* p)
{
	char* s = p;
	while (*p != '\0')
	{
		p++;
	}
	return p - s;//指针减指针
}

注意,这里数组名一般是首元素的地址,所以在函数的形参部分用指针变量来接收地址,关于数组名的理解后面会有详细介绍。

5.3 指针的关系运算

指针就是地址,所以指针比较大小其实就是比较的地址的大小。

#include <stdio.h>
int main()
{
	char arr[] = "abcdef";
	int sz = strlen(arr);
	char* pa = arr;
	for (pa = arr; pa < arr + sz; pa++)//pa < arr + sz 就是指针比较大小
	{
		printf("%c", *(pa));
	}
	return 0;
}

6.野指针

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

6.1 野指针成因

1.指针未初始化

#include <stdio.h>
int main()
{
	int* pa;//这里未对指针变量初始化
	*pa = 20;
	return 0;
}

2.指针越界访问
比如在用指针访问数组时,如果超出了数组的范围,那它就是野指针

3.指针指向的空间释放

#include <stdio.h>
int* test()
{
	int a = 0;
	int* pa = &a;
	return pa;
}
int main()
{
	printf("%p\n", test());
	return 0;
}

这里a是一个局部变量,出了这个test函数a所在的空间就被释放了,这个时候得到的这个地址就没有用了,而且如果后面再去用这个地址访问的话,就修改了别的东西了。

6.2 规避野指针

6.2.1 指针初始化

如果我们我知道指针变量要指向的地址,可以直接给它初始化,如果不知道的话,可以赋值NULL。
NULL的值为0,C语言中,NULL也是个地址,不过这个地址不可用。

6.2.2 避免指针越界

6.2.3 当指针变量不用的时候,及时赋值NULL

当我们不再使用指针变量访问空间的时候,可以把指针置为NULL,而约定俗成的规则是当指针为空指针时,不去使用。

6.2.4 避免返回局部变量的地址

7.assert

assert是在程序运行的确保符合指定条件的,如果不符合,就报错停止运行。assert定义在头文件<assert.h>中,它常常被称为“断言”。
用法是 assert(表达式)
如果表达式为真,那么程序继续运行,如果表达式为假,程序就会终止运行。
在这里插入图片描述可以看到这里表达式为假,程序报错,还指出包含这个表达式的文件名和行号。
assert也可以不用更改代码就可以关闭或开启,只需要定义一个宏NDEBUG,这样程序在运行时就会自动禁用assert语句。如果想重新启用,只需要把宏NDEBUG这一句注释掉就可以。
不过因为引入了assert断言,程序的运行时间增加了。

8.数组名的理解

8.1 一般

一般来说,数组名就是数组首元素的地址,我们来验证一下

#include < assert.h >
int main()
{
	int arr[10] = { 0 };
	int* pa = arr;
	int* pb = &arr[0];
	printf("%p\n", pa);
	printf("%p\n", pb);
	return 0;
}

在这里插入图片描述可以看到两个地址是完全一样的。

8.2 例外

#include < assert.h >
int main()
{
	int arr[10] = { 0 };
	printf("%d\n", sizeof arr);
	return 0;
}

这个代码结果是多少呢,是4吗?
在这里插入图片描述
运行结果显示40,这是因为数组名单独放在sizeof里的话,计算的是整个数组的大小,另外,&操作符后面如果只有数组名的话,取出的也是整个数组的地址。如

在这里插入图片描述
这里因为&arr是取的整个数组的地址,所以&arr + 1跳过了40个字节。
注意,这里arr和&arr显示的地址一样是因为数组首元素的起始地址和整个数组的起始地址是一样的。

总结一下就是一般数组名是表示首元素的地址,除了两个例外:
1.sizeof(数组名)
2.&(数组名)

本篇文章到此结束,进一步的内容将会在下一篇文章中详细讲解,如二级指针,字符指针,数组指针等。欢迎大家多多提建议和交流。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值