目录
一、指针
1、什么是指针?指针就是地址,是一个数据,可以被保存在变量的空间里面
2、什么是指针变量?指针变量是一个变量,用来保存指针,是变量就有地址,有空间,有内容
3、为什么要有指针?提高CPU寻址效率
如何理解编址?
首先,必须理解,计算机内是有很多的硬件单元,而硬件单元是要互相协同工作的。所谓的协同,至少相互之间要能够进行 数据传递 。 但是硬件与硬件之间是互相独立的,那么如何通信呢?答案很简单, 用"线"连起来 。 而 CPU和内存之间也是有大量的数据交互 的,所以,两者必须也用线连起来。 不过,我们今天关心一组线,叫做 地址总线 。 CPU 访问内存中的某个字节空间, 必须知道这个字节空间在内存的什么位置,而因为内存中字节很多,所以需要给内存进行编址 ( 就如同宿舍很多,需要给宿舍编号一样)计算机中的编址,并不是把每个字节的地址记录下来,而是通过硬件设计完成的(约定而成),无需专门去保存地址以及地址对应的空间,因为两者是由厂商规定好的。我们可以简单理解, 32 位机器有 32 根地址总线,每根线只有两态,表示 0,1 【电脉冲有无】,那么一根线,就能表示 2 中含义,2 根线就能表示 4 中含义,依次类推。 32根地址线,就能表示2^32中含义,每一种含义都代表一个地址。2^32bit = 4G,故32位机器内存4GB, 地址信息被下达给内存,在内存内部,就可以找到改地址对应的数据,将数据在通过数据总线传入 CPU 内寄存器。
4、所有变量&,取出的永远是地址值最小的字节的地址
5、强制类型转化的本质就是看待方式的变化,从起始位置连续往后读几个字节,以及后续如何解释他:"12345"——>12345——>一定要改变数据本身——>不是强转 int a = 97——>(char)a——>'a',强转
6、对指针解引用,代表指针所指向的目标
7、*p使用的是p的内容,假设p指向的变量的地址为0x00ff40,则*p = *(0x00ff40)
#include <stdio.h>
int main()
{
int* p = NULL;
p = (int*)&p;
//*p = p变量自身,将10作为一个地址付给p
*p = 10;
//将20做为一个地址赋给p,这里的*p = p
p = 20;
printf("%p", p);//20
return 0;
}
8、对指针变量+1,实际是加其自身类型的大小(指针自身类型,或强转之后的类型)
9、对指针变量+1的深刻理解:实际是加其自身类型的大小,指针变量的内容发生变化,即指针所指向的变量发生变化,而指针变量自身的地址不受影响
10、因为一级指针指向的目标类型多样化,所以+1后的步长也多样化,但是二级及以上指针+1的步长永远都是4/8
11、int sz = sizeof(arr) / sizeof(arr[0]),为什么一般写arr[0]而不写arr[1]....,因为数组至少有一个元素,arr[0]绝对正确
二、数组
1、数组的内存布局
#include <stdio.h> int main() { int a = 10; int b = 20; int c = 30; printf("%p\n", &a); printf("%p\n", &b); printf("%p\n", &c); return 0; }
我们发现,先定义的变量,地址是比较大的,后续依次减小 这是为什么呢? a,b,c都在main函数中定义,也就是在栈上开辟的临时变量。而a先定义意味着,a先开辟空间,那么a就先入栈,所以a 的地址最高,其他类似。#include <stdio.h> int main() { int a[10] = { 0 }; for (int i = 0; i < 10; i++) { printf("a[%d] = %p\n", i, &a[i]); } return 0; }
发现:在数组a中,a[0]到a[9]地址依次增大,为什么不符合栈区内存开辟顺序规律?
原因:数组内存整体开辟,然后把地址值最低的地址作为首元素的地址,以此类推,最后整体释放
数组空间排布:线性连续且递增
数组int a[10],[ ]中的元素个数也是数组类型的一部分
2、数组名做左值和右值的区别
#include <stdio.h>
int main()
{
int a[10] = { 0 };
int* p = a;
printf("%p %p\n", p, &p[0]);
return 0;
}
1、数组名做右值,代表数组首元素地址,等价于&a[0]
#include <stdio.h>
int main()
{
int a[5] = { 0 };
a = { 1,2,3,4,5 };//此写法错误,数组只能整体初始化,不能整体赋值
return 0;
}
2、数组名不能做左值,能做左值的一定要有空间,并且可被修改,而数组名代表首元素地址,是一个指针(数据)
3、数组只能进行整体初始化,不能整体赋值,不能做左值
3、指针和数组的关系
1、char* str = "hello bit",真正意义上的不可被修改,由操作系统保护,而char str[] = "hello bit,在栈上开辟,可以被修改
#include <stdio.h>
void show(int arr[])//实际上已经是int* arr了,所以[ ]内的数组可以忽略(可写可不写,可写任意大于0的数)
{
for (int i = 0; i < 5; i++)
{
printf("%d ", arr[i]);
}
}
void show1(int* p)
{
for (int i = 0; i < 5; i++)
{
printf("%d ", *(p + i));
}
}
int main()
{
int arr[5] = { 1,2,3,4,5 };
show(arr);//在这里降维
show1(arr);
return 0;
}
2、所有数组传参,一定发生降维,降维成指向其内部元素类型的指针,换言之,指针与数组在访问多个连续的元素的时候,既可以采用指针解引用方案,也可以采用[ ]的方案
4、为什么要将数组和指针的访问方式打通?如果没有将指针和数组元素访问打通,那么在C中(面向过程)如果有大量的函数调用且有大量数组传参,会要求程序员进行各种访问习惯的变化。只要是要求人做的,那么就有提升代码出错的概率和调试的难度。所以干脆,C将指针和数组的访问方式打通,让程序员在函数内,也好像使用数组那样进行元素访问,本质值减少了编程难度,节省人力!
5、c中任何函数传参一定形成临时拷贝
6、如果形参写成int arr[ ],那么 [ ]中的内容是被忽略的
结论:指针和数组没有关系
三、指针数组和数组指针
1、指针数组
1、int *a [10]是指针数组,本质是数组,数组有10个元素,每个元素类型是int*
2、[ ]的优先级大于*
2、数组指针
1、int (*a)[10]是数组指针,本质是指针,指向一个数组,类型是int [10]
四、多维数组&&多级指针
1、二维数组在内存空间排布上也是线性连续且递增的
2、所有数组都可以看作是一维数组,所以所有数组在空间排布上都是线性连续且递增的
几乎大部分书中所画的二维数组,都是矩阵样子,具体可以参考书中的图但是,现在我们要在这里澄清,书中的图,最多只能称之为示意图,并非真的内存布局图,所有的数组(不论几维)都是线性连续且递增
3、二维数组可以看作内部元素是一维数组的一维数组
4、n 维数组可以看作内部元素是n-1维数组的一维数组
5、char p[2][3][4][5],离变量名(p)最近的维度([2])决定一维数组p元素的个数,剩下(char [3][4][5])的是数组内容
指针 - 指针代表指针之间所经历的元素个数
#include <stdio.h> int main() { int a[5][5] = { 0 }; int(*p)[4]; p = a; printf("%p %p ", &a[4][2], &p[4][2]); printf("%d", &a[4][2] - &p[4][2]);//4 return 0; }
6、指针变量也是变量,有地址,地址数据可被保存(多级指针)
五、数组传参
1、所有数组传参,一定发生降维,降维成指向其内部元素类型的指针
2、函数传参,形参和实参的类型必须严格一致(数组传参既可以使用数组接受,也可以使用指针接受,但本质上类型一致都是指针)
3、数组传参,直接传数组名即可
4、数组传参为什么要发生降维?提高函数调用效率,减少内存占用
5、一维数组传参,形参可以忽略[ ]里的数字
6、多维数组传参,形参可以忽略第一个[ ]中的内容,但不可忽略其他[ ]内的数字
7、为什么形参不可忽略其他[ ]里的数字?如果忽略了第二个及之后[ ]里的数字,会导致指针类型不明确,形参int a[5][ ]实际为int(*)[ ]接收类型不明确,而用int a[ ][5]接收实际是int (*)[5],类型很明确(char p[2][3][4][5],离变量名(p)最近的维度([2])决定一维数组p元素的个数,剩下(char [3][4][5])的是数组内容【类型】,数组传参降维成指向其内部元素类型的指针,所以元素个数可以忽略,但内部元素类型不可忽略)
二维数组传参的建议:
int a[5][6];print(a, 5);——>print(int a[ ][6], int num)
六、函数指针
1、fun(),()代表函数调用
2、fun和&fun一样,都是函数的地址,建议以后直接用fun来取函数地址
3、为什么函数也有地址?函数也是代码的一部分,程序运行时也要加载到内存,以供CPU寻址访问,代码也有地址
#include <stdio.h>
void show()
{
printf("hello");
}
void main()
{
void(*p)() = show;
p();//推荐使用此写法
(*p)();
return 0;
}
4、(*(void (*)())0)() 这是什么:将0强制类型转换成函数指针,并解引用,相当于对0地址出的函数直接调用
是冬至呀-CSDN博客https://blog.csdn.net/2302_81218652?spm=1000.2115.3001.5343