指针——“C”

news2024/9/29 19:25:33

各位CSDN的uu们你们好呀,今天,小雅兰学习的内容是指针,这次只会讲一些很简单的知识点,更详细的指针知识会在以后的博客中逐步剖析清楚,那么现在,就让我们进入指针的世界吧


指针是什么

指针和指针类型

野指针

指针运算

指针和数组

二级指针

指针数组


指针是什么? 

指针理解的2个要点:

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

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

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

内存

  • 有4G、8G、16G的内存
  • 把内存划分为一个个的内存单元,这个内存单元的大小是1个字节
  • 每个字节都给一个唯一的编号,这个编号我们称之为地址,地址在C语言中也叫:指针。
  • 编号==地址==指针

指针变量 

我们可以通过&(取地址操作符)取出变量的内存其实地址,把地址可以存放到一个变量中,这个 变量就是指针变量

下面,我们来看一小段代码来理解一下

#include<stdio.h>
int main()
{
   int a=10;
   //在内存中开辟一块空间
   //a是整型,占用4个字节的内存空间,每个字节都有对应的地址
   int *pa=&a;
   //这里我们对变量a,取出它的地址,可以使用&操作符
   //&a——得到的是a的地址(指针),其中得到的是a所占内存中4个字节中第一个字节的地址
   //pa是指针变量
   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 类型变量的地址。
  • long*类型的指针是为了存放long类型变量的地址。
  • float*类型的指针是为了存放float类型变量的地址。
  • double*类型的指针是为了存放double类型变量的地址。
  • char*类型的指针解引用只能访问一个字节
  • int*类型的指针解引用可以访问四个字节
  • short*类型的指针解引用可以访问两个字节
  • long*类型的指针解引用可以访问四个字节
  • float*类型的指针解引用可以访问四个字节
  • double*类型的指针解引用可以访问八个字节 
  • 所以:指针类型决定了,在解引用指针的时候,能访问几个字节

 然后我们来看看,指针的大小是不是究竟是我们之前所说的4个字节呢,在VS2022下运行一下

#include<stdio.h>
int main()
{
	printf("%u\n", sizeof(char*));
	printf("%u\n", sizeof(short*));
	printf("%u\n", sizeof(int*));
	printf("%u\n", sizeof(long*));
	printf("%u\n", sizeof(float*));
	printf("%u\n", sizeof(double*));
}

注意:在我的VS2022上,必须使用×86的平台,才能打印出全4的这种结果 

指针类型的意义

指针+-整数

直接上代码:

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

仔细来看,发现:pa的地址和pa+1的地址相差了 四个字节

                             pc的地址和pc+1的地址相差了一个字节

pa+n——>+n*sizeof(int)

pc+n——>+n*sizeof(char)

所以,指针类型决定了,指针进行+1、-1的时候,一步走多远

总结:指针的类型决定了指针向前或者向后走一步有多大(距离)。

指针的解引用

若是给定一个初始化为全0的数组,要把数组的内容改为:1 2 3 4 5 6 7 8 9 10,不能使用数组下标的方式,那该怎么办呢?

这就要用到我们的指针

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

 指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。


野指针

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

那么,为什么会出现野指针这种东西呢?

  • 指针未初始化
#include<stdio.h>
int main()
{
	int* p;
	//局部变量指针未初始化,默认为随机值
	*p = 20;
    //非法访问了
	return 0;
}
  • 指针越界访问
#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;
}
  • 指针指向的空间释放
#include<stdio.h>
int* test()
{
	int a = 10;
	return &a;
    //出了这个函数,a这块空间就还给操作系统了
}
int main()
{
	int* p = test();
	*p = 100;//所以不能这么使用
	return 0;
}

这个问题以后动态内存开辟的博客会仔细讲解,这里先看这么一小段简单的代码

我们又该如何规避野指针这个东西呢?

  •  指针初始化

一个指针不知道应该指向哪里的时候,暂时可以初始化为NULL

  • 小心指针越界
  • 指针指向空间释放,及时置NULL
  • 避免返回局部变量的地址
  • 指针使用之前检查有效性
#include <stdio.h>

int main()
{
    int *p = NULL;
    //....

    int a = 10;
    p = &a;
    if(p != NULL)
   {
        *p = 20;
   }
    return 0;
}

如果是这样使用

int *p=NULL;
*p=100;

程序会崩溃


指针运算

  • 指针+-整数
#define N_VALUES 5

float values[N_VALUES];

float *vp;

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

for (vp = &values[0]; vp < &values[N_VALUES];)
{
     *vp++ = 0;
     //++的优先级比*高
     //++是后置的,先使用vp的值再++
}
  • 指针-指针

我们来模拟实现一下strlen函数,在此之前,我们其实已经学习过了计数器的方法和递归的方法,现在我们来复习一下吧

计数器的方法

#include<stdio.h>
int my_strlen(char* str)
{
	int count = 0;//计数器
	while (*str != '\0')
	{
		count++;
		str++;
        //本质上就是指针+整数
        //str=str+1;
	}
	return count;
}
int main()
{
	char arr[10] = "abcdef";
	int len = my_strlen(arr);
	printf("%d\n", len);
	return 0;
}

 

递归的方法

#include<stdio.h>
int my_strlen(char* str)
{
	if (str != '\0')
	{
		return 1 + my_strlen(str + 1);
	}
	else
	{
		return 0;
	}
}
int main()
{
	char arr[10] = "abcdef";
	int len = my_strlen(arr);
	printf("%d\n", len);
	return 0;
}

指针-指针的方法(地址-地址)

要求:两个指针指向的空间是同一块,指针的类型是一致的

#include<stdio.h>
int my_strlen(char* str)
{
	char* start = str;
	while (*str != '\0')
	{
		str++;
	}
	return str - start;//指针-指针
}
int main()
{
	char arr[10] = "abcdef";
	int len = my_strlen(arr);
	printf("%d\n", len);
	return 0;
}

指针-指针得到的是指针和指针之间的元素个数 

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

 

指针+整数=指针

指针-指针=整数 

  • 指针的关系运算
#define N_VALUES 5

float values[N_VALUES];

float* vp;

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

可以把代码简化一下

#define N_VALUES 5

float values[N_VALUES];

float* vp;

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

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

标准规定:

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

允许p1和p3比较,不允许p1和p2比较 


 指针和数组

指针就是指针,不是数组

数组就是数组,也不是指针

  • 指针的大小:4或者8个字节,指针是存放地址的,地址的存放需要多大空间,指针变量的大小就是多少
  • 数组的大小:取决于数组的元素个数和每个元素的类型
#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种情况除外)

  •  sizeof(数组名),计算整个数组的大小,sizeof内部单独放一个数组名,数组名表示整个数组。
  • &数组名,取出的是数组的地址。&数组名,数组名表示整个数组。

那么这样写代码是可行的:

int arr[10] = {1,2,3,4,5,6,7,8,9,0};

int *p = arr;//p存放的是数组首元素的地址
  •  指针是可以指向数组元素的
  •  因为指针可以运算,所以借助于指针可以访问数组

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

#include <stdio.h>

int main()
{
    int arr[] = {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++)
    { 
       printf("&arr[%d] = %p   <====> p+%d = %p\n", i, &arr[i], i, p+i);
    }
    return 0;
}

所以 p+i 其实计算的是数组 arr 下标为i的地址,那我们就可以直接通过指针来访问数组。  

#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]);
  int i = 0;
  for (i = 0; i<sz; i++)
  {
      printf("%d ", *(p + i));
  }
  return 0;
}


二级指针

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

 a是要向内存申请4个字节的空间的

pa是指针变量,用来存放地址,也得向内存申请4个或者8个字节的空间

对于二级指针的运算有:

  •  *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是一个数组,有五个元素,每个元素是一个整型指针。

#include<stdio.h>
int main()
{
	int a = 10;
	int b = 20;
	int c = 30;
	//指针数组
	//存放指针的数组
	int* arr[] = { &a,&b,&c };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		printf("%d ", *(arr[i]));
	}
	return 0;
}

 

接下来,模拟实现一个二维数组

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

拓展:

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

 

地址完全一样。

所以:*(arr+i)==*(p+i)==arr[i]

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

对于这样一个二维数组int arr[3][5]

arr[i][j]与(*(arr+i))[j]完全等价 与*(*(arr+i)+j)也是完全等价的


好啦,小雅兰今天的内容就到这里了,最近一直忙着C语言课程设计,所以又导致博客的内容比学习的内容慢了好多,不过这个事也不能急,嘿嘿,来日方长,小雅兰一定会继续努力学习、写博客的。

 

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

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

相关文章

Spring 如何解决循环依赖?

什么是循环依赖 &#xff1f; 一个或多个对象之间存在直接或间接的依赖关系&#xff0c;这种依赖关系构成一个环形调用&#xff0c;有下面 3 种方式。 我们看一个简单的 Demo&#xff0c;对标“情况 2”。 Service public class Louzai1 {Autowiredprivate Louzai2 louzai2;…

基于OpenAI搭建自己的ChatGPT环境1

基于OpenAI搭建自己的ChatGPT环境1基于OpenAI搭建自己的ChatGPT环境注册账号生成访问密钥创建虚拟环境安装openai模块环境体验笔者初次接触人工智能领域&#xff0c;文章中错误的地方还望各位大佬指正&#xff01; 基于OpenAI搭建自己的ChatGPT环境 ChatGPT是OpenAI研发的人机…

Java基础之网络编程介绍详尽笔记

目录初识网络编程网络传输模型网络传输协议UDPUDP通信程序UDP的三种通信方式TCPTCP通信协议TCP的三次握手TCP的四次挥手初识网络编程 网络编程三要素 IP 设备在网络中的地址&#xff0c;是唯一的标识。 端口号 应用程序在设备中唯一的标识。 协议 数据在网络中传输的规则&…

童年回忆--扫雷(包括标记功能和递归展开)--万字讲解让你学会扫雷制作

魔王的介绍&#xff1a;&#x1f636;‍&#x1f32b;️一名双非本科大一小白。魔王的目标&#xff1a;&#x1f92f;努力赶上周围卷王的脚步。魔王的主页&#xff1a;&#x1f525;&#x1f525;&#x1f525;大魔王.&#x1f525;&#x1f525;&#x1f525; ❤️‍&#x1…

第九章:创建用户和用户权限

Windows&#xff1a;创建用户&#xff1a;第一种方法创建用户&#xff1a;先点右上角的工具&#xff0c;然后点击AD用户和计算机双击skills.com打开目录&#xff0c;再双击Users&#xff0c;进入文件夹中在右框中右击空白处&#xff0c;新建用户填充好用户信息后点击下一步然后…

Sophos防火墙日志管理

每天&#xff0c;Sophos防火墙都会生成大量的syslog数据&#xff0c;很难独自监控它们。借助EventLog Analyzer&#xff0c;您可以存档系统日志以满足合规性要求&#xff0c;并进行彻底的取证调查&#xff0c;以在发生任何问题&#xff08;例如网络入侵&#xff09;时获得宝贵的…

MySQL用户管理

文章目录MySQL用户管理用户用户信息创建用户修改用户密码删除用户数据库的权限MySQL中的权限给用户授权回收权限MySQL用户管理 与Linux操作系统类似&#xff0c;MySQL中也有超级用户和普通用户之分。如果一个用户只需要访问MySQL中的某一个数据库&#xff0c;甚至数据库中的某…

Unity 资源插件 Agents Navigation 3.1.1.unitypackage

Unity 插件 Agents Navigation 3.1.1.unitypackage 描述 这个软件包包括高性能、模块化和可扩展的代理导航。它是以 DOTS 为核心开发的&#xff0c;因此充分利用了 Unity 的最新技术栈&#xff0c;如 SIMD 数学、Jobs、Burst 编译器和 EntityComponentSystem。此外&#xff0c;…

【ASP.NET】原生JavaScript加Asp.net实现多图片上传

记录一下&#xff0c;Javascript加asp.net实现多文件上传的方法。首先看一下要实现的功能&#xff0c;图片比文字描述更直观。 一、前台代码 前台代码代码分为三个部分&#xff0c;一是HTML代码&#xff0c;二是Style样式代码&#xff0c;三是Javascript代码。 1.html代码 …

亿级高并发电商项目-- 实战篇 --万达商城项目 八(安装FastDFS、安装Nginx、文件服务模块、文件上传功能、商品功能与秒杀商品等功能)

专栏&#xff1a;高并发---分布式项目 &#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是小童&#xff0c;Java开发工程师&#xff0c;CSDN博客博主&#xff0c;Java领域新星创作者 &#x1f4d5;系列专栏&#xff1a;前端、Java、Java中间件大全、微信小程序、微信支…

C语言进阶——自定义类型:枚举、联合

&#x1f307;个人主页&#xff1a;_麦麦_ &#x1f4da;今日名言&#xff1a;如果不去遍历世界&#xff0c;我们就不知道什么是我们精神和情感的寄托&#xff0c;但我们一旦遍历了世界&#xff0c;却发现我们再也无法回到那美好的地方去了。当我们开始寻求&#xff0c;我们就已…

2023春招java面试题及答案

2023春招java面试题及答案总结1.以下Dubbo服务负载均衡策略中&#xff0c;哪一个策略的功能是相同参数的请求总是发到同一个提供者&#xff08;&#xff09;2.如下代码&#xff1a;请问编译运行的结果是什么&#xff1f;3.给出如下代码&#xff1a;请问编译运行的结果是什么&am…

英国访问学者邀请函范例

下面是知识人网访问学者老师分享的一个英国访问学者邀请函范例&#xff0c;邀请函不要复杂&#xff0c;提供签证官想看到的东西即可。Chen xxxDate of Birth: September 1th , 19xxSchool of Computer and InformationXXXX UniversityNo.X South RoadXXX city, XXX Province, 1…

1.Unity之Shader新手入门

Unity Shader着色器的基本概念如何使用Unity Shader着色器示例&#xff1a;如何使用Unity Shader着色器创建复杂的效果总结 什么是Unity中的Shader着色器&#xff1f; Shader着色器是用来控制物体外观的编程代码&#xff0c;它可以改变物体的颜色、纹理、光照、凹凸等&#xf…

智慧校园综合解决方案

在网络和信息技术的普及和国家教育信息化建设的推动下&#xff0c;以计算机网络为基础&#xff0c;以信息和知识资源的共享为手段&#xff0c;强调合作、分享、传承精神的网络化、数字化、智能化有机结合的新型教育、学习和研究的教育环境建设显得尤为重要。 智慧校园是利用信息…

leaflet 纯CSS的marker标记,不用图片来表示(072)

第072个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+leaflet中使用纯CSS来打造marker的标记。这里用到的是L.divIcon来引用CSS来构造新icon,然后在marker的属性中引用。 这里必须要注意的是css需要是全局性质的,不能被scoped转义为其他随机的css。 直接复制下面的 v…

【SPSS】频数分析和基本描述统计量详细操作教程(附实战案例)

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

记录--『uni-app、小程序』蓝牙连接、读写数据全过程

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 本文简介 这是一次真实的 蓝牙收发数据 的全过程讲解。 本文使用 uni-app Vue3 的方式进行开发&#xff0c;以手机app的方式运行(微信小程序同样可行)。 uni-app 提供了 蓝牙 和 低功耗蓝牙 的 api &…

深圳硬件黑客松活动,开放报名!

开源社KAIYUANSHE近期微信公众号订阅功能做调整啦&#xff01;没有被星标的账号在信息流里可能不显示大图了&#xff01;快星标⭐我们&#xff0c;就可以及时看到发布的文章啦&#xff01;STEP01 点击右上角标志STEP02 点击【设为星标】近年来&#xff0c;创客文化越来越受到人…

figma通过什么方式可以转换为sketch

Figma 如何转为 Sketch 文件&#xff1f;巧了&#xff0c;刚好我对这个问题很熟悉&#xff0c;作为一个使用过 Figma 也使用过 Sketch 的人来说&#xff0c;我还蛮希望两个软件能够互相打通的&#xff0c;不过不管是 Figma 也好还是 Sketch 也好&#xff0c;两个设计软件&#…