指针---你真的会使用指针吗?

news2024/11/25 2:50:08

       

        指针作为C语言中的一个部分,可以说指针是C语言的核心,那么它的难度肯定是不言而喻的,总是能把人给绕得找不到方向。

        今天我就好好的说一说指针这个东西。

        1、何为指针?

        指针是C语言中用来存放地址的一个变量类型。我们可以将指针看作独特的一种变量,只用来存放地址。根据指针存放不同类型变量的地址,也可以将指针分为:int型指针,char型指针,等等。由于指针都是存放地址,而所有类型的地址又没有本质上的差异,所以无论任何类型的指针大小都是同样的。同为4个字节或者8个字节

        指针是 C 语言中的一种数据类型,可以用来存储内存地址。每个变量或对象都占据着计算机内存中的某个地址,指针变量可以保存这些地址,并允许对这些变量或对象进行间接访问

        

        2、一级指针

        想要了解指针,咱就得从最基础的一级指针开始。

        1、指针的声明

        在 C 语言中,指针变量声明时需要指定所指向的数据类型,以便编译器能够在使用指针时自动对其进行类型转换和内存管理等操作。

        例如,以下为一个整型指针变量的定义:

int a = 10;
int* pa = &a;

        *即代表这个pa是一个int型指针,指向的是a的地址。

        注意:声明指针之后,必须对其进行初始化!!!(否则它就是一个空指针)不能直接使用它!!!对空指针进行解引用操作系统可能会崩溃!!!后果非常严重!

        2、通过指针对变量或对象进行访问

int a = 10;
int* pa = &a;
*pa = 20;

        这里我们解引用pa,然后将pa的值赋值为20。(对pa解引用后,就相当于a。因为pa是a的地址,解引用a的地址,那就是找到了a)

        这里引出一个问题:

int a = 10, b = 20;
int* pa = &a, * pb = &b;
pa = pb;

        此时a的值是20吗???相信大部分对指针不了解的人都会认为此时a已经变成了20,因为将b的地址pb赋值给了pa。但是结果是:a仍然是10。首先:pa存的确实是a的地址,然后我们确实是对pa存放的地址进行了改变。但是!a是a,pa是pa,在这里改变pa,对a有影响吗?没有!!!!

a的地址并没有发生改变!!!所以a仍然是10。但是如果我们解引用pa后,会发现此时*pa的值是20,因为我们改变的是pa存放的地址。

        既然讲到这,那我们就得提出以下指针的运算了。指针没有复杂的运算,不能指针间进行运算。

        3、指针的运算

        赋值运算:相同类型的指针才能赋值。  算术运算:+,-,++,--  (这里的+和-,指的是+-多少,即对地址的运算,指向前面的地址或者后面的地址。并不是说简单的pa+pb,其实指针间是可以进行运算的,我们后面再谈论关系运算 :==,!=, <=,>=,<,>。

int a[10];
int* p1 = &a[2];
int* p2 = &a[5];
printf("%d", p2 - p1);//answer == 3

        answer == 3,why?        

        这里先不说,后面再说。

         1、算术运算

        a = *p++ ---->结果为:a = *p ,p++。

        a = (*p)++ --->结果为 : a = *p,*p++。

        2、关系运算

    若p1和p2指向同一数组,则  p1<p2    表示p1指的元素在前 。 p1>p2    表示p1指的元素在后。  p1==p2   表示p1与p2指向同一元素 。 若p1与p2不指向同一数组,则比较无意义。

        4、指针与函数

        先来个最简单的交换函数Swap。

        1、交换函数
void Swap(int x, int y)
{
	int c = x;
	x = y;
	y = c;
}
int main()
{
	int a = 10, b = 20;
	printf(" a = %d,b = %d", a, b);
	Swap(a, b);
	printf(" a = %d,b = %d", a, b);
	return 0;
}

        这个函数会起到交换a,b的值的作用吗???答案是不会!这个函数我们在传参的时候,只是将a,b的值传过去了,但没有影响地址。在Swap函数中创建临时变量来交换x,y的值,但是原来a,b的地址并没有改变。而地址才是决定一个变量的值。故a,b没有改变。

        所以应该用地址即指针来修改二者的值,才能改变a,b的地址,进而改变a,b的值。

void Swap(int* x, int* y)
{
	int c = *x;
	*x = *y;
	*y = c;
}
int main()
{
	int a = 10, b = 20;
	printf(" a = %d,b = %d", a, b);
	Swap(&a, &b);
	printf(" a = %d,b = %d", a, b);
	return 0;
}

        因此:函数参数为指针的时候,可以改变实参的值

        3、指针与数组名的关系

        1、数组名

        对于数组名,在大部分情况下数组名都代表首元素的地址。一维数组的数组名代表第一个元素的地址,二维数组的数组名代表第一行元素的地址(即二维数组的数组名代表的是一个一维数组的地址)。那么数组名就可以看作是一个指针。

        2、指针与数组访问元素的等价关系

        既然说了数组名就是指针,那么用数组名可以访问元素,用指针也可以访问元素了。

int arr[5] = { 1,2,3,4,5 };
int* parr = arr;
printf("%d ", *(parr + 1)); // 2
printf("%d ", *(arr + 1)); // 2

        由此可以看出来,对于一维数组,*(parr+i) 等价于arr[i]。同理对于二维数组,不同的是,对于二维数组需要用二级指针*(*(parr+i)+j)就等价于arr[i][j]。为什么呢?对于二维数组:*(parr+i)得到的是第i-1行的数组,而数组名又是一个首元素的地址,那么*(parr+i)得到的就是第i-1行的首元素地址,至此+j再解引用就和一维数组一样了。但是如果传参二维数组名,并不是以二级指针接收。二维数组名代表首行数组的地址,接收数组的地址,需要用数组指针接收。下面再细讲。

        3、字符数组和字符指针的区别

        1、字符数组
char str1[10] = "abcdef";
char str2[10];
str2 = "abcd";//错误!

        虽然数组名代表首元素地址,“abcd”也代表首元素a的地址,但是这样赋值给数组是错误的!我们只能通过拷贝来赋值给数组

char str2[10];
strcpy(str2, "abcd");

        对于此类赋值,我们是可以通过覆盖等操作来修改数组值的。下面将介绍另一种赋值法,这种赋值法是不能修改数组的值的。

            2、字符指针           
	char* str;
	str = "abcde";

        通过指针来给字符数组赋值,这样赋值的时候,“abcde”叫做字符串常量,字符串常量是不能修改的。则此时str是不能修改它的内容的。 

         4、数组指针

        1、数组指针的定义     

        数组指针:就是一个指向数组的指针,这里的指针代表的是整个数组的地址,并非数组首元素的地址。注意区分。

        2、数组指针的表示

int arr[3] = { 1,2,3 };
int(*p)[3] = &arr;

        我们来解析一下数组指针:p代表指针名称,*p代表p是一个指针,[ 10 ]代表这个指针指向一个拥有十个元素的数组,int代表这个数组是int类型的。

        注意: 这里需要用括号将*p结合起来,因为[ ]的优先级比*的优先级高。如果不用括号结合的话,这其实是一个指针数组。

        接下来我们将用代码来分析数组指针和数组名的差别:

	int arr[3] = { 1,2,3 };
	int(*p)[3] = &arr;
	printf("p = %p\n", p);
	printf("arr = %p\n", arr);
	printf("p + 1 = %p\n", p + 1);
	printf("arr + 1 = %p\n", arr + 1);

        可知:p和arr的地址都是一样的,都是指向数组的起始位置的地址。

        但是p+1和arr+1的地址就不一样了:发现:p+1比p的地址增加了12个字节,即三个地址的大小。而arr+1比arr只增加了4个字节,即一个地址的大小。

        由此可以看出:数组指针代表整个数组的地址,数组名只代表数组首元素的地址

        3、数组指针与二维数组传参

        通过上面我们知道,二维数组名代表首元素地址,这里的首元素地址,代表二维数组的第一行元素的地址,即看作一个一维数组的地址。既然如此,一维数组名传参我们需要用一级指针来接收地址。那么二维数组名传参,我们是否需要用数组指针来接收一维数组的地址呢?答案是:是的,要用数组指针

void fun(int(*arr)[3], int row, int col)
{
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
		{
			printf("%d ", arr[i][j]);//或者*(*(arr+i)+j)
		}
		printf("\n");
	}
}
int main()
{
	int arr[2][3] = { {1,2,3},{4,5,6} };
	fun(arr, 2, 3);
	return 0;
}

        注意:这里新参的[ ]里边,必须是那个一维数组的元素个数,否则打印输出的时候就不会正常打印。(如果小于元素个数,它会打印你第二行的元素,因为二维数组在内存中其实是线性连续的。如果大于数组个数,它会乱输出空间中数组末尾后边的数据) 但是:对于二维的字符数组,可能每一个一维数组的字符数是不同的,那么这里使用数组指针接收,[ ]里面就不知道写多少。那么对于这种情况,我们需要用到二级指针,但是这里的二维数组我们需要动态开辟,不能直接自己简单定义二维数组。

        5、二级指针 

         二级指针,显然就是接收二维数组名的指针,但是这里并不是简单的直接传入二维数组名,这里二维数组名需要动态开辟。

        1、二级指针与动态开辟字符二维数组

        一般使用二级指针是为了来应对二维字符数组的每个一维数组元素个数不同的情况。普通情况下,二维数组还是用数组指针来接收。

void fun(char** str, int row)
{
	for (int i = 0; i < row; i++)
	{
		printf("%s\n", str[i]);
	}
}
int main()
{
	char** str = (char**)malloc(sizeof(char*) * 3);//开辟一个二维字符数组,一共有三个字符串
	for (int i = 0; i < 3; i++)
	{
		str[i] = (char*)malloc(sizeof(char) * 10);//给每个字符串开辟空间
		scanf("%s", str[i]);//输入
	}
	fun(str, 3);
	return 0;
}

        6、其他指针类型

        1、函数指针 

        函数指针:顾名思义就是存放函数地址的指针咯。那么问题来了:函数也有地址吗? 函数当然有地址,不然在调用函数的时候,在内存中哪里去寻找这个函数呢?

        1、函数名

        首先来说一下函数名(地址):

int Add(int x, int y)
{
	return x + y;
}
int main()
{
	printf("%p\n", &Add);
	printf("%p\n", Add);
	return 0;
}

        由此我们可以看出,对于函数名,无论是对它取地址,还是直接是函数名,都代表的这个函数的地址。

         2、函数指针的定义
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int* p1 = Add;
	printf("%p\n",p1);
	int (*p2)(int, int) = Add;
	printf("%p\n", p2);
	return 0;
}

        对于如上两种定义,虽然二者打印结果相同,但是第一种定义是错的。在好的编译器下,会发出警告:“初始化”:“int *”与“int (__cdecl *)(int,int)”的间接级别不同。这个警告就说明这种定义函数指针的方法其实是错的,二者并不相同。

        所以正确的函数指针定义方法应该是第二种

	int (*p2)(int, int) = Add;
        3、解析函数指针
	int (*p2)(int, int) = Add;

        p2:指针名称;*p2代表是一个指针类型;前面的int:代表函数的返回值是int;(int,int):代表这个函数的两个参数是int和int类型的。

        4、调用函数指针 
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int (*p2)(int, int) = Add;
	int ret = p2(1, 2);
	printf("%d", ret);
	return 0;
}

         我们可以直接运用函数指针来执行该函数的功能(虽然多此一举哈哈哈哈哈)。

        那有人会问了,为什么这里p2没有解引用就可以使用?前面讲了,函数名取地址和不取地址的结果都是一样的,那么解引用和不解引用结果也是一样的

        2、其他

        对于指针,我们其实可以引出很多不同种类的指针,越往后,指针越头大,我们只需要掌握这些常用的就行了。

        6、总结 

        写了这么久,都给我写累了,但是指针的奥妙远不止于此,还有很多细节需要自己去琢磨。哎,路漫漫其修远兮~ 

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

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

相关文章

图像增强2023最新经典顶会论文和代码合集,附开源数据集下载

图像增强是图像处理领域长期的、不可回避的研究课题&#xff0c;也是活跃在各大顶会中的热门研究方向。其应用范围广泛涉及医学影像分析、安全监控、智能交通、目标检测与识别、遥感影像处理等领域&#xff0c;具有很高的实用性&#xff0c;因此每年的相关论文数量也比较多。 …

2023/12/20 work

1. 使用select完成TCP客户端程序 服务端&#xff1a;#include <stdio.h> #include <string.h> #include <stdlib.h> #include <myhead.h>#define PORT 9999 //端口号 #define IP "192.168.125.12" //IP地址int ma…

C++实现位图

目录 一、什么是位图 二、位图类 1.参数及构造函数 2.set函数设置为1&#xff08;代表存在&#xff09; 3.reset函数设置为0&#xff08;代表不存在&#xff09; 4.test函数查看状态&#xff08;0还是1&#xff09; 三、位图的变形 一、什么是位图 位图这个词汇比较少见…

一行代码修复100vh bug

你知道奇怪的移动视口错误&#xff08;也称为100vh bug&#xff09;吗&#xff1f;或者如何以正确的方式创建全屏块&#xff1f; 一、100vh bug 什么是移动视口错误&#xff1f; 你是否曾经在网页上创建过全屏元素&#xff1f;只需添加一行 CSS 并不难&#xff1a; .my-page {h…

PAT 乙级 1023 组个最小数

1023 组个最小数 分数 20 作者 CAO, Peng 单位 Google 给定数字 0-9 各若干个。你可以以任意顺序排列这些数字&#xff0c;但必须全部使用。目标是使得最后得到的数尽可能小&#xff08;注意 0 不能做首位&#xff09;。例如&#xff1a;给定两个 0&#xff0c;两个 1&#xff…

【教程】cocos2dx资源加密混淆方案详解

1,加密,采用blowfish或其他 2,自定是32个字符的混淆code 3,对文件做blowfish加密,入口文件加密前将混淆code按约定格式(自定义的文件头或文件尾部)写入到文件 4,遍历资源目录,对每个文件做md5混淆,混淆原始串“相对路径”“文件名”混淆code, 文件改名并且移动到资源目录根…

Python开发GUI常用库PyQt6和PySide6介绍之二:设计师(Designer)

Python开发GUI常用库PyQt6和PySide6介绍之二&#xff1a;设计师&#xff08;Designer&#xff09; PySide6和PyQt6都有自己的设计师&#xff08;Designer&#xff09;&#xff0c;用于可视化地设计和布局GUI应用程序的界面。这些设计师提供了丰富的工具和功能&#xff0c;使开…

acwing-蓝桥杯C++ AB组辅导课Day2-递归习题+递推+二分

感谢梦翔老哥的蓝桥杯C AB组辅导课~ 递归习题&#xff1a; 1.递归实现组合型枚举 题意&#xff1a; 题目要求输出组合枚举&#xff0c;与排列不同&#xff0c;排列具有顺序之分&#xff0c;对于组合来说&#xff0c;是没有顺序之分的&#xff0c;所以[1,2,3]和[3,2,1]被看成同…

灰盒测试简要学习指南!

在本文中&#xff0c;我们将了解什么是灰盒测试、以及为什么要使用它&#xff0c;以及它的优缺点。 在软件测试中&#xff0c;灰盒测试是一种有用的技术&#xff0c;可以确保发布的软件是高性能的、安全的并满足预期用户的需求。这是一种从外部测试应用程序同时跟踪其内部操作…

【【迭代16次的CORDIC算法-verilog实现】】

迭代16次的CORDIC算法-verilog实现 -32位迭代16次verilog代码实现 CORDIC.v module cordic32#(parameter DATA_WIDTH 8d32 , // we set data widthparameter PIPELINE 5d16 // Optimize waveform)(input …

数字化时代的智能支持:亚马逊云科技轻量应用服务器技术领先

轻量应用服务器是一种简化运维、门槛低的弹性服务器&#xff0c;它的"轻"主要体现在几个方面&#xff1a;开箱即用、应用优质、上手简洁、投入划算、运维简便以及稳定可靠。相较于普通的云服务器&#xff0c;轻量应用服务器简化了云服务的操作难度、使用和管理流程&a…

全链路压测之分布式架构/SkyWalking链路追踪/中间件

最近刷题&#xff0c;学习了些压测的知识&#xff0c;大多是在小破站上的笔记&#xff0c;仅供大家参考~ 一、分布式微服务架构 微服务&#xff1a;多个系统之间相互调用 全链路&#xff1a;简单理解&#xff0c;就是一个系统调用另一个系统 二、SkyWalking链路追踪平台 链路…

阿里云经济型、通用算力型、计算型、通用型、内存型云服务器最新活动报价

阿里云作为国内领先的云计算服务提供商&#xff0c;提供了多种规格的云服务器供用户选择。为了满足不同用户的需求&#xff0c;阿里云推出了经济型、通用算力型、计算型、通用型和内存型等不同类型的云服务器。下面将详细介绍这些云服务器的最新活动报价。 一、阿里云特惠云服…

01-Go语言介绍以及win环境搭建

1、Go 语言介绍 Go 即 Golang&#xff0c;是 Google 公司 2009 年 11 月正式对外公开的一门编程语言。 根据 Go 语言开发者自述&#xff0c;近 10 多年&#xff0c;从单机时代的 C 语言到现在互联网时代的 Java&#xff0c; 都没有令人满意的开发语言&#xff0c;而 C往往给人…

python学习笔记--异常捕获

异常场景 numinput("input you number:") n9000 try:resultn/int(num)print({} 除以num 结果为{}.format(n,result)) except ZeroDivisionError as err:print("0不可以作为除数&#xff0c;出现报错{}".format(err)) except ValueError as err:print(&quo…

【超图】SuperMap iClient3D for WebGL/WebGPU ——地形影像

作者&#xff1a;taco 号外&#xff01;号外&#xff01;开新坑了&#xff01;开新坑了&#xff01;对于一个代码小白来讲&#xff0c;设置可能是刚接触开发的人&#xff08;还没接触准备接触&#xff09;的人来说。对于读代码或是在对产品的使用上会存在许许多多的疑惑。接下来…

Mybatis基本操作

目录 准备工作 删除操作 预编译SQL 增加操作 获取返回的主键 更新操作 准备工作 准备数据库表 emp创建一个新的springboot工程&#xff0c;选择引入对应的起步依赖&#xff08;mybatis、mysql驱动、lombok&#xff09;application.properties中引入数据库连接信息创建对应…

HTML+CSS做一个冰立方体时钟

文章目录 💕效果展示💕代码展示HTMLJS💕效果展示 💕代码展示 HTML <!DOCTYPE html> <html lang

Python简介:一种强大的编程语言

Python是一种高级、通用的编程语言&#xff0c;以其简洁易读的语法和强大的功能而闻名。它广泛应用于各种领域&#xff0c;包括软件开发、数据分析、人工智能等。本文将详细介绍Python的特点、应用领域以及如何开始学习Python。 &#xfeff; &#xfeff;一、Python的特点 1…

vue2 el-table 行按钮过多,按钮超出指定个数,显示为下拉菜单(简单的自定义组件)01

vue2 el-table 行按钮过多&#xff0c;按钮超出指定个数&#xff0c;显示为下拉菜单&#xff08;简单的自定义组件01&#xff09; 上图 优化前 按钮太多不美观 优化后 默认展示三个按钮 超出显示下拉菜单 上代码 封装按钮组件 OperateBtn.vue // OperateBtn.vue<templ…