指针(进阶)

指针进阶:

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

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是首元素地址,二维数组的首元素是第一行的一维数组。对二维数组解引用,求大小是第一行的大小。 

 习题九:

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

线上项目修改最后一招 修改jar中的文件并重新打包成jar

解压jar包 在要操作的jar文件上边cmd打开命令提示符窗口(windows系统), 在cmd命令下执行 jar -xvf xxx.jar 解压jar包(其中xxx.jar换成你的jar包名) jar -xvf admin-1.0.0.jar 替换或者更改操作 如果要替换jar压缩…

qt 5.15.2 主窗体菜单工具栏树控件功能

qt 5.15.2 主窗体菜单工具栏树控件功能 显示主窗体效果&#xff1a; mainwindow.h文件内容&#xff1a; #ifndef MAINWINDOW_H #define MAINWINDOW_H#include <QMainWindow> #include <QFileDialog> #include <QString> #include <QMessageBox>#inc…

浅谈web性能测试

什么是性能测试&#xff1f; web性能应该注意些什么&#xff1f; 性能测试&#xff0c;简而言之就是模仿用户对一个系统进行大批量的操作&#xff0c;得出系统各项性能指标和性能瓶颈&#xff0c;并从中发现存在的问题&#xff0c;通过多方协助调优的过程。而web端的性能测试…

ChatGPT能帮助--掌握各种AI绘图工具,随意生成各类型性图像

2023年随着OpenAI开发者大会的召开&#xff0c;最重磅更新当属GPTs&#xff0c;多模态API&#xff0c;未来自定义专属的GPT。微软创始人比尔盖茨称ChatGPT的出现有着重大历史意义&#xff0c;不亚于互联网和个人电脑的问世。360创始人周鸿祎认为未来各行各业如果不能搭上这班车…

〖大前端 - 基础入门三大核心之JS篇㊼〗- BOM基础之window对象

说明&#xff1a;该文属于 大前端全栈架构白宝书专栏&#xff0c;目前阶段免费&#xff0c;如需要项目实战或者是体系化资源&#xff0c;文末名片加V&#xff01;作者&#xff1a;不渴望力量的哈士奇(哈哥)&#xff0c;十余年工作经验, 从事过全栈研发、产品经理等工作&#xf…

Caché/M 数据库系统 InterSystems IRIS 的 Windows 安装

针对 InterSystems IRIS 数据库的一些基本概念。 InterSystems IRIS 是什么 InterSystems IRIS 是基于 Cach/M 语言开发的一个数据库&#xff0c;这个数据库被大量使用在医疗系统中&#xff0c;也是北美地区医疗系统病历和文件管理中默认使用的事实标准。 Cach/M 是什么 Ca…

亚马逊云科技Serverless视频内容摘要提取方案

概述 随着GenAI的普及&#xff0c;视频内容摘要生成成为一个备受关注的领域。通过将视频内容转化为文本&#xff0c;可以探索到更广泛的应用场景&#xff0c;其中包括&#xff1a; 视频搜索与索引&#xff1a;将视频内容转化为文本形式&#xff0c;可以方便地进行搜索和索引操作…

Zabbix自定义飞书webhook告警媒介2

说明:适用于7.0及以上版本,低版本可能会有问题。 参数如下: 名称 值EVENT.DURATION{EVENT.DURATION}EVENTDATE

AWS re:Invent 2023-亚马逊云科技全球年度技术盛会

一:会议地址 2023 re:Invent 全球大会主题演讲 - 亚马逊云科技从基础设施和人工智能/机器学习创新,到云计算领域的最新趋势与突破,倾听亚马逊云科技领导者谈论他们最关心的方面。https://webinar.amazoncloud.cn/reInvent2023/keynotes.html北京时间2023年12月1日00:30-02:…

Java架构师系统架构设计原则应用

目录 1 导语2 如何设计高并发系统:局部并发原则3 如何设计高并发系统:服务化与拆分4 高可用系统有哪些设计原则?5 如何保持简单轻量的架构-DRY、KISS,YAGNI原则6 如何设计组件间的交互和行为-HCLC,CQS,SOC7 框架层面的发展趋势-约定大于配置想学习架构师构建流程请跳转:…

一v一聊天

服务端 package 一对一用户;import java.awt.BorderLayout; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.Vector;…

mysql5.7安装详细教程

文章目录 1 引言1.1 现有的数据存储方式有哪些&#xff1f;1.2 以上存储方式存在哪些缺点&#xff1f; 2 数据库2.1 概念2.2 数据库的分类 3 数据库管理系统3.1 概念3.2 常见数据库管理系统 4 MySQL4.1 简介4.2 访问与下载4.3 安装4.3.1 解压缩到非中文目录4.3.2 编写配置文件4…

华为数通---配置端口安全案例

端口安全简介 端口安全&#xff08;Port Security&#xff09;通过将接口学习到的动态MAC地址转换为安全MAC地址&#xff08;包括安全动态MAC、安全静态MAC和Sticky MAC&#xff09;&#xff0c;阻止非法用户通过本接口和交换机通信&#xff0c;从而增强设备的安全性。 组网需…

GitHub工业级开源软件:基于网络的过程可视化(SCADA/HMI/仪表板)

GitHub工业级开源软件:基于网络的过程可视化(SCADA/HMI/仪表板)  作者:本站编辑  2023-11-25 06:52:35  117 大家好,我是 Fun-Fun君,每天介绍github上最有价值的开源项目 今天介绍 FUXA 基于网络的过程可视化(SCADA/HMI/仪表板)软件 github地址:…

unity 2d 入门 飞翔小鸟 飞翔动作动画描点(六)

1、创建文件夹新建动画控制器 右键->create->Animator controer 给图层创建动画 把创建后的动画控制器拖拽至对应图层 点击window->Animation->Animation 点击创建文件夹 把图片拖拽到这里面&#xff0c;就可以了 然后我们就看在player看到已经把动作挂上去 点…

【Openstack Train】十六、swift安装

OpenStack Swift是一个分布式对象存储系统&#xff0c;它可以为大规模的数据存储提供高可用性、可扩展性和数据安全性。Swift是OpenStack的一个核心组件&#xff0c;它允许用户将大量的数据存储在云上&#xff0c;并且可以随时访问、检索和管理这些数据。 Swift的设计目标是为了…

外置固态硬盘配置

1、插上usb外置硬盘盒 2、邮件我的此“电脑”选择“管理” 3、例如新增的固态硬盘如下&#xff1a; 4、这里我选择mrb(旧模式)而没选guid(新模式) 因为mrb兼容模式更加适合windows、ios等系统 5、右击未分区磁盘&#xff0c;选择新增卷区&#xff0c;一路下一步即可

华为数通---BFD多跳检测示例

定义 双向转发检测BFD&#xff08;Bidirectional Forwarding Detection&#xff09;是一种全网统一的检测机制&#xff0c;用于快速检测、监控网络中链路或者IP路由的转发连通状况。 目的 为了减小设备故障对业务的影响&#xff0c;提高网络的可靠性&#xff0c;网络设备需要…

【Linux 进度条小程序】缓冲区+回车换行

文章目录 回车与换行缓冲区举个栗子fflush函数倒计时小程序进度条小程序 回车与换行 回车和换行是不同的两个概念 回车&#xff1a;\r 使光标回到本行行首。 换行&#xff1a;\n使光标下移一格。 一般我们的键盘上的Enter键是回加换行键 在c语言中 \n 表示回车换行 效果和Ent…

IDEA Maven项目如何引用本地jar包,并打包发布

jar包位于当前路径下的lib目录中 引入所需要的配置 查看当前jar包的相关信息 包的引入,需要使用到当前包的artifactId, groupId, version 需要到包的/META-INF/maven/ 下面的 pom.xml 文件里面找 在Maven构建项目时&#xff0c;生成的依赖包中的/META-INF/maven目录存放了一些…