### 数据类型的本质
> 数据类型可理解为创建变量的模具(模子);是固定大小内存的别名.
> 变量本质:( 一段连续)内存空间的别名、内存空间的标号.
> 数据类型的作用 :编译器预算对象(变量)分配的内存;也间接指出了变量的解析方式.
### 指针铁律
a.指针是一种数据类型,是指向它指向的内存空间的数据类型.
> 指针也是一种变量 ,占用内存空间,保存内存地址.<指针变量与它所指向内存空间是两个不同的概念>
& 站在编译器的角度,形参如果是指针类型,C编译器只会分配4个字节的内存.
void function(char ***pt);
& 当我们使用指针的时候,我们才关心这个内存块是一维的,还是二维的。
> 指针指向谁,就把谁的地址赋给指针: pt = &a;
> 指针的步长,根据它所指向的内存空间的类型来决定的.<类型决定解析方式>
b.通过*pt来访问和修改实参的值是指针存在的最大意义
& 在函数调用的时候
用1级指针形参,去间接修改 0级指针(实参)的值。
用2级指针形参,去间接修改 1级指针(实参)的值。
用3级指针形参,去间接修改 2级指针(实参)的值。
用n级指针形参,去间接修改 n-1级指针(实参)的值。
c.辅助指针:处理业务逻辑时常添加一个或两个辅助指针(px,py)来解决问题
uint8_t *pt = (uint8_t *)[data bytes];// uint8_t占1个字节.
### 函数指针与指针函数
void (*foo)(int num);//指向参数为int,返回值为void的函数的指针,函数指针
void func(int);//func是函数的入口地址;&func是函数指针,类型是void(*)(int)
void *foo(int num);//指针函数,返回值为指针的函数
const int *pci; //指向const int的指针就是指向整数常量的指针
int* const pci; //指向int型变量的常量指针,指针地址不可修改.
### 数组指针和指针数组
int (*p)[10] ;//数组指针,p指向的是一个带10个int型元素的数组.
int* arr[5];//指针数组,数组中每一个元素都是指针.
a[i] 等价于 *(a+i);//数组的本质是一段连续的存储空间;
1)数组首元素的地址和数组地址是两个不同的概念
2)数组名代表数组首元素的地址,它是个常量
3)数组首元素的地址和数组的地址值相等
比如int a[10] => a是数组首元素的地址;&a是数组指针,类型是int(*)[10].//&a+1按照步长去理解
typedef int MyArrayType[10];//定义一个int a[10]的数组类型
typedef int (*MyPArrayType)[10];//定义一个指针类型,定义一个指向数组(int a[10])的指针类型.
扩展: int myarr[3][5];# myarr是一个数组指针。
理解: *(*(myarr+i)+j);// *(myarr+i)表示到第几行,记住指针的步长就ok.
### 数组作函数的参数
> C语言中只会以机械式的值拷贝的方式传递参数(实参把值传给形参)
数组参数 等效的指针参数
一维数组 char a[30] <=> 指针 char*
指针数组 char *a[30] <=> 指针的指针 char **a
二维数组 char a[10][30] <=> 数组指针 char(*a)[30]
& 当数组作函数参数的时候会退化为指针.
& 总结:函数调用的时候,把数组首地址和有效数据长度传给被调函数才是最正确的做法.
### 二级指针的3种内存模型
a.第一种内存模型
// char *arr[] = {"cmc","abc","ycs","1111"};
// arr是个二级指针,和int a[10] 级指针不一样,sizeof(arr)=32; []相当于一个指针
void printArray(char **arr,int len){ for (int i = 0; i < len; i++) {
printf("%s,",arr[i]);// *(arr+i) }
}
b.第二种内存模型
// char pArray[10][30] = {"cmc","abc","ycs","1111"};
int printAarray02(char pArray[10][30], int num)
{
for (int i=0; i<num; i++) {
printf("%s \n", pArray[i]); }
return 0; }
c.第三种内存模型
char **pArray = (char**)malloc(10*sizeof(char*));
for (int i = 0; i < 10; i++) {
pArray[i] = (char *)malloc(16*sizeof(char));//分别赋值,可以让字符串长度不一样
}
for (int i = 0; i < 10; i++) {
free(pArray[i]);//释放内存 }
free(pArray);
### 多维数组的本质
// arr的本质是一个数组指针,每次往后跳一维的维数,这里是5*4个字节
int arr[3][5];// arr+i相当于跳到第i行
int (*pArray)[5];
pArray = arr;
int temp = 0;
for (int i=0; i<3; i++) {
for (int j=0; j<5; j++) {
pArray[i][j] = ++temp;
} }
### 数组指针的使用
typedef int MyArrayType[5];
int a[5];
MyArrayType *pArray = &a;//定义一个数组类型的指针
//我通过数组指针的方式来操作a[5]这块内存
for (int i=0; i<5; i++)
{
(*pArray)[i] = i+1; //a[i] = i+1;
}
### 结构体内存对齐
字节对齐的细节和编译器实现相关,但一般而言,满足三个准则:
1) 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;
2) 结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding);
3) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节(trailing padding)。
**说明:
字节对齐第3条准则提及最宽基本类型的概念,所谓基本类型是指像char、short、int、float、double这样的内置数据类型。“数据宽度”就是指其sizeof的大小。诸如结构体、共用体和数组等都不是基本数据类型。除结构体的第一个成员外,其他所有的成员的地址相对于结构体地址(即它首个成员的地址)的偏移量必须为实际对齐单位或自身大小的整数倍(取两者中小的那个)
struct A { char c; short a; int i; };
sizeof(A) = 8;