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

指针与数组的异同

数组名:

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

指针:

一般是一个变量,存储一个内存地址。大小固定为指针类型的大小(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;
}

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值