指针的应用练习(数组与指针的关系)

news2024/11/17 9:25:54

如果对指针不是那么熟悉,我这里有几篇指针相关入门,不知道能不能帮助到你

http://t.csdn.cn/BbVwT

http://t.csdn.cn/eqBng

http://t.csdn.cn/hwNXp

看完后,检测一下这两段代码是否能透彻理解

(1)

#include<stdio.h>
int main()
{
int a=100;
int *p;
p=&a;
printf("%d",*p);
}

其输出为100

(2)

int a = 100;
int   *p1 = &a;  // 一级指针,指向普通变量
int  **p2 = &p1; // 二级指针,指向一级指针
int ***p3 = &p2; // 三级指针,指向二级指针
  • 如果一个指针变量 p1 存储的地址,是另一个普通变量 a 的地址,那么称 p1 为一级指针
  • 如果一个指针变量 p2 存储的地址,是指针变量 p1 的地址,那么称 p2 为二级指针
  • 如果一个指针变量 p3 存储的地址,是指针变量 p2 的地址,那么称 p3 为三级指针
  • 以此类推,p2、p3等指针被称为多级指针

一.数组与指针的表示

int a[3]; // 此处,a 代表整个数组

printf("%d\n", sizeof(a)); // 此处,a 代表整个数组

printf("%p\n", &a); // 此处,a 代表整个数组,此处为整个数组的地址

int *p = a; // 此处,a 代表首元素 a[0] 的地址,等价于 &a[0]

p = a + 1; // 此处,a 代表首元素 a[0] 的地址,等价于 &a[0]

function(a); // 此处,a 代表首元素 a[0] 的地址,等价于 &a[0]

scanf("%d\n", a); // 此处,a 代表首元素 a[0] 的地址,等价于 &a[0]

在大多情况下,指针都是指向数组的首元素,可以这么说,数组运算实际上就是指针运算。

补充:
字符串实质上是一个匿名数组,应用和数组很类似:

printf("%d\n", sizeof("abcd")); // 此处 "abcd" 代表整个数组
printf("%p\n", &"abcd");         // 此处 "abcd" 代表整个数组

printf("%c\n", "abcd"[1]);       // 此处 "abcd" 代表匿名数组的首元素地址
char *p1 = "abcd";                // 此处 "abcd" 代表匿名数组的首元素地址
char *p2 = "abcd" + 1;         // 此处 "abcd" 代表匿名数组的首元素地址

a[i] = 100;  等价于  *(a+i) = 100;

所以易得,a[i]=100,也与下面几项等价

 a[i] = 100;
*(a+i) = 100;
*(i+a) = 100;
  i[a] = 100; 

 例:

用三种等价的方法表示a[0][0]

&a[0][0]

//a[0]是首元素a[0][0]的地址

a[0]

//a[0]=*(a+0)=*(a)=*a

*a

二. 数组与指针语法

数组语法剖析

  • 任意的数组,不管有多复杂,其定义都由两部分组成。
    • 第1部分:说明元素的类型,可以是任意的类型(除了函数)
    • 第1部分:说明数组名和元素个数

int a[4];                     // 第2部分:a[4]; 第1部分:int

int b[3][4];                 // 第2部分:b[3]; 第1部分:int [4]

int c[2][3][4];             // 第2部分:c[2]; 第1部分:int [3][4]

int *d[6];                   // 第2部分:d[6]; 第1部分:int *

int (*e[7])(int, float); // 第2部分:e[7]; 第1部分:int (*)(int, float)

指针语法剖析

  • 任意的指针,不管有多复杂,其定义都由两部分组成。
    • 第1部分:指针所指向的数据类型,可以是任意的类型
    • 第2部分:指针的名字

char   (*p1);                   // 第2部分:*p1; 第1部分:char; 
char  *(*p2);                  // 第2部分:*p2; 第1部分:char *; 
char **(*p3);                 // 第2部分:*p3; 第1部分:char **; 
char   (*p4)[3];             // 第2部分:*p4; 第1部分:char [3]; 
char   (*p5)(int, float); // 第2部分:*p5; 第1部分:char (int, float); 

来一个较为复杂的

 int (*parr3[10])[5];//parr3是一个数组,该数组有10个元素,每个元素是一个数组指针,该数组指针指向的数组有5个元素,每个元素是int类型

分析一下

parr3[10]提出来,是一个数组,该数组有是个元素,剩下:
int(*)[5]:代表的是一个数组指针,数组指针指向的数组有5个元素,每个元素int型

 

例: 

  1. 定义一个整型数 i
  2. 定义一个指向整型数的指针 p
  3. 定义一个指向整型指针的指针 k
  4. 定义一个有 3 个整型数的数组 a
  5. 定义一个有 3 个整型指针的数组 b
  6. 定义一个指向有 3 个整型元素的数组的指针 q
  7. 定义一个指向函数的指针 r,该函数有一个整型参数并返回一个整型
  • 参考代码

1. int i;
2. int *p;
3. int **k;
4. int a[3];
5. int *b[3];
6. int (*q)[3];
7. int (*r)(int);

三.指针运算

指针p自增

#include <stdio.h>
int main(void)
{
    int a[] = {1, 2, 3, 4};
    int i, *p;
    for(i=0, p=a; i<4; i++, p++)
    {
        printf("%d %d\n", a[i], *p);
    }
    return 0;
}

注:这里p=a,表示让指针指向数组a的首元素1,再p++,使指针p不断自增

得到输出结果为

1 1
2 2
3 3
4 4

 *p和*(p+1) 

#include<stdio.h>
int main()
{
    int *p;
    int a[2][2] = {{1, 0}, {2, 3}};
    p= a[0];
    printf("%d, %d", *p, *(p+1));
    return 0; 
}

p指向数组的首元素a[0][0],所以*p=a[0][0]的值,*(p+1)=p[0][1]

所以输出为:1,0

重要知识点,先来看代码

将字符串中的小写字母转换为大写字母

#include <stdio.h>
#include <limits.h>
#include <ctype.h>

void upper_case(char str[])
{
    int step = 'a' - 'A';
    for(int i = 0; i<sizeof(str)/sizeof(str[0]); i++)
    {
        if(islower(str[i]))
            str[i] -= step;
    }
}
int main(void)
{
    char str[] = "abcdefghijklnmopqrstuvwxyz";
    printf("原数组:%s\n", str);
    upper_case(str);
    printf("转换后:%s\n", str);
}

这段代码有一个错误,在函数upper_case中,sizeof(str)/sizeof(str[0])的结果并不是数组长度,而是指针的大小。

这是因为数组在除了定义和sizeof语句之外,均会被视为指向其首元素的指针,因此在上述代码中, upper_case(str) 中的str是一个指针,而非数组,等价于: 

upper_case(&str[0]);

所以str永远都是指针,而非数组,所以sizeof(str)无法计算得数组大小

在这里,可以将数组长度作为额外的参数传递给函数upper_case,或者使用一个特殊的字符(如'\0')作为数组的结束标志,然后在循环中检查该标志来确定数组的长度。 

#include <stdio.h>
#include <limits.h>
#include <ctype.h>

void upper_case(char str[])
{
    int step = 'a' - 'A';
    for(int i = 0; str[i] != '\0'; i++)
    {
        if(islower(str[i]))
            str[i] -= step;
    }
}

int main(void)
{
    char str[] = "abcdefghijklnmopqrstuvwxyz";
    printf("原数组:%s\n", str);
    upper_case(str);
    printf("转换后:%s\n", str);
    return 0;
}

四.结构体中指针的应用

struct node
{
    /* 结构体的其他成员 */
    // 成员1
    // 成员2
    // ... ...
    
    int  len;
    char data[0];
};

// 给结构体额外分配 10 个字节的内存。
struct node *p = malloc(sizeof(struct node) + 10);
p->len = 10;

// 额外分配的内存可以通过 data 来使用
p->data[0] ~ p->data[9]

五.进阶练习

(1)编写一个程序,测试当前平台的字节序

#include <stdio.h>

int main(void)
{
    // 定义一个4字节的整型数据
    int a = 0x12345678;

    // 定义一个char型指针指向最低地址
    char *p = &a;

    // 将最低字节数据打印出来
    // 如果是0x78,那就代表最低地址存储了低有效位,是小端序
    // 如果是0x12,那就代表最低地址存储了高有效位,是大端序
    printf("%#x\n", *p);
}

(2)(数组下标运算、指针运算、内存操作)

注:这里的sizeof(long)是四个字节

#include <stdio.h>
int main(void)
{
    long  a[4] = {1, 2, 3, 4};
    long  *p1=(long *)(&a+1);
    long  *p2=(long *)((long)a+1);
 
    printf("%lx\n", p1[-1]);
    printf("%lx\n", *p2);
    
    return 0;
}

解析:

首先,&a是整个数组的地址,因此&a+1实际上是向后偏移了16个字节,因此 p1 指向了数组边界。(引用博主@赵日天他大哥的图片)

然后输出 p1[-1] 等价于 *(p1-1),因为 p1 是 long 型指针,因此 p1-1 就向前偏移 sizeof(long) 个字节,也就是指向了 a[3],即4。

其次,(long)a+1 是一个纯整数运算(因为a被强转为long了),因此 long型指针p2 就指向了long型数据 a[0] 的第二个字节

最后打印 *p2 时,由于 p2 是一个 long 型指针,系统会从 a[0] 的第二个字节开始,取出 sizeof(long) 个字节出来作为 long 型数据来解释,因此最后输出的结果是 a[0] 的高位三字节和 a[1] 低位一字节的数据

 a[0]的第二个字节开始,往后数四个字节,就是a[0] 的高位三字节和 a[1] 低位一字节的数据

(3)声明一个二维 int 型数组 a,再声明另一个一维数组指针数组 b,使该数组 b 的每一个指针分别指向二维数组 a 中的每一个元素(即每一个一维数组),然后利用数组 b 计算数组 a 的和。

注:“指针数组”指的是装了很多指针的数组,“数组指针”指的是指向数组的指针,“数组指针数组”指的是装了很多数组指针的数组。

#include <stdio.h>

int main(void)
{
	int a[2][3] = {{1, 2, 3}, {4, 5, 6}};
	int (*b[2])[3];
    1. *b[2]//数组b有两个元素,每个元素都是一个指针,与int (*b)[2]进行区别,这里是包含两个整形元素的指针
    2. int (*b[2])[3]//该数组是包含两个指针的数组,每个指针指向3个int类型元素   

	b[0] = &a[0];//第一个指针指向a[0]的地址
	b[1] = &a[1];//第二个指针指向a[1]的地址

	int i, j, sum=0;//初始化sum
	for(i=0; i<2; ++i)
	{
		for(j=0; j<3; ++j)
		{
			sum += (*b[i])[j];//作和
		}
	}

	printf("sum: %d\n", sum);
	return 0;
}

(4)编写一个程序,去掉给定字符串中重复的字符。

#include <stdio.h>
#include <string.h>

void strip(char *str)
{
    if(str == NULL)
        return;

    int i, j;
    char *p = str;

    // 将所有重复的字符标记为-1
    for(i=1; str[i] != '\0'; i++)
    {
        for(j=0; j<i; j++)
        {
            if(p[j] == str[i])
            {
                str[i] = -1;
                break;
            }
        }
    }

    // 找到第一个-1的位置 redun
    // 找到紧随 redun 之后的正常字符的位置 common
    int redun = 0, common = 0;
    for(i=1; str[i] != '\0'; i++)
    {
        if(str[i] != REDUNDANT)
            continue;

        redun = i;
        while(str[i] == REDUNDANT)
            i++;

        common = i;
        break;
    }

    // 若没有重复的字符,则返回
    if(redun == 0)
        return;

    // 将所有正常字符往前移
    while(str[common] != '\0')
    {
        if(str[common] == -1)
        {
            common++;
            continue;
        }
        str[redun++] = str[common];
        str[common++] = -1;
    }
    str[redun] = '\0';
}

int main(void)
{
    // 输入待处理字符串
    char buf[64];
    fgets(buf, 64, stdin);
    //对于fgets的应用可以看
//http://t.csdn.cn/mDMBb

    strip(buf);//删除buf字符串
    printf("%s", buf);
    
    return 0;
}

strip示例

整理写作不易,其中有错误的地方,请大佬们不吝赐教!!💓💓 

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

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

相关文章

<MyBatis>前台同一个参数传多个条件查询方式(传数组或者拼接字符串)

方式一&#xff1a;前台传参为数组&#xff0c;后台SQ查询案例&#xff1a; 一般为多选场景&#xff1a;查询&#xff1b; 举例如下&#xff1a; 传值&#xff1a;“status” : [“保存”,“关闭”], 不传值&#xff1a;“status”: [], 传给后台&#xff1a; 控制层&#xff1…

虚拟机中Linux的IP地址配置详解

目录 第一章、虚拟机中Linux的IP地址配置详解1.1&#xff09;什么是IP地址1.2&#xff09;如何查看自己电脑ip地址1.3&#xff09;虚拟机NAT模式中Linux的IP地址设置有什么要求 第二章、使用Linux中的编辑命令进行网卡信息文件的配置 友情提醒 先看文章目录&#xff0c;大致了…

c++静态代码扫描工具clang-tidy详细介绍

clang-tidy 文章目录 clang-tidy1. 什么是clang-tidy2. clang-tidy可以解决什么问题3. 工作原理4. 如何使用clang-tidy4. 总结5. 举例说明&#xff1a; 1. 什么是clang-tidy Clang-Tidy是一个由LLVM项目提供的开源工具&#xff0c;是一个静态分析工具&#xff0c;用于进行静态…

Emacs之point-undo代码步骤记忆前进/回退(一百二十二)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

Android 面试题 虚拟机、进程、线程 七

&#x1f525; 安卓虚拟机 &#x1f525; 虽然Android程序是使用Java语言开发的&#xff0c;当然&#xff0c;现在也可以使用kotlin语言。但是实际上我们开发出来的Android程序并不能运行在JVM上&#xff0c;而是只能运行在一个类似JVM的Android虚拟机上。Android虚拟机有两种&…

【数据结构】队列(Queue)的实现 -- 详解

一、队列的概念及结构 1、概念 队列&#xff1a;只允许在一端进行插入数据操作&#xff0c;在另一端进行删除数据操作的特殊线性表&#xff0c;队列具有先进先出 FIFO(First In First Out)。 入队列&#xff1a;进行插入操作的一端称为队尾。 出队列&#xff1a;进行删除操作的…

作为程序员,你很有必要了解一下IVX

一、IVX是什么 iVX 是一个“零代码”的可视化编程平台&#xff0c;拥有方便的在线集成开发环境&#xff0c;不需要下载开发环境&#xff0c;打开浏览器即可随时随地进行项目编辑。iVX 还拥有“一站式”的云资源&#xff0c;通过这一套一站式服务&#xff0c;iVX 可以实现一站式…

某渣渣平台APP登录

准备 APP有壳----360的好像是&#xff0c;懒得回头再看了加密参数sign、password 过程就略过吧&#xff01;此处只展示结果

用html+javascript打造公文一键排版系统8:主送机关排版

公文一般在标题和正文之间还有主送机关&#xff0c;相关规定为&#xff1a; 主送机关 编排于标题下空一行位置&#xff0c;居左顶格&#xff0c;回行时仍顶格&#xff0c;最后一个机关名称后标全角冒号。如主送机关名称过多导致公文首页不能显示正文时&#xff0c;应当将主送机…

【雕爷学编程】MicroPython动手做(15)——掌控板之AB按键3

知识点&#xff1a;什么是掌控板&#xff1f; 掌控板是一块普及STEAM创客教育、人工智能教育、机器人编程教育的开源智能硬件。它集成ESP-32高性能双核芯片&#xff0c;支持WiFi和蓝牙双模通信&#xff0c;可作为物联网节点&#xff0c;实现物联网应用。同时掌控板上集成了OLED…

IDEA将本地项目上传到码云

一、创建本地仓库并关联 用IDEA打开项目&#xff0c;在菜单栏点击vcs->create git repository创建本地仓库&#xff0c; 选择当前项目所在的文件夹当作仓库目录。 二、将项目提交本地仓库 项目名右键就会出现“GIT”这个选项->Add->Commit Directory, 先将项目add…

ORCA优化器浅析——QueryToDXL(CDXLLogical+CDXLScalar)主流程

Orca是Pivotal数据管理产品的新查询优化器&#xff0c;包括GPDB和HAWQ。Orca是一个基于Cascades操作时序框架的现代自上而下的查询优化器。虽然许多Cascades优化器与其主机系统紧密耦合&#xff0c;但Orca的一个独特功能是它能够作为独立的优化器在数据库系统之外运行。这种能力…

C++代码格式化工具clang-format详细介绍

文章目录 clang-format思考代码风格指南生成您的配置运行 clang-format禁用一段代码的格式设置clang-format的设置预览 clang-format 我曾在许多编程团队工作过&#xff0c;这些团队名义上都有“编程风格指南”。该指南经常被写下来并放置在开发人员很少查看的地方。几乎在每种…

ios 查看模拟器沙盒的路径

打一个断点运行程序&#xff0c;在xcode consol底部控制台输入&#xff1a; po NSHomeDirectory() 复制路径粘帖到前往文件夹打开沙盒缓存文件夹

Dig the way

前言 什么时候才能乱杀比赛的题啊,给了两个文件第一个是师傅使用ida反编译的数据库文件&#xff0c;有提示但不多&#xff0c;主要还是看程序吧 分析 程序从文件读取输入值&#xff0c;虽然结果和输入无关但是要用到输入时产生的一些触发条件&#xff0c;所以动态强行输出fl…

AI帮你制作海报

介绍 Microsoft Designer是由微软推出的图像处理软件&#xff0c;能够通过套用模板等方式快速完成设计加工&#xff0c;生成能够在社交媒体使用的图片。Designer的使用更为简单便捷&#xff0c;用户能够通过套用模板等方式快速完成设计加工&#xff0c;生成能够在社交媒体使用…

LLaMA模型论文《LLaMA: Open and Efficient Foundation Language Models》阅读笔记

文章目录 1. 简介2.方法2.1 预训练数据2.2 网络架构2.3 优化器2.4 高效的实现 3.论文其余部分4. 参考资料 1. 简介 LLaMA是meta在2023年2月开源的大模型&#xff0c;在这之后&#xff0c;很多开源模型都是基于LLaMA的&#xff0c;比如斯坦福大学的羊驼模型。 LLaMA的重点是比…

Vue如何实现编程式导航声明方法,前进和后退导航

编程式导航声明方法&#xff0c;前进和后退导航 在router中设置路由导航跳转函数 只要发生跳转 导航的声明函数 访问控制系统如何形成 就这三种 导航守卫的案例&#xff0c;写一个Main.Vue 和login .Vue 后台主页 如果想要展示后台主页&#xff0c;就用这种方法 想实现路由跳转…

linux查看服务器系统版本命令

有时我们需要在linux服务器上安装DB、Middleware等&#xff0c;为了保证兼容性&#xff0c;我们需要知晓被提供的linux服务器版本是否满足需求&#xff0c;下面就说一说linux查看服务器系统版本命令。 1.cat /etc/redhat-release 适用于&#xff1a;rhel/centos等 2.cat /etc…

基于minio的dababend部署总结

Databend 是一款开源、弹性、低成本&#xff0c;基于对象存储也可以做实时分析的新式数仓。期待您的关注&#xff0c;一起探索云原生数仓解决方案&#xff0c;打造新一代开源 Data Cloud。 Minio搭建 minio 192.168.10.159 cd /data mkdir minio cd minio wget https://dl…