今天我依旧是沿用之前的套路,先简单介绍一下必会的C语言知识,之后着重介绍一下本期的编程题目以及涉及的编程思想。虽然学习编程不久,但我已经感受到积累编程思想的重要性,如果说C语言,数据结构等编程知识是一台机器的零部件,那么编程思想就是驱动这台机动起来的能源所在,所以让我们一起在各种实例中积累经验,愈战愈勇吧。
一.ASCII码表:
目前为止,表中共定义了128个字符。
在计算机中,所有数据在存储和运算时都要使用二进制表示,那么像a、b、c、A、B、C等这些符号存储时也需要使用二进制来表示,所以ASCII码表就统一规定了上述常用符号用哪些二进制来表示。
这里需要注意的一点就是对应的字符也可以用它对应的十进制表示,比如a对应97,就会有如下的效果:
与此相关的一道题是让我们写一个只能输入大小写英文字母的程序,我们就可以利用大小写英文字母对应的ASCII码值的范围作为if语句限制条件:
//判断是不是字母
int main()
{
char input;
while (scanf("%c", &input) != EOF)
{
if ((input >= 65 && input <= 90) || (input >= 97 && input <= 122))
{
printf("%c is an alphabet.\n", input);
getchar();
}
else
{
printf("%c is not an alphabet.\n", input);
getchar();
}
}
return 0;
}
二.栈区,堆区,静态区
栈区:存放局部变量和形式参数
临时作用的变量均在栈区,这些变量都是进入作用域创建,出了作用域销毁。
堆区:与动态内部内存分配有关
malloc、calloc、realloc、free等。
静态区:存放全局变量和静态变量
静态区的变量创建好后,直到程序结束才销毁。
三.goto语句:
goto语句是转向语句的一种,另外三个是break、continue和return。
goto语句可以随意滥用,但我们平时要尽可能的少用,因为goto语句可以强制改变程序执行顺序,容易写出Bug,使得程序结构层次不清,其实哪怕是完全不用goto语句程序也完全可以写得下去。
goto语句使用起来也非常简单:
这样一个死循环打印hehe的程序就写完了。原理就是当程序执行到goto语句时会被告知回到被标记处接着执行,然后循环往复永远走不出这个圈子(当然start标签也可以写在goto的下面)。
当然,不要觉得goto语句能写出死循环就理所当然的觉得它是循环语句哦。
那么goto语句在哪些情况下适用呢?
1.goto语句常用于跳出深层嵌套的循环。
2.goto语句通常和if语句连用。
四.阶乘之和:
阶乘求和 1! + 2! + 3! +...+ n! = ?
要写出这个程序,我们要先求一个数的阶乘,再把它们加起来。只求阶乘很简单,循环语句走起:
ret 和 i 的初始值不能为0,毕竟0乘以任何数均为0。
写到这里我们就可以考虑一下求 1!+2!+3!+num! 的值了。我一开始的思路是求出每一项的阶乘,之后再把它们相加:
//求阶乘之和(用迭代的方法)
#include <stdio.h>
int main()
{
int num = 0;
int j = 0;
int i = 0;
int ret = 1;
int sum = 0;
int num2 = 0;
scanf("%d", &num);
num2 = num;
for (j = 0; j < num2; j++)
{
for (i = 1; i <= num; i++)
{
ret *= i;
}
sum += ret;
ret = 1;
num--;
}
printf("%d\n", sum);
return 0;
}
这里需要注意:每次循环ret都要重新赋值为1,否则加入输入的num为3,先算3的阶乘ret为6,然后ret没变为1,再下一次循环算2的阶乘时,ret就等于6*2*1=12了。除此之外,每次循环后num要减1,否则输入num为3时,相当于算了3遍3的阶乘并相加,结果也是不对的。最后num2是什么呢?num输入3时,第一个for循环目的是要将3!、2!、1!这三项相加,第二个for是为了算出3!、2!、1!的值并相加,所以如果不单独创建一个num2在num--时,也会影响到第一个for循环的循环次数,进而影响结果。
那么此程序能否优化呢?
我们可以看到,上面的程序在算阶乘之和时,每项都要从1乘到最后,有许多多余的步骤,比如5的阶乘求和乘了5次1,4次2,3次3,2次4和1次5,我们能否改变思路,将程序修改为一遍乘一边求和呢?具体表现就是:算完 1! 后就开始加到sum里,乘2算到了 2! 再加到sum里,乘3就算完了 3! 再加到sum里,这样可以省去许多不必要的步骤。
下面是改进后的程序:
#include <stdio.h>
int main()
{
int num = 0;
int i = 0;
int ret = 1;
int sum = 0;
scanf("%d", &num);
for (i = 1; i <= num; i++)
{
ret *= i;
sum += ret;
}
printf("%d\n", sum);
return 0;
}
改进后不仅代码数有所减少,代码逻辑也简单许多,所以有时候避免一些刻板的思路,我们会发现有许多捷径可走。初极狭,才通人复行数十步,豁然开朗。
五.二分查找:
在以个有序数组中,数组内元素从小打到排列,写一个程序,快速高效找出输入的一个数在此数组中能否找到,若能找到,输出该数的下标,若找不到,输出找不到。
我们首先能想到的思路就是从左往右,依次一个一个比对,这种方法也能写得出来,但效率明显不够高,所以我们用二分查找的方法去编写程序。
什么是二分查找?试想一下,我们从整个数组的正中间那个数入手进行比对,若输入的数小于那个中间数,则说明输入的数一定小于中间数右侧的所有数,一次判断就可以剔除整个数组一半的数字,之后再求出数组左侧所有数字的中间数进行比对,以此类推,效率指数级别的上升。
以下是我写的代码:
//二分查找
int i = 0;
int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int num = 0;
int left = 0;
int right = (sizeof(arr) / sizeof(arr[0])) - 1;
int mid = 0;
printf("请输入要查找的数:> ");
scanf("%d", &num);
do
{
mid = (left + right) / 2;
if (num == arr[mid])
{
printf("找到了,该数字的下标为:> %d", mid);
break;
}
else
{
if (num < arr[mid])
{
right = mid - 1;
}
else
{
left = mid + 1;
}
}
} while (left <= right);
if(left > right)
{
printf("找不到了");
}
return 0;
}
这里面只有一点需要说明,循环结束条件为什么是lesft大于right呢?我们假设输入一个很大的数,那么数组中就找不到这个数,此时left会一直赋值为mid+1,right不动,最终必然有某个时刻,left大于right,这个时候我们发现其实在left等于right依旧找不到时就已经找不到这个数了,所以把循环的限制条件设置为仅当left>=right时进入循环。
此方法我觉得算是编程小白必会的一种编程思想,以后我也会继续积累经验,多记录,多分享。