必须要掌握的工程师思维:抽象、分层
迄今为止,我在优快云训练营学到的最重要的东西,就是工程师思维,今天把它分享给大家
所谓工程师思维,一是抽象,二是分层。我把它理解为以下:
- 抽象:实现某个功能的“块”代码(可以理解为函数)
- 分层:每一层只需要关注本层的内容,所实现的功能,而不需要关注所调用的函数(子代码块)是怎么实现的。
我们拿建(布置)房子来打比方,“块”代码就是我们的一个个实现某个功能的对象(大如客厅、厨房等,小如一支笔、一瓶水,都可以是这个对象),而最终要实现的程序就是我们所要布置的房子。
那该怎么建房子呢?
- 首先当然是从做好一块砖开始(在程序里,就是把程序所要实现的功能先逐步分解,直到分解到最小的模块,不可再分为止)。
- 然后是用很多个这样的砖来实现某个特定的功能,比如做一面墙、一条梁、一块台阶等(在程序里,就是用第1步所构造的代码块,根据不同的要求,来构建功能不同的、更大的代码块)。此时不用管砖是如何被造出来的(即第1步所构造的代码块是如何实现的)。
- …………
- …………
- ………… 然后是功能更大的代码块,越来越大,
- ……
- …… 一步一步
- 直到最终实现我们所需要的功能,做出整个程序。
一个实例:
问题:假设我们要用C语言,在屏幕上输出矩形、空心矩形、等腰三角形、菱形、“回”字,该如何设计你的程序?
对于这个问题,我们先来看一个不好的案例:
#include <stdio.h>
int main()
{
int i,j;
for(i=0;i<4;i++){
for(j=0;j<4;j++){
printf("*");
if(j==3){printf("\n");}
}
}
printf("\n");
for(i=0;i<4;i++){
for(j=0;j<4;j++){
if((j==1&&i==1)||(j==2&&i==1)||(j==1&&i==2)||(j==2&&i==2)){printf(" ");}else{printf("*");}
if(j==3){printf("\n");}
}
}
printf("\n");
//以下就是直接用拼接图形的方法了,实在不知道要怎么写,太麻烦了
for(i=0;i<3;i++){
if(i==0){
printf(" * \n");
}else if(i==1){
printf(" *** \n");
}else{
printf("*****\n");
}
}
printf("\n");
for(i=0;i<3;i++){
if(i==0){
printf(" * \n");
}else if(i==1){
printf(" * * \n");
}else{
printf("*****\n");
}
}
printf("\n");
for(i=0;i<5;i++){
if(i==0||i==4){
printf(" * \n");
}else if(i==1||i==3){
printf(" * * \n");
}else{
printf("* *\n");
}
}
printf("\n");
for(i=0;i<5;i++){
if(i==0||i==4){
printf("******* \n");
}else if(i==1||i==3){
printf("* *** *\n");
}else{
printf("* * * *\n");
}
}
}
怎么样?开头的几个for循环是不是看得头都晕了?这样的代码可读性很差(你根本就无法一下子看懂),可维护性也很差(万一中间某个循环里出了问题,你得一层一层的去找,是不是很累?)。而且当我们在遇到大项目的时候,面对的是成千上万行的代码,如果还按照以前我们的经验来写程序,那到最后很容易就会让代码失控——你也不知道你写的是什么玩意儿,可能调一调程序正常了,然后过了某一段时间突然又崩溃了。
下面我们按抽象、分层的思想来解决一下这个问题。
下面我们来看一下代码:
#include <stdio.h>
//输出一个"*",或其他任意符号
int p_one(char c)
{
printf("%c",c);
return 0;
}
//输出一行n个的"*",或其他任意一行符号
int p_oneline(int n,char c)
{
int i;
for(i=0;i<n;i++){
p_one(c);
}
return 0;
}
//输出一个m行n列的矩形
int p_rectangle(int m,int n,char c)
{
int i;
for(i=0;i<m;i++){
p_oneline(n,c);
printf("\n");
}
return 0;
}
//输出一个m行n列的空心矩形
int p_hollow_rectangle(int m,int n,char c)
{
int i;
p_oneline(n,c); //先输出空心矩形的第一行
printf("\n");
for(i=0;i<m-2;i++){ //中间输出矩形的空心的部分,一共m-2行
p_one(c);
p_oneline(n-2,' ');
p_one(c);
printf("\n");
}
p_oneline(n,c); //最后输出空心矩形的最后一行
printf("\n");
return 0;
}
//输出一个底为m,高为n的等腰三角形
int p_triangle(int m,int n,char c)
{
int i;
for(i=1;i<=n;i++){
p_oneline( (m-(2*i-1))/2 ,' '); //一行的开头先输出((m-(2i-1))/2)个空格
p_oneline(2*i-1,c); //中间输出(2i-1)个'*'
p_oneline( (m-(2*i-1))/2 ,' '); //一行的末尾也要输出((m-(2i-1))/2)个空格
printf("\n");
}
return 0;
}
//输出菱形的上部分长为m,高为n的三角形
int p_up_triangle_for_diamond(int m,int n,char c)
{
int i;
for(i=1;i<=n;i++){
p_oneline( (m-(2*i-1))/2+1 ,' '); //一行的开头先输出((m-(2i-1))/2+1)个空格
p_oneline(2*i-1,c); //中间输出(2i-1)个'*'
p_oneline( (m-(2*i-1))/2+1 ,' '); //一行的末尾也要输出((m-(2i-1))/2+1)个空格
printf("\n");
}
return 0;
}
//输出菱形的下部分长为m,高为n的三角形
int p_down_triangle_for_diamond(int m,int n,char c)
{
int i;
for(i=n;i>=1;i--){
p_oneline( (m-(2*i-1))/2+1 ,' '); //一行的开头先输出((m-(2i-1))/2+1)个空格
p_oneline(2*i-1,c); //中间输出(2i-1)个'*'
p_oneline( (m-(2*i-1))/2+1 ,' '); //一行的末尾也要输出((m-(2i-1))/2+1)个空格
printf("\n");
}
printf("\n");
return 0;
}
//输出一个水平对角线为m,竖直对角线为n的菱形
int p_diamond(int m,int n,char c)
{
int i;
p_up_triangle_for_diamond(m-2,n/2,c); //输出菱形的上半部分三角形
p_oneline(m,c); //输出菱形的水平对角线
printf("\n");
p_down_triangle_for_diamond(m-2,n/2,c); //输出菱形的下半部分三角形
return 0;
}
//输出“回”字的对称轴相邻的行
int p_AxisOfSymmentry_OfHui_neighbor(char c)
{
p_one(c);
p_one(' ');
p_oneline(3,c);
p_one(' ');
p_one(c);
return 0;
}
//输出“回”字的对称轴
int p_AxisOfSymmentry_OfHui(char c)
{
int i;
for(i=0;i<7;i++){
if(i%2==0){
p_one(c);
}else{
p_one(' ');
}
}
return 0;
}
//输出一个长为7,宽为4的“回”字
int p_hui(char c)
{
p_oneline(7,c); //输出最上面一行的“横”
printf("\n");
p_AxisOfSymmentry_OfHui_neighbor(c); //输出“回”字对称轴的相邻行
printf("\n");
p_AxisOfSymmentry_OfHui(c); //输出“回”字的对称轴
printf("\n");
p_AxisOfSymmentry_OfHui_neighbor(c); //输出“回”字对称轴的相邻行
printf("\n");
p_oneline(7,c); //输出最下面一行的“横”
printf("\n");
return 0;
}
int main()
{
p_rectangle(4,3,'*');
printf("\n");
p_hollow_rectangle(5,5,'x');
printf("\n");
p_triangle(5,3,'#');
printf("\n");
p_diamond(5,5,'$');
p_hui('@');
return 0;
}
怎么样?是不是清晰了许多?至少不用再一个循环一个循环里去找了。而且我当初在写这段代码的时候,最后调试几乎一次性就成功了,对比之前那段代码,调了好多遍才出结果。
从中我们可以看出抽象、分层这种思想的精髓,只要你最低层的代码写对了(那块砖做好了),以后就不需要再管这段代码了,它不可能出错。错误只可能出现在你调用的地方。
最后来看一下我们的成果: