mirror of
https://github.com/hairrrrr/C-CrashCourse.git
synced 2026-02-12 23:15:01 +08:00
3-22
This commit is contained in:
77
code/answear/advanced C/test/02/01 实现atoi/MyAtoi.c
Normal file
77
code/answear/advanced C/test/02/01 实现atoi/MyAtoi.c
Normal file
@@ -0,0 +1,77 @@
|
||||
#define _CRT_SECURE_NO_WARNINGS 1
|
||||
|
||||
// atoi 实现
|
||||
// ElementType atoi( const char *str );
|
||||
//转译 str 所指的字节字符串中的整数值。
|
||||
|
||||
//舍弃任何空白符,直至找到首个非空白符,然后接收尽可能多的字符以组成合法的整数表示,并转换之为整数值。合法的整数值含下列部分:
|
||||
|
||||
//(可选) 正或负号
|
||||
//数位
|
||||
|
||||
#include<stdio.h>
|
||||
#include<stdlib.h>
|
||||
|
||||
#define MAXSIZE 100
|
||||
|
||||
typedef long long ElementType;
|
||||
|
||||
ElementType MyAtoi(const char* str);
|
||||
ElementType StrToNum(int numbers[], int size);
|
||||
|
||||
int main(void) {
|
||||
|
||||
char str[MAXSIZE];
|
||||
ElementType NumInStr;
|
||||
|
||||
scanf("%s", str);
|
||||
|
||||
NumInStr = MyAtoi(str);
|
||||
|
||||
printf("%lld\n", NumInStr);// 转换成大的类型需要改变 printf 的转换说明
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
ElementType MyAtoi(const char* str) {
|
||||
|
||||
ElementType ret = 0;
|
||||
int isFirstNumber = 1;
|
||||
int flg = 1;
|
||||
int numbers[MAXSIZE];
|
||||
int size = 0;
|
||||
|
||||
while (*str != '\0') {
|
||||
|
||||
if (*str >= '0' && *str <= '9') {
|
||||
// 判断这个数是否为第一个出现的数字,如果是,我们要判断前一个字符是否是 - 号
|
||||
if (isFirstNumber) {
|
||||
if (*(str - 1) == '-')
|
||||
flg = -1;
|
||||
isFirstNumber = 0;
|
||||
}
|
||||
// 直接将字符转化为数字放入数组内,数组放置的是目标数字的每一位(不带权重)。
|
||||
numbers[size++] = *str - '0';// 数字对应的 ACSII 码值减去 ‘0’对应的 ASCII 值就是实际上数组表示的值
|
||||
}
|
||||
++str;
|
||||
}
|
||||
|
||||
ret = StrToNum(numbers, size);
|
||||
|
||||
return (flg * ret);
|
||||
}
|
||||
|
||||
ElementType StrToNum(int numbers[], int size) {
|
||||
|
||||
ElementType ret = 0;
|
||||
ElementType weight = 1;
|
||||
int i;
|
||||
|
||||
for (i = size - 1; i >= 0; i--) {
|
||||
ret += numbers[i] * weight;
|
||||
weight *= 10;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
82
code/answear/advanced C/test/02/01 实现atoi/MyAtoi2.c
Normal file
82
code/answear/advanced C/test/02/01 实现atoi/MyAtoi2.c
Normal file
@@ -0,0 +1,82 @@
|
||||
#define _CRT_SECURE_NO_WARNINGS 1
|
||||
|
||||
// 这是 atoi 实现的另一个版本
|
||||
// 上一个版本其实和库里面的 atoi 实现有的地方不是一致的
|
||||
// 比如库函数 atoi 处理字符串 "abc123", 返回的结果是 0 ,而上一个我实现的 MyAtoi 会返回 123
|
||||
// "+-123", 返回的结果是 0, 而MyAtoi 会返回 -123
|
||||
// 其实我觉得我都 atoi 函数更强大哈哈
|
||||
// 但是我们也应该规范一点,毕竟是库函数
|
||||
|
||||
#include<stdio.h>
|
||||
#include<stdlib.h>
|
||||
#include<ctype.h>
|
||||
|
||||
int MyAtoi_2(const char* str) {
|
||||
|
||||
int flg = 1;
|
||||
int ret = 0;
|
||||
|
||||
if (str == NULL || str == '\0') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 判断是否为空白字符(空格,换行,制表,翻页 ...)
|
||||
while (isspace(*str)) {
|
||||
str++;
|
||||
}
|
||||
|
||||
// 判断是否为正负号
|
||||
// 用库函数 atoi 试一下就知道 "+-123" 这种字符串是无法转成数字的,所以这是一种要处理的情况
|
||||
// 如果符号位的下一个不是数字,则返回 0
|
||||
if (*str == '+' || *str == '-' ) {
|
||||
|
||||
if (*(str + 1) == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!isdigit(*(str + 1))) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (*str == '-') {
|
||||
flg = -1;
|
||||
}
|
||||
str++;
|
||||
}
|
||||
|
||||
// 处理数字字符
|
||||
while (*str != '\0') {
|
||||
if (isdigit(*str)) {
|
||||
|
||||
// 判断一下会不会超过 int 的范围
|
||||
if (((unsigned long)ret * 10 + (*str - '0')) > INT_MAX) {
|
||||
return INT_MAX;
|
||||
}
|
||||
ret = ret * 10 + (*str - '0');
|
||||
|
||||
// 在 MyAtoi 中,我选择用数组存储每一位,然后从后向前依次乘以每一位的权重
|
||||
// 但是这样直接加一个数之前先乘以 10 的做法更为简洁
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
str++;
|
||||
}
|
||||
|
||||
return ret * flg;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
|
||||
char str[100];
|
||||
int ret;
|
||||
|
||||
scanf("%s", str);
|
||||
|
||||
ret = MyAtoi_2(str);
|
||||
|
||||
printf("%d\n", ret);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
#define _CRT_SECURE_NO_WARNINGS 1
|
||||
|
||||
// strncat 实现
|
||||
|
||||
#include<stdio.h>
|
||||
#include<assert.h>
|
||||
|
||||
char* MyStrncat(char* dest, const char* src, int count);
|
||||
|
||||
int main(void) {
|
||||
|
||||
char dest[100];
|
||||
char src[50];
|
||||
int count = 0;
|
||||
|
||||
printf("Enter string dest: ");
|
||||
scanf("%s", dest);
|
||||
printf("Enter string src: ");
|
||||
scanf("%s", src);
|
||||
printf("Enter number of character(s) copy from src to dest: ");
|
||||
scanf("%d", &count);
|
||||
|
||||
MyStrncat(dest, src, count);
|
||||
|
||||
printf("%s\n", dest);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 其实我觉得这取实现程序不如不写。这种写法你上网一查都是一大片一大片的,
|
||||
// 乍一看感觉是对的,但是自己想一想,这是很危险的写法。当让你也可以归结于
|
||||
// C语言本身的语法不好,但是其实是我们的水品还没到罢了,我们想不到更好的写法,
|
||||
// 我们根本没学精 C!但是这糟糕的函数是 C 的创始人写出来的啊!似乎可以为我们找一些接口。
|
||||
|
||||
// 现在我们来说一下这样的写法有什么漏洞,首先,我们看一看 cppreference 上给的定义:
|
||||
|
||||
//后附来自 src 所指向的字符数组的至多 count 个字符,到 dest 所指向的空终止字节字符串的末尾,
|
||||
//若找到空字符则停止。字符 src[0] 替换位于 dest 末尾的空终止符。
|
||||
//始终后附终止空字符到末尾(故函数可写入的最大字节数是 count + 1 )。
|
||||
|
||||
//若目标数组没有对于 dest 和 src 的首 count 个字符加上终止空字符的足够空间,
|
||||
//则行为未定义。若源与目标对象重叠,则行为未定义。若 dest 不是指向空终止字节字符串的指针,
|
||||
//或 src 不是指向字符数组的指针,则行为未定义。
|
||||
|
||||
// 现在我来说一下我认为这个程序可能存在的问题:
|
||||
// 1.dest 函数本身加上 count 个字符是否会造成数组越界?
|
||||
// 2.dest 函数有没有 '\0' 我没有检测。如果没有,则很有可能造成越界,并且将造成非法内存访问和改写。
|
||||
// 3.如果 src 没有 '\0' 而 count 又大于 src字符数组有意义内容的大小,或者 src字符数组本身的大小,
|
||||
// 这时也会造成非法内存访问。
|
||||
// 4.src 是不是指向字符数组的指针?
|
||||
|
||||
// 如果还有问题欢迎补充,如果你有更好的解法(C语言)欢迎告诉我,我会加上你的真知灼见!(私信我即可)
|
||||
|
||||
char* MyStrncat(char* dest, const char* src, int count) {
|
||||
|
||||
assert(dest && src);
|
||||
|
||||
char* NewStr = dest;
|
||||
|
||||
// 循环结束 NewStr 指向 dest 字符串 '\0' 所在位置
|
||||
while (*NewStr != '\0') {
|
||||
++NewStr;
|
||||
}
|
||||
while (count-- && (*NewStr++ = *src++));
|
||||
|
||||
*NewStr = '\0';
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
#define _CRT_SECURE_NO_WARNINGS 1
|
||||
|
||||
#include<stdio.h>
|
||||
#include<assert.h>
|
||||
|
||||
char* MyStrncpy(char* dest, char* src, int count);
|
||||
|
||||
int main(void) {
|
||||
|
||||
char dest[100];
|
||||
char src[50];
|
||||
int count = 0;
|
||||
|
||||
printf("Enter string dest: ");
|
||||
scanf("%s", dest);
|
||||
printf("Enter string src: ");
|
||||
scanf("%s", src);
|
||||
printf("Enter number of character(s) copy from src to dest: ");
|
||||
scanf("%d", &count);
|
||||
|
||||
MyStrncpy(dest, src, count);
|
||||
|
||||
printf("%s\n", dest);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 这个函数同样是考虑不周全的:
|
||||
// 1. count 如果大于 字符数组dest 怎么办?
|
||||
// 2. count 大于 src 而 src 没有空字符怎么办?
|
||||
|
||||
char* MyStrncpy(char* dest,const char* src, int count) {
|
||||
|
||||
assert(dest && src);
|
||||
char* begin = dest;
|
||||
|
||||
while (count-- && (*begin++ = *src++));
|
||||
|
||||
// count 如果小于 src 的长度,那么得到的字符串是不含空字符的。
|
||||
// 加上 \0 是保险的。
|
||||
if (count + 1 == 0) {
|
||||
*begin = '\0';
|
||||
}
|
||||
else {
|
||||
while (count--) {
|
||||
*begin++ = *src++;
|
||||
}
|
||||
}
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
77
code/answear/advanced C/test/02/03 单身狗问题/SingleDog.c
Normal file
77
code/answear/advanced C/test/02/03 单身狗问题/SingleDog.c
Normal file
@@ -0,0 +1,77 @@
|
||||
#define _CRT_SECURE_NO_WARNINGS 1
|
||||
|
||||
// 找单身狗问题:
|
||||
// 1. 现有一个数组,只有一个元素只出现了一次,其他元素都出现了两次,请找出这个元素。
|
||||
// 2. 如果数组中有两个元素只出现了一次,其他都出现了两次,请找出这两个元素。
|
||||
|
||||
// 这两个问题都应用到了按位异或
|
||||
// 如果两个相同的数按位异或结果为零
|
||||
// 我们在前面的 两数平均值求法 中用异或来求出两数 二进制位 中 不同的位 表示的数的 和
|
||||
|
||||
|
||||
#include<stdio.h>
|
||||
|
||||
int FindOneSingleDog(int arr[], int size) {
|
||||
|
||||
int Dog = arr[0];
|
||||
|
||||
for (int i = 1; i < size; i++) {
|
||||
Dog = Dog ^ arr[i];
|
||||
}
|
||||
|
||||
return Dog;
|
||||
}
|
||||
|
||||
void FindTwoSingleDog(int arr[], int size, int* Dog1, int* Dog2) {
|
||||
|
||||
int rst = arr[0];
|
||||
int leftPos = 0;
|
||||
|
||||
for (int i = 1; i < size; i++) {
|
||||
rst = rst ^ arr[i];
|
||||
}
|
||||
//此时 rst 等价于 Dog1 ^ Dog2, 这个结果的比特位一定有一个是 1
|
||||
|
||||
// 找到第一个是 1 的比特位
|
||||
for (leftPos = 0; leftPos < 32; leftPos++) {
|
||||
if ((rst & (1 << leftPos)) == 1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 将数组元素分成两部分,一部分 leftPos 位为 1,另一部分不为 1
|
||||
// 那么 Dog1 和 Dog2 就在不同的组内了。再次按位异或,结果就是 Dog1 和 Dog2 的值
|
||||
|
||||
*Dog1 = 0;
|
||||
*Dog2 = 0;
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
// leftPos 为 1 的组
|
||||
if ((arr[i] & (1 << leftPos)) == 1) {
|
||||
*Dog1 = *Dog1 ^ arr[i];
|
||||
}
|
||||
else {
|
||||
*Dog2 = *Dog2 ^ arr[i];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
|
||||
int arr1[] = { 1, 2, 3, 4, 1, 2, 3 };
|
||||
int size1 = sizeof(arr1) / sizeof(arr1[0]);
|
||||
int Dog;
|
||||
|
||||
int arr2[] = { 1, 1, 2, 2, 3, 4 };
|
||||
int size2 = sizeof(arr2) / sizeof(arr2[0]);
|
||||
int Dog1, Dog2;
|
||||
|
||||
Dog = FindOneSingleDog(arr1, size1);
|
||||
|
||||
FindTwoSingleDog(arr2, size2, &Dog1, &Dog2);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
51
code/answear/advanced C/test/02/04 两数平均值/main.c
Normal file
51
code/answear/advanced C/test/02/04 两数平均值/main.c
Normal file
@@ -0,0 +1,51 @@
|
||||
#define _CRT_SECURE_NO_WARNINGS 1
|
||||
|
||||
// 今天在听课的时候听到一个老师讲 平均数求法,一时之间我竟无从下手,其实这个问题很早之前我们老师就说过了。
|
||||
// 虽然这种题确实是一种考验计算机专业的人的专业度的问题,但是大多老师讲完之后恐怕根本也不会去给你布置
|
||||
// 练习来强化你的记忆,所以你可能会很快忘记这种特殊的解法(也许是我太卑微了,大家都过目不忘??)。
|
||||
//所以,大多时候,讲到这个的老师无非是为了炫技,让你觉得他很专业。
|
||||
|
||||
// 但是我忘记了是真的,我没能想出来也是真的,我真菜啊,所以,我决定动手写一下
|
||||
|
||||
#include<stdio.h>
|
||||
|
||||
int main(void) {
|
||||
|
||||
int a = 10;
|
||||
int b = 11;
|
||||
double aver;
|
||||
int Aver;
|
||||
|
||||
// 方法1:直接求和除以2 这种方法的问题是:如果 a + b 会超过 int 最大能表示的范围,那结果就不是你想要的了
|
||||
|
||||
|
||||
|
||||
// 方法2:
|
||||
|
||||
// 确定两个的大小
|
||||
int max = a > b ? a : b;
|
||||
int min = a < b ? a : b;
|
||||
|
||||
// 平均值等于:大 - 后面这一坨 或者 小 + 后面这一坨
|
||||
aver = max - (max - min) / 2.0;
|
||||
|
||||
// 比较僵硬的是,整数的平均值要用浮点数来表示,不然可能会丧失精度
|
||||
printf("%.2f\n", aver);
|
||||
|
||||
|
||||
|
||||
// 方法3:这个方法可以说就是炫技的地方所在了,但是位运算确实效率是比较高的。
|
||||
|
||||
Aver = (a & b) + ((a ^ b) >> 1);
|
||||
printf("%d\n", Aver);
|
||||
|
||||
// a & b 得到的是 a,b 相同二进制位所表示的数
|
||||
// 比如 2 和 2 相同,那他们的平均值就是 2
|
||||
// a ^ b 表示 a 异或 b,得到的是 a,b 不同二进制位的和表示的数,除以 2,就是这部分两数的平均值。
|
||||
// 其实这个思想挺像 方法2 的思想,就将数分为两部分,依靠 2 进制的性质进行计算。
|
||||
// 位运算会向下取整,如果要求平均值有小数部分,那这个方法也就僵硬了
|
||||
// 参考博客:https://blog.csdn.net/weixin_43214609/article/details/83547313?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
#define _CRT_SECURE_NO_WARNINGS 1
|
||||
|
||||
// 字符串的左旋问题包含两点:
|
||||
// 1. 左旋字符串
|
||||
// 2. 判断字符串是否是另一个字符串左旋得到的
|
||||
// 这次我们一起处理这两个问题。
|
||||
|
||||
#include<stdio.h>
|
||||
#include<string.h>
|
||||
|
||||
// 让字符串左旋,我们可以先实现一个函数左旋一个字符,然后用另一个函数去调用这个函数,实现旋转多个字符
|
||||
|
||||
void LeftRotateOne(char str[], int size) {
|
||||
|
||||
char tmp;
|
||||
int i;
|
||||
|
||||
if (str == NULL)
|
||||
return;
|
||||
|
||||
if (size == 1)
|
||||
return;
|
||||
|
||||
// 先备份字符串第一个元素
|
||||
tmp = str[0];
|
||||
|
||||
// 将字符串从第二个元素开始依次向前移动一位
|
||||
for (i = 1; i < size; i++) {
|
||||
str[i - 1] = str[i];
|
||||
}
|
||||
|
||||
str[size - 1] = tmp;
|
||||
|
||||
}
|
||||
|
||||
// 这样的算法,如果 size 比较大,效率是很低的,因为数组每次移动复杂度都是 O(n)
|
||||
void LeftRotateN(char str[], int size, int n) {
|
||||
|
||||
if (str == NULL)
|
||||
return;
|
||||
|
||||
// 求模是因为:比如 “ABCD” 左旋 4 次 等于没左旋
|
||||
for (int i = 0; i < n % size; i++) {
|
||||
LeftRotateOne(str, size);
|
||||
}
|
||||
}
|
||||
// 判断 str1 str2 是否互为左旋
|
||||
int LeftRotateCmp(char str1[], char str2[]) {
|
||||
|
||||
int len1 = strlen(str1);
|
||||
int len2 = strlen(str2);
|
||||
|
||||
if (str1 == NULL || str2 == NULL)
|
||||
return -1;
|
||||
|
||||
if (len1 != len2)
|
||||
return 0;
|
||||
|
||||
// 将 str1 用 for 循环左旋 len1 次,每次左旋后与 str2 比较
|
||||
// 其实最多只用循环 len1 - 1 次即可,但是如果 写成 i < len1 - 1
|
||||
// 最后依次旋转的结果不会被比较!!而且即使多旋转了一次,相当于判断结束将 str1 归位了,一举两得!
|
||||
for (int i = 0; i < len1; i++) {
|
||||
if (strcmp(str1, str2) == 0) {
|
||||
return 1;
|
||||
}
|
||||
LeftRotateOne(str1, len1);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
|
||||
char str[] = "ABCD";
|
||||
char str1[] = "DABC";
|
||||
|
||||
//LeftRotateOne(str, strlen(str));
|
||||
//LeftRotateN(str, strlen(str), 4);
|
||||
int a = LeftRotateCmp(str, str1);
|
||||
|
||||
printf("%d", a);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
3
code/answear/advanced C/test/02/05 字符串左旋问题/readme.md
Normal file
3
code/answear/advanced C/test/02/05 字符串左旋问题/readme.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 字符串左旋
|
||||
|
||||
这个问题我们在【习题一】也就是 上一级 【01】目录中讨论过,此次给出另一个中解法
|
||||
Reference in New Issue
Block a user