大家好,这里是我时隔许久又一良心巨著,主要是介绍了指针和数组的一些深入的理解,特意整理出来一篇博客供我们一起学习,如果文章中有理解不当的地方,还希望朋友们在评论区指出,我们相互学习,共同进步!
一:指针
1.1什么是指针
重新理解变量:定义一个变量,本质是在内存中根据类型来开辟空间。有了空间,就必须具有地址来标识空间,来方便CPU进行寻址。有了空间,就可以把数据保存起来。
指针就是地址!那么地址的本质是什么呢?地址是数据,数据可以被保存在变量空间中。
1.2为什么要有指针
一句话,为了方便CPU寻址的效率。
1.3指针解引用
int main()
{
int a = 10;
int *p = &a;
*p = 20;
system("pause");
return 0;
}
*p的完整理解是,取出p的地址,访问该地址指向的内存单元(空间或内容)(其实通过指针变量访问,本质是一种间接寻址的方式) 口诀:对指针解引用,就是指针指向的目标
二:数组
2.1概念
数组是具有相同类型的集合
#define N 10
int main()
{
int a[N] = { 0 };
system("pause");
return 0;
}
我们都知道临时变量是在栈区上开辟空间,且地址由高到低,那么数组是如何开辟空间的呢?
int main()
{
int a[10] = { 0 };
for (int i = 0; i < 10; i++)
{
printf("&a[%d]:%p\n", i, &a[i]);
}
system("pause");
return 0;
}
&a[0]:003CFAB8
&a[1]:003CFABC
&a[2]:003CFAC0
&a[3]:003CFAC4
&a[4]:003CFAC8
&a[5]:003CFACC
&a[6]:003CFAD0
&a[7]:003CFAD4
&a[8]:003CFAD8
&a[9]:003CFADC
请按任意键继续. . .
有代码结果我们可以看出,数组是整体申请空间的,然后将地址最低的空间,作a【0】元素,以此类推。
引用自比特课件
2.2理解指针+1
int main()
{
char * c = NULL;
short * s = NULL;
int * i = NULL;
double * d = NULL;
printf("%d\n", c);
printf("%d\n", c + 1);
printf("%d\n", s);
printf("%d\n", s + 1);
printf("%d\n", i);
printf("%d\n", i + 1);
printf("%d\n", d);
printf("%d\n", d + 1);
system("pause");
return 0;
}
口诀:对指针+1,本质加上其所指类型大小
2.3数组名a作为左值和右值的区别
数组名可以做右值,代表数组首元素的地址。
int main()
{
int a[10] = { 0 };
char *p = a;
system("pause");
return 0;
}
数组名不可以做左值!能够当左值的,必须是有空间可以被修改的,数组名不可以整体使用,只能按照元素为单位使用。
三:指针和数组的关系
没关系
3.1以指针的形式和以数组的形式访问
int main()
{
char * str = "hello world";
char arr[] = "hello world";
int len = strlen(str);
for (int i = 0; i < len; i++)
{
printf("%c\t", *(str + i));
printf("%c\n", str[i]);
}
printf("\n");
for (int i = 0; i < len; i++)
{
printf("%c\t", *(arr + i));
printf("%c\n", arr[i]);
}
system("pause");
return 0;
}
str指针变量在栈上保存,“hello world”在字符常量区,不可修改。 arr整个数组在栈上保存,可以被修改。
代码中,虽然*(str + i)与*(arr + i)的写法一样,但是寻址方法是完全不一样的。
指针和数组指向或表示一块空间的时候,访问方式是可以互通的,既有相似性。
那么C语言为什么要这么设计?
说白了就是方便程序员编程,如果没有将指针和数组元素访问打通,那么在C语言中(面向过程)如果有大量的函数调用和大量数组传参,会要求程序员进行各种各种访问习惯的变化,只要是要求人做的,就会提升代码出错的概率和调试i的难度。
四:指针数组和数组指针
4.1数组名与&数组名的区别
int main()
{
char arr[5] = { 'a', 'b', 'c', 'd', 'e' };
char(*p1)[3] = &arr;
char(*p2)[3]= arr;
system("pause");
return 0;
}
结论:数组元素个数,也是数值指针类型的一部分。
4.2地址的强制转换
struct test
{
int NUM;
char * pcName;
char ch[2];
}*p = (struct test *)0x100000;
int main()
{
printf("%p\n", p + 0x1);
printf("%p\n", (unsigned long)p + 0x1);
printf("%p\n", (unsigned int *)p + 0x1);
system("pause");
return 0;
}
0010000C
00100001
00100004
请按任意键继续. . .
结论:强制类型转换,改变的是对特定内容的看待方式,对数据本身不发生改变。
五:多维数组和多级指针
5.1 二维数组基本内存布局
int main()
{
char arr[3][4] = { 0 };
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 4; j++)
{
printf("arr[%d][%d] : %p\n", i, j, &arr[i][j]);
}
}
system("pause");
return 0;
}
arr[0][0] : 012FFAFC
arr[0][1] : 012FFAFD
arr[0][2] : 012FFAFE
arr[0][3] : 012FFAFF
arr[1][0] : 012FFB00
arr[1][1] : 012FFB01
arr[1][2] : 012FFB02
arr[1][3] : 012FFB03
arr[2][0] : 012FFB04
arr[2][1] : 012FFB05
arr[2][2] : 012FFB06
arr[2][3] : 012FFB07
请按任意键继续.
结论:二维数组在内存地址空间排布上,也是连续递且递增的。
5.2二维数组如何画图
只有正确画出二维数组的布局图,才能算真正深刻理解二维数组的空间布局。 以 char【3】【4】 = { 0 };为例
理解链:数组的定义是既有相同元素类型的集合,特征是数组中可以保存任意类型。那么可以保存数组吗,答案是可以的! 在理解上,我们甚至可以认为所有的数组都可以当作一维数组,就二维数组而言,可以被当作“一维数组”, 只不过内部“元素”也是一维数组。
那么内部一维数组是在内存中布局是“线性连续且递增”的,多个该一维数组构成另一个“一维数组”,那么整体也是线性且连续递增的。这也就解释了为何上述二维数组的地址是连续递增的。
如果愿意的话,我们也可采用如下代码遍历二维数组:
int main()
{
char arr[3][4] = { 0 };
char *p = (char *)arr;
for (int i = 0; i < 3 * 4; i++)
{
printf("%p\n", p + i);
}
system("pause");
return 0;
}
00FDFCCC
00FDFCCD
00FDFCCE
00FDFCCF
00FDFCD0
00FDFCD1
00FDFCD2
00FDFCD3
00FDFCD4
00FDFCD5
00FDFCD6
00FDFCD7
请按任意键继续. . .
下面我给出一组题目,看朋友们能否明白答案的由来:
int main()
{
int arr[3][4] = { 0 };
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr[0][0]));
printf("%d\n", sizeof(arr[0]));
printf("%d\n", sizeof(arr[0] + 1));
printf("%d\n", sizeof(*(arr[0] + 1)));
printf("%d\n", sizeof(arr + 1));
printf("%d\n", sizeof(*(arr + 1)));
printf("%d\n", sizeof(&arr[0] + 1));
printf("%d\n", sizeof(*(&arr[0] + 1)));
printf("%d\n", sizeof(*arr));
system("pause");
return 0;
}
48
4
16
4
4
4
16
4
16
16
请按任意键继续. . .
欢迎评论区讨论
5.3二级指针
同样的,二级指针是变量,变量有地址,地址是数据,数据可以被保存。
int main()
{
int a = 10;
int * p = &a;
int *pp = &p;
p = 100;
*p = 100;
pp = 100;
*pp = 100;
** pp = 100;
return 0;
}
六:数组参数和指针参数
6.1一维数组传参
数组传参是要发生降维的。
void show(int * arr)
{
printf("main: %d\n", sizeof(arr));
}
int main()
{
int arr[10];
printf("main: %d\n", sizeof(arr));
show(arr);
system("pause");
return 0;
}
main: 40
main: 4
请按任意键继续. . .
为什么要降维?
在C语言中,只要函数调用,必定发生拷贝,形参是实参的一份临时拷贝,所以如果不发生降维,那么会导致成本高,效率低。
降维成什么?
降维成指向其内部元素的指针
6.2一级指针传参
发生函数调用,指针作为参数,要不要发生临时拷贝?
void test(char * p)
{
printf("test: &p = %p\n", &p);
}
int main()
{
char * p = "hello world";
printf("main: &p = %p\n", &p);
test(p);
system("pause");
return 0;
}
main: &p = 012FFB50
test: &p = 012FFA7C
请按任意键继续. . .
需要! 因为指针变量,也是变量,在传参上,它也必须符合变量的要求,进行临时拷贝.所以上述代码形参与实参地址不一样!
6.3二维数组参数和二级指针参数
二维数组传参也要发生降维 降维成什么?
指向其内部元素的指针
void show(char arr[][4])
{
printf("hello world!");
}
int main()
{
char arr[3][4] = { 0 };
printf("main: %d\n", sizeof(arr));
show(arr);
system("pause");
return 0;
}
数组名arr代表首元素地址,此时首元素是一个一维数组,其地址是一个数组指针,须知所有的数组传参,都会发生降维,这里则是降维成一个一级数组指针来传参
思考为什么在形参接收是行标可以省略,而列标不可以呢?
这里就涉及到前面所说的数组指针的类型与元素个数有关,如上述代码在形参列表也可表示成降维成: void show(char (*arr)[4]) 所以列标不可以省
综上:任何维度的数组,在传参是时候都要发生降维,降维成指向其内部元素类型的指针. 那么二维数组内部元素是"一维数组",就应该降维成指向一维数组的指针. 同理三维数组,四维数组一样的道理!
谢谢大家的支持,还希望看到这里的能够给一个三连支持,谢谢大家!
|