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语言精讲篇5:指针 -> 正文阅读

[C++知识库]从零开始C语言精讲篇5:指针


在这里插入图片描述

一、指针是什么?

在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向
(points to)存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以
说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址
内存单元

我们的内存是类型下图这样的空间:
在这里插入图片描述
我们在使用的过程中,把内存划分成一个个小的字节,每个字节称为一个内存单元。我们给每个内存单元进行编号,每个编号唯一对应一个内存单元。类似身份证,每人一个身份证,我有这个身份证就可以唯一确定你这个人。这里也是类似的,地址指向了一个确定的内存空间,所以地址也被形象的被称为指针。

举个例子:

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

在这里插入图片描述
int* pa=&a,也就是pa里面放的a的地址(指针),pa是(整形)指针变量,它是用来存放指针的


在这里插入图片描述

二、指针和指针类型

int main()
{
	int a = 0x11223344;//0x表示16进制
	//一位16进制对应四位2进制:2*2*2*2=16
	//我们这里a一共8位16进制,也就是正好把32位2进制占满了(8*4=32)
	int* pa = &a;
	*pa = 0;
	return 0;
}

我们按f10进行调试,监视一下,可以看到我们pa里面存放的就是a的地址
在这里插入图片描述
监视一下内存,可以看到,我们内存中经过代码pa = 0; 32位是全部变成了0
ps:下图中我们是16进制一共8位,对应2进制32位
在这里插入图片描述
经过
pa = 0;赋值后
在这里插入图片描述

那么问题来了,我们之前说过,32位下指针大小都是4字节,64位下指针大小都是8字节。
假如我们都在32位下,我能否用char*来接收这个int型的地址(指针)呢?

我们仅把上面代码中的int * 改成 char * 来测试一下
在这里插入图片描述

可以看到放进去是没有问题的,但是如果要对内容修改呢?
在这里插入图片描述
经过*pa = 0;赋值后
在这里插入图片描述
为什么我这里只改动了一个字节呢?因为我是char * 的指针,我只负责我指向地址开始往后的1个字节。同理,如果我是int * 的指针,我只负责我指向地址开始往后的4个字节。

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

由于这个性质,我们来看看下面这个题目

int main()
{
	int a = 10;
	int* pa = &a;
	char* pa2 = &a;

	printf("%p\n", pa);
	printf("%p\n", pa2);

	printf("%p\n", pa+1);
	printf("%p\n", pa2+1);
	return 0;
}

在这里插入图片描述
可以看到,我们的pa虽然和pa2都是一个地址,但是当他们加1的时候,就受到了指针类型的管控。char * 指针+1只会跳1个字节,而int * 的指针+1则会跳4个字节。

指针类型的意义2:指针类型决定了指针±整数时的步长(跳过几个字节)

三、野指针

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

3.1野指针成因

1. 指针未初始化

int main()
{
	int* p;//没有初始化
	*p = 20;
	return 0;
}

在这里插入图片描述
如果你没有初始化p,那么指针p里面放的是随机的地址,你*p=20是想把随机地址里面的值改成20。但是我们这个随机地址是不属于我们当前程序的,就会造成非法访问,p就是野指针。

2. 指针越界访问

int main()
{
	int arr[10] = 0;
	int i = 0;
	int*p = arr;
	for (int i = 0;i <= 10;i++)
	{//数组元素下标是0-9,你如果访问了10,这个是不属于我们的空间
		//这就是越界访问
		*p = i;
		p++;
	}
	return 0;
}

在这里插入图片描述
3. 指针指向的空间释放
这个知识点笔者会在后面的动态内存释放进行详细讲解,这里简单提一下

int* test()//指针作为函数返回值
{
	int a = 10;
	return &a;
}
int main()
{
	int* p = test();
	printf("%d",*p);
	return 0;
}

我们在main函数里调用test函数,test函数返回a的地址。
需要注意的是,一旦返回了a的地址,test函数结束,a的生命周期也结束了
也就是说,a所在的空间返还给了操作系统。那么你在main函数中通过p再访问a之前的地址,这就是非法访问了。

打个比方:你和你好朋友出去旅游,然后你们开了一间房。在使用期内,你们想在房间里干啥都行。但是房间到期后,你再访问,旅店要告你的。

3.2规避野指针的方法

  1. 指针初始化
  2. 小心指针越界
  3. 指针指向空间释放即使置NULL
  4. 指针使用之前检查有效性

示例如下:

#include <stdio.h>
int main()
{
    int *p = NULL;//不知该该指向哪里,可以先初始化为NULL
    //....
    int a = 10;
    p = &a;//明确地初始化
    if(p != NULL)
   {
        *p = 20;
   }
    return 0;
}

四、指针运算

4.1指针±整数

#define N_VALUES 5
float values[N_VALUES];
float *vp;
//指针+-整数;指针的关系运算
for (vp = &values[0]; vp < &values[N_VALUES];)
{
     *vp++ = 0;
}

上面这段代码啥意思,请看下图的解释
在这里插入图片描述
我们在for循环中判断vp指向的地址是否小于&values[N_VALUES]
ps:地址本质是一个16进制数,可以用来比较

发现vp指向的地址小于&values[N_VALUES],我们就进行 *vp++,
需要注意的是,++的优先级是高于 * 的,所以我们本应先进行++再 *,
但我们这里是后置++也就是说vp在本轮还是指向数组0下标元素,然后 * 解引用赋值为0,
在这里插入图片描述
vp因为++了,所以指向了数组1下标元素
在这里插入图片描述

然后重复for循环,后面的都一样了,就是把数组元素赋为0,然后vp++直到vp指向3停止
(vp < &values[4])

4.2指针-指针

#include<stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	printf("%d\n", &arr[9] - &arr[2]);
	printf("%d\n", &arr[2] - &arr[9]);
	return 0;
}

指针-指针,得到的数字的绝对值,是指针之间元素的个数
在这里插入图片描述

注:肯定有一些杠精会问:“啊,你这个是两个相同类型的指针啊,如一个char类型指针-int类型呢?”

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

像上面这种代码,你自己想想有多亏贼?
首先你两个地址都是随机的,你怎么知道两个随机地址之间隔了多远?
还有一个问题就是,你两个不同类型指针,到时候划分元素个数是按1字节来化还是4字节来化?

指针-指针的前提:两个指针指向同一空间(比如指向同一数组)

具体应用实例:求字符串长度
法一:使用库函数strlen

//求字符串长度
//法一:使用库函数strlen
#include<stdio.h>
#include<string.h>
int main()
{
	char arr[] = "abcdef";
	int len = strlen(arr);
	printf("%d\n", len);
	return 0;
}

法二:使用指针

int my_strlen(char* p)
{
	int count = 0;
	while (*p != '\0')//字符串末尾都是默认有一个\0
	{
		count++;
		p++;
	}
	return count;
}
int main()
{
	char arr[] = "abcdef";
	int len = my_strlen(arr);
	printf("%d\n", len);
	return 0;
}

法三:使用指针-指针

int my_strlen(char* p)
{
	char* tmp = p;//标记arr[0]的位置
	while (*p != '\0')//字符串末尾都是默认有一个\0
	{
		p++;
	}//走完while,p指向arr的末尾元素
	int count = p - tmp;
	return count;
}
int main()
{
	char arr[] = "abcdef";
	int len = my_strlen(arr);
	printf("%d\n", len);
	return 0;
}

在这里插入图片描述

五、指针和数组

区别:
数组是一块连续的空间,里面放的是相同类型的元素。
数组大小和元素类型,元素个数有关系,比如int arr[10],该数组大小=4*10

指针是一个变量,里面存放地址
指针变量的大小是4/8byte,这个取决于是32位系统还是64位

联系:
数组每个内存单元都有自己的内存地址,而我们指针可以存放这些地址

重点!!!:数组名是什么?

#include <stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
	printf("%p\n", arr);
	printf("%p\n", &arr[0]);
	return 0;
}

在这里插入图片描述
可见数组名和数组首元素的地址是一样的
这里的一样,不仅仅是数值上,是各种意义上!
ps:数组名确实是首元素地址,但是有两个例外
1.sizeof(数组名),这里的数组名表示的是整个数组
2.&数组名,这里的数组名也是整个数组,&数组名表示整个数组的地址

#include <stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
	printf("%p\n", arr);
	printf("%p\n", &arr[0]);
	printf("%p\n", &arr);
	printf("---------我是分界线----------\n");
	printf("%p\n", arr+1);//地址+4,跳过1个int型
	printf("%p\n", &arr[0]+1);//地址+4,跳过1个int型
	printf("%p\n", &arr+1);//地址+40,跳过一个数组int arr[40]
	return 0;
}

在这里插入图片描述

既然可以把数组名当成地址存放到一个指针中,我们使用指针来访问一个数组元素就成为可能。


#include <stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
	int* p = arr;
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (i = 0;i < sz;i++)
	{
		*(p + i) = i;
		printf("%d ", *(p + i));
	}
	return 0;
}

在这里插入图片描述

六、二级指针

指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里? 这就是 二级指针 。

int main()
{
	int a = 10;
	int* pa = &a;//pa里面放的是a的地址
	int** ppa = &pa;//ppa里面放的是pa的地址
	return 0;
}

示意图如下:
在这里插入图片描述
对于二级指针,我们也是可以解引用的
*ppa 通过对ppa中的地址进行解引用,这样找到的是 pa , *ppa 其实访问的就是 pa

int b = 20;
*ppa = &b;//等价于 pa = &b;

**ppa 先通过 *ppa 找到 pa ,然后对 pa 进行解引用操作: *pa ,那找到的是 a

**ppa = 30;
//等价于*pa = 30;
//等价于a = 30;

当然了,你也可以无限套娃,就会有三级指针、四级指针。。。

七、指针数组

指针数组是指针还是数组?
这个问题你就想,好男孩,好男孩的本质是男孩啊
指针数组本质还是数组

举个例子:

int main()
{
	int arr1[5];//整形数组,存放整形的数组就是整形数组
	char arr2[3];//字符数组,存放字符的数组就是字符数组

	//指针数组,存放指针的数组就是指针数组
	int* parr[5];//整形指针数组
	char* pbrr[4];//字符指针数组
	return 0;
}

示意图如下
在这里插入图片描述
应用实例:

int main()
{
	int a = 123;
	int b = 213;
	int c = 312;
	int* arr[3] = { &a,&b,&c };

	for (int i = 0;i < 3;i++)
	{
		printf("%d\n", *arr[i]);
		//arr[i]是一个元素地址,*arr[i]对该地址解引用,得到地址指向的元素
	}
	return 0;
}

在这里插入图片描述


总结

本文介绍了指针和指针类型(着重掌握)、野指针及其成因、指针运算、指针数组等相关知识。作为C语言的大头,指针这块知识必须要拿下,最后祝读者学业有成,奥利给!

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-08-06 10:25:04  更:2022-08-06 10:26:01 
 
开发: 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/10 3:56:28-

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