指针初阶(C语言)

news2024/11/29 22:54:22

指针

指针是什么

内存划分是一个字节一个字节来划分的,其中每个字节都有一个编号来标识它,我们将这个编号称为地址,而指针就是地址

注意:编号是不占内存空间的,(这些编号在内存中用十六进制表示)

 

指针变量:

指针变量:就是存放指针(地址)的变量!

我们可以用 & 符号 来取出变量的地址,把地址存放到指针变量中 !

通常我们口语中所说的指针其实就是指针变量,在口语中人们把指针跟指针变量都叫做指针,但我们得好好区分指针和指针变量

在指针变量里面存放的是一个变量字节中最低的地址!

总结:指针变量就是用来存放地址的变量,存放在指针变量里的值都被当做地址来处理!

内存中是如何编址的?

在内存中规定一个字节给一个对应的编址(地址)

如何编址:在32喂的机器下我们假设有32根地址线,那么每根地址线在寻址的时候会产生高电压或低电压,我们将这些高低电压用0和1代替!


那么32根地址线产生的地址情况:

00000000 0000000 00000000 00000000

00000000 0000000 00000000 00000001

……

01111111 11111111 11111111 11111111

10000000 00000000 00000000 00000000

……

11111111 11111111 11111111 11111111

在这里总共有 2的 32次方个地址(每个地址标识一个字节)

因为每个地址标识一个字节 所以2的32次方的个地址,就可以给4GB的空间进行编址

同理 在64位机器下,有2的64次方个地址,就可以给16384GB(理论上讲)空间进行编址

指针变量的大小

我们知道 8bit = 1byte 

在32位机器下,地址由32个比特位组成,

所以存放地址的指针变量,在32位机器下大小是4个字节

在64位机器下,地址由64个比特位组成

所以存放地址的指针变量,在64位机器下大小是8个字节

#include <stdio.h>

int main()
{
	int a = 10;
	int* p = &a;

	//在32位机器下都是4 
	//在64位机器下都是8
	printf("%zd\n", sizeof(p));
	printf("%zd\n", sizeof(int*));
	printf("%zd\n", sizeof(char*));
	printf("%zd\n", sizeof(float*));
	printf("%zd\n", sizeof(double*));

	return 0;
}

指针类型

在c中有很多数据类型,为了通过地址很好的访问这些类型的内容,我们用其对应的指针类型变量来存放这些类型的地址!

eg:char* 、short*  、int* 、float* 、double* ……都是指针的类型

        char*  来存放char类型变量的地址

        int* 存放int类型变量的地址

        ……

#include <stdio.h>

int main()
{
	int a = 100;
	int* pa = &a;//存放整型变量a的地址

	float b = 3.867;
	float* pb = &b;//存放单精度浮点型变量b的地址

	char c = 'A';
	char* pc = &c;//存放字符类型变量c的地址

	double d = 4.88998;
	double* pd = &d;//存放双精度浮点型变量d的地址

	//可以将它们的地址打印出来看是否存进去了
	printf("%p\n", &a);
	printf("%p\n", pa);

	printf("%p\n", &b);
	printf("%p\n", pb);

	printf("%p\n", &c);
	printf("%p\n", pc);

	printf("%p\n", &d);
	printf("%p\n", pd);

	//也可以通过将指针变量解引用操作,打印出指向变量的内容
	printf("%d\n", a);
	printf("%d\n", *pa);

	printf("%f\n", b);
	printf("%f\n", *pb);

	printf("%c\n", c);
	printf("%c\n", *pc);

	printf("%lf\n", d);
	printf("%lf\n", *pd);

	return 0;
}

指针的解引用

* 解引用操作符,对指针变量进行解引用操作

对指针变量进行解引用就是取到指针变量所指向空间的内容

指针的类型决定了对指针变量解引用的时候能操作几个字节

比如:char* 指针变量解引用只能访问一个字节的内存空间

            int* 指针变量解引用只能访问4个字节的内存空间

#include <stdio.h>
int main()
{
	int a = 10;
	//让指针变量指向a
	int* pa = &a;
	printf("%d\n", *pa);
	//通过指针解引用操作访问a并改变内容
	*pa = 20;
	printf("%d\n", a);
	
	//让字符型指针指向a
	char* p = (char*)&a;
	//通过指针访问a的第一个字节的内容
	//因为目前是小端存储第一个字节的值是20
	printf("%d\n", *p);
	printf("%d\n", *(p + 1));//访问第二个字节
	//通过指针解引用操作,访问到a的第一个字节将其改为39
	*p = 39;
	printf("%d\n", a);
	
	return 0;
}

野指针

野指针的成因

1、指针未进行初始化

2、指针越界访问

3、指针指向的空间被释放

#include <stdio.h>


int* test(void)
{
	int a = 10;
	int* p = &a;
	return p;
}

int main()
{
	//未初始化的指针
	int* p; //里面放的是随机值,不安全
	printf("%p\n", p);//vs编译器会报错


	//指针越界访问
	int arr[10];
	int i = 0;
	int* pa = arr;//数组名表示首元素地址
	for (i = 0; i <= 10; i++)
	{
		//当i=10的时候指针就越界访问了
		*pa = i + 1;
		pa++;
	}


	//指针指向的空间被释放
	int* ps = test();
	//函数返回来时a已经被释放,此指针时野指针
	printf("%p\n", ps);//ps里面存的地址
	printf("%d\n", *ps);//结果不可测

	return 0;
}

如何规避野指针

1、指针要初始化

2、小心指针越界

3、指针指向的空间释放,及时把指针置为NULL

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

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

#include <stdio.h>
#include <assert.h>

int* Max(int* p)
{
	int a = 10;
	int* p1 = &a;
	//不要返回局部变量的地址
	//return p1;
	//返回我们传进来的数组首元素的地址
	return p;
}

int main()
{
	//指针要初始化
	int a = 10;
	int* pa = &a;
	//当我们不知道该初始化成什么的时候将指针置为NULL
	char* pc = NULL;

	//要小心指针越界,不能让指针越界访问
	int arr[5];
	int* pr = arr;
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		*(pr+i) = i + 1;
		printf("%d ", *(pr + i));
	}
	printf("\n");

	//指针指向的空间被释放,及时将指针置为空
	//向堆区申请空间
	int* pm = (int*)malloc(sizeof(int) * 20);
	//空间进行释放
	free(pm);
	//及时将指针置为空
	pm = NULL;

	//避免返回局部变量的地址
	int* px = Max(arr);

	//指针在使用之前检查有效性
	char c = 'B';
	char* ptr =NULL;
	ptr = &c;
	//检查有效性
	if (ptr != NULL)
	{
		*ptr = 'A';
		printf("%c\n", c);
	}
	//也可用assert直接判断指针的有效性
	assert(ptr);
	*ptr = 'G';
	printf("%c\n", c);

	return 0;
}

指针运算

对指针进行运算

1、指针 + -整数

2、指针 - 指针

3、指针的关系运算

指针 + -  整数

指针加减整数、加的距离由指针的类型决定,(类型决定步长)

若是char* 的指针给它+1 指针往前走一个字节(地址+1)

若是int* 的指针给它+1 指针的步长为四个字节(地址+4)

同理:指针减多少 就往后走多少,步长由类型决定

总结:指针的类型决定了指针向前或者向后走的步长(距离)

#include <stdio.h>

int main()
{
	int a = 10;
	int* pa = &a;
	char* p1 = (char*)&a;

	//让指针 +1、-1 分别看步长
	printf("%p\n", pa);
	printf("%p\n", pa + 1);//前进4
	
	printf("%p\n", p1);
	printf("%p\n", p1 + 1);//前进1

	printf("%p\n", pa);
	printf("%p\n", pa - 1);//后退4

	printf("%p\n", p1);
	printf("%p\n", p1 - 1);//后退1

	return 0;
}

 指针 - 指针

指针相减的前提是指针都指向同一片内存空间

两个指针相减的绝对值是指针之间的元素个数(距离)

#include <stdio.h>

int my_strlen(char* p)
{
	char* q = p;//开始的位置
	while (*p != '\0')
	{
		p++;
	}
	return p - q;
}

int main()
{
	//指针 - 指针
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;//首元素地址
	int* q = &arr[9];//最后一个元素的地址

	//相减
	int sz = q - p;
	//两个指针之间的元素个数
	printf("%d\n", sz);

	//求字符串长度
	char str[] = "hello";
	int len = my_strlen(str);
	printf("str的长度为:%d\n", len);

	return 0;
}

指针的关系运算

标准规定:

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

#include <stdio.h>
#define NOW 5
int main()
{
	int arr[NOW];
	int* p = NULL;
	int i = 1;
	//这种可行
	for (p = &arr[NOW]; p > arr;)
	{
		/*p--;
		*p = i;*/
		*--p = i;
	}
	for (i = 0; i < NOW;i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");

	//尽量避免这样写,规定是不能指向第一个元素之前的那个地址进行比较
	for (p = &arr[NOW - 1]; p >= arr; p--)
	{
		*p = 3;
	}//大多数编译器能编过,但规定不能这样写
	for (i = 0; i < NOW; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

指针与数组

数组名表示的是首元素地址(只有在 &数组名 和sizeof(数组名)的时候表示整个数组)

arr[i] 等同于 *(数组名+i)

arr[i] 跟 i[arr] 是等价的 因为[] 是一个操作符,i跟arr是它的操作数

跟 a+b 写成 b+a 类似

#include <stdio.h>

int main()
{
	int arr[5] = { 1,2,3,4,5 };

	//数组名表示首元素地址
	printf("%p\n", arr);
	printf("%p\n", &arr[0]);

	//arr[i] 等同于 *(arr+i)
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%d ", *(arr + i));
	}
	printf("\n");
	//也可以将首元素地址用一个指针变量保存起来
	int* p = arr;
	for (i = 0; i < 5; i++)
	{
		printf("%d ", *(p+i));//也可用 p[i] 的方式访问
	}
	printf("\n");

	//arr[i] 与 i[arr] 等价
	//因为[] 是操作符,它的操作数是 arr和i 
	// 跟a+b 写成b+a 类似
	for (i = 0; i < 5; i++)
	{
		printf("%d ", i[arr]);//是一种不好的书写方式不推荐
	}

	return 0;
}

二级指针 

我们知道指针变量是存放地址的变量

指针变量本质上也是一个变量,变量是有地址的可以对它进行取地址操作,同理指针变量也有地址,也可对它进行取地址操作

我们知道变量的地址是存放在指针变量中的,那么用来存放指针变量的地址的变量我们叫做二级指针变量(二级指针)

即 二级指针变量就是用来存放一级指针变量的地址!


那么二级指针怎么写呢?

我们定义一个整型指针变量 :int  *  pa

其中 *号 表示 是一个指针变量,*后面的pa表示变量名,*号前面的 int 表示指针变量所指向空间的类型(也就是指针变量进行访问的时候一次可操作多大的内存空间)

同理:二级指针  也是如此: int* * ppa

其中 *号表示是一个指针变量,*号后面的ppa表示变量名,*号前面的 int* 表示指针变量所指向空间的类型,所指向空间是一个 int*(指针)


解引用操作:

对二级指针解引用,进行两次解引用操作即可

两次解引用:将二级指针先解引用一次取到一级指针

                      再将一级指针解引用一次得到指针最终指向的内容

#include <stdio.h>

int main()
{
	//二级指针
	
	//定义一个整型变量a并赋值成10
	int a = 10;
	
	//定义一个一级指针变量,并将整型变量a的地址存放到指针变量p中
	int* p = &a;

	//定义一个二级指针变量,并将一级指针变量的地址存放到二级指针变量pp中
	int** pp = &p;

	//解引用操作

	//用一级指针p取到变量a的值,将p解引用一次
	printf("%d\n", *p);

	//用二级指针pp取到变量a的值,将pp解引用两次
	//湘江pp解引用取到一级指针p  再将p解引用取到a
	printf("%d\n", *(*pp));

	return 0;
}

指针数组

指针数组是数组,数组里面存放的每个元素都是一个指针

#include <stdio.h>

int main()
{
	//指针数组

	int a = 10;
	int b = 20;
	int c = 30;
	
	//定义一个int* 类型的数组,数组里面存放的是a,b,c的地址
	int* arr[3] = { &a,&b,&c };
	//打印
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		//给指针解引用取到指向的内容
		printf("%d ", *arr[i]);
	}

	// 给指针(地址)解引用就能取到指向的内容
	int s = 200;
	printf("%d\n", *(&s));


	//用一个一维数组表示二维数组

	//定义三个一维数组并且给其赋值
	int a1[5] = { 1,2,3,4,5 };
	int b1[5] = { 6,7,8,9,10 };
	int c1[5] = { 11,12,13,14,15 };

	//定义一个指针数组,里面存放的是三个一维数组的首元素地址
	//数组名表示首元素地址
	int* arr1[3] = { a1,b1,c1 };

	//通过这个指针数组打印这三个一维数组
	int k = 0;//控制行数
	int j = 0;//控制列数
	for (k = 0; k < 3; k++)
	{
		for (j = 0; j < 5; j++)
		{
			//arr[k] 取到的是一维数组的首元素地址 
			//arr[k][j] 等同于 *(arr[i]+j)
			printf("%d ", arr1[k][j]);
		}
		printf("\n");
	}

	return 0;
}

 

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

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

相关文章

正厚软件 | App测试面试题及参考答案

最近整理了一些关于App测试的面试题。 本参照答案是本人在工作实践中总结&#xff0c;仅代表个人观点&#xff0c;如有错误&#xff0c;请谅解。 问&#xff1a;说一些你在测试过程中常用到的adb命名 答&#xff1a;回答本问题时&#xff0c;首先不要想到哪个命名就说哪个命令…

锐捷ISIS基础实验

目录 ISIS理论讲解 配置ISIS邻居建立 配置路由渗透&#xff08;泄露&#xff09; ISIS其它的配置特性 配置ISIS时间属性 配置ISIS认证 ISIS理论讲解 ISIS——基本概念1&#xff08;邻居建立、路由计算、报文封装&#xff09;_静下心来敲木鱼的博客-CSDN博客https://blog…

2022年全球高被引科学家公布

博士后、访问学者及联合培养申请者&#xff0c;都希望去名校及牛导麾下深造。名校有世界几大排名体系做参考&#xff0c;其知名度毋庸置疑。但牛导的概念是什么呢&#xff1f;知识人网小编在此介绍最新推出的“2022年度高被引科学家”&#xff0c;这里云集了全球自然科学和社会…

教你使用 SO_REUSEPORT 套接字选项提升服务性能

前言 Linux 网络栈中有一个相对较新的特性——SO_REUSEPORT 套接字选项&#xff0c;可以使用它来提升你的服务性能。 图 1: 上面的服务是使用并行监听器来避免请求连接瓶颈&#xff0c;而下面的服务只使用一个监听器来接收连接 概要 HAProxy 和 NGINX 是少数几个使用 Linux …

线段树什么的不是简简单单嘛,我教你!:基础篇

线段树什么的不是简简单单嘛&#xff0c;我教你&#xff01;&#xff1a;基础篇 零、序言——万物滴开篇 也许你是苦于笔试的打工人&#xff0c;也许你是步入算法圈不久的小小萌新&#xff08;我也是萌新&#xff09; &#xff0c;也许你是在网上搜索数据结构课设的倒霉学生。…

2049. 统计最高分的节点数目-数组树构造+遍历求解最大值数目

2049. 统计最高分的节点数目-数组树构造遍历求解最大值数目 给你一棵根节点为 0 的 二叉树 &#xff0c;它总共有 n 个节点&#xff0c;节点编号为 0 到 n - 1 。同时给你一个下标从 0 开始的整数数组 parents 表示这棵树&#xff0c;其中 parents[i] 是节点 i 的父节点。由于…

音视频 - 视频编码原理

目录 视频编码主要分为 图像的冗余 熵编码 帧内预测 帧间预测 DCT变换和量化 编码器比较 清晰度和耗时对比 一部电影1080P&#xff0c;帧率25fps&#xff0c;时长2小时&#xff0c;文件大小 1920x1080x1.5x25x2x360 521.4G 数据量非常大&#xff0c;对存储和网络传输都…

GMC Graph-Based Multi-View Clustering

GMC Graph-Based Multi-View Clustering 基于图的多视图聚类 abstract 现有的大多数方法没有充分考虑不同视图的权重&#xff0c;需要额外的聚类步骤来生成最终的聚类。还通常基于所有视图的固定图相似矩阵来优化目标。 本文提出了一种通用的基于图的多视图聚类算法(GMC)来解…

Android程序设计之学生考勤管理系统

基于安卓平台开发的学生考勤管理系统&#xff0c;本系统采用java语言设计&#xff0c;数据存储使用SQLite轻量级数据库实现 SQLite 简介 SQLite是一个软件库&#xff0c;实现了自给自足的、无服务器的、零配置的、事务性的 SQL 数据库引擎。SQLite是一个增长最快的数据库引擎&…

JSON 对比工具

文章目录JSON对比工具JSON对比工具 JSON 是 Web 开发领域中最常用的数据传输格式之一&#xff0c;因为 JSON 的可读性较高&#xff0c;对于一些简单的 JSON 数据&#xff0c;我们不需要借助任何工具就可以轻易的读取。但对于复杂的 JSON 数据就需要借助工具才行&#xff0c;本…

公众号文案写作技巧有哪些?教你几招

公众号文案写作是每个公众号运营者心中的痛&#xff1a; 你是否每天纠结写什么&#xff1f; 你是否写着写着就词穷了&#xff1f; 你是否不知道该如何下手&#xff1f; 公众号文案应该怎么写&#xff1f;今天伯乐网络传媒就来给大家分享一份超实用的公众号文案写作技巧&…

增量模型和迭代模型的优点与缺点

增量模型&#xff1a; 举个例子&#xff1a; 用户有一个需求&#xff0c;功能包含A,B,C... ABC 增量模型&#xff1a; 开发完A我就直接上线供给用户去使用 开发完C我就直接上线供给用户去使用 开发完B我就直接上线供给用户去使用 增量模型的特点 增量模型的特点…

度量BGP监测源数量对AS可见性的影响

首先&#xff0c;本文介绍了两个公开的BGP数据源项目情况&#xff1b;其次&#xff0c;从可见AS数量和可见AS边关系数量两个方面来分析度量BGP监测源中对等AS的可见性。 BGP数据源介绍 BGP数据源有2个公开的项目&#xff0c;分别是RIPE RIS和Route Views&#xff0c;它们使用路…

VUE基础编程(三)

案例要求 基于Vue Cli和嵌套路由技术&#xff0c;完成以下功能&#xff1a; 站点打开后会默认显示如图3.1所示的“关于公司”页面&#xff0c;单击图3.1页面上的“公司简介”链接&#xff0c;站点会显示如图3.2所示的“公司简介”页面&#xff0c;单击图3.1页面上的“公司治理…

【JAVA程序设计】基于SSM的学校教务管理系统-有文档

基于SSM的学校教务管理系统-有文档项目获取项目简介开发环境项目技术功能结构文档目录运行截图项目获取 获取方式&#xff08;点击下载&#xff09;&#xff1a;是云猿实战 项目经过多人测试运行&#xff0c;可以确保100%成功运行。 项目简介 本项目是基于SSM的学校教务管理…

[附源码]java毕业设计校园失物招领平台

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

idea反编译

1、问题描述 只有jar包&#xff0c;反编译下&#xff0c;看几个配置&#xff1b; 2、问题说明 用的idea里面的插件&#xff0c;java Decoplier&#xff0c;可以反编译jar包&#xff0c;效果挺好的&#xff0c;反编译出来的.java没乱码&#xff0c;可以直接看&#xff1b; 2…

139.深度学习分布式计算框架-2

139.1 Spark MLllib MLlib(Machine Learnig lib) 是Spark对常用的机器学习算法的实现库&#xff0c;同时包括相关的测试和数据生成器。MLlib是MLBase一部分&#xff0c;其中MLBase分为四部分&#xff1a;MLlib、MLI、ML Optimizer和MLRuntime。 ML Optimizer会选择它认为最适合…

4款企业常用的工时管理系统盘点

4款企业常用的工时管理系统有&#xff1a;1、Excel&#xff1b;2、8Manage 工时表&#xff1b;3、诺明软件&#xff1b;4、Aceteamwork。 “时间就是金钱”&#xff0c;相信大家都听过这句话。对于企业来说&#xff0c;管理员工工时&#xff0c;其实就是管理企业的人力成本和实…

数据结构-难点突破(C++实现树的双亲表示法,孩子表示法,孩子兄弟表示法(树转化为二叉树))

文章目录1. 树的双亲表示法2. 孩子表示法3. 孩子兄弟表示法&#xff08;树转化为二叉树&#xff09;普通树的存储一半采用三种方式&#xff1a; 双亲表示法&#xff1b;孩子表示法&#xff1b;孩子兄弟表示法&#xff1b; 1. 树的双亲表示法 思路和图片来源 采用双亲表示法…