C指针汇总——藏在数组名与指针之间的异同及使用细节

news2024/11/15 22:56:50

指针与数组的异同

数组名:

是一个指针常量(数组名的值是数组首元素的指针常量),指向数组的首元素。大小固定为整个数组的大小。无法被改变或重新赋值(这里指数组名不能被赋值(指针常量不能指向其它地址),数组元素是可以被重新设置的)。无法进行指针运算(指自增、自减)。

指针:

一般是一个变量,存储一个内存地址。大小固定为指针类型的大小(32bit系统为4字节,64bit系统为8字节)。可以指向任意类型的对象。可以被改变或重新赋值。能够进行指针运算。

内存上的区别

对数组名使用sizeof将会得到整个数组所占的内存大小,假如arr是长度为10的int(4字节)的数组,那么所占内存为40字节。对数组名取地址即数组的地址。(数组的地址数组首元素的地址是不同的概念,尽管二者的值是相同的,arr 和 &arr 的值是相同的,arr 的值是数组首元素的地址,它并不是一个指针,要操作该地址上面的目标值需要指定索引项( arr[index] ),在对硬件资源编程时,如果要操作目标地址目标值还需要强制转换为指针(否则仅仅是个数字,计算机不认为它是个内存地址),这就是为什么外设寄存器地址,要操作它的目标值时,需要转换为指针再解引用的原因)

arr[2]       
*(arr + 2)

sizeof(arr) / sizeof(*arr)    //获得数组元素个数

arr的值被转换成指针常量,指向第一个元素,向右移动2 * sizeof(int)个字节,然后解引用,便得到了第3个元素的内容。(因为第一种写法会自动转换成第二种,这个过程需要一些开销,所以第二种写法通常效率会高一些。)

数组名arr 为int *类型,&arr的类型和二维数组名的类型的区别如下:

int arr[10] ---->  int *

 (数组的类型取决于数组元素的类型)

(如果数组元素是int类型,那么数组名的类型就是“指向int的指针常量”)

&arr的类型:

arr和&arr的值相同,但&arr是指向数组的指针,即类型是int (*)[10]。

(取一个数组名的地址所产生的是一个指向数组的指针,而不是一个指向某个指针常量值的指针)

二维数组名的类型讨论:
二维数组的类型也取决于数组元素的类型,假设有二维数组int arr_2[10][20],
C的多维数组本质是一维数组,即二维数组只是一个每个元素又是一个一维数组的一维数组。因此,可以得出如下推论:

arr_2的类型是int (*)[20],而&arr_2的类型是int (*)[10][20]

结论:  二维数组名 即 一个 列元素个数的数组指针 (最后一个维度为列)

二维数组很多时候又和二维指针挂钩,其区别如下:

( 指针和数组名的操作在不涉及指针运算时, 部分操作是等效的)

&arr;              //代表整个数组的地址,也为arr[0][0]的地址表示
arr[i];            //代表了第i行起始元素的地址
&arr[i];           //代表了第i行的地址,也为arr[i][0]的地址表示
arr[i]+j;          //代表了第i行第j个元素地址,arr[i]就是j==0的情况
&arr[i][j];        //代表了第i行第j个元素的地址
arr[i][j];         //代表了第i行第j个元素


arr;               //代表数组首行地址, 也为arr[0][0]的地址表示
*arr;              //代表数组arr首元素地址也就是arr[0]或者&arr[0][0]
*(arr+i);          //代表了第i行首元素的地址,*arr是i==0的情况
*(arr+i)+j;        //代表了第i行j个元素的地址
**arr;             //代表arr的首元素的值也就是arr[0][0]
*(*(arr+i)+j);     //代表了第i行第j个元素

指针数组

本质是数组,即指针的数组:是一个装着指针的数组。

根据符号的优先级顺序:() > [] > *

*p[n]:根据优先级,先看[],则p是一个数组,再结合*,这个数组的元素是指针类型,共n个元素,这是“指针的数组”,即指针数组。

#include "stdio.h"
 
 
int main()
{
	int a = 1;
	int b = 2;
	int *p[2];
	p[0] = &a;
	p[1] = &b;
 
	printf("%p\n", p[0]);     //a的地址
	printf("%p\n", &a);       //a的地址
	printf("%p\n", p[1]);     //b的地址
	printf("%p\n", &b);       //b的地址
	printf("%d\n", *p[0]);    //p[0]表示a的地址,则*p[0]表示a的值
	printf("%d\n", *p[1]);    //p[1]表示b的地址,则*p[1]表示b的值
 
 
	//将二维数组赋给指针数组
	int *pp[3]; //一个一维数组内存放着三个指针变量,分别是p[0]、p[1]、p[2],所以要分别赋值
	int c[3][4];

	for (int i = 0; i<3; i++)
		pp[i] = c[i];
 
 
    return 0;
}

数组指针

本质是指针,即数组的指针:是一个指向数组的指针

根据符号的优先级顺序:() > [] > *

(*p)[n]:根据优先级,先看括号内,则p是一个指针,这个指针指向一个一维数组,数组长度为n,这是“数组的指针”,即数组指针;

#include "stdio.h"
 
 
int main()
{

	int a[5] = { 1, 2, 3, 4, 5 };

	int (*p)[5];
	
    //把数组a的地址赋给p,则p为数组a的地址,则*p表示数组a本身
	p = &a;
 
	//%p输出地址, %d输出十进制
	//在C中,在几乎所有使用数组的表达式中,数组名的值是个指针常量,也就是数组第一个元素的地址。
	printf("%p\n", a);     //输出数组名,一般用数组的首元素地址来标识一个数组,则输出数组首元素地址
	printf("%p\n", p);     //根据上面,p为数组a的地址,输出数组a的地址
	printf("%p\n", *p);    //*p表示数组a本身,一般用数组的首元素地址来标识一个数组
	printf("%p\n", &a[0]); //a[0]的地址
	printf("%p\n", &a[1]); //a[1]的地址
	printf("%p\n", p[0]);  //数组首元素的地址
	printf("%d\n", **p);   //*p为数组a本身,即为数组a首元素地址,则*(*p)为值,当*p为数组首元素地址时,**p表示首元素的值1
	printf("%d\n", *p[0]); //根据优先级,p[0] 表示首元素地址,则*p[0]表示首元素本身,即首元素的值1
	printf("%d\n", *p[1]); //为一个绝对值很大的负数,不表示a[1]...表示什么我还不知道
 
	
 
	//将二维数组赋给指针
	int b[3][4];
	int(*pp)[4]; //定义一个数组指针,指向含4个元素的一维数组

	pp = b; //将该二维数组的首地址赋给pp,也就是b[0]或&b[0],二维数组中pp=b和pp=&b[0]是等价的
	
    pp++;   //pp=pp+1,该语句执行过后pp的指向从行b[0][]变为了行b[1][],pp=&b[1]
 
    return 0;
}

总结:

数组指针是一个指针变量,占有内存中一个指针的存储空间;

指针数组是多个指针变量,以数组的形式存储在内存中,占有多个指针的存储空间。

注意

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

/*下面是错误的*/
int (*p2)[5] = arr;

一维指针和二维指针的内存操作

动态开辟一个二维数组

指针数组开辟

// 释放较为麻烦,并且无法保证一行最后一个元素与下一行第一个元素是连续存储的

int** p = (int**)malloc(sizeof(int*) * 4);
for (int i = 0; i < 4; i++)
{
	p[i] = (int*)malloc(sizeof(int)*4);
}
for (int i = 0; i < 4; i++)
{
	for (int j = 0; j < 4; j++)
	{
		p[i][j] = j;
	}
}
printf("指针数组开辟二维数组\n");
for (int i = 0; i < 4; i++)
{
	for (int j = 0; j < 4; j++)
	{
		printf("%d ",p[i][j]);
		if (j == 3)
			printf("\n");
	}
}
//先释放每行的元素
for(int i=0; i<4; i++)
{
	free(p[i]);
}
//最后释放二级指针
free(p);

用指针数组开辟二维数组,无法保证一行最后一个元素与下一行第一个元素是连续存储的,释放也麻烦。

数组指针开辟

//但列数需固定,无法自定义列数
const int ROW = 4;
const int COL = 4;//数组指针列数可以用常量定义
printf("数组指针开辟二维数组,打印地址\n");
int(*pp)[COL] = (int(*)[COL])malloc(ROW * COL * sizeof(int));
for(int i=0; i<4; i++)
{
	for(int j=0; j<4; j++)
	{
		printf("%p\n", &pp[i][j]);
    }
}
free(pp);

解决了指针数组开辟释放难、行最后与下一行最前元素不连续的问题

暴力一维模拟

//一维数组当作二维数组来管理
//可使用变量来作为行数和列数,释放简单,但脑海里需要绕弯子易出错,赋值与打印十分麻烦
 
int* ppp = (int*)malloc(ROW*COL*sizeof(int));
//赋值
for(int i=0; i<ROW ;i++)
	for (int j = 0; j < COL; j++)
	{
		ppp[i * ROW + j] = i * ROW + j;
	}

//打印
printf("一维数组模拟二维数组\n");
for  (int i= 0; i < ROW; i++)
	for (int j = 0; j < COL; j++)
	{
		printf("%-4d ", ppp[i * ROW + j]);
		if (j == COL - 1)
			printf("\n");
	}
free(ppp);

动态开辟一个一维数组

#include<stdio.h>
int main()
{
	int input = 0;
	scanf("%d", &input);
	int* p = (int*)malloc(sizeof(int) * input);
	
	for (int i = 0; i < input; i++)
	{
		scanf("%d", &p[i]);
	}
	free(p);
	p = NULL;
	
	return 0;
}

注意:以静态的方式开辟一维数组时,当要以输入控制自定义元素数量时,可能会存在问题。

#include<stdio.h>
int main()
{
	int input = 0;
	scanf("%d", &input);
	int arr[input] = { 0 };
	//因为数组中必须为常量表达式
	//所以此时无法成功创建数组
	return 0;
}

数组和指针存在内存的空间分布

数组和指针变量,作为函数体里的静态定义的为局部变量存储在内存的栈区

数组和指针变量,作为函数体里的动态定义的存储在内存的堆区

数组和指针变量,定义在函数体之外静态定义的为全局变量,可以通过增加修饰词static将其转换为静态变量

对于使用了修饰词const的常量元素数组指针常量,其元素变量或指针变量会成为常量,但其左值并不存储在符号表(常量表),依旧根据位置,函数体内或外,是否动态开辟有关。(但是,字符串的话则有一个特例,由于写法不同,右值存储在文字常量区(.rodata段))

int main(){

    char arr[] = "123456";   // 栈
    char *p = "123456";      // 123456\0在.rodata段 ; p3 在栈上
                             // p是一个指针,指向的是一个常量,存储在常量区,不可以修改
    return 0;
}

完结撒花!!!!!!!!

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

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

相关文章

企业CAD图纸加密软件推荐!2024年好用的10款CAD图纸加密软件排行

在现代企业中&#xff0c;CAD图纸作为重要的设计和工程数据&#xff0c;其安全性和保密性至关重要。为了防止图纸被非法获取、篡改或滥用&#xff0c;选择一款高效的CAD图纸加密软件显得尤为重要。本文将为您推荐2024年市场上十款好用的CAD图纸加密软件&#xff0c;帮助企业保护…

2024最新50道NLP和人工智能领域面试题+答案(中文+英文双版本)

编者按&#xff1a;分享一个很硬核的免费人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c; 可以当故事来看&#xff0c;轻松学习。 中文版本 自然语言处理 (NLP)已成为语言学、人工智能和计算机科学交叉领域的变革性领域。随着文本数据量的不断增加&…

图形编辑器基于Paper.js教程15:在Paper.js中实现拖拽图片导入画布功能

在现代Web开发中&#xff0c;用户体验是至关重要的。而拖拽文件上传的功能&#xff0c;不仅直观易用&#xff0c;还提升了用户与界面的交互体验。在这篇文章中&#xff0c;我们将探讨如何使用Paper.js和HTML5的拖放API&#xff0c;来实现将图片文件直接拖拽并导入到Paper.js的画…

如何让图片清晰度变高?介绍三种转变方案

如何让图片清晰度变高&#xff1f;在数字化时代&#xff0c;图片的质量直接影响着信息传递的效果。但由于拍摄条件、传输方式或存储时间的限制&#xff0c;我们时常会遇到图片清晰度不足的问题。还好随着技术的进步&#xff0c;现在有多种方法可以有效提升图片的清晰度。下文将…

arm 的模式+异常(7)

1 Byte , 8 bit, HarlfWord , 16bit , word ,32bit. 2 在内存中每一字节都有一个地址与它对应。 3 操作系统的8种模式 用户模式&#xff1a; 不能访问硬件资源&#xff0c;但是可以通过系统调用来访问。 疑问&#xff1a; 那不还是可以访问硬件资源吗&#xff1f; 系统模…

Zookeeper集群如何实现强一致性和高可用,集群数据同步过程

在分布式系统中&#xff0c;通常会面临如下问题&#xff1a; 分布式协调&#xff1a;在分布式环境下&#xff0c;多个节点需要协同工作&#xff0c;确保多个服务之间数据的一致性以及系统的可靠性。 分布式锁&#xff1a;在分布式系统中&#xff0c;多个节点可能需要同时访问共…

ppt模板软件哪个好?这5个工具提供各种类型的PPT模板

在筹备一场关于中元节的精彩演讲时&#xff0c;你是否曾陷入过寻找完美PPT模板的困境&#xff1f;那些千篇一律的设计是否让你觉得乏味&#xff0c;渴望一份创意与美感并存的视觉呈现&#xff1f; 别担心&#xff0c;今天&#xff0c;我将为你揭晓几款宝藏级软件&#xff0c;它…

如何为 Nextcloud 配置自动数据库备份 - 应用程序

自动数据库备份模块简化了生成数据库计划备份的过程。这些备份可以存储在各种位置&#xff0c;包括本地驱动器、FTP 服务器、SFTP 服务器、Dropbox、Google Drive、OneDrive、NextCloud 和 Amazon S3 云存储。用户还可以选择启用自动删除过期备份的功能。此外&#xff0c;用户可…

大模型越狱攻击成功率 (ASR) 评判方法

JailbreakBench (24.04) • Rule-based. The rule-based judge from Zou et al. (2023) based on string matching, • GPT-4. The GPT-4-0613 model used as a judge (OpenAI, 2023), • HarmBench. The Llama-2-13B judge introduced in HarmBench (Mazeika et al., 2024), …

Xinstall助力App推广:落地页跳转,轻松提升转化率

在移动互联网时代&#xff0c;App的推广与运营成为了各行各业的关键一环。然而&#xff0c;许多推广者在落地页跳转App这一环节上遇到了不小的挑战。用户点击落地页后&#xff0c;往往需要经过繁琐的步骤才能跳转到App&#xff0c;这不仅降低了用户体验&#xff0c;还严重影响了…

gaussian-splatting环境配置

本文总结了在windows11下配置gaussian-splatting训练环境的步骤&#xff0c;主要包括gaussian-splatting库的下载、python环境配置、cudatoolkit和pytorch安装、diff-gaussian-rasterization/Simple-knn/plyfile/tdqm库安装&#xff0c;接着利用官方提供的已做好SFM的数据训练模…

vue中点击导航栏,动态改变样式,经典写法

vue中点击导航栏&#xff0c;动态改变样式&#xff0c;经典写法 在vue中&#xff0c;我们通常会有这样的情况&#xff0c;在多个子模块之间&#xff0c;点击其中一个子模块&#xff0c;修改当前点击的子模块的样式。如图&#xff0c;点击B模块时&#xff0c;模块B样式改变&…

【LeetCode】433.最小基因变化

1. 题目 2. 思想 这题的思想很经典&#xff0c;使用bfs求最短路径。相似的题目还有这道题。 把每次合理的变换都记录在队列中&#xff0c;然后先进先出&#xff0c;同时记录出执行的次数&#xff0c;得到最后的结果。同时需要把历史上曾经入队的基因串都放到字典里&#xff0…

IAM 编程访问和 AWS CLI

添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; IAM 编程访问&#xff08;欢迎来到雲闪世界。&#xff09; IAM 编程访问是指使用访问密钥通过 API 和命令行工具访问 AWS 服务和资源。 当您为 IAM 用户启用编程访问时&#xff0c;您将生成可用于验证和…

萝卜快跑和端到端的自动驾驶(1)

先看一篇论文 2311.18636 (arxiv.org) 这篇论文里有一个非常好的图 比较了一下模块化任务(级联任务)和端到端自动驾驶的区别 首先什么叫模块化任务(级联) 如上图所示&#xff0c;左边的方块中的子方块&#xff0c;是展示了自动驾驶获取数据的途径&#xff0c;这里包括&…

浅析国有商业银行人力资源数字化平台建设

近年来&#xff0c;在复杂的国际经济金融环境下&#xff0c;中国金融市场整体运行保持稳定。然而&#xff0c;随着国内金融机构改革的不断深化&#xff0c;国有商业银行全面完成股改上市&#xff0c;金融市场规模逐步扩大&#xff0c;体系日益完善&#xff0c;同时行业的竞争也…

五分钟搭建进销存库存表,轻松掌握库存动态!

在企业的日常运营中&#xff0c;库存管理如同心脏之于身体&#xff0c;其重要性不言而喻。一个高效、准确的库存统计看板&#xff0c;就像是企业的“库存晴雨表”&#xff0c;能够实时反映库存状况&#xff0c;助力企业精准决策&#xff0c;确保供应链畅通无阻。今天就来手把手…

qt-15综合实例(电子时钟)-多态重写鼠标单击和移动事件

综合实例-电子时钟 知识点digiclock.hdigiclock.cppmain.cpp运行图 知识点 setWindowOpacity(0.5);//设置窗体透明度 QTimer* Timer new QTimer(this);//新建一个定时器 connect(Timer,SIGNAL(timeout()),this,SLOT(ShowTime())); Timer->start(1000);//启动定时器 digic…

奇迹世界2单机版安装教程+GM工具+无虚拟机

今天给大家带来一款单机游戏的架设&#xff1a;奇迹世界2单机版。 另外&#xff1a;本人承接各种游戏架设&#xff08;单机联网&#xff09; 本人为了学习和研究软件内含的设计思想和原理&#xff0c;带了架设教程仅供娱乐。 教程是本人亲自搭建成功的&#xff0c;绝对是完整…

SAP与航信爱信诺电子发票系统集成案例

一、项目环境 某药业有限公司是一家以医药产业为主营、资本经营为平台的大型民营企业集团。公司成立迄今&#xff0c;企业经营一直呈现稳健、快速发展的态势集团总销售额超40亿元。 为融入企业内部的ERP、CRM等系统&#xff0c;支持自动化记账、对账流程&#xff0c;推动企…