IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C语言之玩转指针(初阶) -> 正文阅读

[C++知识库]C语言之玩转指针(初阶)

现在,我们终于来到了指针——C语言的精髓所在,当然也是C语言的核心所在。其重要程度就不必多说了。在C语言中,指针提供了动态操控内存的机制,强化了对数据结构的支持,且实现了访问硬件的功能。但是指针的这种能力以及其灵活性是有代价的,它很难掌握。不过也不要担心,本文将以循序渐进的方式带大家来初步的学习指针。

1.什么是指针以及指针和指针类型。

什么是指针?从根本上讲,指针(pointer)是一个值为内存地址的变量。如int类型变量的值为整数、char类型变量的值为字符、而指针变量的值是地址。
在计算机科学中,指针式编程语言的一个对象,利用地址,它的值直接指向存在电脑存储器中另一个地方的值,通过地址可以找到所需的变量单元,可以说地址指向该变量单元,总之将地址形象化的称为"指针",在将来我们可以通过它找到以它为地址的内存单元。

在真正讲解指针之前,我们有必要了解一下内存。

下面来看一个简单的代码
在这里插入图片描述
在我们初始化变量a时,即int a = 10,此时此刻在内存中开辟了一块空间,这里我们对变量a取出它的地址,可以使用&操作符,之后把a的地址放到变量p中去,此时变量p就是一个指针变量。
简单来说就是任何一个值,只要将其放在指针变量中去,那么这个值就会被当作地址来处理。

总结:指针就是变量,是用来存放地址的变量。(存放在地址中的值都会被当作地址处理)

下面来看各种类型指针的大小:

printf("%d\n",sizeof(int*));
printf("%d\n",sizeof(char*));
printf("%d\n",sizeof(double*));
printf("%d\n",sizeof(short*));

结果如下:
在这里插入图片描述
既然指针大小在这里都是8个字节,那我们为什么还区分那么多种类型的指针呢?例如:整型指针,字符型指针等等。具体如下代码:

int a=0x11223344;
int* pa=&a;
char* pc=&a;
printf("%p\n",pa);//结果为0000003F986FF504
printf("%p\n",pc);//结果为0000003F986FF504
那是不是就说明指针类型就没有意义呢,答案是否定的,它当然有意义,请看下面代码

在这里插入图片描述
当我们对指针变量pa解引用操作完成后(即*pa=0)发现变量a变成了0先记住这里,下面来看第二段代码:
在这里插入图片描述
注意看原来内存中的 44 33 22 11(4个字节),现在经过char*解引用后变成了
00 33 22 11,仅仅更改了1个字节;而经过之前的int*解引用后变成了00 00 00 00更改了4个字节。
所以,当类型发生变化时,我们对其解引用所产生的结果时不一样的,这是指针类型带来的区别之一,也是其意义之一。
当使用整型指针(即int*)进行解引用操作时,我们操作了4个字节后由44 33 22 11变成了00 00 00 00;但是如果是字符指针(即char*)只能操作1个字节,由原来的44 33 22 11变成了00 33 22 11。故指针类型意义一:指针类型决定了指针在解引用操作时能够访问空间的大小。即:

int* p; --*p能够访问4个字节
double* p;  --*p能够访问8个字节
char* p;  --*p能够访问1个字节
short* p;  --*p能够访问2个字节

下面来看一段代码:
在这里插入图片描述
在这里我们可以知道指针类型决定了指针类型+1能走多远(即决定了指针的步长)。。这也就是指针类型的意义二:指针类型决定了指针向前或向后走一步有多大。

总结:指针类型的意义:1.指针类型决定了对指针解引用时有多大的访问权限(即能操作几个字节)。2.指针类型决定了指针向前或向后走一步有多大。
在明白了指针类型的意义后,下面我们来看一段代码:

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

在这里插入图片描述
当我们把其中的int* p=arr;改为char* p=arr;后,我们再来看一下区别,请看:
aa在这里插入图片描述
这里我们很好看出区别:这里的char* =arr;相当于把之前的int* p=arr;中的两个半整型更改为了1。其根本原因是因为char*每次只能访问1个字节,而int*每次能访问4个字节。

以上就是对指针的基本认识。

2.野指针

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

2.1野指针成因

1.局部变量未初始化

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

2.指针越界访问

#include<stdio.h>
int main()
{
	int a[10]={0};
	int* p=a;
	int i=0;
	for(i=0;i<=12;i++)
	{
		//当指针指向的范围超过数组a的范围时,p就是野指针
		*p++=1;//注意这里为后置++
	}
	return 0;
}

这就是数组越界导致的野指针问题。
3.指针指向的空间被释放

int* test()
{
	int a = 20;//在test()函数中,变量a是一个局部变量a,而局部变量a在出这个函数时就会别销毁
	return &a;//也就是说出来这个函数时,局部变量a会把之前所占据的空间还给内存,既然还给了内存,那么局部变量a就不能使用了。
}
//所以当我们再在主函数中利用*p去访问变量a的空间时,此时就是非法访问,因为这块空间已经不属于变量a了。
#include<stdio.h>
int main()
{
	int* p = test();
	printf("%d", *p);
	return 0;
}

在这里插入图片描述
这里也许程序会正常运行(错误很隐晦是编译器可能会认为正确),但是这段代码简直是大错特错
如果大家还不明白,我给大家举一个生活中的例子:比如小明交了个女朋友叫小兰,但是好了一段时间后,小兰因为又找了个男朋友就把小明给踹了,又由于小明在与小兰交往的时候把小兰的电话号码(指针)记了下来,于是小明就每天给小兰打电话,这难道不是非法骚扰吗。

2.2如何规避野指针问题

1.指针初始化
例如:

#include<stdio.h>
int main()
{
	int a = 10;
	int* p = &a;
	//如果我们不知道该给指针变量p初始化谁时我们可以给它初始化成空指针NULL
	int* p = NULL;//NULL是用来初始化指针的,是用来给指针赋值的
	return 0;
}

2.小心指针越界
3.指针指向的空间如果释放,则我们把指针置为空,即NULL

#include<stdio.h>
int main()
{
	int a = 10;
	int* pa = &a;
	*pa = 20;//将a改为20后,我们如果不想让指针指向其它地方时,或者指针指向的那块空间已经还给操作系统时,我们就可以把这个指针置为空指针。
	pa = NULL;
	return 0;
}

当指针pa被置为空指针时,倘若要强行访问指针pa所指向的空间,此时程序很可能会崩溃掉。
例如:

#include<stdio.h>
int main()
{
	int a = 10;
	int* pa = &a;
	pa = NULL;
	*pa = 50;
	return 0;
}

4.指针使用之前检查有效性。

3.指针运算

3.1指针±整数

下面拿代码进行演示:
在这里插入图片描述
倘若我们把p=p+1(或者p++)换成p+=2呢?我们看看会发生什么:
在这里插入图片描述
倘若用减的方式呢
在这里插入图片描述
下面在举一个指针+-的例子:
在这里插入图片描述
上述代码的功能是这样的:
在这里插入图片描述

3.2指针-指针

指针-指针就是地址-地址,但是得到的是其中间的元素个数,请看:

在这里插入图片描述
如果改为&arr[0]-&arr[9];呢?请看:
在这里插入图片描述

所以说如果是小地址减去大地址的话,得出来的数的绝对值是其中间元素的个数。
我们说指针-指针一定是指向同一个数组中的空间,那我们能不能这样写呢:
在这里插入图片描述
这样的写法是大错特错的,这样做的话带来的结果是不可预知的。这样的代码实际上也没多大的意义和价值。

下面来看这段代码(求字符串长度的一种实现方式):
在这里插入图片描述

3.3指针的关系运算

我们先观察这两种代码的一个不同:
在这里插入图片描述

这两种代码看似相同,但是未来我们非要在这两种代码之间做选择的话,我们应该选择第一种的代码。实际上在大部分的编译器上是可以顺利完成任务的,然而我们应该避免写成第二种代码,因为标准并不保证它可行。
标准规定:允许指向数组元素的指针与指向最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。

在这里插入图片描述
也就是说指针p1可以和指针p2进行比较,但是不能拿指针p1和指针p3进行比较。
所以说刚刚代码中的第一种写法是不存在用指针p1和指针p3进行比较的。

4.指针和数组

我们知道,数组名是首元素的地址。下面我们再次来简单的证明一下:
在这里插入图片描述
一般情况下,数组名的确是首元素的地址。但这里有两个例外

1.&arr–即&数组名–这里的数组名arr不是首元素地址,而是整个元素的地址,即&arr取出的是整个数组的地址(数组首元素地址和整个数组地址下面再来详细介绍。)
2.sizeof(arr)–即sizeof(数组名)–此时的数组名arr表示的是整个数组–sizeof(数组名)计算的是整个数组的大小。

再来看一段代码:
在这里插入图片描述
看到这里有些读者多少会有一些疑惑,既然数组名代表着首元素的地址,而&数组名代表着整个数组的地址,那为什么第一个结果和第三个结果打印出来的地址是一样的呢?下面请再来看一段代码:
在这里插入图片描述
在这里插入图片描述
相信看到这里大家对整个数组的地址有了一个比较清晰的认识。
结论1.数组名和数组首元素的地址是一样的。(数组名表示的就是数组首元素的地址) 2.数组名表示的是首元素的地址时有两个例外:sizeof(数组名),此时的数组名表示的是整个数组;(&数组名)中的数组名表示的是整个数组,即(&数组名)取出来的是整个数组的地址。

既然我们可以把数组名当成一个地址存放到一个指针中,那我们使用指针来访问一个数组就成为了可能,这时我们就可以把数组和指针联系起来。

int arr[10] = { 0 };
int* p = arr;//此时数组arr就可以通过指针变量p来进行访问
//即数组是可以通过指针来访问

下面我们通过一段代码来把指针和数组联系起来:
在这里插入图片描述
我们可以发现&arr[i]取出来的地址跟我们的指针变量p作为首元素地址,再来通过p+i取出来的地址是一样的。
所以我们当然通过指针来访问数组了
比如:
在这里插入图片描述
总结:我们可以通过指针来访问数组来进行一系列的操作。

5.二级指针

我们之前学过了一级指针,比如:

int a = 10;
int* pa = &a;//pa就是一级指针变量,int*就是一级指针变量类型

我们来看上述代码中的变量pa,它是一个一级指针变量,既然是一个变量的话,就需要在内存中开辟一块空间。那我们能不能&pa呢?即从内存中拿到pa这块空间的地址,我们如果要把从内存中拿到pa这块空间的地址存起来应该怎么做呢?假如说我们要把一级指针变量pa的地址存放到ppa中,我们可以这样做:int** ppa = &pa;这里的ppa就是一个二级指针变量,而int**就是二级指针变量类型。依次类推我们当然也可以把二级指针ppa的地址存放起来,即int*** pppa = &ppa;。那么四级指针、五级指针、六级指针等等我们都可以写出来。
上述中的代码是这样的:

int* pa = &a;
int** ppa = &pa;
int*** pppa = &ppa;

可以配合下面这张图来进行理解:
在这里插入图片描述

指针变量也是变量,既然是变量就会有地址,那指针变量的地址存放到哪里呢?这就是二级指针。

那二级指针怎么使用呢?还是以上面的代码为例,倘若我们想要通过二级指针变量ppa来打印变量a中的值10,我们可以这样:
在这里插入图片描述
我们可以这样理解上述代码:*ppa的意思是对ppa进行解引用操作来找到二级指针变量ppa指向的对象pa,在对*ppa进行解引用操作即**ppa就可以找到变量a中存储的值(10)了。
我们也可以通过二级指针变量ppa来改变变量a中的值,请看:
在这里插入图片描述
注意此时变量a中的值就变为了20
所以二级指针只不过是用来存放一级指针变量地址的东西。

6.指针数组

指针数组,指针数组,那它当然是一个数组了,只不过数组中存放的是指针而已,所以大家在学指针数组时不要害怕😰,指针数组本质上就是一个数组,是一个存放指针的数组

int a = 10;
int b = 20;
int c = 30;
int* pa = &a;
int* pb = &b;
int* pc = &c;

我们可以通过指针数组把变量a b c的地址存放到一起,即存放到一个指针数组中去。请看:

int a = 10;
int b = 20;
int c = 30;
int arr1[10];//arr1是一个存放整型的数组
int* arr2[3] = { &a,&b,&c };//arr2是一个存放整型指针的数组

我们也可以通过指针数组把变量a,变量b,变量c中的值打印出来。请看:
在这里插入图片描述
这里一定要注意,arr2[i]代表的是变量a,变量b,变量c的地址,所以我们还需要对其进行解引用操作,即*(arr2[i])才能找到变量a,变量b,变量c中的值,并将其打印。
在这里插入图片描述
最后,初阶部分的指针学习到这里就完全可以了。后面会给大家带来指针的进阶部分。(如若文章有误,欢迎大佬们指出。)

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-11-05 00:06:35  更:2022-11-05 00:08:28 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 7:06:14-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码