【C语言】初阶指针

news2024/11/25 4:46:42

目录

Ⅰ、指针是什么?

总结:

Ⅱ、指针和指针类型

    1 .指针+-整数

2.指针的解引用

Ⅲ、野指针

         1 .野指针成因

        2 如何规避野指针

Ⅳ、指针运算

          1 .指针 +- 整数

        2. 指针 - 指针

        3. 指针的关系运算

Ⅴ、指针和数组

Ⅵ、二级指针

Ⅶ、指针数组


指针
1. 指针是什么
2. 指针和指针类型
3. 野指针
4. 指针运算
5. 指针和数组
6. 二级指针
7. 指针数组

Ⅰ、指针是什么?

指针理解的 2 个要点:
1. 指针是内存中一个最小单元的编号,也就是 地址
2. 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量
总结:指针就是地址,口语中说的指针通常指的是指针变量。
那我们就可以这样理解:
内存

内存中每个字节都有一个地址。

指针变量
我们可以通过 & (取地址操作符)取出变量的内存其实地址,把地址可以存放到一个变量中,这个变量就是指针变量
#include <stdio.h>
int main()
{
 int a = 10;//在内存中 栈区   开辟一块空间
 int *p = &a;//这里我们对变量a,取出它的地址,可以使用&操作符。
    //a变量占用4个字节的空间,这里是将a的4个字节的第一个字节的地址存放在p变量
    //中,p就是一个之指针变量。
 return 0;
}
小总结:
指针变量 ,用来存放 地址 的变量。(存放在指针中的值 都被当成地址 处理)。
那这里的问题是:
一个小的单元到底是多大?( 1 个字节)
如何编址?
经过仔细的计算和权衡我们发现一个字节给一个对应的地址是比较合适的。
对于 32 位的机器,假设有 32根地址线 ,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电平(低电压)就是(1 或者 0 );
那么 32 根地址线产生的地址就会是:
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000001
...
11111111   11111111   11111111   11111111
这里就有 2 32 次方个地址。
每个地址标识一个字节,那我们就可以给 2^32Byte == 2^32/1024KB ==
2^32/1024/1024MB==2^32/1024/1024/1024GB == 4GB 4G的空闲进行编址
64 位机器,如果给 64 根地址线,那能编址多大空间,也是按照上面的算法进行计算。
这里我们就明白:
32 位的机器上,地址是 32 0 或者 1 组成二进制序列,那地址就得用 4 个字节的空间来存储,所以一个指针变量的大小就应该是4 个字节。
那如果在 64 位机器上,如果有 64 个地址线,那一个指针变量的大小是 8 个字节,才能存放一个地址。
小总结:
指针是用来存放地址的,地址是唯一标示一块地址空间的。
指针的大小在 32 位平台是 4 个字节,在 64 位平台是 8 个字节。

总结:

Ⅱ、指针和指针类型

这里我们在讨论一下: 指针的类型
我们都知道,变量有不同的类型,整形,浮点型等。那指针有没有类型呢?
准确的说:有的。
当有这样的代码:
int num = 10 ;
p = & num ;
要将 &num num 的地址)保存到 p 中,我们知道 p 就是一个指针变量,那它的类型是怎样的呢?
我们给指针变量相应的类型。
char   * pc = NULL ;
int   * pi     = NULL ;
short * ps = NULL ;
long   * pl  = NULL ;
float * pf    = NULL ;
double * pd = NULL ;
这里可以看到,指针的定义方式是: type + *
其实:
char* 类型的指针是为了存放 char 类型变量的地址。
short* 类型的指针是为了存放 short 类型变量的地址。
int* 类型的指针是为了存放 int 类型变量的地址。
那指针类型的意义是什么?

示例:
int main()
{
	int a = 0x11223344;
	//char* p = (char*) & a;//int* 
	//*p = 0;

	int* p =  &a;//int*
	*p = 0;
	return 0;
}
可以看到:a的指针类型为int* 把他强转为char* ,对char*类型的p进行赋值的时候, 只修改了一个字节
当把a给int*指针类型的时候,修改时能 修改全部的4个字节
指针类型变量存放地址
int main() 
{
	int a = 10;
	int* pa = &a;
	*pa = 20;
	printf("%d\n", a);
	printf("%d\n", *pa);
	printf("%p\n", &a);
	printf("%p\n", pa);
	return 0;
}

运行结果:

通过这个运行结果可以看见指针类型变量pa里面存了a的地址后,打印出来的他跟a的地址一样;

指针的大小根据不同的系统大小不一样:
64位下:

32位下:

    1 .指针+-整数

示例:

int main()
{
	int a = 0;
	int * pa = &a;
	char* pc = &a;

	printf("pa = %p\n", pa);
	printf("pa+1 = %p\n\n\n", pa+1);

	printf("pc = %p\n", pc);
	printf("pc+1 = %p\n\n\n\n", pc+1);

	return 0;
}

运行结果:

这里也可以看出不同指针类型的意义

这里我们也经常使用这个指针加减整数,例如访问数组的时候:

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = &arr;
	int i = 0;
	while ((i++) < 10)
	{
		printf("%d ", *p);
		p++;
	}
	return 0;
}

输出结果:

当然我们也可以从内存去看这个改变 :

示例:用short*指针修改int,可以清楚看到每次修改2个字节(动图)

用char*指针修改int,可以清楚看到每次修改一个字节(动图)

用int*指针修改int,可以清楚看到每次修改四个字节(动图)

2.指针的解引用

解引用就是可以对指针变量保存地址中的数据进行修改,取值等一系列操作:

示例:

//演示实例
#include <stdio.h>
int main()
{
 int n = 0x11223344;
 char *pc = (char *)&n;
 int *pi = &n;
 *pc = 0;   //重点在调试的过程中观察内存的变化。
 *pi = 0;   //重点在调试的过程中观察内存的变化。
 return 0;
}
执行过程:(动图)
总结:
指针的类型决定了,对指针解引用的时候有多大的权限( 能操作几个字节 )。
比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节。

Ⅲ、野指针

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

         1 .野指针成因

1. 指针未初始化
#include <stdio.h>
int main()
{ 
 int *p;//局部变量指针未初始化,默认为随机值
    *p = 20;
 return 0;
}
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;
   }
    return 0;
}
3. 指针指向的空间释放
int* test()
{
	int a = 10;//0x0012ff40
	return &a;
}
int main()
{
	//0x0012ff40
	int *p = test();
	//p就是野指针
	printf("%d\n", *p);//
	return 0;
}
这里的test()函数会返回一个int*的地址,也就是a的地址。但是变量a是函数test()在执行的时候创建的,函数运行完毕后,a的空间会随着函数栈帧的销毁而释放,在回到main()函数后,指针类型p接收到一个已经释放的变量a的空间,在使用的时候,也会造成野指针问题。

        2 如何规避野指针

这里对于规避野指针有一些小建议:

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

NULL为0,我们用户在使用的时候不能使用。 

Ⅳ、指针运算

指针 +- 整数
指针 - 指针
指针的关系运算

          1 .指针 +- 整数

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

看下面代码,我们可以用多种方法对数组进行访问: 

int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	//            0 1 2 3 4 5 6 7 8 9
	//使用指针打印数组的内容
	int * p = arr;
	int i = 0;
	//arr-->p
	//arr == p
	//arr+i  ==  p+i
	//*(arr+i) == *(p+i) == arr[i]
	//*(arr+i) == arr[i]
	//*(i+arr) == i[arr]
	for (i = 0; i < 10; i++)
	{
		//printf("%d ", *(p + i));
		printf("%d ", *(arr + i));
		//printf("%d ", arr[i]);
		//printf("%d ", i[arr]);


	}
	return 0;
}

数组名是首元素地址,只要拿到首元素地址就可以访问后面的元素;

        2. 指针 - 指针

指针-指针的前提:两个指针指向同一块区域,指针类型时相同的 

指针-指针差值的绝对值,指针和指针之间的元素个数

  指针 - 指针得到的值为两个指针之间的元素个数,如果是小地址减大地址则为元素个数的相反数; 

int main()
{
	int arr[10] = { 0 };

	printf("%d\n", &arr[9] - &arr[0]);
	printf("%d\n", &arr[0] - &arr[9]);

	return 0;
}

执行结果:

下面的代码可以利用指针 - 指针求出某个字符串的长度: 

int my_strlen(char *s)
{
       char *p = s;
       while(*p != '\0' )
              p++;
       return p-s;
}
int main()
{
	char arr[] = "abcdef";
	size_t len = my_strlen(arr);
	printf("%zd\n", len);

	return 0;
}

     运行结果:

当然这里的代码中判断条件也可以去掉  ‘\0'   他的ascall码为0  0为假,也会跳出循环,例如:

size_t my_strlen(char* str)
{
	char* start = str;
	while (*str)
	{
		str++;
	}
	return str - start;
}

        3. 指针的关系运算

int my_strlen(char *s)
{
       char *p = s;
       while(*p != '\0' )
              p++;
       return p-s;
}

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

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

实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行。
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与 指向第一个元素之前的那个内存位置的指针进行比较。

Ⅴ、指针和数组

 示例:

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

执行结果: 

可见数组名和数组首元素的地址是一样的。
结论: 数组名表示的是数组首元素的地址 。( 2 种情况除外)
那么这样写代码是可行的:
#include <stdio.h>
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(i=0; i<sz; i++)
   {
        printf("&arr[%d] = %p   <====> p+%d = %p\n", i, &arr[i], i, p+i);
   }
    return 0;
}

运行结果:

所以 p+i 其实计算的是数组 arr 下标为 i 的地址。
那我们就可以直接通过指针来访问数组。
int main()
{
 int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
 int *p = arr; //指针存放数组首元素的地址
 int sz = sizeof(arr) / sizeof(arr[0]);
 int i = 0;
 for (i = 0; i<sz; i++)
 {
 printf("%d ", *(p + i));
 }
 return 0;
}

执行结果:

Ⅵ、二级指针

二级指针变量存放的是一级指针变量的地址。如下:

指针变量 int*p 中存放的是变量a的地址,指针变量 int**pp中存放的是一级指针变量 p的地址,当然,要存放pp的话就需要 int***  类型的指针,也就是说要比自己高一级。有一点俄罗斯套娃的意思:

然后我们理解一下里面的 *  

对于 int * p  ,我们可以这样理解:*  表示的是 p 是一个指针,前面的int表示的是这个指针指向的数据是 int 类型的。

对于 int ** p,我们可以这样理解:靠近 pp 的那一个 * 表示 pp 是一个指针,前面的 int* 表示pp指针指向的数据是 int* 类型的;

以此类推理解3,4,5 ... ... n级指针

代码示例:

int main()
{
	int a = 10;
	int* p =  &a;//p是指针变量,一级指针变量
	int* * pp = &p;//pp指针变量,二级指针变量

	**pp = 20;
	printf("%d\n", a);//20

	//int** * ppp = &pp;//pp是指针变量,三级指针变量
	//...
	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 arr1 [ 5 ];
char arr2 [ 6 ];

那指针数组是怎样的?
int* arr3 [ 5 ]; // 是什么?
arr3 是一个数组,有五个元素,每个元素是一个整形指针。

下面我们用一维数组模拟一个二维数组:

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

	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

执行结果:

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

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

相关文章

mysql下载安装教程(图文详细版)

如果一次没成功的话&#xff0c;就删掉重安&#xff08;前提是清理干净&#xff09;&#xff08;up就下了好几次&#xff0c;在错误中找到答案&#xff09; navicat(可视化工具)在其他文章里 一、mysql下载 进入官网地址https://www.mysql.com/downloads/ 然后就开始下载了&…

Git的3个主要区域

一般来说&#xff0c;日常使用只要记住下图6个命令&#xff0c;就可以了。但是熟练使用&#xff0c;恐怕要记住60&#xff5e;100个命令。 下面是我整理的常用 Git 命令清单。几个专用名词的译名如下。 Workspace&#xff1a;工作区 Index / Stage&#xff1a;暂存区 Reposito…

Nginx 搭建 lnmp

一.编译安装Nginx 1.新建用户前期准备 官网下载nginx安装包 https://nginx.org/en/download.html yum -y install gcc pcre-devel openssl-devel zlib-devel openssl openssl-devel #安装依赖包 useradd -M -s /sbin/nologin nginx #新建nginx用户便于管理 2.切换到/opt…

idea插件开发之一起来开发个map转对象的插件吧!

写在前面 源码 。 在实际工作中&#xff0c;经常有这样的场景&#xff0c;从map中获取值来赋值到某个对象上&#xff0c;这无疑是一个重复的劳动&#xff0c;本文来尝试通过编写一个这样的插件&#xff0c;让插件来帮我们写代码&#xff0c;不管是有1个属性&#xff0c;还是有…

创维超充车辆交付仪式暨参观座谈会圆满举行

6月14日&#xff0c;创维超充车辆交付仪式暨参观座谈会在南京成功举行。苏舜集团副总经理程璟一行以及近多出行东部大区总经理张显春一行齐聚一堂。创维汽车总裁、联合创始人吴龙八等领导亲临现场&#xff0c;对各位尊贵嘉宾的到来表示热烈欢迎&#xff0c;并与众人共同见证了这…

RAM和ROM

1&#xff0c;RAM和ROM区别 RAM和ROM都是由来存储的&#xff0c;比如CPU缓存&#xff0c;电脑和手机内存等属于RAM,而固态硬盘&#xff0c;U盘&#xff0c;手机的128G,256G存储空间等都属于ROM。他们的最主要区别是RAM在断电后存储数据就没有了&#xff0c;而ROM在断电后存储数…

Java基础面试题自测

文章目录 一、Java 中有哪 8 种基本数据类型&#xff1f;说说这 8 种基本数据类型对应的包装类型&#xff1f;二、包装类型的常量池技术了解么&#xff1f;三、为什么要有包装类型&#xff1f;四、什么是自动拆装箱&#xff1f;原理&#xff1f;四、遇到过自动拆箱引发的 NPE 问…

Ps:脚本与动作

有三种脚本语言可用于编写 Photoshop 脚本&#xff1a;AppleScript&#xff08;macOS&#xff09;、JavaScript 和 VBScript&#xff08;Windows&#xff09;。 Photoshop 脚本文件默认文件夹 Win&#xff1a;C:\Program Files\Adobe\Adobe Photoshop 2024\Presets\Scripts Mac…

无线麦克风推荐哪些品牌?一文读懂家用无线麦克风哪个牌子好!

​在这个充满创意与表达的时代&#xff0c;无线领夹麦克风以其独特的魅力&#xff0c;成为了声音创作者们的得力助手。它小巧便携&#xff0c;功能强大&#xff0c;无论是日常拍摄、直播互动还是专业演出&#xff0c;都能轻松应对&#xff0c;让你的声音随时随地清晰传递。那么…

PIL保存后的图像莫名的失真,部分不失真部分很失真

原图片是这样的&#xff1a; PIL会自行**“自救”被正则化的图片&#xff0c;导致自救过曝&#xff0c;部分颜色非常失真&#xff0c;但是部分又保存的还行。现象如下&#xff1a; 这里你检查一下你保存的是不是被正则化的图片**&#xff0c;如果是&#xff0c;改改。 查看一…

长难句打卡6.17

At a time when Thomas Piketty and other economists are warning of rising inequality and the increasing power of inherited wealth, it is bizarre that wealthy aristocratic families should still be the symbolic heart of modern democratic states. 在托马斯皮凯…

基于Java的二手手机回收平台系统

开头语&#xff1a; 你好呀&#xff0c;我是计算机学长猫哥&#xff01;如果有相关需求&#xff0c;文末可以找到我的联系方式。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;JavaJSPServlet 工具&#xff1a;IDEA/Eclipse、Navicat、Maven 系统展…

Postman接口测试之postman设置接口关联,实现参数化

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 postman设置接口关联 在实际的接口测试中&#xff0c;后一个接口经常需要用到前一个接口返回的结…

如何轻松进行照片压缩?5个软件帮助你快速进行照片压缩

如何轻松进行照片压缩&#xff1f;5个软件帮助你快速进行照片压缩 照片压缩是一种常见的图像处理操作&#xff0c;旨在减小图像文件的大小而尽量保持图像质量。有许多软件和工具可供选择&#xff0c;每个工具都有其独特的压缩算法和功能。以下是一些关于照片压缩的详细信息&am…

XMind for mac/win:解锁思维边界的思维导图神器

在信息爆炸的时代&#xff0c;高效地整理思绪、捕捉灵感成为了每个人的迫切需求。XMind for mac/win作为一款功能强大的思维导图软件&#xff0c;以其卓越的性能和易用性&#xff0c;赢得了广大用户的青睐。 一、跨平台兼容&#xff0c;无缝体验 XMind for mac/win完美兼容Ma…

Ubuntu-24.04-live-server-amd64安装界面中文版

系列文章目录 Ubuntu安装qemu-guest-agent Ubuntu-24.04-live-server-amd64启用ssh Ubuntu乌班图安装VIM文本编辑器工具 文章目录 系列文章目录前言一、准备工作二、开始安装三、测试效果总结 前言 Centos结束&#xff0c;转战Ubuntu。我之所以写这篇文章&#xff0c;是因为我…

计算机网络5:运输层

概述 进程间基于网络的通信 计算机网络中实际进行通信的真正实体&#xff0c;是位于通信两端主机中的进程。 如何为运行在不同主机上的应用进程提供直接的逻辑通信服务&#xff0c;就是运输层的主要任务。运输层协议又称为端到端协议。 运输层向应用层实体屏蔽了下面网络核心…

【x264】整体框架汇总

【x264】整体框架汇总 1. x264整体框架图2. 思考 参考&#xff1a; x264源代码简单分析&#xff1a;概述 参数分析&#xff1a; 【x264】x264编码器参数配置 流程分析&#xff1a; 【x264】x264编码主流程简单分析 【x264】编码核心函数&#xff08;x264_encoder_encode&…

vue大作业-端午节主题网站

vue大作业-端午节主题网站介绍 端午节&#xff0c;又称为龙舟节&#xff0c;是中国的传统节日之一&#xff0c;每年农历五月初五庆祝。这个节日不仅是纪念古代爱国诗人屈原的日子&#xff0c;也是家人团聚、共享美食的时刻。今天&#xff0c;我们非常高兴地分享一个以端午节为…

有效招聘营销策略的六个组成部分

任何想吸引更多人购买其产品的公司都必须投资于市场营销。然而&#xff0c;当涉及到让更多的人了解公司的工作时&#xff0c;许多有效的营销活动可能不是招聘团队的首要考虑因素。为了超越招聘委员会上的“发布祈祷”策略&#xff0c;有必要包括有效招聘营销策略的所有组成部分…