指针与数组的异同
数组名:
是一个指针常量(数组名的值是数组首元素的指针常量),指向数组的首元素。大小固定为整个数组的大小。无法被改变或重新赋值(这里指数组名不能被赋值(指针常量不能指向其它地址),数组元素是可以被重新设置的)。无法进行指针运算(指自增、自减)。
指针:
一般是一个变量,存储一个内存地址。大小固定为指针类型的大小(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;
}
完结撒花!!!!!!!!