指针(五)带你搞定闻之色变的指针——超详细版!(指针相关练习)

系列文章目录

这里是C语言指针系列所有文章的目录。

指针(二)带你搞定闻之色变的指针——超详细版!(指针与数组及其应用冒泡排序)
指针(三)带你搞定闻之色变的指针——超详细版!(指针、函数、数组及其应用转移表)
指针(四)带你搞定闻之色变的指针——超详细版!(指针的应用之库函数qsort的实现)



前言

之前写过不少的指针专题,这次我们来运用一下,包括选择题与编程题,推荐小伙伴在看之前将题目做一下,好查缺补漏!


1. 选择题

1.1 指针运算

下面关于指针运算说法正确的是:( )
A.整型指针+1,向后偏移一个字节
B.指针-指针得到是指针和指针之间的字节个数
C.整型指针解引用操作访问4个字节
D.指针不能比较大小

在这里插入图片描述

1.2 字符串理解

判断下列代码的输出结果

#include <stdio.h>
int main()
{
	char str1[] = "hello bit.";
	char str2[] = "hello bit.";
	char* str3 = "hello bit.";
	char* str4 = "hello bit.";
	if (str1 == str2)
		printf("str1 and str2 are same\n");
	else
		printf("str1 and str2 are not same\n");

	if (str3 == str4)
		printf("str3 and str4 are same\n");
	else
		printf("str3 and str4 are not same\n");

	return 0;
}

以str3与str4初始化的字符串,意为将字符串首字符的地址存在str3与str4中,而str3与str4是不能够被更改的,因此没有必要开辟两份空间存储一个字符串,即str3与str4相同;而以str3与str4初始化的字符串,str1与str2是数组,可以更改,即二者地址不同,str1与str2不同。

1.3 assert宏

下面关于assert宏的描述哪个是正确的?
A.assert宏总是会终止程序的执行
B.assert宏仅在调试模式下起作用
C.assert宏只能用于整数类型的表达式
D.assert宏可以通过编译选项进行启用或禁用

A.assert(表达式),表达式如果为假,会终止程序的运行;
B.在VS中这句话是正确的,但在Linux下不正确;
C. 表达式即可;
D.正确,在程序的最开头加入这句话#define NDEBUG,对assert进行禁用。

2. 编程题

2.1 字符串类

2.1.2 模拟实现库函数strlen

我们先去官网上看一下,strlen的返回参数类型是size_t,是一种无符号整型。
在这里插入图片描述
下面为模拟实现,注意和库函数strlen名避开。

#include <stdio.h>
size_t my_strlen(const char* p)
{
	size_t count = 0;
	while (*p != '\0')
	{
		count++;
		p++;
	}
	return count;
}
int main()
{
	char str[] = "ABCD";
	printf("%zd\n", my_strlen(str));
	return 0;
}

2.1.4字符串逆序

写一个函数,可以逆序一个字符串的内容。字符逆序-牛客网链接

这是牛客网链接的答案

#include <stdio.h>
#include <string.h>
void Reverse(char str[])
{
    int len=strlen(str);
    char* left=str;
    char* right=str+len-1;
    while(left<right)
    {
        char tmp=*left;
        *left=*right;
        *right=tmp;

        left++;
        right--;
    }
}
int main() {
    char str[10001]={0};
    while (gets(str)){ 
    //因为涉及到空格的读取,因此我们使用gets
        Reverse(str);
        printf("%s\n",str);
    }
    return 0;
}

2.1.5 字符串左旋

2.1.5.1 左旋k个字符串

实现一个函数,可以左旋字符串中的k个字符。
例如:
ABCD左旋一个字符得到BCDA
ABCD左旋两个字符得到CDAB

解这个题之前,我们思考一个点,就是以ABCD4个字符为例,如果左旋4n+a次,和左旋a次的结果是一致的,4是字符串长度,也就是sz;
这个题的解法有很多种,我们先看最简单的一种,
思路1:化整为零,左旋k个字符,我们一个一个左旋。左旋一个字符,也就是arr[1]及之后的字符都往前挪1,但是为了防止arr[0]丢失,我们先把它存在一个临时变量里面,等所有的字符挪完,再将其存到最后一个位置。

#include <stdio.h>
#include <string.h>
void LeftRound1(char arr[], int k)
{
	int len = strlen(arr);
	k %= len;
	//左旋len*n+a次,和左旋a次的结果是一致的
	for (int i = 0; i < k; i++)
	{
		char tmp = arr[0];
		int j = 0;
		for (; j < len - 1; j++)
			arr[j] = arr[j + 1];
		arr[j] = tmp;//此时j==len-1,写arr[j]也可,但要注意适用前提是j在此时还有效,
		//如果是在for循环内部定义的j,此时生命周期已经结束了
	}
}
int main()
{
	char arr[] = "ABCD";
	LeftRound1(arr, 2);
	printf("%s\n", arr);
	return 0;
}

解法2:逆置的思想
将前k个元素进行逆置,将后面剩下的元素进行逆置,将所有元素进行逆置,以ABCDE, k=2为例,如下如所示。
或者先将所有元素逆置,将后k个元素逆置,再将前面剩下的元素逆置。
在这里插入图片描述

#include <stdio.h>
#include <string.h>
void Reverse(char arr[], int start, int end)
{
	int left = start;
	int right = end;
	while (left < right)
	{
		char tmp = arr[left];
		arr[left] = arr[right];
		arr[right] = tmp;

		left++;
		right--;
	}
}
void LeftRound2(char arr[], int k)
{
	int len = strlen(arr);
	k %= len;
	//同时保证下列代码的正确性,因为如果k>len,部分代码无法正确执行
	Reverse(arr, 0, k - 1);
	Reverse(arr, k, len - 1);
	Reverse(arr, 0, len - 1);
	//或者下面的代码也可
	/*Reverse(arr, 0, len - 1);
	Reverse(arr, 0, len-k-1);
	Reverse(arr, len -k, len - 1);*/
	
}
int main()
{
	char arr[] = "ABCDE";
	LeftRound2(arr, 2);
	printf("%s\n", arr);
	return 0;
}

解法3:
主要运用字符串函数strcpy, strnact
创建一个临时数组,将arr[k]及之后的元素先复制到临时数组,再将前面剩下的元素拼接到临时数组后面,即达到左旋效果,再将临时数组复制到arr,返回。

#include <stdio.h>
#include <string.h>
void LeftRound3(char arr[], int k)
{
	int len = strlen(arr);
	k %= len;
	char tmp[256] = { 0 };
	strcpy(tmp, arr + k);
	//strcmy(str1,str2) 将str2的内容复制给str1
	strncat(tmp, arr, k);
	//strnact(str1,str2,n) 将str1与str2的前n个字符拼接
	strcpy(arr, tmp);
	//将内容符合要求的tmp复制给arr
}
int main()
{
	char arr[] = "ABCDE";//这样初始化不要用sizeof求字符串长度,会把'\0'也算进去
	LeftRound3(arr, 1);
	printf("%s\n", arr);
	return 0;
}
2.1.5.2 判断字符串是否是左旋关系

写一个函数,判断一个字符串是否为另外一个字符串旋转之后的字符串。
例如:给定s1 =AABCD和s2 =BCDAA,返回1;给定s1=abcd和s2=ACBD,返回0.
AABCD左旋一个字符得到ABCDA ,AABCD左旋两个字符得到BCDAA
AABCD右旋一个字符得到DAABC

两个思路,一是循环,将str1所有可能出现的左旋结果一一与str2进行对比,如果一个都不相同,那就不是;反之则是。

#include <stdio.h>
#include <string.h>
int FindRound1(char* str1, char* str2)
{
	int len = strlen(str1);
	for (int i = 0; i < len; i++)
	{
		char tmp = str1[0];
		int j = 0;
		for (; j < len - 1; j++)
			str1[j] = str1[j + 1];
		str1[j] = tmp; //str1[len - 1] = tmp;
		if (strcmp(str1, str2) == 0)
			return 1;
	}
	return 0;
}
int main()
{
	char str1[] = "AABCD";
	char str2[] = "BCDA";
	//str2的初始化也可以是这样,char str2="BCDA"; 但str1不可以,因为这样初始化str2是不能改变的
	int ret = FindRound1(str1, str2);
	printf("%d\n",ret );
	return 0;
}

二是将str1进行复制后拼接,如果str2是str1左旋的结果,那么str2一定与str1某部分重合
在这里插入图片描述

#include <stdio.h>
#include <string.h>
int FindRound2(char* str1, char* str2)
{
	if (strlen(str1) != strlen(str2))
		return 0;
	char tmp[256] = { 0 };
	strcpy(tmp, str1);
	strcat(tmp, str1);
	return strstr(tmp, str2) != NULL;
	//库函数strstr(str1,str2) 在str1中查找str2,如果找到,返回指针;如果没找到,返回NULL
	//但是有一个bug,比如str2如果是ABCD的话,也返回1,所以要先判断两个字符串的长度,不一致就不考虑了,直接返回0;
}
int main()
{
	char str1[] = "ABCDE";
	char str2[] = "BCDEA";
	FindRound2(str1, str2);
	printf("%d\n", FindRound2(str1, str2));
	return 0;
}

2.2 查找类

2.2.1 找单身狗

思路:

a^a=0; 0^b=b; a^ a^b=b

2.1.1.1 一个单身狗

在一个整型数组中,只有一个数字出现一次,其他数组都是成对出现的,请找出那个只出现一次的数字。
例如,数组中有:1 2 3 4 5 1 2 3 4,只有5出现一次,其他数字都出现2次,找出5

#include <stdio.h>
int Find_Single_1(int arr[], int sz)
{
	int ret = 0;
	for (int i = 0; i < sz; i++)
		ret ^= arr[i];//成双出现的数0^a^a=0, 剩下单独出现的5,0^5=5
	return ret;
}
int main()
{
	int arr[] = { 1,2,3,4,5,1,2,3,4 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	printf("%d\n", Find_Single_1(arr, sz));
	return 0;
}
2.1.1.2 两个单身狗

一个数组中只有两个数字是出现一次,其他所有数字都出现了两次。
编写一个函数找出这两个只出现一次的数字。
例如:有数组的元素是:1,2,3,4,5,1,2,3,4,6;只有5和6只出现1次,要找出5和6.

首先,找出两个值,函数最多返回一个值,只能用指针做形参,才能将结果“带回来”。
化繁为简:要解决一个关键的问题,也就是,有两个单独出现的数,要把他们分开,这样思路和“2.1.1.1 一个单身狗”思路就很像了。

#include <stdio.h>
void Find_Single_2(int arr[], int sz, int* ptr1, int* ptr2)
{
	int tmp = 0;
	for (int i = 0; i < sz; i++)
		tmp ^= arr[i];
	int j = 0;
	int pos = -1;
	for(j=0; j < 32; j++)
	{
		if ((tmp & (1<<pos)) == 1)
		{
			pos = j;
			break;
		}
	}
	if (j >= 32)
	{
		*ptr1 = -1;
		*ptr2 = -1;
	}
	else
	{
		for (int i = 0; i < sz; i++)
		{
		    //把两个单独的数分开
			if ((arr[i] & (1 << pos)) == 1)
				*ptr1 ^= arr[i];
			else
				*ptr2 ^= arr[i];
		}	
	}
}
int main()
{
	int arr[] = { 1,2,3,4,5,1,2,3,4,6 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int ret1 = 0;
	int ret2 = 0;
	Find_Single_2(arr, sz,&ret1,&ret2);
	printf("%d,%d\n",ret1,ret2);
	return 0;
}

2.2.2 找凶手

日本某地发生了一件谋杀案,警察通过排查确定杀人凶手必为4个嫌疑犯的一个。
以下为4个嫌疑犯的供词:
A说:不是我。
B说:是C。
C说:是D。
D说:C在胡说
已知3个人说了真话,1个人说的是假话。 现在请根据这些信息,写一个程序来确定到底谁是凶手。

其实这个题有思路的话挺简单的,杀手可能是a, b, c, d,那就先写一个循环,其次每次进行四个条件,也就是四个人说话真假的判断,如果结果为3,即可确认凶手。

#include <stdio.h>
int main()
{
	for (char killer = 'a'; killer <= 'd'; killer++)
	{
		if ((killer != 'a') + (killer == 'c') + (killer == 'd') + (killer != 'd') == 3)
			printf("killer=%c\n", killer);
	}
	return 0;
}

2.3 打印图形类

图形编程先观察、找规律。

2.3.1 杨氏矩阵

有一个数字矩阵,矩阵的每行从左到右是递增的,矩阵从上到下是递增的,请编写程序在这样的矩阵中查找某个数字是否存在。
要求:时间复杂度小于O(N);

要求表明,不能一个元素一个元素的遍历查找;
我们根据规律,使用排除
因为“矩阵的每行从左到右是递增的,矩阵从上到下是递增的”,如下图所示,左上角是最小值,右下角是最大值,我们选取粉色位置的元素,这样比从最大或最小开始都快一些,进行排查;以右上角元素为例,使用for循环,如果查找元素<粉圈,排除这一列,列数-1;如果查找元素>粉圈,排除这一行,行数加1;同时控制行列下标不要越界。
(以4*4的数组为例)
在这里插入图片描述

#include <stdio.h>
int FindNumber(int arr[][4], int row, int col, int k)
{
	int i = 0;
	int j = col - 1;
	while (i < row && j >= 0)
	{
		if (arr[i][j] < k)
			j--;
		else if (arr[i][j] > k)
			i++;
		else
			return 1;//如果找到了,程序结束
	}
	//在此之前,程序没结束,说明没找到,返回1
	return 0;
}
int main()
{
	int arr[4][4] = { {1,2,3,4},{5,6,7,8},{9,10,11,12} };
	printf("%d\n", FindNumber(arr, 4, 4, -4));
	return 0;
}

2.3.2 杨辉三角

在屏幕上打印杨辉三角。
1
1 1
1 2 1
1 3 3 1
……

规律:

当i==0或i ==j,即第一列和对角线,元素为1;其他位置,如3,是它上方的元素的左上方的元素之和;
每一行数字的数量等于行数+1.

在这里插入图片描述

#include <stdio.h>
void Print(int(*arr)[10], int n)
{
	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j <= i; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}
void YangHuiTriangle(int(*arr)[10], int n)//VS不支持变长数组,这里暂时将最大行数设置为10
{
	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j <= i; j++)//限制每行数的数量
		{
			if (j == 0 || i == j)//第一列和对角线,元素为1
				arr[i][j] = 1;
			else
				arr[i][j] = arr[i - 1][j] + arr[i - 1][j - 1];//其他位置,是它上方的元素和左上方的元素之和
		}
	}
}
int main()
{
	int arr[10][10] = { 0 };
	YangHuiTriangle(arr,5);
	Print(arr, 5);
	return 0;
}

以行数为5进行测试,结果如下图所示。

在这里插入图片描述

2.4 数组类

2.5.1 使用指针打印数组内容

写一个函数打印arr数组的内容,不使用数组下标,使用指针。

#include <stdio.h>
void Print(int* arr, int sz)
{
	for (int i = 0; i < sz; i++)
		printf("%d ", *(arr + i));
}
int main()
{
	int arr[] = { 1,2,3,4,5 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	Print(arr, sz);
	return 0;
}

arr是一个整形一维数组。

2.5.2 调整数组使奇数全部都位于偶数前面

调整数组使奇数全部都位于偶数前面,输入一个整数数组,实现一个函数,来调整该数组中数字的顺序使得数组中所有的奇数位于数组的前半部分,所有偶数位于数组的后半部分。

思路:left=0, 如果arr[left]是奇数,left++,right=len-1,如果arr[right]是偶数,right–, 如果arr[left]是偶数,arr[right]是奇数,二者换位置,在这个过程中注意下标不要越界(凡使用下标的都要注意这一点).

#include <stdio.h>
void Func(int arr[], int sz)
{
	int left = 0;
	int right = sz - 1;
	int tmp = 0;
	while (left < right)
	{
	//有两种写法 第一种if判断语句,条件满足才执行,不需要再次控制下标
		/*if (arr[left] % 2 == 1)
			left++;
		if (arr[right] % 2 == 0)
			right--;
		if (arr[left] % 2 == 0 && right >= 0 && arr[right] % 2 == 1)
		{
			tmp = arr[left];
			arr[left] = arr[right];
			arr[right] = tmp;
		}*/
	//第二种while循环语句,需要在循环条件中加上下标控制,
	//如果不加的话,最后一次进入循环,left<right,此时已经满足奇在偶前,
	//两个子while循环,同时越界,导致结果有一对奇偶数字顺序颠倒
		while (left < right && arr[left] % 2 == 1)
			left++;
		while (left < right && arr[right] % 2 == 0)
			right--;
		tmp = arr[left];
		arr[left] = arr[right];
		arr[right] = tmp;
	}
}
int main()
{
	int arr[] = { 2,3,4,7,8,2,5,4,6,7,9,8 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	Func(arr, sz);
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

如下图调试状态,没有限制下标,此时left==right,且arr[right]是偶数,那么right–后变成4,arr[right]是奇数,二者交换,再次进行判断,不满足left<right,跳出循环,但结果不对。相当于while大条件在最后一次子while中被打破了,所以在子while中也加入下标限制,当然这只是一种情况。

在这里插入图片描述

总结

今天的题目到这里就结束啦,真是一项大工程,有很多细微的点需要去斟酌、思考,遇到的问题要解决,值得做的都值得做好,有门槛的知识需要时间消化,更需要迎难而上的气魄与勇气,但收获是值得我们去付出的。希望被生活磨练的我们最终坚不可摧,用知识武装自己,面对严峻的就业环境。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值