目录
一、数组
1.数组名
1.大多数情况数组名都代表的是数组首元素的地址,除了:
>>>Sizeof(数组名) —— 数组名表示整个数组 ——计算的是整个数组的大小,单位是字节
>>>&数组名 —— 数组名表示整个数组 ——取出的是整个数组的地址
举例验证:
2.数组只能够进行整体初始化,不能整体赋值(数组名是地址常量不能作为左值)。
2.数组的内存布局
1.普通单一局部变量的地址从高往低发展(栈区空间的特点)。
2.数组整体开辟空间,不同数组的地址从高往低发展。
3.数组内部元素的地址连续且随下标递增。
二、指针
1.32位电脑有32根地址线,每根地址线有通(1)断(0)两种状况。因此,32位电脑最多可以访问2^32个地址,每个地址需要32bit == 4byte 存放。又因为一个地址对应一个字节内存,所以32位电脑最大可以访问 2^32byte == 2^10*2^10*2^10*2^2 == 4GB 大小的内存空间。
2.指针类型的意义:
>>>决定指针解引用的权限有多大(能操作几个字节)
>>>决定了指针走一步的步长有多大
3.指针-指针得到的是两个指针之间元素的个数
4.变量的地址是所占内存空间地址最低的字节的地址
5.指针的多种表示形式
Int arr[10]={0};
Int* p=arr;
//arr[2]-->*(arr+2)-->*(2+arr)-->2[arr]
//p[2]-->*(p+2)-->*(2+p)-->2[p]
三、指针与数组
首先声明一点,指针与数组是两套不同的概念,只是因为在使用方法上有相似之处所以经常被初学者搞混。
1.使用方法上的相似点:
指针与数组,在访问多个连续元素的时候,既可以采用指针解引用的方式,也可以采用 [中括号]的 方式。
2.指针与数组名的区别:
>>>数据类型不同:指针是保存地址的变量;数组是相同类型数据的集合。
>>>访问数据的方式:
利用指针访问数据属于间接寻址,本质上是先取p的内容然后加上i*sizeof(类型)字节作为数据真正的地址;
数组名访问数组元素是直接寻址,本质上是arr所代表数组首元素的地址(地址常量)加上i*sizeof(类型)字节作为数据真正的地址。
>>>常用场合:指针通常用于动态数据结构;数组通常用于存储固定数目且数据类型相同的元素。
3.数组传参
1.数组传参,需要降维成指针。如果不降维,就要发生数组拷贝,函数调用效率降低。
2.所有的数组,传参都会降维成指针,降维成为指向其内部元素类型的指针。
3.函数形参列表中的数组会被解释成指向其内部元素类型的指针,因此最高维[中括号]中的数字会被忽略,其他维度是指针的类型,不能省略。
4.C语言为什么要将指针和数组的访问方式设计成通用的?
C语言是面向过程的语言,函数是其核心概念。而在函数调用的过程中数组传参为提高效率,会降维成指针。
假设指针和数组访问元素的方式不通用,程序员需要不断在不同的代码片段处进行习惯的切换,增加代码出错的概率。
为了让程序员统一使用数组,减少出错概率,数组和指针的访问方式才设计成通用的。
5.多维数组与指针
1.在理解上,我们甚至可以将所有的数组都当成“一维数组”。三维数组的元素是二维数组,二维数组的元素是一维数组......
2.多维数组的内存布局也 是“线性连续且递增的”
6.两道经典例题
1.二维数组的元素是一维数组
a[i] <---> *(a+i)
取地址&和解引用*互为逆运算可以相互抵消
2.指针加 i 跳过 i*sizeof(类型) 个字节
指针相减,代表两个指针之间元素的个数
答案:FFFFFFFC,-4
四、字符指针
1.两个不同的字符数组在内存中占两块不同的存储空间,数组名指向的首元素的地址就不同
2.完全相同的常量字符串只开辟一块存储空间,因此不同的字符指针指向的地址相同。
3.字符数组中的元素是可以改变的,而字符串常量是不能改变的
五、指针数组
1.指针数组的定义
指针数组是数组——元素是指针的数组
Int *arrp[10];——arrp就是一个指针数组,其中的每一个元素都是指针。
2.让指针数组中每一个元素都指向一个字符串常量,统一处理这些字符串。
3..让指针数组中每一个元素都指向一个数组首元素的地址,就可以模拟二维数组,访问这些数组中的元素。将相同类型的一维数组串联起来。
六、数组指针
1.数组指针的定义
数组指针是指针——执向数组的指针
Int (*parr)[10]=&arr;——parr就是一个数组指针,其中存放的是数组的地址
4.数组指针的使用
所有的数组,传参都会降维成指针,降维成为指向其内部元素类型的指针。
在理解上,我们甚至可以将所有的数组都当成“一维数组”。三维数组的元素是二维数组,二维数组的元素是一维数组......多维数组传参,降维成数组指针。
七、数组传参、指针传参
1.一维数组传参
实参 | 形参 |
Int arr1[10]; | Int arr[10](形式) |
Int arr1[10]; | Int arr[](形式) |
Int arr1[10]; | Int* arr(本质) |
Int* arr2[20]; | Int* arr[20](形式) |
Int* arr2[20]; | Int* arr[](形式) |
Int* arr2[20]; | Int** arr(本质) |
2.二维数组传参
实参 | 形参 |
Int arr[3][5]; | Int arr[3][5](形式) |
Int arr[3][5]; | Int arr[][5](形式) |
Int arr[3][5]; | Int (*arr) [5](本质) |
3.当函数的参数为一级指针的时候,可以接受的参数:
相应变量的地址(&a)
相应一维数组的数组名(arr)
一级指针(p)
4.当函数的参数为二级指针的时候,可以接受的参数:
一级指针的地址(&p)
二级指针(pp)
指针数组的数组名(arrp)
八、函数指针
1.存放函数地址的指针
2.数组名VS函数名
>>>&数组名 != 数组名
>>>&函数名 == 函数名
3.定义函数指针:
返回类型 (*pf)(参数类型……)
Int (*pf)(int ,int)=&add(或add);
4.使用函数指针调用函数
>>>Int ret = add(3,5);//使用函数名调用函数
>>>Int ret = (*pf)(3,5);//'*'无实际意义
>>>Int ret = pf(3,5);//常用
5.有趣的代码:
对类型重定义简化函数声明
九、函数指针数组——无限套娃
1.存放函数指针的数组;
int(* pfarr[5])(int,int);(在函数指针的基础上加"[ ]")
2.指向函数指针数组的指针
十、回调函数
举例:库函数qsort