目录
一、sizeof和strlen的对比
1、sizeof
2、strlen
3、sizeof 和 strlen的对比
二、数组和指针笔试题解析
1、⼀维数组
重点学习代码:sizeof与一维整型数组类型
2、字符数组
代码1:sizeof与字符数组类型
代码2:strlen与字符数组类型
代码3:sizeof与字符串数组类型
代码4:strlen与字符串数组类型
代码5:sizeof与字符指针数组类型
代码6:strlen与字符指针数组类型
3、二维数组
三、指针运算笔试题解析
1、题目一
2、题目二
3、题目三
4、题目四
5、题目五
6、题目六
7、题目七
一、sizeof和strlen的对比
1、sizeof
- sizeof 可以计算变量所占内存的内存空间大小,单位是字节,它是一个操作符,不是函数。
- 如果操作数是类型的话,则计算的是使用类型创建的变量所占内存空间的大小。
- sizeof 只关注占用内存空间的大小,不在乎内存中存放什么数据。
#inculde <stdio.h>
int main()
{
int a = 10;
printf("%d\n", sizeof(a));//4
printf("%d\n", sizeof(int));//4
int arr[10] = { 100, 200};
printf("%zd\n",sizeof(arr));//40
return 0;
}
2、strlen
size_t strlen ( const char * str );
重点总结:
- strlen 是C语言库函数,使用需要包含头文件 <string.h>;
- 其功能是求字符串长度,只能针对字符串(字符数组),统计的是字符串中\0之前的字符个数。
- 统计的是从 strlen 函数的参数 str 中这个地址开始向后, \0 之前字符串中字符的个数。strlen 函数会⼀直向后找 \0 字符,直到找到为止,所以可能存在越界查找。
#include <stdio.h>
int main()
{
char arr1[3] = {'a', 'b', 'c'};
char arr2[] = "abc";
printf("%d\n", strlen(arr1));//随机值
printf("%d\n", strlen(arr2));//3
printf("%d\n", sizeof(arr1));//3
printf("%d\n", sizeof(arr2));//4
return 0;
}
3、sizeof 和 strlen的对比
sizeof
|
strlen
|
|
|
二、数组和指针笔试题解析
1、⼀维数组
数组名是数组首元素的地址,但有两个例外:
- 第一,sizeof(数组名),表示整个数组的大小;
- 第二,&数组名,表示的是整个数组,取出的是数组的地址。
重点学习代码:sizeof与一维整型数组类型
#include <stdio.h>
#include <string.h>
int main()
{
int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));//16
//数组名a单独放在sizeof内部,a表示整个数组,计算的是整个数组的大小,单位是字节
printf("%d\n",sizeof(a+0));
//这里的a是数组名表示首元素的地址,a+0还是首元素的地址
//这里sizeof计算的是首元素地址的大小 4/8
printf("%d\n",sizeof(*a));//4
//这里的a是数组名表示首元素的地址,*a 就是首元素,就是a[0]
//*a--*(a+0)-- a[0]
printf("%d\n",sizeof(a+1));
//这里的a是数组名表示首元素的地址,a+1是第二个元素的地址(&a[1])
//计算的是地址的大小 4/8
printf("%d\n",sizeof(a[1]));//4
printf("%d\n",sizeof(&a));
//&a -这里的数组名a表示整个数组,&a是整个数组的地址
//数组的地址也是地址,是地址就是 4/8 个字节的长度
printf("%d\n",sizeof(*&a));
//1.*&a,这里的*和&抵消了,所以sizeof(*&a)== sizeof(a) 16
//2. &a - 这是数组的地址,类型是:int(*)[4],*&a 访问的就是这个数组
printf("%d\n",sizeof(&a+1));
//&a是数组的地址,&a+1是跳过整个数组后的那个位置的地址
//&a+1是地址,地址都是4/8个字节
printf("%d\n",sizeof(&a[0]));//4/8
printf("%d\n",sizeof(&a[0]+1));//第二个元素的地址 4/8
return 0;
}
2、字符数组
重点学习代码:
代码1:sizeof与字符数组类型
#include <stdio.h>
#include <string.h>
int main()
{
char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", sizeof(arr));
//计算整个数组的大小 6
printf("%d\n", sizeof(arr+0));
//首元素的地址 4/8
printf("%d\n", sizeof(*arr));
//通过首元素地址访问首元素 1
printf("%d\n", sizeof(arr[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));
//首元素的地址+1,得出来的是第二个元素的地址
//4/8
return 0;
}
代码2:strlen与字符数组类型
#include <stdio.h>
#include <string.h>
int main()
{
char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", strlen(arr));
//表示首元素的地址,往后数有多少个元素,直到找到\0
//随机值
printf("%d\n", strlen(arr+0));
//表示首元素的地址,往后数有多少个元素,直到找到\0
//随机值
printf("%d\n", strlen(*arr));
//arr是数组名表示首元素的地址
//*arr 是首元素 ——'a'-97,传递给strlen后,strlen 会认为97就是地址,然后去访问内存
//error -- 程序奔溃
printf("%d\n", strlen(arr[1]));//'b'——98 error -- 程序奔溃
printf("%d\n", strlen(&arr));
//取整个地址名的地址为表示首元素的地址,往后数有多少个元素,直到找到\0
//随机值
printf("%d\n", strlen(&arr+1));
//取整个地址名的地址为表示首元素的地址,加上1即为跳过一个数组,其跳过的长度为6个字符,再往
后数有多少个元素,直到找到\0
//随机值
printf("%d\n", strlen(&arr[0]+1));
//表示首元素的地址,加上1即为跳过一个数组,其跳过的长度为1个字符,也就是到了字符b的位置,再
往后数有多少个元素,直到找到\0
//随机值
return 0;
}
代码3:sizeof与字符串数组类型
#include <stdio.h>
#include <string.h>
//数组名是数组首元素的地址,但有两个例外:
//第一,sizeof(数组名),表示整个数组的大小;第二,&数组名,表示整个数组,取出的是数组的地址。
int main()
{
char arr[] = "abcdef";
printf("%d\n", sizeof(arr));//7
//数组名单独放在sizeof内部,计算的是数组的大小
printf("%d\n", sizeof(arr+0));
//arr+0是数组首元素的地址,大小就是4/8个字节
printf("%d\n", sizeof(*arr));//1
//*arr是首元素,sizeof(*arr)
printf("%d\n", sizeof(arr[1]));
//数组第二个元素的大小 1
printf("%d\n", sizeof(&arr));
//&arr是数组的地址,大小为4/8个字节
printf("%d\n", sizeof(&arr+1));
//&arr是数组的地址,&arr+1跳过了整个数组,指向了数组后面的那个位置
//&arr+1为地址,大小为4/8个字节
printf("%d\n", sizeof(&arr[0]+1));
//&arr[0]是首元素的地址,&arr[0]+1是第二个元素的地址,大小为4/8个字节
return 0;
}
代码4:strlen与字符串数组类型
#include <stdio.h>
#include <string.h>
int main()
{
char arr[] = "abcdef";
printf("%d\n", strlen(arr));//6
//arr 是首元素的地址,strlen是从第一个元素开始统计\0之前的字符的个数
printf("%d\n", strlen(arr+0));//6
//arr 是首元素的地址,arr+0还是首元素的地址
printf("%d\n", strlen(*arr));
//arr 是首元素的地址,*arr是首元素-'a'-97,97作为地址传给给了strlen
//但是97这个地址不能被访问-程序会崩溃
printf("%d\n", strlen(arr[1]));
//同上,字符数组第二个的元素传过去会被当成地址
//error -程序崩溃
printf("%d\n", strlen(&arr));//6
//-=&arr是数组的地址,数组的地址和数组首元素的地址是指向同一个位置的
//那么strlen也是从第一个元素的位置开始向后访问的
printf("%d\n", strlen(&arr+1));
//&arr是数组的地址,&arr+1跳过了整个数组,指向了数组后面的那个位置
//随机值
printf("%d\n", strlen(&arr[0]+1));//5
//&arr[0]是数组首元素的地址,&arr[0]+1跳过了一个元素,指向了数组中的第二个元素
return 0;
}
代码5:sizeof与字符指针数组类型
#include <stdio.h>
#include <string.h>
int main()
{
char *p = "abcdef";
//这段代码的意思是将这个字符串的首地址存到指针变量p中去
printf("%d\n", sizeof(p));
//p是指针变量,计算的是指针变量p的大小,存的是地址,地址大小为4/8个字节
printf("%d\n", sizeof(p+1));
//p为char*类型的指针变量,因此p+1是第二个元素的地址,地址的大小为4/8个字节
printf("%d\n", sizeof(*p));//1
//p的类型是char*,所以*p只能访问1个字节
printf("%d\n", sizeof(p[0]));
//p[0] -->*(0+0)-- * -- 1个字节
printf("%d\n", sizeof(&p));
//&p是指针变量p的地址,也是地址,是地址就是4/8个字节
//&p -- char**二级指针
printf("%d\n", sizeof(&p+1));
//&p是p的地址,&p+1是跳过p变量,指向了p的后边
//&p+1是地址,就是4/8个字节
printf("%d\n", sizeof(&p[0]+1));
//&p[0]+ 1 就是b的地址
//p[0]-- *(p+0)-- *p
return 0;
}
代码6:strlen与字符指针数组类型
#include <stdio.h>
#include <string.h>
int main()
{
char *p = "abcdef";
//这段代码的意思是将这个字符串的首地址存到指针变量p中去
printf("%d\n", strlen(p));//6
//从字符串的首元素的地址开始数元素个数
printf("%d\n", strlen(p+1));//5
//从字符串的首元素的下一个位置的地址开始数元素个数
printf("%d\n", strlen(*p));
//*p —— 'a' —— 97
//97作为地址传给给了strlen
//但是97这个地址不能被访问-程序会崩溃
printf("%d\n", strlen(p[0]));
//p[0] —— *(p+0)
//97作为地址传给给了strlen
//但是97这个地址不能被访问-程序会崩溃
printf("%d\n", strlen(&p));//随机值
//p本来就是一个指针变量,然后又取p的地址,即为二级指针
printf("%d\n", strlen(&p+1));//随机值
//p本来就是一个指针变量,然后又取p的地址,即为二级指针,+1之后也是一个指向下一位置的二级指针
printf("%d\n", strlen(&p[0]+1));//5
//&p[0]为第一个元素的地址,+1之后为第二个元素的地址,往后数直到遇到\0
return 0;
}
3、二维数组
二维数组的直观表示图形:
#include <stdio.h>
int main()
{
int a[3][4] = {0};
printf("%d\n",sizeof(a));//48
//a作为数组名,单独放在sizeof内部了,a表示的是整个数组,计算的是整个数组的大小,单位是字节
printf("%d\n",sizeof(a[0][0]));//4
//a[0][0]是第一行第一个元素,大小是4个字节
printf("%d\n",sizeof(a[0]));//16
//a[0]是第一行的数组名,单独放在sizeof内部,计算的是数组的大小 4*4 = 16个字节
printf("%d\n",sizeof(a[0]+1));
//a[0]是第一行的数组名,但是没有单独放在sizeof内部,
//那么只能是数组首元素的地址,那就是第一行第一个元素的地址 - &a[0][0]
//a[0]+1 == &a[0][0]+1 == &a[0][1]
//地址的大小为4/8个字节
printf("%d\n",sizeof(*(a[0]+1)));
//*(a[0]+ 1)是第一行第二个元素,大小是4个字节
printf("%d\n",sizeof(a+1));
//a是二维数组的数组名,并没有单独放在sizeof内部,a表示首元素的地址-也就是第一行的地址
//a+1 是第二行的地址,是地址大小就是4/8个字节
printf("%d\n",sizeof(*(a+1)));//16
//1.*(a+1)--a[1]--是第二行的数组名,单独放在sizeof内部,计算的是第二行的大小
//2.a+1是第二行的地址,类型是int(*)[4],数组指针,解引用访问的是这个数组,大小是16个字节
printf("%d\n",sizeof(&a[0]+1));//4/8
//a[0]是第1行的数组名,&数组名其实就是第一行的地址,&a[0]+1就是第二行的地址,是地址就是4/8个字节
printf("%d\n",sizeof(*(&a[0]+1)));//16
printf("%d\n",sizeof(*a));//16
//1.a- 首元素的地址(第一行的地址),*a是第一行了
//2:*a--*(a+0)-- a[0]
printf("%d\n",sizeof(a[3]));//16
//sizeof内部的表达式是不会真实计算的
//a[3]- 第四行的数组名 int [4]
return 0;
}
重点:sizeof内部的表达式是不会真实计算的,体现在:
#include <stdio.h>
int main()
{
short s=8;//2
int n= 12;//4
printf("%zd\n",sizeof(s =n + 5));
printf("%d\n",s);
return 0;
}
- sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。
- &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
- 除此之外所有的数组名都表示首元素的地址。
三、指针运算笔试题解析
1、题目一
#include <stdio.h>
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
int *ptr = (int *)(&a + 1);
//&数组名即为取整个数组的地址,类型为int(*)[5],
//当&a+1时,即为跳过整个数组,即指针现在指向末尾的位置,类型还是为int(*)[5];
//(int*)为强制类型转化,将&a+1强制类型转化为int*的指针变量,再放入ptr的指针变量中
printf( "%d,%d", *(a + 1), *(ptr - 1));
//*(ptr-1)为往前倒退一个元素位置再解引用,*(a+1)为往后前进一个元素位置再解引用
return 0;
}
解题分析思路:
&数组名即为取整个数组的地址,类型为int(*)[5],当&a+1时,即为跳过整个数组,即指针现在指向末尾的位置,类型还是为int(*)[5];(int*)为强制类型转化,将&a+1强制类型转化为int*的指针变量,再放入ptr的指针变量中,*(ptr-1)为往前倒退一个元素位置再解引用,*(a+1)为往后前进一个元素位置再解引用。
以下为思路结构图:
2、题目二
//在X86环境下
//假设结构体的⼤⼩是20个字节
#include <stdio.h>
#include <string.h>
struct Test
{
int Num;
char *pcName;
short sDate;
char cha[2];
short sBa[4];
}*p = (struct Test*)0x100000;
//强制类型转化为struct Test*结构体指针类型
//p为结构体指针变量
int main()
{
printf("%p\n", p + 0x1);
//p是结构体指针,+1就是跳过一个结构体,一个结构体是20个字节
//0x00100014
printf("%p\n", (unsigned long)p + 0x1);
//unsigned long是无符号长整型
//整数+1 0x00100001
printf("%p\n", (unsigned int*)p + 0x1);
//unsigned int*是无符号整型指针类型
//+1跳过一个unsigned int类型的变量,为4个字节
//0x00100004
return 0;
}
注意:
整数+n和指针变量+n是两种不同的概念,整数+n是直接加上所要加上的数字,而指针变量+n是跳过多少个字节,取决于编译器的环境是32位还是64位的。
3、题目三
#include <stdio.h>
int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
//()里面为逗号表达式,是一种运算符号
//逗号表达式运算顺序为从左往右计算,取最大的那个元素作为结果
//()并不是二维数组的表达形式,正确的二维数组表达形式是用{}来表示的
//即为int a[3][2] = { {0, 1}, {2, 3}, {4, 5} };
int *p;
p = a[0];
printf( "%d", p[0]);//1
return 0;
}
解题思路分析:
- ()里面为逗号表达式,是一种运算符号
- 逗号表达式运算顺序为从左往右计算,取最大的那个元素作为结果
- ()并不是二维数组的表达形式,正确的二维数组表达形式是用{}来表示的,即为int a[3][2] = { {0, 1}, {2, 3}, {4, 5} };
- 使用逗号表达式后,数组排序为前三个为逗号表达式的运算结果,而后三个元素用0补齐
该二维数组的直观思维图:
4、题目四
//假设环境是x86环境,程序输出的结果是啥?
#include <stdio.h>
int main()
{
int a[5][5];
int(*p)[4];//数组指针
p = a;//将数组的第一行的首元素地址赋给指针变量p
//等号左边p的类型为int(*)[4],等号右边a的类型为int(*)[5]
//两者类型不相同,强制赋值会报错警告
printf( "%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
//p[4][2]=*(*(p+4)+2)
//两个地址相减得到的是元素与元素之间的个数,相差了几个元素
//%p打印的是地址,即为相差几个元素作为中间值,再将其转化为补码的形式,这个补码就是地址,每四个连在一起的二进制数换成16进制的,环境为32位;
//%d打印的是有符号整型,有符号整型是需要求出其原码再读
//%u打印的无符号整型,无符号整型是需要求出其补码再读
return 0;
}
运行结果:
解题思路分析:
- 将数组的第一行的首元素地址赋给指针变量p;
- 等号左边p的类型为int(*)[4],等号右边a的类型为int(*)[5];
- 两者类型不相同,强制赋值会报错警告
数组的直观表示图:
- 其中的等价关系:p[4][2]=*(*(p+4)+2)
- 两个地址相减得到的是元素与元素之间的个数,相差了几个元素
- %p打印的是地址,即为相差几个元素作为中间值,再将其转化为补码的形式,这个补码就是地址,每四个连在一起的二进制数换成16进制的,环境为32位
- %d打印的是有符号整型,有符号整型是需要求出其原码再读
- %u打印的无符号整型,无符号整型是需要求出其补码再读(有关原反补码的知识详细请关注往后更新的博客)
5、题目五
#include <stdio.h>
int main()
{
int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };//二维数组
int *ptr1 = (int *)(&aa + 1);
//取数组名+1:&aa+1为跳过整个数组,指向数组末尾,再强制类型转化为int*类型赋给指针变量ptr1
int *ptr2 = (int *)(*(aa + 1));
//数组名代表的是数组首元素的地址,即为aa[0]为第一行数组的地址;加上1则变成aa[1]为第二行数组的地址;解引用后再强制类型转化为int*类型赋给指针变量ptr2
printf( "%d,%d", *(ptr1 - 1), *(ptr2 - 1));//10 5
//*(ptr1-1)为退后一个元素,指向前一个元素再解引用
//*(ptr2-1)为前进一个元素,指向后一个元素再解引用
return 0;
}
解题思路分析:
取数组名+1:&aa+1为跳过整个数组,指向数组末尾,再强制类型转化为int*类型赋给指针变量ptr1;数组名代表的是数组首元素的地址,即为aa[0]为第一行数组的地址;加上1则变成aa[1]为第二行数组的地址;解引用后再强制类型转化为int*类型赋给指针变量ptr2。
数组排序的逻辑直观图:
6、题目六
#include <stdio.h>
int main()
{
char *a[] = {"work","at","alibaba"};
//一级指针数组a[]存的是字符串首元素的地址
char**pa = a;//二级指针变量pa存的是a[]首元素的地址
pa++;//指向a[1]的地址
printf("%s\n", *pa);//打印at
return 0;
}
主要内容:
最重要的是学会分别一级指针和二级指针的具体指向的内容,和指针的运算变化。
指向思维直观图:
7、题目七
#include <stdio.h>
int main()
{
char *c[] = {"ENTER","NEW","POINT","FIRST"};
char**cp[] = {c+3,c+2,c+1,c};
char***cpp = cp;
//先看运算的优先级,再下手去运算
printf("%s\n", **++cpp);//打印POINT
//先运算++,再运算*解引用操作符
printf("%s\n", *--*++cpp+3);//打印ER
//先算++,再算*解引用,然后算--,再算*解引用,最后再算+3
printf("%s\n", *cpp[-2]+3);//打印ST
//等价关系*cpp[-2]+3 -> **(cpp-2) + 3
//先算*解引用,再算+3
printf("%s\n", cpp[-1][-1]+1);//打印EW
//等价关系cpp[-1][-1]+1 -> *(*(cpp-1)-1) + 1
//先算(cpp-1),再算*(cpp-1)-1,然后算*(*(cpp-1)-1),最后再+1
return 0;
}
指向直观思维图:
重要前提:
前面运算的++或--运算操作符,会影响后面的运算结果;而数组的等价转化的加减,是不会影响后面的运算结果的。
解题思路分析:
1.第一个打印:printf("%s\n", **++cpp);//打印POINT
先运算++,cpp从原来的指向指针数组cpp的起始地址指向到cpp[1],即下一个地址中去;再运算*解引用,得到c+2(c[2]的地址),再*解引用打印得到POINT。
2.第二个打印:printf("%s\n", *--*++cpp+3);//打印ER
先算++,因为第一次打印cpp已经++过一次了,这次再++,会使cpp从原来的指向指针数组cpp的起始地址指向到cpp[2],即下一个地址中去;再算*解引用,得到c+1,然后--得到c,再*解引用得到ENTER字符串的首元素,最后+3,地址向后移动3位,再通过打印函数得到ER。
3.第三次打印: printf("%s\n", *cpp[-2]+3);//ST
等价关系*cpp[-2]+3 -> **(cpp-2) + 3;等价后先算cpp-2得到cpp当前指向的位置,再算解引用的到c+3(c[3]的地址),然后再解引用得到FIRST字符串的首元素,最后进行+3打印出ST。
4.第四次打印:printf("%s\n", cpp[-1][-1]+1);//打印EW
等价关系cpp[-1][-1]+1 -> *(*(cpp-1)-1) + 1;等价后先算(cpp-1)得到cpp当前指向的位置,再算*(cpp-1)得到c+2,然后再-1得到c+1(指向c[1]),经过*解引用之后得到NEW字符串的首元素,最后再+1跳过第一个字母打印。