C语言指针详解——必备7大知识点

news2024/9/20 14:48:50

Part1指针是什么?

1.1 浅谈指针

理解指针的 两个要点:

  1. 指针是内存中一个最小单元的编号,也就是地址;

  2. 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量。

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

1.2 内存

内存是电脑上特别重要的存储器,计算机中程序的运行都是在内存中进行的。所以为了有效的使用内存,就把内存划分成一个个小的内存单元,每个内存单元的大小是1个字节。为了能够有效的访问到内存的每个单元,就给内存单元进行了编号,这些编号被称为该内存单元的地址。(每个内存单元都有地址)就好比电脑是我们的学校,内存就是学校的一个宿舍楼,而一个内存单元就代表宿舍楼中的一个宿舍,而宿舍的门牌号就表示一个地址。

对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电平(低电压)就是(1或者0);那么32根地址线产生的地址就会是:232也就是说32位机器能够产生2的32次方个地址。每个地址标识一个字节,那我们就可以给(2^32^Byte == 2^32^/1024KB == 2^32^/1024/1024MB==2^32^/1024/1024/1024GB==4GB)4G的空间进行编址。

同理,换作64位机器,如果给64根地址线,那么将有能力管理2^32^×4GB的内存空间。

1.3 指针变量

为了能够更好的访问内存空间,我们可以通过&(取地址操作符)取出变量的内存其实地址,把地址可以存放到一个变量中,这个变量就是指针变量。

📝例如:

#include <stdio.h>
int main()
{
 int num = 10;//在内存中开辟一块空间
 int *p = &num;//这里我们对变量a,取出它的地址,可以使用&操作符。
    //num变量占用4个字节的空间,这里是将a的4个字节的第一个字节的地址存放在p变量
	  中,p就是一个之指针变量。
 return0; }

图片

总结:

  1. 指针变量是用来存放地址的,地址是唯一标示一个内存单元的;

  2. 在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以 一个指针变量的大小就应该是4个字节;

  3. 在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址。

Part2指针和指针类型

2.1 指针类型

我们都知道,变量有不同的类型,整形,浮点型等。

那指针有没有类型呢?准确的说:有的。

C语言为我们提供了丰富的指针类型,如下:

char  *pc = NULL;//字符指针 
int   *pi = NULL;//整形指针 
short *ps =NULL;//短整型指针 
long  *pl = NULL;//长整型指针 
float *pf = NULL;//单精度浮点型指针
double *pd = NULL;//双精度浮点型指针 
……

图片

其实:char* 类型的指针是为了存放 char 类型变量的地址。short* 类型的指针是为了存放 short 类型变量的地址。int* 类型的指针是为了存放 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);
 	return0; 
 }

结果展示:

图片

结论:指针类型决定了指针的步长(向前 / 向后 走一步多大距离)

  1. char* 指针 + 1,意思是跳过一个字符,也就是向后走1个字节;

  2. short* 指针 + 1,向后走2个字节;

  3. int* 指针 + 1,意思是跳过1个整形,也就是向后走4个字节;

  4. double* 指针 + 1,意思是跳过一个double,也就是向后走8个字节;

  5. ……

2.3 指针的解引用

📝演示实例:

//演示实例
#include <stdio.h>
int main()
{
 	int n = 0x11223344;
 	char *pc = (char *)&n;
 	int *pi = &n;
 	*pc = 0;   //重点在调试的过程中观察内存的变化。
 	*pi = 0;   //重点在调试的过程中观察内存的变化。
 	return0; 
 }

调试结果:

图片

结论:指针类型决定了,指针解引用操作的时候,访问几个字节(权限)。

  1. char* 的指针解引用访问1个字节;

  2. int * 的指针解引用访问4个字节;

  3. double* 的指针,解引用访问8个字节;

  4. ……

Part3野指针

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

3.1 野指针成因

3.1.1 指针未初始化

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

注释:局部变量指针未初始化,默认为随机值。

3.1.2 指针越界访问

#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;
   }
    return0;
}

注释:当指针指向的范围超出数组arr的范围时,p就是野指针。

3.1.3 指针指向的空间释放

int* test()
{
	int num = 100;
	return &num;//出了函数,这块内存就不属于我们了,还给了操作系统
}

int main()
{
	int* p = test();
	*p = 200;
	return0;
}

注释:变量num为局部变量,生命周期从创建开始到出test函数结束,test函数调用结束后num会将空间还给操作系统,此时回到主函数p的地址已经被释放,此时p就是野指针。

3.2 如何规避野指针

  1. 指针初始化(已知指向时明确初始化;未知初始化为NULL);

  2. 小心指针越界;

  3. 指针指向空间释放,及时置NULL;

  4. 避免返回局部变量的地址;

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

检查指针有效性,原理:空指针NULL位于内核区域不能直接访问。

#include <stdio.h>
int main()
{
    int *p = NULL;//未知指向初始化为NULL
    int a = 10;
    p = &a;//明确初始化
    if(p != NULL)//为空指针不访问(无效指针)
   {
        *p = 20;//不为空再访问
   }
    return0; 
}

Part4指针运算

4.1 指针± 整数

📝例如:通过指针加减整数遍历数组元素

int main()
{
	double arr[5] = {0};
	double* p = arr;
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%lf ", *(p + i));
	}

	return0;
}

4.2 指针的关系运算

📝例如:通过指针初始化数组;

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

图片

代码简化,故将代码修改如下:

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

图片

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

标准规定:

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

4.3 指针-指针

注释:指针和指针相减的绝对值等于指针和指针之间元素的个数。|p2-p1|==p1p2间元素个数

#include<stdio.h>
int main()
{
	
	int arr[10] = {0};
	printf("%d\n", &arr[9] - &arr[0]);
	printf("%d\n", &arr[0] - &arr[9]);
	
	//两个指针相减的前提是:指针指向的同一块连续的空间
	//int a = 10;
	//char c = 'w';
	//printf("%d\n", &a - &c);//err

	return0;
}

补充:指针+指针没有意义。

Part5指针和数组

5.1 区别

  1. 数组和指针不是一个东西;

  2. 数组能够存放一组数,连续的空间,数组的大小取决于元素个数;

  3. 指针是一个变量,是存放地址的。

5.2 联系

数组名是地址(指针) 数组名把首元素的地址,交给一个指针变量后,可以通过指针来访问数组。

📝通过指针和数组的联系,我们可以利用指针的偏移和解引用访问数组中的元素:

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);

	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));
	}
	return0;
}
//输出:1 2 3 4 5 6 7 8 9 10

Part6二级指针

6.1 二级指针详解

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

答案是:二级指针。

int main()
{
	int a = 10;//
	int *pa = &a;//pa就是指针变量,一级指针变量,表示指针指向的a是int
	int* *ppa = &pa;//ppa就二级指针,表示pp指向的p的类型是int*
	return0;
}

图片

二级指针的运算:

  1. *ppa通过对ppa中的地址进行解引用,这样找到的是pa,*ppa其实访问的就是pa;
    int b = 20;
    ``` ``*ppa = &b;
    //等价于 pa = &b;

  2. **ppa先通过*ppa找到pa ,然后对pa进行解引用操作:*pa,那找到的是a;
    **ppa = 30;
    //等价于*pa = 30;
    //等价于a = 30;

图片

6.2 多级指针

我们将问题故意复杂化一点,你想成为五星程序员吗?

#include <stdio.h>
int main()
{
    int n = 123;                        // int 
    int *oneStar = &n;                  // int *
    int **twoStar = &oneStar;           // int **
    int ***threeStar = &twoStar;        // int ***
    int ****fourStar = &threeStar;      // int ****
    int *****fiveStar = &fourStar;      // int *****

    printf("n = %d\n", *****fiveStar);  // 五次取值,还原为int
    return0;
}

Part7指针数组

7.1 初识指针数组

指针数组是指针还是数组?

答案:是数组,是存放指针的数组。

int main()
{
	//整型数组-存放整型的数组
	int arr[10];
	//字符数组-存放字符的数组
	char arr2[5];
	//指针数组-存放指针的数组
	int* arr3[5];//存放整型指针的数组
	char* arr4[6];//存放字符指针的数组

	return0;
}

📝例如:

图片

7.2 指针数组的使用

📝用一维数组模拟一个二维数组:

int main()
{
	//用一维数组模拟一个二维数组
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };
	int arr4[] = { 4,5,6,7,8 };

	int* arr[4] = {arr1, arr2, arr3, arr4};//用指针数组管理一维数组
	int i = 0;
	for (i = 0; i < 4; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}

	return0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

UPS负载箱的工作原理是什么?

UPS负载箱&#xff08;Uninterruptible Power Supply Load Bank&#xff09;内部包含一组电阻器&#xff0c;通过调节电阻值来模拟不同负载条件。当UPS供电时&#xff0c;电阻器会吸收一定的电能&#xff0c;从而模拟实际负载对UPS的需求。UPS负载箱配备了控制系统&#xff0c;…

2023年京东双11京享红包领取入口介绍

2023年京东双11京享红包领取入口介绍 抢京东2023年双11超级红包共计4步骤即可。在今天京东公布了2023年双11红包密令&#xff0c;这是最新准确有效的哦!建议大家提前收藏密令&#xff0c;开始时间是10月23日生效。具体的2波时间见后&#xff0c;下面跟随小编一起来看看抢红包教…

DC电源模块的数字电源优势

BOSHIDA DC电源模块的数字电源优势 数字电源模块是指在电源的设计和控制上采用数字式方案&#xff0c;采用数字化技术&#xff0c;将传统的电源模块从模拟传统电源转变为数字电源变成的模块。 传统的电源模块使用模拟技术&#xff0c;其主要优势在于可控性高、稳定性好&#…

2-MySQL的基本操作记录

1 数据库相关 -- --------------------表相关的---------- -- 查看字符集 show variables like %character%;show databases;# 创建数据库 create database test2;# 删除数据库 drop database test2; show databases;#查看当前使用的数据库 select database(); 2 用户相关 -…

Spring Boot自动配置原理揭秘

自动配置原理 概述原理Spring Boot Starterspring.factories 文件ConditionalOnX 注解配置 Bean配置属性 源码剖析 主页传送门&#xff1a;&#x1f4c0; 传送 概述 Spring Boot 是一个用于创建独立的、生产级别的 Spring 应用程序的框架。它极大地简化了 Spring 应用程序的开…

潮玩产业迈向千亿级,泡泡玛特未来发展空间可观

作为融合了艺术与创意的新品类&#xff0c;潮玩成为当下一种火热的消费现象。目前&#xff0c;全球潮玩产业处于快速增长期。近期&#xff0c;新华网联合中国社会科学院财经战略研究院发布的《超越潮流&#xff1a;千亿级潮玩产业彰显人文经济价值——潮玩产业发展报告&#xf…

华为OD机试 - 一种字符串压缩表示的解压 - 考生抽中题(Java 2023 B卷 100分)

目录 专栏导读一、题目描述二、输入描述三、输出描述四、解题思路1、题意2、根据题意&#xff0c;不合法方式如下&#xff1a;3、解题思路 五、Java算法源码六、效果展示1、输入2、输出 华为OD机试 2023B卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为O…

Nginx 实战教程

本篇博客我会演示日常的工作中&#xff0c;我们是怎么利用nginx部署项目的。我们以部署一套前后分离的项目为本次讲述的内容 一、搭建后端项目 创建一个最简单的springboot项目&#xff1a; 只需要依赖一个web模块即可&#xff1a; 提供一个api接口&#xff0c;可以获取服务端…

周记之马上要答辩了

“ 要变得温柔和强大&#xff0c;就算哪天突然孤身一人&#xff0c;也能平静地活下去&#xff0c;不至于崩溃。” 10.16 今天提前写完了一篇六级阅读&#xff0c;积累了一些词组&#xff1a; speak out against 公然反对&#xff0c;印象最深刻的就这个&#xff1b; 先了解…

英语——分享篇——每日200词——3001-3200

3001——ascertain——[ˌsəteɪn]——vt.查明&#xff0c;弄清——ascertain——a苹果(编码)s美女(编码)certain确定(熟词)——吃苹果的美女确定已查明此事——It can be difficult to ascertain the facts. ——可能难以查明事实真相。 3002——disrupt——[dɪsrʌpt]——…

深入浅出:Python内存管理机制详解

文章目录 一、什么是内存&#xff1f;1.1、RAM简介1.2、RAM容量1.3、查看电脑内存1.4、监控电脑内存 二、内存管理2.1、python是如何分配内存的&#xff1f;2.2、python采用自动内存管理机制2.3、python自动内存管理机制的缺点2.4、python内存优化的方法 三、项目实战3.1、查看…

防雷检测的项目和行业的等级区分

防雷检测是指对雷电防护装置的性能、质量和安全进行检测的活动&#xff0c;是保障人民生命财产和公共安全的重要措施。 地凯科技防雷检测的项目内容包括接闪器检测、引下线检测、接地装置检测、防雷区的划分、电磁屏蔽防雷检测、等电位连接检测、及电涌保护器 (SPD)性能检测。…

软件打不开,文件找不到了,如何找到隐藏文件?(windows和mac解决方案)

相信大家在学习过程中&#xff0c;会在咱们自己的编程软件安装一些插件&#xff0c;但是我们要知道插件跟版本会有不兼容的情况出现&#xff0c;也就是非法插件&#xff0c;会导致软件打不开&#xff0c;打开了报错等问题。 这个时候它的报错会告诉你一些路径&#xff0c;但是有…

vue3传递prop踩坑

这是官方文档中的介绍&#xff1a; Vue3中文官网 我们在组件中定义props时推荐使用驼峰命名&#xff0c;但是在父组件中传递数据时要使用kebab-case形式 这是我写的loading组件中定义的几个porps 我在使用时是这样传入的 但是打印出来的值是&#xff1a; 可以看到这里的ou…

CMMI V2.0能力域

1、概念 能力域是一组相关的PA&#xff0c;可以提高组织或项目的技能和活动的性能。能力域视图是CMMI模型的一个子集&#xff0c;描述了构成特定能力域的一组预定义PA。能力域是一种视图。如下面规划和管理工作能力域视图&#xff1a; 2、类别 类别是相关能力域的逻辑组或视图…

【html+css】袁进 渡一

文章目录 1. pre标签的原理2. img和map元素结合3. 元素的包含关系4. em单位5. 选择器5.1 简单选择器5.2 选择器组合5.3 选择器并列 6. 层叠7. 继承8. 属性值的计算过程9. 盒模型9.1 盒子类型9.2 盒子组成部分 10. 盒模型的应用10.1 改变宽高范围10.2 改变背景覆盖范围10.3 溢出…

经销商低价数据品牌如何掌握

渠道是由品牌、经销商、消费者组成&#xff0c;每个品牌的发展&#xff0c;离不开良好的渠道规则&#xff0c;一切不利于渠道发展的因素都应及时治理&#xff0c;比如产品假货、经销商低价等&#xff0c;低价除了影响渠道发展&#xff0c;还会带来很多问题&#xff0c;如品牌窜…

Unity 最新DOTS系列之《Baking与Baker的详解》

Unity DOTS Baking与Baker详解 最近DOTS终于发布了正式的版本, 我们来分享一下DOTS里面Baking 与Baker的关键概念&#xff0c;方便大家上手学习掌握Unity DOTS开发。 对惹&#xff0c;这里有一个游戏开发交流小组&#xff0c;希望大家可以点击进来一起交流一下开发经验呀&…

CleanMyMac X靠谱苹果电脑杀毒软件

在过去&#xff0c;人们普遍认为苹果电脑不容易受到病毒和恶意软件的攻击&#xff0c;因此不需要安装杀毒软件。然而&#xff0c;随着苹果电脑的普及和互联网的发展&#xff0c;苹果电脑也逐渐成为黑客和恶意软件的目标。为了保护苹果电脑的安全&#xff0c;使用一款可靠的苹果…

进程中的任务状态解析

在 Linux 里面&#xff0c;无论是进程&#xff0c;还是线程&#xff0c;到了内核里面&#xff0c;我们统一都叫任务&#xff08;Task&#xff09;&#xff0c;由一个统一的结构 task_struct 进行管理。 每一个任务都应该有一个 ID&#xff0c;作为这个任务的唯一标识。到时候排…