【入门C语言第五话】初阶指针

本文详细介绍了指针的概念、类型、野指针的成因及规避策略,包括指针运算(整数加减、指针比较)、指针与数组的关系,以及二级指针和指针数组的应用实例。通过实例演示,帮助读者理解内存地址、不同类型指针的作用和常见陷阱。
摘要由CSDN通过智能技术生成

前言

在这里插入图片描述

正文

一.指针的定义

1.内存

注意:内存指的是电脑的运行内存,一般是8GB或者16GB。
误区:我们电脑的储存空间叫硬盘,一般是512GB。

2.地址

所有的申请的内存都会有地址,就像你从图书馆借来的书一样,每本都有自己的编号,便于查找与保存,那内存的管理就像是一个图书馆的书一样,想象一下如果你是图书管理员你会给他们分类编号吗?答案:毋庸置疑。在计算机中编号可是16进制的哦!

3.计算机科普——32位和64位

计算机处理的最大位数(二进制)有32位和64位。分别为2的32次方和2的64次方,说明了机器能产生的地址数。2的32次方个字节为4GB,64位支持高达亿位数的内存,但在实际电脑的使用中32位识别3.5GB,64位最多识别128GB。

4.指针

我们将地址存在变量中,将这个变量称之为指针。在32位机器中指针为32比特也就是4字节,64位机器中64比特也就是8字节。(一个字节为最小内存单元)
图解:在这里插入图片描述
实际:
在这里插入图片描述

二.指针类型

变量有整形int,char,float,double等,那指针有什么类型呢?
先举个跟int有关的吧!
例一:

int main()
{
	int a = 0;
	int* p = &a;
	*p = 1;//解引用操作,相当于a=1;
	return 0;
}

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

实际:
在这里插入图片描述
例2:

int main()
{
    int b = 0x11223344;
    int *p1 = &b;
    *p1 = 1;
	int a = 0x11223344;
	char* p = (char*)&a;
	*p = 1;
	printf("%d %x", b, a);
	return 0;
}

它们打印的值一样吗?
我们从内存的角度观察一下,
这是b解引用后的值:
在这里插入图片描述
这是a解引用后的值:
在这里插入图片描述
这里我们可以总结:
指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。
总类型我们一 一列出:

char  *pc = NULL;//这里的NULL是为了规范而写的,保证指针的安全使用。
int   *pi = NULL;
short *ps = NULL;
long  *pl = NULL;
float *pf = NULL;
double *pd = NULL;

三.野指针

概念:定义或者说使用指针时,访问到不是已申请的空间,一般都会报错。

1.成因

指针是一把双刃剑,用的好皆大欢喜,用的不好,叫苦连连!
一:直接定义指针或者在申请变量时,不初始化为变量的地址或者空指针(NULL)。

#include <stdio.h>
int main()
{ 
 int *p;//局部变量指针未初始化,默认为随机值
    *p = 20;
 return 0;
}

二:访问到不是此指针范围的值时

#include <stdio.h>
int main()
{
    int arr[10] = { 0 };
    int* p = arr;
    int i = 0;
    for (i = 0; i <= 11; i++)
    {
        //当指针指向的范围超出数组arr的范围时,p就是野指针
        *(p++) = i;
    }
    return 0;
}

2.规避建议

  1. 指针初始化(不使用时置为NULL)
  2. 小心指针越界
  3. 指针指向空间释放,及时置NULL
  4. 避免返回局部变量的地址
  5. 指针使用之前检查有效性
#include <stdio.h>
int main()
{
    int* p = NULL;//定义在变量a之前,最好初始化为空指针(不能进行解引用操作)
    int a = 10;
    p = &a;//使用再进行赋值
    if (p != NULL)//判断是否为空指针,不为空才可进行解引用操作!
    {
        *p = 20;
    }
    printf("%d", a);
    return 0;
}

四.指针的运算

1.指针加减整数

#include <stdio.h>
int main()
{
	int arr[] = { 0, 1 };
	printf("整形:\n");
	printf("%p\n", arr);
	printf("加一后:%p\n", arr + 1);
	char arr1[] = { 0,1 };
	printf("字符类型:\n");
	printf("%p\n",arr1);
	printf("加一后:%p\n", arr1 + 1);
	return 0;
}

结果:
在这里插入图片描述
我们可以得到如下结论:类型的指针决定了加一往后加几个字节。

2.指针减指针

int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;
	int* p1 = p+1;
	printf("%d\n", (int)p);
	printf("%d\n", (int)p1);
	printf("%d",p1-p);
	return 0;
}

结果:
在这里插入图片描述
总结:这里不是4而是1,恰好是相邻变量的个数(语法规定)。
我们也可以这样理解:p1-p=(arr+1)- arr等于1。(仅供参考)

3.指针的关系比较

因为指针本质上是地址一个16进制的数字,因此也可以用来比较。
下面:我们用指针初始化一下数组
法1:

int main()
{
	//用指针模拟初始化
	int arr[10];//里面可是随机值哦!
	int* i = 0;//这里的i可是地址哦!
	for (i = arr; i < &arr[10];)//与数组的最后地址的后面的一个地址作为临界条件
	{
		*(i++) = 0;//先将i解引用后赋值为0,然后再将i+1
	}
	int j = 0;
	for (j = 0; j < 10; j++)
	{
		printf("%d ", arr[j]);
	}
	return 0;
}

法2:

int main()
{
	//用指针使数组初始化
	int arr[10];
	int* i = 0;
	for (i = &arr[9]; i >(arr-1);)
	{
		*(i--) = 0;
	}
	int j = 0;
	for (j = 0; j < 10; j++)
	{
		printf("%d ", arr[j]);
	}
	return 0;
}

两种方法都可完成任务,但是第二种方法不建议,因为标准规定,嘿嘿。
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。

五.指针和数组

int main()
{

	int arr[10] = { 0 };
	int arr1[3][3] = { 0 };
	int i = 0;
	int j = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}
	for (i = 0; i < 10; i++)
	{
		printf("%d ", i[arr]);
	}
	printf("\n\n");
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 3; j++)
		{
			printf("%d ", arr1[i][j]);
		}
		printf("\n");
	}
	return 0;
}

看到这你可能,没感觉到数组跟指针有什么关系,下面可能就懂了。
注意:[ ]下标引用操作符,操作数可是有一个指针的哦!arr为数组首元素的地址,还有特殊的两种可以去看数组理论篇哦!
点击进入
图解:
在这里插入图片描述
那二维数组呢?
同理:arr[i]这里指的是行的首元素的地址
在这里插入图片描述

六.二级指针

我们都知道在函数里面如果要修改变量的值,需要传地址,那如果我们还要修改指针呢?
这里就引出了二级或多级指针。
注:我们使用的一般不超过三级指针。

int b = 0;
void fun(int** p2)
{
	**p2 = 1;//将a的值改为1
	*p2 = &b;//将p的地址改为b的地址
	**p2 = 2;//将b的值改为2
}
int main()
{
	int a = 0;
	int* p = &a;
	int** p1 = &p;//将p的地址存入p1
	printf("%p\n", p);
	printf("%#X\n", (int)p);
	//细节:我们打印的时候一般地址是不会前置0x的
	fun(p1);//传入的是形参,实参的临时拷贝
	printf("%d\n", a);
	printf("%d\n", b);
	return 0;
}

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

七.指针数组

数组是存放相同元素的集合,那把数组里面的元素换成指针,就是指针数组了。所以说数组指针是存放指针的数组,是数组。
那该怎么写呢?
举例1:
将内存不连续的值串起来,就像串糖葫芦一样。

int main()
{
	int a = 0;
	int b = 1;
	int c = 2;
	int d = 3;
	int* arr[] = { &a,&b,&c,&d };//这里是int* 类型 的数组,只能存int*的元素
	int i = 0;
	for (i = 0; i < 4; i++)
	{
		printf("%d ", *arr[i]);//存的是指针自然可以进行解引用
	}
	return 0;
}

结果:
在这里插入图片描述
举例2:
将一维数组用指针改为二维数组

int main()
{
	int arr[9] = { 1,2,3,4,5,6,7,8,9 };
	int* arr1[] = { arr,&arr[3],&arr[6] };
	int i = 0;
	int j = 0;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 3; j++)
		{
			printf("%d ", arr1[i][j]);//注意下标引用操作符的应用哦!
		}
		printf("\n");
	}
	return 0;
}

结果:
在这里插入图片描述

总结

如果能认真看到这里,我坚信你颇有收获!也希望这篇文章能帮助到你,如果觉得不错,请点击一下不要钱的赞,如果有误请温柔的指出,在这里感谢大家了!

评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值