指针
我们可以把每个内存单元看成是一个编号,每个编号都有它相应的地址,这个地址就叫做指针
任何变量创建都会在内存开辟空间。
&a取a的首地址,比如图里的5地址处。还有下图的第一个地址
内存单元
编号-地址-地址也是指针
存放指针(地址)的变量叫做指针变量
p指向了a。也就是指针变量p存放了a的地址,类型为int。
指针就是来存放地址的
*p就是解引用操作符,通过p存放的地址找到p所指向的对象,*p就是p所指向的对象,就是找到了对象。
这里*p找到了a就可以修改了。
指针就是地址,
指针变量就是存放地址的。
区别:口头语的指针就是指针变量。
指针变量的大小
32位上int*,char* float*,long* double*等等,不管是什么类型的指针,都是在创建在指针变量,指针变量是用来存放地址,取决于一个地址存放的时候的空间多大。
32位的机器是32个bit位-4byte
64-32bit-8byte
所以32位机器的指针变量大小是4个字节,64位是8个字节。
指针的初级
把内存单元的编号称为地址,地址也叫指针。
指针就是内存单元的编号。
取地址的时候取的是最开头的地址。
因为知道起始地址后面的3个地址也容易找到。
总结:平时口头语的指针就是指针变量,实际的指针就是地址。
指针变量里面是存放的是地址,通过这个地址可以找到一个内存单元。
NULL是空指针,值为0
int类型
char类型
指针类型是有意义的:指针类型决定了指针在解引用的时候访问几个字节
如果是int*的指针解引用访问4个字节,char*的解引用访问1个字节。
指针的类型决定了+-1操作的时候跳过的几个字节
可以通俗的理解指针迭代步长
char*指针
int*类型指针
int*和float*的指针不能混用。
这个地址编号存起来需要4个或者8个字节,但是这个地址向后访问1个或者2个或者4个字节或者8个字节
总结指针大小是4个或者8个字节,但是指针的类型决定了向后访问的权限的大小
pa是指针变量他的大小取决于他存的地址的大小,与a的大小没有关系
这里的p就是野指针。
野指针的原因有二个
1.没有初始化
2.越界访问。
一个是地址+1 下面是值+1
指针-指针的绝对值=元素之前的个数。
只要拿到a的地址和\0的地址,就可以求字符串的长度。
//指针-指针求字符串长度方法
int my_strlen(char* str)
{
char* start = str;
while (*str != '\0')
{
str++;
}
return str - start;
}
int main()
{
char arr[] = "abc";
int len = my_strlen(arr);
printf("%d\n", len);
return 0;
}
指针-指针的绝对值得到的是元素之间的个数。
允许你p1和p2比,不允许p1和p3比。
这个里的p存储的是arr首元素的地址,首元素的地址+i解引用相当于访问的是这个数组的i个元素
arr是数组的首元素地址 (arr+i)在解应用的时候可以访问下标为i的元素。
指针也是一样 p指向数组的首元素地址 (p+i)解引用就可以访问下标为i元素了。
a在内存中有自己的内存地址,pa虽然指向的a,存储了a的地址,但是pa也有自己的内存空间,同理ppa也是。
这个*说明pa是指针,int说明pa指向的对象是int类型
第二个*说明ppa是指针,第一个int*说明 ppa指向的对象pa的类型是int*
二级指针是用来存放一级指针变量的地址的
指针数组
存放指针的数组就是指针数组本质是个数组
在这里int arr[10] 这里的int 说明 arr存放的是整形类型 int * parr 这里的int* 说明parr存放的是整形指针类型。
parr[i]就是访问下标为i的元素, 就相当于拿a的地址或者b的地址或者c的地址 然后在解引用就可以访问a或者b或者c
通过下标找到a的地址,解引用找到a的元素等等。
parr[i]就是访问下标为i的元素, 就相当于拿arr1的地址或者arr2的地址或者arr3的地址,也就是拿到了他们的数组名,+j然后再解引用就可以访问它第i行第j个元素了。
arr1相对于第一行的数组名,也就是第一行的起始地址,加上j就可以访问第一行的某个元素的地址了。
const修饰
指针进阶
字符指针![](https://img-blog.csdnimg.cn/ccb411a0b7df41dd8ccb6128557ed5ab.png)
这个就是把字符串a的地址存在p里面了。
只要告诉我们这个字符串的起始地址,我们就可以向后打印,直到打印\0为止。
p1与p2都是指针,指向abcdef的起始地址,并且都是常量字符串,都是只读区域不能修改。所以一样
arr1与arr2虽然内容一样,但是开辟的内存首地址不同。因为arr1与arr2是不同的数组名。
指针数组
顾名思义就是存放指针的数组,本质就是数组。
指针数组 说明数组里的每个元素是int*类型的
arr1是parr【下标为1】的数组名,存放的是首元素(1)的地址。
arr1是数组名,arr1相当于于首元素1的地址
parr有三个元素,分别是arr1,arr2,arr3,每个元素的类型是int*的指针
只要parr找到对应的下标,就相当于对应的数组名,就可以访问对应的元素起始地址,
parr【i】就相当于arr1或者arr2或者arr3这个元素,然而arr1又是数组名,数组名又是首元素地址,就可以1访问1的地址,在加上一个j,在解引用就可以访问arr1里的某个元素了。
数组指针
用来存放数组的地址的
存放数组的指针,本质是指针
它的类型就是去掉名字,剩下的就是他的类型。数组指针类型。
*没有类型才叫解引用。
int*p1[10]这个p1是跟[]结合,说明p1是数组,int*说明里面存放的是整形指针。
int(*p2)[]这个p2是跟*结合,说明p2是指针,说明p2指向的是数组,每个元素是int类型。
数组名
数组名能表示首元素地址但是有二个意外
1。sizeof(数组名),单独放 这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节。
2.取地址数组名 ,这里的数组名表示的是整个数组,取出的是整个数组的地址。因为&arr是取的整个数组,也是从arr[0]开始取的,他会一直取到这个数组完为止。+1跳过一个数组地址大小。
数组的地址也是从0开始,数组的首元素地址也是从0开始,只不过数组的地址+1,一次性跳过整个数组,是40个字节,而数组的首元素地址+1跳过的是4个字节。因为这个是int类型,有10个元素
分析 p是指向数组的,相当于指向整个数组的地址,也就是整个数组的地址,*p其实是整个数组,就相当于数组名,数组名代表整个数组,数组名又是首元素地址,所以*p本质上是数组的首元素地址。
访问元素是 *p+i就可以访问下标为i的元素的地址,解引用就可以访问下标为i个元素了
int*p=arr 说明 p指向的是数组名 数组名又是首元素地址 ,所以用指针来接收,使用加*号,说明p是指针,他指向的数组名的类型是int类型 说明是int类型
p2指向的是数组,是整个数组的地址所以用指针来接收,说明p2是指针,为了避免与[]结合要*号加括号括起来好一些,这个数组有10个元素,每个元素是int类型。
这个p指向了数组名的地址,这个数组有10个元素,每个元素的类型是int类型。它加上+i就可以访问下标为i元素的地址了,然后解引用就可以访问下标为
i的元素了。单独的*p就是找到了数组名
总结:
二维数组的数组名是第一行的地址也就是一维数组的地址,所以用一维数组指针来接受,然后
他一有三行,指向的是第一行 ,每一行有5个元素,所以指针指向的数组元素是5个,每个元素的类型是int类型,所以用int(*p)[5]来表示
(p+i)表示的是指向第i行的地址,也就是整行的地址,他+1表示跳过整个数组的大小,*(p+i)表示第i行的数组名也就是arr[i] 他+1表示跳过一个元素的大小。
如果是取地址第i行的数组名,+1就是跳过一行的地址大小。
二维数组的数组名相当于二维数组的首元素地址,这个首元素是第一行,也就是第一行的地址,第一行的地址相当于一维数组的地址 ,一维数组的地址需要一维数组的指针来存储。
所以需要指向一维数组的指针来接受。
p+i是指向i行的地址。,*(p+i)是指向第i行的数组名 相当于拿了第i行
数组名又是首元素地址,*(p+i)就是指向第i行的首元素地址,加上下标j就可以访问下标为j元素的地址,然后括起来解引用就可以访问第i行下标为j的元素了。
&arr的类型是int(*)[10]是个数组,是存放数组的指针,+1相对于跳过一个数组,所以是40个字节。
函数传参
int(*parr3)[10[5] ------> 首先parr3是一个数组,它有10个元素,每一一个元素的类型是int(*)[5] 说明parr3的每一个元素是指针,该指针指向的数组,也就是存了该数组的地址,该数组有5个元素,每个元素是int类型。
arr2是一个存放指针的数组,类型是int*, arr2是数组名,arr2就是首元素地址,存放的就是int*的地址, 一级指针的地址就是用二级指针来接受。
二级指针可以用一级指针的地址、二级指针来传参。
指针数组,里面存放的是指针,传指针数组的数组名,相当于传指针的地址,一级指针的地址就可以用二级指针来接受。
函数指针
指向函数的指针,取地址函数名就是函数指针。
函数名和取地址函数名都是函数的地址所以 函数名=&函数名
pf=&add----->(*pf)();
pf=add -----pf();
pf是指针变量,它指向函数,返回类型是int类型,参数也是int类型,*说明,pf是指针,*pf相当于找到了那个函数,pf存放的函数的地址。
int ret=pf(2,3)
int ret=add(2,3)这个*可以不用写。 为什么不写因为函数名和取地址函数名一样,pf是·个指针,它指向的是函数的地址,*pf就相当于找到了那个函数名
在这里说一遍,函数名和取地址函数名是一样的,因为函数没有首元素地址。
那就相当于pf=add
int add(int x, int y) { return x + y; } int main() { int a = 0; int b = 0; int(*pf) (int ,int ) = add; int red= pf(2, 3); printf("%d\n", red); return 0; }
其实就是对0强制类型转换,被解释为一个函数的地址。
首先void(*)()是一个函数指针类型,它把0强制类型转换成函数指针类型---->void(*)()0
然后对其解引用对其传参,其实这里的解引用可以不写,这个*相当于找到了这个函数,它你可以把(*void(*)()0)看成是一个函数指针变量,它的地址是0的地址 ,对它进行传参。,传参的参数为空---是一次函数调用,调用的是0地址处的函数 void (*pf)()
signal是一个函数名,它第一个参数是int类型,第二个参数是函数指针类型,它的返回值类型是一个void(*)(int)类型,也就是一个函数指针类型。
C 语言提供了 typedef 关键字,您可以使用它来为类型取一个新的名字。
typedef 仅限于为类型定义符号名称,#define 不仅可以为类型定义别名,也能为数值定义别名,比如您可以定义 1 为 ONE
C 语言提供了 typedef 关键字,您可以使用它来为类型取一个新的名字。下面的实例为单字节数字定义了一个术语 BYTE:
typedef unsigned char BYTE;
在这个类型定义之后,标识符 BYTE 可作为类型 unsigned char 的缩写,例如:
BYTE b1, b2;
可以简化成这个样子 typedef void(*pf_t)(int)
制作一个简单的计算器。
通过函数的指针来调用函数
void meau()
{
printf("*******************\n");
printf("****1.add 2.sub****\n");
printf("****3.mul 4.DIV****\n");
printf("****0.exit*********\n");
printf("*******************\n");
}
int add(int x, int y)
{
return x + y;
}
int sub(int x, int y)
{
return x - y;
}
int mul(int x, int y)
{
return x *y;
}
int DIV (int x, int y)
{
return x / y;
}
void calc(int (*pf)(int, int))
{
int a = 0;
int b = 0;
int red = 0;
printf("请输入二个操作数\n");
scanf("%d %d", &a, &b);
red = pf(a, b);//pf里存的是add的地址,所以它调用到add函数里去了
printf("%d\n", red);
}
int main()
{
int input = 0;
do
{
meau();
printf("请选择\n");
scanf("%d", &input);
switch (input)
{
case 1:
calc(add);
break;
case 0:
printf("退出计算\n");
break;
case 2:
calc(sub);
break;
case 3:
calc(mul);
break;
case 4:
calc(DIV);
break;
default:
printf("选择错误请重新选择\n");
break;
}
} while (input);
return 0;
}
回调函数就是通过函数的指针调用函数。
这个也是回调函数。回调函数就是通过a函数的指针调用a函数·,因为pf存的是add的地址,地址就是指针,就可以用pf这个指针来调用a函数。calc这个就是b函数,b函数里存放的是a的地址。
pf里存的是add的地址,所以它调用到add函数里去了
函数指针数组
这个arr就是函数指针的数组本质是数组,存放的函数指针,类型是int(*)(int,int)
找到arr[i]下标为i元素的函数就可以调用这个函数,这里有给误区这里的函数既是函数名又是函数地址,因为arr[]这个数组存的是函数名也就是函数的地址
找到数组arr下标为i的函数名,你可以解引用,也可以不解引用,这里的解引用没有特殊作用,因为函数名和&函数是一个作用,也就是通过arr的下标找到了这个函数然后来访问这个函数。
void meau()
{
printf("*******************\n");
printf("****1.add 2.sub****\n");
printf("****3.mul 4.DIV****\n");
printf("****0.exit*********\n");
printf("*******************\n");
}
int add(int x, int y)
{
return x + y;
}
int sub(int x, int y)
{
return x - y;
}
int mul(int x, int y)
{
return x *y;
}
int DIV (int x, int y)
{
return x / y;
}
int main()
{
int input = 0;
int x = 0;
int y = 0;
int red = 0;
int (*parr[5])(int, int) = { NULL,add,sub,mul,DIV };
do
{
meau();
printf("请选择\n");
scanf("%d", &input);
if (input == 0)
{
printf("退出\n");
}
else if (input >= 1 && input <= 4)
{
printf("请输入二个操作数\n");
scanf("%d %d", &x, &y);
red = parr[input](x, y);
printf("%d\n", red);
}
else
{
printf("选择错误\n");
}
} while (input);
return 0;
}
这个就是函数指针数组的好处。
指向函数指针数组的指针
首先pfarr是个指针,它指向数组,这个数组的类型是int(*)[5](int,int),是一个函数指针数组
回调函数
快排
cmp就是比较函数的地址 ,e1指向了你要比较的第一个元素,e2指向了你要比较的第二个元素
e1和e2是你要比较2个元素的地址。
通过用户自定义的函数比较出二个元素的大小传给cmp这个函数指针,分别对应的他二个参数,让cmp函数自己写出排序的顺序
第四个参数是比较待排序数据的2个元素函数
//快排
int cmp_int(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
int main()
{
int arr[] = { 9,8,7,6,5,4,3 };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_int);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
void*的指针不能直接解引用操作,void*是无类型指针,可以接受任何类型的地址。
void*是无类型指针,所以不能解引用操作,不能加1操作。
所以e1和e2要强制类型转换在解引用才能找到。
//比较结构体里的字符串大小 struct stu { char name[20]; int age; }; int cmp_stu(const void* e1, const void* e2) { strcmp(((struct stu*)e1)->name, ((struct stu*)e2)->name);//结构体指针要用()起来。 } void test() { struct stu s [] = {{"zhangsan", 18}, {"lisi",20}}; int sz = sizeof(s) / sizeof(s[0]); qsort(s, sz, sizeof(s[0]), cmp_stu); } int main() { test(); return 0; }
模拟实现qsort函数
1、cmp()会返回int型返回值;
2、cmp()带有两个指针型形式参数const void *
其中,const为一个限定词,含有const限定词的参数,尤其是以指针传递地址的参数,不会改变其原本的值。
void *,为一个无类型指针,既可以通过强制类型转换,使其转换为任意类型的指针,甚至直接取指向的数据值。
注意cmp这个不是库函数,但名字必须是cmp
cmp作为一个比较函数,可以简单方便的对bsearch、qsort等函数提供交换依据。
它的基本形式为:
用cmp指向的那个函数来比 二个参数是二个元素的地址
首先base是你要排序的起始地址 首先要转换成char*类型,为什么要转换成char*类型,因为char*类型它每跳一个字节,跳的更细,然后+上元素的个数就是j,当j等于0的时候也就是从起始地址开始,最后乘以宽度,这个宽度就是待排序的数据元素的大小,单位是字节,比如我们排int类型的数据就是乘以4
起始地址+偏移量的方法来计算
偏移量计算:=下标*宽度(元素的宽度啊)
知道你要交换的起始地址还不够,还必须知道宽度 就是这二个元素的起始地址每个地址占的宽度。
通过循环和逐个字节交换的方式:
因为我们无法直接知道传入的数据的具体类型,所以只能通过按字节逐个交换的方式来完成内存块的交换。循环的次数为 Width
,即内存块的大小,这样可以确保我们对整个内存块进行遍历和交换。
循环的次数为 Width
是因为这段代码是用来交换内存块内容的通用函数,而不关心数据的具体类型。在这种情况下,我们必须以字节为单位进行操作,因为无法确定传入的数据的具体类型。
假设我们有一个包含 4 个整型元素的数组,每个整型元素占据 4 个字节。如果我们要交换两个元素,就需要将每个字节都进行交换。因此,循环的次数必须等于内存块的大小,以确保每个字节都被正确地交换。
另外,通过循环的方式可以确保在不同平台上也能够正确地执行内存块的交换操作,不受数据类型或内存对齐方式的影响。这样的通用性使得这个交换函数可以适用于各种不同类型和大小的内存块,从而提高了代码的重用性和灵活性
int cmp_int(const void *e1,const void* e2)
{
return *(int*)e2 - *(int*)e1;
}
void Swap(char* e1,char *e2,int width)
{
for (int i = 0; i < width; i++)
{
char tep = *e1;
*e1 = *e2;
*e2 = tep;
e1++;
e2++;
}
}
void bubble_short(void* base, int sz, int width, int (*cmp)(const void* e1, const void* e2))
{
int i = 0;
for (i = 0; i < sz - 1; i++)
{
int j = 0;
for (j = 0; j < sz - 1 - i; j++)
{
//排序
//cmp是你要比较的元素地址
if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
{
//交换
Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
//比较的元素地址 和每个元素的字节大小宽度
}
}
}
}
int main()
{
int arr[] = { 9,8,7,6,5,4,3,2,1 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_short(arr, sz, sizeof(arr[0]), cmp_int);
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
简单制作求最大值和求最小值的方法
int max_1 (int a, int b)
{
return a > b ? a : b;
}
int min_1(int a, int b)
{
return (a < b) ? a : b;
}
int main()
{
int a = 0;
int b = 0;
int input = 0;
printf("输入1是求最大值,输入2是求最小值\n");
scanf("%d", &input);
printf("输入二个操作数\n");
scanf("%d %d", &a, &b);
int (*pf[3])(int, int) = { NULL,max_1,min_1 };
if (input >= 1 && input <= 2)
{
int red = pf[input](a, b);
printf("%d\n", red);
}
return 0;
}
指针的笔试题目
//深度讨论数组名 int main1() { int a[] = { 1,2,3,4 }; printf("%d\n", sizeof(a)); //sizeof内部出现数组名代表是整个数组的大小 //16 printf("%d\n", sizeof(a + 0)); //首元素地址+0还是首元素地址 4/8 printf("%d\n", sizeof(*a)); //首元素地址进行解引用相当于a[0]是4个字节 //*a中的a是数组首元素的地址,* a就是对首元素的地址解引用,找到的就是首元素 //首元素的大小就是4个字节 printf("%d\n", sizeof(a + 1)); //首元素地址+1 相当于a[1]的地址 是地址就是4/8 printf("%d\n", sizeof(a[1])); //第二个元素大小//4 printf("%d\n", sizeof(&a)); //取地址数组名,取的是整个数组的地址,是地址就是4/8 printf("%d\n", sizeof(*&a)); //&a----->的类型是int(*)[4] &a拿的是数组名的地址,数组指针解引用拿到了这个数组是 //是数组的大小就是16 printf("%d\n", sizeof(&a + 1)); //取数组名+1相当于跳了一个数组的大小,是地址就是4/8 printf("%d\n", sizeof(&a[0])); //取第1个元素的地址 是地址就是4/8 printf("%d\n", sizeof(&a[0] + 1)); //取第2个元素的地址 是地址就是4/8 return 0; } int main2() { char arr[] = { 'a','b','c','d','e','f' }; printf("%d\n", sizeof(arr));//sizeof内部出现数组名代表是整个数组的大小 //6 printf("%d\n", sizeof(arr + 0));//首元素'a'的地址---4/8 printf("%d\n", sizeof(*arr)); //'a'的大小/1 printf("%d\n", sizeof(arr[1]));//'a'的大小/1 printf("%d\n", sizeof(&arr));//取出整个数组的地址是地址就是4/8 printf("%d\n", sizeof(&arr + 1));//跳过一个数组的地址,是地址就是4/8 printf("%d\n", sizeof(&arr[0] + 1));//'b'的地址是地址就是4/8 return 0; } #include <string.h> int main3() { char arr[] = { 'a','b','c','d','e','f' }; printf("%d\n", strlen(arr));//随机值 因为不知道编译器在哪里放\0 printf("%d\n", strlen(arr + 0)); //随机值 printf("%d\n", strlen(*arr));//相当于strlen('a')strlen(地址)//err 是一个野指针 printf("%d\n", strlen(arr[1]));//野指针 printf("%d\n", strlen(&arr));//取数组的地址,数组的地址也是从‘a'的地址开始的 随机值 printf("%d\n", strlen(&arr + 1));//取数组名+1跳过了一个数组的地址, 随机值-6 printf("%d\n", strlen(&arr[0] + 1)); // 首元素的地址+1相当于 是字符b的地址开始 随机值-1 return 0; } int main4() { char arr[] = "abcdef";//[a b c d e f\0] printf("%d\n", sizeof(arr)); //计算的是整个数组的大小,7 printf("%d\n", sizeof(arr + 0));//首元素地址 相当于是‘a'的地址 是地址就是4/8 printf("%d\n", sizeof(*arr));//’a'元素的大小 1 printf("%d\n", sizeof(arr[1]));//1 printf("%d\n", sizeof(&arr));//取数组的地址 也是从‘a'的地址开始的 也是 4/8 printf("%d\n", sizeof(&arr + 1));//跳了一个数组的地址是地址就是 4/8 printf("%d\n", sizeof(&arr[0] + 1)); //相当于是’b'的地址 4/8 return 0; } //strlen是求字符串长度的,关注的是字符串中的\0,计算的是\0之前出现的字符的个数 // //strlen是库函数,只针对字符串 // //sizeof只关注占用内存空间的大小,不在乎内存中放的是什么 // //sizeof是操作符 int main5() { char arr[] = "abcdef";//[a b c d e f\0] printf("%d\n", strlen(arr));//6 printf("%d\n", strlen(arr + 0));//6 //printf("%d\n", strlen(*arr));//err //printf("%d\n", strlen(arr[1]));//err printf("%d\n", strlen(&arr));//‘a’字符的地址开始 6 printf("%d\n", strlen(&arr + 1)); //跳过了\0 从\0后面开始 随机值 printf("%d\n", strlen(&arr[0] + 1)); //从'b'字符开始走 5 return 0; } int main() { char* p = "abcdef";//[a b c d e f\0] printf("%d\n", sizeof(p));//p指向的是字符a的地址 4/8 printf("%d\n", sizeof(p + 1)); //指向的是字符b的地址 4/8 //printf("%d\n", sizeof(*p)); //err //printf("%d\n", sizeof(p[0])); //err printf("%d\n", sizeof(&p));//取出的是一级指针的地址 4/8 printf("%d\n", sizeof(&p + 1)); //4/8 printf("%d\n", sizeof(&p[0] + 1)); //4/8 printf("%d\n", strlen(p)); //p指向的是a的地址 6 printf("%d\n", strlen(p + 1)); //p指向的是b的地址 5 //printf("%d\n", strlen(*p)); //P找到了a的这个字符 err ///printf("%d\n", strlen(p[0])); //err printf("%d\n", strlen(&p)); //取出的是一级指针的地址 随机 printf("%d\n", strlen(&p + 1));//随机 printf("%d\n", strlen(&p[0] + 1));//5 //P+1 p指向的是b的地址 return 0; }
为什么strlen(&P)是随机值是因为指向p的那块空间上的地址存储方式不确定,有可能是大端存储,也可能是小段存储
int main()
{
int a[3][4] = { 0 };
printf("%d\n", sizeof(a));//48
printf("%d\n", sizeof(a[0][0]));//4
printf("%d\n", sizeof(a[0]));a[0]是第一行这个一维数组的数组名,单独放在sizeof内部,a[0]表示第一个整个这个一维数组
//sizeof(a[0])计算的就是第一行的大小 也就是第一行的数组名 16
printf("%d\n", sizeof(a[0] + 1)); //没有单独放在sizeof内部,表示的是首元素地址
//也就是第一行这个一维数组的第一个元素的地址--a[0][0]的地址 然后+1 就是第一行第二个元素的地址 4/8
printf("%d\n", sizeof(*(a[0] + 1))); //第一行第二个元素的地址进行解引用 也就是arr[0][1]元素的大小 --4
printf("%d\n", sizeof(a + 1));// 第一行的地址+1 相当于跳到了第二行的地址 是地址就是4/8
printf("%d\n", sizeof(*(a + 1))); //第二行的地址解引用 ---就相当于是第二行的数组名 16
printf("%d\n", sizeof(&a[0] + 1));// 对第一行的数组名取地址 相当于是第一行的地址,第一行的地址+1相当于是第二行的地址 4/8
printf("%d\n", sizeof(*(&a[0] + 1))); //第二行的地址解引用相当于拿到了第二行的数组名 16
printf("%d\n", sizeof(*a)); //第一行地址解引用 拿到了第一行的数组名 16
printf("%d\n", sizeof(a[3]));//第三行的数组名 虽然越界了 但是不影响计算它内部的大小 16
return 0;
}
1.题目1
取数组的地址说明这个指针是的类型是int(*)[5]的,说明这个指针是数组指针,指针指向这个数组,数组有5个元素,每个元素的类型是int类型
题目的指针是int*类型,说明我们要强制类型转换成int*类型的
2.题目2![](https://img-blog.csdnimg.cn/cb01e0fd326b47148880ebc2fae25355.png)
这里的p是一个结构体指针
1.结构体指针+1相当于跳过的是一个结构体的大小 已经知道一个结构体的大小是20个字节就相当于跳了20个字节。0x100000+20----ox1000014
2.把p强制类型转换成无符号长整形,,已经知道1,048,576是p转换成无符号长整形的数据,它+1= 048,577
3.把p转换成无符号整形指针类型 +1就相当于跳过一个无符号整形 相当于+4 = 1,048,576+4
3.题目3
取数组的地址说明这个指针是的类型是int(*)[4]的,说明这个指针ptr1是数组指针,指针指向这个数组,数组有4个元素,每个元素的类型是int类型
题目的指针是int*类型,说明我们要强制类型转换成int*类型的
ptr1[-1]是指向的是04 00 00 这块区域的 同理也是00是高地址的 04是低地址的拿出来的时候就是低地址在后面 00 00 00 04 以16进制打印的时候前面的0全部删除掉 就是4了
ptr2: 首先这个a是数组名,数组名是首元素地址,首元素地址+1是跳过一个整形也就是4个字节,题目强制类型转换成了int类型说明现在的a不是地址了是一个整数,它+1就说明加了一个整数
比如1的16进制是 00 00 00 01
在内存是小端存储模式,01是低地址处,----01 00 00 00
a+1强制类型转换成int*类型的指针,所以ptr2就指向了 00 00 00 02这块起始地址。因为 00 00 00 02在内存中是以小端模式存储,拿出来的时候就是02 00 00 00 以16进制打印的时候就是200 00 00
因为·02是高位的地址就应该放在低地址处
4.题目4
首先这个二维数组的元素是以逗号表达式的形式展开,你要明确这个数组里的元素是{1,3,5,0,0,0}p指向了第一行的数组名,此时的数组名表示的一行的地址,数组名没有放在sizeof内部,也没有取地址,说明数组名表示首元素地址 也就是a[0][0]的地址然后解引用就是1了
5.题目5
这里的a作为数组名,它是第一行的地址,它的类型是int(*)[5] ,因为二维数组的数组名代表的是第一行的地址,也就是第一行数组名的地址 一维数组名的地址就需要用一维数组的指针来接受。
这里的p是一个数组指针,它指向的是一个数组,这个数组有4个元素,每个元素的类型是int类型
这里把二维数组的数组名赋给数组指针 类型存在差异,但不影响,只不过是报警告
再来看看 p是一个数组指针 它+1跳过一个数组的大小,这个数组有4个元素,每个元素的类型是int类型,相当于跳了4个整形的大小,p[4][2]可以看成 *( *(p+4)+2)
*(p+4)这个相当于p跳了4个整形的大小 再图可以看出 它跳到了a[3][1]的 地址,然后解引用找到了arr[3]的数组名,就可以访问相当于arr[3][1]~ arr[3][4]这块区域的元素的地址,然后+2跳过二个整形长度指向了arr[3][3]这块地址然后解引用找到了arr[3][3] 这个元素
我们是把p[4][2]看成arr[3][3] ,指针-指针的绝对值是元素之间的个数,他们相差了4个 又因为地址再内存中是从低到高存放的,所以结果是-4
打印地址是打印它在内存中的地址,所以这里不考虑大小端字节序,也不考虑原码补码反码,
再x86的环境下就是FF FF FC 记住打印内存是打印反码的形式打印内存
6.题目6
7.题目7
pa是指向的是a的起始地址
pa++跳到了第二行,对pa解引用找到了a的地址相当于指向了a的地址
pa指向了a,a是一个指针数组pa指向的对象是一个char*类型的,第二*说明pa是一个指针
pa++=pa+1 自然是跳过一个char*类型 也就是4个字节。
int*p 是一个指针,它指向的对象是int类型,所以+跳过了4个字节。