1.一维数组
int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(a+0));
printf("%d\n",sizeof(*a));
printf("%d\n",sizeof(a+1));
printf("%d\n",sizeof(a[1]));
printf("%d\n",sizeof(&a));
printf("%d\n",sizeof(*&a));
printf("%d\n",sizeof(&a+1));
printf("%d\n",sizeof(&a[0]));
printf("%d\n",sizeof(&a[0]+1));
- 分析上述代码的打印结果
int main()
{
int a[] = { 1,2,3,4 };
// sizeof(数组名),数组名表示整个数组,计算的是整个数组的大小,单位是字节
printf("%d\n", sizeof(a));//16
printf("%d\n", sizeof(a + 0));//4
// a不是单独放在sizeof内部,也没有取地址,所以a就是首元素的地址,a+0还是首元素的地址
// 只要a表示的是地址,大小就是4/8个字节(32位就是4个字节,64位就是8个字节)
printf("%d\n", sizeof(*a));//4
// a是数组首元素的地址,即&a[0];而 *a <==> *(a+0) <==> *(&a[0]) <==> a[0]
// *a中的a是数组首元素的地址,*a就是对首元素的地址解引用,找到的就是首元素
// 首元素的大小就是4个字节(存储的元素是int类型,int是4个字节)
printf("%d\n", sizeof(a + 1));
// 这里的a是数组首元素的地址
// a+1是第二个元素的地址
// sizeof(a+1)就是第二个元素的地址大小
printf("%d\n", sizeof(a[1]));//4
//计算的是第二个元素的大小
printf("%d\n", sizeof(&a));//4/8
// &a取出的数组的地址,数组的地址,也就是个地址
printf("%d\n", sizeof(*&a));//16
// sizeof(*&a) 表示的是整个数组的大小
// &a----> int(*)[4]
// &a拿到的是数组名的地址,类型是 int(*)[4],是一种数组指针
// 数组指针解引用找到的是数组
// *&a ---> a
//
// 2.
// &和*抵消了
// *&a ---> a
printf("%d\n", sizeof(&a + 1));//4/8
// &a取出的是数组的地址
// &a--> int(*)[4]
// &a+1 是从数组a的地址向后跳过了一个数组大小的字节(整个数组)
// &a+1还是地址,是地址就是4/8字节
printf("%d\n", sizeof(&a[0]));//4/8
// &a[0]就是第一个元素的地址
// 计算的是地址的大小
printf("%d\n", sizeof(&a[0] + 1));//4/8
// &a[0]+1是第二个元素的地址
// 大小是4/8个字节
// &a[0]+1 ---> &a[1]
return 0;
}
2.字符数组
- 在做题之前,先给大家一些小tips
- 首先了解strlen()函数
strlen是求字符串长度的,关注的是字符串中的\0,计算的是\0之前出现的字符的个数
strlen是库函数,只针对字符串
sizeof只关注占用内存空间的大小,不在乎内存中放的是什么
sizeof是操作符
32位机器,指针变量是4byte; 64位机器,指针变量是8byte
2.1 char arr[] = {‘a’,‘b’,‘c’,‘d’,‘e’,‘f’};
char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr+0));
printf("%d\n", sizeof(*arr));
printf("%d\n", sizeof(arr[1]));
printf("%d\n", sizeof(&arr));
printf("%d\n", sizeof(&arr+1));
printf("%d\n", sizeof(&arr[0]+1));
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr+0));
printf("%d\n", strlen(*arr));
printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr+1));
printf("%d\n", strlen(&arr[0]+1));
- 解析如下
char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", sizeof(arr)); // 6
// sizeof(数组名), 计算的是整个数组的大小
printf("%d\n", sizeof(arr+0)); // 4/8;
// arr为首元素的地址,arr+0也是首元素的地址
printf("%d\n", sizeof(*arr));// 1
// arr <==> &a[0]
// *arr <==> *(&a[0]) <==> a[0] ==> 'a'
printf("%d\n", sizeof(arr[1]));// 1
// arr[1] <==> 'b'
printf("%d\n", sizeof(&arr));// 4/8
// &arr就是整个数组的地址,也就是一个地址
printf("%d\n", sizeof(&arr+1));// 4/8
// &arr+1 就是跳过整个数组之后的地址
printf("%d\n", sizeof(&arr[0]+1));// 4/8
// &arr[0]是首元素的地址,即a的地址; &arr[0]+1 是首元素地址跳过一个元素之后的地址,即b的地址
========================================================================================// strlen
printf("%d\n", strlen(arr));// 随机值
// arr即首元素的地址;
// strlen(arr) 是求首元素地址之后字符串的长度,直到'\0'中止
// (a,b,c,d,e,f 还有其他的字符等,直到遇到'\0'中止)
printf("%d\n", strlen(arr+0));// 随机值
// arr+0也是首元素的地址
printf("%d\n", strlen(*arr));
// *arr 即'a';
// strlen(*arr) ==> strlen('a') ==> strlen(97) ;所以为野指针,是错误的
printf("%d\n", strlen(arr[1]));
// arr[1] 即'b'; 同理也为野指针
printf("%d\n", strlen(&arr));/ / 随机值
// &arr 是数组的地址,数组的地址也在首元素的地址处
printf("%d\n", strlen(&arr+1)); // 随机值
// &arr+1 跳过整个数组之后的地址
printf("%d\n", strlen(&arr[0]+1));// 随机值
// &arr[0]+1 第一个元素的地址跳过一个元素之后的地址
2.2 char arr[] = " abcdef ";
char arr[] = "abcdef";
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr+0));
printf("%d\n", sizeof(*arr));
printf("%d\n", sizeof(arr[1]));
printf("%d\n", sizeof(&arr));
printf("%d\n", sizeof(&arr+1));
printf("%d\n", sizeof(&arr[0]+1));
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr+0));
printf("%d\n", strlen(*arr));
printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr+1));
printf("%d\n", strlen(&arr[0]+1));
- 以上代码的解析如下
char arr[] = "abcdef";[a b c d e f \0]
printf("%d\n", sizeof(arr)); // 7
// sizeof(arr) 计算的是整个数组的大小; 字符串"abcdef",其实是7个字符,还有一个隐藏的\0
printf("%d\n", sizeof(arr+0));// 4/8
// arr+0 为首元素的地址
printf("%d\n", sizeof(*arr));// 1
// *arr <==> 'a'
printf("%d\n", sizeof(arr[1]));// 1
// arr[1] <==> 'b'
printf("%d\n", sizeof(&arr));// 4/8
// &arr 数组的地址
printf("%d\n", sizeof(&arr+1));// 4/8
// &arr+1 跳过整个数组之后的地址
printf("%d\n", sizeof(&arr[0]+1));// 4/8
// &arr[0]+1 首元素地址跳过一个元素之后的地址
========================================================================================// strlen
printf("%d\n", strlen(arr));// 6
// 首元素地址之后的字符串个数,以'\0'结束
printf("%d\n", strlen(arr+0));// 6
// 首元素地址之后的字符串个数,以'\0'结束
printf("%d\n", strlen(*arr));
// *arr <==> 'a'; 所以为野指针
printf("%d\n", strlen(arr[1]));
// arr[1] <==> 'b'; 野指针
printf("%d\n", strlen(&arr));// 6
// &arr 数组的地址,数组的地址也是从首元素开始
printf("%d\n", strlen(&arr+1));// 随机值
// &arr+1 跳过整个数组的地址,求这个地址之后字符的个数,遇到'\0'结束
printf("%d\n", strlen(&arr[0]+1));// 5
// &arr[0]+1 即'b'的地址;
2.3 char *p = “abcdef”;("abcdef"储存在常量区)
char *p = "abcdef";
printf("%d\n", sizeof(p));
printf("%d\n", sizeof(p+1));
printf("%d\n", sizeof(*p));
printf("%d\n", sizeof(p[0]));
printf("%d\n", sizeof(&p));
printf("%d\n", sizeof(&p+1));
printf("%d\n", sizeof(&p[0]+1));
printf("%d\n", strlen(p));
printf("%d\n", strlen(p+1));
printf("%d\n", strlen(*p));
printf("%d\n", strlen(p[0]));
printf("%d\n", strlen(&p));
printf("%d\n", strlen(&p+1));
printf("%d\n", strlen(&p[0]+1));
- 以上代码的分析如下
char *p = "abcdef";
// p里面存放的是首元素a的地址
printf("%d\n", sizeof(p));// 4/8
// p也就是 char*类型指针变量的大小
printf("%d\n", sizeof(p+1));// 4/8
// p是首元素a的地址; p+1为跳过一个元素之后的地址; *(p+1) <==> 'b'
printf("%d\n", sizeof(*p));// 1
// *p <==> *(p+0) <==> 'a'
printf("%d\n", sizeof(p[0]));// 1
//p[0] <==> *(p+0) <==> 'a'
printf("%d\n", sizeof(&p));// 4/8
// &p是指针变量的地址
printf("%d\n", sizeof(&p+1));// 4/8
// &p+1 即:跳过4字节之后的地址,即跳过元素p之后的地址;
printf("%d\n", sizeof(&p[0]+1));// 4/8
// &p[0]+1 即首元素的地址跳过一个元素之后的地址
========================================================================================// strlen
printf("%d\n", strlen(p));// 6
// p也就是首元素的地址(计算的是p指向的字符串中的字符个数,到\0终止)
printf("%d\n", strlen(p+1));// 5
// p+1 首元素地址跳过一个元素之后的地址,即'b'的地址
// 因为p是char*类型,所以 p+1 就是跳过一个字节
printf("%d\n", strlen(*p));err(野指针)
// *p <==> 'a';
printf("%d\n", strlen(p[0]));err(野指针)
// p[0] <==> 'a';
printf("%d\n", strlen(&p));// 随机值
// &p 指的是指针变量p的地址,而不是字符串首元素的地址p
// &p 即p的地址之后的字符长度
printf("%d\n", strlen(&p+1));// 随机值
// &p+1 即:跳过4字节之后的地址,即跳过元素p之后的地址;
printf("%d\n", strlen(&p[0]+1));// 5
// &p[0]+1 即'b'的地址;
3.二维数组
int a[3][4] = {0};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(a[0][0]));
printf("%d\n",sizeof(a[0]));
printf("%d\n",sizeof(a[0]+1));
printf("%d\n",sizeof(*(a[0]+1)));
printf("%d\n",sizeof(a+1));
printf("%d\n",sizeof(*(a+1)));
printf("%d\n",sizeof(&a[0]+1));
printf("%d\n",sizeof(*(&a[0]+1)));
printf("%d\n",sizeof(*a));
printf("%d\n",sizeof(a[3]));
- 以上代码的分析如下
int main()
{
int a[3][4] = { 0 };
printf("%d\n", sizeof(a)); // 48
// sizeof(数组名), 表示的是整个数组的大小(整个数组一共有12个元素,每个元素为4个字节,因此共48字节)
printf("%d\n", sizeof(a[0][0])); // 4
// a[0][0] 表示的是第一行第一个元素
printf("%d\n", sizeof(a[0]));// 16
// a[0] ==> *(a+0);
// a是首行元素的地址(二维数组的数组名就是首行的地址),
// *(a+0)也是首行的地址,也是首行首元素的地址,两个地址是相等的
// a[0]如果单独使用,那么是整个第一行的大小,如sizeof(a[0])
// 不单独使用就是 计算的就是首行首元素的地址的大小
// &a[0],也就是第一行数组地址(数组的地址的大小就是4字节)
// 通过如下代码进行验证
// printf("%d\n", sizeof(&a[0])); // 没有单独使用a[0],打印结果为4
// printf("%d\n", sizeof(a[0])); // 打印结果为16
// printf("%d\n", sizeof(*(a+0))); // 打印结果为16
// printf("%d\n", sizeof(a[0]+0)); // 没有单独使用a[0],打印结果为4
printf("%d\n", sizeof(a[0] + 1)); // 4/8
// a[0] + 1 ==> *(a+0) + 1
// 当a[0]在sizeof()中不单独使用时,a[0]就是首行首元素的地址
// a[0] + 1就是第一行第二个元素的地址
printf("%d\n", sizeof(*(a[0] + 1)));// 4
//a[0] + 1就是第一行第二个元素的地址
//*(a[0] + 1))就是第一行第二个元素
printf("%d\n", sizeof(a + 1));//4/8
// a虽然是二维数组的地址,但是并没有单独放在sizeof内部,也没取地址
// 当a单独放在sizeof中,或者是&a放在sizeof中,都是整个数组的大小
// 一维数组a表示首元素的地址,二维数组的首元素是它的第一行,a就是第一行的地址
// a+1就是跳过第一行,表示第二行的地址(此时a没有单独使用就是首行的地址)
printf("%d\n", sizeof(*(a + 1))); // 16
// a二维数组的数组名,也就是首行元素的地址
// *(a + 1)是对第二行数组地址的解引用,拿到的是第二行(a + 1 指向的是整个数组,解引用后,拿到的是整个数组的数据)
// a+1的类型是 int (*)[4],解引用后是数组 int [4]
// *(a+1)-->a[1];a[1]也就是第二行数组的数组名
// sizeof(*(a+1))-->sizeof(a[1])
printf("%d\n", sizeof(&a[0] + 1)); //4/8
// *(a + 0) ==> a[0] ,单独使用就是首行的地址,不单独使用就是首行首元素的地址
// &a[0] 对第一行的数组名取地址,拿出的是第一行的地址
// &a[0]+1 得到的是第二行的地址
printf("%d\n", sizeof(*(&a[0] + 1))); //16
// &a[0] 是第一行数组的地址, &a[0]+1第二行数组的地址,类型是 int (*)[4]
// *(&a[0] + 1)就是对第二行地址的解引用,拿到的就是整个第二行的整个数组,类型为 int [4]
printf("%d\n", sizeof(*a));//16
// a表示首元素的地址,就是第一行的地址, 类型是 int (*)[4]
// *a就是对第一行地址的解引用,拿到的就是第一行,类型为 int [4]
printf("%d\n", sizeof(a[3]));//16
// 并没有访问a[3],所以没有越界
// sizeof(a[3]); 只是分析a[3]类型的大小
// 所以 sizeof(a[3]) 的大小相当于 sizeof(a[0])
//int a = 10;
//sizeof(int);
//sizeof(a);
return 0;
}
总结:
数组名的意义:
1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。
2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
3. 除此之外所有的数组名都表示首元素的地址。
4.指针笔试题
笔试题1:
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
int *ptr = (int *)(&a + 1);
printf( "%d,%d", *(a + 1), *(ptr - 1));
// 注:ptr - 1,是跳过4个字节,因为ptr的类型是int*
// &a + 1是跳过20个字节,因为&a的类型是int(*)[5]
return 0;
}
//程序的结果是什么?
- 解析如下:
&a 是数组的地址,类型为 int(*)[5];
&a+1 是数组的地址跳过整个数组的地址,类型仍为int(*)[5]
所以*(ptr - 1) 打印的整型是5;
*(a + 1) 打印的整型是2
笔试题2:
//由于还没学习结构体,这里告知结构体的大小是20个字节
struct Test
{
int Num;
char *pcName;
short sDate;
char cha[2];
short sBa[4];
}*p; // p为结构体的指针变量 即 struct test *p
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
printf("%p\n", p + 0x1);
printf("%p\n", (unsigned long)p + 0x1);
printf("%p\n", (unsigned int*)p + 0x1);
return 0;
}
- 解析如下:
struct Test
{
int Num;
char* pcName;
short sDate;
char cha[2];
short sBa[4];
}* p = (struct Test*)0x100000;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节(在x86环境下)
int main()
{
printf("%p\n", p + 0x1);// 指针变量+1,跳过test类型变量大小的字节
//0x100000+20-->0x100014
printf("%p\n", (unsigned long)p + 0x1);// 整型+1, 将地址转化为整型+1;
// 0x100000变为10进制数 --> 1,048,576
// 1,048,576+1 --> 1,048,577
// 0x100001
printf("%p\n", (unsigned int*)p + 0x1);
// 强转为无符号整型的指针变量,所以指针变量+1,跳过4byte
//0x100000+4-->0x100004
return 0;
}
笔试题3
int main()
{
int a[4] = { 1, 2, 3, 4 };
int *ptr1 = (int *)(&a + 1);
int *ptr2 = (int *)((int)a + 1);
printf( "%x,%x", ptr1[-1], *ptr2);
return 0;
}
- 解析如下:
int main()
{
int a[4] = { 1, 2, 3, 4 };
int *ptr1 = (int *)(&a + 1);
// &a是数组的地址,&a+1 的类型是int(*)[4], &a+1 是数组的地址跳过整个数组之后的地址,
// ptr1[-1] <==> *(ptr - 1); 所以 ptr1[-1]打印的结果是数组元素4
int *ptr2 = (int *)((int)a + 1);
// a为首元素的地址,类型是 int*,将a强转为int类型
// 此时(int)a + 1 就是向高地址前进了一个字节
// *ptr2 打印的结果为 2000000
printf( "%x,%x", ptr1[-1], *ptr2);
// %x 表示用16进制打印输出
return 0; }
笔试题4:
#include <stdio.h>
int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
// 注:二维数组的初始化,只有用大括号括起来的才是对每一行的初始化
// 这里用的是小括号,其实是逗号表达式
// 逗号表达式,从左向右依次执行,最后一个表达式的结果是整个表达式的结果
// 如:(0, 1),第一个表达式的结果为0,第二个表达式的结果为1,所以这个逗号表达式的结果为1
// 即int a[3][2] ={1,3,5};
int *p;
p = a[0];
printf( "%d", p[0]);
return 0;
}
- 解析如下:
笔试题5
int main()
{
int a[5][5];
int(*p)[4]; // 数组指针
p = a;
printf( "%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
// 对于&p[4][2],因为指针p的类型是int(*p)[4],所以p+1每次是跳过4个int
// 而对于&a[4][2],类型是int(*)[5],每次是跳过5个int
return 0;
}
// 打印结果是:FFFFFFFC, -4
- 解析如下:
笔试题6
int main()
{
int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int *ptr1 = (int *)(&aa + 1);
int *ptr2 = (int *)(*(aa + 1));
printf( "%d,%d", *(ptr1 - 1), *(ptr2 - 1));
return 0;
}
- 解析如下:
int main()
{
int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int *ptr1 = (int *)(&aa + 1);
// &aa 是整个二维数组的地址,&aa+1 是跳过整个二维数组之后的地址
int *ptr2 = (int *)(*(aa + 1));
// aa 是二维数组的数组名,就是首元素的地址,也就是二维数组第一行的地址
// aa+1 是二维数组第二行的地址
// *(aa + 1) <==> aa[1] 也就是第二行首元素的地址
printf( "%d,%d", *(ptr1 - 1), *(ptr2 - 1));
// 因为ptr1 , ptr2 的类型为int* 所以解引用访问4byte
// 所以*(ptr1 - 1) 打印的结果是10
// *(ptr2 - 1) 打印的结果是5
return 0;
}
第一行: 1 2 3 4 5
第二行: 6 7 8 9 10
笔试题7
#include <stdio.h>
int main()
{
char *a[] = {"work","at","alibaba"};
char** pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
- 解析如下:
因此最终打印的值为 at
笔试题8
int main()
{
char *c[] = {"ENTER","NEW","POINT","FIRST"};
char** cp[] = {c+3,c+2,c+1,c};
char*** cpp = cp;
printf("%s\n", **++cpp);
printf("%s\n", *--*++cpp+3);
printf("%s\n", *cpp[-2]+3);
printf("%s\n", cpp[-1][-1]+1);
return 0;
}
// 打印结果:
POINT
ER
ST
EW
- 解析如下
// 注:操作数两侧的操作符进行优先级的比较
// 前置++ 的优先级高于 +;