初识C语言·指针(5)

目录

1 sizeof和strlen的对比

2 数组和指针题目详解

i)一维数组

ii)字符数组

iii)二维数组

3 指针运算题目详解


1 sizeof和strlen的对比

在指针进行运算的时候,sizeof和strlen挺绕的,今天我们就重温一下这两个

sizeof是C语言的关键字,被用来计算某个数据在内存中占的空间大小,不会关心存放的是什么数据。sizeof有个值得注意的点就是sizeof后面是类型的话一定加括号,如果是变量的话可以不用加,其次就是sizeof的返回值是size_t类型的,打印的时候需要用%zd打印,最后的结果单位是字节。

strlen是C语言中的库函数,使用的时候需要引用头文件string,是用来计算字符串长度的,计算的时候是通过找到'\0'来返回值的,如果后面有其他字符,strlen也是不会继续计算的,如果没有'\0'就会一直往下找,可能就会导致越界访问的问题,当然,返回值也是size_t类型的,需要%zd打印。


2 数组和指针题目详解

以下代码均是在x64的环境下运行的。

i)一维数组

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));

sizeof(a),这里的a是特殊情况中的一种,a表示的是整个数组,所以计算的是整个数组的大小,答案就是16。

sizeof(a + 0),现在不满足特殊情况,因为加了一个0,所以a这里表示的首元素地址,+0表示地址+0,还是指向的是首元素,但是现在sizeof计算的就是指针的大小了,因为是地址,在x64环境下运行的,所以答案是8。

sizeof(*a),同样不是特殊情况(特殊情况可以死板一点去识别),*a表示的是对首元素地址解引用,所以计算的是首元素的大小,因为是整型数组,所以答案是4。

sizeof(a + 1),a + 1表示的是首元素地址加一个整型的地址,所以指向的是第二个元素,但是仍然是地址,所以结果还是8。

sizeof(a[1]),这个不用过多解释,计算的是第二个元素的大小,答案是4。

sizeof(&a),这里&数组名是特殊情况,但是对结果影响不大,因为取出来的是地址,那么计算的结果不是8就是4,这里答案就是8了。

sizeof(*&a),这里可能看来比较怪?&先于a结合,表示的把整个数组的地址取出来,然后解引用表示整个数组,所以计算的是整个数组的大小,答案就是16。

sizeof(&a + 1),这里表示的是把整个数组的地址取出来在运算,最后指向的是数组最后一个元素的后面。但是它也是地址,所以答案是8.

sizeof(&a[0]),这里&a[0]表示的把首元素地址取出来,答案就是8了。

sizeof(&a[0] + 1),这里表示的是把首元素地址取出来进行运算,最后指向的是第二个元素,但是还是地址,所以答案也是8。

初识C语言·指针(5)_第1张图片

ii)字符数组

代码1:

int main()
{
	char arr[] = { 'a','b','c','d','e','f' };
	printf("%zd\n", sizeof(arr));
	printf("%zd\n", sizeof(arr + 0));
	printf("%zd\n", sizeof(*arr));
	printf("%zd\n", sizeof(arr[1]));
	printf("%zd\n", sizeof(&arr));
	printf("%zd\n", sizeof(&arr + 1));
	printf("%zd\n", sizeof(&arr[0] + 1));
	return 0;
}

sizeof(arr),arr表示的是整个数组,数组有6个字符,所以答案是6。

sizeof(arr + 0),arr表示的是首元素地址,+ 0指向首元素,是地址,答案就是8。

sizeof(*arr),arr表示的是首元素,对它解引用,得到了首元素,元素是字符类型的,所以结果是1

sizeof(arr[1]),计算的是第二个元素占的内存大小,答案是1。

sizeof(&arr),取出了整个数组的地址,地址,答案是8。

sizeof(&arr + 1),取出了整个数组的地址,最后指向的是最后一个元素的后面,但仍然是地址,所以结果是8。

sizeof(&arr[0] + 1),取出了首元素的地址,+1指向了第二个元素,还是地址,所以结果是8。

初识C语言·指针(5)_第2张图片

代码2:

int main()
{
	char arr[] = { 'a','b','c','d','e','f' };
	printf("%zd\n", strlen(arr));
	printf("%zd\n", strlen(arr + 0));
	printf("%zd\n", strlen(*arr));
	printf("%zd\n", strlen(arr[1]));
	printf("%zd\n", strlen(&arr));
	printf("%zd\n", strlen(&arr + 1));
	printf("%zd\n", strlen(&arr[0] + 1));
	return 0;
}

如果sizeof计算的是地址的话,我们可以很容易得出答案,但是strlen呢?strlen能传一个具体的值过去吗?当然不能,能传地址吗?能,但是能不能找到结束标志就是另一个说法了。

所以我们可以很直观的发现,strlen(arr[1])和strlen(*arr)是会进行报错的,最好自己写一下调试看一下。

那么我们在一起看strlen(arr),strlen(arr + 0),strlen(&arr),strlen(&arr + 1),strlen(&arr[0] + 1),我们知道系统打印地址的时候,如果有一串地址的话,只会打印首地址,所以arr,arr+ 0 &arr,传过去的时候都是该字符数组的首元素地址,那么结果都是一样的,答案是6吗?

哈哈!错辣!因为这个字符数组一没手动给它结束标志,二没初始化它的空间,所以'\0'的位置是不确定的,所以结果是不确定的,结果都是随机值,但是它们的值都是一样的。那么我们再看strlen(&arr + 1)和strlen(&arr[0] + 1),&arr + 1表示的是整个地址取出来进行运算,最后指向的是字符数组最后一个元素的后面,而上面三个的地址是从首元素开始的,所以&arr + 1的结果会比上面的少6,但是仍然是个随机值,那么,&arr[0] + 1就是一样的了,就不过多介绍了。

初识C语言·指针(5)_第3张图片

代码3:

int main()
{
	char arr[] = "abcdef";
	printf("%zd\n", sizeof(arr));
	printf("%zd\n", sizeof(arr + 0));
	printf("%zd\n", sizeof(*arr));
	printf("%zd\n", sizeof(arr[1]));
	printf("%zd\n", sizeof(&arr));
	printf("%zd\n", sizeof(&arr + 1));
	printf("%zd\n", sizeof(&arr[0] + 1));
	return 0;
}

这串代码我们可以发现和第一串代码很像,可以说是一模一样,不同的是数组的形式不一样,是用双引号括起来的,同样的是没有初始化,但是唯一的区别就是双引号的字符数组有'\0',但是你看不到,所以答案的唯一差别只有第一个计算数组的大小,代码1的答案是6,但是这个因为有结束标志,所以答案是7。其他的是一模一样的。

代码4:

int main()
{
	char arr[] = "abcdef";
	printf("%zd\n", strlen(arr));
	printf("%zd\n", strlen(arr + 0));
	printf("%zd\n", strlen(*arr));
	printf("%zd\n", strlen(arr[1]));
	printf("%zd\n", strlen(&arr));
	printf("%zd\n", strlen(&arr + 1));
	printf("%zd\n", strlen(&arr[0] + 1));
	return 0;
}

这串代码与代码2的区别就是数组的区别,有结束标志的,那么同理,*arr arr[1]都会报错,因为传的不是地址,其他的arr arr+0 &arr传的地址都是首元素的地址,结果是6, &arr[0] + 1传的是第二个元素的地址,所以结果是5,&arr + 1因为跳过了整个数组,所以答案是随机值。

代码5:

int main()
{
	char* p = "abcdef";
	printf("%zd\n", sizeof(p));
	printf("%zd\n", sizeof(p + 1));
	printf("%zd\n", sizeof(*p));
	printf("%zd\n", sizeof(p[0]));
	printf("%zd\n", sizeof(&p));
	printf("%zd\n", sizeof(&p + 1));
	printf("%zd\n", sizeof(&p[0] + 1));
	return 0;
}

char* 的指针p指向的是常量字符串的首元素地址,也就是a,但是p始终是一个指针变量,所以

p p+1 的结果都是8,因为是指针。

&p &p + 1  &p[0] + 1答案都是8,但是传过去的是指针变量p的地址,这点需要注意。

p[0] *p传过去的都是首元素字符a,所以结果都是1。

初识C语言·指针(5)_第4张图片

代码6:

int main()
{
	char* p = "abcdef";
	printf("%zd\n", strlen(p));
	printf("%zd\n", strlen(p + 1));
	printf("%zd\n", strlen(*p));
	printf("%zd\n", strlen(p[0]));
	printf("%zd\n", strlen(&p));
	printf("%zd\n", strlen(&p + 1));
	printf("%zd\n", strlen(&p[0] + 1));
	return 0;
}

首先我们需要知道的是常量字符串也是有结束标志的,只是数组是在栈区存放,常量字符串是在i静态区存储的而已,但是都有'\0'这个结束标志。

*p p[0]都会报错,因为传的是个具体的值。

p &p的答案都是6,因为传的都是首元素的地址。

p + 1,&p[0] + 1答案都是5,因为传的都是第二个元素的地址。

&p + 1的答案是随机值,指向的位置在'\0'后面了。


iii)二维数组

int main()
{
	int arr[3][4] = { 0 };
	printf("%zd\n", sizeof(arr));
	printf("%zd\n", sizeof(arr[0][0]));
	printf("%zd\n", sizeof(arr[0]));
	printf("%zd\n", sizeof(arr[0] + 1));
	printf("%zd\n", sizeof(*(arr[0] + 1)));
	printf("%zd\n", sizeof(arr + 1));
	printf("%zd\n", sizeof(*(arr + 1)));
	printf("%zd\n", sizeof(&arr[0] + 1));
	printf("%zd\n", sizeof(*(&arr[0] + 1)));
	printf("%zd\n", sizeof(*arr));
	printf("%zd\n", sizeof(arr[3]));
	return 0;
}

sizeof(arr),表示的是计算整个二维数组的大小,该二维数组有12个空间,是整型数组,所以答案是48。

sizeof(arr[0][0]),表示的是计算第一行第一列的元素的大小,所以结果是4。

sizeof(arr[0]),表示的是计算整个第一行的大小,因为传的是整个第一行的地址,所以答案是16。

sizeof(arr[0] + 1),表示的是传了第一行的地址,然后对地址进行运算,所以计算的是指针的大小,结果就是8。

sizeof(*(arr[0] + 1)),表示的是计算整个第一行的第二列元素的大小,arr[0]可以看成第一行数组的数组名。

sizeof(arr + 1),表示的是传过去第一行的地址,然后指针运算,所以计算的是指针的大小,答案是8。

sizeof(*(arr + 1)),表示的是计算第二行的大小,答案是16。

sizeof(&arr[0] + 1),表示的是传了第一行的地址进去,然后进行指针运算,但终究是指针,所以答案是8。

sizeof(*(&arr[0] + 1)),表示的是对整个第二行的地址解引用,计算的是整个第二行的大小,结果是16。

sizeof(*arr),表示的是计算首元素的大小,因为传的是第一行的地址,然后对它解引用,答案就是4。

sizeof(arr[3]),这里有一个重要的知识点就是sizeof()内部是不进行运算的,所以虽然arr[3]越界了,但是sizeof不会关心这点,当你传进去的时候,里面与元素类型就确定了,,不会进行运算所以sizeof计算的时候只知道arr,后面的下标方括号就根本不会管了,那么答案就是16。

初识C语言·指针(5)_第5张图片


3 指针运算题目详解

题目1:

int main()
{
	int arr[5] = { 1,2,3,4,5 };
	int* pa = (int*)(&arr + 1);
	printf("%d,&d", *(arr + 1), *(pa - 1));
	return 0;
}

先说为什么会强制转化为(int*),因为&arr + 1最后指向的是内存的一块我们并不知道的空间,虽然我们的指针指向了那里,但我们是没有权限去使用的,为了严谨起见我们强制转化一下。

那么arr + 1 指向的是第二个元素,然后解引用得到2,pa - 1指向的是5最后的一块空间,运算之后解引用得到的就是5。

初识C语言·指针(5)_第6张图片

题目2:

struct Test
{
	int num;
	char* pc;
	short a;
	char b;
	short c;
}*p = (struct Test*)0x100000;
int main()
{
	printf("%p\n", p + 0x1);//100018
	printf("%p\n", (unsigned long)p + 0x1);//100001
	printf("%p\n", (unsigned int*)p + 0x1);//100004
	return 0;
}

我们创建了一个结构体Test ,且这个结构体的大小是18字节(为什么是18)会在后面的结构体的章节中的内存对齐涉及到。还创建了一个结构体指针p,并把0x100000强行转化为结构体指针,作为p存放的地址。

p + 0x1,因为p是结构体指针,结构体大小是18,所以会跳过一整个结构体的大小18,最后指向的是100018的位置。

(unsigned  long)p + 0x1,表示的是先把p强制转化为无符号长整型,在加1,那么p里面的100000就变成了一个整型,加1,结果就是100001了,但是用%p打印确实不妥,会有警告。

(unsigned int*)p + 0x1,表示的是把p强制转化为无符号整型指针变量,在加1进行指针运算,因为无符号整型的大小是4,所以加1跳过一个无符号整型的大小,结果就是100004。

初识C语言·指针(5)_第7张图片

题目3:

int main()
{
	int arr[3][2] = { (0,1),(2,3),(4,5) };
	int* p = arr[0];
	printf("%d", p[0]);
	return 0;
}

我们创建了一个3行2列的整型数组,并且采用了不完全初始化,给进去3个逗号表达式,逗号表达式的结果是以最后一个表达式为结果,所以实际上的初始化是int arr[3][2] = {1,3,5};

前面提及可以把arr[0]看成第一行的数组名,所以p[0]就是访问的第一行第一列的元素,那么结果就是1。

初识C语言·指针(5)_第8张图片

题目4:

int main()
{
	int arr[2][5] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p1 = (int*)(&arr + 1);
	int* p2 = (int*)(*(arr + 1));
	printf("%d %d", *(p1 - 1), *(p2 - 1));
	return 0;
}

&arr + 1表示的是取出整个数组的地址 + 1,所以指向的是10之后的那块空间,打印的时候 - 1就指向回来了,指到了10的位置,所以打印的结果是10。

arr + 1表示的是取出整个第一行的地址+1进行指针运算,得到的是整个第二行的地址,那么低地址位就是6,打印的时候- 1在解引用,指向的位置就是5的位置,所以打印的结果就是5。

题目5:

int main()
{
	char* p[] = { "work","at","alibaba" };
	char** pa = p;
	pa++;
	printf("%s\n", *pa);
	return 0;
}

我们创建了一个指针数组,里面存放了三个常量字符串的首元素地址,再创建了一个二级指针存放p的地址,原本p是指向work中的w字符的,但是pa++后,指向的就是at中的a字符了。

打印的结果就是at。

初识C语言·指针(5)_第9张图片

题目6:

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;
}

这是本文的终极boss了,理论性极强,咱们就尽量理解咯。

我们创建了一个字符指针数组,存放的四个首元素地址分别是E N P F的地址,又创建了一个二级指针数组,存放的是一级字符指针数组进行运算后的地址,分别是F P N  E的地址,最后创建了一个三级指针,指向的是二级指针的地址。

**++cpp,表示的是先对三级指针cpp自增,最后解引用,自增之后指向的是c + 2,两次解引用之后指针指向的是P,所以打印的结果是POINT。

*--*++cpp + 3,表示的是先对三级制作cpp自增,但是要注意的是cpp已经自增过一次,所以自增之后指向的是c + 1,也就是N,在自减一次,指向的是E,(*的优先级比较高)解引用之后指向的是E,+3最后指向的是第二个E,所以打印的结果是ER。

*cpp[-2] + 3,这里cpp已经自增两次了,所以解引用之后指向的应该是N,结合方括号之后,-2得到的是c + 3,也就是F,解引用 + 3之后指向的是s的位置,所以打印的结果是ST。

cpp[-1][-1] + 1,上次的cpp是没有自增的,所以解引用之后指向的是c + 1位置,那么cpp[-1]就是c + 2,c + 2指向的是P,P的地址 - 1,也就指向了NEW中N的位置,+1也就是指向了E,所以最后打印的时候,打印的是EW。

初识C语言·指针(5)_第10张图片


感谢阅读!

你可能感兴趣的:(c语言,算法,开发语言)