指针与数组
习题一
在32位机器下,求下述代码打印的内容
#include<stdio.h>
int main()
{
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));
return 0;
}
解析:
在解题之前,我们需要先清楚下述知识点:
sizeof并不关注内容,只关注内存空间
数组名在普遍情况下,都表示数组首元素地址,只有两个例外:
1.数组名单独存在于sizeof中
2.&数组名
int main()
{
int a[] = { 1,2,3,4 };
printf("%d\n", sizeof(a));//16
//sizeof(数组名),数组名表示整个数组,计算的是整个数组的大小,单位是字节
printf("%d\n", sizeof(a + 0));//4
// 数组名 + 0 表明数组名并不是单独存在,
// 在sizeof中数组名如果不是单独存在则数组名表示数组首元素地址
//首元素地址 + 0 仍然是首元素地址
// 首元素地址相当于是一个指针,指针变量占4个字节空间
printf("%d\n", sizeof(*a));//4
//数组名不是单独存在,则表示首元素地址
//首元素地址指向的内容为整型1
//解引用这个地址得到整形元素1
//整型占4个字节空间
printf("%d\n", sizeof(a + 1));//4
//数组名不是单独存在,表示首元素地址
//首元素地址指向第一个元素
//由于数组是一段连续的空间,所以地址 + 1 便是指向下一个元素的地址
//由于仍然是地址,也就是指针,指针占四个字节的空间
printf("%d\n", sizeof(a[1]));//4
//a[1]是数组第二个元素,也就是整型2
//整型2占4个字节的空间
printf("%d\n", sizeof(&a));//4
//取数组的地址,也就是一个数组指针 -> int(*)[4]
//数组指针也是指针,占4个字节空间
printf("%d\n", sizeof(*&a));//16
//&a取的是数组的地址,也就是数组指针
//*(&a) 便是解引用一个数组指针,取得的是一个数组
//这个数组有4个整形元素,所以占16个字节
printf("%d\n", sizeof(&a + 1));//4
//对数组取地址,便是一个数组指针
//对指针 + 1仍然是一个指针,指针占4个字节大小
printf("%d\n", sizeof(&a[0]));//4
//由于优先级的原因,a先于下标引用运算符[]结合,取得整型1
//再对元素1取地址,取到的地址也就是首元素的地址
//地址也是指针,指针占4个字节
printf("%d\n", sizeof(&a[0] + 1));//4
//地址 + 1也是指针,指针占4个字节
return 0;
}
习题二
int main()
{
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));
return 0;
}
解析
int main()
{
char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", sizeof(arr));//6
//sizeof(数组名),数组名表示整个数组,计算的是数组的大小
//一个字符占1个字节,总共占6个字节空间
printf("%d\n", sizeof(arr + 0));//4
//数组名在sizeof内部并不单独存在,则表示首元素地址
//首元素地址 + 0 仍然是首元素地址
//地址也就是指针,占4个字节
printf("%d\n", sizeof(*arr));//1
//数组名并不单独存在,表示首元素地址
//解引用首元素地址,得到首元素
//首元素是char类型,占1个字节
printf("%d\n", sizeof(arr[1]));//1
//arr[1] -> *(arr + 1)
//表示第二元素,第二个元素也是char类型,占1个字节
printf("%d\n", sizeof(&arr));//4
//&arr表示整个数组的地址,也就是数组指针-> char (*)[6]
//数组指针也是指针,占4个字节
printf("%d\n", sizeof(&arr + 1));//4
//指针 + 1仍是指针
//指针占4个字节
printf("%d\n", sizeof(&arr[0] + 1));//4
//数组名先于下标引用[]结合,表示首元素
//再对首元素取地址,表示首元素地址
//首元素地址是指针,指针 + 1仍是指针,占4个字节
return 0;
}
习题三
int main()
{
char arr[] = { 'a','b','c','d','e','f' };
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));
return 0;
}
解析:
在解题前,我们仍然需要了解以下知识点:
1.strlen的原理:
strlen 接收一个字符指针,并计算字符串长度, 遇见\0终止
库函数strlen的声明:size_t strlen(const char* str)
2.字符数组并没有以\0结尾
int main()
{
char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", strlen(arr));//随机值
//arr数组并没有以\0结尾,strlen仍然会对后面的数据进行读取
//直到遇见\0
//因为不知道内存中什么时候会出现\0,所以strlen返回的值为随机值
//这个随机值 >= 6
printf("%d\n", strlen(arr + 0));//随机值
//arr + 0仍然是首元素地址,与上同
printf("%d\n", strlen(*arr));//error
//arr表示首元素地址
//*arr取到首元素'a'
//'a'的ASCII值为97
//97作为地址传递给strlen
//系统不允许用户访问地址为97的空间,所以error
printf("%d\n", strlen(arr[1]));//error
//arr[1]取到第二个元素'b'
//'b'的ASCII码值为98
//98作为地址传给strlen
//系统仍不允许用户访问地址为98的空间,所以error
printf("%d\n", strlen(&arr));//随机值
//&arr取得是整个数组的地址,传递给strlen
//由于strlen只接收字符指针,所以数组指针会转变为字符指针
//由于&arr的起始位置与数组首元素地址相同
//所以strlen(&arr) 与 strlen(arr) 的结果相同
printf("%d\n", strlen(&arr + 1));//随机值 - 6
//&arr + 1仍然为数组指针,指向跨越一个数组后的空间
//由于仍然不知道后面内存空间的数据,所以strlen的值仍然为随机
//但因为跨越了数组arr的长度,所以这个值为: strlen(&arr)的值 - 6
printf("%d\n", strlen(&arr[0] + 1));//随机值 - 1
//arr先于[]结合,为第一个元素
//&arr[0]为第一个元素的地址 -> char*
//指针 + 1为 指向第二个元素'b'的指针
//由于跳过一个元素,所以值为strlen(arr) - 1
return 0;
}
习题四
#include<stdio.h>
#include<string.h>
int main()
{
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 + 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));
return 0;
}
解析:
#include<stdio.h>
#include<string.h>
int main()
{
char arr[] = "abcdef";
//字符串,末尾存在'\0'
printf("%d\n", sizeof(arr));//7
//sizeof(数组名),数组名表示整个数组
//sizeof计算字符串占用内存空间大小
//arr字符串包括'\0'总共7个字符,占7个字节
printf("%d\n", sizeof(arr + 0));//4
//数组名在sizeof内部并不单独存在,表示首元素地址
//首元素地址+0仍然是首元素地址
//地址即指针,占4个字节空间
printf("%d\n", sizeof(*arr));//1
//数组名表示首元素地址,解引用数组名得到首元素
//首元素为char类型变量,占1个字节空间
printf("%d\n", sizeof(arr[1]));//1
//arr[1] 表示数组第二个元素
//数组中每个元素为char类型,占1个字节空间
printf("%d\n", sizeof(&arr + 1));//4
//&arr表示取出整个数组的地址 - char(*)[7]
//地址即指针,指针 + 1仍是指针,占4个字节
printf("%d\n", sizeof(&arr[0] + 1));//4
//arr先于[]结合,表示首元素
//&arr[0]表示首元素地址
//地址即指针,指针 + 1仍是指针,占4个字节
--------------------------------------------------
printf("%d\n", strlen(arr));//6
//arr表示首元素地址
//strlen从首元素地址向后依次计算字符串长度
//直至遇见末尾'\0'停止
printf("%d\n", strlen(arr + 0));//6
//首元素地址 + 0 仍是首元素地址
//与上同
printf("%d\n", strlen(*arr));//error
//arr表示首元素地址
//*arr表示首元素'a'
//a转为ASCII码中的97,将97作为地址传递给strlen
//系统不允许用户访问地址为97的空间,所以error
printf("%d\n", strlen(arr[1]));//error
//arr[1]表示第二个元素
//与上同理,将'b'转换为98传递给strlen
//系统仍不允许访问,error
printf("%d\n", strlen(&arr));//6
//&arr取出整个数组的地址 - char(*)[7]
//将数组的地址传递给strlen,strlen强制类型转换这个地址为char*
//也就是转换为首元素地址
//strlen从首元素地址依次向后计算,遇'\0'终止
printf("%d\n", strlen(&arr + 1));//随机值
//&arr + 1表示指向数组末端的地址
//由于不清楚数组后面数据的情况,无法知晓何时会遇到'\0'
//所以为随机值
printf("%d\n", strlen(&arr[0] + 1));//5
//arr先于[]结合,表示首元素
//&arr[0]表示首元素地址
//&arr + 1表示首元素地址 + 1 -> 第二个元素的地址
//strlen从第二个元素地址开始计算,遇'\0'停止
//所以为strlen(arr) - 1
return 0;
}
习题五
#include<stdio.h>
#include<string.h>
int main()
{
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));
return 0;
}
解析:
#include<stdio.h>
#include<string.h>
int main()
{
char* p = "abcdef";
//"abcdef"存在于常量区,不可被修改
//字符指针p指向首元素地址 -> a的地址
printf("%d\n", sizeof(p));//4
//p是指向'a'的指针
//指针占4个字节
printf("%d\n", sizeof(p + 1));//4
//p + 1即指向'b'的指针
//指针占4个字节
printf("%d\n", sizeof(*p));//1
//*p取到首元素即'a'
//'a'为字符,占1个字节
printf("%d\n", sizeof(p[0]));//1
//p[0]为首元素即'a'
//占1个字节
printf("%d\n", sizeof(&p));//4
//取一级指针的地址,即二级指针
//二级指针也是指针,占4个字节
printf("%d\n", sizeof(&p + 1));//4
//&p + 1 也表示二级指针,占4个字节
printf("%d\n", sizeof(&p[0] + 1));//4
//p先于[]结合,表示'a'
//&p[0]表示首元素地址
//&p[0] + 1表示第二个元素的地址
//地址即指针,占4个字节
-------------------------------------------
printf("%d\n", strlen(p));//6
//p为首元素地址
//strlen遇'\0'停止计算,总长度为6
printf("%d\n", strlen(p + 1));//5
//p + 1表示第二个元素的地址
//strlen从第二个元素地址开始计算
//长度为5
printf("%d\n", strlen(*p));//error
//*p 表示 'a','a'转换为ASCII码97
//97作为地址传递给strlen
//系统不允许用户访问地址为97的空间,所以error
printf("%d\n", strlen(p[0]));//error
//p[0]为首元素,即'a','a'转换为ASCII码97
//同上理
printf("%d\n", strlen(&p));//随机
//&p取一级指针的地址,即二级指针 char**
//由于strlen会解引用二级指针来读取p的数据
//因为不清楚p的内容,所以该值为随机
printf("%d\n", strlen(&p + 1));//随机
//&p + 1与上同理
//请看下图解
printf("%d\n", strlen(&p[0] + 1));//5
//p先于[]结合,即首元素'a'
//&p[0]表示首元素地址
//&p[0] + 1表示首元素地址 + 1,即第二个元素地址
//strlen从第二个元素地址开始计算
//长度为5
return 0;
}
下图是根据strlen(&p)某种情况作分析,每次运行程序p中的数据都不一样!!!
习题五:二维数组
#include<stdio.h>
int main()
{
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]));
return 0;
}
知识点分析:
首先明确二维数组的特性:
1.二维数组是一个每个元素都是数组的一维数组;
2.二维数组在定义时,可以没有行,但不能没有列;
再其次是明确sizeof的功能:
sizeof只关注使用者传递的数据类型,并依据数据类型对内存空间进行计算
指针加减运算
解析:
#include<stdio.h>
int main()
{
int a[3][4] = { 0 };
printf("%d\n", sizeof(a));//48
//sizeof(数组名),数组名表示整个数组
//sizeof求整个数组的大小,占3 * 4 * sizeof(int) -> 48
printf("%d\n", sizeof(a[0][0]));//4
//a[0]表示首元素,即第一个数组
//a[0][0]表示第一个数组的首元素
//第一个数组的首元素为int型,占4个字节
printf("%d\n", sizeof(a[0]));//16
//a[0]表示首元素,即第一个数组
//a[0]即第一个数组,也是第一个数组的数组名
//每个数组有4个int型元素,占16个字节
printf("%d\n", sizeof(a[0] + 1));//4
//a[0]表示第一个数组的数组名
//但在sizeof中并不单独存在,所以表示第一个数组的首元素地址
//首元素地址为int*,占4个字节
printf("%d\n", sizeof(*a[0] + 1));//4
//a先与[]结合,表示第一个数组的数组名
//*a[0]求得第一个数组的首元素
//第一个数组的首元素加1仍然为整型,占4个字节
printf("%d\n", sizeof(a + 1));//4
//数组名在sizeof中不是单独存在,表示首元素地址
//即第一个数组的地址 int(*)[4]
//第一个数组的地址 + 1跳过一个数组
//表示第二个数组的地址,地址即指针,占4个字节
printf("%d\n", sizeof(*(a + 1)));//16
//*(a+1) -> a[1],即第二个数组
//第二个数组占16个字节
printf("%d\n", sizeof(&a[0] + 1));//4
//a先与[]结合,表示第一个数组
//&a[0]表示第一个数组的地址 int(*)[4]
//&a[0] + 1 -> 第一个数组的地址跳过一个数组
//表示第二个数组的地址
//地址即指针,占4个字节
printf("%d\n", sizeof(*(&a[0] + 1)));//16
//&a[0] + 1表示第二个数组的地址
//解引用第二个数组的地址得到第二个数组
// *(&a[0] + 1) -> *(a + 1)
//占16个字节
printf("%d\n", sizeof(*a));//16
//数组名并不是单独存在
//表示首元素地址,即第一个数组的地址
//解引用第一个数组的地址,得到第一个数组
//第一个数组占16个字节
printf("%d\n", sizeof(a[3]));//16
//虽然a数组并不存在a[3],但还记得吗?
//sizeof只关注你传递的类型,并不关心你传递的数据究竟合不合法
//a[3]表示一个一维数组,类型为 int (*)[4]
//sizeof根据int(*)[4] 得出该数据占16个字节这个结论
return 0;
}
指针面试题
习题一
#include<stdio.h>
int main()
{
int a[5] = {1,2,3,4,5};
int* ptr = (int*)(&a + 1);
printf("%d,%d",*(a + 1),*(ptr - 1));
return 0;
}
解析:
#include<stdio.h>
int main()
{
int a[5] = {1,2,3,4,5};
int* ptr = (int*)(&a + 1);
//&a 取出整个数组的地址 -> int(*)[5]
//&a + 1表示数组的地址跳过一个数组的距离
//(int*)将数组指针强制类型转换为整型指针
//改变该指针+-整数将会跳过的距离
//从原来的跳过一个数组变为跳过一个整型
printf("%d,%d",*(a + 1),*(ptr - 1));
//a表示首元素地址,首元素地址+1表示第二个元素的地址
//*(a+1)就是第二个元素 -> 2
//由于ptr是int*类型,所以-1就是向后退一个整型的距离
//ptr-1就是指向5的指针
//*(ptr - 1)就是末尾元素 -> 5
return 0;
}
习题二
#include<stdio.h>
struct Test
{
int Num;
char* pcName;
short sDate;
char ch[2];
short sBa[4];
}*p;
//假设p的值为0x100000;如下表达式的值分别为多少?
int main()
{
printf("%p\n",p + 0x1);
printf("%p\n",(usigned long)p + 0x1);
printf("%p\n",(unsigned int*)p + 0x1);
return 0;
}
解析:
#include<stdio.h>
struct Test
{
int Num;
char* pcName;
short sDate;
char ch[2];
short sBa[4];
}*p;
//该结构体的大小为20个字节
//假设p的值为0x100000;如下表达式的值分别为多少?
int main()
{
printf("%p\n",p + 0x1);//0x100014
//p作为结构体指针使用
//p + 1 表示指针跳过一个结构体大小的距离
//0x100000 + 14
printf("%p\n",(usigned long)p + 0x1);//0x100001
//将指针强制类型转换为长整型
//长整型0x100000 + 1就是0x100001
printf("%p\n",(unsigned int*)p + 0x1);//0x100004
//将结构体指针强转为整形指针
//整形指针 + 1跳过一个整型的距离
//0x100000 + 4
return 0;
}
习题三
#include<stdio.h>
int main()
{
int a[4] = {1,2,3,4};
int* ptr1 = (int*)(&a + 1);
int* ptr2 = (int*)((int)a + 1);
printf("%x %x\n",ptr1[-1],*ptr2);
return 0;
}
解析:
#include<stdio.h>
int main()
{
int a[4] = {1,2,3,4};
int* ptr1 = (int*)(&a + 1);
//&a + 1表示数组的地址向后跨越一个数组的距离
//(int*)表示将数组指针强制类型转换为整型指针
//ptr1是整形指针,所以其+-整数都是跳过整型的倍数的距离
int* ptr2 = (int*)((int)a + 1);
//(int)将数组首元素地址强制类型转换为int
//此时a+1相当于这个指针的值向后走一个字节的距离
printf("%x %x\n",ptr1[-1],*ptr2);
//数据以小端方式进行存储,请看下图解析
return 0;
}
习题四
#include<stdio.h>
int main()
{
int a[3][2] = { (0,1), (2,3), (4,5) };
int* p;
p = a[0];
printf("%d\n",p[0]);
return 0;
}
解析:
#include<stdio.h>
int main()
{
int a[3][2] = { (0,1), (2,3), (4,5) };
//要注意( , )是一个逗号表达式,只保留最后一个数作结果
//所以该数组的实际数据应该是 {1,0} {3,0} {5,0}
int* p;
p = a[0];
//a[0]表示第一个数组
//第一个数组的数组名表示该数组的首元素地址
printf("%d\n",p[0]);
//p[0]表示首元素 -> 1
return 0;
}
习题五
#include<stdio.h>
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;
}
解析:
#include<stdio.h>
int main()
{
int a[5][5];
int(*p)[4];
//p指向一个具有4个整型的数组
p = a;
printf("%p,%d\n",&p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
//请看下图解
//指针减指针等于两个指针之间相差的数据个数!
//两者之间相差4个数据
//所以&p[4][2] - &a[4][2] = -4
//%d打印-4的原码 -> -4
//%p打印-4在内存中的存储形式-补码 -> ff ff ff fc
//10000000 00000000 00000000 00000100 -4的原码
//11111111 11111111 11111111 11111011 -4的反码
//11111111 11111111 11111111 11111100 -4的补码
//ff ff ff fc 16进制形式的补码
return 0;
}
习题六
#include<stdio.h>
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\n",*(ptr1 -1), *(ptr2 - 1));
return 0;
}
解析:
#include<stdio.h>
int main()
{
int aa[2][5] = {1,2,3,4,5,6,7,8,9,10};
int* ptr1 = (int*)(&aa + 1);
//&aa + 1表示数组地址跨越整个数组后的地址
//(int*)表示将数组指针强转为整形指针
int* ptr2 = (int*)(*(aa + 1));
//aa表示二维数组的首元素地址
//首元素地址也就是第一个数组的地址 - int(*)[5]
//aa + 1表示第一个数组的地址向后跨越一个一维数组的地址 - a[0] -> a[1]
printf("%d,%d\n",*(ptr1 -1), *(ptr2 - 1));
//ptr1作为整型指针 - 1表示 向后退一个整型的距离
//此时指向末尾元素10
//ptr2 指向第二个数组的地址,其也是整形指针
//所以 - 1 表示向后退一个整型的距离
//指向5
return 0;
}
习题七
#include<stdio.h>
int main()
{
char* a[] = {"work","at","alibaba"};
char** pa = a;
pa++;
printf("%s\n",*pa);
return 0;
}
解析:
习题八
#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);
printf("%s\n",*--*++cpp + 3);
printf("%s\n",*cpp[-2] + 3);
printf("%s\n",cpp[-1][-1] + 1);
return 0;
}
解析: