一、编程和语言

1.1 C/C++

指针数组、数组指针、函数指针的概念

什么是结构体对齐

静态数组和动态数组区别

​ 静态数组是编译时就分配好大小的数组,内存在栈区(全局数组在全局区),能分配的大小有限,受限于栈的大小。动态是程序运行后动态调用malloc或者new生成的,内存在堆区,可分配大小相较于栈区较大。

static的作用

sizeof的作用

C++

  • 重载和重写的区别
    答:重载时同一个函数的不同实现方式,通过入参的不同调用不同的函数(重载函数不能以返回值为区别)。重写是指子类覆盖父类的函数,隐藏父类实现达到多态的机制。
  • 运算符重载
  • C++中virtual关键字的作用
    virtual是实现多态的前提,对函数使用virtual将在类中产生虚函数表。对类使用virtual该类将变成虚基类。
  • 菱形继承:B和C继承A,D继承B和C)时每个对象的空间结构分布,比如问D有几份虚表,D中B和C的成员空间排布。
  • 指针和引用的区别
  • new/delete/new[]/delete []/malloc/free的区别
  • RAII/pimpl的惯用方式
  • 构造函数的调用顺序
  • 静态类静态方法
  • 智能指针的实现原理

STL

  • vecotr 和list 的区别,适用情况
  • vector的扩容机制
  • set承载结构体
  • 迭代器的使用方式,迭代器失效场景

其他

  • 程序的内存布局:(堆、栈、静态/全局/局部变量)
  • 函数的堆栈,调用函数时参数如何入栈。
  • 内存泄漏的原因及避免措施
    内存泄漏的原因是因为申请了内存没有被释放掉,写代码时应有良好的编程习惯,对申请的内存一定要即时释放
  • gdb的使用方法(断点、查看内存、执行跟踪、了解CPU主要寄存器作用)

1.2 Go

  • 协程实现原理
  • 管道实现原理

1.3 设计模式

    • OO思想、常见设计模式(单例模式、工厂设计模式、装饰者模式、Builder模式、生产者消费者模式、策略模式等)
    • Observer模式的思想,策略模式的思想,两者区别,优缺点,适用情况
    • OOP特性,通过哪些机制实现的

二、linux

2.1 系统编程

  • 多线程相关的API:创建、等待、终止以及获取线程ID等
  • 进程相关的API:创建、等待、终止以及获取进程ID等。
  • 线程同步方式:互斥体、读写锁、屏障、信号量、条件变量、自旋锁
  • 多线程和多进程的区别,为什么选用多线程或者是多进程
  • 线程和进程里面各有什么资源,都有什么用处?
    进程间都是相互独立的,产生一个新的进程,大部分的数据都被独立开来,每个进程拥有自己的堆区、栈区等数据,遵循“读时共享、写时复制”的原则。多个线程间大部分数据是共享的,如全局变量、堆等,都是共享的,线程只有自己独立的栈以及线程ID等少量资源,线程还拥有自己独立的信号屏蔽字。
  • forkwait有什么作用,fork进程时的操作。
    fork会产生一个子进程,在子进程中,fork返回0,父进程中,返回新创建的子进程id。wait是父进程用来回收子进程资源用的,避免子进程不回收处在僵尸态。fork后子进程和父进程采用“读时共享、写时复制”的方式来运行。
  • 父子进程、孤儿进程以及僵尸进程,僵尸进程如何产生和消除?
    在进程中调用fork函数会产生一个子进程,当前进程即为新进程的父进程,父进程调用fork返回子进程ID,子进程返回0。孤儿进程是指某个进程的父进程执行完退出了,但是子进程还在执行,此时子进程就是孤儿进程,会被1号进程init回收。僵尸进程是指子进程运行完毕退出了,父进程没有调用wait或者waitpid回收子进程资源,导致子进程处于僵尸态。消除僵尸进程的方法是杀掉父进程,使子进程被1号进程收养并回收掉。
  • 进程间通信:共享内存、匿名管道、有名管道、消息队列、信号量和socket。
  • linux应用层常见的锁,什么是死锁、如何避免死锁。
    互斥量、读写锁、自旋锁、信号量、屏障以及条件变量等。死锁是指进程间由于加锁不当导致程序hang住的现象,死锁只会发生在一个线程试图锁住另一个线程以相反顺序锁住的互斥量以及同一个进程对同一把锁重复加锁。
    避免死锁:锁住的区域尽量少,加完即使释放,尽量不多多个进程同时加多把锁,加锁时注意顺序。
  • 守护进程,操作以及实现原理。

2.2 网络编程

  • socket API:connect/accept/bind/listen/send/sendto/recv/recvfrom/select/gethostbyname
  • 异步IO框架:select/poll/epoll
  • 什么是阻塞模式和非阻塞模式,两种模式下的socket在connectsend以及recv等行为上的区别,如何将socket设置为非阻塞的
    阻塞模式:函数没有达到触发状态时,会一直卡在这里处于等待状态,直到又事件发生为止。
    将socket设置为非阻塞的只要使用fcntl函数给文件描述符的状态加上O_NONBLOCK标记即可。如果是文件描述符,直接在打开时也可以设置非阻塞。
  • 大端小端问题,主机是什么字节序
  • epoll水平触发与边缘触发
    边沿触发(ET模式):epoll中产生可读事件后,如果没有读完缓冲数据域,剩余的数据部分将会在下次有数据过来时才能继续读。水平触发(LT模式):epoll产生可读事件后,如果没有一次没有读完,epoll_wait()继续返回可读事件。
    正常来说,ET模式比LT模式效率更高,因为它在epoll产生的触发事件更少,更多情况下都采用“ET+非阻塞”模式。
  • libeventreactor模式。
    libevent的核心思想便是reactor,当一个socket产生可读事件时,读取数据,然后设置可写事件,再对socket写数据。
    工作流程:socket --> 添加到epoll --> 可读 --> 从epoll中摘下,读数据 --> 设置可写事件 --> 放回红黑树
  • 为什么select有文件句柄限制,pollepoll没有。
    select的文件句柄限制是写死在头文件里面的,编译时就已经固定了。poll和select工作原理类似,它的出现就是为了解决select文件句柄限制的问题。
    而epoll基于事件触发,和前面两者不同,它底层通过红黑树来存储文件描述符,插入删除的效率非常高。
  • epoll_event结构中的epoll_data_t的fd与ptr的使用场景。
    以reactor模式为例,可以把fd设置为当前的文件描述符,ptr设置为函数指针,当socket有可读或者可写事件的时候,自动调用函数指针执行,从而达到异步IO的效果。
  • select函数可以检测网络异常吗
    可以用过select设置超时时间来检测网络异常,很多时候可以通过非阻塞socket+select来对函数添加超时逻辑。
  • send/recv以及read/write返回值大于0、等于0、小于0的区别,错误码EINTR如何处理。
    大于0表示读到n字节的数据,等于0表示对端已经关闭连接,小于0表示错误,需要判断错误码。
    常见的错误码EINTR表示遇到中断了,重新读。EAGAIN也要重新读。
  • 发送数据缓冲区与接收数据缓冲区如何设计,如何编写正确的收数据代码与发数据代码。
  • shutdown与优雅关闭
    close会直接关闭两端的socket连接,并销毁内存对象。shutdown只会关掉本机的连接,对向还可以传输数据。也就是TCP的瓣关闭状态
  • 如何解决tcp粘包问题。
    添加协议,最简单的就是添加一个数据包头,然后写明该条数据包的长度n。读取时根据长度来读数据。
  • http代理、socks4代理与socks5代理如何编码实现。
  • gethostbyname阻塞与错误码获取问题
    gethostbyname默认是阻塞的
  • 如何清除无效的死链(端与端之间的线路故障)
    长连接,持续发送心跳包检测。
  • 长连接和短连接
    长连接表示连接一直处于打开状态,并且定时发送心跳包,当一定时间内心跳包如果发送失败或者没有收到回包,就认为连接发生故障。长连接的目的就是为了实时检测网络故障,避免出现连接异常断开,但是另一端并不知情还处在打开的状态。

2.3 操作系统

2.4 linux命令和shell

  • crontabiptables等命令的的使用
  • 进程、线程状态查看命令top/strace/pstack
  • 内存状态查看命令memstat/free
  • IO状态查看命令iostat/df/du
  • linux文件的权限、用户、时间(ctime/mtime/atime)和inode
  • 文件传输命令scp/rz/sz命令
  • 文件定位命令find/whereis命令
  • 硬链接和软链接,ln命令的用法,硬链接和软连接区别
  • lsof命令
  • kill用法,某个进程杀不掉的原因(进入内核态,忽略kill信号)
  • 系统管理命令(如查看内存使用、网络情况)
  • 管道的使用|
  • grep的使用
  • 文本处理命令awk/sed/grep
  • 本编辑工具vi/vim
  • 了解shell基本语法、变量操作、函数、循环/条件判断等程序结构
  • linux环境下的几种文件类型
  • linux常用端口和网络命令

三、计算机网络

3.1 基础网络知识

  • TCP/UDP报头格式,tcp和ip包头常见有哪些字段。
  • TCP/IP协议栈层次结构。
  • TCP三次握手和四次挥手,各种状态的细节点:CLOSE_WAIT、TIME_WAIT、2MSL。
  • TCP与UDP的区别与适用场景
  • nagle / keepalive / linger等选项的区别
  • 拥塞控制慢开始、拥塞避免、快重传、滑动窗口协议、停止等待协议、超时重传机制
  • IP地址子网划分
  • DNS解析过程
  • 地址解析协议ARP
  • 交换机和路由器的区别
  • http和https区别,https在请求时额外的过程,https是如何保证数据安全的
  • SESSION机制、cookie机制
    session和cookie的目的相同,都是为了克服http协议无状态的缺陷,但完成的方法不同。 session通过cookie,在客户端保存session id,而将用户的其他会话消息保存在服务端的session对象中, 与此相对的,cookie需要将所有信息都保存在客户端。因此cookie存在着一定的安全隐患,例如本地cookie 中保存的用户名密码被破译,或cookie被其他网站收集(例如:1. appA主动设置域B cookie,让域B cookie获取;2. XSS,在appA上通过javascript获取document.cookie,并传递给自己的appB)。
  • HTTP请求方法(GET、HEAD、POST、PUT、DELETE、CONNECT、OPTIONS、TRACE)
  • 熟悉tcpdump命令,windows下wireshark使用

3.2 常见问题

  • A与B建立了正常连接后,从未相互发过数据,这个时候B突然机器重启,问A此时的tcp状态处于什么状态?如何消除服务器程序中的这个状态
  • 聊天用什么协议(UDP),为什么使用UDP
  • 微信qq用什么协议
  • 跟多个人聊天怎么实现的(多线程),多线程怎么判断和哪个人聊天,需要设置什么全局变量

四、数据库

4.1 MySql

  • 数据表结构设计(三范式、字段属性)
  • 查询优化(索引的概念与创建、sql优化)
  • 事务四大特性(ACID)
  • 数据库隔离级别,每个级别会引发什么问题,mysql默认是哪个级别
  • MYSQL的两种存储引擎区别(事务、锁级别等等),各自的适用场景
  • 数据库的优化(从sql语句优化和索引两个部分回答)
  • 索引有B+索引和hash索引,各自的区别
  • B+索引数据结构,和B树的区别
  • 索引的分类(主键索引、唯一索引),最左前缀原则,哪些情况索引会失效
  • 聚集索引和非聚集索引区别
  • 有哪些锁(乐观锁悲观锁),select时怎么加排它锁
  • 关系型数据库和非关系型数据库区别
  • 数据库的主从复制
  • long_query怎么解决
  • 使用explain优化sql和索引
  • 内连接、外连接、交叉连接、笛卡儿积等
  • MVCC机制
  • 死锁怎么解决
  • varchar和char的使用场景。
  • mysql并发情况下怎么解决(通过事务、隔离级别、锁)
  • 字典树
  • 平时怎么写数据库的模糊查询

4.2 Redis

  • redis基本数据结构
  • redis队列应用场景
  • redis和Memcached的区别(支持数据持久化)
  • 分布式使用场景(储存session等)
  • 发布/订阅使用场景
  • redis的数据存储结构、rehash;
  • redis的事务与集群。
  • redis的持久化
  • redis热备
  • redis和的hash和string选择的优劣
  • redis单线程设计的原因
  • redis淘汰策略

五、数据结构和算法

5.1 排序和查找

5.2 树

  • AVL和红黑树的区别和原理实现
  • 哈夫曼树,哈夫曼编码。哈夫曼译码
  • AVL树和B树的概念、细节
  • 红黑树的概念、平均算法复杂度、最好最坏情况下的算法复杂度,左右旋转、颜色变换
  • 层次遍历、求深度、求两个节点距离、翻转二叉树、前中后序遍历
  • B+树和B树的区别,插入节点怎么分裂

5.3 哈希表

  • 建表以及解决哈希冲突
  • 哈希表的冲突检测、哈希函数常用实现、算法复杂度
  • 哈希表插入元素算法,元素类型是任意类型
  • hashmap的实现原理 采用什么方法能保证每个bucket中的数据更均匀

5.4 链表

  • 删除一个节点
  • 单链表倒转
  • 两个链表找相交的部分
  • 插入节点、链表逆置、使用链表进行大数字的加减,双向链表实现队列、寻找链表中的环

5.5 堆

  • 大量数据中寻找最大N个数字几乎每次都会问
  • 堆在插入时进行的调整

5.6 栈和队列

  • 两个栈实现队列

5.6 其他

  • KMP算法描述

5.7 常见问题

  • 单纯反转,hello world! :!!dlrow olleh 剑指offer原题。然后问,如果不用栈怎么做。指针找空格,后往前反转
  • 1000万个数,取出top100 堆实现和二分
  • 有100W个数字存放在一个文件中,然后让你随机生成100个数字不要与文件中的数字重复。(关键代码实现)典型的大文件处理
  • 两个有序数组,a[],b[],大小分别为m,n,求第k 大的数
  • 在一组数里面找到一个数,比它左右相邻的数都要大
  • 一个文本文件中每一行中有一个URL,最多一万行,统计每一个URL的次数,输出到另外一个文件中,每一行前面是URL,后面是个数。
  • 一个函数实现给定字符串,去除前面和后面的空格,比如“ ab cd ”,最后得到的结果是”ab cd”,不能改变字符串的地址
  • 对比cookie和session,有一个值错误则不正确
  • 查找10的阶乘后面有几个0
  • 字符串移位,给出字符串abc##dfg##gh,实现将所有#移至字符串串头。输出####abcdfggh(个人认为可以用后向移位,减少移位次数)
  • 给出一颗二叉树,两个叶节点,找到这两个叶节点互连通的一条最短路径
  • 两个日期计算天数差
  • 100个有序数组合并
  • 在一个字符串中找出第一个字符出现的位置,保证高效

alpine是一个轻量级的linux系统,由于太轻量了,目前被广泛用于docker镜像的制作上了(最新版的docker镜像才5M)。

体积小就有缺点了,缺点就是内部除了基本的命令以外,大部门的功能都不支持,默认连bash都没有。需要自己手动添加并重新构建。

1. 基于alpine:3.8添加bash并设置阿里云源

FROM alpine:3.8

MAINTAINER MaQian

RUN echo "https://mirrors.aliyun.com/alpine/v3.8/main/" > /etc/apk/repositories

RUN apk update \
        && apk upgrade \
        && apk add --no-cache bash bash-doc bash-completion \
        && rm -rf /var/cache/apk/* \
        && /bin/bash

2. 基于最新版alpine并设置阿里云源

相比上面的多了一个获取当前版本的逻辑,Dockerfile如下:

from alpine:latest

MAINTAINER MaQian

RUN alpine_version=`cat /etc/issue | head -1 | awk '{print $5}'` \
    && echo "https://mirrors.aliyun.com/alpine/v${alpine_version}/main/" > /etc/apk/repositories   \
    && apk update && apk upgrade && apk add --no-cache bash bash-doc bash-completion \
    && rm -rf /var/cache/apk/*

CMD ["/bin/bash"]

一、描述

前阵子硬盘出了问题,导致之前的vmware esxi系统坏了,重装一直报错,搞了几次搞不定干脆换成XenServer了。

XenServer相对vmware来说不是特别友好,第一步在创建新虚拟机的时候就卡了很久,主要是创建时虚拟机的镜像文件无法找到。很奇葩,竟然不能直接从本地导入上传的,只能从远端的NAS或者其他文件系统中获取,对于windows来说最简单的就是设置共享文件夹。

二、步骤

2.1 XenServer创建新虚拟机

首先,在界面上选择New VM按钮进入新建虚拟机向导页面:

选择好自己的系统类型,然后继续下一步,我这里是CentOS 7

然后选择系统镜像位置,这里不能直接从本地上传,也正是卡壳的地方。要点击右边的新建iso仓库选项先创建一个仓库:

选择仓库类型为Windows File Sharing,下一步:

如果已有共享文件夹,直接输入共享文件夹地址和账号密码即可,没有的的话参考2.2节先配置windows共享文件夹目录:

不出意外镜像仓库就添加成功了,只要把镜像放在这个目录下就行了。正常情况下这里是有意外的,有意外的话看下面的错误解决方案。

注意事项
iso文件一定要放在共享文件夹的当前目录下,不能在子目录下边。
例如共享目录为\\domain\b,则iso文件一定要在\\domain\b目录下,不能在\\domain\b\c\下边,否则会找不到文件

找到的iso文件后后面就比较顺畅了,和其他虚拟化产品一样,依次设置内存、磁盘已经网络等信息,一直下一步即可:

设置CPU配置:

设置磁盘,默认10G,可能较小,适当调整:

设置网络,网络比较重要,这里不描述:

设置完成:

2.2 windows开启共享文件夹

首先在需要共享的文件夹内右键属性,进入共享TAB页面:

PIC20190406_231541.png

选择共享,添加允许访问的用户:

PIC20190406_231606.png

选好后,确认,返回到第一层页面,点击高级共享,设置共享的名字,这个名字是后续需要访问的地址的一部分:

加上windows名字为maqian,设置的共享名为share,开启共享后访问的路径就是\\maqian\share

PIC20190406_231624.png

设置读写权限,然后确定退出当前页面:

PIC20190406_231624.png

再点击第一层页面的网络共享中心,关闭密码访问:

PIC20190406_231731.png

共享文件夹设置完成。

三、错误问题解决

3.1 DNS找不到

DNS找不到会报以下错误:

此时需要进入到XeNserver的后台,用SSH登陆,修改hosts文件,把主机指向当前设备的IP。

例如我的windows名字为maqian,共享文件夹的路径为\\maqian\share,正常情况下maqian只在windows下生效,XenServer是不知道这个主机的IP是哪里的,因为没有DNS解析。

要做的事情就是把maqian添加解析,windows下通过ipconfig命令看到网卡IP为192.168.1.3,添加到XenServer的hosts文件:

echo "192.168.1.3 maqian" > /etc/hosts

然后ping maqian,如果能通表明正常,否则需要检查网络问题。

3.2 认证问题

如果出现以下错误,说明windows的账号和密码不对:

检查当前的windows登入账号或者密码,这个账号密码是windows共享文件夹共享时添加的用户的账户和密码。

一、问题现象

使用golang编译了一个二进制程序,在CentOSUbuntu的镜像上运行是可以的,但是在Alpine运行就不行,使用./运行报错:

/bin/sh: ./saas_server: not found

二、解决方案

编译时添加参数CGO_ENABLED=0,关闭CGO就可以了:

CGO_ENABLED=0 go build

三、参考文档

Installed Go binary not found in path on Alpine Linux Docker

使用map要添加头文件#include <map>,命名空间using namespace std

初始化一个map:

map<int, bool> m1; 
map<int, const char *> m2; 

对于C++11,还可以在初始化时设定一系列初始值:

map<const char *, int> age_map = {
    pair<const char *, int>("maqian", 22),
    pair<const char *, int>("xiaobai", 1)
};

基本用法:

// 插入元素
insert();
// 删除元素
erase();
// 元素是否存在
count():
// 得到k的值
map[k];

注意的是获取key的值的时候,如果不存在这个元素,map将会自动增加一个当前key的元素。

int main() {
    map<int, bool> m1;
    map<int, const char *> m2;

    map<const char *, int> age_map = {
        pair<const char *, int>("maqian", 22),
        pair<const char *, int>("xiaobai", 1)
    };

    age_map.insert(
        pair<const char *, int>("zhouzhou", 3)
    );

    printf("count: %d, name: %s, age: %d\n", age_map.size(), "maqian", age_map["maqian"]);

    age_map.erase("maqian");
    printf("count: %d, name: %s, age: %d\n", age_map.size(), "maqian", age_map["maqian"]);


    return 0;
}
count: 3, name: maqian, age: 22
count: 3, name: maqian, age: 0
因此,注意不要通过map[x]来判断元素是否存在,使用count()方法。

给nginx配置https时报错:

the "ssl" directive is deprecated, use the "listen ... ssl" directive instead in "nginx.conf:44" 

问题原因

nginx新版本修改了ssl的配置方式,老的ssl on语法已经不推荐使用,建议使用以下形式开启ssl:

listen 443 ssl;

ssl_certificate xx.crt;
ssl_certificate_key xx.key;

低版本nginx开启ssl指令:

ssl on;
ssl_certificate xx.crt;
ssl_certificate_key xx.key;

c++中,调用c语言函数时要对函数添加声明extern "C",这是因为c和c++的符号表机制不一样导致的。

假设存在函数fun,经过gcc编译后,该函数在符号表中的名字是fun,但是经过g++编译后,该函数在符号表中的结果是xxfunxx,c++编译器会在函数两侧加上一些随机字符。因为两者的编译结果不统一,所以混合使用的时候程序找不到符号,无法运行(甚至都无法编译)。一个典型的错误就是(参考实际开发中出现的一个案例:一次undefined reference to *的编译错记录):

/tmp/cccZeFer.o: In function `main':
main.cpp:(.text+0xf): undefined reference to `add'
collect2: error: ld returned 1 exit status

extern "C"语句的作用就是告诉c++编译器:我这个是c语言函数,不要按照你的格式来修改这个函数的符号,按照我的标准来就行了。于是c++编译器编译的时候就按照c的规范来,把两者符号统一。

示例

#include<stdio.h>

int add(int i, int j) {
    return i + j;
} 

int main() {
    printf("%d\n", add(1, 2));
    return 0;
}

代码中有一个函数add,看看它经过gcc和g++编译后分别变成了什么。

使用gcc编译:

image.png

使用g++编译:

imagedb520d0468aeb499.png

两者对比,很明显能看到,使用g++编译后的add函数,符号表前后加上了一些特殊字符。而gcc编译出来的符号就是add本身。

给add函数加上extern "C"声明后:

#ifdef __cplusplus
extern "C" {
#endif

int add(int i, int j) {
    return i + j;
} 

#ifdef __cplusplus
}
#endif

再次使用g++编译,add的符号就和gcc编译出来的符号一样了:

image416e247cd34d6a8f.png

ARQ全称是Automatic repeat request,是TCP中实现可靠传输的重传协议,它的核心思想是停止等待协议。

一、停止等待协议

停止等待协议是数据链路层中最基础的协议,在数据发送出去后,如果没有收到对端的回复,会一直等待对方回复。直到触发超时机制再自动重传,确保数据传输的可靠性。

一个没差错的停止等待协议交互过程为:

一旦出现了差错,停止等待协议会重发数据包,直到数据发送成功:

TCP协议的核心思想就是这样,如果发现数据出现了丢失,就要重传这个数据包,直到数据发送成功为止。

那么如何确定数据报丢失了?TCP使用一个重传计时器对发送报文段计时,如果在计时器归零时还没有收到对端回复,那么就认为数据报已经丢失,需要重传。重传计时器的计时时间被称为RTO,他是通过数据报往返时间动态测量出来的,不同系统上实现也不同。参考:TCP中的RTT和RTO

二、连续ARQ协议

连续ARQ协议指的是每次发送数据时,不是发送一个数据报文,而是同时发送多个报文。因为如果每次都只发送一个数据报,信道的利用率极低,传输效率不高。

连续ARQ协议的作用就是为了弥补这个不足,在发送数据时,不用每次都等到上一个数据包确认后才发送下一个,而是每次都发送一组数据包。确认数据包的同时还继续发送数据包,这样就大大提高了效率。

不出现差错的情况下的交互过程:

出现差错的交互过程:

三、滑动窗口协议

连续ARQ协议是配合滑动窗口协议来完成,每次分组发送的报文数量取决于窗口大小。

参考:TCP的滑动窗口协议

使用logging模块打印日志的时候遇到错误:

Traceback (most recent call last):
  File "saas_utils.py", line 106, in <module>
    sangfor_login("192.168.10.1", login_user, login_pwd)
  File "saas_utils.py", line 72, in sangfor_login
    logging.INFO("%s: %s" % (type(cookie_str), cookie_str))
TypeError: 'int' object is not callable

网上查了一下,出现这个问题的原因是函数或者变量命名错误了,但是检查了一下没有发现有重复的函数和变量名。

后面仔细推敲了一下才发现函数名字写错了,logging.info被我写成了loggin.INFOloggin.INFO是日志的调试级别,当成函数使用当然报错了。

win10的输入法十分简洁,用起来也十分酸爽,对于程序员来说再适合不过了。

只是唯一不好的是输入法没有学习功能(默认情况下没有,需要手动开启),最基本的根据词频调整顺序的功能都没有。

想想这么牛逼的输入法连这个功能都没有岂不是个渣渣?百度一番之后,果然。。。

进入设置页面,点击隐私

然后选择语音、墨迹书写和键入,把开关打开:

enjoy it!