轻松学编程

轻松学编程 查看完整档案

长沙编辑华南理工大学  |  计科 编辑字节  |  后端工程师 编辑 space.bilibili.com/151088805 编辑
编辑

个人动态

轻松学编程 发布了文章 · 2020-12-11

腾讯T1~T9级别工程师分别需要具备哪些能力你知道吗?

前言

2020年秋招已经结束了,很多没拿到满意offer的同学已经在备战2021年春招了。腾讯在国内无论是知名度还是体量都是当之无愧的的巨无霸,本身也是很多朋友的目标,就跟清北之于高考生一样,先不说能不能考上,但没有考生是不向往的。

而且鲁迅说过刚毕业的程序员能进BAT这等大厂是最好的,因为不论是技术体系、职业规划和发展前途都不是小公司能比拟的。只是该怎么进鲁迅没说,但是别急,今天我来带大家看看腾讯T1~T9各级别工程师需要具备哪些能力,同学们也可以

对照自身所学看看自己在哪个级别,都为大家明年春招进大厂造桥铺路,文末准备了一些学习干货给大家,有需要的朋友可以看看。好了,话不多说,一起来看看。

目录

  1. 语言
  2. 数据结构和算法
  3. 数据库
  4. 网络原理
  5. 操作系统
  6. 网络编程
  7. 分布式架构
  8. 云原生
  9. 软技能
    • *

一、语言

  1. 开发环境搭建
  2. 运行第一个hello word(T1~T9)
  3. 运用基本数据类型
  4. 表达式与操作符进行简单开发(T1~T9)
  5. 字符串、向量和数组(T1~T9)
  6. 语句和函数(T1~T9)
  7. 分支控制、循环(T1~T9)
  8. 对象生命周期 & 垃圾回收(T2~T9)
  9. 错误和异常处理(T2~T9)
  10. 标准库使用(T2~T9)
  11. 日志分析、断点调试等简单代码调试方法(T3~T9)
  12. 面向对象设计原则(T3~T9)
  13. 单例模式、工厂模式等简单的设计模式(T4~T9)
  14. gdb高级调试技巧和常用系统分析工具使用(T5~T9)
  15. 适配器模式、代理模式等常见设计模式(T6~T9)
  16. 创建型、结构型、行为型设计模式的应用技巧(T7~T9)
  17. 代码耦合的产生原因和规避方法(T9)

二、数据结构和算法

  1. 初识数据结构和算法 - 冒泡排序(T2~T9)
  2. 数组、列表、栈,队列等基本线性数据结构(T2~T9)
  3. 递归、排序、二分查找算法(T2~T9)
  4. 跳表、散列表、hash算法(T3~T9)
  5. 二叉树、红黑树(T3~T9)
  6. 堆排序、归并排序、二分查找等高阶算法(T4~T9)
  7. 字符串匹配、KMP算法(T4~T9)
  8. 熟悉图论算法:dijkstra算法、最小生成树、深度优先搜索等(T5~T9)
  9. 贪心算法、动态规划(T5~T9)
  10. 能够分析算法的执行效率和资源消耗、时间和空间复杂度分析(T6~T9)
  11. 能够根据实际场景,选用合适的数据结构和算法进行程序设计(T7~T9)

三、数据库

  1. MySQL服务器的安装与配置(T1~T9)
  2. 数据表的新建、查询和删除操作(T1~T9)
  3. 标准语言SQL和CURD能力(T2~T9)
  4. 表、主键、索引、视图(T2~T9)
  5. 表结构设计规范(T3~T9)
  6. 事务的使用以及其ACID特性(T3~T9)
  7. 不同存储引擎的区别(T3~T9)
  8. 事务的隔离级别和实践原理(T4~T9)
  9. 数据库的连接池管理(T4~T9)
  10. 数据库的权限管理(T4~T9)
  11. SQL注入、web shell攻击的危害和规避方法(T4~T9)
  12. 数据库的集群和高可用(T5~T9)
  13. SQL语句优化、索引和参数调优(T5~T9)
  14. 运用Redis进行数据缓存处理,提高系统性能(T6~T9)
  15. redis内存模型(T7~T9)
  16. redis的持久化、主从同步以及集群(T7~T9)

四、网络原理

  1. redis的持久化、主从同步以及集群(T7~T9)
  2. 利用ping、ifconfig、telnet等命令查看计算机网络状况(T1~T9)
  3. 网络体系中每一层的作用和基本协议(T2~T9)
  4. TCP和UDP的基本原理(T3~T9)
  5. http协议基础与应用(T3~T9)
  6. TCP协议的可靠传输机制(T4~T9)
  7. TCP协议的流量控制和拥塞控制(T4~T9)
  8. Session、Cookie 与 Application(T5~T9)
  9. 网络安全的加密算法与数字签名(T5~T9)
  10. https协议的基本原理(T5~T9)
  11. http2.0与http3.0特性(T6~T9)
  12. XSS攻击的危害和规避方法(T6~T9)
  13. Wireshark、tcpdump等网络抓包工具使用和结果分析(T6~T9)
  14. 网络故障分析与问题解决(T6~T9)

五、操作系统

  1. 常用的文件、目录操作命令使用(T1~T9)
  2. 程序的编译与运行(T1~T9)
  3. shell脚本与vi使用(T2~T9)
  4. linux系统性能监控命令的使用(T2~T9)
  5. 线程、进程的概念、创建与调度(T3~T9)
  6. 用户态和内核态的基本概念、区别(T3~T9)
  7. CPU的上下文切换、中断处理与系统调度(T3~T9)
  8. 磁盘文件系统、虚拟文件系统与文件缓存(T4~T9)
  9. 物理内存和虚拟内存(T4~T9)
  10. 进程间通信机制(T5~T9)
  11. 死锁的危害、出现原因、解决方法(T5~T9)
  12. 内存溢出、内存泄漏的原因与解决(T6~T9)
  13. 如何高效利用CPU缓存(T7~T9)
  14. 内存问题分析与性能优化(T7~T9)
  15. 磁盘 I/O 性能优化(T7~T9)
  16. 系统网络性能评估与优化(T9)

六、网络编程

  1. 利用socket编程编写简单客户/服务器程序(T2~T9)
  2. 基本TCP/UDP套接字编程(T3~T9)
  3. 五种常见I/O模型(T4~T9)
  4. I/O多路复用技术(T5~T9)
  5. time_wait、close_wait状态产生的原因、危害与避免方法(T5~T9)
  6. 熟练掌握各种I/O模型的运用场景(T5~T9)
  7. 掌握C10k问题的基本解决方案(T5~T9)
  8. 网络框架学习(T9)

七、分布式架构

  1. 简单rpc协议设计和框架搭建(T5~T9)
  2. rpc的服务寻址、数据流的序列化与反序列化和网路传输(T5~T9)
  3. 分布式事务的学习(T5~T9)
  4. 一致性算法(T5~T9)
  5. 分布式锁的设计与实现(T5~T9)
  6. 主流的微服务rpc框架(T5~T9)
  7. 服务注册、服务发现(T6~T9)
  8. 分布式trace、监控告警(T7~T9)
  9. 负载均衡(T7~T9)
  10. 主流分布式系统架构设计,比如分布式缓存、API网关、分布式消息组件(T8~T9)
  11. 分布式系统的缓存设计、缓存雪崩、缓存击穿等现象(T8~T9)
  12. 容灾保护、降级熔断、流量控制、故障隔离、故障恢复(T9)
  13. 分布式架构的高并发、高可用、可扩展(T9)

八、云原生

  1. Docker原理与搭建(T4~T9)
  2. Kubernetes(T5~T9)
  3. 容器编排、容器网络(T6~T9)
  4. 集成构建(T6~T9)
  5. 云上常见的SaaS服务和组件(T7~T9)
  6. 弹性扩缩容(T8~T9)
  7. 服务网格Istio和Serverless(T9)

九、软技能

  1. 需求分析(T9)
  2. 项目设计方法(T9)
  3. 项目流程管理(T9)
  4. 项目风险控制(T9)
  5. 跨部门合作(T9)
  6. 沟通技巧(T9)
  7. 组织协调(T9)
  8. 目标管理(T9)
    • *

好了,本篇文章就写到这吧,在备战2021春招和准备年初跳槽的朋友们可以进群973961276来跟大家一起交流技术和面试经验,个人也整理了一些不错的学习书籍、视频资料以及面试干货,完整思维导图也在群里哦。希望就业跟跳槽的大家都能早日上岸!最后推荐一下对标腾讯T9高工的linux c/c++后台架构师课程,希望对你们入职大厂能有所帮助

查看原文

赞 0 收藏 0 评论 0

轻松学编程 发布了文章 · 2020-12-02

王者荣耀如何使用UDP做到低延迟

赞 0 收藏 0 评论 0

轻松学编程 发布了文章 · 2020-12-02

阿里云用DPDK如何解决千万级流量并发

赞 0 收藏 0 评论 0

轻松学编程 发布了文章 · 2020-12-02

非科班学员斩获京东校招 28woffer

赞 0 收藏 0 评论 0

轻松学编程 发布了文章 · 2020-12-02

版本管理之git神器-创建仓库和搭建git服务器

赞 0 收藏 0 评论 0

轻松学编程 发布了文章 · 2020-11-28

还搞不懂C语言中的函数指针?看完你就明白了

1.函数指针的定义

  顾名思义,函数指针就是函数的指针。它是一个指针,指向一个函数。看例子:

A) char * (*fun1)(char * p1,char * p2);

B) char * *fun2(char * p1,char * p2);

C) char * fun3(char * p1,char * p2);

看看上面三个表达式分别是什么意思?  

C)这很容易,fun3是函数名,p1,p2是参数,其类型为char 型,函数的返回值为char 类型。
B) 也很简单,与C)表达式相比,唯一不同的就是函数的返回值类型为char**,是个二级指针。
A) fun1是函数名吗?回忆一下前面讲解数组指针时的情形。我们说数组指针这么定义或许更清晰:

int (*)[10] p;

再看看A)表达式与这里何其相似!明白了吧。这里fun1不是什么函数名,而是一个指针变量,它指向一个函数。这个函数有两个指针类型的参数,函数的返回值也是一个指针。同样,我们把这个表达式改写一下:

char * (*)(char * p1,char * p2) fun1;

这样子是不是好看一些呢?只可惜编译器不这么想。^_^。

2.函数指针使用的例子

  上面我们定义了一个函数指针,但如何来使用它呢?先看如下例子:

#include <stdio.h>

#include <string.h>



char * fun(char * p1,char * p2)

{

  int i = 0;

  i = strcmp(p1,p2);



  if (0 == i)

  {

    return p1;

  }

  else

  {

    return p2;

  }

}



int main()

{

  char * (*pf)(char * p1,char * p2);

  pf = &fun;

  (*pf) ("aa","bb");

  return 0;

}

  这里需要注意到是,在Visual C++6.0里,给函数指针赋值时,可以用&fun或直接用函数名fun。这是因为函数名被编译之后其实就是一个地址,所以这里两种用法没有本质的差别。这个例子很简单,就不再详细讨论了。  我们使用指针的时候,需要通过钥匙(“”)来取其指向的内存里面的值,函数指针使用也如此。通过用(pf)取出存在这个地址上的函数,然后调用它。

3.(int)&p ----这是什么?

  也许上面的例子过于简单,我们看看下面的例子:

void Function()

{

  printf("Call Function!n");

}<br>

int main()

{

  void (*p)();

  *(int*)&p=(int)Function;

  (*p)();

  return 0;

} 

这是在干什么?(int)&p=(int)Function;表示什么意思?
别急,先看这行代码:

void (*p)();

这行代码定义了一个指针变量p,p指向一个函数,这个函数的参数和返回值都是void。
&p是求指针变量p本身的地址,这是一个32位的二进制常数(32位系统)。
(int*)&p表示将地址强制转换成指向int类型数据的指针。
(int)Function表示将函数的入口地址强制转换成int类型的数据。
分析到这里,相信你已经明白(int)&p=(int)Function;表示将函数的入口地址赋值给指针变量p。

那么(*p) ();就是表示对函数的调用。

讲解到这里,相信你已经明白了。其实函数指针与普通指针没什么差别,只是指向的内容不同而已。
使用函数指针的好处在于,可以将实现同一功能的多个模块统一起来标识,这样一来更容易后期的维护,系统结构更加清晰。或者归纳为:便于分层设计、利于系统抽象、降低耦合度以及使接口与实现分开。

推荐一下自己的linuxC/C++交流群:973961276!整理了一些个人觉得比较好的学习书籍、视频资料以及大厂面经视频共享在群文件里面,有需要的小伙伴可以自行添加哦!~

4.((void() ())0)()------这是什么?

  是不是感觉上面的例子太简单,不够刺激?好,那就来点刺激的,看下面这个例子:

(*(void(*) ())0)();

这是《C Traps and Pitfalls》这本经典的书中的一个例子。没有发狂吧?下面我们就来分析分析:

第一步:void(*) (),可以明白这是一个函数指针类型。这个函数没有参数,没有返回值。

第二步:(void(*) ())0,这是将0强制转换为函数指针类型,0是一个地址,也就是说一个函数存在首地址为0的一段区域内。

第三步:(*(void(*) ())0),这是取0地址开始的一段内存里面的内容,其内容就是保存在首地址为0的一段区域内的函数。

第四步:(*(void(*) ())0)(),这是函数调用。 

好像还是很简单是吧,上面的例子再改写改写:

(*(char**(*) (char **,char **))0) ( char **,char **);

如果没有上面的分析,肯怕不容易把这个表达式看明白吧。不过现在应该是很简单的一件事了。读者以为呢?

5.函数指针数组

  现在我们清楚表达式

char * (*pf)(char * p);

定义的是一个函数指针pf。既然pf是一个指针,那就可以储存在一个数组里。把上式修改一下:

char * (*pf[3])(char * p);

这是定义一个函数指针数组。

  它是一个数组,数组名为pf,数组内存储了3个指向函数的指针。这些指针指向一些返回值类型为指向字符的指针、参数为一个指向字符的指针的函数。

  这念起来似乎有点拗口。不过不要紧,关键是你明白这是一个指针数组,是数组。函数指针数组怎么使用呢?这里也给出一个非常简单的例子,只要真正掌握了使用方法,再复杂的问题都可以应对。

如下:

#include <stdio.h>

#include <string.h>

<br>char * fun1(char * p)

{

  printf("%sn",p);

  return p;

}

char * fun2(char * p)

{

  printf("%sn",p);

  return p;

}

char * fun3(char * p)

{

  printf("%sn",p);

  return p;

}

<br>int main()

{

  char * (*pf[3])(char * p);

  pf[0] = fun1; //可以直接用函数名

  pf[1] = &fun2; //可以用函数名加上取地址符

  pf[2] = &fun3;<br>

  pf[0]("fun1");

  pf[0]("fun2");

  pf[0]("fun3");

  return 0;

} 

6.函数指针数组的指针

  看着这个标题没发狂吧?函数指针就够一般初学者折腾了,函数指针数组就更加麻烦,现在的函数指针数组指针就更难理解了。
其实,没这么复杂。前面详细讨论过数组指针的问题,这里的函数指针数组指针不就是一个指针嘛。只不过这个指针指向一个数组,这个数组里面存的都是指向函数的指针。仅此而已。

下面就定义一个简单的函数指针数组指针:

1

char  ((pf)[3])(char  p);

注意,这里的pf和上一节的pf就完全是两码事了。上一节的pf并非指针,而是一个数组名;这里的pf确实是实实在在的指针。这个指针指向一个包含了3个元素的数组;这个数字里面存的是指向函数的指针;这些指针指向一些返回值类型为指向字符的指针、参数为一个指向字符的指针的函数。

  这比上一节的函数指针数组更拗口。其实你不用管这么多,明白这是一个指针就ok了。其用法与前面讲的数组指针没有差别。下面列一个简单的例子:

#include <stdio.h>

#include <string.h>



char * fun1(char * p)

{

    printf("%sn",p);

    return p;

}



char * fun2(char * p)

{

    printf("%sn",p);

    return p;

}



char * fun3(char * p)

{

    printf("%sn",p);

    return p;

}



int main()

{

    char * (*a[3])(char * p);

    char * (*(*pf)[3])(char * p);

    pf = &a;



    a[0] = fun1;

    a[1] = &fun2;

    a[2] = &fun3;



    pf[0][0]("fun1");

    pf[0][1]("fun2");

    pf[0][2]("fun3");

    return 0;

}


本篇文章就到这里了,学习中有什么疑惑或者需要什么资料,可以来跟大家一起交流哦!

c/c++零基础小白到企业级项目实战

查看原文

赞 0 收藏 0 评论 0

轻松学编程 发布了文章 · 2020-11-26

让c/c++堆栈的工作机制变得通俗易懂

前言

    我们经常会讨论这样的问题:什么时候数据存储在堆栈(Stack)中,什么时候数据存储在堆(Heap)中。我们知道,局部变量是存储在堆栈中的;debug时,查看堆栈可以知道函数的调用顺序;函数调用时传递参数,事实上是把参数压入堆栈,听起来,堆栈象一个大杂烩。那么,堆栈(Stack)到底是如何工作的呢? 本文将详解C/C++堆栈的工作机制。阅读时请注意以下几点:

    1)本文讨论的编译环境是 Visual C/C++,由于高级语言的堆栈工作机制大致相同,因此对其他编译环境或高级语言如C#也有意义。

    2)本文讨论的堆栈,是指程序为每个线程分配的默认堆栈,用以支持程序的运行,而不是指程序员为了实现算法而自己定义的堆栈。

    3)  本文讨论的平台为intel x86。

    4)本文的主要部分将尽量避免涉及到汇编的知识,在本文最后可选章节,给出前面章节的反编译代码和注释。

    5)结构化异常处理也是通过堆栈来实现的(当你使用try…catch语句时,使用的就是c++对windows结构化异常处理的扩展),但是关于结构化异常处理的主题太复杂了,本文将不会涉及到。

6)推荐一下自己的linuxC/C++交流群:973961276!整理了一些个人觉得比较好的学习书籍、视频资料以及大厂面经视频共享在群文件里面,有需要的小伙伴可以自行添加哦!~

从一些基本的知识和概念开始

    1) 程序的堆栈是由处理器直接支持的。在intel x86的系统中,堆栈在内存中是从高地址向低地址扩展(这和自定义的堆栈从低地址向高地址扩展不同),如下图所示:

image

    因此,栈顶地址是不断减小的,越后入栈的数据,所处的地址也就越低。

    2) 在32位系统中,堆栈每个数据单元的大小为4字节。小于等于4字节的数据,比如字节、字、双字和布尔型,在堆栈中都是占4个字节的;大于4字节的数据在堆栈中占4字节整数倍的空间。

    3) 和堆栈的操作相关的两个寄存器是EBP寄存器和ESP寄存器的,本文中,你只需要把EBP和ESP理解成2个指针就可以了。ESP寄存器总是指向堆栈的栈顶,执行PUSH命令向堆栈压入数据时,ESP减4,然后把数据拷贝到ESP指向的地址;执行POP命令时,首先把ESP指向的数据拷贝到内存地址/寄存器中,然后ESP加4。EBP寄存器是用于访问堆栈中的数据的,它指向堆栈中间的某个位置(具体位置后文会具体讲解),函数的参数地址比EBP的值高,而函数的局部变量地址比EBP的值低,因此参数或局部变量总是通过EBP加减一定的偏移地址来访问的,比如,要访问函数的第一个参数为EBP+8。

    4) 堆栈中到底存储了什么数据? 包括了:函数的参数,函数的局部变量,寄存器的值(用以恢复寄存器),函数的返回地址以及用于结构化异常处理的数据(当函数中有try…catch语句时才有,本文不讨论)。这些数据是按照一定的顺序组织在一起的,我们称之为一个堆栈帧(Stack Frame)。一个堆栈帧对应一次函数的调用。在函数开始时,对应的堆栈帧已经完整地建立了(所有的局部变量在函数帧建立时就已经分配好空间了,而不是随着函数的执行而不断创建和销毁的);在函数退出时,整个函数帧将被销毁。

    5) 在文中,我们把函数的调用者称为caller(调用者),被调用的函数称为callee(被调用者)。之所以引入这个概念,是因为一个函数帧的建立和清理,有些工作是由Caller完成的,有些则是由Callee完成的。

开始讨论堆栈是如何工作的

    我们来讨论堆栈的工作机制。堆栈是用来支持函数的调用和执行的,因此,我们下面将通过一组函数调用的例子来讲解,看下面的代码:

intfoo1(`intm,intn)`

{

intp=m*n;

returnp;

}

intfoo(`inta,intb)`

{

intc=a+1;

intd=b+1;

inte=foo1(c,d);

returne;

}

intmain()

{

intresult=foo(3,4);

return0;

}

    这段代码本身并没有实际的意义,我们只是用它来跟踪堆栈。下面的章节我们来跟踪堆栈的建立,堆栈的使用和堆栈的销毁。

堆栈的建立

    我们从main函数执行的第一行代码,即int result=foo(3,4); 开始跟踪。这时main以及之前的函数对应的堆栈帧已经存在在堆栈中了,如下图所示:

image

图1

参数入栈 

   当foo函数被调用,首先,caller(此时caller为main函数)把foo函数的两个参数:a=3,b=4压入堆栈。参数入栈的顺序是由函数的调用约定(Calling Convention)决定的,我们将在后面一个专门的章节来讲解调用约定。一般来说,参数都是从右往左入栈的,因此,b=4先压入堆栈,a=3后压入,如图:

image

图2

返回地址入栈

    我们知道,当函数结束时,代码要返回到上一层函数继续执行,那么,函数如何知道该返回到哪个函数的什么位置执行呢?函数被调用时,会自动把下一条指令的地址压入堆栈,函数结束时,从堆栈读取这个地址,就可以跳转到该指令执行了。如果当前"call foo"指令的地址是0x00171482,由于call指令占5个字节,那么下一个指令的地址为0x00171487,0x00171487将被压入堆栈:

image

图3

代码跳转到被调用函数执行

    返回地址入栈后,代码跳转到被调用函数foo中执行。到目前为止,堆栈帧的前一部分,是由caller构建的;而在此之后,堆栈帧的其他部分是由callee来构建。

   EBP指针入栈

    在foo函数中,首先将EBP寄存器的值压入堆栈。因为此时EBP寄存器的值还是用于main函数的,用来访问main函数的参数和局部变量的,因此需要将它暂存在堆栈中,在foo函数退出时恢复。同时,给EBP赋于新值。

    1)将EBP压入堆栈

    2)把ESP的值赋给EBP

image

图4

    这样一来,我们很容易发现当前EBP寄存器指向的堆栈地址就是EBP先前值的地址,你还会发现发现,EBP+4的地址就是函数返回值的地址,EBP+8就是函数的第一个参数的地址(第一个参数地址并不一定是EBP+8,后文中将讲到)。因此,通过EBP很容易查找函数是被谁调用的或者访问函数的参数(或局部变量)。

为局部变量分配地址

    接着,foo函数将为局部变量分配地址。程序并不是将局部变量一个个压入堆栈的,而是将ESP减去某个值,直接为所有的局部变量分配空间,比如在foo函数中有ESP=ESP-0x00E4,(根据烛秋兄在其他编译环境上的测试,也可能使用push命令分配地址,本质上并没有差别,特此说明)如图所示:

image

图5

     奇怪的是,在debug模式下,编译器为局部变量分配的空间远远大于实际所需,而且局部变量之间的地址不是连续的(据我观察,总是间隔8个字节)如下图所示:

 image

图6

    我还不知道编译器为什么这么设计,或许是为了在堆栈中插入调试数据,不过这无碍我们今天的讨论。

通用寄存器入栈

     最后,将函数中使用到的通用寄存器入栈,暂存起来,以便函数结束时恢复。在foo函数中用到的通用寄存器是EBX,ESI,EDI,将它们压入堆栈,如图所示:

image

图7

   至此,一个完整的堆栈帧建立起来了。

堆栈特性分析

   上一节中,一个完整的堆栈帧已经建立起来,现在函数可以开始正式执行代码了。本节我们对堆栈的特性进行分析,有助于了解函数与堆栈帧的依赖关系。

   1)一个完整的堆栈帧建立起来后,在函数执行的整个生命周期中,它的结构和大小都是保持不变的;不论函数在什么时候被谁调用,它对应的堆栈帧的结构也是一定的。

   2)在A函数中调用B函数,对应的,是在A函数对应的堆栈帧“下方”建立B函数的堆栈帧。例如在foo函数中调用foo1函数,foo1函数的堆栈帧将在foo函数的堆栈帧下方建立。如下图所示:

image

图8 

  3)函数用EBP寄存器来访问参数和局部变量。我们知道,参数的地址总是比EBP的值高,而局部变量的地址总是比EBP的值低。而在特定的堆栈帧中,每个参数或局部变量相对于EBP的地址偏移总是固定的。因此函数对参数和局部变量的的访问是通过EBP加上某个偏移量来访问的。比如,在foo函数中,EBP+8为第一个参数的地址,EBP-8为第一个局部变量的地址。

   4)如果仔细思考,我们很容易发现EBP寄存器还有一个非常重要的特性,请看下图中:

image

图9

   我们发现,EBP寄存器总是指向先前的EBP,而先前的EBP又指向先前的先前的EBP,这样就在堆栈中形成了一个链表!这个特性有什么用呢,我们知道EBP+4地址存储了函数的返回地址,通过该地址我们可以知道当前函数的上一级函数(通过在符号文件中查找距该函数返回地址最近的函数地址,该函数即当前函数的上一级函数),以此类推,我们就可以知道当前线程整个的函数调用顺序。事实上,调试器正是这么做的,这也就是为什么调试时我们查看函数调用顺序时总是说“查看堆栈”了。

返回值是如何传递的

    堆栈帧建立起后,函数的代码真正地开始执行,它会操作堆栈中的参数,操作堆栈中的局部变量,甚至在堆(Heap)上创建对象,balabala….,终于函数完成了它的工作,有些函数需要将结果返回给它的上一层函数,这是怎么做的呢?

    首先,caller和callee在这个问题上要有一个“约定”,由于caller是不知道callee内部是如何执行的,因此caller需要从callee的函数声明就可以知道应该从什么地方取得返回值。同样的,callee不能随便把返回值放在某个寄存器或者内存中而指望Caller能够正确地获得的,它应该根据函数的声明,按照“约定”把返回值放在正确的”地方“。下面我们来讲解这个“约定”: 
    1)首先,如果返回值等于4字节,函数将把返回值赋予EAX寄存器,通过EAX寄存器返回。例如返回值是字节、字、双字、布尔型、指针等类型,都通过EAX寄存器返回。

    2)如果返回值等于8字节,函数将把返回值赋予EAX和EDX寄存器,通过EAX和EDX寄存器返回,EDX存储高位4字节,EAX存储低位4字节。例如返回值类型为__int64或者8字节的结构体通过EAX和EDX返回。

    3)  如果返回值为double或float型,函数将把返回值赋予浮点寄存器,通过浮点寄存器返回。

    4)如果返回值是一个大于8字节的数据,将如何传递返回值呢?这是一个比较麻烦的问题,我们将详细讲解:

        我们修改foo函数的定义如下并将它的代码做适当的修改:

MyStruct foo(`inta,intb)`

{

...

}

MyStruct定义为:

structMyStruct

{

intvalue1;

__int64value2;

boolvalue3;

};

     这时,在调用foo函数时参数的入栈过程会有所不同,如下图所示:

image

图10

    caller会在压入最左边的参数后,再压入一个指针,我们姑且叫它ReturnValuePointer,ReturnValuePointer指向caller局部变量区的一块未命名的地址,这块地址将用来存储callee的返回值。函数返回时,callee把返回值拷贝到ReturnValuePointer指向的地址中,然后把ReturnValuePointer的地址赋予EAX寄存器。函数返回后,caller通过EAX寄存器找到ReturnValuePointer,然后通过ReturnValuePointer找到返回值,最后,caller把返回值拷贝到负责接收的局部变量上(如果接收返回值的话)。

    你或许会有这样的疑问,函数返回后,对应的堆栈帧已经被销毁,而ReturnValuePointer是在该堆栈帧中,不也应该被销毁了吗?对的,堆栈帧是被销毁了,但是程序不会自动清理其中的值,因此ReturnValuePointer中的值还是有效的。

堆栈帧的销毁

    当函数将返回值赋予某些寄存器或者拷贝到堆栈的某个地方后,函数开始清理堆栈帧,准备退出。堆栈帧的清理顺序和堆栈建立的顺序刚好相反:(堆栈帧的销毁过程就不一一画图说明了)

   1)如果有对象存储在堆栈帧中,对象的析构函数会被函数调用。

    2)从堆栈中弹出先前的通用寄存器的值,恢复通用寄存器。

    3)ESP加上某个值,回收局部变量的地址空间(加上的值和堆栈帧建立时分配给局部变量的地址大小相同)。

    4)从堆栈中弹出先前的EBP寄存器的值,恢复EBP寄存器。

    5)从堆栈中弹出函数的返回地址,准备跳转到函数的返回地址处继续执行。

    6)ESP加上某个值,回收所有的参数地址。

    前面1-5条都是由callee完成的。而第6条,参数地址的回收,是由caller或者callee完成是由函数使用的调用约定(calling convention )来决定的。下面的小节我们就来讲解函数的调用约定。

函数的调用约定(calling convention)

    函数的调用约定(calling convention)指的是进入函数时,函数的参数是以什么顺序压入堆栈的,函数退出时,又是由谁(Caller还是Callee)来清理堆栈中的参数。有2个办法可以指定函数使用的调用约定:

    1)在函数定义时加上修饰符来指定,如

void__thiscall mymethod();

{

...

}

2)在VS工程设置中为工程中定义的所有的函数指定默认的调用约定:在工程的主菜单打开Project|Project Property|Configuration Properties|C/C++|Advanced|Calling Convention,选择调用约定(注意:这种做法对类成员函数无效)。

    常用的调用约定有以下3种:

    1)__cdecl。这是VC编译器默认的调用约定。其规则是:参数从右向左压入堆栈,函数退出时由caller清理堆栈中的参数。这种调用约定的特点是支持可变数量的参数,比如printf方法。由于callee不知道caller到底将多少参数压入堆栈,因此callee就没有办法自己清理堆栈,所以只有函数退出之后,由caller清理堆栈,因为caller总是知道自己传入了多少参数。

    2)__stdcall。所有的Windows API都使用__stdcall。其规则是:参数从右向左压入堆栈,函数退出时由callee自己清理堆栈中的参数。由于参数是由callee自己清理的,所以__stdcall不支持可变数量的参数。

    3) __thiscall。类成员函数默认使用的调用约定。其规则是:参数从右向左压入堆栈,x86构架下this指针通过ECX寄存器传递,函数退出时由callee清理堆栈中的参数,x86构架下this指针通过ECX寄存器传递。同样不支持可变数量的参数。如果显式地把类成员函数声明为使用__cdecl或者__stdcall,那么,将采用__cdecl或者__stdcall的规则来压栈和出栈,而this指针将作为函数的第一个参数最后压入堆栈,而不是使用ECX寄存器来传递了。

反编译代码的跟踪(不熟悉汇编可跳过)

    以下代码为和foo函数对应的堆栈帧建立相关的代码的反编译代码,我将逐行给出注释,可对照前文中对堆栈的描述:

    main函数中 int result=foo(3,4); 的反汇编:

008A147E  push        4//b=4 压入堆栈

008A1480  push        3//a=3 压入堆栈,到达图2的状态

008A1482  call        foo (8A10F5h)//函数返回值入栈,转入foo中执行,到达图3的状态

008A1487  add         esp,8//foo返回,由于采用__cdecl,由Caller清理参数

008A148A  mov         dword ptr [result],eax//返回值保存在EAX中,把EAX赋予result变量

  下面是foo函数代码正式执行前和执行后的反汇编代码

008A13F0  push        ebp//把ebp压入堆栈

008A13F1  mov         ebp,esp//ebp指向先前的ebp,到达图4的状态

008A13F3  sub         esp,0E4h//为局部变量分配0E4字节的空间,到达图5的状态

008A13F9  push        ebx//压入EBX

008A13FA  push        esi//压入ESI

008A13FB  push        edi//压入EDI,到达图7的状态

008A13FC  lea         edi,[ebp-0E4h]//以下4行把局部变量区初始化为每个字节都等于cch

008A1402  mov         ecx,39h

008A1407  mov         eax,0CCCCCCCCh

008A140C  rep stos    dword ptr es:[edi]

......//省略代码执行N行

......

008A1436  pop         edi//恢复EDI

008A1437  pop         esi//恢复ESI

008A1438  pop         ebx//恢复EBX

008A1439  add         esp,0E4h//回收局部变量地址空间

008A143F  cmp         ebp,esp//以下3行为Runtime Checking,检查ESP和EBP是否一致

008A1441  call        @ILT+330(__RTC_CheckEsp) (8A114Fh)

008A1446  mov         esp,ebp

008A1448  pop         ebp//恢复EBP

008A1449  ret//弹出函数返回地址,跳转到函数返回地址执行                                            //(__cdecl调用约定,Callee未清理参数)

参考

Debug Tutorial Part 2: The Stack

Intel汇编语言程序设计(第四版) 第8章

查看原文

赞 0 收藏 0 评论 0

轻松学编程 发布了文章 · 2020-11-26

不要再满世界搜linux命令了,我给你整理到一块了

前言

做为一位后端开发,怎能不会点Linux命令?总结了一套非常实用的Linux命令(基于CentOS 7.6),希望对大家有所帮助!

推荐一下自己的linuxC/C++交流群:973961276!整理了一些个人觉得比较好的学习书籍、视频资料以及大厂面经视频共享在群文件里面,有需要的小伙伴可以自行添加哦!~

系统服务管理

systemctl

systemctl命令是 servicechkconfig命令的组合体,可用于管理系统。
  • 输出系统中各个服务的状态:
systemctl list-units --type=service 

 

  • 查看服务的运行状态:
systemctl status firewalld 

  • 关闭服务:
systemctl stop firewalld 

  • 启动服务:
systemctl start firewalld 

  • 重新启动服务(不管当前服务是启动还是关闭):
systemctl restart firewalld 
  • 重新载入配置信息而不中断服务:
systemctl reload firewalld 
  • 禁止服务开机自启动:
systemctl disable firewalld 

  • 设置服务开机自启动:
systemctl enable firewalld 

文件管理

ls

列出指定目录下的所有文件,列出/目录下的文件:

ls -l / 

pwd

获取目前所在工作目录的绝对路径:

cd

改变当前工作目录:

cd /usr/local 

date

显示或修改系统时间与日期;

date '+%Y-%m-%d %H:%M:%S' 

passwd

用于设置用户密码:

passwd root 

su

改变用户身份(切换到超级用户):

su - 

clear

用于清除屏幕信息

man

显示指定命令的帮助信息:

man ls 

who

  • 查询系统处于什么运行级别:
who -r 
  • 显示目前登录到系统的用户:
who -buT 

free

显示系统内存状态(单位MB):

free -m 

ps

  • 显示系统进程运行动态:
ps -ef 
  • 查看sshd进程的运行动态:
ps -ef | grep sshd 

都看到这了还不给我进群973961276来学习???

top

查看即时活跃的进程,类似Windows的任务管理器。

mkdir

创建目录:

more

用于分页查看文件,例如每页10行查看boot.log文件:

more -c -10 /var/log/boot.log 

cat

用于查看文件,例如查看Linux启动日志文件文件,并标明行号:

cat -Ab /var/log/boot.log 

touch

用于创建文件,例如创建text.txt文件:

touch text.txt 

rm

  • 删除文件:
rm text.txt 
  • 强制删除某个目录及其子目录:
rm -rf testdir/ 

cp

用于拷贝文件,例如将test1目录复制到test2目录

cp -r /mydata/tes1 /mydata/test2 

mv

用于移动或覆盖文件:

mv text.txt text2.txt 

压缩与解压

tar

  • /etc文件夹中的文件归档到文件etc.tar(并不会进行压缩):
tar -cvf /mydata/etc.tar /etc 
  • gzip压缩文件夹/etc中的文件到文件etc.tar.gz
tar -zcvf /mydata/etc.tar.gz /etc 
  • bzip2压缩文件夹/etc到文件/etc.tar.bz2
tar -jcvf /mydata/etc.tar.bz2 /etc 
  • 分页查看压缩包中内容(gzip):
tar -ztvf /mydata/etc.tar.gz |more -c -10 
  • 解压文件到当前目录(gzip):
tar -zxvf /mydata/etc.tar.gz 
  • 解压文件到指定目录(gzip):
tar -zxvf /mydata/etc.tar.gz -C /mydata/etc 

磁盘和网络管理

df

查看磁盘空间占用情况:

df -hT 

dh

查看当前目录下的文件及文件夹所占大小:

du -h --max-depth=1 ./* 

ifconfig

显示当前网络接口状态:

netstat

  • 查看当前路由信息:
netstat -rn 
  • 查看所有有效TCP连接:
netstat -an 
  • 查看系统中启动的监听服务:
netstat -tulnp 
  • 查看处于连接状态的系统资源信息:
netstat -atunp 

wget

从网络上下载文件

文件上传下载

  • 安装上传下载工具lrzsz
yum install -y lrzsz 
  • 上传文件,输入以下命令XShell会弹出文件上传框;
rz 
  • 下载文件,输入以下命令XShell会弹出文件保存框;
sz fileName 

软件的安装与管理

rpm

RPM是 Red-Hat Package Manager的缩写,一种Linux下通用的软件包管理方式,可用于安装和管理.rpm结尾的软件包。
  • 安装软件包:
rpm -ivh nginx-1.12.2-2.el7.x86_64.rpm 
  • 模糊搜索软件包:
rpm -qa | grep nginx 
  • 精确查找软件包:
rpm -qa nginx 
  • 查询软件包的安装路径:
rpm -ql nginx-1.12.2-2.el7.x86_64 
  • 查看软件包的概要信息:
rpm -qi nginx-1.12.2-2.el7.x86_64 
  • 验证软件包内容和安装文件是否一致:
rpm -V nginx-1.12.2-2.el7.x86_64 
  • 更新软件包:
rpm -Uvh nginx-1.12.2-2.el7.x86_64 
  • 删除软件包:
rpm -e nginx-1.12.2-2.el7.x86_64 

yum

Yum是 Yellow dog Updater, Modified的缩写,能够在线自动下载RPM包并安装,可以自动处理依赖性关系,并且一次安装所有依赖的软件包,非常方便!
  • 安装软件包:
yum install nginx 
  • 检查可以更新的软件包:
yum check-update 
  • 更新指定的软件包:
yum update nginx 
  • 在资源库中查找软件包信息:
yum info nginx* 
  • 列出已经安装的所有软件包:
yum info installed 
  • 列出软件包名称:
yum list nginx* 
  • 模糊搜索软件包:
yum search nginx

好了,文章就写到这里吧,有什么疑问可以评论区留言,觉得写的还不错的朋友点个赞给个关注那就再好不过了!

查看原文

赞 0 收藏 0 评论 0

轻松学编程 发布了文章 · 2020-11-24

很认真的谈一谈程序员的自我修养

首先要谈的是,今天的话题所聊的程序员包含哪些人?

在中国,写程序,不仅仅是一种兴趣,更多的时候,还是一种普通职业和谋生工具

大公司有厉害的程序员,优秀的架构师,但大量的小公司也有很多普通的程序员。在我这些年的工作经历中,也越来越深刻的感受到普通程序员的影响和力量。对于高阶程序员,所谓八仙过海各有神通,各有各的成就,各有各的修养,但程序员在达成较高的水平之前,有一些“自我修养”,是最基础的,是普世的。

所以今天的话题面向的程序员,就是所有的正在写代码或者曾经写过代码的程序员,也包括广义上的程序员,例如项目经理、架构师等等。

做任何事都是有明确目的,那么

再谈一谈,程序员提高自我修养是为了什么?

程序写的好有人崇拜,有妹子喜欢?还是到博客、论坛、社区发表文章进行分享获得成就?我想这是少数人的追求,也是更高的追求,在这之前

我认为,在中国,程序员提高自我修养的目的,是为了

1、更好的融入工作,减少困难,增加成就

2、稳步的提升能力,提高收入,达成财务自由

2、站在更高的层面看待自己的学习和工作,树立更加适合的人生观价值观,家庭幸福,生活愉快

说的更通俗一点,就是用更加合理的方式和方法,赚取到更多的收入

推荐一下自己的linuxC/C++交流群:973961276!整理了一些个人觉得比较好的学习书籍、视频资料以及大厂面经视频共享在群文件里面,有需要的小伙伴可以自行添加哦!~

说了这么多废话,进入正题

何为程序员的自我修养?

正面论述很难说清楚,反向描述可能更通俗易懂一些,自我修养的对立面是“没有修养”,先说一说在这么多年的工作、学习、生活中,遇到的一些我认为“没有修养”的程序员形态

1、程序员小张遇到了一个开发问题,很着急,想到了有几个群,于是到群里发了他的问题,坐等回答,发现没有人回答,就直接对话群主的QQ,群主也不回答,于是小张就搜索,突然搜到某个社区有个帖子讲解了相关话题,他看完就给博主留言,我的邮箱是:XXXXX@qq.com,麻烦博主把源码发给我一下,谢谢。

2、程序员小张进公司3个月了,老板布置了很多任务,他觉得老板很没人性,工资给的不高,加班也不给钱,于是在写代码的时候能省就省,客户反馈有问题也不主动解决,敷衍为主,又过了一个月,跳槽了。

3、程序员小张正在写一个功能模块,需要进行某种加密,到百度搜到了一个编码模块,看不明白具体写了什么,但是放到程序里刚好适用,于是就这么原封不动放进去了。

4、程序员小张要对某个功能进行研发,项目经理对他说,这个功能应该能搜索到,你去搜搜看,小张就在百度搜啊搜,一天过去了啥都没找到,项目经理来到小张身边坐下,换了个关键词,1分钟就搜到了解决方案。

5、程序员小张学.NET已经工作3年了,工资还是10000,和公司提涨工资也没答应,想跳槽又犹豫,这时某个前辈对他说,你去看书吧,多看一些书,例如 《Visual C# 从入门到精通》,《CLR via C#》《Javascript权威指南》等等,于是小张买回来了, 随手翻了翻发现有些东西是他已经会的,有些看不懂的好像又用不到,而且书这么厚,要不要浪费时间去看呢?小张就这样反复纠结了半年,依然每天上班工作,下班LOL,偶尔还抱怨一下工资低。

6、程序员小张到了一家新公司,在做一个项目实现某个功能时,想起来以前做过这样的功能,可是竟想不起怎么实现了,于是就到自己电脑上找文档,找了好久也没找到,只好放弃,最后又折腾了2天,终于还是把这个功能给实现了。

7、程序员小张某天非常不高兴,因为他的项目经理和项目组的产品人员又变更需求了,新的需求又要对整个结构进行大的调整,小张很郁闷,到一个QQ里发泄情绪,说了这个事,于是立马,QQ群里面炸开锅了,程序员小李说,对,产品就是狗日的!程序员小王说,对,他妈的项目经理整天高枕无忧,就知道压榨开发人员!程序员小孙说,是的是的,我上一家公司也是这样,压榨程序员,幸好我走了。就这样,在一片骂声中,几个程序员心情舒畅了,小张开心的去玩王者荣耀去了。

我想,有些人可能已经明白我要说什么,有些人可能还不明白,具体的话我也说不出来,只能用一句话来概括就是:

在编写代码的过程中,善于学习、掌握方法、勤加思考、勤奋努力、持之以恒,长此以往,在编程中,你会发现不一样的自己。

以上这些还是比较抽象,那么

提升自我修养的具体方法有哪些?

程序员具体如何达成“较高的修养”,每个人各有自己的办法,我无法说到很细,就和如何提高做人修养一样,一句两句话是说不清楚的,但是有些说法也通俗易懂,比如一个小孩,有教育良好的父母,父母彬彬有礼,小孩从小开始接受正规教育,小学、初中、高中、大学,然后文化课程和社会实践良好,那么这个小孩最终的做人修养,一定比没有经历过这个过程的小孩更好一些。

同样的,写程序也是如是,下面我就讲一些最基本的、最浅显易懂的学习方法和道理,我把它叫做:

程序员基础的基础

一个好的开发人员,应该能够全面、高效、严谨的去处理任何软件程序和业务问题,成为一个好的开发,是一个很有意思的话题,不过无论这个话题如何开展,基础两个字必不可少,虽然代码量是衡量开发能力的重要指标,但仅能够熟练的进行代码编写是不够的,更要能深刻的理解技术原理和业务逻辑,扎实的个人基础和技术基础往往会促进代码的编写,更游刃有余的解决问题。

下面说的一些基础,可能绝大部分开发人员都不会在意甚至忽略,但恰恰这些才是开发大厦的基石。

1、科学基础

成为开发人员的过程不尽相同,有的是科班出身,有的是兴趣爱好,还有的是专业机构的培训,在这个过程中,可能全面或者零散甚至没有学习过计算机基础学科,但无论是哪一种,想要成为更高层次的开发人员,写出更高质量的代码,计算机基础学科的学习,是非常非常非常(重要的事情说三遍)重要的。具体的来说,基础学科在实践应用中,有如下几门是一定需要的,按照学习顺序排列如下

1)数据结构

数据结构课程通俗的说就是告诉你如何用最基本的语言类型、变量,关键词语句等,去处理各式各样的逻辑问题,我们称之为算法,而日常编程中的各种问题,例如排序、文件夹遍历操作、数据库查询等,都可以在数据结构课程中,找到对应的数学原型。数据结构课程的理解能力,也是一个人数学能力的体现,数据结构学习的好坏,是程序员水平差异的一个重要分水岭,对于这一块内容的学习,有如下建议:使用VB、C、C++、Pascal等语言,买一本相关语言数据结构与算法的书,或者在网上下载相关的PDF电子书,完整的学习一边,并将书本中的所有案例亲自编写运行调试一遍,当能够领悟到某些日常编程中常见手法源于某些数据结构和算法时,就基本达到了学习效果。

2)操作系统

所有编程语言的开发以及应用的运行,都基于操作系统,桌面编程中的大部分场景包括内存、进程、文件系统、网络通讯、用户界面等,都源于操作系统的定义和概念,完整的了解操作系统的起源和组成以及运行逻辑,对多线程、复杂界面、文件管理以及一些难以正常理解编程思路等开发中遇到的场景,有非常大的帮助,不仅帮助理解,也能掌握更多有效的程序写法。具体可以买一本操作系统的书或者下载相关PDF电子书,完整的浏览一遍,做到能够结合实际编程场景来看待操作系统原理,就基本达到了学习效果。

3)数据库

传统的关系型数据库,入门简单,深入却难,往往开发人员能够较快的掌握增删改查、视图、索引、存储过程等基本数据库操作,却在编写复杂查询、设计主外键、优化字段、去除冗余等时,出现只会依葫芦画瓢却不能自主思考扩展的状况。究其原因还是没能了解关系数据库的根本原理,而数据库这一门课程,系统的阐述了关系型数据库的来龙去脉,了解其中的数学原理或逻辑基础所在,对提升数据库编程水平有质的影响。建议也是买一本数据库的相关书籍或者下载PDF电子书,能够把熟练的把第一范式、第二范式等数据库课程的基本知识点与数据库编程场景建立起关联,也基本达到了学习效果。

4)编译原理

编译原理是编程语言以及各类语言编译器的科学基础,可以说编译原理创造了世界上的几乎所有的IT应用,学习编译原理的基础是数据结构和算法,因此编译原理的学习要花费更多的时间和精力,由于现代高级编程语言的编译器,在代码优化、资源优化方面已经做的足够智能,因此,编译原理的学习对实战的影响越来越小,但是正所谓本盛末荣,如果认为自己对数据结构和算法的学习达到了一个较高的水平和状态,可以在编译原理学习上进一步深入,最终把自己和普通程序员拉开更大的差距。

2、英语能力

英语的天然特性和字母长度还有学科发展的历史因素,决定了编程语言一定是基于英语的,在编程过程中,从语言的关键词到文档的内容又或是搜索引擎的搜索结果,都不可避免的会遇到英文。大部分编程人员,都具备英语四级左右的英文基础,却由于非专业以及工作环境原因,逐渐疏远甚至完全淡忘了英语。而实际操作中,大部分编程语言资料都是英文,在线编程问答内容也是英文,因此,很有必要把英语能力重新恢复到一个不用太高但行之有效的水平,达到如下效果:

1)对自己所使用语言,每一个关键词都知道具体的英文翻译、逻辑含义以及读音。

2)对于自己使用语言所涉及到的相关方法、类库、框架、工具等,能知道其中每一个方法、过程以及参数关键词等的英文翻译、逻辑含义以及读音。

3)对常见的编程逻辑和核心关键词,能够用英文组织问题的描述,最简答的也行,只要能被搜索引擎读懂就可以。比如如何在C#中把整形转换为字符串类型这个问题,最简易的英文描述就是 C# Integer Covert To String。

4)在自己技术知识范围内的任何的英文的技术手册、文档、文章或是问题描述,能够读懂8成的内容含义,能够读懂完整的技术含义。

3、搜索方法

任何一个开发人员,都应当具备搜索能力,甚至是一定要具备搜索能力,搜索引擎的宝藏,是无穷无尽的,同样具备搜索意识的不同程序员,却因为搜索技巧的差异最终在程序开发质量、项目实施效率、甚至是工程产品质量上出现数倍的差异,因此,掌握高效、先进、灵活的搜索方法和技巧,是非常非常非常(重要的事情说三遍)有用的。其中主要的方法介绍如下:

1)搜索源选择

  • 虽然英文的编程资料更为准确高效,但中文的编程资料数量上却占优,因此遇到问题第一搜索选择还是百度
  • 谷歌对于专业中文词汇的处理能力有时候甚至比百度还要强,而且谷歌能搜出大量的英文资源,因此谷歌也是首选之一,但是由于谷歌被封锁,因此需要进行VPN、SSH等翻墙操作,或者在百度搜索“谷歌镜像”关键词,通过谷歌的镜像网站进行访问。
  • 除了搜索引擎,专业的技术网站、论坛、社区也是非常直接有效的搜索源,比如国外的StackOverFlow网站,国内的Cnblogs博客园、OSChina开源中国等,都具备搜索功能,将问题关键词输入其中,也许也会很快的得到相关答案。
  • 对于QQ群,建议不要使用,除非QQ群主或者成员是非常闲或者非常非常热心的人,否则在QQ群询问技术问题,是非常低效率的搜索方式。

2)关键词构造

搜索关键词的构造,直接影响搜索效率和正确结果的过滤,没有什么特别的技巧,关键在于搜索积累,但是总体遵循的原则是,准确和简洁,比如当出现一个描述,如何用C#对XML进行序列化和反序列化,非常愚蠢的关键词构造就是“如何用C#对XML进行序列化和反序列化”,而正确高效的关键词则是“C# XML 序列化 反序列化”,或者在谷歌里面搜索则是“C# XML Serialization”。在平时的编程中,一定要注意相关方法和经验的积累

3)联想搜索

联想搜索,不属于搜索引擎的范畴,却是在搜索中很有用的高级技巧,举一个通俗的例子,比如想使用C#,利用某个.NET类处理一种HTTP通讯,但是一直搜索不到完美的结果,不过换个思路,考虑到http://VB.NET也是.NET体系,和C#完全相通,那么也可以试着用http://VB.NET关键词进行搜索,搜索到完美代码后再临摹成C#代码。这样的联想搜索,不仅能够帮助搜索正确结果,也是对大脑思维的训练,值得多多尝试。

4)资源搜索

开源的框架、产品、工具、控件等开发辅助类东西越来越多,稳健性和迭代性越来越强,去寻找一款成熟的工具或者插件,也成为了大量开发者的必备方法和技能,而如何高效的搜索出想要的资源,也成为了一门学问,其核心方法就在于知晓资源网站的地址,常见的例如有开源中国、Github、CSDN下载、pudn等。资源类网站需要平时多积累,到用到的时候会非常关键。

4、思维模式

开发人员,一定要养成业务思维的模式,所谓的业务思维,就是在做任何一个项目的时候,写任何代码前,需要对项目本身的业务概念和业务逻辑甚至业务流程都要有一个全面的学习和理解,这虽然不是一个项目的强制要求,却是一个很好的开发习惯,无论自己的觉得是开发者还是测试员又或是技术总监,掌握了业务原理,才能够更好的设计或阅读项目的数据结构和流程结构。程序员的思维往往和用户或者客户是不一致的,摆脱技术思维模式,习惯于用业务思维解决问题的程序员,不一定最优秀,但一定是一个很容易沟通的程序员

5、工作与编程习惯

有的人说爱干净浪费时间,所以不修边幅,但归根结底这还是习惯问题,当养成清洁卫生的习惯并使之成为生活惯性时,往往就不会耗费更多的时间,反而显得干净干练。写程序同样如是,有一些编程习惯,看似不足为道,看似浪费时间,可是如果坚持下去,最终都能收到意想不到的奇效。下面列举一些特别重要的习惯。

1)快捷键的使用

无论是使用Windows、Linux操作系统,还是在IDE中,快捷键都是系统本身的标配,事实上,Ctrl+C、V这样的操作,大部分人都能尝到在节省时间上的甜头,把这个概念进一步扩散,如果在IDE中编写代码,除了代码本身,将其余所有的鼠标操作、键盘定位操作,都用快捷键来代替的话,在时间上将会有数量级的节省,然而看上去这么好的事情,真正坚持去执行并形成习惯的人屈指可数,因此,在初期的改变习惯,记住快捷键,会是一个长期的过程,需要不断的坚持。

2)代码注释

一个开发人员随着年龄和经验的增长,所参与的项目,再也不是靠一个人或者几个人就能完成的。系统的重构、代码的重构、工作的交接、对新进人员的培训等等类似的事情,会越来越多的遇到,这些事情无一例外都会把已经写过的代码重新或者重复阅读,如果在初始编写代码时,就做到完整、清晰明了的代码注释,对后续工作会有巨大的帮助。不仅提高工作效率,还能增强合作好感。事实上,就算只是自己看自己的代码,如果有注释,也能加深印象,缩短代码查找时间。因此,任何开发人员,都应该养成良好的代码注释习惯。

优秀的代码注释应该能做到:

  • 每一个函数、每一个属性甚至是变量的划分,都可以找到对应的解释。
  • 多使用越来越被IDE支持的XML注释方式,不仅有注释文字,更有详细的参数描述。
  • 对程序结构、模块、组成部分划分等也加以注释

3)命名规则

具备一定规模的软件公司,在代码编写上都有一套自己的命名规则,涵盖项目、模块、函数、变量等等,标准化命名的好处不言而喻,然而被动、被迫去遵守命名规则和主动习惯于使用命名规则是完全不一样的。一个优秀的开发人员,应当发自内心的希望各种代码命名都是有规则的,易读的,而不是纠结于命名规则会增加码字长度。

4)不将就的编程逻辑

所谓不将就的编程逻辑,其对立面就是不讲究的编程逻辑,不讲究的编程,不仅是一种很坏的编程习惯,也体现了低下的生活品质,很多开发人员,因为个人习惯、赶工期、客户要求不高等多种原因,在编程时特别随意,体现在比如为了实现某个功能,百度出一段代码,直接套用,10行的代码只理解8行,有两行看不懂也放到程序里去使用,很多这样的小细节,就好比在项目中埋下了无数的定时炸弹,不仅有很大概率形成返工,更是为项目埋下了风险。编程人员,应当有担当有态度,养成不将就的编程逻辑,不勉强自己,也不轻视程序。

5)数据备份

误删、误操作、电脑断电、文件遗失等等状况是每一个开发按人员都可能遇到的问题,如果不希望辛勤的劳作被浪费,不希望偶然的意外影响工作,那做好备份是必不可少的,在较大规模的公司,会有完整的源代码管理以及信息安全防护,而无论是在大公司工作,还是身处较小公司或者在实现个人代码价值时,都要做好代码和文档的数据备份,备份方式的选择灵活多样,有使用在线的CVS、SVN、TFS、Git源代码管理,也可以手工拷贝文件至云空间或者本地硬盘,甚至可以在个人电脑上组成RAID磁盘阵列等等,养成周期性、规律性的备份习惯。

6)邮件工作方式

沟通是进步的源泉,如果说开发小组的热烈讨论是性格和激情的体现,那邮件的工作方式也是另一种稳重和高效。无论是公司层面的工作沟通,还是开发小组的问题交流,邮件的作用包括问题正规化描述、工作留档留痕、工作流程流转、责任分工明确等等,习惯于将重大问题、重要事项通过邮件的方式与同事、主管等进行沟通,将会非常有助于团队协作。

以上这些方法,是我这么多年来的感受和体会,也给了我很大的帮助,希望也能够帮助到大家,不能说一定可以“提升修养“,但也是”提升修养”的有效方式。

最后还想再说一说坚持的力量

分享一个真实的小故事,公司有两个开发人员,1个做.NET好多年了,但是很油滑,做事能省就省,抓到可以偷懒的机会就偷懒,让他学点新知识新方法总是自以为是觉得自己都会;还有1个毫无.NET基础,一直做低级语言开发,从15年才开始学习.NET和Web前端,但是做事很积极,几乎每天都自己抽空学习,遇到不懂的都琢磨清楚,遇到不会的场景就上网或者找人寻求帮助,项目结束后还反复思考有什么地方可以改进。从15年到现在,短短1年,这两个人的发展已经是天壤之别,工资差距也越来越大,后者已经能够独自操盘中小型软件外包项目,而前者还在混着日子,以后他们各自的发展也完全可以预见。

我想说的是,本篇里面分享的一些道理和方法,都是通俗易懂的,就和常听到的例如101%和99%的365次方的故事、1万小时的道理等等一样,但真正去认真思考并实践的屈指可数,也许,坚持才是程序员最大的修养,和各位共勉!

查看原文

赞 0 收藏 0 评论 0

认证与成就

  • 获得 7 次点赞
  • 获得 3 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 3 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2020-10-30
个人主页被 791 人浏览