初始C语言(7)——详细讲解有关初阶指针的内容

系列文章目录

 第一章 “C“浒传——初识C语言(1)(更适合初学者体质哦!)

 第二章 初始C语言(2)——详细认识分支语句和循环语句以及他们的易错点 

 第三章 初阶C语言(3)——特别详细地介绍函数 

 第四章 初始C语言(4)——详细地讲解数组的内容以及易错点 

 第五章 初始C语言(5)——详细讲解操作符以及操作符的易错点  

 第六章 初始C语言(6)——详细讲解表达式求值以及其易错点 

 第七章 初始C语言(7)——详细讲解有关初阶指针的内容


目录

系列文章目录

前言

一、指针是什么?

1.1 指针 

总结:指针就是地址,口语中受到指针通常是指针变量。 

1.2 指针变量

1.3 内存

1.2.1 什么是内存: 

1.2.2 内存的抽象模型:

二、指针和指针类型

2.1 指针的解引用

2.2 指针 +- 整数

2.3 指针类型的应用

三、野指针

3.1 野指针成因 

3.2 如何规避野指针 

3.2.1 指针初始化要注意两个点 

3.2.2 指针使用之前要检查有效性

悬空指针

四、指针运算

4.1 指针 +- 整数

4.2 指针 - 指针

4.3 指针的关系运算

五、指针和数组

六、二级指针

七、指针数组

总结


前言

       在上一章内,小编带领大家详细学习了有关表达式求值的相关内容,学习了隐式类型转换和显式类型转换的相关内容介绍了操作符的一些属性

       而在这一章内,小编要带领大家进行学习初阶指针的一些内容,不要害怕,这一部分只是为了后面讲述进阶指针做一个铺垫,所以不要担心指针这一节很难,当你刚开始就害怕的话,你将永远地活在这个阴影之下,会永远学不会这一章的内容,希望大家能够有耐心地将这一章看完!


一、指针是什么?

       说起指针,小编当年也是比较害怕的,因为指针算的上是C语言中很难的一个知识点,但是一般写代码的时候也不常用,导致很多人不是非常重视,所以请认真学习一下这一节,刚开始先讲一下之前讲过的东西。

指针理解的两个要点

  1. 指针是内存中一个最小单元的编号,也就是地址
  2. 平时口语中说的指针,通常指的是指针变量,是用来存放地址的变量

1.1 指针 

  • 指针是C语言非常重要的特征,指针也是一种变量,只不过它所表示的不是数据的值,而是内存的地址。
  • 通过使用指针,可以对任意(非绝对)内存地址的数据进行读写。
  • 在了解指针读写的过程前,我们先需要了解如何定义一个指针,和普通的变量不同,在定义指针时,我们通常会在变量名前加一个*号。

       我们以32位计算机为例,32位计算机的内存地址是4个字节,在这种情况下,指针的长度也是32位。下面会讲解32位计算机的地址线。

总结:指针就是地址,口语中受到指针通常是指针变量。 

       在上面总结中有两个名词:内存指针变量。 我们先来认识一下指针变量,再来认识一下内存。

1.2 指针变量

       在前面初始C语言(5)——详细讲解操作符以及操作符的易错点我们学过一个操作符 & ,我们可以通过 & (取地址操作符)取出变量的内存其实就是地址,把地址可以存放在一个变量中,这个变量就是指针变量。在了解指针读写的过程前,我们先需要了解如何定义一个指针,和普通的变量不同,在定义指针时,我们通常会在变量名前加一个 *

int main()
{
	int a = 0x11223344; //在内存中开辟一个空间
	int* p = &a;//这里我们对变量a,取出他的地址
	//a变量占用4个字节的空间,这里是将a的4个字节的第一个字节的地址存放在p变量中,p是一个指针变量。
	return 0;
}

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

1.3 内存

       可能大家对内存的理解都不是很到位,在这里小编将要带领大家进行深一步学习,大家先记住一句话就是:内存和存储空间不是一回事。(更加详细地请看内存)

1.2.1 什么是内存: 

  • 内存是计算机中的重要部件,也称内存储器和主存储器,它是程序和CPU进行沟通的桥梁
  • 计算机中所有程序的运行都在内存中进行它用于暂时存放CPU中的运算数据以及与硬盘等外部存储器交换的数据内存性能的强弱影响计算机整体发挥的水平
  • 只要计算机开始运行,操作系统就会把需要运算的数据从内存调到CPU中进行运算,当运算完成,CPU将结果传送出来。

  

1.2.2 内存的抽象模型:

       为了方便大家记住,我们把内存模型映射成为我们现实生活中的模型,内存的模型是一层一层的,在现实生活中,其很像我们生活中的高楼大厦。

       在这个高楼大厦中,一层可以存储一个字节的数据楼层号就是地址,下面是内存和楼层整合的模型图。

  • 我们知道程序中的数据不仅只有数值,还有数据类型的概念 ,从内存上看就是占用内存大小(占用楼层数)的意思。
  • 即使物理上强制以1个字节为单位来逐一读写数据的内存,在程序中,通过指定其数据类型,也能实现以特定字节数为单位来进行读写。

在上面的讲解中也会有一些问题:

  • 一个小的单元到底是多大?(一个字节)
  • 如何编址?

       我们先来回答第一个问题:经过仔细地计算和权衡会发现一个字节给一个对应的地址是比较合适的。因为最小的数据类型是char,其单位为一个字节。如果过小,不好存储数字;如果过大,又很浪费空间。   

       那么回答第二个问题: 对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的时候产生高电平(高电压)低水平(低电压)就是(1或者0);那么32根地址线产生的地址就会是:

00000000 00000000 00000000 00000000

00000000 00000000 00000000 00000001

00000000 00000000 00000000 00000002

......

11111111   11111111   11111111   11111111

       这里就有2的32次方个地址,每一个地址表示一个字节。那我们就可以给(2^32byte == 2^32/1024KB ==  2^32/1024/1024MB == 2^32/1024/1024/1024GB == 4GB)4G的空间进行编址。同样的方法,那64位机器如果给64根地址线,那能编址多大空间?答案是16G的空间

但这里我们应该明白:

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

总结:

  • 指针是用来存放地址的,地址是唯一标识一块地址空间的。
  • 指针的大小在32位平台是4个字节,在64位平台上是8个字节

二、指针和指针类型

       在之前,我们学习变量时,会根据现实生活提供不同的类型:整形,浮点型等……那么指针有没有类型呢?准确地说:有的。

当有这样的代码:

int num = 10;
p = #

       我们要将 &num(num的地址)保存到p中,我们知道p是一个指针变量,那么它的类型是什么呢?我们要给指针变量相应的类型。

char*   pc = NULL;
int*    pc = NULL;
short*  pc = NULL;
long*   pc = NULL;
float*  pc = NULL;
double* pc = NULL;

这里可以看到,指针的定义方式是:type + *

其实:

  • char* 类型的指针是为了存放 char 类型变量的地址;
  • short* 类型的指针是为了存放 short 类型变量的地址;
  • int* 类型的指针是为了存放 int 类型变量的地址。

       那为什么要这么麻烦?指针变量的大小不是都是4或者8个字节吗?那指针类型的意义是什么?请看下面进行讲解: 

2.1 指针的解引用

#include <stdio.h>
int main()
{
	int n = 0x11223344;
	char* pc = (char*)&n; //为了消除警告
	int* pi = &n;
	*pc = 0;//重点在调试的过程中观察内存的变化
	*pi = 0;//重点在调试的过程中观察内存的变化
	return 0;
}

在调试过程中,我们可以发现,指针类型是有意义的,指针类型决定了指针进行解引用操作时,访问几个字节。

总结:

       指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。比如:char* 的指针解引用就只能访问一个字节,而 int* 的指针解引用就能访问四个字节

       经过上面的学习,有些人可能要疑惑,为什么不自动识别传来的地址是什么类别的呢? 其实是已经识别了。

  

2.2 指针 +- 整数

#include <stdio.h>
int main()
{
	int n = 10;
	char* pc = (char*)&n;
	int* pi = &n;
	printf("%p\n", &n);
	printf("%p\n", pc);
	printf("%p\n", pc + 1);
	printf("%p\n", pi);
	printf("%p\n", pi + 1);
	return 0;
}

为什么这两个指针跳过的大小不同呢?

 首先,这两个指针的类型是不一样的;

其次,整形类型指针指向的是一个整形的对象;字符类型指针指向的是一个字符的对象。

总结:

       指针的类型决定了指针向前或者向后走一步跳过几个字节(距离)。 比如:一个 char* 的指针加1跳过一个字节,一个 int* 的指针加1跳过4个字节。

2.3 指针类型的应用


三、野指针

       野指针,顾名思义就是不确定的指针,大家可以用野人来类比。概念:野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的) 

说明:指针变量也是变量,在前面我们知道如果变量不初始化,在内存中会存放随机值(全局变量不初始化,存放0,局部变量不初始化,存放随机值)。同理指针变量如果赋给随机值是没有意义的,会成为野指针的。随机值会导致指针无法指向一个有效的内存空间,操作系统不允许操作此指针指向的内存区域

注释:野指针是不会直接引发错误的,而野指针指向的内存空间会出现问题的。 

3.1 野指针成因 

1.指针未初始化:指针变量终究只是一个变量(就和全局变量与局部变量一样),如果不给这个变量进行初始化,那么这个变量将会存放随机值

下面进行代码演示:

    int* p; //局部变量指针为初始化,默认为随机值
//正确写法为:int* p = NULL;
    *p = 20;

2.指针越界访问:在数组这一节中,我们学习数组越界访问,其实指针越界访问与其类似,指针指向的空间超出了其分配的合理空间

下面进行代码演示:

    int arr[10] = {0};
    int* p = arr;
    for(int i = 0; i <= 10; i++)
//正确写法为:for(int i = 0; i < 10; i++)
    {
        //当指针指向的范围超出数组arr的范围时,p就是野指针
        *(p++) = i;
    }

3.指针指向的空间释放:在未学习动态内存开辟时,我们先用函数释放来解释这个成因。建立一个返回值为指针类型的函数,在主函数中用指针变量进行接收,当程序出函数体后,原先函数所指向的空间进行释放,此时指针就为野指针

下面进行代码演示:

int* test()
{
    int a = 10;
    return &a;
}
int main()
{
    int* p = test();
    printf("%d\n", *p);
    return 0;
}

3.2 如何规避野指针 

  1. 指针初始化
  2. 小心指针越界
  3. 指针指向的空间释放,及时置NULL
  4. 避免放回局部变量的地址
  5. 指针使用之前检查有效性

3.2.1 指针初始化要注意两个点 

先看代码,进行举例:

int a = 10;
int* p = &a;
  • 如果明确指针应该指向哪里的话,就应该初始化正确的地址
  • 如果不能明确指针应该指向哪里的话,安全起见要将指针初始化为NULL (空指针,就是0)

3.2.2 指针使用之前要检查有效性

#include <stdio.h>
int main()
{
    int* p = NULL; //不管三七二十一,我们要进行初始化
    //……
    int a = 10;
    p = &a;
    if(p != NULL)
        *p = 20;
    return 0;
}

       在这里,我们要先说一句,NULL是0,其实它也是一个地址,但是在我们用户手中是无法访问的,一旦我们去访问,程序就会崩溃。 

悬空指针


四、指针运算

4.1 指针 +- 整数

int main()
{
	int arr[] = { 0,1,2,3,4,5,6,7,8,9 };
	//            0 1 2 3 4 5 6 7 8 9 
	//使用指针打印数组的内容
	int* p = arr;
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
		//p指向的是数组首元素
		//p+i 是数组中下标为i的元素的地址
        //p+i 起始时跳过了i*sizeof(int)个字节
	}
	printf("\n");
	return 0;
}

 扩展:arr == p

            arr+i == p+i 

            *(arr+i) == *(p+i) == arr[i]

            *(i+arr) == i[arr]

4.2 指针 - 指针

  • 指针 - 指针的前提是:两个指针指向同一块区域,指针类型相同的
  • 指针 - 指针差值的绝对值是:指针和指针之间的元素个数
int arr[10];
printf("%d\n", &arr[0] - &arr[9]);  //-9
printf("%d\n", &arr[9] - &arr[0]);  //9

  

模拟实现一下strlen()函数

size_t my_strlen(char* ptr)
{
    char* s = ptr;
    while(*s)
//还可以这样写:while(*s != '\0')
        s++;
    return s - ptr;
}

4.3 指针的关系运算

#define N_VALUES 5
for(vp = &values[N_VALUES];vp > &values[0];)
{
    *--vp = 0;
}

代码简化,将这个代码进行修改:

for(vp = &values[N_VALUES];vp > &values[0]; vp--)
{
    *vp = 0;
}

       实际上在绝大部分的编译器上是可以顺利完成任务的,然而我们还是要避免这样写,因为标准并不能保证这个方法可行。 

标准规定: 

       在C语言中,允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。


五、指针和数组

在一小部分中,我们要知道:

  • 指针就是指针,指针变量就是变量,是用来存放地址的,指针变量的大小是4/8个字节
  • 数组就是数组,可以存放一组数,数组的大小是取决于元素的类型和个数
  • 数组的数组名是数组首元素的地址(两种情况除外),指针变量是可以访问地址的

在绝大多数情况下,数组名和数组首元素的地址是一样的,看下图:

总结:

数组名表示数组首元素的地址。 

但是有两个例外:

  1. sizeof(数组名),数组名单独放在sizeof内部,数组名表示整个数组,计算的是数组的大小,单位是字节。
  2. &数组名,数组名表示整个数组,取出的是数组的地址。数组的地址和数组首元素的地址,值是一样的,但是类型和意义是不一样的。

       既然可以把数组名当成地址存放到一个指针中,我们使用指针来访问就成为可能,那我们可以直接通过指针来访问数组。看下下面代码:

int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,0 };
	int* p = arr; //指针存放数组首元素的地址
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));
	}
	return 0;
}

六、二级指针

       指针变量也是变量(一级指针变量),是变量就有地址,那指针变量的地址存放在哪里?这个就是二级指针。下面就是代码解释:

int a = 10;
int* p = &a; //p是指针变量,一级指针变量
int* * pp = &p; //pp是指针变量,二级指针变量
//你还可以继续写,有几个*,就是几级指针变量
//int** * ppp = &pp; //ppp是指针变量,三级指针变量


七、指针数组

       指针数组是指针还是数组呢?答案是:是数组,是存放指针的数组。我们可以使用指针数组来模拟实现一个二维数组。

int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };
	int* arr[] = { arr1, arr2, arr3 };
	for (int i = 0; i < 5; i++)
	{
		for (int j = 0; j < 5; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

  

总结

       在这一部分,小编详细地编写了有关初阶指针的一篇博客。希望大家看完以后,进行点评,谢谢大家!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/84798.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

arm:day6

实现UART通信&#xff1a; 1.键盘输入一个字符a,串口工具显示b 2.键盘输入一个字符串"nihao",串口工具显示"nihao" uart.h #ifndef __UART4_H__ #define __UART4_H__#include "stm32mp1xx_uart.h" #include "stm32mp1xx_gpio.h" #in…

用于智能图像处理的计算机视觉和 NLP

莫斯科&#xff0c;神秘之城...&#xff08;这张照片由伊戈尔沙巴林提供&#xff09; 一、说明 如今&#xff0c;每个拥有智能手机的人都可能成为摄影师。因此&#xff0c;每天都有大量新照片出现在社交媒体、网站、博客和个人照片库中。尽管拍照的过程可能非常令人兴奋&#x…

Unity解决:3D开发模式第三人称视角 WASD控制角色移动旋转 使用InputSystem

Unity版本&#xff1a;2019.2.3f1 目录 安装InputSystem 1&#xff1a;创建InputHander.cs脚本 挂载到Player物体上 获取键盘输入WADS 2.创建PlayerLocomotion.cs挂载到Player物体上&#xff0c;控制物体移动转向 安装InputSystem 菜单栏/Window/Package Manager/Input Syst…

CentOS中Oracle11g进程有哪些

最近遇到Oracle数据库运行过程实例进程由于某种原因导致中止的问题&#xff0c;专门看了下正常Oracle数据库启动后的进程有哪些&#xff0c;查阅资料了解了下各进程的作用&#xff0c;记录如下。 oracle 3032 1 0 07:36 ? 00:00:00 ora_pmon_orcl oracle …

Linux:安全技术与防火墙

目录 一、安全技术 1.安全技术 2.防火墙的分类 3.防水墙 4.netfilter/iptables关系 二、防火墙 1、iptables四表五链 2、黑白名单 3.iptables命令 3.1查看filter表所有链 iptables -L ​编辑3.2用数字形式(fliter)表所有链 查看输出结果 iptables -nL 3.3 清空所有链…

计算机竞赛 垃圾邮件(短信)分类算法实现 机器学习 深度学习

文章目录 0 前言2 垃圾短信/邮件 分类算法 原理2.1 常用的分类器 - 贝叶斯分类器 3 数据集介绍4 数据预处理5 特征提取6 训练分类器7 综合测试结果8 其他模型方法9 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 垃圾邮件(短信)分类算…

UE4/UE5 “无法双击打开.uproject 点击无反应“解决

一、方法一&#xff1a;运行UnrealVersionSelector.exe 1.找到Epic Game Lancher的安装目录&#xff0c; 在lancher->Engine->Binaries->Win64->UnrealVersionSelector.exe 2.把UnrealVersionSelector.exe 分别拷贝到UE4 不同版本引擎的 Engine->Binaries->…

「UG/NX」Block UI 体收集器BodyCollector

✨博客主页何曾参静谧的博客📌文章专栏「UG/NX」BlockUI集合📚全部专栏「UG/NX」NX二次开发「UG/NX」BlockUI集合「VS」Visual Studio「QT」QT5程序设计「C/C+&#

创建型(二) - 单例模式

一、概念 单例设计模式&#xff08;Singleton Design Pattern&#xff09;&#xff1a;一个类只允许创建一个对象&#xff08;或者实例&#xff09;&#xff0c;那这个类就是一个单例类。 优点&#xff1a;在内存里只有一个实例&#xff0c;减少了内存的开销&#xff0c;避免…

.NET应用UI组件DevExpress XAF v23.1 - 全新的日程模块

DevExpress XAF是一款强大的现代应用程序框架&#xff0c;允许同时开发ASP.NET和WinForms。DevExpress XAF采用模块化设计&#xff0c;开发人员可以选择内建模块&#xff0c;也可以自行创建&#xff0c;从而以更快的速度和比开发人员当前更强有力的方式创建应用程序。 在新版中…

chatGPT-对话柏拉图

引言&#xff1a; 古希腊哲学家柏拉图&#xff0c;在他的众多著作中&#xff0c;尤以《理想国》为人所熟知。在这部杰作中&#xff0c;他勾勒了一个理想的政治制度&#xff0c;提出了各种政体&#xff0c;并阐述了他对于公正、智慧以及政治稳定的哲学观点。然而&#xff0c;其…

使用Jetpack Compose的镜像效果

使用Jetpack Compose的镜像效果 您是否曾想过在列表或一般情况下为图像创建镜像效果&#xff1f;有了强大的Jetpack Compose UI工具包&#xff0c;这变得简单而容易。 正如您所看到的&#xff0c;此效果包括以下内容 反转图像反转图像的50&#xff05;可见性模糊的反转图像与…

5、css学习5(链接、列表)

1、css可以设置链接的四种状态样式。 a:link - 正常&#xff0c;未访问过的链接a:visited - 用户已访问过的链接a:hover - 当用户鼠标放在链接上时a:active - 链接被点击的那一刻 2、 a:hover 必须在 a:link 和 a:visited 之后&#xff0c; a:active 必须在 a:hover 之后&…

ElasticSearch7.x + kibana7.x使用记录

目录 查询所有索引 查询索引的mapping信息 添加索引的同时添加mapping 在原有基础上新增字段 旧的索引迁移到新的索引&#xff08;使用场景&#xff1a;数据迁移、索引优化、数据转换&#xff09; 查询索引下的文档总数 场景1&#xff1a;某一个字段的值是数组&#xff0…

回归预测 | MATLAB实现WOA-SVM鲸鱼算法优化支持向量机多输入单输出回归预测(多指标,多图)

回归预测 | MATLAB实现WOA-SVM鲸鱼算法优化支持向量机多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09; 目录 回归预测 | MATLAB实现WOA-SVM鲸鱼算法优化支持向量机多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09;效果一览基本介绍程…

【操作系统】寄存器

概念 寄存器是CPU内部用来存放数据的一些小型存储区域&#xff0c;用来暂时存放参与运算的数据和运算结果。其实寄存器就是一种常用的时序逻辑电路&#xff0c;但这种时序逻辑电路只包含存储电路。寄存器的存储电路是由锁存器或触发器构成的&#xff0c;因为一个锁存器或触发器…

作为一名8年测试工程师,因为偷偷接私活被····

接私活 对程序员这个圈子来说是一个既公开又隐私的话题&#xff0c;不说全部&#xff0c;应该大多数程序员都有过想要接私活的想法&#xff0c;当然&#xff0c;也有部分得道成仙的不主张接私活。但是很少有人在公开场合讨论私活的问题&#xff0c;似乎都在避嫌。就跟有人下班后…

2023河南萌新联赛第(五)场:郑州轻工业大学

A.买爱心气球 原题链接 : 登录—专业IT笔试面试备考平台_牛客网 博弈论 : #include <iostream> using namespace std; int t,n,m; string s1 "Alice",s2 "Bob"; int main() {cin>>t;while(t--){cin>>n>>m;if (n % 3 0) {cou…

简单介绍 CPU 的工作原理

内部架构 CPU 的根本任务就是执行指令&#xff0c;对计算机来说最终都是一串由 0 和 1 组成的序列。CPU 从逻辑上可以划分成 3 个模块&#xff0c;分别是控制单元、运算单元和存储单元 。其内部架构如下&#xff1a; 【1】控制单元 控制单元是整个CPU的指挥控制中心&#xff…

[ubuntu]ubuntu安装vncserver后,windows连接灰屏解决方法

修改配置文件~/.vnc/xstartup为如下内容&#xff1a; #!/bin/bash export $(dbus-launch) export XKL_XMODMAP_DISABLE1 unset SESSION_MANAGERgnome-panel & gnome-settings-daemon & metacity & nautilus & gnome-terminal &# [ -x /etc/vnc/xstartup…