指针进阶(三)

嘿嘿,uu们,今天呢我们来剖析指针进阶的剩下部分,好啦,废话不多讲,开干!

1:回调函数

概念:回调函数是指一个通过函数指针调用的函数,如果将函数的地址作为参数传递给另外一个函数,当这个指针被用来调用所指向的函数时,那么这个被调用的函数就是回调函数.回调函数不是由该函数的实现放直接调用,而是在特定的事件或条件发生时由另外一方调用的,用于对该事件或条件进行响应.

在之前我们有使用过通过函数指针来实现一个小型计算器,在那种场景下,我们是将调用的函数以参数的形式传递过去,使用函数指针进行接收,函数指针指向什么函数就调用什么函数,这里其实使用的就是回调函数.

2:qsort函数

了解了回调函数之后,接下来我们来看一个使用回调函数的例子,qsort函数,qsort函数是一个库函数,底层采用的是快速排序的方式对数据进行排序,该函数能够用来排序任意类型的数据.对qsort函数有了基本了解后,接下来我们来看官方的介绍

里面有四个参数

(1):void * base------>待排序数组的首元素地址;

(2):size_t num------->待排序数组的元素个数;

(3):size_t size ------->待排序数组中的一个元素的大小;

//函数指针,compar指向了一个函数,该函数用来比较两个元素的大小,e1与e2存放的是两个元素的地址

(4):int (*compar) (const void *e1,const void *e2));

uu们可能对上面的void * 这一数据类型有些小问题,博主在这里对其简单补充下.

2.1:void * 指针介绍

  • void * 是无具体类型的指针.
  • void * 类型的指针能够存放任意数据类型的地址.
  • void *类型的指针不能对其进行解引用操作,也不能进行 +-整数的操作.

2.1.1:代码1

cpp 复制代码
#include <stdio.h>

int main() 
{
	int a = 10;
	double b = 1.5;
	void* pa = &a;
	void* pb = &b;
	printf("%p\n", pa);
	printf("%p\n", pb);
	return 0;
}

2.1.2:代码2

cpp 复制代码
#include <stdio.h>

int main() 
{
	int a = 10;
	double b = 1.5;
	void* pa = &a;
	void* pb = &b;
	pa++;
	pb++;
	return 0;
}

2.2:qsort函数的使用

了解了qsort函数后,接下来我们来看几段代码,观察下qsort函数的使用.

2.2.1:代码1

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>

int cmp(const void * e1,const void * e2)
{
	return (*(int*)e1) - (*(int*)e2);
}
int main()
{
	int arr[10] = { 1,5,6,7,8,9,10,2,3,4 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	printf("排序前:>");
	for (size_t i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
	printf("排序后:>");
	qsort(arr, sz, sizeof(int), cmp);
	for (size_t i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

上面则是通过使用qsort函数对整型数据进行排序,是以升序的方式,可能uu们会有如下的疑惑

(1):qsort函数的第四个参数的函数指针所指向的函数的参数为什么是const void *

  • 博主将通过一个小故事来带着理解下,譬如说有两个程序员,一个是张三,一个是李四.有一天呢,李四弄了个榜单但是这个排行榜还没有进行排序,这个时候李四就去找张三帮忙,张三啊,你帮我给这个榜单进行一个排序吧,这个时候张三就问,那你希望我按照什么方式来排序嘞,是按照榜单上的名字还是按照榜单的上编号还是按照榜单上的总成绩来排序呢?到这里为止,uu们有没有发现,张三对榜单的排序是不是有多种方式,而每种排序方式所排序的数据类型是不一样的,这个时候我们再回到qsort函数,qsort函数是C语言的一个库函数,开发者在写这个函数并不知道使用者会按照什么样的方式去进行排序,而void *又能够存放任意数据类型的地址,因此开发者在编写的时候则是使用了void *类型的指针,这样子使用者在使用的时候能够根据自己想要的排序方式进行任意的变化.

(2):函数指针所指向的函数的返回值为什么是int

  • 对于这个问题,博主将通过官方的文档来解释

在官方的文档中,表示了compar函数的返回值的意义

(1): > 0:说明p1所指向的元素在 p2所指向的元素的前面;

(2):== 0:说明p1所指向的元素与p2所指向的元素是等价的;

(3):< 0:说明p1所指向的元素在p2所指向的元素的后面;

解决了上述的疑问后,我们再回到这段代码

第一个参数arr为数组名,数组名在一般情况下表示的是首元素地址.

第二个参数sz则为待排序数组的元素个数

由于待排序数组的元素的数据类型为整型,因此第三个参数为 sizeof(int)即4个字节

第四个参数是函数指针,指向的是cmp函数,由于排序的元素都是整型,因此要对其进行强制类型转换成 int *,然后再对其解引用计算其返回值.

在上面我们有了解到,回调函数是指一个通过函数指针调用的函数,而cmp函数则是将其地址作为qsort函数的参数,因此这里的cmp函数则是回调函数!

2.2.2:代码2

在上面我们有讲到过,qsort函数能够排序任意类型的数据,那么我们再来看下面这段代码.

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int cmp(const void ** e1,const void ** e2)
{
	return strcmp((const char*)(*e1),(const char*)(*e2));
}
int main()
{
	char* arr[10] = { "ly","wj","duck","gzx","yx","ljh","cj","jwt","lat","hjy"};
	int sz = sizeof(arr) / sizeof(arr[0]);
	printf("排序前:>");
	for (size_t i = 0; i < sz; i++)
	{
		printf("%s ", arr[i]);
	}
	printf("\n");
	qsort(arr, sz, sizeof(arr[0]), cmp);
	printf("排序后:>");
	for (size_t i = 0; i < sz; i++)
	{
		printf("%s ", arr[i]);
	}
	return 0;
}

上面这段代码呢,则是通过qsort函数实现了对字符串的排序,可能uu们在想字符串怎么能够对其进行排序呢?博主将带着uu们一步一步地剖析.

(1):qsort函数的第四个参数cmp函数的两个参数为什么是const void **类型

  • 在函数那一章节我们有讲到过,函数在传值传参时,形参是实参的一份临时拷贝,改变形参不会改变实参,而数组中的每个元素的元素类型都是char * 类型的,那么要令实参能够发生变化,则需要传址传参,因此这里使用 const void ** 来存储一级指针的地址.

(2):strcmp函数

uu们应该发现了,博主在cmp函数那里使用了strcmp函数,这是C语言的一个库函数,用于比较字符串的大小的,并且在使用的时候需要包含<string.h>这个头文件.

  • strcmp函数是字符串比较函数,它会比较两个字符串的大小,是通过比较字符串中的每一个字符的ascii码值,譬如:若 str1中的第一个字符与str2中的第一个字符相等则继续向后比较,直到遇到第一个不匹配的字符或者比较结束.
  • 当返回值 > 0:说明 str1 中 第一个不匹配的字符大于str2中第一个不匹配的字符.
  • 当返回值 ==0:说明 str1 中 的每一个与str2中的每一个字符都相等.
  • 当返回值 < 0:说明 str1 中 第一个不匹配的字符小于str2中第一个不匹配的字符.

(3):由于strcmp函数的参数类型都是const char *,而在传参时是通过传址传参,因此首先对二级指针 void ** 解引用得到一级指针 void *,接着再对其进行强制类型转换.

3:冒泡排序模拟实现qsort函数

在数组那一章节,博主讲了一个冒泡排序,冒泡排序的核心是:两两互相比较然后进行交换.这里博主就不再细讲啦,忘了uu可以去看看博主的数组那一篇博客哦!

cpp 复制代码
#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
	int arr[10] = { 1,3,2,5,6,7,8,9,10,4 };
	//求出数组中的元素个数
	int sz = sizeof(arr) / sizeof(arr[0]);
	printf("排序前:>");
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	//确定总趟数
	for (int i = 0; i < sz - 1; i++)
	{
		//每进行一次冒泡排序,趟数要减少,因此- i
		for (int j = 0; j < sz - 1 - i; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
	}
	printf("\n");
	printf("排序后:>");
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}

	return 0;
}

以上呢就是我们之前实现冒泡排序的代码,那么现在博主将使用冒泡排序来模拟实现qsort函数.首先是参数部分,qsort函数有4个参数

(1):第一个参数是待排序的数组的首元素地址.

(2):数组的元素的个数.

(3):数组的每一个元素的所占的内存空间的大小.

(4):一个函数指针,用来比较元素的大小的.

那么我们冒泡排序的参数应该如下面所示

cpp 复制代码
void Bubble_sort(void * base,size_t num,size_t size,int(*compare)(const void * e1,const void * e2));

PS:由于不确定数组的元素是什么类型的,那么我们就用void *指针来封装这个首元素地址.

然后接下来我们再来看还有哪些部分要变化,首先能够确定的是,两个for循环是不需要变化,因为冒泡排序的本质是两两互相交换.

那么接下来看循环的判断部分

由于此时第一个参数是void *,那么我们则不能用[]的方式去获取元素了,那么有什么办法呢?在指针初阶那一阶段,博主有讲到过,指针的类型决定了其在解引用以及+-整数的时候跳过几个字节.那么首先不确定要排序的数据类型是什么,但是有这个数据元素的所占的内存空间大小,并且也有待排序的数组的元素个数.那么我们是不是可以用char *指针来进行访问呢,因为char * 指针每次 +-1都是跳过一个字节,然后我又有了这个元素的所占的内存空间的大小,并且又有这个cmp函数可以来比较大小,那么就能很轻易地去改写if的判断条件.

cpp 复制代码
void Bubble_sort(void * base,size_t num,size_t size,int(*compare)(const void * e1,const void * e2))
{
	for (int i = 0; i < num; i++)
	{
		for (int j = 0; j < num - i - 1; j++)
		{
			if (compare((char*)base + j * size, (char*)base + (j + 1) * size) > 0)
			{
				;
			}
		}
	}
}

判断是否符合交换条件
由于不确定要排序的数据类型,因此使用字符指针,因为字符指针每+1是跳过一个字节
当确定比较的数据元素的类型,由于冒泡排序的核心是两两互相比较
那么此时通过初始地址base + j * size(数据类型的大小)得到第一个元素的地址
然后通过base + (j + 1) * size得到第二个比较元素的地址

然后接下来就是交换部分了,由于我不确定我要交换的数据类型,那么此时我同样可以使用char *指针,这个时候就是一个字节一个字节地进行交换,并且需要传第三个参数,每一个元素所占的内存空间的大小.那么此时交换函数swap如下

cpp 复制代码
void swap(char* buf1, char* buf2, size_t size)
{
	int i = 0;
	for (i = 0; i < size; i++)
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}


void bubble_sort(void* base, size_t num, size_t size, int (*compare)(const void* e1, const void* e2))
{
	int i = 0;
	int j = 0;
	//确定冒泡排序的趟数
	for (i = 0; i < num - 1; i++)
	{
		for (j = 0; j < num - 1 - i; j++)
		{
			//判断是否符合交换条件
			/*
			* 由于不确定要排序的数据类型,因此使用字符指针,因为字符指针每+1是跳过一个字节
			* 当确定比较的数据元素的类型,由于冒泡排序的核心是两两互相比较
			* 那么此时通过初始地址base + j * size(数据类型的大小)得到第一个元素的地址
			* 然后通过base + (j + 1) * size得到第二个比较元素的地址
			*/
			if (compare((char*)base + j * size, (char*)base + (j + 1) * size) > 0)
			{
				//调用交换函数进行交换
				swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
			}
		}
	}
}

3.1:总代码(排序整型数据--->升序)

cpp 复制代码
#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int cmp(const void * e1,const void * e2)
{
	return *(int*)e1 - *(int*)e2;
}

void swap(char* e1,char*e2,size_t size)
{
	for (size_t i = 0; i < size; i++)
	{
		char temp = *e1;
		*e1 = *e2;
		*e2 = temp;
		e1++;
		e2++;
	}
}

void Bubble_sort(void * base,size_t num,size_t size,int(*compare)(const void * e1,const void * e2))
{
	for (int i = 0; i < num; i++)
	{
		for (int j = 0; j < num - i - 1; j++)
		{
			if (compare((char*)base + j * size, (char*)base + (j + 1) * size) > 0)
			{
				swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
			}
		}
	}
}
//
int main()
{
	int arr[10] = { 1,3,2,5,6,7,8,9,10,4};
	size_t sz = sizeof(arr) / sizeof(arr[0]);
	Bubble_sort(arr, sz,sizeof(arr[0]),cmp);
	for(int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

3.2:总代码(排序结构体数据--->按照年龄排序)

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct Person
{
	char name[20];
	int age;
}Person;

int cmp(const void * e1,const void * e2)
{
	return ((struct Person *)e1)->age - ((struct Person *)e2)->age;
}

void swap(char* e1,char*e2,size_t size)
{
	for (size_t i = 0; i < size; i++)
	{
		char temp = *e1;
		*e1 = *e2;
		*e2 = temp;
		e1++;
		e2++;
	}
}

void Bubble_sort(void * base,size_t num,size_t size,int(*compare)(const void * e1,const void * e2))
{
	for (int i = 0; i < num; i++)
	{
		for (int j = 0; j < num - i - 1; j++)
		{
			if (compare((char*)base + j * size, (char*)base + (j + 1) * size) > 0)
			{
				swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
			}
		}
	}
}
//
int main()
{
	Person arr[4] = { {"张三",20},{"李四",21},{"王五",15},{"小明",6} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	printf("排序前:>");
	for (int i = 0; i < sz; i++)
	{
		printf("%s:>%d ", arr[i].name, arr[i].age);
	}
	printf("\n");
	Bubble_sort(arr, sz,sizeof(arr[0]),cmp);
	printf("排序后:>");
	for(int i = 0; i < sz; i++)
	{
		printf("%s:>%d ", arr[i].name,arr[i].age);
	}
	return 0;
}

3.3:总代码(排序结构体数据---->按照名字排序)

cpp 复制代码
#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct Person
{
	char name[20];
	int age;
}Person;

int cmp(const void * e1,const void * e2)
{
	return strcmp(((struct Person *)e1)->name,((struct Person *)e2)->name);
}

void swap(char* e1,char*e2,size_t size)
{
	for (size_t i = 0; i < size; i++)
	{
		char temp = *e1;
		*e1 = *e2;
		*e2 = temp;
		e1++;
		e2++;
	}
}

void Bubble_sort(void * base,size_t num,size_t size,int(*compare)(const void * e1,const void * e2))
{
	for (int i = 0; i < num; i++)
	{
		for (int j = 0; j < num - i - 1; j++)
		{
			if (compare((char*)base + j * size, (char*)base + (j + 1) * size) > 0)
			{
				swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
			}
		}
	}
}
//
int main()
{
	Person arr[4] = { {"张三",20},{"李四",21},{"王五",15},{"小明",6} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	printf("排序前:>");
	for (int i = 0; i < sz; i++)
	{
		printf("%s:>%d ", arr[i].name, arr[i].age);
	}
	printf("\n");
	Bubble_sort(arr, sz,sizeof(arr[0]),cmp);
	printf("排序后:>");
	for(int i = 0; i < sz; i++)
	{
		printf("%s:>%d ", arr[i].name,arr[i].age);
	}
	return 0;
}

4:sizeof与strlen的对比

4.1:sizeof

在操作符那一章节,我们学习过sizeof操作符,sizeof计算的是变量所占据的内存空间的大小,单位是字节,若操作数为数据类型的话,则计算的是该数据类型在空间中所占的大小.

PS:sizeof只关注所占据的内存空间的大小,不在乎内存中所占据的数据.

cpp 复制代码
#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
	int a = 0;
	double b = 0.0;
	float c = 0.0f;
	printf("变量a所占据的内存空间大小为:>%zd\n", sizeof(a));
	printf("变量b所占据的内存空间大小为:>%zd\n", sizeof(b));
	printf("变量c所占据的内存空间大小为:>%zd\n", sizeof(c));
	return 0;
}

4.2:strlen

strlen是C语言的一个库函数,用于求取字符串的长度,其原型如下:

strlen函数统计的是从参数str向后,\0之前的字符个数,因为之前我们学习过字符串的结束标志是\0.

PS:strlen函数会一直向后\0,直到找到为止,因此在使用时可能会存在越界的检查.我们来看下面这段代码

cpp 复制代码
#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
	char arr[] = { 'a','b','c','d' };
	char str[] = "abcd";
	printf("%d\n", sizeof(arr));
	printf("%d\n", sizeof(str));
	printf("\n");
	printf("%d\n", strlen(arr));
	printf("%d\n", strlen(str));
	return 0;
}

(1)首先sizeof(arr)求的是arr数组在整个内存空间所占据的大小,在数组那一章节博主有讲到过,数组名一般情况下表示的是首元素地址,但是有两种特殊情况表示的是整个数组

  • sizeof(数组名),计算的是整个数组的大小算的是整个数组的大小,sizeof内部单独放一个数组名,数组名表示整个数组。
  • &数组名,取出的是整个数组的地址。&数组名,数组名表示整个数组

而arr数组里头有4个字符变量,一个字符变量所占据的内存空间大小为1个字节因此sizeof(arr)的结果为4.

(2):有的uu根据第一个解释就在想,按照上面这种解释的方法的话,那么sizeof(str)应该也是4啊,这个5是怎么来的呢?uu们这里要注意,arr是字符数组,str是字符串,字符串有一个特性,其结束标志是\0,因此str中还有一个\0字符,所以这里sizeof(str)的结果是5.

我们可以通过监视窗口观察arr与str中所存储的内容.

很明显,字符串str比字符数组arr多了\0字符.

(3):由于字符串str中的\0字符有4个字符,因此strlen(str)的结果为4,这里相信uu们没有什么问题.

(4):在上面博主有讲到过strlen统计的是\0之前的字符个数,如果没有找到\0字符,则会一直向后找,直到找到为止,很明显字符数组arr中有4个字符,但是没有\0字符,因此strlen函数在求其长度的时候会一直向后找,找到\0,这个时候求出来的值是随机值,具体是多少要看编译器了,博主求出来的结果是15.如果uu们多运行几次这段代码的话,会发现求出来的值是不一样滴,这里博主就不多次运行啦!

4.3:sizeof与strlen的对比

sizeof

  • sizeof是操作符.
  • sizeof计算操作数的类型在内存空间中所占据的空间大小,单位是字节.
  • 不关注内存中存放的数据.

strlen

  • strlen是库函数,使用时需要包含头文件<string.h>.
  • strlen是计算字符串⻓度的,统计的是 \0 之前的字符个数.
  • strlen会关注内存中是否有 \0 ,如果没有 \0 字符,则会持续往后找,因此会存在越界的可能性.

5:常见数组与指针笔试题

熟悉了sizeof与strlen的区别后,接下来我们来看一些常见的笔试题.uu们可以自己先做一下哦~

5.1:一维数组

cpp 复制代码
#include <stdio.h>
int main()
{
	int arr[] = { 1,2,3,4 };
	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));
	printf("%d\n", sizeof(*&arr));
    printf("%d\n", sizeof(&arr + 1));
	printf("%d\n", sizeof(&arr[0]));
	printf("%d\n", sizeof(&arr[0] + 1));
	return 0;
}

(1):在数组阶段,我们讲到过,数组名在一般情况下表示的是首元素地址,但是存在两种特例

  • sizeof(数组名),计算的是整个数组的大小算的是整个数组的大小,sizeof内部单独放一个数组名,数组名表示整个数组.
  • &数组名,取出的是整个数组的地址。&数组名,数组名表示整个数组名

因此,这里sizeof(arr)计算的是整个数组的大小,1个整型所占据的内存空间大小为4个字节,而arr数组里头有4个整型,因此sizeof(arr)的结果是16.

(2):与第1种情况相比,第2个多了个 +0,我们说数组名表示的是首元素地址,+ 0之后则依旧是首元素地址,这里有个小细节不知道uu们有没有观察到,sizeof(数组名)这里,数组名只能够单独出现,不能够有其他表达式,否则表示则不是整个数组.所以,sizeof(arr + 0)这里计算的则是地址的大小,地址的大小在指针初阶我们有学习到,要取决于平台,32位平台下地址的大小为4,64位平台下地址的大小为8,因此,这里的sizeof(arr + 0)的结果为 4 或者 8.

(3):数组名表示的是首元素地址,这里对其解引用得到的是第1个元素,因此这里sizeof(*arr)计算的是arr数组中第1个元素 1 所占据的内存空间,1为整型数据,占据4个字节,因此这里的结果是4

(4):数组名表示的是首元素地址,地址 + 1以后还是地址,因此这里的sizeof(arr + 1)计算的是地址所占据的内存空间的大小,所以这里的结果为 4 或者 8.

(5):arr[1]表示通过下标访问数组中的第二个元素,因此这里的sizoef(arr[1])计算的是数组中的第2个元素所占据的内存空间的大小,所以这里的结果为 4.

(6)这一题很多uu会掉坑,很多uu的结果会这么想:&数组名,取出的是整个数组的地址.所以这里的结果是16.如果这么想的话就大错特错啦,虽然&数组名,取出的是整个数组的地址,但是,数组的地址也是地址呀,因此这里依旧计算的是地址所占据的内存空间的大小,所以这sizeof(&arr)的结果是 4 或者 8.

(7):这里首先&arr,取出的是整个数组的地址,得到数组的地址后,再对其解引用,这样子的话,得到的就是整个数组,因此这里sizeof(*&arr)计算的是整个数组的大小,所以结果为16.

uu们还可以这样子去理解,这里的解引用操作符 *与 取地址操作符 &相抵消了,那么剩下来的则是sizeof(arr)等价于sizeof(数组名),计算的是整个数组的大小.

(8):这里首先&arr,取出的是整个数组的地址,接着对地址 + 1,地址 + 1以后还是地址,因此这里的sizeof(&arr + 1)计算的是地址所占据的内存空间的大小,所以这里的结果为 4 或者 8.

(9):由于[]操作符的优先级高于&,因此arr首先与[]结合,这里则是通过下标访问拿到首元素 1,然后再通过取地址符号该元素的地址,因此sizeof(&arr[0])计算的是首元素的地址所占据的内存空间的大小,因此结果为 4 / 8.

(10):由于[]操作符的优先级高于&,因此arr首先与[]结合,这里则是通过下标访问首元素 1,然后再通过取地址符号该元素的地址,接下来再对地址 + 1,地址 +1以后依旧是地址并且这里得到的地址是第2个元素的地址(这里不明白为什么是第二个元素的地址的uu可以再去看看博主指针初阶那篇博客哦),因此这里的sizeof(&arr[0] + 1)计算的是地址的大小,结果为 4 或者 8.

32位平台
64位平台

5.2:字符数组与字符串

5.2.1:字符数组(sizeof)

cpp 复制代码
int main()
{
	char arr[] = { 'a','b','c','d','e' };
	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;
}

(1):这里的sizeof(arr)属于两种情况之一,因此这里计算的是整个数组的大小,1个字符型数据在内存空间中占据1个字节,而arr数组中有5个字符型数据,因此这里的结果为 5.

(2):数组名表示的是首元素地址,+ 0之后则依旧是首元素地址,所以,sizeof(arr + 0)这里计算的则是地址的大小,因此,这里的sizeof(arr + 0)的结果为 4 或者 8.

(3):数组名表示的是首元素地址,这里对其解引用得到的是第1个元素,因此这里sizeof(*arr)计算的是arr数组中第1个元素 'a' 所占据的内存空间,'a'为字符型数据,占据1个字节,因此这里的结果是1.

(4):arr[1]表示通过下标访问数组中的第2个元素,因此这里的sizoef(arr[1])计算的是数组中的第2个元素所占据的内存空间的大小,所以这里的结果为 1.

(5)&数组名,取出的是整个数组的地址.数组的地址也是地址,因此这里依旧计算的是地址所占据的内存空间的大小,所以这sizeof(&arr)的结果是 4 或者 8.

(6):这里首先&arr,取出的是整个数组的地址,接着对地址 + 1,地址 + 1以后还是地址,因此这里的sizeof(&arr + 1)计算的是地址所占据的内存空间的大小,所以这里的结果为 4 或者 8.

(7):由于[]操作符的优先级高于&,因此arr首先与[]结合,这里则是通过下标访问首元素 'a',然后再通过取地址符号该元素的地址,接下来再对地址 + 1,地址 +1以后依旧是地址并且这里得到的地址是第2个元素的地址,因此这里的sizeof(&arr[0] + 1)计算的是地址的大小,结果为 4 或者 8.

32位平台
64位平台

5.2.2:字符数组(strlen)

cpp 复制代码
int main()
{
	char arr[] = { 'a','b','c','d','e' };
	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;
}

在上面博主有讲到过,strlen函数统计的是\0之前的字符个数,如果没有遇到\0,则会继续向后找,直到遇到\0,因此有存在越界的可能性.

(1):arr为数组名,在一般情况下表示的是首元素地址,因此这里的strlen(arr)是从字符'a'开始统计,直到遇到\0为止,我们仔细观察看这个字符数组,发现里面没有'\0',因此在统计的时候,会发生越界的情况,所以这里的结果则是随机值.

(2):arr为数组名,表示的是首元素地址,+ 0之后依旧表示的是地址且是首元素地址,因此这里的strlen(arr)是从字符'a'开始统计,直到遇到'\0'为止,由于字符数组arr里面没有'\0',因此在统计的时候,会发生越界的情况,所以这里的结果则是随机值.

(3):arr为数组名,表示的是首元素地址,对其解引用后得到首元素字符'a',在前面的时候,我们知道字符型是整型家族中的一种,字符型数据在内存中是以ascii码存储的,'a'的ascii码为97,从地址的角度出发,相当于使用strlen函数去访问97那一块地址空间,可是97那块空间并不是数组arr的空间,所以发生了空间的非法访问,因此这里的结果是错误的.

(4):arr[1]表示通过下标访问数组中的第2个元素'b','b'的ascii码为98,从地址的角度出发,相当于使用strlen函数去访问98那一块地址空间,由于98那块空间并不是数组arr的空间,所以发生了空间的非法访问,因此这里的结果是错误的.

(5):&arr表示取出的是数组的地址,uu们如果还记得的话,在数组那一章节,博主有讲到过,数组的地址与首元素的地址是一样的,因此这里的strlen(&arr)是从字符'a'开始统计,直到遇到'\0'为止,由于字符数组arr里面没有'\0',因此在统计的时候,会发生越界的情况,所以这里的结果则是随机值.

(6):&arr表示取出的是数组的地址,数组的地址 + 1跳过的大小是整个数组的大小,因此这里是strlen(&arr + 1)是从 'e'的下一个位置开始统计,直到遇到'\0'为止,由于字符数组arr里面没有'\0',因此在统计的时候,会发生越界的情况,所以这里的结果则是随机值.

(7)::由于[]操作符的优先级高于&,因此arr首先与[]结合,这里则是通过下标访问首元素 'a',然后再通过取地址符号该元素的地址,接下来再对地址 + 1,地址 +1以后依旧是地址并且这里得到的地址是第2个元素的地址即 'b'的地址,因此这里是strlen(&arr[0] + 1)是从 'b'的位置开始统计,直到遇到'\0'为止,由于字符数组arr里面没有'\0',因此在统计的时候,会发生越界的情况,所以这里的结果则是随机值.

5.2.3:字符串(sizeof)

cpp 复制代码
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));
	printf("%d\n", sizeof(&arr + 1));
	printf("%d\n", sizeof(&arr[0] + 1));
	return 0;
}

在前面的章节博主有讲到过,字符串的结束标志是'\0'并且字符串也是能够存储在字符数组里头的,并且在存储的时候存储的是首字符的地址.

(1):arr为数组名,并且是单独出现,因此这里的sizeof(arr)求的是整个数组所占的内存空间大小,由于字符串的结束标志是'\0',因此在计算的时候要带上'\0'字符,所以结果是 7.

(2):数组名表示的是首元素地址,+ 0之后则依旧是首元素地址,所以,sizeof(arr + 0)这里计算的则是地址的大小,因此,这里的sizeof(arr + 0)的结果为 4 或者 8.

(3):数组名表示的是首元素地址,这里对其解引用得到的是第1个元素,因此这里sizeof(*arr)计算的是arr数组中第1个元素 'a' 所占据的内存空间,'a'为字符型数据,占据1个字节,因此这里的结果是1.

(4):arr[1]表示通过下标访问数组中的第2个元素,因此这里的sizoef(arr[1])计算的是数组中的第2个元素所占据的内存空间的大小,所以这里的结果为 1.

(5)&数组名,取出的是整个数组的地址.数组的地址也是地址,因此这里依旧计算的是地址所占据的内存空间的大小,所以这sizeof(&arr)的结果是 4 或者 8.

(6):这里首先&arr,取出的是整个数组的地址,接着对地址 + 1,地址 + 1以后还是地址,因此这里的sizeof(&arr + 1)计算的是地址所占据的内存空间的大小,所以这里的结果为 4 或者 8.

(7):由于[]操作符的优先级高于&,因此arr首先与[]结合,这里则是通过下标访问首元素 'a',然后再通过取地址符号该元素的地址,接下来再对地址 + 1,地址 +1以后依旧是地址并且这里得到的地址是第2个元素的地址,因此这里的sizeof(&arr[0] + 1)计算的是地址的大小,结果为 4 或者 8.

32位平台
64位平台

5.2.4:字符串(strlen)

cpp 复制代码
int main()
{
	char arr[] = "abcdef";
	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):arr为数组名,在一般情况下表示的是首元素地址,因此这里的strlen(arr)是从字符'a'开始统计,直到遇到\0为止,由于字符串的结束标志是'\0',所以这里的结果则是6.

(2):arr为数组名,表示的是首元素地址,+ 0之后依旧表示的是地址且是首元素地址,因此这里的strlen(arr)是从字符'a'开始统计,直到遇到'\0'为止,由于字符串的结束标志是'\0',所以这里的结果则是6.

(3):arr为数组名,表示的是首元素地址,对其解引用后得到首元素字符'a',在前面的时候,我们知道字符型是整型家族中的一种,字符型数据在内存中是以ascii码存储的,'a'的ascii码为97,从地址的角度出发,相当于使用strlen函数去访问97那一块地址空间,可是97那块空间并不是数组arr的空间,所以发生了空间的非法访问,因此这里的结果是错误的.

(4):arr[1]表示通过下标访问字符串中的第2个元素'b','b'的ascii码为98,从地址的角度出发,相当于使用strlen函数去访问98那一块地址空间,由于98那块空间并不是数组arr的空间,所以发生了空间的非法访问,因此这里的结果是错误的.

(5):&arr表示取出的是数组的地址,uu们如果还记得的话,在数组那一章节,博主有讲到过,数组的地址与首元素的地址是一样的,因此这里的strlen(&arr)是从字符'a'开始统计,直到遇到'\0'为止,由于字符串的结束标志是'\0',所以这里的结果则是6.

(6):&arr表示取出的是数组的地址,数组的地址 + 1跳过的大小是整个数组的大小,因此这里是strlen(&arr + 1)是从 '\0'字符的下一个位置开始统计,直到遇到'\0'为止,所以这里的结果则是随机值.

(7)::由于[]操作符的优先级高于&,因此arr首先与[]结合,这里则是通过下标访问首元素 'a',然后再通过取地址符号该元素的地址,接下来再对地址 + 1,地址 +1以后依旧是地址并且这里得到的地址是第2个元素的地址即 'b'的地址,因此这里是strlen(&arr[0] + 1)是从 'b'的位置开始统计,直到遇到'\0'为止,由于字符串的结束标志是'\0',所以这里的结果则是 5.

5.2.5:指针存储字符串(sizeof)

在指针进阶(一)博主有讲到过,字符串能够存储在指针变量中,这个指针变量存储的是首元素的地址.

cpp 复制代码
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));
	return 0;
}

(1):p是个指针变量,指针存储的是地址,因此sizeof(p)计算的则是地址所占据的内存空间的大小,所以结果为 4 或者 8;

(2):p是个指针变量,在指针初阶博主有讲到过,指针+-整数会所跳过几个字节取决于该指针的类型,这里是字符指针,因此 + 1跳过一个字节,p + 1以后跳过一个字节即指向字符'b'的地址,因此这里的sizeof(p + 1)计算的依旧是地址所占据的内存空间的大小,所以结果为 4 或者 8;

(3):指针变量p存储的是字符串中'a'的地址,对其解引用得到字符'a',因此sizeof(*p)计算的则是字符'a'所占据的内存空间的大小,所以结果为 1;

(4):p[0] 等价于 *(p + 0),因此这里计算的依旧是字符'a'所占据的内存空间的大小,所以结果为 1;

(5):&p得到的是指针变量p的地址,指针的地址也是地址,因此这里的sizeof(&p)计算的依旧是地址所占据的内存空间的大小,所以结果为 4 或者 8;

(6):&p得到的是指针变量p的地址,地址 + 1依旧是地址,因此这里的sizeof(&p + 1)计算的依旧是地址所占据的内存空间的大小,所以结果为 4 或者 8;

(7):p[0] 等价于 *(p + 0),然后对其取地址得到的是字符变量'a'的地址,,地址 + 1依旧是地址,因此这里的sizeof(&p[0] + 1)计算的依旧是地址所占据的内存空间的大小,所以结果为 4 或者 8;

32位平台
64位平台

5.2.6:指针存储字符串(strlen)

cpp 复制代码
int main()
{
	char* p = "abcdef";
	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;
}

(1):p是个指针变量,指针存储的是首元素地址,因此这里的strlen(p)是从字符'a'开始统计,直到遇到\0为止,由于字符串的结束标志是'\0',所以这里的结果则是6.

(2):p为字符指针,p + 1跳过一个字节,因此这里的strlen(p + 1)是从字符'b'开始统计,直到遇到'\0'为止,由于字符串的结束标志是'\0',所以这里的结果则是5.

(3):指针变量p存储的是字符串中'a'的地址,对其解引用得到字符'a','a'的ascii码为97,从地址的角度出发,相当于使用strlen函数去访问97那一块地址空间,可是97那块空间并不是指针变量p所指向的空间,所以发生了空间的非法访问,因此这里的结果是错误的.

(4):p[0] 等价于 *(p + 0),因此这里得到'a','a'的ascii码为97,从地址的角度出发,相当于使用strlen函数去访问97那一块地址空间,可是97那块空间并不是指针变量p所指向的空间,所以发生了空间的非法访问,因此这里的结果是错误的.

(5):&p得到的是指针变量p的地址,由于指针变量p的地址后面的内容是未知的,因此这里strlen(&p)的结果是随机值.

(6):&p得到的是指针变量p的地址,p的地址 + 1以后还是地址,这块地址后面所指向的内容也是未知的,因此这里的strlen(&p + 1)的结果是随机值.

(7):p[0] 等价于 *(p + 0),然后对其取地址得到的是字符变量'a'的地址,接下来再对地址 + 1,地址 +1以后依旧是地址并且这里得到的地址是第2个元素的地址即 'b'的地址,因此这strlen(&p[0] + 1)是从 'b'的位置开始统计,直到遇到'\0'为止,由于字符串的结束标志是'\0',所以这里的结果则是 5.

5.3:二维数组

cpp 复制代码
	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]));

(1):a表示数组名,并且单独出现,所以sizeof(a)计算的是整个二维数组的大小,而二维数组a是一个3行4列的数组,每一个元素类型都是整型,因此sizeof(a) = 3 * 4 * 4 = 48.

(2):a[0][0]访问的是二维数组中第一行第一列的元素,因此sizeof(a[0][0])的结果是 4.

(3):a[0]访问的是二维数组中的第一个元素即第一行这个一维数组的数组名,由于数组名单独出现在了sizeof内部,因此sizeof(a[0]) 的结果为 16.

(4):a[0]访问的是二维数组中的第一个元素即第一行这个一维数组的数组名,由于数组名既没有单独出现在sizeof内部,也没有对其取地址,因此这里的a[0]表示的是第一行这个一维数组的首元素地址,+ 1以后还是地址即第一维数组第二个元素的地址,因此sizeof(a[0] + 1)的结果是 4或者8.

(5):a[0]访问的是二维数组中的第一个元素即第一行这个一维数组的数组名,由于数组名既没有单独出现在sizeof内部,也没有对其取地址,因此这里的a[0]表示的是第一行这个一维数组的首元素地址,+ 1以后还是地址即第一维数组第二个元素的地址,对其解引用得到的是一个整型元素,因此这里的 sizeof(*(a[0] + 1))的结果为 4.

(6):由于数组名既没有单独出现在sizeof内部,也没有对其取地址,因此这里的a表示的首元素的地址即第一行的起始地址, + 1以后跳过一行,得到第二行的地址即一维数组的地址,所以这里的sizeof(a + 1)的结果为 4 或者 8.

(7):由于数组名既没有单独出现在sizeof内部,也没有对其取地址,因此这里的a表示的首元素的地址即第一行的起始地址, + 1以后跳过一行,得到第二行的地址即一维数组的地址,对其解引用访问整个数组,因此这里的sizeof(*(a + 1))的结果为 16.

(8):a[0]访问的是二维数组中的第一个元素即第一行这个一维数组的数组名,对其进行取地址得到一维数组的地址,那么&a[0]的类型为int (*) [4],地址 + 1以后还是地址,所以sizeof(&a[0] + 1)的结果为 4或者8.

(9):a[0]访问的是二维数组中的第一个元素即第一行这个一维数组的数组名,对其进行取地址得到一维数组的地址,那么&a[0]的类型为int (*) [4],+1以后跳过的字节数为该指针指向元素数据类型的大小,即此时得到第二行的地址,对其解引用访问整个一维数组,所以sizeof(*(&a[0] + 1))的结果为 16.

(10):a表示数组名,由于数组名既没有单独出现在sizeof内部,也没有对其取地址,所以a表示的是首元素地址即一维数组的地址,对其进行解引用访问整个一维数组,所以sizeof(*a)的结果为16.

(11):这里有些uu就会有些疑问,这个二维数组明明是3行4列,a[3]访问的是第四行的数据,这样子做岂不是发生了越界吗?其实没有发生越界,在之前博主有讲到过,sizeof计算的是操作数的类型在内存空间中所占据的大小.举例简单例子:

那么这里也是同理,编译器同样也会进行转换,所以这里的sizeof(a[3])等价与sizeof(a[0]),所以这里的结果是16.

32位平台

64位平台

6:指针运算笔试题

6.1:题目1

cpp 复制代码
#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;
}

首先来看第一题,这里出现了&符号,因此此时a代表的是整个数组的地址,对数组的地址 +1则跳过整个数组,然后对其强制类型转换成整型指针存放到ptr中,那么ptr里面存储的是 5这个元素的下一个元素的地址.

a代表数组名,由于既没有单独出现在sizeof内部,也没有&符号,因此 a+1这里的a则表示首元素地址,又因为数组在内存中是连续存储的,因此 a + 1此时指向的是数组中的第二个元素也就是2

ptr由于是整型指针,整型指针 - 1跳过四个字节,因此此时 *(ptr - 1)的值为5.所以答案是:2,5.

6.2:题目2

cpp 复制代码
//在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;
}

(1):p为结构体的指针,那么结构体的指针 + 1跳过该结构体所占的字节数也就是 20(16进制:0x000014),所以 p + 0x1的结果为0x100014.

(2):首先对p进行强制类型转换成无符号长整形,那么 + 0x1也就是数值上的 +1,所以结果为0x100001.

(3):首先对p进行强制类型转换成无符号整型指针,那么 + 0x1也就是跳过4(16进制:0x000004)个字节,所以结果为0x100004.

6.3:题目3

cpp 复制代码
#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", ptr1[-1], *ptr2);
	return 0;
}

(1):这里出现了&符号,因此此时a代表的是整个数组的地址,对数组的地址 +1则跳过整个数组,然后对其强制类型转换成整型指针存放到ptr1中,那么ptr1里面存储的是 4这个元素的下一个元素的地址,在指针初阶博主有讲到过 arr[1]----->*(arr + 1),那么prt[-1]------>*(ptr -1),由于ptr为整型指针,因此 - 1跳过4个字节,那么*(ptr - 1)的结果为4.

(2):a为首元素的地址,将其强制类型转换为int类型,那么在数值上+1,也就是跳过1个字节,因此是在1这个元素所占的4个字节中跳过1个字节然后将这个表达式强制类型转换为整型指针,对其解引用访问4个字节,再以%x的方式输出得到2000000.

6.4:题目4

cpp 复制代码
#include <stdio.h>
int main()
{
	int a[3][2] = { (0, 1), (2, 3), (4, 5) };
	int* p;
	p = a[0];
	printf("%d", p[0]);
	return 0;
}

二维数组里头是有三个逗号表达式,之前博主讲过,逗号表达式的是特点:从左向右依次计算,整个表达式的结果为最后一个表达式的计算结果. 那么这个二维数组等价于 a = {1,3,5}.

p = a[0],那么p[0] = a[0][0]访问的是二维数组的第一个元素,也就是1.

6.5:题目5

cpp 复制代码
#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;
}

p是个数组指针,指向的是一个元素类型为int,元素个数为4的数组,a为数组名,代表的是首元素地址,那么a的类型为int (*)[5],有的uu就会想,那这样子的话,类型合适吗?其实不管类型的话,是能够存的,因为a是地址,p是指针变量,指针变量就是用来存储地址的嘛,但是在编译时,编译器会发出警告.

然后我们看表达式,这里是地址减地址,那么就是指针减去指针,只是一个是使用%p的方式输出,一个是以整型的方式输出,p[4][2]等价与 *(*(p + 4) +2),根据p的类型我们可以得知,p每次 + 1跳过 4个整型元素的大小.那么我们来看如下这张图.

根据上面的图再结合之前在数组阶段我们有讲到过,二维数组在内存中是连续存储的并且是从低地址到高地址.因此&p[4][2] - &a[4][2]为深蓝色的区域的地址 - 紫色区域的地址,结果为-4

PS:这里以%p的方式输出-4是输出的是补码哦~,这里要将-4的补码转换为16进制,这里博主就不转啦.

6.6:题目6

cpp 复制代码
#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", *(ptr1 - 1), *(ptr2 - 1));
     return 0;
}

(1):aa为数组名,由于有&符号,因此此时aa代表的是整个数组的地址, + 1则跳过整个数组的大小,那么ptr1这个整型指针则指向的是 10这个元素的下一个位置. 整型指针 - 1则跳过 4个字节,所以*(ptr1 - 1)的结果为 10.

(2):aa为数组名,由于这里既没有单独出现在sizeof里面,也没有&符号,所以aa为首元素地址,因此aa的类型为int(*)[5], + 1则跳过 5个整型元素的大小,此时aa + 1指向的是第二行的地址,那么对其解引用得到第二行首元素的地址,所以 ptr2指向的是6这个元素,那么*(ptr2 - 1)的结果为5.

6.7:题目7

cpp 复制代码
#include <stdio.h>
int main()
{
     char *a[] = {"work","at","alibaba"};
     char**pa = a;
     pa++;
     printf("%s\n", *pa);
     return 0;
}

a为数组名,由于这里既没有单独出现在sizeof内部,又没有&符号,因此a代表的数组首元素地址,即第一个字符串的地址.pa为二级指针,即将第一个字符串的地址存储在二级指针pa里头,然后pa++,由于数组在内存中是连续存储的,那么此时pa指向的第二个字符串的地址,所以*pa的结果为at.

6.8:题目8

cpp 复制代码
#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;
}

在做这道题之前,我们首先来画出每个变量的布局

(1):**++cpp,由于是前置++,那么首先加+1,此时cpp指向的c+2这块内存空间,然后对其两次解引用,第一解引用,访问到c+2,然后再进行解引用,访问到字符串POINT,所以结果为POINT.

那么此时所对应的布局为下图

(2):+号的优先级是低于 ++与--还有*的,那么首先执行前置++,此时cpp指向c+1这块内存空间,然后解引用访问到c+1,对其进行--得到c,那么此时指向的是ENTER这个字符串,再对其解引用访问到字符串ENTER,由于是char *指针,因此每一次+1跳过1个字节,所以+3则跳过3个字节,此时从E开始向后打印,直到遇到\0,所以结果为ER.

此时所对应的布局图为下图

(3):*cpp[-2] 等价于 **(cpp - 2),那么首先cpp -2指向的是 c+ 3这块内存空间,然后对其两次解引用访问到FIRST字符串,然后 +3跳过3个字节,所以结果为ST.

此时所对应的布局图为下图

(4):cpp[-1][-1]等价于 *(*(cpp-1) -1) + 1,cpp-1访问到c+2的内存空间,然后对其解引用得到c+2,c+2 -1得到 c+1,然后再对其解引用得到NEW字符串,接着 +1跳过1个字节,那么结果为EW.

好啦,uu们,关于指针进阶的剩下这部分滴详细内容博主就讲到这里啦,如果uu们觉得博主讲的不错的话,请动动你们滴小手给博主点点赞,你们滴鼓励将成为博主源源不断滴动力.

相关推荐
vortex51 小时前
解决 VSCode 中 C/C++ 编码乱码问题的两种方法
c语言·c++·vscode
Xiao Fei Xiangζั͡ޓއއ2 小时前
一觉睡醒,全世界计算机水平下降100倍,而我却精通C语言——scanf函数
c语言·开发语言·笔记·程序人生·面试·蓝桥杯·学习方法
子朔不言2 小时前
[ARM-2D 专题]6.脏矩形定义的宏使用技巧和分析
c语言·arm开发·arm2d·显控开发-新龙微
BT-BOX3 小时前
STM32仿真proteus位带操作和keil增加头文件C文件
c语言·stm32·proteus
醉颜凉3 小时前
【NOIP提高组】潜伏者
java·c语言·开发语言·c++·算法
五味香6 小时前
Linux学习,ip 命令
linux·服务器·c语言·开发语言·git·学习·tcp/ip
lb36363636367 小时前
整数储存形式(c基础)
c语言·开发语言
浪里个浪的10247 小时前
【C语言】从3x5矩阵计算前三行平均值并扩展到4x5矩阵
c语言·开发语言·矩阵
<但凡.7 小时前
编程之路,从0开始:知识补充篇
c语言·数据结构·算法
f狐0狸x8 小时前
【数据结构副本篇】顺序表 链表OJ
c语言·数据结构·算法·链表