指针(进阶)

news2024/11/29 19:55:48

指针进阶:

       通过指针基础我们已经了解了指针,这篇文章我们会举大量的例子,使我们对指针透彻理解,我们下来看一段代码:

int main()
{
	char a[] = "ab";
	char* pc = a;
	printf("%c\n", *pc);
	printf("%s\n", pc);
	//当指针接收的是字符串时,打印这个指针所指向的字符串不需要解引用
	return 0;
}

       当指针接收的是字符串时,打印这个指针所指向的字符串不需要解引用;可是当要打印首个元素时,需要解引用。

int main()
{
	char* ch = "abcdef";
	//"abcdef"是一个常量字符串
	printf("%c\n", *ch);
	printf("%s\n", ch);
	return 0;
}

       在这个例子中,字符串“abcdef”,储存在连续的无名储存区中,通过语句ch=“abcdef”,将无名储存区的首地址赋给指针ch,也就是说:指针变量ch指向无名储存区域的首地址,而不是把无名储存区域的内容保存在ch中,因为它们各自所占空间大小不同(指针变量大小是固定的)。

       道理同上,打印字符串不需要解引用,打印首元素需要解引用。 

       此时我们想修改其中一个字符。

int main()
{
	char* ch = "abcdef";
	//"abcdef"是一个常量字符串
	//所以*p就是首个元素
	*ch= 'W';
	printf("%s\n", ch);
	return 0;
}

       无法输出结果,因为"abcdef"是常量字符串,常量意味着不可改变,这就是错误的,所以最好这样写const char*ch,在星号前加上const,下面就直接报错了。

       我们也可以这样访问:

指针的比较: 

       在基础篇我们其实就已经已经讲过指针的比较了(详情指针(基础篇)-CSDN博客),指针比较必须是指向同一块内存的,我们来看代码。

int main()
{
	char arr1[] = "abcd";
	char arr2[] = "abcd";
	char* p1 = "abcd";
	char* p2 = "abcd";
	if (arr1 == arr2)
		//比较的是地址
		printf("hehe\n");
	else
		printf("haha\n");
	return 0;
}

       创建了两个字符型数组,虽然内容一样,可是两个数组各有各的内存空间,地址完全不同,所以执行else。 

       但是p1和p2确实指向同一块空间,这就很奇怪,内容相同的常量字符串只会保存一份,所以他们只想的空间的地址相同。

       还有一种指针类型是空指针类型,不是整形,不是字符型,不是数组型,而是一种空指针类型。空指针类型可以接收任何类型的地址。

int main()
{
	int a = 10;
	void* p = &a;//空指针可以接收任意类型的地址
	    //void* 类型指针不能进行解引用操作
	p++;//void* 类型的指针不能进行+-操作(不知道步长)
	printf("%p\n", p);//空指针有地址
	printf("%d\n", *p);//空指针类型不能通过解引用来找到原有数据
	return 0;
}

       此时会报错,是编译性错误。

       当const修饰变量时,我们不能直接去修改它,但是还是可以通过指针修改。 

       const分为放在*的左边和右边,控制的权限不一样。

       当constant限制的是*p,意思是不能通过p来修改指向的空间内容,就是不能解引用了;当const放在*的右边,限制的是p变量,也就是p变量不能被修改了,没办法再指向其他地址了,但*p不受限制,还是可以通过p来修改所指向的变量。 

指针练习题: 

       此时我们已经几乎会所有的指针相关概念,那么我们就拿一些小习题练练手。

习题一: 


int main()
{
    int a[5] = { 1,2,3,4,5 };
    int* ptr = (int*)(&a + 1);//下一个数组的地址,就变成了数组指针类型,之后强制转换
    printf("%d,%d\n", *(a + 1), *(ptr - 1));//-1就是数组的最后一个元素
    return 0;
}

       把&a+1的地址强制付给ptr,因为取地址a+1是下一个数组的地址,所以要强制转换类型。注意:*(a+1)==a[1]。

习题二: 

       


struct Test
{
    int Num;
    char* pcNum;//64位指针大小是8字节
    short sDate;//short是两个字节
    char cha[2];
    short sBa[4];
}*p;//此结构体大小为32
int main()
{
    p = (struct Test*)0x100000;

    printf("%p\n", p + 0x1);

    printf("%p\n", (unsigned long)p + 0x1);

    printf("%p\n", (unsigned int*)p + 0x1);
    
    return 0; 
}

习题三: 

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

    int* ptr1 = (int*)(&a + 1);

    int* ptr2 = (int*)((int)a + 1);

    printf("%x,%x", ptr1[-1], *ptr2);
    return 0;
}

       一定要记得,强制转换的优先级>加减的优先级。 

习题四: 

int main()
{
    int a[3][2] = { (0,1),(2,3),(4,5) };//注意是逗号运算符

    int* p;

    p = a[0]; 
    printf("%d\n", p[0]);
    printf("%d\n", p[1]);//等价于*(p+1)
    //结果为1 3
    return 0;
}

       注意是小括号,小括号里逗号是运算符。

习题五: 


int main()
{
    int a[5][5];
    int(*p)[4];//定义了一个指针数组
    p = a;

    printf("%p,%d\n", &p[4][2] - &a[4][2],&p[4][2] - &a[4][2]);
    return 0;
}

 

习题六: 

int main()
{
    int a[2][5] = { 1,2,3,4,5,6,7,8,9,10 };
    int* ptr1 = (int*)(&a + 1);
    int* ptr2 = (int*)(*(a + 1));

    printf("%d,%d", *(ptr1 - 1), *(ptr2));
    //*(a+1)是第二行数组的地址,解引用后只想第二行数组的第一个元素
    return 0;
}

       二维数组如arr[1],是第一行第一个元素的地址,arr[2]是第二行第一个元素的地址。

习题七: 


int main()
{
    char* a[] = { "work","at","alibaba" };//指针数组,数组每一个元素是char*
    //每个char*都存放每个元素首元素地址

    char** pa = a;//二级指针,类型为char*,变量为指向char*

    pa++;

    printf("%s\n", *pa);
    return 0;
}

习题八: 

int main()
{
    char* c[] = { "ENTER","NEW","POINT","FIRST" };
    char** cp[]={ c + 3,c + 2,c + 1,c };
    //因为c是首元素地址,c+3指向第四个元素
    //所以cp首元素指向c的第四个元素地址
    char*** cpp = cp;

    printf("%s\n", **++cpp);

    printf("%s\n", *-- * ++cpp + 3);
    //注意此时解引用后又--了,原来c+1变为c也被保留下来
    printf("%s\n", *cpp[-2] + 3);

    printf("%s\n", cpp[-1][-1] + 1);

    printf("%s\n", **cpp);
    return 0;
}

       看一下内存布局: 

 

       注意指针的自增自减,会改变原来指针指向。 

习题九: 


int main()
{
    unsigned long pu[] = { 6,7,8,9,10 };
    unsigned long* pulptr;
    pulptr = pu;
    *(pulptr + 3) += 3;//指针指向没有改变
    //把第4个元素的值换为12
    printf("%d,%d\n", *pulptr, *(pulptr + 3));
    return 0;
}

 指针sizeof和strlen习题:

       先看几个知识点,sizeof读取‘\0’,strlen不读取‘\0’;strlen接收的是地址。一定要记得,字符串的双引号中最后包含一个'/0',可以不写,但是有。如果用大括号是字符串,没有声明个数,也没有声明'/0',也不知道字符串在哪里结束;若声明了个数,一定要有'/0'的位置。

       两个例外情况:

  1. sizeof(数组名) - 数组名表示整个数组大小。
  2. &数组名 - 数组名表示整个数组。

习题一:

int main()
{
    //数组名是元素地址
    //例外:
    //1.sizeof(数组名) - 数组名表示整个数组
    //2.&数组名 - 数组名表示整个数组
    int a[] = { 1,2,3,4 };//4*4=16
    printf("%d\n", sizeof(a));  //16    sizeof(数组名)-计算数组总大小
    printf("%d\n", sizeof(a+0));//8/4 数组名+0,这里表示首元素地址,地址大小就是指针大小
    printf("%d\n", sizeof(*a)); //4     数组名解引用,找到数据类型
    printf("%d\n", sizeof(a+1));//8/4   指针指向数组名第二个元素,是指针大小
    printf("%d\n", sizeof(a[1]));//4    数组第二个元素大小
    printf("%d\n", sizeof(&a)); //8/4    &a是数组地址,大小为指针大小
    printf("%d\n", sizeof(*&a));//16     &a使整个数组的地址,解引用后就是整个数组大小
    printf("%d\n", sizeof(&a + 1));//8/4 先&a整个数组大小,之后指向下一个数组,还是地址
    printf("%d\n", sizeof(&a[0]));//8/4  []方块优先级高,还是地址
    printf("%d\n", sizeof(&a[0]+1));//8/4 第二个元素的地址,还是地址
    return 0;
}

 

习题二: 

int main()
{
    char a[] = { 'a','b','c','d','e','f' };
    //strlen函数也是从首元素地址读取到'\0'为止
    printf("%d\n", strlen(a));     //随机值,strlen找到'\0'才停止
    printf("%d\n", strlen(a+0));   //随机值,和上一个一样
    //printf("%d\n", strlen(*a));    //随机值,'a' - 97 strlen从地址97开始寻找 报错
    //printf("%d\n", strlen(a[1]));  //和上一个一样
    printf("%d\n", strlen(&a));    //和第一个一样
    printf("%d\n", strlen(&a+1));  //和第一个相差6
    printf("%d\n", strlen(&a[0]+1));//和第一个相差1
    return 0;
}

       strlen函数读取到‘\0’停止,在大括号中没有‘\0’,所以第1个是随机数。 

习题三: 

       用指针定义一个字符串并研究大小。


int main()
{
    char* p = "abcdef";
    printf("%d\n", sizeof(p));     //8/4 p是指针,存放a的地址
    printf("%d\n", sizeof(p+1));   //8/4 b的地址,还是地址
    printf("%d\n", sizeof(*p));    //1   是数据类型的大小
    //解引用后找到a,a是字符型大小为1
    printf("%d\n", sizeof(p[0]));  //1   是数据类型大小
    printf("%d\n", sizeof(&p));    //8/4 还是地址
    printf("%d\n", sizeof(&p+1));  //8/4
    printf("%d\n", sizeof(&p[0]+1));//8/4
    return 0;
}

习题四: 

int main()
{
    char a[] = { 'a','b','c','d','e','f' };
    printf("%d\n", sizeof(a));   //6   这里不是字符串,所以是6个字节
    printf("%d\n", sizeof(a+0)); //4/8 还是地址
    printf("%d\n", sizeof(*a));  //1   解引用后就是数据类型大小 
    printf("%d\n", sizeof(a[1]));//1   数组第一个元素大小
    printf("%d\n", sizeof(&a));  //4/8 地址
    printf("%d\n", sizeof(&a+1));//4/8 地址
    printf("%d\n", sizeof(&a[0]+1));// 地址
    return 0;
}

习题五: 

int main()
{
    char a[] = "abcdef";
    printf("%d\n", sizeof(a));      //7 因为有默认'\0',sizeof读取它
    printf("%d\n", sizeof(a+0));    //8 地址
    printf("%d\n", sizeof(*a));     //1 数据类型大小
    printf("%d\n", sizeof(a[1]));   //1
    printf("%d\n", sizeof(&a));     //8 地址
    printf("%d\n", sizeof(&a+1));   //8
    printf("%d\n", sizeof(&a[0]+1));//8
    return 0;
}

习题六: 

int main()
{
    char a[] = "abcdef";
    printf("%d\n", strlen(a));      //6 strlen不读取'\0'
    printf("%d\n", strlen(a+0));    //6 首元素地址
    //printf("%d\n", strlen(*a));   //报错 非法访问
    //printf("%d\n", strlen(a[1])); //报错 非法访问
    printf("%d\n", strlen(&a));     //6 有警告,这相当于数组,应该用数组指针接收
    printf("%d\n", strlen(&a+1));   //随机值
    printf("%d\n", strlen(&a[0]+1));//5 从b开始读取
    return 0;
}

 习题七:


int main()
{
    char* p = "abcdef";
    printf("%d\n", sizeof(p));     //8/4 p是指针,存放a的地址
    printf("%d\n", sizeof(p+1));   //8/4 b的地址,还是地址
    printf("%d\n", sizeof(*p));    //1   是数据类型的大小
    //解引用后找到a,a是字符型大小为1
    printf("%d\n", sizeof(p[0]));  //1   是数据类型大小
    printf("%d\n", sizeof(&p));    //8/4 还是地址
    printf("%d\n", sizeof(&p+1));  //8/4
    printf("%d\n", sizeof(&p[0]+1));//8/4
    return 0;
}

习题八:

int main()
{
    char* p = "abcdef";
    printf("%d\n", strlen(p));        //6 a的地址被接收,之后计算长度 
    printf("%d\n", strlen(p + 1));    //5 b的地址,长度少了1
    //printf("%d\n", strlen(*p));     //报错 
    // a的ASCII码是97,从地址97处读取,报错  
    //printf("%d\n", strlen(p[0]));   //报错
    printf("%d\n", strlen(&p));       //随机值
    printf("%d\n", strlen(&p+ 1));    //随机值
    printf("%d\n", strlen(&p[0] + 1));//5 第二个元素读取地址
    return 0;
}

习题八: 

int main()
{
    int a[3][4] = { 0 };
    printf("%d\n", sizeof(a));   
    //48 总数组大小
    printf("%d\n", sizeof(a[0][0]));
    //4 第一个数组第一个元素的大小
    printf("%d\n", sizeof(a[0]));
    //16 第一个元素是一维数组,一维数组的大小
    printf("%d\n", sizeof(a[0]+1));
    //8/4 a[0]是第一行的数组名,数组名此时是首元素地址,a[0]是第一行第一个元素地址
    //所以 a[0]+1就是第一行第二个元素地址,地址的大小
    printf("%d\n", sizeof(*(a[0]+1)));
    //4 第一个数组中第二个元素的地址,解引用后找到元素,是类型的大小
    printf("%d\n", sizeof(a+1));
    //8/4 第二个元素是一维数组的地址,地址的大小
    printf("%d\n", sizeof(*(a+1)));
    //16 sizeof(a[1])第二个数组中的第一个元素的地址,解引用后是它第二个元素的大小
    printf("%d\n", sizeof(&a[0]+1));
    //8/4 第二个一维数组的地址,地址的大小
    printf("%d\n", sizeof(*(&a[0] + 1)));
    //16 第二个一维数组地址解引用,第二个一维数组元素的大小
    printf("%d\n", sizeof(*a));
    //16 第一行一维数组元素的大小
    printf("%d\n", sizeof(a[3]));
    //16 sizeof不去真实计算,可能会举一反三,根据类型计算大小
    return 0;
}

       a是二维数组的数组名,没有sizeof(数组名),也没有&(数组名),所以a是首元素地址,二维数组的首元素是第一行的一维数组。对二维数组解引用,求大小是第一行的大小。 

 习题九:

       这里可以看出指向常量字符串是指向字符串的首元素地址。 

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

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

相关文章

指定分隔符对字符串进行分割 numpy.char.split()

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 指定分隔符对字符串进行分割 numpy.char.split() 选择题 请问下列程序运行的的结果是: import numpy as np print("【执行】np.char.split(I.Love.China, sep .)") p…

GNSEC 2022年第8届全球下一代软件工程线上峰会-核心PPT资料下载

一、峰会简介 新一代软件工程是指利用新的理论、方法和技术,在软件开发、部署、运维等过程中,实现软件的可控、可预测、可维护的软件生产方式。它涵盖了多个领域,如软件开发、测试、部署、运维等,旨在提高软件生产效率和质量。 …

13款趣味性不错(炫酷)的前端动画特效及源码(预览获取)分享(附源码)

文字激光打印特效 基于canvas实现的动画特效&#xff0c;你既可以设置初始的打印文字也可以在下方输入文字可实现激光字体打印&#xff0c;精简易用。 预览获取 核心代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8&q…

Windows server 部署iSCSI共享磁盘搭建故障转移群集

在域环境下&#xff0c;在域控制器中配置iSCSI服务&#xff0c;配置共享网络磁盘&#xff0c;在节点服务器使用共享磁盘&#xff0c;并在节点服务器中搭建故障转移群集&#xff0c;实现故障转移 环境准备 准备3台服务器&#xff0c;配置都是8g2核&#xff0c;50g硬盘&#xf…

SpringBoot 属性配置解析

属性配置介绍 spring官方提供的17中属性配置的方式 Devtools全局配置测试环境TestPropertySource注解测试环境properties属性命令行参数SPRING_APPLICATION_JSON属性ServletConfig初始化参数ServletContext初始化参数JNDI属性JAVA系统属性操作系统环境变量RandomValueProperty…

9大高效的前端测试工具与框架!

在每个Web应用程序中&#xff0c;作为用户直接可见的应用程序外观&#xff0c;“前端”包括&#xff1a;图形化的用户界面、相应的功能、及其整体站点的可用性。我们可以毫不夸张地说&#xff1a;如果前端无法正常工作&#xff0c;您将无法“拉新”网站的潜在用户。这也正是我们…

HarmonyOS4.0从零开始的开发教程02初识ArkTS开发语言(上)

HarmonyOS&#xff08;二&#xff09;初识ArkTS开发语言&#xff08;上&#xff09;之TypeScript入门 前言 Mozilla创造了JS&#xff0c;Microsoft创建了TS&#xff0c;而Huawei进一步推出了ArkTS。因此在学习使用ArkTS前&#xff0c;需要掌握基本的TS开发技能。 从最初的基…

Kafka 的消息格式:了解消息结构与序列化

Kafka 作为一款高性能的消息中间件系统&#xff0c;其消息格式对于消息的生产、传输和消费起着至关重要的作用。本篇博客将深入讨论 Kafka 的消息格式&#xff0c;包括消息的结构、序列化与反序列化&#xff0c;以及一些常用的消息格式选项。通过更丰富的示例代码和深入的解析&…

人工智能-编译器和解释器

编译器和解释器 命令式编程使用诸如print、“”和if之类的语句来更改程序的状态。 考虑下面这段简单的命令式程序&#xff1a; def add(a, b):return a bdef fancy_func(a, b, c, d):e add(a, b)f add(c, d)g add(e, f)return gprint(fancy_func(1, 2, 3, 4)) 10 Python…

【分布式微服务专题】从单体到分布式(一、SpringCloud项目初步升级)

目录 前言阅读对象阅读导航前置知识笔记正文一、单体服务介绍二、服务拆分三、分布式微服务升级前的思考3.1 关于SpringBoot/SpringCloud的思考【有点门槛】 四、SpringCloud升级整合4.1 新建父子项目 学习总结感谢 前言 从本节课开始&#xff0c;我将自己手写一个基于SpringC…

谷歌刚刚发布了Gemini 1.0,采用了OpenAI的GPT4

我的新书《Android App开发入门与实战》已于2020年8月由人民邮电出版社出版&#xff0c;欢迎购买。点击进入详情 对于谷歌和安卓来说&#xff0c;这是一个重要时刻。谷歌刚刚发布了 Gemini 1.0&#xff0c;这是其最新的LLM&#xff0c;它采用了 OpenAI 的 GPT4。 共有三种不同…

WPF仿网易云搭建笔记(0):项目搭建

文章目录 前言项目地址项目Nuget包搭建项目初始化项目架构App.xaml引入MateralDesign资源包 项目初步分析将标题栏去掉DockPanel初步布局 资源字典举例 结尾 前言 最近在找工作&#xff0c;发现没有任何的WPF可以拿的出手的工作经验&#xff0c;打算仿照网易云搭建一个WPF版本…

深度解析 Kafka 中的 Offset 管理与最佳实践

Kafka 中的 Offset&#xff08;偏移量&#xff09;是消息处理的关键元素&#xff0c;对于保证消息传递的可靠性和一致性至关重要。本篇博客将深度解析 Kafka 中的 Offset 管理机制&#xff0c;并提供丰富的示例代码&#xff0c;让你更全面地理解 Offset 的原理、使用方法以及最…

鸿蒙Harmony ArkUI十大开源项目

一 OH哔哩 https://gitee.com/ohos_port/ohbili 项目简介 【OH哔哩】是一款基于OpenHarmony系统ArkUI框架开发的哔哩哔哩动画第三方客户端 用到的三方库 bilibili-API-collect 哔哩哔哩-API收集整理ohos_ijkplayer 基于FFmpeg的视频播放器PullToRefresh 下拉刷新、上拉加载组件…

html通过CDN引入Vue组件抽出复用

html通过CDN引入Vue组件抽出复用 近期遇到个需求&#xff0c;就是需要在.net MVC的项目中&#xff0c;对已有的项目的首页进行优化&#xff0c;也就是写原生html和js。但是咱是一个写前端的&#xff0c;写html还可以&#xff0c;.net的话&#xff0c;开发也不方便&#xff0c;还…

CleanMyMac X4.15.0最新官方和谐版下载

Mac系统进行文件清理&#xff0c;一般是直接将文件拖动入“废纸篓”回收站中&#xff0c;然后通过清理回收站&#xff0c;就完成了一次文件清理的操作&#xff0c;但是这么做并无法保证文件被彻底删除了&#xff0c;有些文件通过一些安全恢复手段依旧是可以恢复的&#xff0c;那…

持续集成交付CICD: Sonarqube REST API 查找与新增项目

目录 一、实验 1.SonarQube REST API 查找项目 2.SonarQube REST API 新增项目 一、实验 1.SonarQube REST API 查找项目 &#xff08;1&#xff09;Postman测试 转换成cURL代码 &#xff08;2&#xff09;Jenkins添加凭证 &#xff08;3&#xff09;修改流水线 pipeline…

解决finalshell右键选择粘贴后出现直接执行的问题

文章目录 已经找到问题原因我的问题错误的解决 已经找到问题原因 复制的时候&#xff0c;只复制名字&#xff0c;不要复制后面多出来的东西&#xff0c;不然会自动加上回车换行 我的问题 我当时是想通过 ls -l 查出jdk的文件后&#xff0c; 复制文件名就不用看着敲了&#x…

李宏毅bert记录

一、自监督学习&#xff08;Self-supervised Learning&#xff09; 在监督学习中&#xff0c;模型的输入为x&#xff0c;若期望输出是y&#xff0c;则在训练的时候需要给模型的期望输出y以判断其误差——有输入和输出标签才能训练监督学习的模型。 自监督学习在没有标注的训练…

U-boot(八):官方uboot移植

本文主要探讨从ubboot官方移植uboot到x210。 基础 确定设备的配置文件 通过board.cfg中的cpu型号(s5pc1xx)确定设备的配置文件 头文件:include/configs/s5p_goni.h cpu: u-boot-2013.10\arch\arm\cpu\armv7 board: u-boot-2013.10\b…