好库文摘 http://doc.okbase.net/ 自制操作系统Antz day12——承上启下 http://doc.okbase.net/LexMoon/archive/305511.html AntzUhl 2018/10/18 13:58:36

我已经规范了系统代码风格,类似于按照linux分包,把各部分功能区分开了

Antz系统更新地址

Linux内核源码分析地址

Github项目地址

在之前的工作中,AntzOS已经从单调的界面,变得逐渐拥有自己的功能了。

真机运行情况 :

os1

os2

os3

这个系统在我最初的目的中就是实现一个半图形半终端的轻巧OS。

完成了当前的工作后,Antz接下来需要实现的则是关于任务调度相关的。

目前实现的是在Terminal中对命令的响应,还有一个简易的vim,可以用于右边界面的文本编辑。

对于按键中断,对全键盘的响应改良之后不会出现之前说的bug,但是在shift按下时的按键模式却是有很大问题,虽然我已经想到了解决方案,不外乎给shift的按下一个flag,弹起一个flag,但这部分感觉现在实现与否都是不怎么重要,所以就先忽略这里了。

最近同时也在读Linux内核源码。发现其中的注释也是很有意思,甚至Linus自己写的,他也不知道这部分为什么这样写,不断尝试之后发现可以实现,他就这样用了。

仔细看看关于按键响应命令的实现,现在只能说很懵,又臭又长,估计随便改改我就会不知道怎么继续下一步了。(不过好在我每添加一个功能都会把整个项目备份一份)

void key(Binfo binfo,char s[40]){
    if((strcmp(s,"1C")==0)){  // enter
            if(x_move!=0){
                // 右边
                write_x = 58 ;
                write_y += 19 ;
            }else {
                // 左边
                action_command(binfo);
                write_x = 58 ;
                write_y += 19 ;
                if (x_move==0)
                    printasc(binfo->vram, binfo->scrnx, 4, write_y, COL8_FFFFFF, "AntzOS>");
            }
    }else if((strcmp(s,"0F")==0)){
        // 关于tab 0F 8F
        printasc(binfo->vram, binfo->scrnx, x_move+write_x, write_y, COL8_FFFFFF, " ");
        write_x += 8 ;
        border(binfo);
        printasc(binfo->vram, binfo->scrnx, x_move+write_x, write_y, COL8_FFFFFF, " ");
        write_x += 8 ;
        border(binfo);
        printasc(binfo->vram, binfo->scrnx, x_move+write_x, write_y, COL8_FFFFFF, " ");
        write_x += 8 ;
        border(binfo);
    }else if((strcmp(s,"3B")==0)){  //关于F1的响应中断
            sprintf(command,"%s","");
            // flag = 0 
            x_move = 0 ;
            new_pe(binfo);
            printasc(binfo->vram, binfo->scrnx, 4, write_y, COL8_FFFFFF, "AntzOS>");
    }else if(strcmp(s,"0E")==0){
            // 回退
            int len = strlen(command);
            command[len - 1] = '\0';
            write_x -= 8 ;
            flash(binfo->vram, binfo->scrnx , COL8_000000,  x_move + write_x,     write_y,     x_move+write_x+19, write_y+19);
            if(x_move!=0){
                // 正在右边界
                    if(write_x<=60) {
                        write_x = 202 ;
                        write_y -= 19 ;
                    }
            }else if(x_move==0){
                    // 正在左边界
                    if(write_x<=4) {
                        write_x = 146 ;
                        write_y -= 19 ;
                    }
            }

    }else {
            char *in = replace_char(s) ;
            if(strcmp(in,"")==0){

            }else {
                printasc(binfo->vram, binfo->scrnx,  x_move + write_x,  write_y, COL8_FFFFFF, in);
                add_command(in);
                write_x += 8 ;
            }
            // 添加响应区
            //清除
            //打印字符 Only use debug
    }
    border(binfo);
}
// 边界处理
void border(struct BOOTINFO *binfo){
    if (x_move==0){
        // 左边
        if(write_x>148){
            write_x = 4 ;
            write_y += 19 ;
        }
        if(write_y>180){
            new_pe(binfo);
            printasc(binfo->vram, binfo->scrnx, 4, write_y, COL8_FFFFFF, "AntzOS>");
        }
    }else if(x_move!=0){
        // vim模式
        if(write_x>310-x_move){
            write_x = 58 ;
            write_y += 19 ;
        }
        if(write_y>180){
            write_y = 15 ;
            write_x = 58 ;
            flash(binfo->vram,binfo->scrnx,COL8_000000, 160,0,320-3,260-3);
            printasc(binfo->vram,binfo->scrnx,162,2,COL8_00FF00,"Vim :");
        }
    }
}

目前的项目目录,请忽略掉md文件,这个镜像文件可以直接使用工具写入u盘启动,或者在虚拟机打开。thun.c是今天(2018年10月18日)新增的,目的是为了抽离其他c文件中的工具化函数,不然以后只是一个源码文件都会让人头疼。

▒▒  antz.img
▒▒  Makefile
▒▒  README.md
▒▒
▒▒▒▒asmfunc
▒▒      naskfunc.nas
▒▒
▒▒▒▒assic
▒▒      char.set
▒▒
▒▒▒▒boot
▒▒      asmhead.asm
▒▒      mbr.asm
▒▒
▒▒▒▒include
▒▒      errno.h
▒▒      float.h
▒▒      limits.h
▒▒      math.h
▒▒      stdio.h
▒▒      string.h
▒▒      todo.h
▒▒
▒▒▒▒interrupt
▒▒      int.c
▒▒
▒▒▒▒io
▒▒      fifo.c
▒▒
▒▒▒▒lgdt
▒▒      set_lgdt.c
▒▒
▒▒▒▒log
▒▒      build.log
▒▒      delete.log
▒▒
▒▒▒▒main
▒▒      bootpack.c
▒▒      README..md
▒▒      shell_1.md
▒▒      shell_2.md
▒▒
▒▒▒▒thun
▒▒      thun.c
▒▒
▒▒▒▒windows
        graphic.c

Makefile非常关键,如果没有这个,怕是我只是编译链接就得花费很长时间,而且中间估计会错误频出。

Antz_kernel = main/bootpack.obj asmfunc/naskfunc.obj assic/char.set windows/graphic.obj lgdt/set_lgdt.obj interrupt/int.obj io/fifo.obj thun/thun.obj

之后的添加c文件,只需要在这里预先确定路径,然后将需要实现的函数声明在include/todo.h中即可。显卡虽然实现了函数可以直接操作,但是分辨率太低,导致界面极不美观,网卡驱动暂时没有想过要写,如果要实现,恐怕得整个antz都可以让用户使用的情况下才具备条件。

AntzOs需要更多优秀的开发者来实现,无论你是正在学习操作系统课程还是其他方面,我相信AntzOs都可以帮助你更加的深入了解计算机操作系统底层的实现。

]]>
Python:lambda表达式实现求两个变量的最大值 http://doc.okbase.net/longyuu/archive/305510.html 直着走的小螃蟹 2018/10/18 13:58:29

lambda 表达式(又称匿名函数)

  作用:
    创建一个匿名函数对象
    同def 类似,但不提供函数名
  格式:
    lamdda [参数1,参数2,.....]: 表达式(默认只能写一个)

  说明:

    1.lambda 只是一个表达式,它用来创建一个函数对象
    2.当lambda表达式调用时,先执行冒号(:)后的表达式,并返回表达式的结果的引用关系
    3.lambda 表达式创建的函数只能包含一条表达式
    4.lambda 比函数简单且可以随时创建和销毁,有利于减少程序的偶合度
#方法1
def mymax(x,y):
    return max(x,y)
print('def语句实现:',mymax(11,22))
print('def语句实现:',mymax(45,18))

#输出结果:
def语句实现: 22
def语句实现: 45

#方法2
fa = lambda x,y:x if x > y else y
print('lambda表达式实现:',fa(23,34))
print('lambda表达式实现:',fa(35,12))

#输出结果
lambda表达式实现: 34
lambda表达式实现: 35

 

]]>
现代C语言程序设计之数据存储 http://doc.okbase.net/ittimeline/archive/305509.html ittimeline 2018/10/18 13:33:31

现代C语言程序设计之数据存储

2.1 计算机信息数据存储

2.1.1 计算机信息数据存储单位

在计算机最底层,数据都是以二进制(01010)的方式存储,而计算机中最小的存储单位是位(bit),用来表示0或者1。计算机中最基本的存储单位是字节(Byte),1个字节对应8个位(Bit)。
而日常应用中常使用的基本存储单位包括KB,MB,GB,TB,PB,

  • KB,MB:使用迅雷下载某些资源时的网速就是KB或者MB,它们之间的换算关系如下
1MB=1024KB
1KB=1024B
1B=8bit

但是网络运营提供商(例如长城宽带、移)声称的百兆带宽实际上是100Mb,但是网络下载速度是以字节为单位的,因此真实的网速理论上只有100Mb/8=12.5MB

  • GB:在买内存或者买移动硬盘时,通常使用的存储单位就是GB
1GB=1024MB
1TB=1024GB

但是在买4T的移动硬盘时,实际的可用容量却只有3T多,因为计算机的存储单位是以2的10次方(即1024)换算,而硬盘厂商们是以1000为换算单位。

4T的硬盘换算成位如下所示

4T=4*1024GB*1024MB*1024KB*1024B*8bit

而硬盘厂商的实际容量

4T=1000*1000*1000*1000*8

因此实际的可用容量是

4*1000*1000*1000*1000/1024/1024/1024/10243.63T

而在一些互联网巨头(例如国内的BAT,国外的亚马逊、苹果、微软、谷歌)公司中,可能使用到比TB更大的海量数据,也就是PB或者EB。

1PB=1024TB
1EB=1024PB

2.1.2 计算机内存

为什么说32位系统只能使用4G内存?下面是4G的内存换算

4G=2^2 * 2^10 * 2^10 * 2^10 =4*1024*1024*1024=2^32

因为4G只能够寻址到2^32,使用16进制表示就是0xFFFFFFFF,这里可以借助Visual Studio的调试功能查看内存的寻址,如下图所示
内存寻址

2.2 变量

2.2.1 变量概述

内存在程序看来就是有地址编号的一块连续空间,当数据放到内存中后,为了方便的找到和操作这个数据,需要给这个位置起名字,编程语言通过变量来表示这个过程。

2.2.2 变量的声明和初始化赋值

在使用变量前必须先要声明变量并初始化赋值,并且要遵守变量的命名规范

  • 变量名由字母数字下划线组成,不能以数字开头
  • 变量名区分大小写。
  • 变量名不能是C语言的关键字(Visual Studio中的关键字都是蓝色的)
  • 考虑到软件的可维护性,建议变量见名知意

如下应用案例所示展示了C语言的变量命名案例

#include <stdio.h>
#include <stdlib.h>
/*
    变量的命名规范
*/
void main() {
    //合法的标识符
    int number;
    //见名知意
    int age;
    char ch;
    double db;
    //变量名不能是关键字
    //int void;
    //变量名不能以数字开头
    //int 1num;
    /****************************************编译器特性*******************************/
    //VC支持中文变量,GCC不支持中文命名
    int 年龄 = 29;
    printf("年龄 =%d\n", 年龄);
    //在老版(C++11之前)的编译器中,变量声明必须放在函数调用之前
    /****************************************编译器特性*******************************/
    //声明多个变量
    int one, two, three;
    system("pause");
}

在声明变量后,一定要给变量赋初始值,否者无法编译通过,如下应用案例所示

#include <stdio.h>
#include <stdlib.h>
/*
    变量初始化赋值
    在使用变量时必须手动初始化赋值,否则会得到一个随机的垃圾值
*/
void main(){
    int num;
    //编译错误错误	C4700	使用了未初始化的局部变量“num”
    printf("num =%d\n",num);
    system("pause");
}

2.2.2 变量存储

如下应用程序所示,通过"="可以给变量赋值,同时可以通过printf()函数传递%p参数来获取变量在内存中的地址。

#include <stdio.h>
#include <stdlib.h>
/*
    变量在内存中的存储
*/
void main () {
    int num = 20;
    //查看num的内存地址
    printf("整数变量num的地址是%p\n", &num);
    printf("整数变量num = %d\n", num);
    num = 30;
    printf("修改之后整数变量num的值是%d\n", num);
    system("pause");

}

如下图所示,还可以通过Visual Studio 提供的调试功能通过断点查看变量在内存的存储,通过输入变量的内存地址便可以观察变量对应的值。
变量在内存中的存储
在同一时刻,内存地址对应的值只能存储一份,如果修改地址对应的值,之前的值会被覆盖,这个就是变量的特点,变量名是固定的,但是变量值在内存中是随着业务逻辑在变化的,例如最常见的游戏场景中,游戏人物生命值的变化。

2.2.3 编译器对变量的处理

当在程序中声明变量并赋值时,编译器会创建变量表维护变量的信息,包括变量的地址,变量的类型以及变量的名称。
而在内存中变量的内存地址和变量值是一一对应的,编译器正是通过变量表的内存地址和内存中的变量地址关联。因此在使用变量进行相关操作之前必须先声明并赋值,否则程序会发生编译错误,如下代码片段所示。

#include <stdio.h>
#include <stdlib.h>

/*
    编译器和内存对变量的处理
*/
void main(){

    int a, b, c;
    //不能使用未声明的变量
    printf(" %d\n",d);
    system("pause");
}

2.2.4 变量运算的原理

当两个变量在执行相关运算(例如加法)时,系统会将把两个变量地址对应的变量值移动到CPU内部的寄存器中执行运算后将运算结果返回给内存,如下应用程序所示

#include <stdio.h>
#include <stdlib.h>
/*
    变量运算的原理
*/
void main() {
    int a = 1;
    int b = 2;
    //分配四个字节的内存
    int c;
    printf("变量a的地址是%p\t,变量b的地址是%p\t,变量c的地址是%p\n",&a,&b,&c);
    //数据的运算是在CPU的寄存器完成的
    c = a + b;
    c = b - a;
    //对数据的操作是由CPU完成的
    //a + 1 = 4;
    printf("c=%d\n",c);
    system("pause");

}

如下图所示,可以借助VisualStudio的调试功能来观察EAX寄存器的变化的值。
EAX寄存器

为了能够更加直接的理解寄存器的作用,这里使用C语言嵌套汇编语言来完成变量的赋值运算和加法运算。

#include <stdio.h>
#include <stdlib.h>
/*
    使用汇编语言实现变量的赋值以及运算来理解数据的运算是在CPU内部的寄存器完成的
*/
void main() {

    //申请四个字节的内存
    int a;
    printf("整数变量a的地址是%p\n",&a);

    
    //变量的赋值都是通过CPU的寄存器来完成的
    //这里借助汇编语言实现将10赋值给变量a
    _asm {
    
        mov eax, 10
        mov a, eax
    }

    printf("整数变量a的值等于%d\n",a);

    _asm {
    
        //把变量a的值赋值给寄存器eax
        mov eax,a
        //将eax的值加5
        add eax,5
        //把eax的值赋值给a
        mov a,eax
    }
    printf("变量a加5之后的结果是%d\n",a);
    system("pause");
}

2.2.5 变量交换的实现

如下应用案例所示,实现了三种变量交换的算法,同时也比较了每种算法的时空复杂度,变量交换的应用场景主要在使用在排序算法中。

1.通过使用中间变量实现交换

#include <stdio.h>
#include <stdlib.h>
/*
    使用临时变量实现变量交换
    赋值运算三次 
    增加空间
*/
void varriable_swap_with_tmp(int left,int right) {

    printf("使用临时变量实现变量交换交换之前\t left=%d \t right=%d\n",left,right);
    int middle = left;
    left = right;
    right = middle;
    printf("使用临时变量实现变量交换交换之后\t left=%d \t right=%d\n",left,right);
}

void main() {
    int left = 5;
    int right = 10;
    varriable_swap_with_tmp(left,right);
    system("pause");
}
  1. 使用算术运算实现变量交换
#include <stdio.h>
#include <stdlib.h>
/*
    使用算术运算实现变量交换 考虑数据越界的问题
    不需要开辟额外的空间
    赋值运算三次,算术运算三次 总运算次数6次
*/
void varriable_swap_with_algorithm(int left,int right) {
    printf("使用算术运算实现变量交换交换之前\t left=%d \t right=%d\n", left, right);
    left = left + right; // 加号变成乘号
    right = left - right;//减号变成除号
    left = left - right; //减号变成除号
    printf("使用算术运算实现变量交换交换之后\t left=%d \t right=%d\n", left, right);
}


void main() {

    int left = 5;
    int right = 10;

    varriable_swap_with_algorithm(left,right);
    system("pause");
}
  1. 使用异或运算实现变量交换
#include <stdio.h>
#include <stdlib.h>

/*
    使用异或运算实现变量交换 
    不用考虑运算结果溢出的问题
*/
void varriable_swap_with_xor(int left, int right) {
    printf("使用异或运算实现变量交换交换之前\t left=%d \t right=%d\n", left, right);
    left = left ^ right;
    right = left ^ right;
    left = left ^ right;
    printf("使用异或运算实现变量交换交换之后\t left=%d \t right=%d\n", left, right);

}

void main() {
    int left = 5;
    int right = 10;
    varriable_swap_with_xor(left,right);
    system("pause");
}

2.2.6 自动变量与静态变量

在函数中的形式参数和代码块中的局部变量都是自动变量,它们的特点是只有在定义的时候才会被创建(即系统自动开辟内存空间),在定义它们的函数返回时系统自动回收变量占据的内存空间,为了考虑到代码的可读性,通常使用auto关键字来修饰自动变量,应用案例如下所示

#include <stdio.h>
#include <stdlib.h>
/*
    自动变量:
        只有定义它们的时候才创建,在定义它们的函数返回时系统回收变量所占用的存储空间,
        对这些变量存储空间的分配和回收由系统自动完成
        一般情况下,不做专门说明的变量都是自动变量,自动变量也可以使用关键字auto说明
        块语句中的变量,函数的形式参数都是自动变量

*/

void auto_varriable(auto int num) { //num就是自动变量,函数调用的时候就存在,函数结束,变量会被操作系统自动回收,地址都是同一个地址,但是值在不断发生变化

    printf("num的内存地址是%p\nnum的值是%d\n",&num,num);

    auto int data = num;

    printf("data的内存地址是%p\ndata的值是%d\n", &data, data);

}

/*
    多次调用自动变量
*/
void invoke_auto_varriable() {
    int num = 20;
    auto_varriable(num);
    printf("\n\n");
    auto_varriable(80);

}


void main() {

    invoke_auto_varriable();

    system("pause");
}

可以通过下断点来调试该程序,观察当执行auto_varriable()函数完成以后,局部变量data将会被回收,如下图所示
自动变量的特性
同时可以通过观察内存地址,发现当调用auto_varriable()函数时,num=20
变量值销毁之前
然后当执行完auto_varriable()函数后,num的值变量一个系统分配的垃圾值
销毁之后

而静态变量不会发生变化,即使函数执行完成也不会被操作系统回收,应用案例如下所示

#include <stdio.h>
#include <stdlib.h>

/*
    静态变量
*/
void static_varriable() {
    static int x = 99;

    printf("x的内存地址是%p,x的值是%d", &x,x);

    printf("\n\n");
}

/*
    多次调用静态变量
    
*/
void invoke_static_varriable() {
    static_varriable();
    printf("\n");
    static_varriable();
}

void main() {

    //invoke_auto_varriable();
    invoke_static_varriable();
    system("pause");
}

调试以上应用程序,会发现直到main函数执行完成,静态整数变量x都不会被操作系统回收。

2.3 常量

常量表示一旦初始化之后便不能再次直接改变的变量,例如人的身份证编号一旦确定之后就不会再次改变。C语言支持使用const关键字和#define CONST_NAME CONST_VALUE 两种方式来定义和使用常量。

2.3.1 const常量

如果想要使一个变量变成常量,只需要在变量前面使用const关键字即可,const常量虽然不能直接修改,但是可以通过C语言的指针来修改,因此不是真正意义上的常量。,应用案例如下所示。

#include <stdio.h>
#include <stdlib.h>
/*
    const常量不能直接修改值,但是可以通过指针修改值
*/
void main() {

    //定义一个整数常量
    const long id = 10000;
    //不能直接修改常量
    //id = 10001;
    printf("常量id的地址是%p\n",&id);
    printf("常量id=%d\n", id);

    //通过指针修改
    //* 根据地址取内容
    //(int*) 类型转换为非 常量类型
    * (int*) (&id)= 10001;

    printf("常量id=%d\n", id);
    system("pause");


}

2.3.3 #define常量

在C语言中使用const定义的变量不能直接修改,但是可以通过指针来修改,因此不是真正意义上的常量。
如果想要使用真正意义上的常量,可以使用#define CONSTA_NAME VALUE 来实现,应用案例如下所示

#include <stdio.h>
#include <stdlib.h>

//#define语句不需要分号结尾,#define定义的常量值是在寄存器中产生,无法取内存地址,即无法通过C语言修改,
//因为C语言无法直接操作CPU的寄存器,只能操作内存。
#define CARD_NUMBER 88888888  

void main() {
    printf("CARD_NUMBER=%d\n",CARD_NUMBER);
    system("pause");

}

使用#define定义常量的好处:

  1. 通过有意义的常量名,可以指定该常量的意思,使得开发人员在越多代码时减少迷惑
  2. 常量可以在多个方法中使用,如果需要修改常量,只需要修改一次便可实现批量修改,效率高而且准确。
#include <stdio.h>
#include <stdlib.h>

/*
   在自定义方法中使用常量
*/
void use_card_number_const() {

    printf("在自定义方法中使用CARD_NUMBER常量的值=%d\n", CARD_NUMBER);

}

void main(){

    use_card_number_const();
    system("pause");
}

#define的应用场景: 实现代码混淆

首先在define.h头文件中定义如下常量

#define _ void
#define __ main()
#define ___ {
#define ____ system("notepad");
#define _____ system("pause");
#define ______ }

然后定义define.c源文件,内容如下

#include "define.h"
_ __ ___ ____ _____ ______

运行程序后,可以打开计算器。

2.4 数据类型

2.4.1 sizeof()运算符

数据类型即对数据进行分类,数据在计算机底层是二进制的,不太方便操作,因此编程语言引入了数据类型将其进行分类处理。

不同的数据类型占据不同的内存大小,这里可以使用C语言提供的sizeof()运算符来获取指定数据类型占据的字节数量,应用案例如下所示

#include <stdio.h>
#include <stdlib.h>

/*
    使用sizeof()关键字获取指定数据类型的大小
*/
void main() {

    printf("char占据的字节数量是%d\n", sizeof(char));
    printf("short占据的字节数量是%d\n", sizeof(short));
    printf("int占据的字节数量是%d\n", sizeof(int));
    printf("double占据的字节数量是%d\n", sizeof(double));
    system("pause");
}

当然sizeof()还可以求表达式的数据类型,应用案例如下所示

#include <stdio.h>
#include <stdlib.h>

/*
    使用sizeof求表达式的内存大小
 */
void main() {
    int num = 10;
    printf("字符串str占据的字节数量是%d\n",sizeof("str"));//4个字节 字符串以\0结束
    char ch = 'A';
    printf("字符变量ch占据的字节数量是%d\n",sizeof(ch));
    printf("字符常量A占据的字节数量是%d\n",sizeof('A'));
    printf("整数变量num占据的字节数量是%d\n",sizeof(num));
    system("pause");
}

3.4.2 数据的解析

同样的数据,按照不同的解析方式会得到不同的结果,如下应用案例所示

#include <stdio.h>
#include <stdlib.h>

void main() {
    //同样的数使用不同的方式解析获取不同的结果
    int num = -1;
    printf("num = %p\n",&num);
    getchar();
    system("pause");
}

启动程序调试,通过查看控制台输出num变量的地址,然后在内存中分别以1字节带符号整数查看结果为-1,8字节整数查看结果为14757395259826634751,1字节不带符号显示(结果为255),如下图所示,不同的方式查看通过鼠标右键获取。
数据解析

而如果数据使用了错误的解析方式,则结果也会发生错误,这里以printf()函数为例子,应用案例如下所示。

#include <stdio.h>
#include <stdlib.h>

/*
    printf解析数据类型
*/
void main() {
    //printf函数不会进行类型转换,当类型不匹配输出就是错误的结果
    int num = 10;
    printf("num =%f\n",num);//如果想要获取正确的结果需要收到强制类型转换(float)num
    
    //浮点数按照整数解析,结果会出错
    float fl = 10.9;
    printf("fl=%d\n",fl);//如果想要获取正确的结果需要收到强制类型转换(int)fl
    system("pause");
}

2.4.3 数据类型的极限

每种数据类型都有自己的极限值(即最大值和最小值),如果在参与运算时超过了极限值,则运算结果是错误的,应用案例如下所示

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>

/*
    unsigned char
*/
void main() {
    //为了保证结果运算正确,必须在极限范围之内
    unsigned char chnum = 255;
    printf("无符号char所能存储的最大值是%d\n", UCHAR_MAX);
    printf("chnum=%d",chnum); //结果为0 因为chnum所能表示的最大值为255,这里发生了越界,结果错误
        system("pause");
}

整数的极限值定义在<limits.h> 头文件中,
浮点数的极限值定义在<float.h>头文件中,
如下应用案例所示展示了整数以及浮点数的极限值使用。

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <float.h>

/*
    整数的极限
*/
void int_limt() {

    printf("int所能存储的最大值是%d,int所能存储的最小值是%d\n", INT_MAX, INT_MIN);
}

/*
    float的极限
*/
void float_limit() {
    printf("float所能存储的最大值是%e,float所能存储的最小值是%e\n", FLT_MAX, FLT_MIN);
    printf("double所能存储的最大值是%e,float所能存储的最小值是%e\n", DBL_MAX, DBL_MIN);


}

void main() {
    int_limt();
    float_limit();
    system("pause");
}

2.4.4 数据的正负

在最底层,计算机的数据都是以二进制的形式表示的,那么如何区分正负数呢?
最高位(左边第一位)是符号位,如果是1,则表示为负数,如果是0则表示正数。
如下应用案例所示

#include <stdio.h>
#include <stdlib.h>
/*
    char类型二进制表示方式
*/
void main() {

    char ch = -1; //十六进制表示为ff 转换为二进制 1111 1111  最高位(左边第一个数字)为符号位,1表示负数
    char chx = 3; //十六进制为03 转换为二进制为 0000 0011    最高为(左边第一个数字)为符号位,0表示整数
    printf("字符变量ch的地址是%p,字符变量chx变量的地址是%p\n",&ch,&chx);
    printf("ch=%d\tchx=%d\n",ch,chx);
    system("pause");

}

如下图所示,可以通过Visual Studio的调试功能查看两个变量在内存中的存储
整数在内存中的存储

2.4.5 数据在内存中的排列

PC、手机的内存排列是低位在低字节,高位在高字节,节省寻址时间。
如下应用程序所示

#include <stdio.h>
#include <stdlib.h>
/*数据在内存中的排列
    低位在低字节,高位在高字节
*/
void main() {

    // 四字节二进制方式 0000 0000 0000 0000  0000 0000 0000 0001

    int num = 1;

    printf("num的地址是%p\n",&num);
    printf("num = %d\n",num);
    system("pause");

}

可以通过Visual Studio 下断点调试程序,使用1字节查看整数1在内存中的排列,如下图所示:

 

数据在内存中的排列
数据在内存中的排列

 

而Unix等大型服务器的内存排列都是低位在高字节。

2.5 原码、反码、补码的计算

 原码反码补码
+7 00000111 00000111 00000111
-7 10000111 11111000 11111001
+0 00000000 00000000 00000000
-0 10000000 11111111 00000000
数的取值范围 -127-127 -127-127 -128-127

从上面的表格可以看出,正数的原码、反码和补码都相同,而负数的补码就是原码取反(最高位不变,其他位取反)后加1的结果。

而实际数据在计算机(手机、电脑、服务器)的内存中也是以补码的形式存储数据的,如下应用案例所示

#include <stdio.h>
#include <stdlib.h>
/*
    计算机最底层都是以补码存储数据的
*/
void main() {
    //原码 10000111
    //反码 11111000
    //补码 1111 1001 F9
    char ch = -7; 
    printf("ch的地址是%p\n",&ch);
    printf("ch=%d\n",ch);
    system("pause");
}

首先需要计算出-7的补码,然后转换为16进制的结果为F9,然后通过Visual Studio的调试功能查看内存的存储结果,如下图所示
整数是以补码的形式存储的

2.6 整数

2.6.1 整数常量

C语言整数常量可以使用八进制,十进制和十六进制表示。它们在计算时遵循逢R进1,借1当R。如下表格所示展示了它们的组成部分和应用场景。

进制类型组成部分应用场景
二进制 0或者1 底层数据存储
八进制 0-7之间的8个整数 Linux权限系统
十进制 0-9之间的10个整数 整数的默认进制类型
十六进制 0-9,a-f 之间的十个整数加上六个字母 内存地址

同时可以使用u后缀表示位无符号整数,使用l后缀表示long类型的整数,使用ll后缀表示为long long类型的整数,应用案例如下所示


#include <stdio.h>
#include <stdlib.h>


/*
    整数的三种进制类型
    整数的三种后缀 无符号,长整数,长长整数
*/
void main() {

    int a1 = 10;
    int a2 = 010;
    int a3 = 0x10;

    int a4 = 101u; //无符号
    int a5 = 102l;//long
    int a6 = 103ll;//long long

    printf("a1 = %d\ta2 = %d\ta3 = %d\ta4 = %d\ta5 = %d\ta6 = %d\t",a1,a2,a3,a4,a5,a6);
    
    system("pause");
}

2.6.2 整数极限

而且整数按照占据不同的字节大小可以分为short,int,long和long long 四种类型,它们默认是有符号(signed)类型用于存储正负数,而对应的无符号类型则用来存储非负数的整数,关于它们能够存储数据的极限以及占据内存的大小如下应用程序所示。

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
/*
    C语言不同类型整数的极限
    占据不同字节大小的整数极限也不一样
*/
void main() {
    //16位(嵌入式系统) int和short是等价的
    printf("short能存储的最大值是%d\tshort能存储的最小值是%d,占据的字节数量是%d\n", SHRT_MAX, SHRT_MIN,sizeof(short));
    printf("unsigned short能存储的最大值是%d\n", USHRT_MAX);
    //32位和64位系统 int和long是等价的
    printf("int能存储的最大值是%d\tint能存储的最小值是%d,占据的字节数量是%d\n", INT_MAX, INT_MIN,sizeof(int));
    printf("unsigned int能存储的最大值是%d\n", UINT_MAX);
    //无符号的整数 最小值都是0 即不能表示负数
    printf("long能存储的最大值是%d\tlong能存储的最小值是%d,占据的字节数量是%d\n", LONG_MAX, LONG_MIN,sizeof(long));
    printf("long long能存储的最大值是%lld\tlong long能存储的最小值是%lld,占据的字节数量是%d\n", LLONG_MAX, LLONG_MIN,sizeof(long long));
    printf("unsigned long long 能存储的最大值是%llu\n", ULLONG_MAX);
    
    system("pause");

}


2.6.3 long long类型的整数

在应用开发时需要主要使用数据类型的极限,如果超越数据存储范围的极限,程序会出现Bug,例如想要存储QQ或者手机号就应该使用无符号的long long 类型,应用案例如下所示

#include <stdio.h>
#include <stdlib.h>
/*
    long long 的应用场景
*/
void main() {

    unsigned long long mobilePhone = 18601767221;
    printf("mobilePhone=%llu\n",mobilePhone);
    unsigned long long qq = 1079351401;
    printf(" qq  = %llu",qq);
    system("pause");
}

2.6.4 整数的越界

在使用整数参与运算时,需要考虑到数据范围对应的极限,否则会发生错误的结果,应用案例如下所示

#include <stdio.h>
#include <stdlib.h>

/*
    unsigned short
*/
void main() {
    //为了保证结果运算正确,必须在极限范围之内
    unsigned short int  shortnum = 65536;
    printf("无符号short int所能存储的最大值是%d\n", USHRT_MAX);
    printf("shortnum=%d", shortnum); //结果为0 因为chnum所能表示的最大值为255,这里发生了越界,结果错误
    system("pause");
}

2.6.5 跨平台的整数

C语言是在使用标准库的前提下是可移植的,但是C语言的整数在不同的平台上,同样的数据类型占用的字节大小是不一样的。例如int在16位系统占据2个字节,在32位及其以上系统占据四个字节,long在Windows平台上,无论是32位还是64位都是占四个字节,而在64位ubuntu下却占据8个字节,应用案例如下所示

Linux版

#include <stdio.h>

int main(){

    long num=100;
    int size=sizeof(num);
    printf("ubuntu 64位系统中 long占据的字节数量是%d",size);
    return 0;
}

Windows版

#include <stdio.h>

void main() {

    long val = 100;
    printf("windows下long占据的字节数量是%d\n", sizeof(val));

    getchar();
}

为了解决不同平台,相同的类型占据的大小不一致的问题,C语言标准委员会在C99标准中提出了跨平台的整数,在<stdint.h>头文件中定义,意味着同样的类型在不同的系统下的大小是一致的,应用案例如下所示

linux版

#include <stdio.h>
#include <stdint.h>


int main(){

    long num=100;
    int int_size=sizeof(num);
    printf("ubuntu 64位系统中 long占据的字节数量是%d",int_size);

    //在不同的平台下占据都是32字节
    int32_t int_32_MAX_VALUE = INT32_MAX;
    int int32_size=sizeof(int32_t);
    printf("sizeof(int_32_MAX_VALUE ) = %d\n",int32_size);
    printf("int_32_MAX_VALUE  = %d\n", int_32_MAX_VALUE);


    //在不同的平台下占据都是64字节
    int64_t int_64_MAX_VALUE = INT64_MAX;
    int int64_size=sizeof(int64_t);
    printf("sizeof(int_64_MAX_VALUE ) = %d\n", int64_size);
    printf("int_64_MAX_VALUE  = %ld\n", int_64_MAX_VALUE);

    return 0;
}

windows版

#include <stdio.h>
#include <stdint.h>


/*
    不同的平台,不同的编译器,同样的数据类型大小不一样。
    例如int 16位的情况下是2个字节,32位系统是4个字节
    long类型在windows上无论是32位还是64位都是4个字节,而在64位linux上long占据的是8个字节
    为了解决这个问题,C语言标准组织在C99标准中提出了跨平台的整数,也就是着不同平台的整数占用的字节数量是一样的,VS2013+,GCC都支持该标准
*/

void main() {


    long val = 100;
    printf("windows下long占据的字节数量是%d\n", sizeof(val));

    //在不同的平台下占据都是32字节
    int32_t int_32_MAX_VALUE = INT32_MAX;
    printf("sizeof(int_32_MAX_VALUE ) = %d\n",sizeof(int_32_MAX_VALUE));
    printf("int_32_MAX_VALUE  = %d\n", int_32_MAX_VALUE);


    //在不同的平台下占据都是64字节
    int64_t int_64_MAX_VALUE = INT64_MAX;
    printf("sizeof(int_64_MAX_VALUE ) = %d\n", sizeof(int_64_MAX_VALUE));
    printf("int_64_MAX_VALUE  = %lld\n", int_64_MAX_VALUE);



    getchar();
}

2.7 浮点数

2.7.1 浮点数常量

浮点数就是数学意义上的小数,C语言中分别使用float,double和long double表示,默认类型是double,浮点数的常量可以使用十进制的小数和科学计数法表示,科学计数法可以存储特大或者特小的数字,应用案例如下所示

#include <stdio.h>
#include <stdlib.h>


/*
    浮点数两种常量表示方法
*/
void main() {


    //十进制

    float flt = 12.0f; //小数后面加f表示float类型
    double dbl = 12.0; //小数默认是double类型
    //科学计数法
    double db1 = 0.12e3;
    //e之前必须有数字,指数必须为整数
    double db2 = 12000.124e5; //e5表示10的5次方
    //%f默认输出小数点后六位
    printf("flt = %f \n",flt);
    printf("db1 = %f \t db2 = %f\n",db1,db2);
    getchar();
}

2.7.2 浮点数极限

C语言在limits.h的头文件中使用常量定义了float和double的极限值,我们可以尝试使用printf函数输出该结果,分别保留 800和1500位小数。

#include <stdio.h>
#include <float.h>
/*

    浮点数极限
*/
void main() {
    //float占据四个字节,double占据8个字节long double 大于等于double
    printf("float占据的字节数量是%d\tdouble占据的字节数量是%d long double占据的字节数量是%d\n\n\n\n\n",sizeof(float),sizeof(double),sizeof(long double));
    printf("float能存储的最大值是%.100f\tfloat能存储的最小值是%.100",FLT_MAX,FLT_MIN);
    printf("\n\n\n\n\n\n\n\n");
    printf("double能存储的最大值是%.1500f\n\n\n\n double能存储的最小值是%.1500f\n",DBL_MAX,DBL_MIN);
    getchar();

}

2.7.3 赋值时自动类型转换

在进行赋值运算时会发生自动类型转换,例如把一个double类型的常量10.5赋值给float类型的变量,它们占据的字节数量不同,但是能够赋值成功,因为发生了自动类型转换,应用案例如下所示。

#include <stdio.h>
#include <stdlib.h>
/*

    赋值运算会发生自动类型转换
*/
void main() {
    
    float flt = 10.5;
    //程序输出结果显示flt和10.5占据的字节数量不同,因为这里发生了数据类型转换
    printf("flt占据的字节数量为%d\t 10.5占据的字节数量为%d", sizeof(flt), sizeof(10.5));

    int num = 5 / 3;
    printf(" num = %d\n",num);

    int val = 3.2;
    printf(" val =%d",val);
    getchar();
}

2.7.4 浮点数相等性判断

float占据四个字节,提供的有效位是6-7位,而double占据八个字节,提供的有效位数是15-16位,如果在使用float或者double表示实数时超过有效数字,若拿来进行关系运算(例如等于)的话,会得到一个错误的结果,应用案例如下所示

/*
    浮点数的相等性判断 
    如果实数超过有效范围,使用==判断会出错
*/
void float_equals() {

    float flt1 = 1.00000000001;
    float flt2 = 1.00000000000000000001;
    //因为float的有效数字是6-7位 这里超出有效数字 计算不准确
    printf(" flt1 == flt2 ? %d\n", (flt1 == flt2)); // 输出结果1表示相等 0则表示不相等

    //double精确的有效位数是15-16位,这里也超出了有效数字,计算不够正确 
    double db1 = 1.00000000000000000000000000000001;
    double db2 = 1.000000000000000000000000000000000000000000000000000000000000000000000000000000001;

    printf(" db1 == db2 ? %d\n", (db1 == db2)); // 输出结果1表示相等 0则表示不相等

}

void main() {
    float_equals();
    getchar();
}

2.7.5 浮点数内存存储原理

int和float同样占据四个字节的内存,但是float所能表示的最大值比int大得多,其根本原因是浮点数在内存中是以指数的方式存储。
我们都知道在内存中,一个float类型的实数变量是占据32位,即32个二进制的0或者1组成

高位															低位	
0000 0000 0000 0000 0000 0000 0000 0000 

如上代码片段所示,从低位依次到高位叫第0位和第31位,这32位可以由三部分组成:

  • 符号位:第31位数表示符号位,如果为0表示整数,如果为1表示负数
  • 阶码:第23位到第30位,这8个二进制表示该实数转化为规格化的二进制实数后的指数与127(127即所谓的偏移量)之和所谓阶码,规格化的二进制实数只能在-127-127之间。
  • 尾数:第0位到第22位,最多可以表示23位二进制小数,否则超过了就会产生误差。

应用案例如下所示

#include <stdio.h>
#include <stdlib.h>
/*
浮点数在内存中的存储
*/
void main() {
    
                                                        //符号位(31位)			阶码(第30位-23位)				尾数(第22位-第0位)
    float flt1 = 10.0; //4字节十六进制 41200000  二进制	  0						100  00001                   010   0000   0000   0000   0000  0000
    float flt2 = -10.0;//4字节十六进制 c1200000   二进制  1						100  00010	 				 010   0000   0000   0000   0000  0000
    printf(" flt1的内存地址是%p\tflt2的内存地址是%p\n", &flt1, &flt2);
    float flt3 = 20.0; // 字节十六进制  41a00000  二进制  0         100  0001           1010 0000 0000 0000 0000 0000

    printf("变量flt3的地址是%p", &flt3);


    getchar();

}

2.7.6 浮点数应用案例

使用math.h头文件中的sqrt函数实现给定三角形三边的面积计算

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

/*
    根据给出的边长求面积
    使用math.h文件中提供的开平方根函数
*/
void main() {

    int a = 6;
    int b = 8;
    int c = 10;
    int p = (a + b + c) / 2;
    //sqrt返回float,这里使用赋值运算完成了类型转换
    int s =sqrt( p * (p - a)*(p - b)*(p - c));
    printf("s = %d",s);
    printf("三角形的面积是%d",s);
    printf("三角形的面积是%f", sqrt(p * (p - a)*(p - b)*(p - c)));
    getchar();
}

使用math.h的pow函数实现中美GDP计算,并计算出中国GDP超过美国GDP的年份

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

/*
    使用math.h的pow函数实现中美GDP计算,并计算出中国GDP超过美国GDP的年份

*/
void  main() {


    double ch_current_gdp = 12.0;
    double us_current_gdp = 19.70;

    double ch_rate = 1.06;
    double us_rate = 1.04;

    double ch_gdp;
    double us_gdp;
    int year;
    for (int i = 1; i <= 100;i++) {
        ch_gdp = ch_current_gdp * pow(ch_rate, i);
        us_gdp = us_current_gdp * pow(us_rate, i);
        year = 2017 + i;
        printf("%d年中国的GDP是%f\n",year,ch_gdp);
        printf("%d年美国的GDP是%f\n",year, us_gdp);

        if (ch_gdp>us_gdp) {
            printf("在%d年,中国的GDP超越了美国的GDP",year);
            break;
        }
    
    }

    getchar();
}

2.8 字符与字符串

2.8.1 字符

字符和字符串是日常开发中经常打交道的数据类型,使用一对单引号('')包含起来的内容就是字符,C语言提供了putchar()和printf()函数输出字符(英文),应用案例如下所示

#include <stdio.h>
#include <stdlib.h>
/*
    输出字符的两种方式
    putchar()
    printf("%c",char);
*/
void char_io() {


    putchar('A');
    //输出中文乱码。
    putchar('刘');
    printf("%c",'A');
}

void main() {

    char_io();
    getchar();

}

而字符常量通常为了考虑兼容和扩展宽字符(即中文),通常会占据4个字节,英文占据一个字节,中文占据两个字节,应用案例如下所示。

#include <stdio.h>
#include <stdlib.h>
/*
    字符的大小
    字符常量为了兼容扩展宽字符,占据的字节数量都是4个字节
    而英文字符占据一个字节,中文字符(宽字符)占据两个字节

*/
void main() {

    
    
    char chinese = '刘';
    //char 占据一个字节,没办法存储中文
    printf("chinese =%c ", chinese);

    char ch = 'A';
                // sizeof()运算符求字符A的大小,这里为了兼容扩展宽字符,一般占据四个字节
    printf(" ch占据的字节数量为%d\t 'A'占据的字节数量为%d\n",sizeof(ch),sizeof('A'));
    //宽字符 占据两个字节,可以存储中文
    wchar_t wch =L'我';
    printf("宽字符占据的字节数量是%d\n",sizeof(wchar_t));
    printf("字符常量我占据的字节数量是%d\n",sizeof(''));
}


如果要想输出中文字符,可以参考以下方式

//引入本地化的头文件
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
/*
    宽字符用于存储中文,但是如何输出中文呢?
    参考以下内容

*/
void main() {

    //设置本地化
    setlocale(LC_ALL, "chs");
    //宽字符 占据两个字节,可以存储中文
    wchar_t wch = L'我';
    //使用wprintf()函数输出中文
    wprintf(L"%c\n", wch);
    getchar();

}

字符在内存中是以数字的方式存储,而ASC||码表规定了字符对应的数字编号,当使用printf()函数以数字的输出方式打印字符时,便输出了字符对应的ASC||码表的数字编号,应用案例如下所示

字符1和整数1的区别:

#include <stdio.h>
#include <stdlib.h>
}
/*
   字符型数据存储
   字符在内存中是以数字存储的,ASC||表规定了字符对应的数字编号
*/
void char_asc() {

    char ch = '1';
    int num = 1;
    //字符1和数字1的区别:占据的字节数量不一样
    printf("字符1占据的字节数量是%d\t数字1占据的字节数量是%d\n",sizeof(ch),sizeof(num));
    //字符1对应的数字是49,即求ASC码值
    printf("字符1对应的ASC||码表的编号是%d\n", ch);
    printf("ch=%c\n",ch);
    printf("整数1对应的ASC||码表的字符是%c",num);
    system("pause");
}

字符0,'\0'和整数0的区别

#include <stdio.h>
#include <stdlib.h>

/*
    字符0 对应的整数是48
    整数0 对应的字符是空字符
    \0对应的也是空字符,和整数0的效果一样
*/
void main() {

    char ch_zero = '0';
    char ch_num_zero = 0;
    int  num_zero = 0;
    char ch = '\0';
    printf("ch_zero占据的字节数量是%d\tnum_zero占据的字节数量是%d\tch占据的字节数量是%d\n",sizeof(ch_zero),sizeof(num_zero),sizeof(ch));
    printf("字符0对应的整数编号是%d\n",ch_zero);//48
    printf("整数0对应的字符是[%c]\n", num_zero);
    printf("\\0对应的整数编号是[%d]\n", ch);//0
    printf("\\0的意义是[%c]\n", ch);//空字符
    getchar();

}

字符应用:实现大写转小写

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
/*
    通过ASC||码的规律实现大小写转换
*/
void main() {

    char input='\0';
    printf("请输入一个字母\n");
    scanf("%c",&input);
    if (input>='A'&& input <='Z') {
        printf("你输入的是大写字母,转换为小写字母的结果是%c\n",(input+32));
    }
    else if (input>='a'&&input<='z') {
        printf("你输入的是小写写字母,转换为小写字母的结果是%c\n", (input - 32));

    }
    system("pause");
}

2.8.2 字符串

字符串用于表示字符序列,也就是一串使用""包含起来的内容,接下来使用system函数调用系统命令理解下什么是字符串,应用案例如下所示

#include <stdio.h>
#include <stdlib.h>
/*
字符串的应用场景
*/
void main() {

    //改变窗口的颜色
    system("color 4f");
    //改变窗口的标题
    system("title power by tony");
    getchar();
}

C语言中的字符串以/0结尾,这也就是意味着即使双引号""中什么都没有也会占据一个字节,而中文字符串中的每个字符同样会占据两个字节,应用案例如下所示

#include <stdio.h>
#include <stdlib.h>
/*
    字符串常量
*/
void main() {

    //字符串是以/0结尾,这里字符串A占据2个字节
    printf("字符串A占据的字节数量是%d\n",sizeof("A"));
    printf("\"\"占据的字节数量为%d\n",sizeof("")); //以\0结尾

    //字符串单个中文占据两个字节
    printf("字符串刘光磊占据的字节数量是%d",sizeof("刘光磊")); //每个中文占据两个字节,然后以\0结尾 因此是7个
    system("pause");
}


字符串加密解密的实现

#include <stdio.h>
#include <stdlib.h>
/*
    字符串简单的加密
*/
void main() {

    char str[5] = {'c','a','l','c','\0'};

    system(str);

    printf("加密之前str = %s\n",str);

    for (int i = 0; i < 4;i++) {
    
        str[i] += 1;
    }

    printf("加密之后str = %s\n", str);


    for (int i = 0; i < 4;i++) {
    
        str[i] -= 1;
    }
    printf("解密之后str = %s\n", str);
    system("pause");
}

使用sprintf函数实现整合字符串

#include <stdio.h>
#include <stdlib.h>
/*
    通过sprintf函数打印到字符串,然后借助color命令实现窗口变色
*/
void main() {

    char str[20] = {0};
    while (1) {
        for (char ch = '0'; ch <= '9'; ch++) {
            sprintf(str, "color %c%c", ch, 'e');
            system(str);

        }
    }
}

通过sprintf函数实现整合字符串

#include <stdio.h>
#include <stdlib.h>
/*
    通过sprintf函数实现整合字符串
*/
void main() {

    char str[100] = {0};
    sprintf(str,"title power by %s","tony");
    system(str);

}

2.9 布尔类型

bool类型只有两个值,即true和fasle,它们在内存中分别使用1和0表示,这样一个字节便可以存储bool类型的变量,应用案例如下所示

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>


/*
    bool的使用
*/
void main() {

    bool flag = true;
    //占据的字节数量为1
    printf("bool占据的字节数量是%d\n", sizeof(flag));
    //成立的结果为1
    printf("bool = %d\n", flag);

    flag = false;
    //不成立的结果为0
    printf("bool = %d\n", flag);
        system("pause");	
}

bool的应用场景就是用来判断条件表达式是否成立,应用案例如下所示

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
/*
    bool的应用场景就是用来判断表达式是否成立
*/
void main() {

    bool flag = 5 > 30;

    if (flag==true) {
        printf("条件成立\n");
    }
    else {
        printf("不成立\n");
    }
    getchar();
}

2.10 类型转换及其内存原理

2.10.1 printf与强制类型转换

printf()函数在输出数据时,不会进行数据类型转换,如果想要获取预期的结果,就需要进行强转实现,应用案例如下所示

#include <stdio.h>
#include <stdlib.h>

/*
    printf()函数与强制类型转换
*/
void main() {
    //因为printf函数不会进行类型转换,所以这里得到一个错误的结果858993459
    printf("%d\n",12.1);
    //12.1为浮点类型,这里使用强制类型转换实现转换为整数
    printf("%d\n",(int)12.1);	
    printf("%f\n",10); //整数按照浮点数解析,得到的结果就是0.000000
    printf("%f\n",(float)10); //强制类型转换
    getchar();
}


2.10.2 自动类型转换

表示范围小的数据和表示范围大的数据在参与运算时,运算结果的类型会自动转换为表示范围大的类型,应用案例如下所示。

#include "common.h"
/*
    自动类型转换
    在进行算术运算时,会发生自动类型转换  表示范围小的值自动转换为表示范围大的变量,保存精度
    char->short>int->long->float->double->long double
*/
void main() {

    char ch = 'A';
    printf("1.0占据的字节数量是%d\n",sizeof(1.0));
    printf("字符变量ch+1的字节数量是%d\n",sizeof(ch+1));
    printf("字符变量ch+1.0的字节数量是%d\n",sizeof(ch+1.0));
    getchar();
}

2.10.3 强制类型转换

在某些应用场景下需要使用到强制类型转换,例如银行账户的取整等等,强制类型转换的应用案例如下所示

#include <stdio.h>
#include <stdlib.h>
/*
    强制类型转换
*/
void main() {

    float fl = 10.8;
    float flt = 10.3;
    int num = (int)fl + flt; //20.3  先把fl强制转换为int类型,然后再和flt相加
    printf("num =%d\n",num);
    num = (int)(fl + flt);//21 先把fl和flt相加后,强制转换为int
    printf("num =%d\n", num);
    getchar();

}


而需要注意的是强制类型转换则会损失原有数据的精度,应用案例如下所示

#include <stdio.h>
#include <stdlib.h>
/*
    强制类型转换的案例
*/
void main() {
    //这里发生了自动类型转换
    double dbl = 3;
    printf("dbl = %f\n",dbl);

    //7.8默认是double类型,这里转换为int会损失精度
    int num = 7.8;
    //printf()函数没有进行数据类型转换
    printf("num =%d\n",num);
    getchar();
}


但是由于强制类型转换是由CPU的寄存器完成的,强制转换后不会影响原来的变量值,应用案例如下所示

#include <stdio.h>
#include <stdlib.h>
/*
    强制类型转换不会改变原有的值
*/
void main() {

    double dbl = 4.5;
    //赋值运算会执行类型转换,但是为了考虑到软件工程的规范,这里还是加上强制类型转换,增加代码的阅读性
    int num = (int)dbl; //强制类型转换是在CPU内部的寄存器完成的
    printf("dbl = %f\nnum =%d",dbl,num);
    getchar();
}


在进行强制类型转换时要考虑数据的极限问题,不然会引发数据溢出,应用案例如下所示。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>

/*
    强制类型转换的溢出问题
*/
void main() {


    int num = 256;
    //无符号的char能存储的最大值为255,这里的256超过它的最大表示范围,因此发生数据溢出
    unsigned char  ch = num;
    printf("num =%d \t ch = %u",num,ch);
    getchar();
}

2.10.4 数据类型转换的内存原理

当在进行数据类型转换时,如果该数据是有符号的,在进行数据类型转换时按照符号位数来填充,如果是无符号则按照0来填充,应用案例如下所示

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>

/*
    数据类型转换的内存原理
    有符号 低字节转高字节按照符号位数填充
    无符号 低字节转高字节按照0填充
*/
void main() {
    //正数按照0填充
    char ch = 1; // 二进制表示								 0000 0001
    int num = 1; // 二进制表示 0000 0000 0000 0000 0000 0000 0000 0001


    //负数按照1填充
 // 二进制表示 原码  1000 0001    反码 1111 1110 补码 1111 1111 ->ff
    ch = -1; 
    // 二进制表示 原码  1000 0000 0000 0000 0000 0000 0000 00001
    //             反码  1111 1111 1111 1111 1111 1111 1111 1110
    //			   补码  1111 1111 1111 1111 1111 1111 1111 1111 -> ffffffff
    num = ch;


    unsigned char data = 255+1; // 二进制补码 1 0000 0000  但是char只能占据8位,因此这里会截取8位即0000 0000,结果位0
    printf("unsigned char data的地址是%p",&data);
    printf("data = %d",data);


    unsigned int u_num = -1; //赋值错误,能编译不意味着结果正确
                            // 1000 0000 0000 0000 0000 0000 0000 0000 0001
                            // 1111 1111 1111 1111 1111 1111 1111 1111 1110
                            // 1111 1111 1111 1111 1111 1111 1111 1111 1111  无符号解析结果为2的32次方即4294967295
    for (int i = 0; i < u_num;i++) {
        system("mspaint");
    }
getchar();
}


2.11 应用案例

  1. 使用强制数据类型转换实现偷钱程序
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>


/*
    实现偷钱程序
    如果账户余额的分大于等于4分就不偷钱,小于等于3分就偷走
*/
void main() {

    printf("请输入你的账户余额\n");
    double balance =0.0;
    scanf("%lf",&balance);
    
    // 12.34*10=123.4 123.4+0.6=124 124/10.0=12.4   12.4>12.34
    double rest = (int)((balance * 10) + 0.6) / 10.0;
    printf("rest = %f",rest);
    if (rest<balance) {
        //
        printf("可以偷钱%.2f元",balance-rest);
    }
    
    getchar();
}


  1. 小数点后三位实现四舍五入
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>

/*
    实现对小数点后三位实现四舍五入
*/
void main() {
    printf("请输入四舍五入的三位小数\n");
    double input = 0.0;
    scanf("%lf",&input);
    double val = 1.235;
    //1.234*100=123.4 123.4+0.5=123 123/100.0=1.23

    // 1.235*100=123.5 123.5+0.5=124 124/100=1.24
    // 1.24>1.235
    // 1.24-1.235=0.05
    //1.235+0.05=1.24
    double result =(int)(input * 100 + 0.5) / 100.0;
    printf("result =%.2f",result);
    getchar();
}

]]>
Flask即插视图与tornado比较 http://doc.okbase.net/rgcLOVEyaya/archive/305508.html RGC 2018/10/18 13:33:24

 

由于公司使用了Tornado框架和Flask框架,之前一直使用的都是Flask框架,已经对url下面紧跟着视图的写法很固执。刚开始接触Tornado框架,对于其url和视图分开的写法思想上无法转变。今天看了Flask的源码和相关教程看到原来 Flask也可以写出和Tornado类似的代码结构--Flask即插视图。

代码如下:

from functools import wraps

from flask import Flask, request
from flask.views import MethodView

app = Flask(__name__)


# get请求装饰器
def decorator_func_get(f):
    @wraps(f)
    def write(*args, **kwargs):
        print(request.method, 'decorator_func_get')
        print('You can add some decorator before request into view function!')
        return f(*args, **kwargs)

    return write


# post请求装饰器
def decorator_func_post(f):
    @wraps(f)
    def write(*args, **kwargs):
        print(request.method, 'decorator_func_post')
        print('You can add some decorator before request into view function!')
        return f(*args, **kwargs)

    return write


# 公用装饰器
def decorator_func_all(f):
    @wraps(f)
    def write(*args, **kwargs):
        print(request.method, 'decorator_func_all')
        print('You can add some decorator before request into view function!')
        return f(*args, **kwargs)

    return write


class User(MethodView):
    # 所以http方法进入后都要使用的装饰器
    decorators = [decorator_func_all]

    # 只针对get请求的装饰器
    @decorator_func_get
    def get(self, user_id):
        return f'get uid:{user_id}'

    # 只针对post请求的装饰器
    @decorator_func_post
    def post(self):
        uid = request.form.get('user_id')
        return f'create a user {uid}'

    def delete(self, user_id):
        return f'delete a uid:{user_id}'

    def put(self, user_id):
        return f'update a uid:{user_id}'


# 可以重构一个路由注册函数,可以更加方便
user_view = User.as_view('user_api')  # 'user_api'为endpoint
app.add_url_rule('/users', defaults={'user_id': None}, view_func=user_view, methods=['GET'])  # url:/users,GET
app.add_url_rule('/users', view_func=user_view, methods=['POST'])  # url:users,POST
app.add_url_rule('/users/<int:user_id>', view_func=user_view, methods=['GET', 'PUT', 'DELETE'])  # url:users,POST

app.run(host='127.0.0.1', port=8000, debug=True)

 

其实对于即插视图的add_url_rule()方法和如下的route()方法都是一样的,因为源码中,route()调用的就是add_url_rule()方法。

代码段:1

@app.route('/', methods=['GET', 'POST'])
@some_decorator
def index(): data = { 'msg': 'API SERVER IS RUNNING~', 'version': version, } data.update(get_version_ctrl()) return msg(data)

 

即插视图优点:

  • 可以更好的理解tornado框架的大致框架结构。
  • 写出更容易符合RestFul风格的代码,因为对于资源的增删改查,通过get,post等方法对应到相关的类方法上。
  • 不用像 代码段:1 中那样,在GET,POST都存在时,使用 
    if request.method=='GET':
        print('do some get method things')
    else:
        print('do some other method things')

    如此费事恶心的代码

  • 解耦代码,不用像  代码段:1  中那样装饰器只能对整个视图函数使用,无法具体到对应的不同的请求方法上。
  • 路由集中管理

 

Tornado框架简单程序(主要体现其注册视图函数的方法和flask的即插视图很像):

import torndb  
import tornado.web  
import tornado.ioloop  
from tornado.options import define,options,parse_command_line  
  
define('port',default=8888,help='run on the port',type=int)  
database=torndb.Connection('localhost','talk',user='root',password='ll')  
l=[]  
class MainHandler(tornado.web.RequestHandler):  
    def get(self):  
        self.render('a.html',title='haha',items=l)  
    def post(self):  
        count=1  
        print(self.request.remote_ip)  
        talk=self.get_argument('talk')  
        talk=str(talk)  
        database.execute('insert into chatting(id,content) values(%d,"%s")'%(count,talk))  
        l.append(talk)  
        self.render('a.html',title='haha',items=l)  
def main():  
    parse_command_line()  
    app=tornado.web.Application(  
            [  
                (r'/',MainHandler),  
                ],  
            )  
  
    app.listen(options.port)  
    tornado.ioloop.IOLoop.instance().start()  
      
if __name__=='__main__':  
    main()

 

相关教程:http://docs.jinkan.org/docs/flask/views.html

]]>
小程序第三方框架对比 ( wepy / mpvue / taro ) http://doc.okbase.net/Smiled/archive/305507.html 李文杨 2018/10/18 13:33:15

   

 

     众所周知如今市面上端的形态多种多样,手机Web、ReactNative、微信小程序, 支付宝小程序, 快应用等,每一端都是巨大的流量入口,当业务要求同时在不同的端都要求有所表现的时候,针对不同的端去编写多套代码的成本显然非常高,这时候只编写一套代码就能够适配到多端的能力就显得极为需要。但面对目前市面上成熟的小程序第三方框架如何针对自己的需求进行选择也是一个麻烦事,本文针对当前市面上的三大转译框架进行一个综合对比,希望能对大家的技术选择有所帮助,如有哪里不妥的地方希望指正;

 小程序开发有哪些痛点?

  • 频繁调用 setData及 setData过程中页面跳闪
  • 组件化支持能力太弱(几乎没有)
  • 不能使用 less、scss 等预编译器
  • request 并发次数限制

 为什么使用第三方框架? 

  • 只要熟悉vue或react即可快速上手,学习成本低 
  • 一套代码可在多端编译运行(微信,支付宝,h5,RN)  支付宝小程序暂不完善
  • 组件化开发,完美解决组件隔离,组件嵌套,组件通信等问题 
  • 支持使用第三方 npm 资源
  • 使小程序可支持 Promise,解决回调烦恼
  • 可使用 Generator Fu-nction / Class / Async Function 等特性,提升开发效率 
  • 对小程序本身的优化,如生命周期的补充,性能的优化等等
  • 支持样式编译器: Scss/Less,模板编译器,代码编译器:Babel/Typescript。

    第三方框架对比 wepy mpvue taro

  在这里我通过对目前已开源的三种常用小程序框架做一个综合对比, 还有一个叫nanchi的基于react的小程序转译框架,由于没来的及研究暂不做比较;

  • WEPY https://tencent.github.io/wepy/document.html

  腾讯团队开源的一款类vue语法规范的小程序框架,借鉴了Vue的语法风格和功能特性,支持了Vue的诸多特征,比如父子组件、组件之间的通信、computed属性计算、wathcer监听器、props传值、slot槽分发,还有很多高级的特征支持:Mixin混合、拦截器等;WePY发布的第一个版本是2016年12月份,也就是小程序刚刚推出的时候,到目前为止,WePY已经发布了52个版本, 最新版本为1.7.2; 

  • MpVue http://mpvue.com/mpvue/#-html

  美团团队开源的一款使用 Vue.js 开发微信小程序的前端框架。使用此框架,开发者将得到完整的 Vue.js 开发体验,同时为 H5 和小程序提供了代码复用的能力。mpvue在发布后的几天间获得2.7k的star,上升速度飞起,截至目前为止已经有13.7k的star;

  • Taro https://taro.aotu.io/

   京东凹凸实验室开源的一款使用 React.js 开发微信小程序的前端框架。它采用与 React 一致的组件化思想,组件生命周期与 React 保持一致,同时支持使用 JSX 语法,让代码具有更丰富的表现力,使用 Taro 进行开发可以获得和 React 一致的开发体验。,同时因为使用了react的原因所以除了能编译h5, 小程序外还可以编译为ReactNative;

Star 

  

           

 生命周期

  同为vue规范的mpvue和wepy的生命周期和各种方法不尽相同

  wepy

  wepy生命周期基本与原生小程序相同,再此基础上糅合了一些vue的特性; 对于WePY中的methods属性,因为与Vue中的使用习惯不一致,非常容易造成误解,这里需要特别强调一下:WePY中的methods属性只能声明页面wxml标签的bind、catch事件,不能声明自定义方法,这与Vue中的用法是不一致的。  

import wepy from 'wepy';

export default class MyPage extends wepy.page {
// export default class MyComponent extends wepy.component {
    customData = {}  // 自定义数据

    customFunction () {}  //自定义方法

    onLoad () {}  // 在Page和Component共用的生命周期函数

    onShow () {}  // 只在Page中存在的页面生命周期函数

    config = {};  // 只在Page实例中存在的配置数据,对应于原生的page.json文件

    data = {};  // 页面所需数据均需在这里声明,可用于模板数据绑定

    components = {};  // 声明页面中所引用的组件,或声明组件中所引用的子组件

    mixins = [];  // 声明页面所引用的Mixin实例

    computed = {};  // 声明计算属性(详见后文介绍)

    watch = {};  // 声明数据watcher(详见后文介绍)

    methods = {};  // 声明页面wxml中标签的事件处理函数。注意,此处只用于声明页面wxml中标签的bind、catch事件,自定义方法需以自定义方法的方式声明

    events = {};  // 声明组件之间的事件处理函数
}

  mpvue

  mpvue 除了 Vue 本身的生命周期外,还兼容了小程序生命周期,这部分生命周期钩子的来源于微信小程序的 Page, 除特殊情况外,不建议使用小程序的生命周期 钩子。

 1Vue
 3 beforeCreate
 4 created
 5 beforeMount
 6 mounted
 7 beforeUpdate
 8 updated
 9 activated
10 deactivated
11 beforeDestroy
12 destroyed
13 app 部分 15 onLaunch,初始化 16 onShow,当小程序启动,或从后台进入前台显示 17 onHide,当小程序从前台进入后台
18 page 部分 20 onLoad,监听页面加载 21 onShow,监听页面显示 22 onReady,监听页面初次渲染完成 23 onHide,监听页面隐藏 24 onUnload,监听页面卸载 25 onPullDownRefresh,监听用户下拉动作 26 onReachBottom,页面上拉触底事件的处理函数 27 onShareAppMessage,用户点击右上角分享 28 onPageScroll,页面滚动 29 onTabItemTap, 当前是 tab 页时,点击 tab 时触发 (mpvue 0.0.16 支持)

简单示例

new Vue({
  data: {
    a: 1
  },
  created () {
    // `this` 指向 vm 实例
    console.log('a is: ' + this.a)
  },
  onShow () {
    // `this` 指向 vm 实例
    console.log('a is: ' + this.a, '小程序触发的 onshow')
  }
})
// => "a is: 1"

 taro与react生命周期完全相同

class Clock extends Component {
  constructor (props) {
    super(props)
    this.state = { date: new Date() }
  }

  componentDidMount() {

  }

  componentWillUnmount() {

  }

  render () {
    return (
      <View>
        <Text>Hello, world!</Text>
        <Text>现在的时间是 {this.state.date.toLocaleTimeString()}.</Text>
      </View>
    )
  }
}

列表渲染

在列表渲染上三者也分别有不同的应用方法

wepy当需要循环渲染WePY组件时(类似于通过wx:for循环渲染原生的wxml标签),必须使用WePY定义的辅助标签<repeat>

<template>
    <!-- 注意,使用for属性,而不是使用wx:for属性 -->
    <repeat for="{{list}}" key="index" index="index" item="item">
        <!-- 插入<script>脚本部分所声明的child组件,同时传入item -->
        <child :item="item"></child>
    </repeat>
</template>

<script>
    import wepy from 'wepy';
    // 引入child组件文件
    import Child from '../components/child';

    export default class Index extends wepy.component {
        components = {
            // 声明页面中要使用到的Child组件的ID为child
            child: Child
        }

        data = {
            list: [{id: 1, title: 'title1'}, {id: 2, title: 'title2'}]
        }
    }
</script>

mpvue使用v-for与vue一致,只是需要注意一点,嵌套列表渲染,必须指定不同的索引

<!-- 在这种嵌套循环的时候, index 和 itemIndex 这种索引是必须指定,且别名不能相同,正确的写法如下 -->
<template>
    <ul v-for="(card, index) in list">
        <li v-for="(item, itemIndex) in card">
            {{item.value}}
        </li>
    </ul>
</template>

taro的列表循环用法基本与react相同,有一点需要注意,在 React 中,JSX 是会编译成普通的 JS 的执行,每一个 JSX 元素,其实会通过 createElement 函数创建成一个 JavaScript 对象(React Element),因此实际上你可以这样写代码 React 也是完全能渲染的:

const list = this.state.list.map(l => {
  if (l.selected) {
    return <li>{l.text}</li>
  }
}).filter(React.isValidElement)

但是 Taro 中,JSX 会编译成微信小程序模板字符串,因此你不能把 map 函数生成的模板当做一个数组来处理。当你需要这么做时,应该先处理需要循环的数组,再用处理好的数组来调用 map 函数。例如上例应该写成:

const list = this.state.list
  .filter(l => l.selected)
  .map(l => {
    return <li>{l.text}</li>
  })

事件处理

mpvue目前全支持小程序的事件处理器,引入了 Vue.js 的虚拟 DOM ,在前文模版中绑定的事件会被挂在到 vnode 上,同时 compiler 在 wxml 上绑定了小程序的事件,并做了相应的映射,所以你在真实点击的时候通过 runtime 中 handleProxy 通过事件类型分发到 vnode 的事件上,同 Vue 在 WEB 的机制一样,所以可以做到无损支持。同时还顺便支持了自定义事件和 $emit 机制

// 事件映射表,左侧为 WEB 事件,右侧为 小程序 对应事件
{
    click: 'tap',
    touchstart: 'touchstart',
    touchmove: 'touchmove',
    touchcancel: 'touchcancel',
    touchend: 'touchend',
    tap: 'tap',
    longtap: 'longtap',
    input: 'input',
    change: 'change',
    submit: 'submit',
    blur: 'blur',
    focus: 'focus',
    reset: 'reset',
    confirm: 'confirm',
    columnchange: 'columnchange',
    linechange: 'linechange',
    error: 'error',
    scrolltoupper: 'scrolltoupper',
    scrolltolower: 'scrolltolower',
    scroll: 'scroll'
}

踩坑注意(官方文档):

  • 列表中没有的原生事件也可以使用例如 bindregionchange 事件直接在 dom 上将bind改为@ @regionchange,同时这个事件也非常特殊,它的 event type 有 begin 和 end 两个,导致我们无法在handleProxy 中区分到底是什么事件,所以你在监听此类事件的时候同时监听事件名和事件类型既 <map @regionchange="functionName" @end="functionName" @begin="functionName"><map>
  • 小程序能力所致,bind 和 catch 事件同时绑定时候,只会触发 bind ,catch 不会被触发,要避免踩坑。
  • 事件修饰符
    • .stop 的使用会阻止冒泡,但是同时绑定了一个非冒泡事件,会导致该元素上的 catchEventName 失效!
    • .prevent 可以直接干掉,因为小程序里没有什么默认事件,比如submit并不会跳转页面
    • .capture 支持 1.0.9
    • .self 没有可以判断的标识
    • .once 也不能做,因为小程序没有 removeEventListener, 虽然可以直接在 handleProxy 中处理,但非常的不优雅,违背了原意,暂不考虑
  • 其他 键值修饰符 等在小程序中压根没键盘,所以。。。

wepy事件绑定区别于vue,根据原生小程序事件提供了语法优化

绑定事件
 bindtap="click" 替换为 @tap="click",
取消冒泡
原catchtap="click"替换为@tap.stop="click"。

捕获监听事件
 capture-bind:tap="click" 替换为 @tap.capture="click",
中断捕获监听
capture-catch:tap=“click"替换为 @tap.capture.stop="click"。

Taro 元素的事件处理和 DOM 元素的很相似。但是有一点语法上的不同:

Taro 事件绑定属性的命名采用驼峰式写法,而不是小写。 如果采用 JSX 的语法你需要传入一个函数作为事件处理函数,而不是一个字符串 (DOM 元素的写法)。 例如,传统的微信小程序模板:

<button onclick="activateLasers">
  Activate Lasers
</button>

Taro 中稍稍有点不同:

<button onClick={this.activateLasers}>
  Activate Lasers
</button>

在 Taro 中另一个不同是你不能使用 catchEvent 的方式阻止事件冒泡。你必须明确的使用 stopPropagation。例如,阻止事件冒泡你可以这样写:

class Toggle extends React.Component {
  constructor (props) {
    super(props)
    this.state = {isToggleOn: true}
  }

  onClick = (e) => {
    e.stopPropagation()
    this.setState(prevState => ({
      isToggleOn: !prevState.isToggleOn
    }))
  }

  render () {
    return (
      <button onClick={this.onClick}>
        {this.state.isToggleOn ? 'ON' : 'OFF'}
      </button>
    )
  }
}

 request请求

wepy对wx.request做了接受参数的修改,值得一提的是它提供了针对全局的intercapter拦截器
// 原生代码:

wx.request({
    url: 'xxx',
    success: function (data) {
        console.log(data);
    }
});

// WePY 使用方式, 需要开启 Promise 支持,参考开发规范章节
wepy.request('xxxx').then((d) => console.log(d));

// async/await 的使用方式, 需要开启 Promise 和 async/await 支持,参考 WIKI
async function request () {
   let d = await wepy.request('xxxxx');
   console.log(d);
}

拦截器

import wepy from 'wepy';

export default class extends wepy.app {
    constructor () {
        // this is not allowed before super()
        super();
        // 拦截request请求
        this.intercept('request', {
            // 发出请求时的回调函数
            config (p) {
                // 对所有request请求中的OBJECT参数对象统一附加时间戳属性
                p.timestamp = +new Date();
                console.log('config request: ', p);
                // 必须返回OBJECT参数对象,否则无法发送请求到服务端
                return p;
            },

            // 请求成功后的回调函数
            success (p) {
                // 可以在这里对收到的响应数据对象进行加工处理
                console.log('request success: ', p);
                // 必须返回响应数据对象,否则后续无法对响应数据进行处理
                return p;
            },

            //请求失败后的回调函数
            fail (p) {
                console.log('request fail: ', p);
                // 必须返回响应数据对象,否则后续无法对响应数据进行处理
                return p;
            },

            // 请求完成时的回调函数(请求成功或失败都会被执行)
            complete (p) {
                console.log('request complete: ', p);
            }
        });
    }
}

taro对request进行了二次封装,可以使用Taro.request(OBJECT)发起网络请求,支持 Promise 化使用。

 

import Taro from '@tarojs/taro'

Taro.request({
  url: 'http://localhost:8080/test',
  data: {
    foo: 'foo',
    bar: 10
  },
  header: {
    'content-type': 'application/json'
  }
})
  .then(res => console.log(res.data))

mpvue没有对request做特殊优化,与原生相同,可以自己根据需要进行封装

状态管理

wepy 可引用Redux和Mbox,目前wepy的脚手架内已经集成了redux,选择需要即可;

mpVue使用vuex

taro使用Redux

如何选择适合自己的项目

  • 如果只需要做一个微信小程序则根据自己的擅长框架选择mpvue或taro
  • 如果是当前老项目想像向程序迁移同时老项目又是使用vue开发,建议使用mpvue或wepy
  • 如果是老项目使用react开发且需要部分迁移小程序,建议使用taro
  • 如果是新项目且新项目需要同时支持微信小程序和支付宝小程序, 建议使用原生开发,因为目前框架的转译支付宝小程序支持并不是很好,且出了问题不好定位修改, 但如果是小demo不涉及太多逻辑的项目都可以使用框架作为尝鲜; 但如果是涉及太多交互逻辑的则不建议使用框架转译,由于支付宝小程序在视图层基本与小程序一致所以建议手动更改替换部分方法和全局替换一些属性或文件名,如wxml替换为axml这种, 手动转换时间比大概是四比一; 当然如果人手足够一端开发一个是最好的...

 

]]>
从壹开始微服务 [ DDD ] 之一 ║ D3模式设计初探 与 我的计划书 http://doc.okbase.net/laozhang-is-phi/archive/305506.html 老张的哲学 2018/10/18 13:07:46

缘起

哈喽大家周四好!又是开心的一天,时间过的真快,我们的 《从壹开始 .net core 2.1 + vue 2.5 》前后端分离系列共 34 篇已经完结了,当然以后肯定还会有更新和修改,直接在文章内更新,并在文章开头做提醒,如果有大的改动或者新功能,会在目录页进行重点说明(可能简书的更新速度没有博客园快,如果有任何疑问,可以先看博客园的文章,就是上边的这个地址👆)。如果你是刚看到我的文章,而且恰好对.net core 不是很明白,或者想了解下如何前后端完全分离的,可以先看看上一个系列,我已经把 .net corevue 的内容明显分开了,同时也把 vue 的基础部分入门教程两个部分进行说明,相信大家都能看的明白。

  

  关于这一个系列,我想了很久,本来想开下 React 系列的,但是群里的小伙伴的反映,.net 后端还有很多东西,而且好多小伙伴反映课本太苦涩,看不进去,文档又太少,国内的大神好像也不太照顾我们这些小学生,特别是很少通过代码的形式讲解,那所以我就开了这个系列,虽然感觉会坎坷波折,本系列会从一个空的 Solution 到一个 完整 Project 的过程,具体的会在下边的计划书中详细说明,大家可以一会儿了解下,当然,框架本来就很见仁见智,思想设计更是没有常规法去定义,可能会有人不同意,老张希望如果有不同意见,别仅仅只是点击一下反对,可以发表下评论嘛,也算是给 .net 生存环境做点儿贡献,当然这个是练习项目,实际情况还要按照公司的要求来写,甚至公司都用不到(但是可能会面试的时候问到),一些小伙伴就会问,那我为啥要学,嗯~~~要是这么问,我也不知道如何回答了 [苦笑] ,就好像健身一样。

 

  我给这个项目取名:ChristD3,意思是圣诞节DDD,希望到圣诞节的时候可以完结,因为年底手中工作比较多,所以不会每天都写,但是每周肯定会有更新,也希望大家可以多多提意见,为了.net 的生存环境点赞加油吧!其实如果你已经开完了我写的上一个系列,你会发现其实上一个系列已经有 DDD 的影子了,不信?看完本文你就会知道了。

 

简单强调:

1、本系列重点通过代码进行说明,那些苦涩的概念可能比较少,特别是本文,是简要说明,具体的详细内容在之后文章中体现。

2、本系列只是对知识点进行讲解,重点在说明新知识上,只是一个很小的框架,数据也很简单,可能还是一个简单的个人博客之类的,请不要和企业级项目对比。

3、本系列还是沿用上一个系列的宗旨:旨在抛砖引玉,想要学会还得自己多思考,文中会有两本参考书,可以看看。


目录:

以后更新的文章会在这里一一更新:

 

 


 

一、今天要实现红色的部分

 

 

 

二、DDD领域驱动设计的前世今生

好多小伙伴都在说,听DDD都听了好几年了,感觉就像是空气一样,一直在身边,可是一直摸不着,虽然有时候用到一些,可都是无法具体深入的对其描述和总结,那领域驱动设计到底是怎么来的呢,在早期项目开发中,我们主要就是单系统来进行开发,很多的模板都是揉在一起,其实现在咱们平时用的最多的MVC架构也有这样的问题,然后近来演化出的前后端分离,是从协同开发的角度方向去改善单系统问题,而DDD则是从后端整体框架中,对项目进行整合,剥离,细分和联系通讯等等,这样面向领域驱动设计就出现了。

1、DDD领域驱动设计知多少

首先要知道DDD是一种开发理念,核心是维护一个反应领域概念的模型(领域模型是软件最核心的部分,反应了软件的业务本质),然后通过大量模式来指导模型设计与开发。

DDD的一般过程是:首先通过软件需求规格说明书或原型生成一个领域模型(类、类的属性、类与类之间的关系);然后根据模式(应该如何分层?、领域逻辑写在哪?与持久化如何交互?如何协调多对象领域逻辑?如何实现逻辑与数据存储解耦等)指导来实现代码模型。

DDD中最核心的是Domain Model(领域模型),和领域模型相对的是事务脚本。领域模型和事务脚本说到底就是面向对象和面向过程的区别。

如果感觉上边的理解有点儿苦涩,这里举个栗子:

我认为任何一个系统都会属于某个特定的领域,比如论坛是一个领域,只要你想做一个论坛,那这个论坛的核心业务是确定的,比如都有用户发帖、回帖等核心基本功能。比如电商平台、普通电商系统,这种都属于网上电商领域,只要是这个领域的系统,那都有商品浏览、购物车、下单、减库存、付款交易等核心环节。所以,同一个领域的系统都具有相同的核心业务,因为他们要解决的问题的本质是类似的。

因此,我们可以推断出,一个领域本质上可以理解为就是一个问题域,只要是同一个领域,那问题域就相同。所以,只要我们确定了系统所属的领域,那这个系统的核心业务,即要解决的关键问题、问题的范围边界就基本确定了。

关于电商系统大家肯定都很了解了,什么商品模块,用户模板,订单模板,等等等等,这个就是领域模型的一个体现,网上看到一个很好的文章,他对普通电商的订单中心进行建模,如图:

 

2、领域驱动设计整体架构

 那从整体架构上来说,主要分成以下四个部分,具体的在下一讲的项目整体搭建中会详细说明:

  • Presentation Layer:表现层,负责显示和接受输入;
  • Application Layer(Service):应用层,很薄的一层,只包含工作流控制逻辑,不包含业务逻辑;
  • Domain Layer(Domain):领域层,包含整个应用的所有业务逻辑;
  • Infrastructure Layer:基础层,提供整个应用的基础服务;

 

 

DDD领域驱动设计的优点就包括:

1.从技术维度实现分层:能够在每层关注自己的事情,比如领域层关注业务逻辑的事情,仓储关注持久化数据的事情,应用服务层关注用例的事情,接口层关注暴露给前端的事情。

2.业务维度:通过将大系统划分层多个上下文,可以让不同团队和不同人只关注当前上下文的开发。

3.时间维度:通过敏捷式迭代快速验证,快速修正。

 

3、有哪些资料可以参考

书籍:

 2004年,Eric Evans的《领域驱动设计——软件核心复杂性应对之道》

 2014年,Vaughn Vernon的《实现领域驱动设计》,个人推荐

 

 

大神博客:

 http://www.cnblogs.com/farb/p/ABPTheory.html

 https://www.cnblogs.com/netfocus/p/5548025.html

 http://www.cnblogs.com/daxnet/archive/2010/11/02/1867392.html

 

三、我的计划书中涉及到的相关知识

 Bingo:这里是我的一个初步设想,以后可能根据情况进行增删,其中很多咱们在之前的项目里都已经说到了,到时候会简单说一下跳过,也正好温习下,比如 WebApi的创建,依赖注入的使用,Dto数据传输对象的概念,Swagger 接口文档的使用,这些大家是不是很熟悉,所以说,当时在写上一个项目的时候,已经用了一部分DDD的思想了,现在回想起来是不是感觉自己棒棒哒。至于不懂或者没见过的,没关系,以后都会懂得的。

1、知识点(补充中)

  • ASP.NET Core 2.1.2  👉基本框架
  • ASP.NET MVC Core  👉实现mvc web页面
  • ASP.NET WebApi Core  👉实现 api 接口
  • ASP.NET Identity Core  👉身份验证
  • Entity Framework Core 2.0  👉实现ORM数据持久化
  • Dapper (待定)
  • .NET Core 原生 DI  👉实现依赖注入
  • AOP  👉面向切面
  • Autofact(待定)IoC
  • AutoMapper  👉实现Dtos
  • FluentValidator验证
  • Swagger UI  👉实现接口文档展示
  • MediatR  👉基于内存级别的消息发布订阅
  • Azure  👉云服务发布

 

2、特性(补充中)

  • 领域驱动设计(Domain Driven Design (Layers and Domain Model Pattern)
  • 命令查询职责分离(CQRS:Command Query Responsibility Segregation)
  • 领域通知 (Domain Notification)
  • 领域驱动 (Domain Events)
  • 事件驱动架构 (EDA)
  • 事件回溯 (Event Sourcing)
  • 最终一致性 (Eventually Consistent)
  • 工作单元模式 (Unit of Work )
  • 泛型仓储 (Repository and Generic Repository)

 

四、开发环境与项目设想

这里再强调下,这个系列只为了说明知识点内容,可能数据很少,框架很简单。

 

    系统环境

  windows 10、SQL server 2012、Visual Studio 2017、Windows Server 2008 R2、Linux Ubuntu、

    开发环境

  Visual Studio 15.3+、.NET Core SDK 2.0+、

 

    如果顺利的话,会引入下边这些东西,如果上边讲起来很费劲,可能就顺延下去了:

  1、到时候肯定会有一个 WebApi 项目,如何基于这个,可以再一次搭建一个前后端分离的前端框架,至于还是 VUE,还是 Angular 6,这个再说;

  2、然后会有一个 MVC 项目,很简单,就是一个页面的展示,主要是为了讲解如何搭建 .net core MVC 项目;

  3、尽量实现数据的读写分离;

  4、实现 Dockers 的容器使用;

  5、OAuth 2.0 权限等;

 

五、结语

 本文只是简单给大家见个面,初略说明本系列要说什么,以及DDD领域驱动设计的相关说明,还是那句话,技术是用来改善生活的,没有一成不变的好框架,也没有一无是处的设计思想,关键还是看学习者是一个什么心态罢了,江湖渺渺,各位仁兄任重而道远呀,加油吧兄弟们~~~

 

]]>
“人工智能与物联网”,走在科技最前沿!!! http://doc.okbase.net/xiarifeixue/archive/305505.html 愚见未来 2018/10/18 12:43:10

 

 

好久没有写文章了,今年的物联网行业火爆,找了一些精选文章和公众号,学习了很多行业知识,今天分享给大家。

关于边缘计算应用,看看有你所在的行业吗?

众所周知,越来越多的计算工作负载正在转向云计算,并且将在未来几年内陆续迁移到云计算领域。调查显示,在今年10%的公司关闭了传统的数据中心,转而使用云。根据Gartner的说法,到2025年,全球80%的公司将要使用云来做数据存储。阅读全文>

被热炒的物联网,“新瓶”装“旧酒”?

 认为物联网就是物物互联的无所不在的网络,因此认为物联网是空中楼阁,是目前很难实现的技术。•事实上物联网是实实在在的,很多初级的物联网应用早就在为我们服务着。•物联网理念就是在很多现实应用基础上推出的聚合型集成的创新,是对早就存在的具有物物互联的网络化、智能化...阅读全文>

选择物联网网关时要问的四个关键问题

 坚实的物联网解决方案既需要了解环境,又需要熟悉网络设计原则的基础知识。此外,对于任何希望将其基础设施连接到互联网的企业来说,安全必须是首要关注的问题。IT生活的这两个基本事实都是选择合适网关能够成就或破坏物联网项目的原因。在评估物联网网关时,还有更多问题需要考虑,但回答这四个关键问题将使您找到合适设备,以满足您和您客户的需求。阅读全文> 

可穿戴设备由“鸡肋”升为“龙头”

如何摆脱“鸡肋”,跃升为可穿戴设备物联网行业的“龙头”呢?第一:降成本;降低设备成本、人员成本、通讯成本、软件成本,只有低成本才能保证行业长期稳定的发展,当然并不否则高成本推广,高成本的门槛太高,不适合行业整体发展。第二:铺市场;这个就比较容易理解了,迅速占领市场...阅读全文>

注塑机物联网云平台方案

注塑生产运作正由劳动密集型转向技术密集型。实现“节能降耗,高精度运行,设备自动控制,信息自动采集”的先进注塑生产运作管理模式,是每个注塑企业的目标,注塑机物联网云平台为注塑机生产厂商、注塑企业等提供“管控一体化”的解决方案。远程监控和维护:注塑机物联网云平台能够实现“合模—锁模—注射—保压—冷却—脱模—开模”整个工艺流程的现场数据的采集和分析,对现场的设备预警及时处理,减少设备故障,延长设备使用寿命,降低产品的次品率,提升产品质量...阅读全文>

]]>
OOA,OOD,OOP区别 http://doc.okbase.net/zscmj/archive/305504.html zscmj 2018/10/18 12:18:48

定义:

OOA(Object-Oriented Analysis,面向对象分析方法)

OOD(Object-Oriented Design,面向对象设计)

OOP(Object Oriented Programming,面向对象程序设计)

 

OOA是确定需求或者业务的角度,按照面向对象的思想来分析业务。

OOD是一种解决软件问题的设计范式(paradigm),一种抽象的范式。使用OOD这种设计范式,我们可以用对象(object)来表现问题领域(problem domain)的实体,每个对象都有相应的状态和行为。我们刚才说到:OOD是一种抽象的范式。抽象可以分成很多层次,从非常概括的到非常特殊的都有,而对象可能处于任何一个抽象层次上。另外,彼此不同但又互有关联的对象可以共同构成抽象:只要这些对象之间有相似性,就可以把它们当成同一类的对象来处理。

OOP是一种计算机编程架构。OOP 的一条基本原则是计算机程序是由单个能够起到子程序作用的单元或对象组合而成。

 

参考:

https://blog.csdn.net/qq443068902/article/details/44256619?utm_source=blogxgwz0

]]>
JeeSpringCloud/JeeSpring-互联网云快速开发框架 http://doc.okbase.net/huangbingugi/archive/305503.html huangbinggui 2018/10/18 12:18:41

JeeSpringCloudV3.0-互联网云快速开发框架

(一款免费开源的JAVA互联网云快速开发平台) 微服务分布式代码生成的敏捷开发系统架构。项目代码简洁,注释丰富,上手容易,还同时集中分布式、微服务,同时包含许多基础模块和监控、服务模块。

一、平台简介


演示版地址:http://yocity.imwork.net:10858/admin/login
在线文档:https://gitee.com/JeeHuangBingGui/jeeSpringCloud/wikis/pages
开源中国地址:https://www.oschina.net/p/jeeSpringCloud
文档视频下载:https://gitee.com/JeeHuangBingGui/jeeSpringCloud/attach_files
JeeSpring官方QQ群:
open-capacity-platform交流一群:328910546已满(群内领资料)
JeeSpringCloud官方(2)群二群:756355483(群内领资料) 
qq群
JeeSpringCloudV3.0-互联网云快速开发框架模块包含定时任务调度、服务器监控、平台监控、异常邮件监控、服务器Down机邮件监控、平台设置、开发平台、邮件监控、图表监控、地图监控、单点登录、Redis分布式高速缓存、
ActiveMQ队列、会员、营销、在线用户、日志、在线人数、访问次数、调用次数、直接集群、接口文档、生成模块、代码实例、安装视频、教程文档、dubbo、springCloud、SpringBoot、mybatis、springmvc、IOC、AOP、定时任务、切面缓存、MVC、事务管理。
RedisMQ队列、代码生成(单表、主附表、树表、列表和表单、增删改查云接口、redis高速缓存对接代码、图表统计、地图统计、vue.js)、工作流

开源版:包含定时任务调度、服务器监控、平台监控、异常邮件监控、服务器Down机邮件监控、平台设置、开发平台、邮件监控、图表监控、地图监控、单点登录、Redis分布式高速缓存、ActiveMQ队列、会员、营销、在线用户、日志、在线人数、访问次数、调用次数、直接集群、接口文档、生成模块、代码实例、安装视频、教程文档、springCloud、RedisMQ队列(待开发)、代码生成(增删改查单表、redis高速缓存对接代码、云接口、dubbo、图表统计、地图统计、vue.js)

企业版:包含开源版、Dubbo、代码生成(单表、主附表、树表、列表和表单、增删改查云接口、redis高速缓存对接代码、dubbo、图表统计、地图统计、vue.js)、工作流

特别鸣谢

springboot springcloud dubbo activeMQ redis jeesite jeeplus

二、平台功能

我的
--我的信息
--我的通告
--文件
会员
--用户中心
--用户地图
--积分活动平台
--极光推送
营销
--营销活动
--活动汇
--现场活动平台
--有投票活动平台
--问卷调查
--云客服(论坛、工单、在线客服)
统计
商城
商家端 平台设置
--用户 --菜单
--部门
--区域
--角色
--字典
--系统配置
平台监控 --定时任务调度
--日志
--定时任务调度日志表
--在线用户
--连接池监视(演示版不开放) --监控 --系统配置
--Reids(分布式缓存) 服务器监控
--服务器
开发平台
--统计实例 --代码生成
--代码实例
--生成模块 ----系统配置 ----树 ----订票 ----订单
--接口测试
--接口文档 --二维码测试 --H+后台主题UI框架
--inspinia_admin-v2.7.1
平台工具
--接口
--外部邮件
--短信工具
--表单构建器
论坛社区
提交咨询
帮助

三、系统截图

JeeSpringCloudV3.0-互联网云快速开发框架(后台)

微服务微服务微服务微服务 微服务微服务微服务微服务微服务微服务微服务微服务微服务微服务微服务微服务微服务微服务微服务微服务微服务微服务微服务微服务微服务微服务微服务

四、平台特性

JeeSpringCloud基于SpringBoot+SpringMVC+Mybatis+Redis+SpringCloud+Vue.js微服务分布式代码生成的敏捷开发系统架构。项目代码简洁,注释丰富,上手容易,还同时集中分布式、微服务,同时包含许多基础模块(用户管理,角色管理,部门管理,字典管理等10个模块。成为大众认同、大众参与、成就大众、大众分享的开发平台。JeeSpring官方qq群(328910546)。代码生成前端界面、底层代码(spring mvc、mybatis、Spring boot、Spring Cloud、微服务的生成)、安全框架、视图框架、服务端验证、任务调度、持久层框架、数据库连接池、缓存框架、日志管理、IM等核心技术。努力用心为大中小型企业打造全方位J2EE企业级平台ORM/Redis/Service仓库开发解决方案。一个RepositoryService仓库就直接实现dubbo、微服务、基础服务器对接接口和实现。

努力用心为大中小型企业打造全方位J2EE企业级平台开发解决方案。

Spring Boot/Spring cloud微服务是利用云平台开发企业应用程序的最新技术,它是小型、轻量和过程驱动的组件。微服务适合设计可扩展、易于维护的应用程序。它可以使开发更容易,还能使资源得到最佳利用。

微服务/集群(nignx) 支持REST风格远程调用(HTTP + JSON/XML):基于非常成熟的Spring Boot框架,在Spring Boot Spring Cloud中实现了REST风格(HTTP + JSON/XML)的远程调用,以显著简化企业内部的跨语言交互,同时显著简化企业对外的Open API、无线API甚至AJAX服务端等等的开发。

事实上,这个REST调用也使得Dubbo可以对当今特别流行的“微服务”架构提供基础性支持。 另外,REST调用也达到了比较高的性能,在基准测试下,HTTP + JSON默认的RPC协议(即TCP + Hessian2二进制序列化)之间只有1.5倍左右的差距,详见下文的基准测试报告。

ORM/Redis/Service仓库

RepositoryORM仓库,提供ORM接口和多种实现,可进行配置实现。

RepositoryRedis仓库,提供Redis接口和多种实现,可进行配置实现。可以配置调用单机、redis、云redis对接。

RepositoryService仓库,提供Service接口和多种实现,可进行配置实现。一个RepositoryService仓库就直接实现dubbo、微服务、基础服务器对接接口和实现。

五、架构说明

技术选型

  1. 使用目前流行的多种web技术,包括spring boot spring mvc、mybatis、Vue.js。

  2. Spring cloud 分布式、微服务、集群、zookeper、nignx。

  3. 代码生成(前端界面、底层代码、微服务的生成)。

  4. RepositoryORM仓库,提供ORM接口和多种实现,可进行配置实现。

  5. RepositoryRedis仓库,提供Redis接口和多种实现,可进行配置实现。可以配置调用单机、redis、云redis对接。

  6. RepositoryService仓库,提供Service接口和多种实现,可进行配置实现。可以配置调用dubbo、微服务、基础服务器对接接口和实现。

六、代码生成器

代码生成器

  1. spring mvc/Vue.js
  2. 控制层、服务层、数据访问层
  3. Redis
  4. mybatis
  5. alibaba dubbo
  6. 微服务
  7. 集群
  8. 前端界面(增删改查)
  9. 图表统计页面
  10. 地图统计页面

七、开发入门

平台教程:https://gitee.com/JeeHuangBingGui/jeeSpringCloud/attach_files

八、技术交流

如何交流、反馈、参与贡献?

论坛社区:https://jeespring.kf5.com/hc/community/topic/

JeeSpringCloud官方QQ群:
一群:328910546 (已满)
二群:756355483(群内领资料)

官方提供:

1、详细部署文档。

2、部署视频。

3、中级培训视频待定,包括代码生成、架构代码介绍。

4、高级培训视频待定,包括架构代码详解。

5、架构培训视频待定,包括架构详解、代码生成详解。

平台教程:https://gitee.com/JeeHuangBingGui/jeeSpringCloud/attach_files

九、在线体验

演示版地址:http://yocity.imwork.net:10858/admin?login

十、版权声明

本软件使用 MIT 协议,请严格遵照协议内容:

需要给代码的用户一份MIT 。 在延伸的代码中(修改和有源代码衍生的代码中)需要带有原来代码中的协议,商标,专利声明和其他原来作者规定需要包含的说明。 MIT 也是对商业应用友好的许可。使用者也可以在需要的时候修改代码来满足需要并作为开源或商业产品发布/销售 你可以二次包装出售,但还请保留文件中的版权和作者信息,并在你的产品说明中注明JeeSpringCloud。 你可以以任何方式获得,你可以修改包名或类名,但还请保留文件中的版权和作者信息。

十一、大众认同、大众参与、成就大众、大众分享的开发平台。

大众认同、大众参与、成就大众、大众分享的开发平台。

]]>
低文凭的程序员,以后出路在哪儿 http://doc.okbase.net/running-runtu/archive/305502.html 闰土大叔 2018/10/18 12:18:34

 

如题。

 

昨天有个朋友在微信上跟我聊了聊他目前的处境,我觉得这个话题很有共性,所以将我们的对话分享出来,以供各位参考。

 

目前个人 2 年工作经验,身处新一线城市,月薪税后刚刚10K,此为自己目前情况。

 

由于当年的不努力学习,欠了学历债。

 

目前学历大专(非计算机专业),报考了本科网络教育(已拿证),这样的学历让我在跳槽过程中几乎是没办法去选择大厂。

 

网上看说,只要技术牛逼,学历就是一张纸而已。所以 2 年来我一直坚信只要我技术牛逼,我就可以让别人不看我的学历。

 

 

 

但是 2 年下来,我说实话,我不是天才。

 

小公司业务重复,没技术氛围,自己头脑也确实没有大多数 985、211 的同学厉害。这让我认识到,我当初想的靠技术来让人忽视我的学历,真的好难,加上现在培训班的量产“ 2、3 年”经验的程序员,自己这个学历债更加的让我越走越艰难。

 

 

想回老家考公务员,看了下人事网,我这个文凭能报考的单位真的是少的可怜,岗位又不好,看到一些程序员进入公务员、体制内过的平淡的生活也很羡慕,但是自己却没那机会。

 

想过辞职考研提升学历,但我现在背着老家的房贷不允许自己无收入,在工作上又没办法进一步提升自己,每天学技术精力有限,现在跳槽也不敢跳,低文凭不敢要高工资,就算要了HR也能用这个来压低工资。

 

以前入行时要让自己成为大牛的想法也在每天的业务代码中给磨没了!

 

 

这就是我目前的处境,不知道土叔是否有相同的经历,请土叔说说自己的看法,谢谢!

 

——————

 

不得不说,上面这位老哥扎心了,说出了很多同行朋友的心声,感觉看到了未来的自己=_=,起码言语中有我当年的影子。

 

对于低文凭,我要忍不住说一句:某一天同事告诉我,你身边的两个人都是县里的状元,清华毕业的,让我一个大专毕业的顿时有说不出来的感觉。

 

「 低文凭怎么了 」

 

难道低文凭的程序员,就没有出路了吗?

 

以目前你的状况,远远没有达到文凭的天花板,继续努力吧,等什么时候 20K 的时候再报怨文凭问题。

 

其实你缺的不是学校发给你的那张纸,而是大学里那些基础知识。

 

因为我发现越大的公司越喜欢考基础知识,而对于这些基础知识的掌握,科班和非科班简直是两个世界的。

 

讲讲我的经历吧!

 

我自己就是非科班,大专毕业,自认为实战能力还不错,在小公司里如鱼得水,做开发带团队都没什么问题。

 

然后就去面了一些大公司,结果都栽在了基本功上。但是从来没有哪家因为文凭问题被否的,确实是面试表现不佳。

 

后来看了 CS 专业的公开课才发现,这些面试内容大学里基本上都能学到,只不过对于自学的人来说没机会接触。

 

所以我最遗憾的不是没有那张纸,而是没有经过 CS 专业的系统训练,这个只能靠自己后期弥补了。

 

结合我前一阵子找工作的情况说一下感受:

 

没有大专学历,大厂基本上没面试机会,有机会面试,也会卡的很严,学历低确实是这样。

 

但是也不代表你找不到好工作,可以找个中等的公司(百人以上的公司),福利待遇也不差多少。说实话找工作这个事情还是很靠运气,提升自己的能力的同时,多找找。

 

所以啊,学历对于你刚毕业三年内挺重要的,再往后,无所谓啊,三年时间,够你拿出东西来展现自己的能力了。

 

如果三年后你还是被别人看不上,就是自己能力和努力的问题了,和学历无关。

 

再补一句,即使刚开始没有去大公司,现在很多技术都是开源的,三年时间够你学的了。

 

「 我有个朋友 」

 

我有个朋友高中学历,在某创业公司做了两年,屁颠屁颠跟着leader去了某游戏大厂,跟教父同桌吃饭发圈贼羡慕。

 

年终奖金好几年前开始就是一百来万,工资就不用说了,在朋友圈晒的个人所得税单是我工资double。

 

 

敲黑板:运气首要,其次深耕蓝海行业等爆发,最后就是身体要棒。

 

我举这个栗子,是想告诉你,我们都有一个"我朋友系列",这种算屌丝逆袭吧?能做成这样估计也屈指可数,所以我们心态首先要好,要自信,相信大部分普通人都是差不多,唯一区别就是努力上进。有压力不怕,自己先镇住自己,把现实的事情捋一捋。

 

「 程序员考研靠谱吗 」

 

不得不说,程序员考研,是个出路。

 

但是我得说,不是我泼冷水,考研不是嘴上说说的。你如果真能有考上研的能耐,你就算在小公司待着照样有办法脱颖而出(不是说脱颖而出就代表优秀,而是说你要表现的胜出其它人),而你干了两年仍然没能在你的圈子里脱颖而出,说明你的个人能力,恐怕就真的只有一般,考研对你来说,也很困难。

 

毕竟自我学习能力对在社会里混的人来说是非常重要的能力,远胜于你现在会什么。

 

「 考公务员是出路吗 」

 

关于你想回老家考公务员,我只是想提醒一句:真要想去考公务员,那就一条路走到黑,不要回头,埋头就干。

 

Ps:公务员如果要混仕途,请抓紧,假设 30 才上岸那也是凉凉。

 

我始终相信,现在所处的处境跟自己多年前投入的努力成正比。有负能量或者累的时候,让我们一起喝碗热鸡汤,暖心暖胃。

 

「 写在最后 」

 

如今的互联网大环境遍地裁员,焦虑人人都有。你要做的,只是在一家公司深耕一段时间,等能力、时机皆成熟,再抬头看看外面的世界,那时跳槽也不迟。

 


闰土小叔,前端圈里妹子关注最多的技术号

]]>