艾莉亚的猫 Time is limited, To be a better man

故事三则

《史记·鹖冠子》

魏文王问曰:“子昆弟三人其孰最善为医?” 扁鹊曰:“长兄最善,中兄次之,扁鹊最为下。” 魏文侯曰:“可得闻邪?” 扁鹊曰:“长兄於病视神,未有形而除之,故名不出於家。中兄治病,其在毫毛,故名不出於闾。若扁鹊者,鑱血脉,投毒药,副肌肤,闲而名出闻於诸侯。”

启发: 研发要做好内测,不要等到出现bug了,充当救火队员而被人夸奖。


《韩非子·内储说上》

齐宣王使人吹竽,必三百人。南郭处士请为王吹竽,宣王说之,廪食以数百人。宣王死,湣王立,好一一听之,处士逃。

启发: 要有真才实学。


邹忌讽齐王纳谏-程序员版

马农毕业八年多,而且手指灵活思维敏捷。有一天早晨他来到公司,穿着体恤,留着胡茬,对他的老板说:“我与清河的徐工程师相比,谁更会写代码呢?”他的老板说:“您太会写了,徐工怎么能比得上您呢!”清河的徐工,是 BAT 的大牛。马农不相信自己会比徐工会写代码,于是又问他的小弟说:“我和徐工相比,谁更会写代码?”小弟说:“徐工怎么能比得上您呢?”第二天,有 PM 从隔壁来拜访,马农和他坐着谈话。马农问 PM 道:“我和徐工相比,谁更会写代码?” PM 说:“徐工不如您会写代码啊。”又过了一天,徐工前来拜访,马农通过和他交流,自己技术觉得不如他;再上 github 一看,更觉得远远比不上人家。晚上,他躺在床上想这件事,说:“我的老板认为我会写代码,是偏爱我;我的小弟认为我会写代码,是惧怕我; PM 认为我会写代码,有求于我。”

启发: 正确认识自己,不要被别人的赞美蒙蔽了双眼。

找出链表中间元素

单链表最大的特点就是“不走回头路”,不能实现随机存取。如果我们想要找一个数组a的中间元素,直接a[len/2]就可以了,但是链表不行,因为只有a[len/2 - 1] 知道a[len/2],其它节点不知道。因此,如果按照数组的做法依样画葫芦,要找到链表的中点,我们需要做两步(1)知道链表有多长(2)从头结点开始顺序遍历到链表长度的一半的位置。这就需要1.5n(n为链表的长度)的时间复杂度了。

有没有更好的办法呢?答案是有的。想法很简单:两个人赛跑,如果A的速度是B的两倍的话,当A到终点的时候,B应该刚到中点。这只需要遍历一遍链表就行了,还不用计算链表的长度。

大小端判断

大端模式,是指数据的高位,保存在内存的低地址中,而数据的低位,保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放;

小端模式,是指数据的高位保存在内存的高地址中,而数 据的低位保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低,和我们的逻辑方法一致。

CPU大小端判断函数,C/C++代码

#include <iostream>
using namespace std;
int judgeLittleEndian()
{
  union{
    int i;
    char c;
  }u;
  u.i = 1;
  return u.c;
}

int main(int argc, char *argv[])
{
  if(judgeLittleEndian())
    cout<<"\tLittle Endian!"<<endl;
  else
    cout<<"\tBig Endian!"<<endl;

  return 0;
}

引用计数

最近在看《提高c++性能的编程技术》一书,第十二章提到引用计数,要开始学习新知识了~

学习之前先抄录书中一段精彩的言论

于自然界,熵的原理亦可用于软件工程之中–所有实体都会随着时间的消逝而消失。一个软件项目最初可能源起于某个设计清晰、实现简单的小型原型系统。只包含基本功能的原型系统,要使它们满足上市需求,往往都要经历急速扩展的过程。这通常是为了满足客户对于新功能(有时是难解的)的突发性需求以及对原有瑕疵的改进需要。新功能的开发加上旧错误的修补会对原先清晰明了的设计造成重大破坏。随着时间的推移,设计和实现的清晰性也会随着代码维护及频繁的发布周期而不复存在,软件不可避免的变得紊乱不堪,区分软件好坏的唯一标准就是衰退率。


引用计数是对共享的动态内存的一种管理方法,STL库的string就是用到了引用计数的方法

1. 概念

引用计数用来记录当前有多少指针指向同一块动态分配的内存。当有指针指向这块内存时,计数器加1;当指向此内存的指针销毁时,计数器减1。当引用计数为0时,表示此块内存没有被任何指针指向,此块被共享的动态内存才能被释放。

2. STL库的string利用引用计数共享内存

#include <stdio.h>
#include <string>

using namespace std;

int main()
{
	string str1("aaa");
	printf("the address of str1 is: %x\n",str1.c_str());
	string str2("bbb");
	printf("the address of str2 is: %x\n",str2.c_str());
	string str3("aaa");
	printf("the address of str3 is: %x\n",str3.c_str());
	string str4 = str1;
	printf("the address of str4 is: %x\n",str4.c_str());
	str1 = str2;
	printf("the address of str1 is: %x\n",str1.c_str());
	str2[2] = 'a';
	printf("the address of str2 is: %x\n",str2.c_str());
}

结果如下:

the address of str1 is: 1816028

the address of str2 is: 1816058

the address of str3 is: 1816088 //str3和str1并不共享一块内存

the address of str4 is: 1816028 //str4和str1共享一块内存,所以初始化(string(const char*))和赋值(operator=(const string&))是不一样的

the address of str1 is: 1816058 //str1和str2共享一块内存

the address of str2 is: 18160b8 //写str2,因为引用计数的关系,要分配新的内存给str2

STL string共享内存的原则(copy on write):

利用初始化或赋值生成多个内容相同的string,当没有对这些string进行写操作时,它们会共享同一块内存。当有写操作出现时,才会有新的内存重新分配。

3. 利用引用计数实现简易String

问题:我们对String类的内存进行计数,但引用计数这个变量是类的什么类型的成员变量?

解答:

1) 普通私有成员变量,每个类都有一个独立的计数器变量,在新的类对象拷贝构造和赋值的时候需要进入原来的类对象中去获取这个计数器,再进行修改,在同一个类对象之间缺乏通用性。

2) static变量,类的所有对象公用一个计数器变量。字符串”aaaa”和”bbbb”使用不同的内存,它们应该分别有对应自己内存的计数器变量。

结论,对每一块内存分配一个变量,可以在动态分配字符串内存是多分配一个字节,将计数器的值放在第一个字节中;也可以在动态分配内存前先动态分配一个int型的内存来存放这个计数器。


在《More Effective C++》的Item 29中有关于引用计数的实现,可供参考

2014年阅读书单

深入理解计算机系统

数码单反摄影轻松入门

写给大家看的PPT设计书

计算机算法设计与分析

啊哈!算法

深入理解C指针

C语言接口与实现

你必须知道的495个C语言问题

提高C++性能的编程技术

Essential C++中文版

黄金时代

沉默的大多数

学习vi和Vim编辑器