指针和数组的关系
- 指针指的是指针变量,不是数组,指针变量的大小是4/8个字节,是专门来存放地址的。
- 数组也不是指针,数组是一块连续的空间,存放一组相同类型的数据的。
没有关系,但是它们之间有比较相似的地方
以指针的形式访问和以数组的形式访问
#include <stdio.h> #include <string.h> int main() { //这是指针 char* str = "abcdef"; //str指针变量在栈上保存,“abcdef”在字符常量区,不可被修改 //这是数组 char arr[] = "abcdef"; //整个数组都在栈上保存,可以被修改 //访问指针 //1. 以指针的形式访问指针和以下标的形式访问指针 printf("以指针的形式访问指针和以下标的形式访问指针\n"); int len = strlen(str); for (int i = 0; i < len; i++) { printf("%c\t", *(str + i)); printf("%c \n", str[i]); } printf("\n"); //访问数组 //2. 以指针的形式访问数组和以下标的形式访问数组 printf("以指针的形式访问数组和以下标的形式访问数组\n"); len = strlen(arr); for (int i = 0; i < len; i++) { printf("%c\t", *(arr + i));//数组名在大部分表达式中,代表的是首元素的地址 printf("%c \n", arr[i]); } printf("\n"); return 0; }
结论:指针和数组指向或者表示一块空间的时候,访问方式是可以互通的,具有相似性。但是具有相似性,不代表是一个东西或者具有相关性。
C为何要这样设计?
//我们初步阅读一下下面代码 #include <stdio.h> #include <string.h> //这里两种写法都行,大家都知道,函数传参是要发生降维的(为什么?) //降维成指针。 //换句话说,arr在main里面是数组,传入InitArr函数之后,就成了数组首元素的指针变量。 //void InitArr(int arr[], int n)//这里的arr就不是数组了,它成了数组首元素的指针变量。 //降维后,此时arr就没有进行每个元素的临时拷贝,只进行了首元素地址的临时拷贝。 //在c中,任何函数调用,只要有形参实例化,必定形成临时拷贝 void InitArr(int* arr, int n) { //sizeof(arr)结果为4 - 是32位平台下指针的大小。 for (int i = 0; i < n; i++) { *(arr + i) = i; //以指针的形式访问 } } int main() { int arr[5] = {1,2,3,4,5}; //sizeof(arr)结果为20 - 是数组的大小。 int num = sizeof(arr)/sizeof(arr[0]);//求数组元素的个数,[]为什么为0?因为0下标绝对存在 InitArr(arr, num); for (int i = 0; i < num; i++) { printf("%d\n", arr[i]); //以数组的形式访问 } return 0; } //上面代码其实没有什么问题,不过,不知道大家发现没有,\ 如果没有将指针和数组元素访问打通,\ 那么在C中(面向过程,函数是核心概念,离不开定义与调用函数)\ 如果有大量的函数调用且有大量数组传参,会要求程序员进行各种访问习惯的变化。\ 只要是要求人做的,那么就有提升代码出错的概率和调试的难度。 //假设指针和数组的访问方式方式不通用,\ 程序员就需要不断地在不同的代码片段处,进行习惯的切换 //所以干脆,C将指针和数组的访问方式打通,让程序员在函数内,\ 也好像使用数组那样进行元素访问,本质是减少了编程难度! //打通之后,* 和[ ]两个操作符都可以对数组进行访问 //故整个降维过程,对于使用者是透明的,使用者不需要了解降维过程。
函数传参是要发生降维的
为什么?
复杂数据类型通常需要较大的内存空间和更复杂的操作,直接传递复杂数据类型可能会导致效率低下,而且在栈上分配大块的内存也可能导致栈溢出的问题。
降维成什么?
函数传参中的“降维”是指将复杂数据类型(如数组)降维成为指向其内部元素的指针来传递的过程。
比如:int arr[10];会降维成int类型的指针。
a 和 &a的区别 ----- 复习 + 练习
#include <stdio.h> int main() { int a[5] = { 1, 2, 3, 4, 5 }; int* ptr = (int*)(&a + 1); printf("%d %d\n", *(a + 1), *(ptr - 1)); return 0; }
结论:&a叫做数组的地址,a做右值叫做数组首元素的地址,本质是类型不同,进而进行+-计算步长不同
- &a:+1表示步长为sizeof(arr)。
- a:+1表示步长为sizeof(arr[0])。
指针数组和数组指针
- 指针数组:是由若干个指针所组成的数组,每个指针指向一个特定类型的变量。在指针数组中,每个元素都是一个指针变量。
- 数组指针:是一个指向数组的指针变量。
那么下面的到底那个是数组指针,那个是指针数组呢?
(A)、int* p1[10]; (B)、int(*p2)[10];
提示:
- [ ]的优先级大于 *
- ( )的优先级最大
那要怎么使用它们呢?
- 指针数组
#include <stdio.h> int main() { int* p1[5]; // 声明一个指针数组,数组中包含 5 个指向 int 类型变量的指针变量 int a = 1, b = 2, c = 3, d = 4, e = 5; p1[0] = &a; // 将指针变量 p1[0] 设置为变量 a 的地址 p1[1] = &b; p1[2] = &c; p1[3] = &d; p1[4] = &e; //这里的[]优先级比*高 printf("%d %d %d %d %d\n", *p1[0], *p1[1], *p1[2], *p1[3], *p1[4]); // 输出 1 2 3 4 5 return 0; }
- 数组指针
#include <stdio.h> int main() { int(*p2)[5]; // 声明一个数组指针,指向包含 5 个 int 类型变量的数组 int a[5] = { 1, 2, 3, 4, 5 }; p2 = &a; // 将指针变量 p 设置为数组 a 的首地址 printf("%d %d %d %d %d\n", (*p2)[0], (*p2)[1], (*p2)[2], (*p2)[3], (*p2)[4]); // 输出 1 2 3 4 5 return 0; }
总结:
- 整形指针数组,数组内部,后面可以放置任何类型(内置、结构体、联合体等)的内容。
(A)、int* p1[10];
- 整形数组指针,指针可以指向任何合法的类型变量。
(B)、int(*p2)[10];
我们发现我们在定义变量的都是先写变量的类型,然后再写变量名。
- 提示:数组的数据类型是int [num]。
- 数组指针一般使用的时候都是指向整个数组的地址。
//变量类型为char,变量名为c char c; //变量类型为int,变量名为a int a; //变量类型为double*,变量名为b double* b; //那我们定义数组应该是 int[5] d; //定义指针数组应该是 int*[5] p1; //定义数组指针应该是 int[5]* p2 //但是实际上是 //变量类型是int[5]变量名是d int d[5]; //变量类型是int*[5],变量名是p1 int *p1[5]; // -> 指针数组 //变量类型是int[5]*,变量名是p2 int (*p2)[5];// -> 数组指针 //这是c语言的规定写法
不过我们这里可以用typedef来改这个规定
#include<stdio.h> typedef int* p_1[5]; typedef int(*p_2)[5]; int main() { //变量类型是int*[5],变量名是p1 int* p1[5]; // -> 指针数组 p_1 p3; //变量类型是int[5]*,变量名是p2 int(*p2)[5];// -> 数组指针 p_2 p4; return 0; }
总结:
- 指针数组的数据类型是int *[num]。
- 数组指针的数据类型是int[num] *。
//这两个是什么意思? //指针数组 int *p[4]; //数组指针数组 int(*p[4])[5]; //p先和[4]结合,是一个数组,可以把这里的p[4]整体理解为上面的p,只不过此时的p是数组 //数组指针 int (*p)[4] //数组指针数组的指针 int(*(*p)[4])[5]; //p先和*结合,是一个指针,然后指向一个[4]的数组,数组里面的内容是指针,\ 而这个指针是指向一个[5]的指针
地址的强制转化
- 先回答问题,强制类型转换,究竟在做什么?---> 将一种数据类型强制转换成另一种数据类型,在强制类型转换中,数据的内容不会发生改变,只是改变了数据的解释方式。
- 强制类型转换,和把字符串“1234”转化成int 1234的转化,有区别吗?---> 将字符串转换为整数是指将一个表示数字的字符串转换为整数类型,这种一定会改变数据本身,称为强制类型转化。
结论:强制类型转化,改变的是对特定内容的看待方式,在C中,就是只改变其类型,对数据的本身是不好发生任何变化的!
#include<stdio.h> int main() { int a = 0x11223344; printf("%x\n", *(&a)); //将整型的地址强制转化为字符型的地址,查看输出结果? printf("%x\n", *((char*)&a)); return 0; }
- 这也符合我们之前的结论,将整形指针类型的&a强制转化为字符型指针类型的&a, 但是&a的内容不会发生改变,只是改变了&a的解释方式。
- 然后由于char类型每次只能访问一个字节,且访问的时候都是开辟空间的众多字节中地址最小的那个字节。
- 由于我们目前电脑的是小端模式,拿出的是权值最低的那个字节,即44。
#include <stdio.h> struct Test { int Num; char* pcName; short sDate; char cha[2]; short sBa[4]; }*p = (struct Test*)0x100000; //假设p 的值为0x100000。 如下表表达式的值分别为多少? int main() { printf("%p\n", p + 0x1);//0x100000 + 20(0x14) == 0x100014 //此时的p经过强制类型转化后,不再是指针了,而是一个无符号长整型的数据 printf("%p\n", (unsigned long)p + 0x1);//0x100000(无符号长整型) + 1 = 0x100001 //强制转换为无符号整形指针,+1跨过sizeof(int)个字节 printf("%p\n", (unsigned int*)p + 0x1);//0x100000 + 4(0x04) = 0x100004 return 0; }
#include <stdio.h> int main() { int a[4] = { 1, 2, 3, 4 }; int* ptr1 = (int*)(&a + 1); int* ptr2 = (int*)((int)a + 1); printf("0x%x,0x%x\n", ptr1[-1], *ptr2); //数组[]内只能为正数,这里为负数说明这里是指针,等价于*(ptr1-1) return 0; }
【程序猿必备:指针与数组的高级技能秘籍】(中):https://developer.aliyun.com/article/1424716