目录
- [1. sizeof和strlen的对比](#1. sizeof和strlen的对比)
-
- 1.1sizeof
- [1.2 strlen](#1.2 strlen)
- [1.3 sizeof 和strlen的对比](#1.3 sizeof 和strlen的对比)
- [2. 数组和指针笔试题解析](#2. 数组和指针笔试题解析)
-
- [2.1 ⼀维数组](#2.1 ⼀维数组)
- [2.2 字符数组](#2.2 字符数组)
- [2.3 ⼆维数组](#2.3 ⼆维数组)
- [3. 指针运算笔试题解析](#3. 指针运算笔试题解析)
-
- [3.1 题⽬1:](#3.1 题⽬1:)
- [3.2 题⽬2](#3.2 题⽬2)
- [3.3 题⽬3](#3.3 题⽬3)
- [3.4 题⽬4](#3.4 题⽬4)
- [3.5 题⽬5](#3.5 题⽬5)
- [3.6 题⽬6](#3.6 题⽬6)
- [3.7 题⽬7](#3.7 题⽬7)
1. sizeof和strlen的对比
1.1sizeof
前面我们已经知道了sizeof是运算符(operator),用来计算操作数所占内存空间的大小,单位是字节。如果操作数是数据类型的话,计算的就是类型所创建的变量占内存空间的大小。
注意 :sizeof仅计算内存占用空间的大小,不关心存储的具体数据内容。sizeof仅计算内存占用空间的大小,不关心存储的具体数据内容。
d
代码1:
c
int main()
{
int a = 2;
int b = 3;
double c = 3.4;
int d = 0;
printf("%zu\n", sizeof a);//4
printf("%zu\n", sizeof (a));//4
//这里注意如果操作数是表达式就需要添加括号
//因为sizeof的优先级小于运算符,所以会出现先计算所占内存的大小再进行运算的情况
printf("%zu\n", sizeof a+b);//7
printf("%zu\n", sizeof (d=a+b));//4
printf("%d", d);//sizeof(d=a+b)操作数不会被计算,依然是0
//如果是不同类型的数据计算,一般是小类型自动转大类型,最终的结果的类型是按照大类型算的
printf("%zu\n", sizeof (a+c));//8
return 0;
}
在代码1中,操作数如果是表达式,一般是用括号把表达式括起来。根据优先级来说sizeof的优先级小于算数运算符的,如果不用括号的话那么就会先计算内存大小,再进行算数运算。比如printf("%zu\n", sizeof a+b);//7这个代码就是先计算变量a的内存大小,再和变量b进行加法运算,得出的结果是7。
sizof的操作数是表达式的时候,仅仅进行类型推断,而不会执行该表达式。printf("%zu\n", sizeof (d=a+b));
printf("%d", d);
例如上述两行代码,sizeof运算符不会对其操作数进行实际计算,也不会执行赋值操作。这意味着a+b的结果不会被赋给d,sizeof仅进行类型推断。由于a+b的结果类型为int,第一行代码输出4(即int类型的大小),而第二行代码输出变量d的初始值0。
这里值得注意一下,如果表达式的数据类型是不一样的话,编译器会自动进行隐式转换,将较小的类型自动转为较大的类型。比如整型提升就是将小于int的类型自动转为int。整型提升完了之后与另一个数据的类型比较,如果另一个数据的类型大于int,编译器就进行普通算数转换,即自动转为大于int的类型。比如char+double,先对char整型提升,即int整型提升之后与double比较,明显double范围更大,就进行普通算数转换,将int转为double,然后再计算,结果的类型就是double即范围更大的类型。
可以用sizeof运算符来验证:printf("%zu\n", sizeof (a+c));这里变量a的类型是int,变量c的类型是double,结果输出就是8。
1.2 strlen
strlen是C语⾔库函数,功能是求字符串⻓度。函数原型如下:
c
size_t strlen(const char* str)
从原型也可以看出来函数strlen的操作数只能是字符串或字符,求字符串的字符个数。
strlen统计的是\0之前字符的个数,指针会一直向后查找直到遇见\0,所以在特定情况可能会越界查找。
代码2:
c
int main()
{
char arr1[] = { 'a','c','d' };
char arr2[] = "acd";//字符串默认以\0结尾
printf("arr1:%zu\n", strlen(arr1));//出现意料不到的值
printf("arr2:%zu\n", strlen(arr2));//3
return 0;
}
arr1因为后面没有\0,所以用strlen算出来的值我们不知道。
arr2因为字符串默认以\0结尾,所以输出的是3。
如果数组arr1在末尾出手动添加\0,输出的结果就也是3,因为strlen执行的逻辑就是遇到\0就停止。比如这个数组char arr[] = "ac\0d";,因为字符串中有一个\0所以统计的时候就是统计\0之前的字符个数,即为2。
1.3 sizeof 和strlen的对比
sizeof是运算符,计算操作数所占内存的大小,不关心操作数具体的内容(赋值还是自增)单位是字节。
strlen是函数,使用时要包含头文件#include<string.h>,一般的操作数是字符串,求字符串的个数,关注字符串的\0位置,如果没有\0就会一直往后找,可能会越界。
代码3:
c
int main()
{
char arr1[] = { 'a','c','d' };
char arr2[] = "acd";//字符串默认以\0结尾
printf("arr1:%zu\n", strlen(arr1));//出现意料不到的值
printf("arr2:%zu\n", strlen(arr2));//3
printf("arr1:%zu\n", sizeof arr1);//3
printf("arr2:%zu\n", sizeof arr2);//4,包括\0
return 0;
}
用sizeof计算字符串所占内存的时候,是包括\0的。
2. 数组和指针笔试题解析
数组名的意义:
- sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。
- &数组名,这⾥的数组名表⽰整个数组,取出的是整个数组的地址。
- 除此之外所有的数组名都表⽰⾸元素的地址
2.1 ⼀维数组
c
int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));//只有一个单独的数组名,计算的是整个数组的大小,16
printf("%d\n",sizeof(a+0));//数组名a自动退化成指针,指向的是数组起始元素的地址,+0表示指针偏移0个元素,是指针的话内存就是4/8
printf("%d\n",sizeof(*a));//对指向数组起始元素的指针解引用,计算的是元素的大小,4
printf("%d\n",sizeof(a+1));//+1表示指针偏移一个元素,还是指针,4/8
printf("%d\n",sizeof(a[1]));//求的是下标为1的元素所占的内存大小,4
printf("%d\n",sizeof(&a));//取地址操作,取出整个数组的地址,类型也是指针,4/8
printf("%d\n",sizeof(*&a));//先取出整个数组的地址,再对整个数组的地址解引用,求的是整个数组所占内存大小,16
printf("%d\n",sizeof(&a+1));//先取出整个地址,再+1跳过整个数组地址,类型还是指针,4/8
printf("%d\n",sizeof(&a[0]));//这里是取出下标为0的元素地址,类型是指针,4/8
printf("%d\n",sizeof(&a[0]+1));//从下标为0的元素地址开始向右偏移4个字节(1个整型元素),4/8
2.2 字符数组
c
int main()
{
char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", sizeof(arr));//求的是整个数组所占内存大小,6
printf("%d\n", sizeof(arr+0));//数组名退化成指向起始元素的指针,+0表示指针向右偏移0字节,类型是指针,求的是指针所占内存的大小,4/8
printf("%d\n", sizeof(*arr));//对指向起始元素的指针解引用,得到起始元素,1
printf("%d\n", sizeof(arr[1]));//求的是下标为1的元素所占内存,1
printf("%d\n", sizeof(&arr));//取整个数组的地址,是指针类型,4/8
printf("%d\n", sizeof(&arr+1));//先取出整个数组的地址,+1跳过了整个数组,越界访问,是指针,4/8
printf("%d\n", sizeof(&arr[0]+1));//取出下标为0的元素地址,跳过一个字节(char),4/8
return 0;
}
c
int main()
{
char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", strlen(arr));//这个字符数组末尾不是默认\0结尾,未知
printf("%d\n", strlen(arr+0));//arr+0表示指向数组起始元素的地址偏移0个字节所得到的地址,是指针,数组内部没有\0,未知
printf("%d\n", strlen(*arr));//对指向起始元素的指针解引用得到'a',表示97,strlen会把97当作地址看待,向后统计字符个数,属于非法访问,程序会崩溃
printf("%d\n", strlen(arr[1]));//'b'---98,程序一样会崩溃
printf("%d\n", strlen(&arr));//表示整个数组的地址的指针char(*)[],类型不匹配,未知,x
printf("%d\n", strlen(&arr+1));//x-6
printf("%d\n", strlen(&arr[0]+1));x-1
return 0;
}
c
int main()
{
char arr[] = "abcdef";
printf("%d\n", sizeof(arr));//求的是整个字符串所占的内存,包括\0,7
printf("%d\n", sizeof(arr+0));//指向起始元素的指针,4/8
printf("%d\n", sizeof(*arr));//解引用得到起始元素,求起始元素所占的内存,1
printf("%d\n", sizeof(arr[1]));//下标为1的元素所占的内存,1
printf("%d\n", sizeof(&arr));//指针,4/8
printf("%d\n", sizeof(&arr+1));//指针类型为char[],+1跳过整个数组,4/8
printf("%d\n", sizeof(&arr[0]+1));//从指向起始元素的指针跳过一个元素大小所得到的指针,4/8
return 0;
}
c
int main()
{
char arr[] = "abcdef";
printf("%d\n", strlen(arr));//求字符串的长度,末尾有\0,6
printf("%d\n", strlen(arr+0));//从指向起始元素地址的指针开始往后统计字符个数,遇到\0停止,6
printf("%d\n", strlen(*arr));//解引用得到的是'a'--97,strlen会把97当作地址访问统计,非法访问程序会崩溃
printf("%d\n", strlen(arr[1]));//解引用得到'b',非法访问,程序崩溃
printf("%d\n", strlen(&arr));//取出的是整个地址char(*)[],类型不匹配,未知
printf("%d\n", strlen(&arr+1));//类型不匹配,未知
printf("%d\n", strlen(&arr[0]+1));//从指向第二个元素的指针向后统计,5
return 0;
}
c
int main()
{
char *p = "abcdef";
printf("%d\n", sizeof(p));//求指针所占的内存,4/8
printf("%d\n", sizeof(p+1));//一样的指针,4/8
printf("%d\n", sizeof(*p));//对指向起始字符的指针解引用,1
printf("%d\n", sizeof(p[0]));//1
printf("%d\n", sizeof(&p));//取出的是整个字符串的地址,4/8
printf("%d\n", sizeof(&p+1));//4/8
printf("%d\n", sizeof(&p[0]+1));//从起始元素往后跳过一个元素,4/8
return 0;
}
c
int main()
{
char *p = "abcdef";
printf("%d\n", strlen(p));//从第一个元素开始统计字符个数,6
printf("%d\n", strlen(p+1));//从第二个元素开始,5
printf("%d\n", strlen(*p));//解引用得到'a'--97,非法访问地址,崩溃
printf("%d\n", strlen(p[0]));//程序崩溃
printf("%d\n", strlen(&p));//指向整个字符串指针的指针,二级指针,指向的是指针p所占的内存空间,与字符串没有了任何关系,随机值
printf("%d\n", strlen(&p+1));//表示指向指针p所在的空间的指针跳过一个指针然后向后统计,随机值
printf("%d\n", strlen(&p[0]+1));//指向下标为0的元素的指针往后移动一个元素所得到的指针,然后向后统计,5
return 0;
}
2.3 ⼆维数组
c
int main()
{
int a[3][4] = {0};
printf("%d\n",sizeof(a));//二维数组名在sizeof中表示整个数组,48
printf("%d\n",sizeof(a[0][0]));//表示下标为0的一维数组中下标为零的元素的内存,4
printf("%d\n",sizeof(a[0]));//下标为0的一维数组名,单独存在,代表这个一维数组的全体,16
printf("%d\n",sizeof(a[0]+1));//指针从指向下标为0的一维数组跳过一个数组的内存空间,表示指针的偏移,还是指针,4/8
printf("%d\n",sizeof(*(a[0]+1)));//跳到下标为1的一维数组后,其数组名自动退化为指向起始元素的指针,解引用得到的是int类型的数据,4
printf("%d\n",sizeof(a+1));//二维数组名本身退化成指向起始一维数组的指针,+1就是指针跳过一个一维数组的内存空间,结果的类型还是指针,4/8
printf("%d\n",sizeof(*(a+1)));//指针先从起始一维数组跳过一个一维数组的内存空间,然后对指向这块空间的指针解引用,得到的类型是退化了的一维数组int(*)[4],16
printf("%d\n",sizeof(&a[0]+1));//先对退化成指向起始一维数组的指针解引用,然后再取出整个一维数组的地址,代表指向第二行一维数组的指针,类型是指针int(*)[4],加1指针跳过16个字节,4/8
printf("%d\n",sizeof(*(&a[0]+1)));//对已经偏移的指针解引用,得到的是退化了的指向新一维数组起始元素的指针,但在sizeof里表示整个数组,16
printf("%d\n",sizeof(*a));//对指向起始一维数组的指针解引用,得到指向的一维数组名,代表整个一维数组,16
printf("%d\n",sizeof(a[3]));//下标为3的一维数组的内存空间,16
return 0;
}
3. 指针运算笔试题解析
3.1 题⽬1:
c
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
int *ptr = (int *)(&a + 1);
printf( "%d,%d", *(a + 1), *(ptr - 1));
return 0;
}
//
程序的结果是什么?
分析这段代码:int *ptr = (int *)(&a + 1);。&a表示取出整个数组的地址,类型是一维数组指针int(*)[5],&a+1就是指向一维数组的指针向后偏移20个字节(相当于跳过了整个一维数组),此时指针类型还是int(*)[5]。(int*)是将int(*)[5]强制类型转化成int*,然后存储到指针ptr中。
printf( "%d,%d", *(a + 1), *(ptr - 1));中,*(a+1)表示指向起始元素的指针向后偏移4个字节(因为数组名a表示退化的指针,指向起始元素,类型是int*,所以+1能跳过四个字节),然后对偏移后指向的新内存空间一次性访问4个字节。解引用得到的是int类型的数据2。
*(ptr - 1)中,ptr是指向这个数组末尾的指针,类型是int*,*(ptr - 1)则是指针先向前偏移4个字节,然后解引用偏移后所指向的内存空间,一次访问4个字节。输出是5。这里再次强调,指针的类型决定了指针+1能跳过多少字节和解引用能访问多少个字节 。

3.2 题⽬2
c
//在X86环境下
//假设结构体的⼤⼩是20个字节
//程序输出的结果是啥?
struct Test
{
int Num;
char *pcName;
short sDate;
char cha[2];
short sBa[4];
}*p = (struct Test*)0x100000;
int main()
{
printf("%p\n", p + 0x1);
printf("%p\n", (unsigned long)p + 0x1);
printf("%p\n", (unsigned int*)p + 0x1);
return 0;
}
这里*p表示变量p是一个指针,指针类型是结构体指针,(struct Test*)0x100000就是将一个整型数据强制类型转化成struct Test*,作为地址赋值给指针p。
printf("%p\n", p + 0x1),p是一个结构体指针,+1就是跳过一个结构体大小的字节,可以知道一个结构体变量的大小是20个字节,在这里0x表示十六进制,所以p+0x1就是指针跳在0x100000+0x14的地址中,输出0x100014
printf("%p\n", (unsigned long)p + 0x1); printf("%p\n", (unsigned int*)p + 0x1);
第一行代码将指针类型(struct Test*)强制类型转化为unsigned long类型,这个变量已经不是指针了而是看成普通的整数,所以输出就是0x100000+0x1即0x100001
第二行代码是将(struct Test*)强转成(unsigned int*),还是指针,只不过是不同类型的指针,+1可以跳过4个字节,所以输出0x100000+0x4即0x100004
3.3 题⽬3
c
int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
int *p;
p = a[0];
printf( "%d", p[0]);
return 0;
}
这里要特别注意这个逗号表达式( , ),这个逗号表达式的结果是末尾的数字,所以数组可以进一步化简:int a[3][2]={1,3,5};余下未初始化的部分默认是0。
a[0]表示下标为0的一维数组名,退化成指向起始元素的指针,类型是int *。所以输出是1
3.4 题⽬4
c
//假设环境是x86,程序输出的结果是啥?
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]);
return 0;
}
这里要知道指针p的类型是int(*)[4],数组名a的类型是int(*)[5]。
p=a可以理解将类型为int(*)[5]的数据解释成类型为int(*)[4]的数据,但是数组a指向的内存空间是不变的。改变了指针的类型就是改变指针的步长和解引用所访问的字节数。

&p[4][2] - &a[4][2]相减得到的是-4,但打印格式不一样,%p格式打印出来的是-4在内存的补码再转成十六进制,%d打印的就是-4。%p会把你给的数当成内存地址打印,按照十六进制,无符号整数打印,-4在内存中是以补码形式存储的:11111111 11111111 11111111 11111100转成十六进制就是fffffffc。%d就是按照有符号十进制整数打印。

3.5 题⽬5
c
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;
}
指针ptr1的类型是int*,指向起始指针跳过整个二维数组所指向的位置。
指针ptr2的类型是int*,*(aa + 1)表示起始指针跳过一个一维数组后指向的指针,然后解引用得到第二行起始元素的地址。

对ptr1 - 1,ptr2 - 1解引用得到10 和 5.

3.6 题⽬6
c
int main()
{
char *a[] = {"work","at","alibaba"};
char**pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
这里变量a是字符数组变量,存储的元素类型是char*,是指针。
数组的元素是字符串,我们知道字符串可以用char*接收,表示字符串首个字符的地址。因此单个数组名a表示指向起始元素的指针,这里是指向字符串"work"的指针。这里因为起始元素也是指针,所以数组名a的类型就是二级指针(指向指针的指针就是二级指针)。将a的指向的地址赋值给二级指针pa,char**也是指针,指向的对象类型是char*,所以pa++表示的是指针pa跳过一个指针的长度,也就指向了"at"这块内存空间。printf("%s\n", *pa);输出的就是at。

3.7 题⽬7
c
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;
}
- 变量
c是一个指针数组,存储四个字符串的地址。数组名初始指向c[0]="ENTER" - 变量
cp是一个二级指针数组,存储的是c的元素地址和关于指针c偏移之后的元素地址。数组名初始指向cp[0]=c+3 cpp是三级指针,初始指向cp[0]。
第一个printf:
printf("%s\n", **++cpp);
++cpp:cpp从指向cp[0]变成指向cp[1].*++cpp:解引用拿出cp[1]的值,即c+2**++cpp:解引用c+2指向的对象,即取出c[2]的值,即"POINT"
输出"POINT"
第二个printf
printf("%s\n", *--*++cpp+3);
++cpp:cpp从指向cp[1]变为指向cp[2]*++cpp:取出cp[2]的值,即c+1--*++cpp:c+1自减1,即变成c*--*++cpp:取出c指向的对象,即c[0]*--*++cpp+3:从字符串"ENTER"的第三个字符(索引从0开始)开始输出,即"ER"
输出"ER"
第三个printf
printf("%s\n", *cpp[-2]+3);
cpp[-2]:相当于*(cpp-2)(实际上变量cpp本身的值并没有发生变化,只是进行了下标引用操作),cpp从指向cp[2]变成指向cp[0],然后解引用拿出cp[0]的值,即c+3*cpp[-2]:解引用拿出c+3的值,即c[3],"FIRST"*cpp[-2]+3:从字符串"FIRST"的第三个字符开始输出,即"ST"
输出"ST"
注意 :此时cpp的初始状态仍然不变,即cpp=&cp[2]
第四个printf
printf("%s\n", cpp[-1][-1]+1);
cpp[-1]:即*(cpp-1),cpp从指向cp[2]变成指向cp[1],再接着解引用得到c+2,即c[2]cpp[-1][-1]:即*(cpp[-1]-1),c[2]-1变成c[1],指向的是"NEW",解引用得到字符串。cpp[-1][-1]+1指针后移一位,即从第一个指针开始输出,为EW
输出:"EW"

