C语言进阶——指针练习

目录

一、猜名次

二、猜凶手

三、杨辉三角

1.解法一

2.解法二

四、杨氏矩阵

1.解法一

2.解法二

五、字符串左旋

1.解法一

2.解法二

六、判断是否为字符串左旋字串

1.解法一

2.解法二

七、指针和数组笔试题

1.一维数组

2.字符数组

3.字符数组放字符串

4.指针 

5.二维数组 

八、指针笔试题

1.题目一

2.题目二

3.题目三

4.题目四

5.题目五

6.题目六

7.题目七

8.题目八


一、猜名次

5位运动员参加了10米台跳水比赛,有人让他们预测比赛结果:

A选手说:B第二,我第三;

B选手说:我第二,E第四;

C选手说:我第一,D第二;

D选手说:C最后,我第三;

E选手说:我第四,A第一;

比赛结束后,每位选手都说对了一半,请编程确定比赛的名次。

对于这道题我们是这样思考的,五次循环遍历,穷举所有可能性,然后由于题目描述说每人说对了一半,而他们正好每人说了两句话,那么也就是我们让两句话都与真进行比较,如果为真则为1,如果为假则为0,加起来就是1即可。具体代码如下

#include<stdio.h>
int main()
{
	int a = 0;
	int b = 0;
	int c = 0;
	int d = 0;
	int e = 0;
	for (a = 1; a <= 5; a++)
	{
		for (b = 1; b <= 5; b++)
		{
			for (c = 1; c <= 5; c++)
			{
				for (d = 1; d <= 5; d++)
				{
					for (e = 1; e <= 5; e++)
					{
						if (   (b == 2) + (a == 3) == 1
							&& (b == 2) + (e == 4) == 1
							&& (c == 1) + (d == 2) == 1
							&& (c == 5) + (d == 3) == 1
							&& (e == 4) + (a == 1) == 1 
							&& a*b*c*d*e==120)
						{
							printf("A:%d B:%d C:%d D:%d E:%d\n", a, b, c, d, e);
						}
 					}
				}
			}
		}
	}
	return 0;
}

二、猜凶手

日本某地发生了一件谋杀案,警察通过排查确定杀人凶手必为4个嫌疑犯的一个。

以下为4个嫌疑犯的供词:

A说:不是我。

B说:是C。

C说:是D。

D说:C在胡说

已知3个人说了真话,1个人说的是假话。

现在请根据这些信息,写一个程序来确定到底谁是凶手。

#include<stdio.h>
int main()
{
	char killer;
	for (killer = 'A'; killer <= 'D'; killer++)
	{
		if (((killer!='A') + (killer=='C') + (killer == 'D') + (killer != 'D')) == 3)
		{
			printf("凶手是:%c", killer);
		}
	}
	return 0;
}

三、杨辉三角

在屏幕上打印杨辉三角

1

1 1

1 2 1

1 3 3 1

……

1.解法一

对于这道题,我们最简单最暴力的思路就是直接创建一个二维数组,然后将杨辉三角的数据都存储到这个数组中去,我们这个打印的杨辉三角其实它是把前面的空格都给删除了,就一边倒的样子,导致有点跟数学中的杨辉三角长得有点不太一样。但是没关系它也是有规律的

它的规律是这样的:第一列和主对角线都是1,然后其他位置的元素都是由于这个位置的上方的元素加上左上角的元素的。所以我们可以写出如下代码

#define N 10
#include<stdio.h>
int main()
{
	int arr[N][N] = { 0 };
	int i = 0;
	int j = 0;
	for (i = 0; i < N; i++)
	{
		for (j = 0; j < N; j++)
		{
			if (j == 0 || i == j)
			{
				arr[i][j] = 1;
			}
			if (i > 0 && j > 0)
			{
				arr[i][j] = arr[i - 1][j] + arr[i - 1][j - 1];
			}
		}
	}
	for (i = 0; i < N; i++)
	{
		for (j = 0; j < N; j++)
		{
			if (i >= j)
			{
				printf("%d ", arr[i][j]);
			}
		}
		printf("\n");
	}
	return 0;
}

2.解法二

其实我们也能发现,第一种解法的空间复杂度太大了,始O(N^2),那么我们可不可以使用一个一维数组来解决呢?答案当然是可以的。

我们的思路是这样的,先创建一个一维数组,然后这个数组每次存储一行的数据。根据我们的规律,第一个元素必须得是1才可以。这个是不可以修改的。然后其他位置的元素就简单了,我们从后往前赋值,就是它原来这个位置的元素加上前一个元素的值。所以我们得到代码如下

#include<stdio.h>
#define N 10
int main()
{
	int arr[N] = { 1 };
	//第一行直接打印出来
	printf("%d\n", arr[0]);
	int i = 0;
	for (i = 1; i < N; i++)
	{
		//修改数组的元素的值,第几行就有几个元素,从后往前开始进行,第一个元素不需要修改
		int j = i;
		for (j = i; j >= 1; j--)
		{
			arr[j] = arr[j] + arr[j - 1];
		}
		//打印数组
		for (j = 0; j <= i; j++)
		{
			printf("%-3d ", arr[j]);
		}
		printf("\n");
	}
	return 0;
}

四、杨氏矩阵

有一个数字矩阵,矩阵的每行从左到右是递增的,矩阵从上到下是递增的,请编写程序在这样的矩阵中查找某个数字是否存在。

要求:时间复杂度不超过O(N);

1.解法一

#define M 3
#define N 4
int find1(int(*arr)[N], int* i, int* j, int n)
{
	int x = 0;
	int y = N - 1;
	while (x<=M-1 && y >= 0)
	{
		if (arr[x][y] > n)
		{
			y--;
		}
		else if (arr[x][y] < n)
		{
			x++;
		}
		else
		{
			*i = x;
			*j = y;
			return 1;
		}
	}
	return 0;
}
int main()
{
	int arr[M][N] = { 1,2,3,4,5,6,7,8,9,10,11,12 };
	int n = 0;
	printf("请输入查找的数字>:\n");
	scanf("%d", &n);
	int i = 0;
	int j = 0;
	int ret = find1(arr, &i, &j, n);
	if (ret == 0)
	{
		printf("没找到\n");
	}
	else
	{
		printf("找到了,他在数组中的行下标是:%d,列下标是:%d\n", i, j);
	}
	return 0;
}

2.解法二

其实对于上面的方法还不是最优的解法,我们之前说过二分查找算法,我们这道题也可以使用二分法,我们只需要先对第一列进行二分查找,然后找到行下标,然后在对行二分查找,就找到了行下标了

#define _CRT_SECURE_NO_WARNINGS 1
#define M 3
#define N 4
#include<stdio.h>
int find2(int arr[M][N], int* i, int* j, int n)
{
	//先在第一列中找到行下标的范围,也就是纵向寻找
	int row_left = 0;
	int row_right = M - 1;
	int row_mid = (row_left + row_right) / 2;
	while (row_left <= row_right)
	{
		if (arr[row_mid][0] < n)
		{
			row_left = row_mid + 1;
		}
		else if (arr[row_mid][0] > n)
		{
			row_right = row_mid - 1;
		}
		else if (arr[row_mid][0] == n)
		{
			*i = row_mid;
			*j = 0;
			//返回1代表找到了,返回0代表没找到
			return 1;
		}
		row_mid = (row_left + row_right) / 2;
	}
	//第一列中已经找出来列下标了,现在该让行下标不动、列下标开始变化了,也就是横向寻找
	int col_left = 0;
	int col_right = N- 1;
	int col_mid = (col_left + col_right) / 2;
	while (col_left <= col_right)
	{
		if (arr[row_mid][col_mid] < n)
		{
			col_left = col_mid + 1;
		}
		else if (arr[row_mid][col_mid] > n)
		{
			col_right = col_mid - 1;
		}
		else if (arr[row_mid][col_mid] == n)
		{
			*i = row_mid;
			*j = col_mid;
			return 1;
		}
		col_mid = (col_left + col_right) / 2;
	}
	//也就是没找到返回0
	return 0;
}
int main()
{
	int arr[M][N] = { 1,2,3,4,5,6,7,8,9,10,11,12 };
	int n = 0;
	printf("请输入查找的数字>:\n");
	scanf("%d", &n);
	int i = 0;
	int j = 0;
	int ret = find2(arr, &i, &j, n);
	if (ret == 0)
	{
		printf("没找到\n");
	}
	else
	{
		printf("找到了,他在数组中的行下标是:%d,列下标是:%d\n", i, j);
	}
	return 0;
}

五、字符串左旋

实现一个函数,可以左旋字符串中的k个字符。

例如:

ABCD左旋一个字符得到BCDA

ABCD左旋两个字符得到CDAB

1.解法一

对于这道题我们最最最简单的思路就是,先想办法左旋一个字符,然后重复k次,而要左旋一个字符的话,我们可以将第一个字符放到一个临时变量里面,然后我们将后面的元素都覆盖到前面来,最后将这个临时变量的放到最后一个即可。代码如下

#include<stdio.h>
#include<string.h>
void left_reverse(char* arr, int k)
{
	int i = 0;
	int len = strlen(arr);
	for (i = 0; i < k; i++)
	{
		//左旋一次
		int j = 0;
		char tmp = arr[0];
		for (j = 0; j < len - 1; j++)
		{
			arr[j] = arr[j + 1];
		}
		arr[j] = tmp;
	}
}
int main()
{
	char arr[] = "abcdef";
	int k = 0;
	scanf("%d", &k);
	left_reverse(arr, k);
	printf("%s\n", arr);
	return 0;
}

2.解法二

解法二比较神奇,我们先前前k个元素给逆序,然后将其余的元素给逆序,最后整体逆序,经历三次逆序后,也能解决问题

 

#include<stdio.h>
#include<assert.h>
#include<string.h>
void reverse(char* arr, int left, int right)
{
	assert(arr);
	int i = 0;
	while (left < right)
	{
		char tmp = arr[left];
		arr[left] = arr[right];
		arr[right] = tmp;
 
		left++;
		right--;
	}
}
void left_reverse(char* arr, int k)
{
	int left = 0;
	int right = k - 1;
	reverse(arr, left, right);
	left = right + 1;
	right = strlen(arr) - 1;
	reverse(arr, left, right);
	left = 0;
	right = strlen(arr) - 1;
	reverse(arr, left, right);
}
int main()
{
	char arr[] = "abcdef";
	int k = 0;
	scanf("%d", &k);
	left_reverse(arr, k);
	printf("%s\n", arr);
	return 0;
}

六、判断是否为字符串左旋字串

写一个函数,判断一个字符串是否为另外一个字符串旋转之后的字符串。

例如:给定s1 =AABCD和s2 = BCDAA,返回1

给定s1=abcd和s2=ACBD,返回0.

AABCD左旋一个字符得到ABCDA

AABCD左旋两个字符得到BCDAA

AABCD右旋一个字符得到DAABC

1.解法一

对于这道题,我们最简单的思路就是利用第五题函数,穷举所有旋转可能性判断是否相等

#include<stdio.h>
#include<assert.h>
#include<string.h>
void reverse(char* arr, int left, int right)
{
	assert(arr);
	int i = 0;
	while (left < right)
	{
		char tmp = arr[left];
		arr[left] = arr[right];
		arr[right] = tmp;
 
		left++;
		right--;
	}
}
void left_reverse(char* arr, int k)
{
	int left = 0;
	int right = k - 1;
	reverse(arr, left, right);
	left = right + 1;
	right = strlen(arr) - 1;
	reverse(arr, left, right);
	left = 0;
	right = strlen(arr) - 1;
	reverse(arr, left, right);
}
int is_reverse(char* arr1,char* arr2)
{
	int i = 0;
	int len = strlen(arr1);
	for (i = 0; i < len; i++)
	{
		if (strcmp(arr1, arr2) == 0)
		{
			return 1;
		}
		left_reverse(arr1, 1);
	}
	return 0;
}
int main()
{
	char arr1[100] = { 0 };
	char arr2[100] = { 0 };
	gets(arr1);
	gets(arr2);
	int ret = is_reverse(arr1, arr2);
	if (ret == 1)
	{
		printf("是左旋得到的\n");
	}
	else
	{
		printf("不是左旋得到的\n");
	}
	return 0;
}

2.解法二

我们还有一种思路就是利用库函数,使用strncat这个函数对arr1追加一个arr1,这样arr1中肯定包含所有的左旋情况,这样我们只需要在arr1中找出子串即可,而找出子串也有自己的库函数,strstr

#include<stdio.h>
#include<string.h>
int is_reverse(char* arr1, char* arr2)
{
	int len1 = strlen(arr1);
	int len2 = strlen(arr2);
	if (len1 != len2)
	{
		return 0;
	}
	strncat(arr1, arr1, len1);
	if (strstr(arr1, arr2) != NULL)
	{
		return 1;
	}
	return 0;
}
int main()
{
	char arr1[100] = { 0 };
	char arr2[100] = { 0 };
	gets(arr1);
	gets(arr2);
	int ret = is_reverse(arr1, arr2);
	if (ret == 1)
	{
		printf("是左旋得到的\n");
	}
	else
	{
		printf("不是左旋得到的\n");
	}
	return 0;
}

七、指针和数组笔试题

先了解

sizeof(数组名),数组名表示整个数组。计算的是整个数组的大小,单位是字节

&数组名,数组名表示整个数组。取出的是整个数组的地址

除此之外,所有的数组名都是数组首元素的地址

再了解一下sizeof原理 ,无符号整型

sizeof ----> size_t -> unsigned int

1.一维数组

int a[] = { 1,2,3,4 };//4*4=16
	printf("%d\n", sizeof(a));
//16 因为sizeof里面是单独的数组名的时候,是整个数组的大小
	printf("%d\n", sizeof(a + 0));
//4/8 因为sizeof里面是单独的数组名的时候,才是整个数组的大小,这里是首元素地址,加零还是首元素地址,地址大小是4/8个字节
	printf("%d\n", sizeof(*a));
//4 因为sizeof里面是单独的数组名的时候,才是整个数组的大小,这里是首元素地址,*a是数组首元素,计算的是数组首元素的大小,单位是字节
	printf("%d\n", sizeof(a + 1));
//4/8 因为sizeof里面是单独的数组名的时候,才是整个数组的大小,这里是首元素地址,a+1是第二个元素的地址,是地址大小就是4/8
	printf("%d\n", sizeof(a[1]));
//4 因为a[1]是第二个元素,计算的是第二个元素的大小-4-单位是字节
	printf("%d\n", sizeof(&a));
//4/8 因为&a是整个数组的地址,整个数组的地址也是地址,地址的大小就是4/8字节,&a---> 类型:int(*)[4] 数组指针类型
	printf("%d\n", sizeof(*&a));
//16 因为&a是数组的地址,*&a就是拿到了整个数组,*&a--> a,a就是数组名,sizeof(*&a)-->sizeof(a),计算的是整个数组的大小,单位是字节
	printf("%d\n", sizeof(&a + 1));
//4/8 因为&a是整个数组的地址,&a+1,跳过整个数组,指向数组后边的空间,是一个地址,大小是4/8字节
	printf("%d\n", sizeof(&a[0]));
//4/8 因为&a[0]是首元素的地址,计算的是首元素地址的大小,4/8字节
	printf("%d\n", sizeof(&a[0] + 1));
//4/8 因为&a[0] + 1是第二个元素的地址,地址的大小就是4/8字节

2.字符数组

char arr[] = { 'a','b','c','d','e','f' };//6个元素
	printf("%d\n", sizeof(arr));
//6 因为arr单独放在sizeof内部,计算的是整个数组的大小,单位是字节
	printf("%d\n", sizeof(arr + 0));
//4/8 因为arr不是单独放在sizeof内部,所以arr是首元素地址,arr + 0是数组首元素的地址
	printf("%d\n", sizeof(*arr));
//1 因为arr是数组的首元素地址,解引用*arr是数组首元素,计算的是首元素的大小
	printf("%d\n", sizeof(arr[1]));
//1 因为arr[1]是第二个元素,大小1字节
	printf("%d\n", sizeof(&arr));
//4/8 因为取出的数组的地址,数组的地址也是地址,是地址大小就是4/8
	printf("%d\n", sizeof(&arr + 1));
//4/8 因为&arr+1是跳过整个,指向数组后边空间的地址,4/8
	printf("%d\n", sizeof(&arr[0] + 1));
//4/8 因为&arr[0] + 1是数组第二个元素的地址,是地址4/8字节

我们换成strlen

    printf("%d\n", strlen(arr));
//随机值,压根不知道数组前后空间,只知道abcdef,不知道\0在哪
	printf("%d\n", strlen(arr + 0));
//随机值,跟上面的那个一样
	printf("%d\n", strlen(*arr));
//这里就是首元素,strlen('a')->strlen(97),非法访问-err
	printf("%d\n", strlen(arr[1]));
//'b'-98,和上面的代码类似,是非法访问 - err
	printf("%d\n", strlen(&arr));
//&arr虽然是数组的地址,但是也是从数组起始位置开始的,计算的还是随机值
	printf("%d\n", strlen(&arr + 1));
//&arr是数组的地址,&arr+1是跳过整个数组的地址,求字符串长度也是随机值
	printf("%d\n", strlen(&arr[0] + 1));
//&arr[0] + 1是第二个元素的地址,是'b'的地址,从b往后数,求字符串长度也是随机值

sizeof 只关注占用内存空间的大小,单位是字节,不关心内存中存放的是什么

sizeof 是操作符

strlen是求字符串长度的,统计的是\0之前出现的字符个数,一定要找到\0才算结束,所以可能存在越界访问的

strlen是库函数

3.字符数组放字符串

char arr[] = "abcdef";//数组是7个元素
    printf("%d\n", sizeof(arr));
//7 因为数组名单独放在sizeof内部,计算的是数组的总大小,单位是字节
	printf("%d\n", sizeof(arr + 0));
//4/8 因为arr+0是首元素的地址,大小是4/8
	printf("%d\n", sizeof(*arr));
//1 因为*arr是数组首元素,大小是1字节
	printf("%d\n", sizeof(arr[1]));
//1 因为arr[1]是数组的第二个元素,大小是1字节
	printf("%d\n", sizeof(&arr));
//4/8 因为&arr是数组的地址,数组的地址也是地址,是4/8字节
	printf("%d\n", sizeof(&arr + 1));
//4/8 因为&arr + 1是跳过整个数组的地址,是4/8字节
	printf("%d\n", sizeof(&arr[0] + 1));
//4/8 因为&arr[0] + 1是第二个元素的地址,是4/8字节

我们换成strlen

    printf("%d\n", strlen(arr));
//6,arr是数组首元素的地址,strlen从首元素的地址开始统计\0之前出现的字符个数,是6
	printf("%d\n", strlen(arr + 0));
//arr + 0是数组首元素的地址,同第一个,结果是6
	printf("%d\n", strlen(*arr));
//*arr是'a',是97,传给strlen是一个非法的地址,造成非法访问
	printf("%d\n", strlen(arr[1]));
//err,跟上一个一样也是非法
	printf("%d\n", strlen(&arr));
//6 取的是数组的地址,但是还是从第一个开始的
	printf("%d\n", strlen(&arr + 1));
//&arr + 1是跳过数组后的地址,统计字符串的长度是随机值
	printf("%d\n", strlen(&arr[0] + 1));
//&arr[0]+1是b的地址,从第二个字符往后统计字符串的长度,大小是5

4.指针 

const char* p = "abcdef";
    printf("%d\n", sizeof(p));
//p是指针变量,大小就是4/8字节
	printf("%d\n", sizeof(p + 1));
//p + 1是b的地址,是地址,就是4/8个字节
	printf("%d\n", sizeof(*p));
//*p是'a',sizeof(*p)计算的是字符的大小,是1字节
	printf("%d\n", sizeof(p[0]));
//p[0]-->*(p+0) --> *p  就同上一个,1字节
	printf("%d\n", sizeof(&p));
//&p是二级指针,是指针大小就是4/8
	printf("%d\n", sizeof(&p + 1)); 
//&p + 1是跳过p变量后的地址,4/8字节
	printf("%d\n", sizeof(&p[0] + 1));
//p[0]就是‘a’,&p[0]就是a的地址,+1,就是b的地址,是地址就是4/8

 我们换成strlen

    printf("%d\n", strlen(p));
//6- 求字符串长度
	printf("%d\n", strlen(p + 1));
//p + 1是b的地址,求字符串长度就是5
	printf("%d\n", strlen(*p));
//err,*p是'a'
	printf("%d\n", strlen(p[0]));
//err - 同上一个
	printf("%d\n", strlen(&p));
//&p拿到的是p这个指针变量的起始地址,从这里开始求字符串长度完全是随机值
	printf("%d\n", strlen(&p + 1));
//&p+1是跳过p变量的地址,从这里开始求字符串长度也是随机值
	printf("%d\n", strlen(&p[0] + 1));
//&p[0] + 1是b的地址,从b的地址向后数字符串的长度是5

5.二维数组 

//二维数组——第一行数组名是a[0][j],第二行数组名a[1][j],第三行数组名a[2][j]
	int a[3][4] = { 0 };
//a就是首元素地址,就是第一行的地址,a[0]就是第一行首元素地址,就是第一个元素的地址  (注意分清是不是单独放在sizeof中,在里面就是整个代表数组了)

	printf("%d\n", sizeof(a));
//48 = 3*4*4 整个数组的大小,单位字节
	printf("%d\n", sizeof(a[0][0]));
//4 第一行第一个元素
	printf("%d\n", sizeof(a[0]));
//a[0]是第一行的数组名,数组名单独放在sizeof内部,计算的就是整个数组(第一行)的大小,但是是这一行,16个字节
	printf("%d\n", sizeof(a[0] + 1));
//a[0]作为第一行的数组名,没有单独放在sizeof内部,没有取地址,表示的就是数组首元素的地址,那就是a[0][0]的地址,a[0]+1就是第一行第二个元素的地址,是地址就是4/8个字节
	printf("%d\n", sizeof(*(a[0] + 1)));
//*(a[0] + 1)是第一行第2个元素,上一个是地址,这个是元素,计算的是元素的大小-4个字节
	printf("%d\n", sizeof(a + 1));
//a是二维数组的数组名,数组名表示首元素的地址,就是第一行的地址,a+1就是第二行的地址,第二行的地址也是地址,是地址就是4/8   
	printf("%d\n", sizeof(*(a + 1)));
//a+1是第二行的地址,*(a+1)表示的就是第二行,*(a+1)--a[1]  16
	printf("%d\n", sizeof(&a[0] + 1));
//&a[0]是第一行的地址,&a[0]+1是第二行的地址,地址的大小就是4/8
	printf("%d\n", sizeof(*(&a[0] + 1)));
//*(&a[0] + 1) 是对第二行的地址解引用,得到的就是第二行,计算的就是第二行的大小 16
	printf("%d\n", sizeof(*a));
//a表示首元素的地址,就是第一行的地址,*a就是第一行,计算的就是第一行的大小,*a -- *(a+0)--a[0]  16
	printf("%d\n", sizeof(a[3]));
//16字节 int[4],如果数组存在第四行,a[3]就是第四行的数组名,数组名单独放在sizeof内部,是这整一行数组的大小,计算的是第四行的大小

我们还是再看一下sizeof

int a = 10;
printf("%d\n", sizeof(int));
//a的类型是int,a的大小就是int的大小
int main()
{
	short s = 3;
	int a = 10;
	printf("%d\n", sizeof(s = a + 2));//2
	//a整型加二整型赋给short,截断,还是s说了算,俩个字节
	printf("%d\n", s);//3
	//放在sizeof里的表达式是不参加运算的
    return 0;
}

一个程序test.c——————————test.exe——————运行

                                                            编译+连接                 表达式运算

                                                            sizeof在这里运行,不参与运算         

八、指针笔试题

1.题目一

int main()
{
	int a[5] = { 1, 2, 3, 4, 5 };
	int* ptr = (int*)(&a + 1);
	//&a——类型是int(*)[5]——拿到整个数组的地址,加一跳过整个数组
	//ptr是整型指针,后面类型int(*)[5],类型不合适,强制类型转化为int,跳过数组后面是5后面的地址
	printf("%d,%d", *(a + 1), *(ptr - 1));
	//a是一个数组名,首元素地址,加一往后跳一个整型,解引用就是2,ptr-1是5后面那个地址,往前一个,解引用就是5
	return 0;
}

2.题目二

//由于还没学习结构体,这里告知结构体的大小是20个字节
struct Test
{
	int Num;
	char* pcName;
	short sDate;
	char cha[2];
	short sBa[4];
}* p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?已知,结构体Test类型的变量大小是20个字节
int main()
{
	p = (struct Test*)0x100000;
	printf("%p\n", p + 0x1);
	//0x00100014
	//结构体指针加一,加的就是整个结构体就是20个字节
	printf("%p\n", (unsigned long)p + 0x1);
	//0x00100001
	//强制类型转化无符号长整型,整型加一,就是加一
	printf("%p\n", (unsigned int*)p + 0x1);
	//0x00100004
	//强制类型转化为无符号整型指针,指针加一,跳过去一个整型,就是+4
	return 0;
}

 

3.题目三

//小端,x86环境
int main()
{
	int a[4] = { 1, 2, 3, 4 };
	int* ptr1 = (int*)(&a + 1);
	//&a取出数组的地址,加一跳过整个数组,强制类型转化为整型指针,指向4后面的地址
	int* ptr2 = (int*)((int)a + 1);
	//a首元素地址,强制类型转化为整型,然后加一,就是加一(这个时候是整型)元素1里面有四个字节,这个加一假设为地址,就是加了一个字节,还在元素1里面
	printf("%x,%x", ptr1[-1], *ptr2);
	//ptr1[-1]——*(ptr1-1)   就是ptr1-1就是前面一个地址,然后解引用
	//ptr2,就是元素1里面四个字节的第二个字节,ptr2解引用,就是向后访问四个字节
	01   00   00   00   02    00   00   00   03    00   00    00    04
        |ptr2              |到这
		小端化为原来的就是0x02 00 00 00    %x打印就是2000000   %p打印地址
	
		return 0;
}

4.题目四

#include <stdio.h>
int main()
{
	int a[3][2] = { (0, 1), (2, 3), (4, 5) };
	//用逗号表达式为{1,3,5}——逗号表达式结果是最后一个数
	//1 3
	//5 0
	//0 0
	//int a[3][2] = { {0,1}, {2,3}, {4,5} };这才是二维数组初始化
	int* p;
	p = a[0];//第一行数组名,就是首元素地址,就是1的地址a[0][0]
	printf("%d", p[0]);//p[0]——*(p+0),结果就是1
	return 0;
}

5.题目五

int main()
{
	int a[5][5];//数组五行五列
	int(*p)[4];//指针变量p,指向数组的指针,指向的数组是四个元素,每个元素类型是整型,如果加一,就是跳过四个整型,
	p = a;//现在把a赋给p,p是指向包含四个整型的数组的指针
	//p的类型int(*)[4]——a的类型int(*)[5]
	
	printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
	//这一题需要画图,看指向的地址在哪,a和p是不一样的,a是五行,p是四行,两个指针相减,差四个整型,为-4,小地址减去大地址
	//%d打印是-4,在内存中是补码存放
	10000000000000000000000000000100
	11111111111111111111111111111011
	11111111111111111111111111111100
	地址是不管你原码补码的,直接把补码当成地址打印,就是
	0xff ff ff fc
	return 0;
}

 

6.题目六

int main()
{
	int aa[2][5] = { 10,9,8,7,6,5,4,3,2,1 };//二维数组
	int* ptr1 = (int*)(&aa + 1);
	//取出二维数组地址,加一跳过整个数组,然后强制类型转换整型指针
	int* ptr2 = (int*)(*(aa + 1));
	//aa作为数组名,是首元素地址,是第一行数组地址,加一跳到第二行,解引用,就是第二行数组名
	printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));
	//ptr1向前移动一个,然后解引用就是1
	//ptr2是第二行数组名,就是首元素地址,就是第二行第一个元素地址,向前移动一个,然后解引用就是6
	return 0;
}

7.题目七

#include <stdio.h>
int main()
{
	char* a[] = { "work","at","alibaba" };
	//是把这几个字符串地址放到数组里面,数组里面每个元素类型是char*
	char** pa = a;
	//数组的名字赋给pa,pa是二级指针,存放的是数组a的地址,数组a是存放字符串的指针数组
	pa++;
	//pa类型是char**,pa是指针,指针pa指向的是char*,++跳过一个char*类型
	printf("%s\n", *pa);
	//然后到这个地址上,解引用就是at
	return 0;
}

8.题目八

int main()
{
	char* c[] = { "ENTER","NEW","POINT","FIRST" };
	//c数组里面放的四个字符串的地址,每个元素是char*
	char** cp[] = { c + 3,c + 2,c + 1,c };
	//cp数组里面每个元素类型是char**,里面放的是c+3,c+2,c+1,c,c是数组名,是首元素地址
	//所以cp里面是FIRST起始地址,POINT起始地址,NEW起始地址,ENTER起始地址
	char*** cpp = cp;
	//cpp数组里面每个元素类型是char***,里面放的是cp,cp是首元素地址,就是c+3地址
	
	printf("%s\n", **++cpp);
	//cpp指向是c+3地址,++就是跳过一个char***类型,就指向了c+2的地址,解引用就是c+2,就是存放POINT起始地址的数组的地址,再次解引用就是POINT起始地址,就是POINT
	printf("%s\n", *-- * ++cpp + 3);
	//cpp指向是c+3地址,++就是跳过一个char***类型,就指向了c+1的地址,解引用就是c+1,--变为c,就变为指向存放ENTER起始地址的数组的地址,再解引用就是ENTER起始地址,再加3,跳过3,就是ER
	printf("%s\n", *cpp[-2] + 3);
	//cpp[-2]——*(cpp-2),所以*cpp[-2] + 3——**(cpp-2)+3,cpp在上一步中指向的是c+1改为的c地址,-2,跳到了c+3的地址,解引用就是c+3,就是存放FIRST起始地址的数组的地址,再次解引用就是FIRST起始地址,再加3,跳过3,就是ST
	printf("%s\n", cpp[-1][-1] + 1);
	//cpp[-1]——*(cpp-1),所以cpp[-1][-1] + 1——*(*(cpp-1)-1)+1,cpp在上一步中指向的是c+1改为的c地址,-1,跳到了c+2的地址,解引用就是c+2,-1变为c+1,就变为指向存放NEW起始地址的数组的地址,再次解引用就是NEW起始地址,再加1,跳过1,就是EW
	//!!!++和--是对自己操作的,所以本身的地址或者值是会改变的,+1和-1是不会改变本身的值或者地址
	return 0;
}

 

解释:cpp存放的是cp首元素的地址,cp 存放的是c的地址

1.由操作符优先级可知,先进行++cpp,所以cpp指向了c+2,然后对cpp第一次解引用得到了c+2的地址,再进行解引用找到了POINT首字符的地址,所以打印出来的是POINT

2.由于cpp已经指向了c+2,所在进行++cpp,就又指向了c+1,对他进行解引用找到了c+1的地址再进行--,所以c+1变为了(c+1-1)得到c,再进行解引用找到了ENTER首字符的地址,再进行+3,跳过三个字节,所以打印出来的是ER

3.由于cpp已经指向了c+1,而那个c+1地址又已经变为了c的地址,所以cpp[-2]本质为*(cpp-2)找到了c+3的地址再进行解引用得到了FIRST首字符的地址,再进行+3跳过了三个字节,所以打印出来的是ST

4.由于cpp已经指向了c+1,而那个c+1地址又已经变为了c的地址,所以cpp[-1][-1]本质为

*(*(cpp-1)-1)找到了NEW首字符的地址,再进行+1跳过一个字节,所以打印出EW

  • 0
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值