鸟类天堂:
一:函数类型的对应
代码呈现:
二:浮点数运算
三:scanf的使用
NUM 1:遇见空格,回车符,制表符,结束。
NUM 2:达到域宽结束
NUM 3:输入非法字符结束
NUM 4:结束之后的回车会存储在缓冲区
代码呈现:
四:异或操作符的妙用
NUM 1:找单身狗问题
代码呈现:
NUM 2:寻找缺失的数字
代码呈现:
五:“字符串”部分移动
倒序字符串
简易方法一
代码呈现 :
简易方法二
代码呈现 :
旋转数组:
代码呈现 :
欢迎各位伙伴们来到我们以C语言为专题的冷知识课堂,每系列的博客当中都会抽取五个最易错的冷知识和大家分享,快去看看吧!
(1)在编写代码的时候一定要注意函数的返回类型是否和自己标明的一致,函数并不会检查所有类型不匹配的问题,如整形和浮点型不匹配只会报一个警告,极容易被忽略。之前我定义过一个函数,我定义函数的返回类型应该是int但是却将一个浮点数直接返回了,使得1.78-1.23的时候虽然结果是0.55,但是经过强制类型转换float->int之后就变成了0。导致程序运行出现错误。
我们需要将函数的类型改为浮点型值后才可以正确计算。
如上图的运行结果所示。
//#include
//
减法函数
//int sub(double a, double b)
//{
// return a - b;
//}
//
//int main()
//{
// double a = 0.0, b = 0.0;
// scanf("%lf %lf", &a, &b);
// double ret=sub(a,b);
// printf("%.2lf", ret);
// return 0;
//}
#include
//减法函数
double sub(double a, double b)
{
return a - b;
}
int main()
{
double a = 0.0, b = 0.0;
scanf("%lf %lf", &a, &b);
double ret = sub(a, b);
printf("%.2lf", ret);
return 0;
}
(2)浮点数的比较只可以运用一定的范围进行比较,如果大家去查阅资料的话,官方的比较浮点数的方法肯定都是范围比较。范围比较的含义也就是:想要判断两个浮点数谁大谁小,那么就需要把这两个浮点数相减去和自定义的 “0” 进行比较,也就是我们需要将两数之差和1e-7进行比较(1e-7在浮点数的精确度上等于0)。官方的解释是:浮点数并非真正意义上的数字,只是其在某种范围内的近似。因此也就只能用近似的方法将实数与0进行比较。 这个在超过七位小数运算与0比较上值得大家注意。(7位之后是随机数不一定为0),我们可以利用一个实例来理解这部分的知识。
就像是 上图中我们看到的结果一样,并不是所有的浮点数在内存中储存的都是我们输入的准确值,那么我们上面的浮点数需要进行范围比较的说法就容易理解得多了。浮点数在内存中的存储请参考:http://t.csdn.cn/7MFlV
(3)关于 scanf 值得我们注意的是 scanf 的读取结束判定条件,以及结束后的注意事项。
也就是说当你需要输入一个字符串的时候,如果字符串中存在空格,制表符,(可以产生四个空格),回车,对于 scanf 可能无法正常读取,遇到这三种符号会自动判定第一次输入结束。通常我们可以利用 gets 进行替代 scanf 对存在空格等特殊符号的字符串进行输入。
虽然很少,但是我们刷题的时候也会看到输入的数字等数据是一行输入且没有空格等分割标志,那么我们就需要限定域宽对输入的两个或多个数据进行限定。如:我想输入12和34,但是输入的却是1234这时候我们就需要将我们 scanf 中的内容改为 sacnf("%2d%2d",&a,&b); 这样我们的scanf 进行数据读取的时候就会读到两位之后自动判定输入为下一个数字。
当我们使用 sacnf 的时候我们一般都会有特定要求的输入格式,比如:输入一个整型就是 %d,那么我们在键盘上输入的只能是连续的数字,否则也会异常结束。就像我们想要输入一个数字1234,但是我们再输入的时候却一不小心打错了,打成了12e4,这个时候我们输入的数字就变成了 12 。读到字符 e 的时候由于 e 不属于整形所以 scanf 判定输入结束。
当我们输入数据结束的时候一般都会输入回车来告诉程序,我已经输入完毕了,你可以开始运行了。但是我们需要特别注意的是,这个回车仅仅是告诉程序开始执行后就消失了吗?不,它会停留在缓冲区中。等待下一次被唤醒,就一个简单的例子:当我们需要分两次输入的时候第一次输入一个整数,第二次输入一个字符,那么我们必须对这个回车符进行处理了。我们用一个程序来证明。
我们可以看出的程序似乎出现了异常,第一次输入一个整型 45 之后原本应该再一次进行输入一个字符的,但是系统却异常结束了,中间还空了一行。这就验证了我们上面所说的,我们在输入第一个整形的时候残留下的一个回车并没有消失,下次读取一个字符并打印的时候会先读取回车。(但是不要担心,假如你想输入的第二个数据不是字符的话,那么不用考虑这个回车符,因为scanf 会自动匹配到第一个符合的数据进行输出。) 如果出现这种情况的话,可以利用 getchar 函数进行读取一个字符,我们将缓冲区的回车拿走之后就不会出现这种情况了。如下图。
//scanf不会删除输入的回车符证明:
//#include
//int main()
//{
// int n = 0;
// char ch = 0;
//
// scanf("%d", &n);
// printf("%d", n);
//
// scanf("%c", &ch);
// printf("%c", ch);
//
// return 0;
//}
//解决办法:
#include
int main()
{
int n = 0;
char ch = 0;
scanf("%d", &n);
printf("%d\n", n);
getchar(); //拿走回车
scanf("%c", &ch);
printf("%c", ch);
return 0;
}
在刷题的过程中你经常会被一些很奇特的思想所震惊到,异或操作符就是其中之一。所谓的异或就是将两个存储在内存中的数据按位进行操作。两个数据依次对其,每一位上的数字相等就为0,不相等就为1。例如:
但是你看到这里肯定一头雾水:这有什么用呀?别着急,这需要涉及异或操作符的一个特性,连续操作同一个数字时还会变成原来的数。
那么接下来的内容就有趣了,我们可以利用异或操作符的这个性质做许多事,比如说加密,找到数组中的重复的数字,找落单的数字,对 1—N 无序数列找出缺失的数字等等。我们通过几道题目来体会一下其中的奥秘。
这个题目的要求是这样的:有一个数组中的数字总是成对存在,但是有一个数字却只有一个,那么请找出这个数字。要是我们不适用异或的方法会怎么求解呢?循环嵌套之后一个一个匹配?这不免效率太慢了,但是我们要是使用异或操作符进行求解的话只需要进行一遍。就可以得到想要的答案。
我们经过尝试可以发现0无论异或任何数字得到的结果都会是这个数字,那么我们就可以利用这个性质对数组进行操作。是不是一下子变得很简单?
那么我们的问题是不是得到了解决?简单了不少吧?别着急精彩的还在后面!
这道题的题目要求是这样的:从 1—N一共N个数字乱序存储在一个数组中,但是存储的时候不小心漏掉了一个数字,请找出漏掉的这个数字。看到这个题目的时候别着急取用循环嵌套,看咱们的大标题!异或操作符呀!但是你可能会说这里没有的数字呀?没有枪没有炮我们自己造!我们可以先使用0进行从1—N进行第一遍异或,之后将之后的结果再和数组中的数字异或,这么一来不是又出现落单的数字了吗?我们按照这个思路来执行我们的操作。
这么一简化,是不是程序变得更加简洁了?编写思路也更加清晰了呢?那么下一次再遇到对数组中的数字进行修改的问题记得优先考虑异或哦!
#include
int main()
{
//N为20 缺失的数字是 14
int arr[20] = { 3,2,5,4,7,9,8,1,6,10,13,16,12,15,18,19,17,20,11 };
int n = 20, i = 0, res = 0;
for (i = 1; i <= n; i++)
{
res ^= i;
res ^= arr[i-1];
}
printf("%d", res);
return 0;
}
还记得我们之前说到的我第一次oj考试吗?上面有一道字符串逆序的题目,我当时傻傻的使用 getchar 进行一个字符串一个字符串的读取,导致那道题我到最后时间到了也没有写完,现在想想真的好傻,那我们就利用这道题来给大家讲讲一个更加简单的思想:“字符串”的部分逆序。题目如下:
拿到这道题的时候是不是觉得思路很简单,但是好复杂?先创建三个数组?将三个单词分别装起来?NO,NO,NO,题目中可没说每次测验只对三个单词进行位置转换。怎么样?一下子被难住了?那么我们接下来看一种奇特的思想:
对于这种部分前置的题目我们可以先对部分进行逆序操作,在对整体进行逆序操作。示例如下:
是不是很神奇?我第一次也是这么觉得的,难以置信!这是怎么想出来的!没办法总有一些大佬能想到好的方法,我们只需要对这种方法进行学习即可。那么在遇到对部分进行不调换顺序前置的情况我们就可以利用这种方式进行操作。记住先部分逆序,再整体逆序。下面我们将上面的思想转化为代码的形式:
#include
#include
int main()
{
char ch[100] = { 0 };
gets(ch); //像静态区中输入字符串包括空格
int i = 0, left = 0, right = 0;
char tmp = 0;
int len = strlen(ch);
//对部分进行逆序
for (i = 0; i < len; i++)
{
if ((ch[i] == ' ')||(i==(len-1)))
{
if (ch[i] == ' ')
{
right = i-1;
}
else
{
right = i;
}
//对下标为left到i-1的单词进行逆序
for (; left
虽然上面的思路有了,但是是不是感觉还是有点复杂?我第一次编写这个代码的时候调试错误调试了好几个小时,总会忽略一些细节。那么觉得麻烦的话我再来给大家介绍一种方法:部分倒置的时候肯定得有一个标志吧?让字符串从后向前查找,找到这个标志对后面字符串中的内容直接打印。如下:
这样的话我们就可以将我们的第二次两次逆序操作都省去了,怎么样?这次够简单了吧?
运行效果和想象的一样。那么接下来就将代码呈现给大家:
#include
#include
int main()
{
char a[1000];
int len = 0, i = 0, j = 0;
gets_s(a);
len = strlen(a);
for (i = len - 1; i >= 0; i--)
{
if (a[i] == ' ')
{
for (j = i + 1; a[j] != '\0' && a[j] != ' '; j++)
{
printf("%c", a[j]);
}
printf(" ");
}
}
i = 0;
while (a[i] != ' ')
{
i++;
}
for (j = 0; j < i; j++)
{
printf("%c", a[j]);
}
return 0;
}
是不是感觉一道题做起来不过瘾?我们接着看一道类似的题目:旋转数组:给定一个数组arr[7]={1,2,3,4,5,6,7},旋转N次,求旋转之后的数组是什么样的。(旋转指的是将数组中的最后一个数字拿到第一个数字的位置上,其他数字向后移,例:旋转一次:7,1,2,3,4,5,6)你可能没发现这道题和我们前面讲的旋转数组有什么区别,但是仔细解析一下就会恍然大悟,把旋转的次数作为数组的个数,从后向前进行计数,然后直接打印。例如:
这么一来我们就可以用我们的老方法进行求解这道题了,简单又高效!只不过需要注意的是我们每旋转七次都是一个循环,数组会恢复到原本的样子,因此我们需要对输入的旋转的次数进行取余操作,防止运行出错。我们程序的编写效果如下:
#include
int main()
{
int arr[7] = { 1,2,3,4,5,6,7 };
int n = 0, i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
printf("请输入旋转的次数:");
scanf("%d", &n);
n %= 7;
for (i = 0; i < n; i++)
{
printf("%d ", arr[sz-n+i]);
}
for (i = 0; i < sz - n; i++)
{
printf("%d ", arr[i]);
}
return 0;
}