C语言指针必备基础全面覆盖

 更新时间:2021年10月25日 16:42:59   作者:波风张三  
数据对象是指存储在内存中的一个指定数据类型的数值或字符串,它们都有一个自己的地址,指针是保存这个地址的变量,本篇文章带你掌握C语言指针的用法

前言

指针是C语言中的一个重要概念。正确而灵活的运用指针,可以使程序间接、紧凑、高效。每一个学习和使用C语言的人,都应当深入地学习和掌握指针。

提示:以下是本篇文章正文内容,下面案例可供参考

一、指针是什么?

指针是包含内存地址的变量,这个地址是内存中另一个对象(通常是另一个变量)的位置。例如如果一个变量包含另一个变量的地址,我们说第一个变量指向第二个变量。

相信大家看到上面这段话,可能有点懵,不急,我稍后再给大家解释。在这里,我先给大家讲述一下,数据在内存中是如何存储和读取的?

1.数据在内存中的存储

如果在程序中定义了一个变量,在对程序进行编译的时候,系统就会给这个变量分配内存单元。编译系统根据程序中的定义的变量类型,分配一定长度的空间

在这里插入图片描述

那么,这些字节在内存中被分配到哪里?我们如何找到呢?
为了解决这个问题,我们就给内存区的每一个字节一个编号,这个就是它们的“地址”。它相当于旅馆中的房间号,在地址所标志的内存单元中存放的数据则相当于旅馆房间中居住的旅客。

在这里插入图片描述

所以指针是个变量,存放内存单元的地址(编号)

2.一个小的单元到底是多大?

1、对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电平(低电压)就是(1或者0);

2根地址线上的电信号转换成数字信号用(1/0)表示,所以可能性
00000000000000000000000000000000–11111111111111111111111111111111
也就是有2^32 编号,说明可以管理2的32次方个单元
这里就有2的32次方个地址。

每个地址标识一个字节,那我们就可以给
(2^32Byte == 2^32/1024KB == 2^32 /1024/1024MB==2^32/1024/1024/1024GB == 4GB) 4G的空闲进行编址。

按照同样的方法,我们可以计算出64四位机器,下面就直接给结论了。

1、在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所 以一个指针变量的大小就应该是4个字节。
2、在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址

指针的大小在32位平台是4个字节,在64位平台是8个字节。

二、指针变量

1.什么是指针变量

思考一个问题,在编译器中,如何把3赋值给i这个变量中?

第一种作法,把3直接送到i所表示的单元中,例如“i=3”;

int main()
{
	int i=3;
	return 0;
}

第二种方法,把3送到变量p所指向的单元(即变量i的存储单元,也就是地址,如p=3,其中i表示p指向的对象)

int main()
{
	int i;
	//int i = 3;//第一种方法
	int *p = &i;//第二种方法
	//这里我们对变量a,取出它的地址,可以使用&操作符。
   //将i的地址存放在p变量中,p就是一个指针变量。
	*p = 3;
	printf("%d\n", i);
	return 0;
}

在这里插入图片描述

2.指针类型

思考一个问题:

把int型变量a和float型变量b先后分配到2000开始的存储单元中,&a和&b的信息完全相同吗?

答案是不相同的,因为虽然存储单元的编号相同,但他们的数据类型不同。

此外,还因为数据类型的不同,无法确定是从一个字节中取信息(字符数据),还是从两个字节取信息(短整型),抑或是从四个字节取信息(整型),不同的类型,存储方式是不一样的。

如果我们要将&num(num的地址)保存到p中,我们需要我们给指针变量相应的类型。

如下:

char  *pc = NULL;//har* 类型的指针是为了存放 char 类型变量的地址。
int   *pi = NULL;//int* 类型的指针是为了存放 int 类型变量的地址。
short *ps = NULL;//short* 类型的指针是为了存放 short 类型变量的地址
long  *pl = NULL;
float *pf = NULL;
double *pd = NULL;

这里可以看到,指针的定义方式是: 类型名 * 指针变量名 。

【总结】

C语言中的地址包括位置信息(内存编号,或称纯地址)和它所指向的数据的类型信息,或者说它是“带类型的地址”,如&a,一般称它位“变量a的地址”,但是确切地说,它是“整型变量a的地址”

3.指针类型的作用

作用一:

指针类型决定了指针解引用操作的时候,一次访问几个字节(访问内存的大小)

int main()

{	int a = 0x11223344;
	int* pa = &a;
	*pa = 0;
	return 0;
}

在这里插入图片描述

int main()
{
	int a = 0x11223344;
/*	int* pa = &a;
	*pa = 0;*/
	char* pc = &a;//int*
	*pc = 0;
	return 0;
}

在这里插入图片描述

指针类型的意义1

指针类型决定了指针解引用操作的时候,一次访问几个字节(访问内存的大小)
char* 指针解引用访问1个字节
int* 指针解引用访问4个字节

作用二:

指针类型决定了,指针±整数的时候的步长(指针±整数的时候,跳过几个字节)

int main()
{
	int a = 10;
	int * pa=&a;
	char *pc = &a;
	printf("%p\n", pa);
	printf("%p\n", pc);

	printf("%p\n", pa+1);//如果是整型指针int*,+1则跳过4个字节、
	printf("%p\n", pc+1);//char* 指针+1,跳过1个字节
	return 0;
}

在这里插入图片描述

三、野指针

1.什么是野指针

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

什么意思?举个例子
就是你捡到一把钥匙,但是不知道它可以开那道门。

2.野指针成因

2.1. 指针未初始化

指针没有初始化,里面放的是随机值

#include <stdio.h>
int main()
{ 
 	int *p;//局部变量指针未初始化,默认为随机值
    *p = 20;//通过p中存的随机值作为地址,找到一个空间,这个空间不属于我们当前的程序,就造成了非法访问
//如果非法访问了,p就是野指针
 return 0; }

2.2指针越界访问

指针越界造成野指针问题

int main()
{
	int arr[10] = 0;
	int i = 0;
	int * p = arr;

	for (i = 0; i <= 10; i++)//这里循环了11次,当指针指向的范围超出数组arr的范围时,p就是野指针
	{
		*p = 1;
		p++;
	}
	return 0;
}

2.3指针指向的空间释放

当一个指针指向的空间释放了,这个指针就变成野指针了

int* test()
{
	int a = 10;
	return &a;  //int *,生命周期,出来就销毁了
}
int main()
{
	int *p = test();
	//printf("不愧是你\n");//加入这里加了一条语句,下面的值就变了
	printf("%d\n", *p);//编译出10是因为编译器会对值做一次保留。所以能访问到上面函数不一定是对的
	return 0;
}

3.如何规避野指针

  • 指针初始化
  • 小心指针越界
  • 指针指向空间释放即使置NULL
  • 避免返回局部变量的地址
  • 指针使用之前检查有效性
//规避野指针
int main()
{
	int a = 10;
	int * p = &a;//1、明确初始化,确定指向

	int * p2 = NULL;//NULL本质是0,2、不知道一个指针当前应该指向哪里是,可以初始化位NULL
	//*p2 = 100;//err,对于空指针,是不能直接解引用的

	//如何规避? 
	if (p2 != NULL)//先判断是不是空指针
	{
		*p2 = 100;//这样才对
	}

}

四、指针运算

1.指针±整数

int main()
{
	float arr[5];
	float *p;
	for (p = &arr[0]; p < &arr[5];)
	{
		*p++ = 0;//对一个指针加1使它指向数组中的下一个元素,把指针指向的内容全部赋值给0
	}
	return 0;
}

在这里插入图片描述

也就说,如果加2使它向右移动2个元素的位置,依次类推。把一个指针减去2使它向左移动2个元素的位置。

2.指针-指针

1、指针减去指针的前提,是两个指针指向同一块区域
2、指针减去指针,得到数字的绝对值,是指针和指针之间元素的个数

int main()
{
	int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
	char ch[5] = { 0 };
	//printf("%d\n", &arr[9] - &ch[0]);//这种算法是错误的

	printf("%d\n", &arr[9] - &arr[0]);//算出的是元素的个数
	printf("%d\n", &arr[0] - &arr[9]);//
	//指针减去指针的前提,是两个指针指向同一块区域
	//指针减去指针,得到数字的绝对值,是指针和指针之间元素的个数

	return 0;
}

在这里插入图片描述

【注意】
指针与指针之间不能进行加法运算,因为进行加法后,得到的结果指向一个不知所向的地方,没有实际意义

什么意思,举个例子。

在这里插入图片描述

五、指针和数组

1.数组元素的指针

一个变量有地址,一个数组包含若干元素,每个数组元素都在内存中占用存储单元,它们都有相应的地址。

指针变量既然可以指向变量,当然也可以指向数组元素,也就是把某一元素地址放到一个指针变量中。

所谓数组元素的指针就是数组元素的地址

(1)用一个指针变量指向一个数组元素

int main()
{
	int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
	int* p;//定义p位指向整型变量的指针变量
	p = &arr[0];//把a[0]元素的地址赋给指针变量p
	return 0;
}

以上是使指针变量p指向a数组的第0号元素

在这里插入图片描述

2.通过指针引用数组元素

(1)下标法,如a[i]形式
(2)指针法,如*(a+i)

下标法:

int main()
{
	int arr[10];
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	printf("%d\n", sz);
	printf("%p\n", arr);//数组名就是首元素地址
	//下标法
	printf("%p\n", &arr[0]);
	int* p=&arr[5];//整型地址放在整型指针上,从而让指针跟数组建立联系

	//数组名确实是首元素地址,
	//但是有两个例外
	//1.sizeof(数组名),这里的数组名不是首元素地址,是表示整个数组,计算的是整个数组的大小,单位是字节
	//2.&数组名,拿到的是整个数组的地址

	return 0; 
}

在这里插入图片描述

指针法:

int main()
{
	int arr[10];
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);

	int* p=&arr[0];//整型地址放在整型指针上,从而让指针跟数组建立联系
	//指针法
	for (i = 0; i < sz; i++)
	{
		*(p + i) = i;// p+i 其实计算的是数组 arr 下标为i的地址。
	}
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));
	}
	return 0; 
}

在这里插入图片描述

一个小知识:

int main()
{

	int arr[10] = { 0 };
	arr;//数组名
	&arr[0];//取出首元素地址
	&arr;//取出整个数组的地址
	printf("%d\n", &arr[0]);
	printf("%d\n", &arr);
	return 0;
}

在这里插入图片描述

六、二级指针

指针变量的地址二级指针

什么意思?举个例子

int main()
{
	int a = 10;//4byte,向内存申请4个字节
	int* p=&a;//p指向a,称为一级指针

	int* *pp=&p;//pp就是二级指针,pp存放的是一级指针的地址
	* *pp = 20;//需两层解引用	
	printf("%d\n", a);
	//int** * ppp = &pp;//ppp就是三级指针
	return 0;
}

七、指针数组

存放指针的数组就是指针数组

int main()
{
	int arr[10];//整型数组,存放整型的数组就是整型数组
	char ch[5];//字符数组,存放字符的数组就是字符数组
	//指针数组,存放指针的数组就是指针数组
	//int*  整型指针的数组
	//char* 字符指针的数组

	int* parr[5];//整型指针的数组,存放的类型都是int*
	char* pc[6];//字符指针的数组
	return 0;
}

我们也可以用同样的方式来访问指针数组。
如下

int main()
{
	int a = 10;
	int b = 20;
	int c = 30;

	int * arr[3] = { &a, &b, &c };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		printf("%d\n",*(arr[i]));
	}

	int *pa = &a;
	int *pb = &b;
	int *pc = &c;
	return 0;
}

在这里插入图片描述

最后

本文介绍的是指针的基础知识,往后还会继续深入讲解指针更深入的知识。此外,本文参考了谭浩强《C语言设计》(第五版),以及网上的部分资料,加之自己在学习听课时的笔记,梳理而成,花费了我很多心思。当文章写成之时,时间已过去4个多小时!

希望能对看到的大家有所帮助!

以上就是C语言指针必备基础全面覆盖的详细内容,更多关于C语言 指针的资料请关注脚本之家其它相关文章!

相关文章

  • 引用numpy出错详解及解决方法

    引用numpy出错详解及解决方法

    这篇文章主要介绍了引用numpy出错 解决方法的相关资料,需要的朋友可以参考下
    2017-02-02
  • C++ OpenCV实战之车道检测

    C++ OpenCV实战之车道检测

    这篇文章主要介绍了基于C++ OpenCV实现的车道检测,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-01-01
  • Qt中const QString转换 char *可能的坑

    Qt中const QString转换 char *可能的坑

    本文主要介绍了Qt中const QString转换 char *可能的坑,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-07-07
  • Qt读取和写入配置(ini)文件

    Qt读取和写入配置(ini)文件

    ini文件在windows系统中可以存储需要持久保存的配置信息,本文主要介绍了Qt读取和写入配置(ini)文件,具有一定的参考价值,感兴趣的可以了解一下
    2023-09-09
  • C语言中回调函数和qsort函数的用法详解

    C语言中回调函数和qsort函数的用法详解

    这篇文章主要为大家详细介绍一下C语言中回调函数和qsort函数的用法教程,文中的示例代码讲解详细,对我们学习C语言有一定帮助,需要的可以参考一下
    2022-07-07
  • C++实现简单扫雷小游戏

    C++实现简单扫雷小游戏

    这篇文章主要为大家详细介绍了C++实现简单扫雷小游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-09-09
  • C++实现defer声明方法详解

    C++实现defer声明方法详解

    这篇文章主要介绍了C++实现defer声明,在和朋友交谈时候,无意间了解到Go语言的defer,发现挺有意思的。和智能指针类似,当出了作用域后,被defer修饰的操作才会执行
    2022-11-11
  • c++ 开发中如何读写yaml配置文件

    c++ 开发中如何读写yaml配置文件

    这篇文章主要介绍了c++ 开发中如何读写yaml配置文件,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-04-04
  • Qt使用QJson模块实现解析Json文件

    Qt使用QJson模块实现解析Json文件

    在项目开发过程中,经常会遇到读写Json文件的需求,掌握Json文件的操作是基础中的基础,下面我们就来看看如何使用QT内置的QJson模块解析Json文件吧
    2023-10-10
  • 基于Matlab实现山脊图的绘制

    基于Matlab实现山脊图的绘制

    这篇文章主要介绍了如何利用Matlab实现山脊图的绘制,文中的示例代码讲解详细,对我们学习Matlab有一定的帮助,需要的可以参考一下
    2022-05-05

最新评论