梦开始的地方—— C语言指针入门

news2024/11/18 10:44:16

文章目录

  • 指针入门
    • 1.指针概念
    • 2. 指针和指针类型
    • 3. 野指针
      • 造成野指针的原因
      • 如何避免野指针
    • 4. 指针的运算
      • 指针加减整数
      • 指针的运算关系
      • 指针的关系运算
    • 5.指针和数组
    • 6. 二级指针
    • 7. 指针数组


指针入门

1.指针概念

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

指针是个变量,存放内存单元的地址(编号) ,一个内存单元的大小是一字节,也就是8比特,是一串8比特的二进制数。

#include <stdio.h>
int main()
{
	int num = 10;
	int* p = &num; //取出num的地址,p里面存放的就是num变量的首地址


	return 0;
}

在这里插入图片描述

在32位的机器上存在着32根地址线,64位机器上存在着64根地址线。每根地址线对应1个比特位,通电后产生了0和1的正电和负电,对应着二进制位。也就是说32位机器上一个地址是32个比特位,也就是4个字节。而64位机器上地址是64个比特位,也就是8个字节

我们知道指针存放的是地址,那么这样说的话32位机器和64位机器上的指针大小是不一样的。

  • 在32位机器上,有32根地址线,所以指针大小要4个字节才能存放地址
  • 在64位机器上,如果有64根地址线,那么指针要是8个字节才能把地址存放起来

2. 指针和指针类型

我们都知道变量有不同的类型,那么指针也是有不同的类型的。

	int* p1 = NULL;
	short* p2 = NULL;
	float* p3 = NULL;
	double* p3 = NULL;
	char* p4 = NULL;
	long* p5 = NULL;

以上可以看到,指针变量的定义是 type+*,分别对应着各种变量的类型。

那么指针类型的意义又是什么呢?

来看代码

#include <stdio.h>
int main()
{
	int* a = 10;
	int* p1 = &a;
	char c = '1';
	char* p2 = &c;

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

	return 0;
}

运行结果,从下面的运行结果我们发现int型的指针+1跳过了4个字节,而char类型的指针只跳过了1个字节。

00B5FE30
00B5FE34
====================
00B5FE1B
00B5FE1C

也就是说指针的类型决定了指针+1/-1能走多大距离。

再来看一段代码,我们通过解引用来修改变量的值

#include <stdio.h>
int main()
{
	int a = 0x11223344;
	int* p1 = &a;
	*p1 = 0;


	return 0;
}

这是修改前a在内存中的值

在这里插入图片描述

这是修改后内存中的值,我们发现4个字节的值全被改为0了

在这里插入图片描述

接着来看这么一段代码,,同样是&a,不过这里是哪char类型的指针来接受int类型的地址,因为指针大小是相同的所以是可以放得下的

#include <stdio.h>
int main()
{
	int a = 0x11223344;
	char* p1 = &a;
	*p1 = 0;


	return 0;
}

在这里插入图片描述

同样是修改值,我们发现在内存中只有一个字节的值被修改了。

在这里插入图片描述

所以指针的类型的又一个作用就是:决定了指针的访问权限,也就是能访问几个字节。

#include <stdio.h>
int main()
{
	int arr[10] = { 0 };
	int* p = arr;
	int i = 0;
	for (i = 0; i < 10; i ++)
	{
		*(p + i) = i;
	}
	i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}

	return 0;
}

这段代码输出

0 1 2 3 4 5 6 7 8 9

小结

  • 指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节) 。
  • 指针的类型决定了指针+1/-1能走多大距离

3. 野指针

什么是野指针?

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

造成野指针的原因

  1. 指针指向的空间已经释放

    下面这段代码正确输出10,貌似没什么问题

    #include <stdio.h>
    int* test()
    {
    	int a = 10;
    
    
    	return &a;
    }
    int main()
    {
    	int* p = test();
    	printf("%d", *p);
    
    
    	return 0;
    }
    

    再来看看,我们加上语句打印的代码

    #include <stdio.h>
    int* test()
    {
    	int a = 10;
    
    
    	return &a;
    }
    int main()
    {
    	int* p = test();
    	printf("hello world!\n");
    	printf("%d", *p);
    
    
    	return 0;
    }
    
    //输出???
    

    这段代码的打印结果就不能确定了,打印的就是随机值。这就是个典型的野指针

    p得到的地址之后,地址指向的空间已经释放了,所以这个时候的p就是野指针

  2. 指针未初始化

    下面这段代码也是野指针,指针变量未初始化,里面放的就是随机值,也就是编译器给我们分配的地址。这段代码运行是直击报错的

    #include <stdio.h>
    
    int main()
    {
    	int* p;
    	*p = 10;
    	printf("%d\n", *p);
    
    	return 0;
    }
    
  3. 指针越界访问

    假设我们对一个指针越界访问,也就是访问程序外的内存地址。也是会造成野指针的。

    下面数组最后一个元素是arr[4],尝试使用指针越界访问,打印的就是随机值。

    当指针指向的范围超出数组的范围的时候,此时的p就是野指针

    #include <stdio.h>
    
    int main()
    {
    	int arr[5] = { 0 };
    	int* p = &arr;
    	int i = 0;
    	for (i = 0; i < 6; i++)
    	{
    		printf("%d ", *(p + i));
    	}
    
    	return 0;
    }
    

如何避免野指针

  1. 定义指针变量前记得初始化或者让其指向NULL
  2. 注意不要让指针越界访问
  3. 指针指向的空间释放时,及时让该指针指向NULL

来看一段代码

#include <stdio.h>

int main()
{
	int arr[5] = { 0 };
	int* p = &arr;
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%d ", *(p + i));
	}
	//假设指针已经不使用
	int num = 20;
	p = NULL;
	if (p != NULL)
	{
		*p = 10;
	}
	else
	{
		p = &num;
	}
	printf("%d\n", *p);

	return 0;
}

注意,NULL本身是等于0的,在源代码中定义了,只是将其强转成了 viod*

#define NULL ((void *)0)

NULL指向的空间是不能访问的

其实指针变量p本身存的就是一个16进制的地址,语法上是支持直接赋值地址给指针变量的,虽然语法上是支持,但这属于非法访问。

#include <stdio.h>

int main()
{
	int* p = 0x00112233F5;

	printf("%p\n", p);
	return 0;
}

4. 指针的运算

指针加减整数

前面已经提到过对指针进行加减操作,指针能走多远取决于指针的类型。比如int类型的指针+1就是跳过4个字节,char类型的指针+1就是跳过1个字节,同理-1就是往回退几个字节

#include <stdio.h>

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

	return 0;
}

在这里插入图片描述

指针的运算关系

来看一段代码

#include <stdio.h>

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

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

	return 0;
}

运行结果

9
-9
4
-4

&arr[index]是一个地址,本质上就是一个指针,两个指针相减得到了数组元素的个数。

所以

  • 指针-指针 绝对值的是指针和指针之间的元素个数
  • 注意:指针-指针 计算的前提条件是:两个指针指向的是同一块连续的空间的

通过指针-指针模拟实现strlen函数

int my_strlen(char* str)
{
	char* index = str;
	while (*index != '\0')
	{
		index++;
	}

	return index - str;
}

指针的关系运算

我们知道指针可以加减操作,来看这么一段代码,这个代码的指针p指向数组的最后一个元素,然后不断减减,判断当前地址是否要大于等于数组第一元素的地址,完成了数组的赋值。

#include <stdio.h>

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

	for (p = &arr[9]; p >= &arr[0]; p--)
	{
		*p = 10;
	}
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

这个代码输出10个10,表面上好像没有问题,大部分编译器都是这个结果。

但是需要注意的是C语言标准规定

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

因为这里for循环最后一次判断比较的是数组首元素前面的那块地址,但是C语言标准不建议这么做。这么做可能会出现问题

我们可以把代码改进一下,这样就是拿数组最后一个元素的地址和它后面的地址比较了

#include <stdio.h>

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

	for (p = &arr[0]; p < &arr[10]; p++)
	{
		*p = 10;
	}
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

5.指针和数组

指针不是数组,数组也不是指针。但是数组可以通过指针来访问

来看一段代码

#include <stdio.h>

int main()
{
	int arr[5] = { 0 };
	printf("arr == %p\n", arr);
	printf("&arr[0] == %p\n", &arr[0]);
	
	return 0;
}

输出

arr == 0000005DD98FF578
&arr[0] == 0000005DD98FF578

可以说名数组名就是数组的首元素地址.

既然数组名可以当做一个内存地址放到指针中,那么就可以通过指针来访问。

#include <stdio.h>

int main()
{
	int arr[5] = { 0 };
	int* p = arr;
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("&arr[%d] = %p <===> p+%d = %p\n",i ,&arr[i],i,p+i);
	}
	return 0;
}

输出

&arr[0] = 000000B9FD36FC58 <===> p+0 = 000000B9FD36FC58
&arr[1] = 000000B9FD36FC5C <===> p+1 = 000000B9FD36FC5C
&arr[2] = 000000B9FD36FC60 <===> p+2 = 000000B9FD36FC60
&arr[3] = 000000B9FD36FC64 <===> p+3 = 000000B9FD36FC64
&arr[4] = 000000B9FD36FC68 <===> p+4 = 000000B9FD36FC68

二维数组名的地址也是首元素地址,二维数组的首元素是一位数组,所以二维数组名是二维数组第一行的地址。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qwQgYSeK-1666788548464)(assets/1666615950740.png)]

6. 二级指针

指针是一个变量,我们知道是个变量在内存中就会有一个地址。那么指针变量的地址怎么存放?那么就可以使用二级指针来存放。

#include <stdio.h>

int main()
{
	int a = 100;
	int* p = &a;
	int** pp = &p;
	printf("%p\n", p);//存的是a的地址
	printf("%p\n", pp);//存的是p的地址
	printf("%p\n", &pp);//取出二级指针pp的地址
	
	return 0;
}

运行结果

00000094626FFA24
00000094626FFA48
00000094626FFA68

pp是一个二级指针变量里面存的是一级指针p的地址,通过对pp进行解引用操作就能拿到p的地址,而p里面存的是变量a的地址,再次对p进行解引用操作就能得到变量a的值

#include <stdio.h>

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

多级指针

#include <stdio.h>

int main()
{
	int a = 100;
	int* p = &a;
	int** pp = &p;
	int*** ppp = &pp;
	int**** pppp = &ppp;

	printf("%d\n", ****pppp);
	
	return 0;
}


//输出100

7. 指针数组

指针数组是指针还是数组?指针数组是存放指针的数组。

我们整形数组和字符型数组,里面存的是整形变量和字符变量。

int arr[5] = { 0 };
char str[] = "hello";

在这里插入图片描述

那么指针数组又是什么样子的呢?

下面这段代码我们知道对变量取地址得到的是一个地址也就是指针,把这个指针放到一个数组里,这个数组就是指针数组,[]表示是数组,int*是一个整形指针,所以这是一个整形的指针数组。

#include <stdio.h>

int main()
{
	int a = 10;
	int b = 20;
	int c = 30;

	int* arr[] = { &a,&b,&c };
	
	return 0;
}

字符指针数组

我们知道字符数组可以写成指针的形式,存的就是字符串的首地址。那么我们把几个字符串放到一个数组中,就可以让一个字符指针数组来接收了。

字符指针数组里面存的都是字符串的首元素地址,可以直击通过%s来打印

#include <stdio.h>

int main()
{
	char* str = "hhh";//存放的是字符串的首地址

	char* arr[] = { "hello","world","linux" };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		printf("%s ", arr[i]);
	}
	
	return 0;
}

= “hello”;


[外链图片转存中...(img-civQkyF4-1666788548465)]

那么指针数组又是什么样子的呢?

下面这段代码我们知道对变量取地址得到的是一个地址也就是指针,把这个指针放到一个数组里,这个数组就是指针数组,[]表示是数组,int*是一个整形指针,所以这是一个整形的指针数组。

```c
#include <stdio.h>

int main()
{
	int a = 10;
	int b = 20;
	int c = 30;

	int* arr[] = { &a,&b,&c };
	
	return 0;
}

字符指针数组

我们知道字符数组可以写成指针的形式,存的就是字符串的首地址。那么我们把几个字符串放到一个数组中,就可以让一个字符指针数组来接收了。

字符指针数组里面存的都是字符串的首元素地址,可以直击通过%s来打印

#include <stdio.h>

int main()
{
	char* str = "hhh";//存放的是字符串的首地址

	char* arr[] = { "hello","world","linux" };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		printf("%s ", arr[i]);
	}
	
	return 0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MaNgbR3H-1666788548465)(assets/1666666525408.png)]

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

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

相关文章

31、Java——JDBC实现账号密码登录

✅作者简介&#xff1a;热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;乐趣国学的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏&#xff1a;Java案例分…

Kong自动注册kong-spring-boot-stater

前言 kong-spring-boot-stater框架是为了解决SpringBoot项目和kong网关的自动注册&#xff0c;虽然Kong网关有提供可视化管理后台的操作界面&#xff0c;但是在多服务、多环境的时候在管理后台挨个配置每个服务节点是比较麻烦的&#xff0c;所以这也是kong-spring-boot-stater…

P3654 First Step (ファーストステップ)——暴力枚举

First Step (ファーストステップ) 题目背景 知らないことばかりなにもかもが&#xff08;どうしたらいいの&#xff1f;&#xff09; 一切的一切 尽是充满了未知数&#xff08;该如何是好&#xff09; それでも期待で足が軽いよ&#xff08;ジャンプだ&#xff01;&#xff09…

项目流程管理工具:OmniPlan Pro 4 中文

如何更好的管理项目流程&#xff1f;OmniPlan Pro 4中文是个很好的帮手&#xff0c;非常好用的项目流程管理工具&#xff0c; 强大的规划&#xff0c;管理复杂性&#xff1a; 引入三个新的内置模板&#xff1a;标准项目、标准项目&#xff08;样式&#xff09;和简单项目。 …

【TA】Unity角色二次元风格渲染

NRMToonLitSample Author : 文若 我的Demo地址 &#xff1a; NRMToonLitSample 学习视频地址 &#xff1a; Kerry大佬的 技术美术实战培训课程——卡通人物渲染方案 文章目录NRMToonLitSample1. 模型贴图基本信息2. 基础渲染效果2.1 基础shader Toon2.2 光照模型效果第一步&…

【从零到一的Raspberry】树莓派踩坑实录(一)系统安装与简单开发

写在前面 本系列作为树莓派上手记录&#xff0c;同时将本人的踩坑以及参考进行记录汇总&#xff0c;必要时罗列出小组分工&#xff0c;作为《嵌入式软开》小组参考文件。 0 硬件准备 名称描述树莓派3B支持wifi&#xff0c;包含了散热器、外壳、电源线等配件网线感谢王emo同学…

【小程序websocket前后端交互】uniapp写微信小程序聊天功能功能,websocket交互功能,心跳重连【详细注释,复制即用】

前言 这几天在做的一个需求&#xff0c;就是要写一个小程序端的页面&#xff0c;用于跟客服聊天。 然后就用到了websocket技术&#xff0c;以前我做过网页版的&#xff0c;但是做小程序后发现网页版的逻辑放过来没问题&#xff0c;但是很多的方法和api是不生效的&#xff0c;所…

立方体的表面积 长方体的表面积 公里转换为米 温度对照

立方体的表面积 难度&#xff1a;青铜 时间限制&#xff1a;1秒 占用内存&#xff1a;64 M 输入立方体边长&#xff0c;输出立方体的表面积。不考虑非法输入。格式 输入格式&#xff1a;输入实型 输出格式&#xff1a;输出实型 #include<bits/stdc.h> using namespace s…

万字长文的CSS与JavaScript简易学习

近期学习web笔记&#xff0c;可供参考 目录 css: css导入方式&#xff1a; css选择器&#xff1a; javascript: javascript介绍&#xff1a; js引入方式&#xff1a; js书写语法&#xff1a; js变量&#xff1a; 5种原始类型&#xff1a; 运算符&#xff1a; JavaScr…

推荐一款基于.Net Core开发简约漂亮的 WPF UI库

今天给大家推荐一个开源WPF UI库。 项目简介 这是一款使用简单、UI评论的WPF UI库&#xff0c;借鉴了多个开源框架。UI简单清晰、大气。 技术架构 1、跨平台&#xff1a;这是基于.Net Core开发的系统&#xff0c;可以部署在Docker, Windows, Linux, Mac。 2、开发环境&…

微视网媒:沃尔沃质量有什么魅力 让大佬罗永浩、樊登纷纷翻牌S90?

在消费市场&#xff0c;选对代言人&#xff0c;产品就成功了一半&#xff0c;这话可是一点都没说错。 从一定程度上来说&#xff0c;代言人就是产品对外形象的展示&#xff0c;甚至有不少消费者还会因为代言人激情下单。 当然&#xff0c;成也代言&#xff0c;败也代言&#xf…

拉格朗日对偶问题的一些介绍

文章目录参考前言拉格朗日函数例1例2拉格朗日函数的对偶问题参考 “拉格朗日对偶问题”如何直观理解&#xff1f;“KKT条件” “Slater条件” “凸优化”打包理解 感觉有时间看视频的还是看视频比较好&#xff0c;本文只是记录一下以防以后忘记。 前言 还记得SVM里用到拉格朗…

Python代码的编写运行方式简介

Python代码的编写运行方式简介 Python编写方式 Python 是一种解释型的脚本编程语言&#xff0c;支持两种代码编写方式&#xff1a;交互命令行方式和运行.py代码文件方式。 Python的交互命令行方式和直接运行.py代码文件方式有什么区别呢&#xff1f; 交互模式&#xff0c;相当…

Python爬虫|采集开源众包的悬赏任务,自动翻页

前言 现在互联网,有很多网站提供一些接单外派的形式,提供给有能力的人或者团队去接单。比如说,很多人熟悉的猪八戒,程序员客栈,CODING 码市,开源众包等等平台,相信很多同学也都知道。 如果要第一时间了解某个接单平台发布的第一手悬赏任务,选择爬虫也是非常不错的选择…

websocket接口自动化集成pytest测试框架

01、websocket协议 1、介绍 WebSocket是一种在单个TCP通信的协议。WebSocket通信协议于2011年被IETF定为标准RFC 6455&#xff0c;并由RFC7936补充规范。WebSocket API也被W3C定为标准。 WebSocket使得客户端和服务器之间的数据交换变得更加简单&#xff0c;允许服务端主动向…

【2020-10-26】JS逆向之同盾滑块

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录前言总结前言 用官网的做个例子吧&#xff1a;https://sec.xiaodun.com/onlineExperience/slidingPuzzle?Rbdjj?sourcetongdun&keywordtongdun 简单分析下这个…

项目整体管理

项目整体管理 1.1 项目整体管理概述 1.项目整体管理知识领域包括识别、确定、结合、统一与协调各项目管理过程组内不同过程与项目管理活动所需进行的各种过程和活动。 2.“整体管理”的基本任务就是为了按照实施组织确定的程序实现项目目标&#xff0c;将项目管理过程组中需要…

CalBioreagents 艾美捷芽孢杆菌多克隆抗体说明书

艾美捷CalBioreagents 芽孢杆菌多克隆抗体英文说明&#xff1a; CATALOG NUMBER: P043 PRODUCT DESCRIPTION: Rabbit anti-Bacillus polyclonal antibody. IMMUNOGEN: Immunogen was prepared from a pool of purified spores from Bacillus subtilis and Bacillus cereus.…

一句话需求,让你去抄人家产品,你遇到了咋办

打工者联盟为了抵抗996、拖欠工资、黑心老板、恶心公司&#xff0c;让我们组成打工者联盟。客观评价自己任职过的公司情况&#xff0c;为其他求职者竖起一座引路的明灯。https://book.employleague.cn/话说&#xff1a;工地有个包工头&#xff0c;找来两个瓦匠要承包工程。 瓦…

ZooKeeper基础

一、概念 架构&#xff1a; Client/ServerEnsemble&#xff08;集群&#xff0c;ZK服务器组&#xff09;&#xff0c;最小节点数为3ZK LeaderaZK Follower ZooKeeper数据模型 znode&#xff1a;用于存储数据&#xff0c;分为持久的&#xff08;default&#xff09;、临时的…