「地表最强」C语言(十六)一些自定义函数和宏

环境:CLion 2021.3;64位macOS Big Sur

文章目录


地表最强C语言系列传送门:
「地表最强」C语言(一)基本数据类型
「地表最强」C语言(二)变量和常量
「地表最强」C语言(三)字符串+转义字符+注释
「地表最强」C语言(四)分支语句
「地表最强」C语言(五)循环语句
「地表最强」C语言(六)函数
「地表最强」C语言(七)数组
「地表最强」C语言(八)操作符
「地表最强」C语言(九)关键字
「地表最强」C语言(十)#define定义常量和宏
「地表最强」C语言(十一)指针
「地表最强」C语言(十二)结构体、枚举和联合体
「地表最强」C语言(十三)动态内存管理,含柔性数组
「地表最强」C语言(十四)文件
「地表最强」C语言(十五)程序的环境和预处理
「地表最强」C语言(十六)一些自定义函数和宏
「地表最强」C语言(十七)阅读程序

十六、一些自定义函数

16.1 自定义函数

16.1.1 模拟实现strcpy()

char* myStrcpy2(char* dest,const char* src)
{
	assert(src != NULL);//断言,表达式为假报错
	assert(dest != NULL);//断言,表达式为假报错
	char* ret = dest;
	while (*dest++ = *src++)
	{
		;
	} 
	return ret;
}

16.1.2 模拟实现strlen()

(1)计数器实现

size_t myStrlen(const char* str)
{
	assert(str != NULL);
	//assert(str);
	size_t len = 0;
	while (*str++ != '\0')
	{
		len++;
	}
	return len;
}

(2)递归实现

int myStrlen(char *a)
{
	if ('\0' != *a)
		return 1 + myStrlen(a + 1);
	else
		return 0;
}

(3)指针运算实现

int myStrlen(char* str)
{
	char* start = str;
	while('\0' != str)
	{
		str++;
	} 
	return str - start;
}

16.1.3 模拟实现strcat()

char* myStrcat(char *dest,const char *src)
{
	assert(dest && src);
	char *p = dest;//不操作起始地址
	//找 \o
	while(*p)
	{
		p++;
	}
	//赋值,包括\0
	while( *p++ == *src++)
	{
		;
	}
	return dest;
}

16.1.4 模拟实现strcmp()

int strcmp( const char *str1, const char *str2 )
{
	assert( str1 && str2 );
	while( *str1 == *str2 )
	{
		if( '\0' == *str1 )
			return 0;//相等,返回0
		str1++;
		str2++;
	} 
	return *str1 - *str2;//小于返回正数;否则返回负数
}

16.1.5 模拟实现strstr()

char *strstr( const char* str, const char* substr )
{//返回字串首次出现的首地址
	assert( str && substr );
	//记录每次比较的开始位置和substr的起始位置
	const char* pstr = str;
	const char* psub = substr;
	//substr是空串
	if( '\0' == *substr)
		return (char*)str;
		
	while( *pstr )
	{
		str = pstr;
		substr = psub;
		while( (*str == *substr) && *substr && *str )
			{//逐个字符比较
				str++;
				substr++;
			}
			//找到了
			if( '\0' == *substr )
				return (char*)pstr;
			//本趟未找到,开始下一次
			pstr++;
	}
	return NULL;
}
  1. KMP算法
    有空补充

16.1.6 模拟实现memcpy()

void* myMemcpy( void* dest, const void* src, size_t count )     
{
//只实现不重叠的情况,重叠的情况在myMemmove中实现
	assert( dest && src);
	void* tmp = dest;
	while(count--)
	{
		*(char*)dest = *(char*)src;
		dest = (char*)dest + 1;
		src = (char*)src + 1;
		//注意不能使用*(char*)dest++的方式自增,因为强制类型转换是一个临时的状态,
		//当++运行时(不论是前置还是后置),dest不再是char*了而是void*,自增不知道应该加多少
	}
	return tmp;
}

16.1.7 模拟实现memmove()

void *myMemmove(void *dest, const void *src, size_t count) 
{
	assert( dest && src);
	void* tmp = dest;
	//为了防止数据被覆盖的情况,分两种情况赋值
	if(dest < src)
	{//前 -> 后 赋值
		while(count--)
		{
		*(char*)dest = *(char*)src;
		dest = (char*)dest + 1;
		src = (char*)src + 1;
		}
	}
	else
	{//后 -> 前 赋值
		while(count--)
		{
			*((char*)dest + count) = *((char*)src + count); 
		}
	}
	return tmp;
}

16.1.8 模拟实现atoi():将字符串转换为整型

enum
{
	INVALID,
	VALID,
}state = INVALID;//非法的情况较多,因此将非法设置为默认状态
//将状态设置为全局变量,使用的时候只要在主函数中判断一下状态,即可知道是正常转换还是非正常转换
int myAtoi(const char *string)
{
	int flag = 1;//标记正负号
	//空指针检查
	if(NULL == string)
		return 0;
	//空字符串检查
	if(*string == '\0')
		return 0;
	//移除空白字符
	while(isspace(*string))//isspace()函数用于检查是否为空格,制表符等空白字符
		++string;
	//检查正负号
	if(*string == '-')
	{
		flag = -1;
		++string;
	}
	else if(*string == '+')
		++string;
		
	//正常情况,开始转换
	long long num = 0;
	while(isdigit(*string))
	{
		num = 10 * num + flag * (string - '0');
		//数字过大或过小
		if(num > INT_MAX || num < INT_MIN)
			return (int)num;
		string++;
	}
	//判断while循环是否正常结束
	if(*string == '\0')
	{
		state = VALID;//正常转换的结束
		return (int)num;
	}
	else
		return (int)num;
}

16.1.9 计算整数n的二进制中1的个数

int numOf1(int n)
{
	int count = 0;
	while(n)
	{
		n = n & (n - 1);
		count++;
	}
	return count;
}

16.1.10 判断一个数是否是2的n次方

int isPower2(int n)
{
	int flag = 0;
	if(0 == n & (n - 1))
		flag = 1;
	return flag;
}

16.1.11 判断两个数不同位的个数

int dif(int m,int n)
{
	int res = m ^ n;
	int count = 0;
	for(int i = 0;i < 32;i++)
	{
		if(1 == (1 & (res >> i)))
			count++;
	}	
	return count;
}

16.1.12 分别打印一个二进制数的奇数位和偶数位

void oddAndEven(int n)
{
	for(int i = 31;i >= 1;i-=2)
		printf("%d",1 & (n >> i));
	for(int i = 30;i >= 0;i-=2)
		printf("%d",1 & (n >> i));
}

16.1.13 最小公倍数

int leastMul(int m,int n)
{
	for(int i = 0; ;i++)
	{
		if(m * i % n == 0)
			return m * i;
	}
}

16.1.14 倒置字符串中的单词

void reverse(char* left,char* right)
{
	while(left < right)
	{
		int tmp = *left;
		*left = *right;
		*right = tmp;
		left++;
		right--;
	}
}
void reverseWord(char* str)
{
	char* start = str;
	while(*start)
	{
		char* end = str;
		while(' ' != *end && '\0' != *end)
			{
				end++;
			}
		reverse(start,end - 1);
		if(' ' == *end)
			start = end + 1;
		else
			start = end;
	}
}

16.1.15 计算Sn = a + aa + aaa + aaaa + aaaaa的前n项之和,不考虑溢出

int Sn(int a,int bit)
{
	int sum = 0;
	int res = 0;
	for(int i =0;i < bit;i++)
	{
		res = res * 10 + a;
		sum += res;
	}
	return sum;
}

16.1.16 打印1-100000之间的自幂数

void ziMi()
{
	for(int i = 0;i < 100000;i++)
	{
		int bit = 1;
		int tmp1 = i
		while(tmp1 / 10)
		{
			bit++;
			tmp1 /= 10;
		}
		int sum = 0;
		int tmp2 = i;
		for(int j = 0;j < bit;j++)
		{
			sum += pow(tmp2 % 10,bit);
			tmp2 /= 10;		
		}
		if(i == sum)
			printf("%d ", i);
	}
}

16.1.17 1瓶汽水1元,2个空瓶子可以换一瓶汽水,n元可以买多少汽水

int drink(int money)
{
	int drink = money;
	int empty = drink;
	int total = money;
	while(empty >= 2)
	{
		drink = empty / 2;
		empty = empty % 2 + drink;
		total += drink;
	}
	return total;
}

16.1.18 杨辉三角

void yangHui()
{
	int arr[10][10] = { 0 };
	for(int i = 0; i < 10; i++)
	{
		for(int j = 0; j <= i; j++)
		{
			if(0 == j || i == j)
				arr[i][j] = 1;
			else
				arr[i][j] = arr[i - 1][j - 1] + arr[i - 1][j];
		}
	}
	for(int i = 0; i < 10; i++)
	{
		for(int j = 0; j <= i; j++)
		{
			printf("%-3d",arr[i][j]);
		}
		printf("\n");
	}
}

16.1.19 运动员跳水问题

5位运动员参加了10米台跳水比赛,有人让他们预测比赛结果
A:B第二,我第三;
B:我第二,E第四;
C:我第一,D第二;
D:C最后,我第三;
E:我第四,A第一。
四人中每人说对了一半,请确定名次

void order()
{
	for(int a = 1; a <= 5; a++)
	{
		for(int b = 1; b <= 5; b++)
		{
			for(int c = 1; c <= 5; c++)
			{
				for(int d = 1; d <= 5; d++)
				{
					for(int e = 1; e <= 5; e++)
					{
						if((2 == b) ^ (3 == a)
						 && (2 == b) ^ (4 == e)
						 && (1 == c) ^ (2 == d)
						 && (5 == c) ^ (3 == d)
						 && (4 == e) ^ (1 == a))
						 if(120 == a * b * c * d * e)
						 	printf("a=%d b=%d c=%d d=%d e=%d",a,b,c,d,e);
					}
				}
			}
		}
	}
}

16.1.20 杨氏矩阵

有一个数字矩阵,矩阵的每行从左到右是递增的,矩阵从上到下是递增的(不要求等差递增)
编写程序在这样的矩阵中查找某个数字是否存在,要求时间复杂的小于O(N),若找到返回1,且需要这个元素的位置信息,没找到返回0

int fingYang(char arr[][3], char* px, char* py, int n)
{
	assert(px);
	assert(py);
	int x = 0; 
	int y = *py- 1;
	while(x < *px && y >= 0)
	{
		if (arr[x][y] > n)
			y--;
		else if (arr[x][y] < n)
			x++;
		else
		{
			*px= x;//通过指针将位置信息带回去
			*py= y;
			return 1;
		}
	}
	return 0;
}

16.1.21 左旋字符串

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

void strRotate(char* pch, int n)
{
	assert(pch);
	int len = strlen(pch); 
	for(int i = 0; i < n; i++)//需要旋几次
	{
		char tmp = *pch;
		for(int j = 0; j < len - 1; j++)//移动元素
		{
			*(pch + j) = *(pch + j + 1);
		}
		*(pch + len - 1) = tmp;
	}
}
  1. 三步逆序法
void reverse(char* left, char* right)
{
	assert(left && right);
	while(left < right)
	{
		char tmp = *left;
		*left = *right;
		*right = tmp;
		left++;
		right--;
	}
}

void strRotate(char* pch, int n)
{
	assert(pch);
	int len = strlen(pch);
	reverse(pch, pch + n - 1);
	reverse(pch + n, pch + len  - 1);
	reverse(pch, pch + len  - 1);
}

16.1.22 判断一个字符串是否为另一个字符串旋转之后的字符串

是返回1,否则返回0

int isRotate(char* pch1,char* pch2)
{
	assert(pch1 && pch2);
	int len = strlen(pch);
	for(int i = 0; i < len; i++)//有几个元素就旋几次,暴力求解
	{
		char tmp = *pch;
		for(int j = 0; j < len - 1; j++)//移动元素
		{
			*(pch + j) = *(pch + j + 1);
		}
		*(pch + len - 1) = tmp;
		
		if(0 == strcmp(pch1,pch2))//每旋完一次就判断一次
			return 1;
	}
	return 0;
}
int isRotate(char* pch1,char* pch2)
{
	if( strlen(pch1) != strlen(pch2) )
		return 0;//长度不一样一定不是旋转得到的
	int len = strlen(pch1);
	strncat(pch1, pch1, len);//追加一个自己本身,此时包含了所有旋转的情况
	char* p = strstr(pch1,pch2);//若是子串,则返回子串在母串中第一次出现的位置,否则返回空指针
	return p != NULL;
}

16.1.23 判断当前机器的大小段字节序

  1. 普通判断
int check()
{
	int a = 1;
	char* pa = (char*)&a;
	if(1 == *pa)
		return 1;//小端
	else
		return 0;//大端
}
  1. 使用联合体判断
int check()
{
	union U
	{
		int i;
		char c;
	}u;
	u.i = 1;
	return u.c;//小端返回1,大端返回0
}

16.1.24 一个数组中只有一个数字出现一次,其他所有数字都出现了两次,编写一个函数找出这个只出现一次的数字并返回

int FindOne(int arr[],int size)
{
	int ret = 0;
	for(int i = 0; i < size; i++)
	{
		ret ^= arr[i];//相同的数字做异或结果为0,0与任何数字异或都是那个数字本身
		//因此最后就只剩下一个不一样的数字
	}
	return ret;
}

16.1.24 一个数组中只有两个数字出现一次,其他所有数字都出现了两次,编写一个函数找出这两个只出现一次的数字并返回

假设数组中的元素为1234561234,返回5,6

void Find(int arr[], int size, int *px, int *py)
{
	int tmp = 0;//记录一下两个不同的数异或的结果
	for(int i = 0; i < size; i++)
	{
		tmp ^= arr[i];
	}
	//找两个数字第一个不同的二进制位的位置(其实也不一定非得第一个,只要找到不同就可以)
	int firDifPos = 0//位置从0开始记录
	while((tmp & 1) != 1)
	{
		tmp >> 1;
		++firDifPos;
	}
	//按照第firDifPos位是否为0分组,并将结果带回
	for(int i = 0; i < size; i++)
	{
		if(((arr[i] >> firDifPos) & 1) == 0)
			*px ^= arr[i];
		else
			*py ^= arr[i];
	}
}

16.2 自定义宏

16.2.1 写一个宏,可以将一个整数的二进制位的奇数位和偶数位交换

#define SWAP(num) (((num & 0xaaaaaaaa) >> 1) + ((num & 0x55555555) << 1))

0xaaaaaaaa转换为二进制就是10101010 10101010 10101010 10101010,将num与之做按位与运算,会将num的偶数位全部置为0(若按照0-31位计算),然后右移一位,相当于将num的奇数位移到了偶数位上;
0x55555555转换为二进制就是01010101 01010101 01010101 01010101,将num与之做按位与运算,会将num的奇数位全部置为0(若按照0-31位计算),然后左移一位,相当于将num的偶数位移到了偶数位上;
最后将上述的两个移位后的结果相加,其实就是num奇数位和偶数位交换后的结果。

16.2.2 写一个宏模拟实现offsetof,即计算结构体中某变量相对于首地址的偏移

#define OFFSETOF(struct_name,mem_name) ((int)&(((srtuct_name*)0)->mem_name))

首先将0强制类型转换为struct_name类型的指针,再用它访问目标成员,访问到后取地址,将这个地址在强制类型转换为int型,此时得到的就是目标成员相对于结构体首地址的偏移量。
在内存中的地址比如0x0000aabb,其实也就是一个十六进制的数而已,因此将0看作为地址也没什么;
这里的用法只是假设0为地址,并没有访问实际地址为0的空间,这样做的便利之处在于不必每次都用目标成员的地址减去起始地址,因为其实地址已经是0了。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值