目录
(1)sizeof和strlen的对比
(2)数组和指针笔试题解析
(3)指针运算笔试题解析
1. sizeof和strlen的对比
sizeof是操作符,不是函数
strlen是函数,求字符串长度,且只能针对字符串/字符数组,统计的是字符串中'\0'之前字符的个数
1.1 sizeof(是操作符,不是函数)
sizeof计算变量所占内存空间大小的,单位是字节,如果操作数是类型的话,计算的是适用类型创建的变量所占内存空间的大小
sizeof只关注占用内存空间的大小,不在乎内存中存放什么数据
1.2 strlen
strlen是c语言库函数(string.h),统计的是参数str这个地址开始往后,'\0'之前字符串中字符的个数,strlen函数会一直往后找'\0'字符,直到找到为止,所以可能存在越界查找。
函数原型如下:
size_t strlen ( const char * str );
返回 C 字符串 str 的长度。
C 字符串的长度由终止 null 字符决定:C 字符串的长度等于字符串开头和终止 null 字符之间的字符数(不包括终止 null 字符本身)。
这不应与保存字符串的数组的大小相混淆。例如:
char mystr[100]=“test string”;
定义一个大小为 100 个字符
的字符数组,但初始化 mystr 的 C 字符串的长度只有 11 个字符。因此,sizeof(mystr)
的计算结果为 100
,而 strlen(mystr)
返回 11
。
在 C++ 中,char_traits::length 实现相同的行为。
#include <stdio.h> #include <string.h> int main () { char szInput[256]; printf ("Enter a sentence: "); gets (szInput); printf ("The sentence entered is %u characters long.\n",(unsigned)strlen(szInput)); return 0; }
2. 数组和指针笔试题解析
2.1 一维数组
int a[] = {1,2,3,4}; printf("%zd\n",sizeof(a));//16 //数组a单独放在sizeof内部,a表示整个数组,计算的是整个数组的大小,单位是字节 printf("%zd\n",sizeof(a+0));//8(x64环境下)、4(x86环境下) //这里的a是数组名,表示首元素的地址,a+0还是首元素的地址,计算的是首元素地址的大小 printf("%zd\n",sizeof(*a));//4 //a依旧是首元素的地址,*a就是a[0],计算的结果就是a[0]在内存中所占空间的大小 printf("%zd\n",sizeof(a+1));//8(x64环境下)、4(x86环境下) //这里的a是数组名,表示首元素的地址,a+1是第二个元素(a[1])的地址,计算的是首元素地址的大小 printf("%zd\n",sizeof(a[1]));//4 printf("%zd\n",sizeof(&a));//8(x64环境下)、4(x86环境下) //&a-这里的数组名a表示整个数组,&a是整个数组的地址 //数组的地址也是地址,是地址就是4/8个字节长度 printf("%zd\n",sizeof(*&a));//16 //角度1:*&a,这里的*和&抵消了,所以sizeof(*&a) 等价于 sizeof(a) //角度2:&a - 这是数组的地址,类型是(int*)[4],*&a是对(int*)[4]的解引用,访问的就是这个数组 printf("%zd\n",sizeof(&a+1));//8(x64环境下)、4(x86环境下) //&a是数组的地址,&a+1是跳过这个数组之后的那个位置的地址 //&a+1 师弟地址,地址都是4/8个字节 printf("%zd\n",sizeof(&a[0]));//8(x64环境下)、4(x86环境下) //&a[0]就是数组中第一个元素的地址 printf("%zd\n",sizeof(&a[0]+1));//8(x64环境下)、4(x86环境下) //&a[0]+1就是数组中第二个元素的地址
2.2 字符数组
例1:
char arr[] = {'a','b','c','d','e','f'}; printf("%zd\n",sizeof(arr));//6 printf("%zd\n",sizeof(arr+0));// 4/8 printf("%zd\n",sizeof(*arr));//1 printf("%zd\n",sizeof(arr[1]));//1 printf("%zd\n",sizeof(&arr));// 4/8 printf("%zd\n",sizeof(&arr+1));// 4/8 printf("%zd\n",sizeof(&arr[0]+1));// 4/8
例2:
char arr[] = {'a','b','c','d','e','f'}; printf("%zd\n",strlen(arr));//随机值a printf("%zd\n",strlen(arr+0));//随机值a printf("%zd\n",strlen(*arr));//程序崩溃 //*arr是首元素 —— 'a' —— 97,传递给strlen后,strlen会认为97是地址,然后去访问内存 printf("%zd\n",strlen(arr[1]));//程序崩溃 printf("%zd\n",strlen(&arr));//随机值a printf("%zd\n",strlen(&arr+1));//随机值 a+6 printf("%zd\n",strlen(&arr[0]+1));//随机值 a-1
例3:
char arr[] = "abcdef";//末尾隐藏了'\0' printf("%zd\n",sizeof(arr));//7 //计算的是数组的大小,记得还有'\0' printf("%zd\n",sizeof(arr+0));// 4/8 //arr+0是数组首元素的地址,既然是地址,大小就是4/8字节 printf("%zd\n",sizeof(*arr));//1 //*arr是首元素,sizeof(*arr) -- 1 printf("%zd\n",sizeof(arr[1]));//1 printf("%zd\n",sizeof(&arr));// 4/8 //&arr取出的是整个数组的地址,既然是地址就是4/8 printf("%zd\n",sizeof(&arr+1));// 4/8 //&arr是数组的地址,&arr+1跳过了整个数组,指向了数组后的那个位置的地址,所以&arr+1还是地址 printf("%zd\n",sizeof(&arr[0]+1));// 4/8 //&arr[0]是首元素的地址,&arr[0]+1是第二个元素的地址,依然是地址
例4:
char arr[] = "abcdef";//末尾隐藏了'\0' printf("%zd\n",strlen(arr));//6 //strlen是从第一个元素开始统计'\0'之前字符的个数 printf("%zd\n",strlen(arr+0));//6 //arr+0是数组首元素的地址 —— 6 printf("%zd\n",strlen(*arr));//程序崩溃 //*arr是首元素,'a' —— 97,97作为地址传入,strlen从内存中访问97对应内容,程序崩溃 printf("%zd\n",strlen(arr[1]));//程序崩溃 printf("%zd\n",strlen(&arr));//6 //&arr是数组的地址,数组的地址和数组首元素的地址是指向同一个位置的 printf("%zd\n",strlen(&arr+1));//随机值 //&arr是数组的地址,&arr+1跳过了整个数组,指向了数组后的那个位置的地址,所以&arr+1还是地址,具体指向的数据是啥由于没有初始化所以是随机值 printf("%zd\n",strlen(&arr[0]+1));//5 //&arr[0]是首元素的地址,&arr[0]+1是第二个元素的地址,那么strlen从数组的第二个元素开始往后统计字符的个数直到遇到'\0'
例5:
char* p = "abcdef";//末尾隐藏了'\0' printf("%zd\n",sizeof(p));// 4/8 //p是指针变量,计算的是指针变量p的大小,4/8个字节 printf("%zd\n",sizeof(p+1));// 4/8 //p+1是第二个元素的地址,地址的大小就是4/8个字节 printf("%zd\n",sizeof(*p));//1 //p是char* 所以*p只能访问1个字节 printf("%zd\n",sizeof(p[0]));//1 //p[0] ——>*(p+0) —— *p —— 只能访问一个字节 —— 1 printf("%zd\n",sizeof(&p));// 4/8 //&p取出的是指针变量p的地址,既然是地址就是4/8 //*p —— char** —— 二级指针 printf("%zd\n",sizeof(&p+1));// 4/8 //&p是p的地址,&p+1是跳过p变量,指向了p的后边,&p+1还是地址,所以就是4/8 printf("%zd\n",sizeof(&p[0]+1));// 4/8 //&p[0]+1就是b的地址 //p[0]-- *(p+0) —— *p
例6:
char* p = "abcdef";//末尾隐藏了'\0' printf("%zd\n",strlen(p));//6 printf("%zd\n",strlen(p+1));//5 printf("%zd\n",strlen(*p));//程序崩溃 *p -- a -- 97 printf("%zd\n",strlen(p[0]));//程序崩溃 printf("%zd\n",strlen(&p));//随机值 //&p strlen统计的是p变量里面的字符个数,p是指针变量 printf("%zd\n",strlen(&p+1));//随机值 printf("%zd\n",strlen(&p[0]+1));//5
上面题考来考去就是指针的运算和指针类型的理解
2.3 二维数组
数组名的意义:
1.sizeof(数组名),这里的数组名表示的是整个数组,计算的是整个数组的大小
2.&数组名,这里的数组名表示整个数组,取出的是整个数组的地址
3.除此之外的所有数组名都表示首元素的地址
int main() { int a[3][4] = {0}; printf("%zd\n",sizeof(a));//4*(3*4)= 48 //a作为数组名,单独放在sizeof内部了,a表示的是整个数组,计算的是整个数组的大小,单位是字节 printf("%zd\n",sizeof(a[0][0]));//4 //a[0][0]是第一行第一个元素,大小是四个字节 printf("%zd\n",sizeof(a[0]));//4*4 = 16 //a[0]是第一行的数组名,单独放在sizeof内部,计算的是数组的大小,计算的第一行数组的大小 printf("%zd\n",sizeof(a[0]+1));// 4/8 //a[0]没有单独放在sizeof内部,所以a[0]是数组首元素地址,那就是第一行第一个元素的地址 //a[0] + 1 等价于 &a[0][0]+1 --第一行第二个元素的地址,是地址那就是4/8 printf("%zd\n",sizeof(*(a[0]+1)));//4 //a[0] + 1 等价于 &a[0][0]+1 --第一行第二个元素的地址,*之后就是第一行第二个元素咯,大小是4字节 printf("%zd\n",sizeof(a+1));// 4/8 //a是二维数组的数组名,并没有单独放在sizeof内部,a表示首元素的地址-也就是第一行的地址 //a+1 是第二行的地址,既然是地址,所以大小是4/8字节 printf("%zd\n",sizeof(*(a+1)));//16 //角度1:*(a+1) -- a[1] -- 单独放在sizeof内部,计算的是第二行的大小 //角度2:a+1 是第二行的地址,类型是int(*)[4],数组指针,*之后就是第二行元素的大小,就是4*4 = 16 printf("%zd\n",sizeof(&a[0]+1));// 4/8 //a[0]是第一行的数组名,&数组名就是第一行的地址,&a[0] -- 第一行的地址;&a[0]+1 -- 第二行的地址 printf("%zd\n",sizeof(*(&a[0]+1)));//16 //*(第二行的地址),访问的是第二行的元素 printf("%zd\n",sizeof(*a));//16 //角度1:a没有单独放在sizeof内部,所以a是首元素地址(第一行地址),*a就是访问第一行的元素 //角度2:*a就是*(a+0) -- a[0] printf("%zd\n",sizeof(a[3]));//16 //但是a[3]数组越界了,但程序不会崩溃 -- sizeof内部的表达式是不会真实运算的 //a[3] -- 第四行的数组名,类型是int(*)[4],sizeof根据类型推断长度 }
回忆:sizeof内部的表达式是不会真实运算的
int main() { short s = 8;//short是两个字节 int n = 12; printf("%zd\n",sizeof(s = n + 5));//打印2 printf("%d\n",s);//打印8;s = n + 5没有计算 return 0; }
3. 指针运算的笔试题解析
3.1 题目1
int main() { int a[5] = {1,2,3,4,5}; int* ptr = (int*)(&a + 1); printf("%d,%d",*(a + 1),*(ptr - 1));//打印2,5 //ptr是整型指针,指向的是a数组的结尾处的地址; //由于ptr是int*类型,-1相当于回退一个整型的地址,指向a最后一个元素就是5 //a是数组首元素的地址,a+1就是数组第二个元素的地址,*后访问第二个元素,打印2 return 0; }
3.2 题目2
//在x86环境下 //假设结构体的大小是20个字节 //程序输出的结果是啥? struct Test { int Num; char* pcName; short sDate; char cha[2]; short sBa[4]; }*p = (struct Test*)0x100000; //指针 + 1 和类型有关系 //整数 + 1 就是+ 1 int main() { //%p打印的是十六进制的地址 printf("%p\n",p + 0x1);//p是结构体指针,+1就是跳过一个结构体,一个结构体是20个字节 //20在十六进制下是14,所以最后结果是0x100014 printf("%p\n",(unsigned long)p + 0x1);//p被强制转换为整型了哈,+1就是 0x100001 printf("%p\n",(unsigned int*)p + 0x1);//p被强制转换为另一类指针,+1跳过一个unsigned int类型的变量,就是四个字节,0x100004 return 0; }
3.3 题目3
int main() { int a[3][2] = {(0,1),(2,3),(4,5)};//注意这里是(); //用的是(),那这里就是逗号表达式了,从左向右依次计算,但整个表达式的结果取决于最后一个表达式结果 //(0,1)表达式的结果就是1 //所以数组实际上是{{1,3},{5,0},{0,0}}; int* p; p = a[0];//数组名a[0]是第一行的地址,也就是第一行第一个元素的地址,p指向1所在的位置 printf("%d",p[0]);//p[0] -- *(p+0),结果就是1 return 0; }
3.4 题目4
//假设环境是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]);//打印FFFFFFFC,-4 return 0; }
此题有一点点抽象成分,我们需要学会自己画内存的情况来分析(大致如下,不好语言描述,大家借助图标理解)
两个地址相减得到的两个地址间元素的个数,而 &p[4][2]-&a[4][2]是小-大得到-4,得到的是负值。
但用%p打印的是地址,所以就把-4在内存里的值直接就当成地址,而-4在地址中以补码形式储存:11111111111111111111111111111100,在x86环境下转换为十六进制就是FFFFFFFC
用%p打印的时候,直接就把-4在内存中的补码当作地址(转化为十六进制)了
而%d打印的时候是有符号整型就会将-4的补码转化为十进制打印出来
3.5 题目5
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));//打印 10,5 return 0; }
//&aa + 1,跳过整个二维数组,就是二维数组aa的末尾处的地址,还是个二维数组的地址;于是有将他强制转换为int*的操作进而赋给ptr1; //ptr1 - 1就是ptr1地址向前挪一个整型大小,*之后访问一个整型就是10; //aa是数组名,就是数组第一行第一个元素的地址,aa + 1就是数组第二行第一个元素的地址,*(aa + 1)就是访问第二行这个数组,这个本身就是int*的,所以前面的强制转换(int*)就只是迷惑人的; //ptr2和aa+1指向的都是6,ptr2 - 1之后指向的就是5;
3.6 题目6(alibaba曾经出的一个题 )
int main() { char* a[] = {"work","at","alibaba"}; //数组里面存的是三个单词的第一个字母的地址 char** pa = a; //a是数组名,就是首元素的地址,pa要存放这个地址的话就要用二级指针,也就是char** pa++; //pa+1之后就是单词at首字母的地址 printf("%s\n",*pa);//解引用pa,访问pa内的字符串"at",打印at //%s 打印的时候就需要地址 return 0; }
3.7 题目7
int main() { char* c[] = {"ENTER","NEW","POINT","FIRST"}; char** cp[] = {c + 3,c + 2,c + 1,c}; char*** cpp = cp; printf("%s\n",**++cpp);//语句1 printf("%s\n",*-- * ++cpp + 3);//语句2 printf("%s\n",*cpp[-2] + 3);//语句3 printf("%s\n",cpp[-1][-1] + 1);//语句4 return 0; }
c(char*) | cp(char**) | cpp(char***) --- cp | |
---|---|---|---|
ENTER | E的地址 - c | c + 3 | |
NEW | N的地址 - c + 1 | c + 2 | |
POINT | P的地址 - c + 2 | c + 1 | |
FIRST | F的地址 - c + 3 | c |
对于语句1:
printf("%s\n",**++cpp);//打印POINT //++cpp是指向cp的c+2处的地址的指针变量; //*++cpp就找到c+2的地址; //c+2又是指向字符串首字母P地址的指针变量; //*(c+2)就找到POINT字符串的首字母的地址;(%s需要字符串首元素的地址,即可打印出字符串) //而*(c+2)也就是**++cpp;
对于语句2:
printf("%s\n",*-- * ++cpp + 3);//打印ER //+3优先级很低,最后再+3; //++cpp是指向cp的c+1处的地址的指针变量;(经历语句1之后cpp已经是指向cp的c+2处地址的指针变量了) //*++cpp就找到c+1的地址; //--*++cpp就是c的地址; //c又是指向字符串首字母E地址的指针变量; //*--*++cpp就找到ENTER字符串首字母E的地址; //*--*++cpp + 3,由于此处的类型是char*,+3就相当于指针往后跳三个字节,指向第二个E;最后打印ER
对于语句3:
printf("%s\n",*cpp[-2] + 3);//打印ST //cpp[-2]等价于*(cpp-2),*cpp[-2]等价于*(*(cpp-2)); //cpp-2就是是指向cp的c+3处的地址的指针变量;(经历语句2之后cpp已经是指向cp的c+1处地址的指针变量了) //*(cpp-2)就找到c+3的地址 //*(*(cpp-2))就找到FIRST字符串首字母F的地址; //*(*(cpp-2)) + 3后指向S,打印ST;
对于语句4:
printf("%s\n",cpp[-1][-1] + 1);// //cpp[-1]等价于*(cpp-1);cpp[-1][-1]等价于*(*(cpp-1)-1); //cpp-1就是指向cp的c+2处地址的指针变量;(经历语句2、3之后cpp已经是指向cp的c+1处地址的指针变量了); //*(cpp-1)就找到c+2的地址; //*(cpp-1) - 1就找到cp中c+1的地址; //*(*(cpp-1)-1)就找到NEW字符串首字母N的地址; //*(*(cpp-1)-1) + 1后指向E;于是打印EW;
上述题目,看似很难但实际上还是指针运算和指针类型的理解
要达到学以致用,任重而道远
Mais iln’ya pas de limites pour aimer et que m’importe de mal éteindre si je peux tout embrasser