精华内容
下载资源
问答
  • //两种实例化方式区别 ... //将字符串的匿名对象设置了一个名字 ///会在内存里开辟一块堆内存 ///并且由一块栈内存指向该堆内存 String str = "hello"; ////// /// //你会发现所有采用直接赋...
    //两种实例化方式区别
    //
    
    public class xian{
    	public static void main(String[] args) {
    		//直至赋值
    		//将字符串的匿名对象设置了一个名字
    		///会在内存里开辟一块堆内存
    		///并且由一块栈内存指向该堆内存
    		String str = "hello";
    
    		//
    		///
    		//你会发现所有采用直接赋值的String类对象的所占内存地址相同
    		//栈内存       堆内存
    		//a    --->    A
    		//b    --->指向第一个
    		//c    --->指向第一个
    		//d    --->    B          
    		String a = "A";
    		String b = "A";
    		String c = "A";
    		String d = "B";
    		System.out.println(a == b);//true
    		System.out.println(a == c);//true
    		System.out.println(b == c);//true
    		System.out.println(a == d);//false
    
    		//我们解释他必须理解什么是共享设计模式
    		//比如你去用钳子修电线
    		//修完之后你肯定不会扔了,而是下次再用
    		//但是你的老婆要拔牙
    		//然后你就把钳子给你了老婆
    		//老婆发现这是一把钳子于是开始拔牙
    		//这叫共享
    		//
    		//在JVM的底层会存在一个对象池
    		//不仅保存String对象S
    		//当你定义了一个String类对象时
    		//会将此字符串对象所使用的匿名对象保存在池中
    		//之后你有设置了其它String类对象时,并设置同样的内容
    		//那么不会开辟新的内存空间
    		//而是使用已有的对象进行引用分配
    		//从而继续使用
    
    		//上述采用直接赋值
    		//接下来哦我们讲采用构造方法赋值的原理
    		//一旦使用了关键字new就要开启新的堆内存
    		String s = new String ("hello");
    		//上行代码的new指向第二行,hello指向第一行
    		//栈内存                              堆内存
    		//s    ---->指向下一个               "hello"
    		//                                   "hello"
    		//那第一个hello就没用了       
    		//所以采用这种方式那么会开启2块堆内存空间
    		//并且其中有一块堆内存空间成为辣鸡空间
    		//现在考虑构造这种方法能否入池
    		//
    		//
    		System.out.println("-------------");
    		String stra = new String("hello");
    		String strb = "hello";
    		System.out.println(stra == strb);//false
    
    		//其内容不会保存在对象池中
    		//因为使用了new开辟了新内存
    		//如果希望开辟的新内存也可以进行对象池的保存
    		//那么可以采用String定义的手工入池方法
    		//public String intern()
    			
    		System.out.println("-------------");
    		String stra1 = new String("hello").intern();
    		String strb1 = "hello";
    		System.out.println(stra1 == strb1);//true
    
    		//所以总结第一:只开辟一块内存空间,并会保存到池中
    		//第二:开启两块内存空间,不会自动入池,但可以手工入池
    		//所以我们建议直接赋值
    		
    	}
    }
    
    展开全文
  • Python day3 函数

    2017-09-23 01:34:00
    函数对象函数嵌套、名称空间与作用域、装饰器 一 文件处理 1. 文件操作 (1)介绍 计算机系统分为:计算机硬件,操作系统,应用程序三部分。 我们用python或其他语言编写的应用程序若想要把数据永久保存...

    一 文件处理

    二 函数知识体系

    三 函数基础

    四 函数对象、函数嵌套、名称空间与作用域、装饰器

    一 文件处理

     1. 文件操作

      (1)介绍

    计算机系统分为:计算机硬件,操作系统,应用程序三部分。

    我们用python或其他语言编写的应用程序若想要把数据永久保存下来,必须要保存于硬盘中,这就涉及到应用程序要操作硬件,众所周知,应用程序是无法直接操作硬件的,这就用到了操作系统。操作系统把复杂的硬件操作封装成简单的接口给用户/应用程序使用,其中文件就是操作系统提供给应用程序来操作硬盘虚拟概念,用户或应用程序通过操作文件,可以将自己的数据永久保存下来。

    有了文件的概念,我们无需再去考虑操作硬盘的细节,只需要关注操作文件的流程:

    #1. 打开文件,得到文件句柄并赋值给一个变量
    #2. 通过句柄对文件进行操作
    #3. 关闭文件
    操作文件的流程

      (2)在Python中

    #1. 打开文件,得到文件句柄并赋值给一个变量
    f=open('a.txt','r',encoding='utf-8') #默认打开模式就为r
    
    #2. 通过句柄对文件进行操作
    data=f.read()
    
    #3. 关闭文件
    f.close()
    在Python中文件操作流程

     (3) f=open('a.txt','r')的过程分析

    #1、由应用程序向操作系统发起系统调用open(...)
    
    #2、操作系统打开该文件,并返回一个文件句柄给应用程序
    
    #3、应用程序将文件句柄赋值给变量f
    f=open('a.txt','r')的过程分析

    (4)注意

    #强调第一点:
    打开一个文件包含两部分资源:操作系统级打开的文件+应用程序的变量。在操作完毕一个文件时,必须把与该文件的这两部分资源一个不落地回收,回收方法为:
    1、f.close() #回收操作系统级打开的文件
    2、del f #回收应用程序级的变量
    
    其中del f一定要发生在f.close()之后,否则就会导致操作系统打开的文件还没有关闭,白白占用资源,
    而python自动的垃圾回收机制决定了我们无需考虑del f,这就要求我们,在操作完毕文件后,一定要记住f.close()
    
    虽然我这么说,但是很多同学还是会很不要脸地忘记f.close(),对于这些不长脑子的同学,我们推荐傻瓜式操作方式:使用with关键字来帮我们管理上下文
    with open('a.txt','w') as f:
        pass
     
    with open('a.txt','r') as read_f,open('b.txt','w') as write_f:
        data=read_f.read()
        write_f.write(data)
    注意1.操作完成文件回收
    f=open(...)是由操作系统打开文件,那么如果我们没有为open指定编码,那么打开文件的默认编码很明显是操作系统说了算了,操作系统会用自己的默认编码去打开文件,在windows下是gbk,在linux下是utf-8。
    这就用到了上节课讲的字符编码的知识:若要保证不乱码,文件以什么方式存的,就要以什么方式打开。
    
    f=open('a.txt','r',encoding='utf-8')
    注意2.文件编码

     (5)python2中的file与open

    #首先在python3中操作文件只有一种选择,那就是open()
    
    #而在python2中则有两种方式:file()与open()
    两者都能够打开文件,对文件进行操作,也具有相似的用法和参数,但是,这两种文件打开方式有本质的区别,file为文件类,用file()来打开文件,相当于这是在构造文件类,而用open()打开文件,是用python的内建函数来操作,我们一般使用open()打开文件进行操作,而用file当做一个类型,比如type(f) is file
    Python2中的file与open

     

     2.打开文件的模式

    (1)r:默认的打开模式,只读,文件不存在则报错

    #r:默认的打开模式,只读,文件不存在则报错
    f=open('a.txt',encoding='utf-8')
    print('===>',f.read()) #读所有,bytes---decode('utf-8')--->str
    print('===>',f.read())
    
    print(f.readlines()) #读所有,结果放入列表中
    
    print(f.readline(),end='') #一次读一行
    print(f.readline(),end='')
    print(f.readline(),end='')
    print(f.readline(),end='')
    
    f.close()
    只读模式

     

    (2)w:只写模式,如果文件存在则清空,如果文件不存在则新建

    #w:只写模式,如果文件存在则清空,如果文件不存在则新建
    f=open('b.txt',mode='w',encoding='utf-8')
    f.write('11111\n') #unicode---encode-->bytes
    f.write('2222\n')
    f.write('333333\n')
    
    #l=['444\n','55555\n','66666\n']
    #for line in l:
    #     f.write(line)
    
    f.writelines(['444\n','55555\n','66666\n'])#写入多行
    f.close()
    只写模式

     

    (3)a:追加写模式,如果文件存在则把光标移动到文件末尾,如果文件不存在则新建

    #a:追加写模式,如果文件存在则把光标移动到文件末尾,如果文件不存在则新建
    f=open('c.txt','a',encoding='utf-8')
    f.write('333333\n')
    f.write('444444\n')
    f.writelines(['5555\n','666\n'])#追加多行
    
    f.close()
    追加模式

     

    (4)遍历文件

    #遍历文件
    with open('a.txt',encoding='utf-8') as f:
        #不推荐使用
        # lines=f.readlines()
        # for line in lines:
        #     print(line,end='')
        #推荐使用
        for line in f:
            print(line,end='')
    遍历文件

     

    (5)布尔值

     

    #bool值:所有的数据类型都自带布尔值
    #布尔值为假的情况:0,空,None
    
    x=None
    # print(bool(x))
    
    if x:
        print('ok')
    else:
        print('ono')
    bool值:所有的数据类型都自带布尔值

     

     

     

    (6)b:以bytes的形式去操作文件内容,不能指定编码

    #b:以bytes的形式去操作文件内容,不能指定编码
    
    with open('yuanhao.jpg',mode='rb') as f:
        print(f.read()) #
    
    with open('a.txt',mode='rb') as f:
        data=f.read()
        print(data.decode('utf-8'))
    
    
    with open('d.txt',mode='wb') as f:
        f.write('哈哈哈hello'.encode('utf-8'))
    
    with open('d.txt', mode='ab') as f:
        f.write('哈哈哈hello'.encode('utf-8'))
    b:以bytes的形式去操作文件内容,不能指定编码

     

    (7)练习

             练习,利用b模式,编写一个cp工具,要求如下:

      1. 既可以拷贝文本又可以拷贝视频,图片等文件

      2. 用户一旦参数错误,打印命令的正确使用方法,如usage: cp source_file target_file

      提示:可以用import sys,然后用sys.argv获取脚本后面跟的参数

    #cp source_file dst_file
    import sys
    
    sfile=sys.argv[1]
    dfile=sys.argv[2]
    
    # with open(sfile,'rb') as read_f:
    #     data=read_f.read()
    #
    # with open(dfile,'wb') as write_f:
    #     write_f.write(data)
    #
    with open(sfile,'rb') as read_f,open(dfile,'wb') as write_f:
        # data=read_f.read()
        # write_f.write(data)
        for line in read_f:
            write_f.write(line)
            write_f.flush()
    详细版本

     

    import sys
    if len(sys.argv) != 3:
        print('usage: cp source_file target_file')
        sys.exit()
    
    source_file,target_file=sys.argv[1],sys.argv[2]
    with open(source_file,'rb') as read_f,open(target_file,'wb') as write_f:
        for line in read_f:
            write_f.write(line)
    精简版本

     

    3. 操作文件的方法

    #掌握
    f.read() #读取所有内容,光标移动到文件末尾
    f.readline() #读取一行内容,光标移动到第二行首部
    f.readlines() #读取每一行内容,存放于列表中
    
    f.write('1111\n222\n') #针对文本模式的写,需要自己写换行符
    f.write('1111\n222\n'.encode('utf-8')) #针对b模式的写,需要自己写换行符
    f.writelines(['333\n','444\n']) #文件模式
    f.writelines([bytes('333\n',encoding='utf-8'),'444\n'.encode('utf-8')]) #b模式
    
    #了解
    f.readable() #文件是否可读
    f.writable() #文件是否可读
    f.closed #文件是否关闭
    f.encoding #如果文件打开模式为b,则没有该属性
    f.flush() #立刻将文件内容从内存刷到硬盘
    f.name
    操作文件方法

     

     4. 文件内光标移动

    #以文本的模式读文件,n代表的是字符的个数
    with open('a.txt','r',encoding='utf-8') as f:
        data=f.read(3)
        print(data)
    #以b的模式读文件,n代表的是字节的个数
    with open('a.txt','rb') as f:
        data=f.read(3)
        print(f.tell())
        print(data.decode('utf-8'))
    read

     

    #tell:告诉当前光标的位置
    with open('a.txt','r',encoding='utf-8') as f:
        data=f.read(3)
        print(f.tell())
        print(data)
    tell:告诉当前光标的位置

     

    #seek:移动光标
    with open('a.txt','r',encoding='utf-8') as f:
        data1=f.read()
        print('first: ',data1)
        print(f.tell())
        f.seek(0)
    
        data2 = f.read()
        print('second: ',data2)
    seek:移动光标

     

    #0:文件开头
    # 1:当前位置
    #2:文件末尾
    with open('a.txt','r',encoding='utf-8') as f:
        f.seek(3,0)
        print(f.read())
    
    with open('a.txt', 'rb',) as f:
        f.read(3)
        f.seek(3,1)
        # print(f.read())
        print(f.read().decode('utf-8'))
    
    
    with open('a.txt', 'rb',) as f:
        f.seek(-3,2)
        print(f.read())
    seek 的方式0 1 2

     

    import time
    with open('access.log','rb') as f:
        f.seek(0,2)
        while True:
            line=f.readline()
            if line:
                print(line.decode('utf-8'),end='')
            else:
               time.sleep(0.2)
    
    
    #以a的模式打开文件,追加内容,
    with open('access.log','a',encoding='utf-8') as f:
        f.write('11111\n')
    练习:基于seek实现tail -f功能

     

    5. 文件的修改

    文件的数据是存放于硬盘上的,因而只存在覆盖、不存在修改这么一说,我们平时看到的修改文件,都是模拟出来的效果,具体的说有两种实现方式

     (1)方式一:将硬盘存放的该文件的内容全部加载到内存,在内存中是可以修改的,修改完毕后,再由内存覆盖到硬盘(word,vim,nodpad++等编辑器)

    import os
    
    with open('a.txt') as read_f,open('.a.txt.swap','w') as write_f:
        data=read_f.read() #全部读入内存,如果文件很大,会很卡
        data=data.replace('alex','SB') #在内存中完成修改
    
        write_f.write(data) #一次性写入新文件
    
    os.remove('a.txt')
    os.rename('.a.txt.swap','a.txt') 
    复制代码
    一次读入

     

    (2)方式二:将硬盘存放的该文件的内容一行一行地读入内存,修改完毕就写入新文件,最后用新文件覆盖源文件

    import os
    
    with open('a.txt') as read_f,open('.a.txt.swap','w') as write_f:
        for line in read_f:
            line=line.replace('alex','SB')
            write_f.write(line)
    
    os.remove('a.txt')
    os.rename('.a.txt.swap','a.txt')
    一行一行的读入

     

    (3)练习:修改文件内容,把文件中的alex都替换成SB

    #方式一(占用内存过大,仅适用于小文件):把硬盘中文件的数据全部读入内存,然后在内存里进行修改,最后保存
    import os
    with open('e.txt','r',encoding='utf-8') as read_f,\
            open('.e.txt.swap','w',encoding='utf-8') as write_f:
        data=read_f.read()
        # print(type(data))
        data=data.replace('alex','SB')
        write_f.write(data)
    
    os.remove('e.txt')
    os.rename('.e.txt.swap','e.txt')
    
    #方式二:一行一行地读,一行一行地改
    import os
    with open('e.txt','r',encoding='utf-8') as read_f,\
            open('.e.txt.swap','w',encoding='utf-8') as write_f:
        for line in read_f:
            line=line.replace('SB','alex')
            write_f.write(line)
    
    os.remove('e.txt')
    os.rename('.e.txt.swap','e.txt')
    
    #e.txt
    
    alex say i have on tesla
    my name is alex
    alex is good
    alex xxxx hahaha alex
    View Code

     

    6. 把文件当成数据库

    with open('db.txt','r',encoding='utf-8') as f:
        for line in f:
            user_l=line.split(',')
            print(user_l[1],int(user_l[2]))
    从文件里取数据
    1,peigen1,38,male,1234563378
    2,peigen2,28,female,1234335678
    3,peigen3,18,male,123145678
    4,peigen4,8,male,1234115678
    5,peigen5,48,female,1232245678
    6,peigen6,58,male,1234335678
    db.txt

     

    二 函数知识体系

    1 什么是函数?
    2 为什么要用函数?
    3 函数的分类:内置函数与自定义函数
    4 如何自定义函数
      语法
      定义有参数函数,及有参函数的应用场景
      定义无参数函数,及无参函数的应用场景
      定义空函数,及空函数的应用场景
    
    5 调用函数
        如何调用函数
        函数的返回值
        函数参数的应用:形参和实参,位置参数,关键字参数,默认参数,*args,**kwargs
    
    6 高阶函数(函数对象)
    7 函数嵌套
    8 作用域与名称空间
    9 装饰器
    10 迭代器与生成器及协程函数
    11 三元运算,列表解析、生成器表达式
    12 函数的递归调用
    13 内置函数
    14 面向过程编程与函数式编程
    函数知识体系

     

    三 函数的基础

    1.引子

    (1)为什么用函数

    #1、代码的组织结构不清晰,可读性差
    #2、遇到重复的功能只能重复编写实现代码,代码冗余
    #3、功能需要扩展时,需要找出所有实现该功能的地方修改之,无法统一管理且维护难度极大 
    不用函数的缺点

     

    (2)函数的定义

    针对二中的问题,想象生活中的例子,修理工需要实现准备好工具箱里面放好锤子,扳手,钳子等工具,然后遇到锤钉子的场景,拿来锤子用就可以,而无需临时再制造一把锤子。
    
    修理工===>程序员
    具备某一功能的工具===>函数
    
    要想使用工具,需要事先准备好,然后拿来就用且可以重复使用
    要想用函数,需要先定义,再使用
    函数

     

    (3)函数的分类

    #1、内置函数
    为了方便我们的开发,针对一些简单的功能,python解释器已经为我们定义好了的函数即内置函数。对于内置函数,我们可以拿来就用而无需事先定义,如len(),sum(),max()
    ps:我们将会在最后详细介绍常用的内置函数。
    
    #2、自定义函数
    很明显内置函数所能提供的功能是有限的,这就需要我们自己根据需求,事先定制好我们自己的函数来实现某种功能,以后,在遇到应用场景时,调用自定义的函数即可。例如
    函数分类

     

    2.定义函数

    (1)如何定义函数

    复制代码
    #语法
    def 函数名(参数1,参数2,参数3,...):
        '''注释'''
        函数体
        return 返回的值
    
    #函数名要能反映其意义
    函数的语法
    #定义阶段
    def tell_tag():
        print('===========')
    
    def tell_msg(msg):
        print(msg)
    
    
    #调用阶段
    tell_tag()
    tell_tag()
    tell_msg('hello world')
    tell_tag()
    tell_tag()
    # func()
    
    print(tell_msg)
    
    '''
    ===========
    ===========
    hello world
    ===========
    ===========
    
    '''
    函数定义的实例

     

     (2)函数的使用:先定义,再调用

    复制代码
    函数即“变量”,“变量”必须先定义后引用。未定义而直接引用函数,就相当于在引用一个不存在的变量名
    #测试一
    def foo():
        print('from foo')
        bar()
    foo() #报错
    
    #测试二
    def bar():
        print('from bar')
    def foo():
        print('from foo')
        bar()
    foo() #正常
    
    #测试三
    def foo():
        print('from foo')
        bar()
        
    def bar():
        print('from bar')
    foo() #会报错吗?
    
    
    #结论:函数的使用,必须遵循原则:先定义,后调用
    #我们在使用函数时,一定要明确地区分定义阶段和调用阶段
    
    #定义阶段
    def foo():
        print('from foo')
        bar()
    def bar():
        print('from bar')
    #调用阶段
    foo()
    先定义,再调用

     

    (3)函数在定义阶段都干了哪些事?

    #只检测语法,不执行代码
    也就说,语法错误在函数定义阶段就会检测出来,而代码的逻辑错误只有在执行时才会知道
    定义阶段的干那些事

     

    (4)定义函数的三种方式

    #1、无参:应用场景仅仅只是执行一些操作,比如与用户交互,打印
    #2、有参:需要根据外部传进来的参数,才能执行相应的逻辑,比如统计长度,求最大值最小值
    #3、空函数:设计代码结构

     

    #1.定义
    #无参
    def main():
        while True:
            user=input('>>: ').strip()
            # if len(user) == 0:continue
            if not user:continue
            password=input('>>: ')
            res=auth(user,password)
            if res:
                print('login successful')
            else:
                print('logon err')
    #有参
    def auth(user,pwd):
        if user == 'egon' and pwd == '123':
            return True
        else:
            return False
    #2.调用
    main()
    
    #1、定义时无参,意味着调用时也无需传入参数
    无参函数

     

    #有参函数
    def my_max(x,y):
        if x > y:
            return x
        else:
            return y
    
    res=my_max(1,3)
    print(res)
    
    #2、定义时有参,意味着调用时则必须传入参数
    有参函数

     

    #空函数
    def select(sql):
        '''
        查询功能
        :param sql: 格式后的sql
        :return: xxxx
        '''
        pass
    
    def update():
        pass
    
    def insert():
        pass
    
    def delete():
        pass
    
    #程序的体系结构立见 
    空函数

     

    3.函数调用

    (1)函数调用

    #1 调用函数:函数名(),
    #需要注意:先通过名字找到函数的内存地址,然后加括号调用
    函数调用

     

    (2)函数的返回值

    #2 函数的返回值return
    #注意的第一点:
    #在调用函数的过程中,一旦执行到return,就会立刻终止函数,并且把return后的结果当做本次调用的返回值返回
    #函数体内可以有多个return,但是只能执行一次
    def foo():
        print('111')
        return 1
        print('2222')
        return 2
        print('3333')
        return 3
    
    res=foo()
    print('函数调用完毕',res)
    
    #注意的第二点:
    #返回的值,可以是任意类型
    
    #注意的第三点:
    
    #没有return:默认返回None
    #可以返回一个值===>值
    #可以用逗号分隔,返回多个值===>tuple
    def foo():
        return None
    
    res=foo()
    print('函数调用完毕',res,type(res))
    函数返回值

     

    (3)函数的三种调用方式

    def foo():
        print('from foo')
        return 123
    
    
    #1
    foo()
    
    
    #2
    res=foo()
    print(res)
    
    #3
    res=foo()*10
    print(res)
    三种调用方式

     

    4.函数的参数

     (1)形参与实参

    #1:形参与实参
    #形参:在函数定义阶段,括号内定义的参数的称为形参,就相当于变量名
    #实参:在函数调用阶段,括号内定义的参数的称为实参,就相当于变量值
    
    #在调用阶段,实参的值会绑定给形参,在调用结束后,解除绑定
    形参与实参

     

    (2)参数的分类

    '''
    一:位置参数
        位置形参:必须被传值的参数,多一个不行,少一个也不行
        位置实参:从左到右依次赋值给形参
    
    '''
     def foo(x,y):
         print(x,y)
    
     foo(1,2)
    位置参数

     

    '''
    二:关键字参数:在函数调用阶段,按照key=value的形式定义实参
        可以不依赖位置而指名道姓地给形参传值
        需要注意的问题(可以与位置实参混用,但是):
            1. 位置实参必须在关键字实参的前面
            2. 不能为一个形参重传值
    '''
    # def foo(x,y):
    #     print(x,y)
    #
    # foo(1,2,y=20)
    关键字参数

     

    '''
    三:默认参数:在定义函数阶段,已经为形参赋值了,在定义阶段已经赋值,意味着在调用阶段
    可以不传值
        注意的问题:
            1 默认参数的值,只在定义时赋值一次
            2 位置形参应该在默认参数的前面
            3 默认参数的值应该是不可变类型
    
    '''
    def foo(x,y=10):
        print(x,y)
    
    
    foo(y=11,x=1)
    
    
    def register(name,age,sex='male'):
        print(name,age,sex)
    
    
    register('egon',18)
    register('wsb',18)
    register('alex',38,'xxxxxx')
    
    
    
    x='male'
    def register(name,age,sex=x):
        print(name,age,sex)
    
    x='female'
    register('alex',18)
    
    
    def register(name,sex='male',age):
        print(name,age,sex)
    
    
    '''
    默认参数

     

    '''
    
    四:可变长参数
    
    实参可变长度指的是:实参值的个数是不固定
    而实参的定义形式无非两种:1、位置实参,2、关键字实参
    针对这两种形式的实参个数不固定,相应的,形参也要有两种解决方案
    *
    **
    
    
    '''
    
    #针对按照位置定义的溢出的那部门实参,形参:*args
    def func(x,y,z,*args): #args=(4,5,6)
        print(x,y,z)
        print(args)
    
    func(1,2,3)
    func(1,2,3,4,5,6)
    func(1,2,3,*[4,5,6]) #func(1,2,3,4,5,6)
    func(*[1,2,3,4,5,6]) #func(1,2,3,4,5,6)
    
    func([1,2,3,4,5,6]) #func(1,2,3,4,5,6)
    
    
    def func(x,y,z):
        print(x,y,z)
    
    l=[1,2,3]
    func(*l)
    
    #针对按照关键字定义的溢出的那部分实参,形参:**kwargs
    def foo(x,y,**kwargs): #kwargs={'a':1,'z':3,'b':2}
        print(x,y)
        print(kwargs)
    
    foo(y=2,x=1,z=3,a=1,b=2)
    foo(1,2,3,z=3,a=1,b=2)
    
    foo(y=1,x=2,**{'a':1,'b':2,'c':3}) #foo(x=2,y=1,c=3,b=2,a=1)
    foo(**{'x':1,'a':1,'b':2,'c':3}) #foo(x=1,c=3,b=2,a=1)
    
    def foo(x,y,z):
        print(x,y,z)
    
    dic={'x':1,'y':3,'z':1}
    foo(**dic)  #foo(x=1,y=3,a=1)
    
    
    
    def home(name,age,sex):
        print('from home====>',name,age,sex)
    
    def wrapper(*args,**kwargs): #args=(1,2,3,4,5,6,7),kwargs={'c':3,'b':2,'a':1}
        home(*args,**kwargs)
        # home(*(1,2,3,4,5,6,7),**{'c':3,'b':2,'a':1})
        #home(1,2,3,4,5,7,a=1,b=2,c=3)
    
    
    
    # wrapper(1,2,3,4,5,6,7,a=1,b=2,c=3)
    wrapper('egon',sex='male',age=19)
    可变长参数

     

    #五:命名关键字参数(了解):
    # 形参中,在*后定义的参数称之为命名关键字参数,
    # 它的特性是;传值时,必须按照关键字实参的形式传值
    def foo(x,y=20,*args,a=1,b):
        print(x,y,a,b)
    
    # foo(10,b=3)
    foo(10,22,33,44,a=2,b=3)
    命名关键字(了解)

     

    5. 练习题

    1、写函数,,用户传入修改的文件名,与要修改的内容,执行函数,完成批了修改操作
    2、写函数,计算传入字符串中【数字】、【字母】、【空格] 以及 【其他】的个数
    
    3、写函数,判断用户传入的对象(字符串、列表、元组)长度是否大于5。
    
    4、写函数,检查传入列表的长度,如果大于2,那么仅保留前两个长度的内容,并将新内容返回给调用者。
    
    5、写函数,检查获取传入列表或元组对象的所有奇数位索引对应的元素,并将其作为新列表返回给调用者。
    
    6、写函数,检查字典的每一个value的长度,如果大于2,那么仅保留前两个长度的内容,并将新内容返回给调用者。
    dic = {"k1": "v1v1", "k2": [11,22,33,44]}
    PS:字典中的value只能是字符串或列表
    练习

     

    #题目一
    def modify_file(filename,old,new):
        import os
        with open(filename,'r',encoding='utf-8') as read_f,\
            open('.bak.swap','w',encoding='utf-8') as write_f:
            for line in read_f:
                if old in line:
                    line=line.replace(old,new)
                write_f.write(line)
        os.remove(filename)
        os.rename('.bak.swap',filename)
    
    modify_file('/Users/jieli/PycharmProjects/爬虫/a.txt','alex','SB')
    
    #题目二
    def check_str(msg):
        res={
            'num':0,
            'string':0,
            'space':0,
            'other':0,
        }
        for s in msg:
            if s.isdigit():
                res['num']+=1
            elif s.isalpha():
                res['string']+=1
            elif s.isspace():
                res['space']+=1
            else:
                res['other']+=1
        return res
    
    res=check_str('hello name:aSB passowrd:alex3714')
    print(res)
    
    
    #题目三:略
    
    #题目四
    def func1(seq):
        if len(seq) > 2:
            seq=seq[0:2]
        return seq
    print(func1([1,2,3,4]))
    
    
    #题目五
    def func2(seq):
        return seq[::2]
    print(func2([1,2,3,4,5,6,7]))
    
    
    #题目六
    def func3(dic):
        d={}
        for k,v in dic.items():
            if len(v) > 2:
                d[k]=v[0:2]
        return d
    print(func3({'k1':'abcdef','k2':[1,2,3,4],'k3':('a','b','c')}))
    答案

     

    四 函数对象、函数嵌套、名称空间与作用域、装饰器

    1. 函数对象

    (1)函数是第一类对象,即函数可以当作数据传递

    #函数是第一类对象:函数可以当做数据来使用
    def foo():
        print('from foo')
    
    #可以被引用
    f=foo
    # print(f)
    f()
    
    #可以当做参数传入一个函数
    def wrapper(x):
        # print(x)
        x()
    wrapper(foo)
    
    
    
    #可以当做函数的返回值
    def wrapper():
        return foo
    
    f=wrapper()
    print(f is foo)
    
    #可以当做容器类型的一个元素
    
    l=[foo,1,2]
    l[0]()
    函数是第一类对象,即函数可以当作数据传递

     

    (2)利用该特性,优雅的取代多分支的if

    data_dir='/usr/local/mysql/data'
    def select(sql):
        print('select功能: ',sql)
    
    def insert(sql):
        print('insert功能: ', sql)
    
    def update(sql):
        print('update功能: ', sql)
    
    def delete(sql):
        print('delete功能: ', sql)
    
    def alter(sql):
        print('alter功能:',sql)
    
    func_dic={
        'select':select,
        'update':update,
        'insert':insert,
        'delete':delete,
        'alter':alter
    }
    
    def main():
        while True:
            inp=input('>>: ').strip()
            if not inp:continue
            sql=inp.split()
            cmd=sql[0]
            # if cmd == 'select':
            #     select(sql)
            # elif cmd == 'update':
            #     update(sql)
            # elif cmd == 'insert':
            #     insert(sql)
            # elif cmd == 'delete':
            #     delete(sql)
            if cmd in func_dic:
                func_dic[cmd](sql)
            else:
                print('command not found')
    
    main()
    利用该特性,优雅的取代多分支的if

     

    2. 函数嵌套

    (1)函数的嵌套调用

    #1 函数的嵌套调用:在调用一个函数的过程中,又调用其他的函数
    def my_max2(x,y):
        if x > y:
            return x
        else:
            return y
    
    
    def my_max4(a,b,c,d):
        res1=my_max2(a,b)
        res2=my_max2(res1,c)
        res3=my_max2(res2,d)
        return res3
    
    res=my_max4(1,2,3,4)
    print(res)
    函数的嵌套调用

     

    (2)函数的嵌套定义

    #2 函数的嵌套定义:在定义一个函数内部,又定义了一个函数
    def f1():
        def f2():
            def f3():
                print('from f3')
            f3()
        x=1
        f2()
        print(x)
    f1()
    
    # f2()
    # print(x)
    函数的嵌套定义

     

    3.名称空间与作用域

     (1)名称空间

    '''
    名称空间:存放名字与值绑定关系的地方
    
    
    内置名称空间:
        存放的是:内置的名字与值的绑定关系
        生效:python解释器启动
        失效:Python解释器关闭
    
    全局名称空间
        存放的是:文件级别定义的名字与值的绑定
        生效:执行python文件时,将该文件级别定义的名字与值的绑定关系存放起来
        失效:文件执行完毕
    
    局部名称空间
        存放的是:函数内部定义的名字与值的绑定关系
        生效:调用函数时,临时生效
        失效:函数调用结束
    
    
    '''
    名称空间

     

    (2)名称空间的加载顺序

    python test.py
    #1、python解释器先启动,因而首先加载的是:内置名称空间
    #2、执行test.py文件,然后以文件为基础,加载全局名称空间
    #3、在执行文件的过程中如果调用函数,则临时产生局部名称空间
    加载顺序:先内置,再全局,最后局部
    名称空间的加载顺序

     

    (3)查找名字的顺序

    局部名称空间--->全局名称空间--->内置名称空间
    
    #需要注意的是:在全局无法查看局部的,在局部可以查看全局的,如下示例
    
    # max=1
    def f1():
        # max=2
        def f2():
            # max=3
            print(max)
        f2()
    f1()
    print(max) 
    查找名字的顺序

     

    (4)作用域

    '''
    作用域:
    全局作用域:包含内置名称空间的名字与全局名称空间的名字
                全局存活,全局有效
    
    局部作用域:包含局部名称空间的名字
                临时存活,局部有效
    
    '''
    
    
    
    
    x=1011111111111111111111111111111111111111111
    def f1(a):
        y='fffffffffffffffffffffffffffffff1'
        print(locals())
        print(globals())
    
    
    print(globals())
    print(dir(globals()['__builtins__']))
    
    print(locals() is globals())
    
    
    f1(12321312312312312312312312312312312312313213)
    
    
    
    
    #作用域关系,在函数定义时,就已经固定了,与调用位置无关
    x=10000000000000000000000000
    def f1():
        def f2():
            # x='123123123123123123123123123123123'
            print(x)
        return f2
    
    f=f1()
    
    # print(f)
    
    
    def func():
        x=123
        f()
    x='hello'
    func()
    
    
    
    
    
    global nonlocal
    x=1
    def f1():
        global x
        x=10
    f1()
    print(x)
    
    
    
    x=1
    def f1():
        x=2
        def f2():
            nonlocal x
            x=111111
        f2()
        print(x)
    
    f1()
    作用域

     

    转载于:https://www.cnblogs.com/wxp20170901/p/7569961.html

    展开全文
  • 对面向对象的一些思考

    千次阅读 2011-08-12 19:47:59
    对面向对象的一些思考 面向对象方法被人谈论了二十多年了。我接触它比较晚,直到九十年代中期才开始学习使用它。若说对这个方法做些评价,那还真是大言不惭了。不过这么些年来,也周期性的对面向对象做些思考。或...
      
    

    对面向对象的一些思考

    面向对象方法被人谈论了二十多年了。我接触它比较晚,直到九十年代中期才开始学习使用它。若说对这个方法做些评价,那还真是大言不惭了。不过这么些年来,也周期性的对面向对象做些思考。或对或错,我想都值得总结一下。一家之言,来看的同学不必太当真。

    首先我们要区分一下“基于对象”和“面向对象”的区别。

    基于对象,通常指的是对数据的封装,以及提供一组方法对封装过的数据操作。比如 C 的 IO 库中的 FILE * 就可以看成是基于对象的。

    面向对象,则在基于对象的基础上增加了多态性。所谓多态,就是可以用统一的方法对不同的对象进行同样的操作。当然,这些对象不能完全不同,而需要有一些共性,只有存在了这些共性才可能用同样的方法去操作它们。我们从 C++ 通常的实现方法的角度来看,A 和 B 在继承关系上都有共同的祖先 R ,那么我们就可以把 A 和 B 都用对待 R 的控制方法去控制它们。

    为什么需要这样做?

    回到一个古老的话题:程序是什么?

    程序 = 算法 + 数据结构

    在计算机的世界里,数据就是一个个比特的组合;代码的执行流程就是顺序、分支、循环的程序结构的组合。用计算机解决问题,就是用程序结构的组合去重新排列数据的组合,得到结果。为了从庞大的输入数据(从 bit 的角度上看,任何输入数据都可能非常的庞大),通过代码映射到结果数据。我们就必须用合理的数据结构把这些比特数据组合起来,形成数量更少的单元。

    这些单元,就是对象。对象同时也包括了对它进行操作的方法。这样,我们完成了一次封装,就变成了:

    程序 = 基于对象操作的算法 + 以对象为最小单位的数据结构

    封装总是为了减少操作粒度,数据结构上的封装导致了数据数据的减少,自然减少了问题求解的复杂度;对代码的封装使得代码得以复用,减少了代码的体积,同样使问题简化。

    接下来来看 基于对象操作的算法。这种算法必须将操作对象看成是同样的东西。在没有对象的层次上,算法操作的都是字节,是同类。但是到了对象的层次,就不一定相同了。这个时候,算法操作的是一个抽象概念的集合。

    在面向对象的程序设计中,我们便少不了容器。容器就用来存放一类有共同抽象概念的东西。这里说有共同概念的东西,而没有说对象。是因为对于算法作用于的集合,里面放的并不是对象实体,而是一个对实体的引用。这个引用表达的是,算法可以对引用的那一头的东西做些什么,而并不要求那一头是什么。

    比如,我实现一个 GUI 系统(或是一个 3d 世界)。需要实现一个功能——判断鼠标点选到了什么物件。这里,每个物件提供了一个方法,可以判断当前鼠标的位置有没有捕获(点到)它。

    这时最简单的时候方法是:把所有可以被点选的物件都放在一个容器中,每次遍历这个容器,查看是哪一个物件捕获了鼠标。

    我们并不需要可被点选的物件都是同类,只需要要求从容器中可以以统一方法访问每个元素的是否捕获住鼠标的这个判定方法。

    也就是说,把对象置入容器时,只需要让置入的东西有这一个判定方法即可。了解 COM 的同学应该明白我要说什么了。对,这就是 QueryInterface 的用途。com 的 query interface 就是说,从一个对象里取到一个特定可以做某件事情的接口。通常接下来的代码会把它放在一个容器里,方便别处的代码可以干这些事情。

    面向对象的本质就是让对象有多态性,把不同对象以同一特性来归组,统一处理。至于所谓继承、虚表、等等概念,只是实现的细节。

    说到这里,再说一下 COM 。COM 允许 接口继承 ,但不允许接口多继承。这一点是从二进制一致性上来考虑的。

    为什么没提 实现继承 的事情?因为实现继承不属于面向对象的必要因素。而且,现在来看,实现继承对软件质量来说,是有负面影响的。因为如果你改写基类的虚方法,就意味着有可能破坏基类的行为(继承角度看,基类对象是你这个对象的一部分)。往往基类的实现早于派生类,并不能了解到派生类的需求变化。这样,在不了解基类设计的前提下,冒然的实现继承都是有风险的。这不利于软件的模块化分离和组件复用。

    但是接口继承又有什么意义呢?以我愚见,绝大多数情况下,同样对设计没有意义。但具体到 COM 设计本身,让每个接口都继承于 IUnknown 却是有意义的。这个意义来至于基础设施的缺乏。我指的是 GC 。在没有 GC 的环境中,AddRef 和 Release 相当于让每个对象自己来实现 RC (引用计数)的自动化管理。对于非虚拟机的原生代码,考虑到 COM 不依赖具体语言,这几乎是唯一的手段。另外 COM 还支持 apartment 的概念,甚至允许 COM 对象处于不同的机器间,这也使得 GC 实现困难。

    QueryInterface 存在于每个 COM 接口中却有那么一点格格不入。它之所以存在,是因为 COM 接口指针承担了双重责任,既指出了一个抽象概念,又引用了对象的实体。但从一个具体算法来看,它只需要对一组相同的抽象概念做操作即可。但它做完操作后,很可能(但不是必须)需要把对象放入另一个不同的集合中,供其它算法操作。这个时候,就需要 QueryInterface 将其转换为另外一个接口。

    但是,从概念上讲,让两个不相关的接口相互转换是不合逻辑的。本质上,其实在不相关的接口间转换做的事情等价于:从一个接口中取得对对象的引用,然后调用这个对象的方法,取到新的接口。

    如果去掉了 AddRef Release (依赖 GC )以及 QueryInterface (只在需要时增加一个接口获得对象的引用),IUnknown 就什么都不剩了。那么接口继承也完全不必存在。


    回头再来看程序语言。

    C++ 提供了对面向对象的支持,但 C++ 所用的方法(虚表、继承、多重继承、虚继承、等等)只是一种在 C 已有的模型上,追加的一种高效的实现方式而已。它不一定是最高效的方式(虽然很少能做到更高效),也不是最灵活的方式(可以考察 Ruby )。我想,只用 C++ 写程序的人最容易犯的错误就是认为 C++ 对面向对象的支持的实现本身就是面向对象的本质。如果真的理解了面向对象,在特定需求下可以做出特定的结构来实现它。语言就已经是次要的东西了。

    了解你的需求,区分我需要什么和我可以做到什么,对于设计是很重要的。好的设计在于减无可减。

    你需要面向对象吗?你需要 GC 吗?你需要所有的类都有一个共同的基类吗?你需要接口可以继承吗?你为什么需要这些?

    Comments

    太赞了。看完文章和评论,受益匪浅。

    看东西的层次问题。
    面向对象是帮助人来理解代码的方式。
    就象我看见灯
    我只需要知道:我一拉它就亮。

    我不关心他是什么样的灯 在客观世界就是有各种的标签 这些标签形成了我们的认知。

    不明何谓“减无可减”。
    代码量?
    数据量?
    还是接口规模?
    --
    只知3NF,5NF等早已对某些东西有所指明。

    唉,我们这些浅薄的程序员,也不过是讨口饭吃,也许是我意志薄弱,曾经想成为高手的想法已经不知道藏哪里去了

    所谓的设计模式,n多类其实在直接和OS打交道的领域里,都是垃圾

    @ghost

    这样看来我和你的看法又没什么大的出入。只是我说得没你那么详细而已。
    曾经也在改别人的错误,也曾自己获过错误,后来是被逼着去重复错误,接着是辞职,呵呵。不过这样的公司还真不少:(
    那现状如此,除了一气走人,坚持走自己的路,我们于大环境又能改变什么呢?
    或许真的没那些有着花哨名词的新技术,会多几个潜心修行的开发者。

    看ghost的发言,让我有一种泄愤的快感:)

    但是,从概念上讲,让两个不相关的接口相互转换是不合逻辑的。本质上,其实在不相关的接口间转换做的事情等价于:从一个接口中取得对对象的引用,然后调用这个对象的方法,取到新的接口。


    有趣的是,对于COM来说,不光“本质上”类型转换等价于方法调用,“实际实现”也是如此。

    好的设计?
    那么什么才算是好的设计呢?
    高效?简洁?扩展性?维护性?
    评价的标准似乎很多。但是那似乎也只是理论而已。
    设计人对项目的理解和把握,曾经的项目经验,甚至学习经验。项目本身的要求?

    “正因为设计如此重要,所以必须禁止外行染指这个领域。”
    “禁止”是人为的壁垒,而不是知识壁垒。

    回 zen_yue朋友:

    1、设计是最最重要的。没有好的架构设计,万字就要画一万横。
    正因为设计如此重要,所以必须禁止外行染指这个领域。
    这不是什么行业壁垒,而是知识壁垒。

    20个线程读磁盘,真正懂得计算机、真正动手做过东西的一定会感到可笑,进而担心这个项目的命运。

    不仅仅是数据结构和算法,UML、面向对象还有设计模式都是必要的好东西——飞机也是好东西;但绝不应让拉登等心智还不健全的原教旨主义分子接触。

    2、如果你觉得Linus Torvalds居然像个小P孩般去争论语言的优劣,那么你也把他看得太低了些。

    仔细品品他话里的味道吧。

    明确点说,就是:C++和UML一样,是一种威力强大的好东西。但正因为它的强大,仅仅学会“用VB编计算器”的家伙就多了起来。

    由于缺少对计算机真正深刻的理解,这些C++语言专家就觉得一切也不过如此,按照对象发现方法论把类啊模式啊往上一套,一切就都有了;甚至于有了相关知识后,他们还是脱离不了那些可笑的固定套路——这就是所谓的心智包袱。

    必须明白:没有任何一种方法论可以告诉你“不能用20个线程读同时读写磁盘”以及诸如此类的其他东西。

    如果法律执行严格,这些浪费了纳税人数千万的不称职的家伙,处境相当危险。

    但显然,这些人的自我感觉还是非常良好的。从他们自称“白领”并且想用机器替代“蓝领”便可见一斑。
    他们不知道,自己的一只脚就在牢门里放着。

    能把一个好好的人整成这样,你说C++以及UML、设计模式算什么?


    3、正因为人才难找,这才彰显能干活的软件“蓝领”的重要。

    追加投入100亿,给他们100年,你觉得我说的那帮子“白领”能把东西做出来吗?

    给你100万,但必须承担项目失败的后果;你愿意以“不修改高层设计”的前提接下那个“以O(N^2)效率存储用户信息、数据库存储程序状态却又让其他模块无限制访问、20线程玩磁盘、5000并发用户连接用5000个进程实现且5000个进程无任何锁自由访问(包括添加和删除!)同一块共享内存里的session、并且认为单核CPU根本就不可能出现并行因而不管多少线程访问同一块内存都不需要锁”的“白领”项目烂摊子吗?


    一个破网上超市项目而已,不考虑用户界面,内部逻辑在云风这里可能就是一两个月;而且做出的一定还是通用内核(连我都觉得这是个半年单人干的活!)。

    但那帮家伙楞是做了好几年,花了数千万!


    没有人说方案不可以折中,不可以靠牺牲某些东西以绕开技术难点——相反,能够做到这个的,才配叫技术人才。

    是的。企业不需要天才,他们只需要开发人员能把任务完成。

    问题是:你用哪只眼睛看到,只要会画画UML、像“学用VB编计算器”一样玩玩对象发现,就能“完成而且低成本高效率的完成任务”?

    那些在监狱门外徘徊的白领们的教训,还不够深刻么?


    现在根本的问题不是什么“开发/设计人员的实力”,而是把草包剔除。

    当这些草包滚蛋之后——只要有类似云风这样懂技术的主心骨在,把核心做了,剩下的直接上lua或其他脚本即可:这时候,对开发人员的素质要求才可能真正降下来。


    ——相反,当初在草包的手下,我们几个会干活的那可是名符其实的“救火队”:从数据库性能不足需调查原因、到莫名其妙的偶发crash(被我查出原因是线程退出次序紊乱)、再到“由于一点点设计疏漏,我们要帮另一个模块更新数据库状态”、继而“由于对方数据库负荷过重,那个更新导致我们的服务失去响应”(为这个,我不得不写了个自动管理线程及相关资源的小调度模块,以保证主逻辑里可以像函数调用一样完成数据库异步更新工作——并且这个模块的健壮性必须达到7×24小时级别;由于这个200多行代码写的模块太干净利落了点,导致另一个想当“白领”的家伙对我产生了警惕,从此时时处处给我下绊子!)……

    这份“蓝领”的工作看起来如何?您拿得下吗?

    人贵有自知之明。
    对初学者来说,千万要明白画UML、玩面向对象、看设计模式等等花活和真正的项目设计之间的区别。
    在工程师的世界里,一个算法可能都值得很多人研究一辈子;甚至他们的后人还要接着研究;或者,他会知道关于软硬件的一切,然后小心翼翼地引领团队绕开一个个陷阱:而为了这个,他需要懂得一切,甚至包括生物学(比如遗传算法、神经网络以及人眼人耳的物理原理及特性等等等等)——这些都和管理者的“浅薄(无贬义)”的世界在根本上是不同的。

    不打算在engineer的世界里投入一辈子的,可能是好的engineer吗?

    我是个新人,读了ghost的回复,觉得说的没错,但有些地方难以理解,接受不了。对一个软件难道设计不重要吗?除去说什么对象与模式的,至少也要设计个数据结构与算法吧。
    对于我,虽然有用起来不喜欢的语言,但没有觉得什么是垃圾的语言。存在就有它的道理,或许只是你不需要而已。
    人才是难找的,你不可能要求每个开发人员都有天才般的开发实力,但项目必须完成,有些东西就是折衷的方案。企业要的不是一个天才的团队,而是一个能完成项目,同时低成本,高效率(依靠某些工具或方法)的团队。一般都以设计做主导,如果设计出问题,无疑项目会一团糟,但假设设计没有问题,那项目就有可能接近理想。
    所说的管理者与工程师的问题,哎,管理者不该涉及系统设计与程序的。项目经理应该只负责调度,当然如果他是个牛人,能说善听,精力旺盛,程序写得顶呱呱,兼个架构师的职位也没什么不可以。
    发现是看的方向不太相同,似乎也没什么好讨论的:(
    还是那句话,存在有它的道理。发现这个道理就知道该用在哪里。没发现前也用用吧,不用又怎么会明白其中的道理呢。用完,记得深思,审视。
    嗯,看完,也提醒自己再审视一些东西。

    Q1:你需要面向对象吗?
    A1:需要,在设计时会使我的思路更加清晰,可扩展性可读性上都得到很大的提升。
    Q2:你需要 GC 吗?
    A2:需要。使我写代码时比较安心一些。
    Q3:你需要所有的类都有一个共同的基类吗?
    A3:通常时候会用大部分类继承于某一基类,原因如A1,良好的抽象的设计能很大程度上方便之后的工作。
    Q4:你需要接口可以继承吗?
    A4:是的。有接口,容易扩展,并且为若干时间后的补充提供一套规范。
    Q5:你为什么需要这些?
    A5:综上。
    OO并没有错,是好东西。语言之争实在没意思的很,就象砍木头,愿意用锯还是愿意用斧头有什么区别。关键是使用工具的人怎么想。

    说实话,感觉这篇文章,有点怪。对于“基于对象”“面象对象”这种咬字眼的东西实在不感冒。

    你的心智,到能够继承它们的时候了吗?

    好文共赏!受教了!

    呵呵,我倒觉得,现在颇有些人以为拿ROSE画UML就是“面向对象软件设计”了。

    事实上,所谓的“对象发现”、用例设计等等东西,和“跟我学用VB编写计算器”,在水平层次上毫无差异。

    其实,虽然宣传起来吹得很大,rose还是相当清楚自己的定位的:一个专业程序员和普通用户间的交流工具。

    用一位外国朋友的话来说就是:这东西是manager's world用来沟通user's world和engineer's world的。

    用户基本没有关于计算机知识;他只能说出他的工作是什么;manager善于沟通,但对程序设计并不精通;engineer虽然精于程序设计,但往往并不善于与人打交道。

    借助UML和用例以及对象发现,manager可以做出一个虽不比“用VB学编计算器”更高明、但至少不能算是外行的初步框架——这是对engineer来说的。
    而对于用户来说,UML避免了engineer随口说出的术语,给出了一个虽然样式有点怪异、但毕竟不算难懂的模型。

    于是,三方皆大欢喜,这事就这样成了。


    但是,倘若manager学了个“VB写计算器”的方法,便以为engineer们的时代过去了,一切有他和机器足矣;那么大家就应该知道,是该给什么医院打个电话的时候了。

    比如,我之前提的那个“每天50G以上日志;单个日志文件小于2M;每天定时分析此日志;消耗时间尽可能少”的需求,你拿UML+用例+对象发现 能捣鼓出什么?!

    manager只要能确定如上需求,他就已经非常优秀了;但适可而止吧,再多走一步就是犯罪(现实是:数千万已经这样打了水漂了;而且显然还要继续投入更多的钱打水漂!)

    相反,这东西在我们engineer手里,又怎么样呢?
    我们马上就会想起,一次拖10个1G的文件到另一个硬盘可能需要10分钟;但一次拖一个文件,让10个拷贝操作同时运行,可能就要几个小时。
    由我们的专业知识,很容易知道这是因为乱序访问磁盘,导致出现大量磁头寻道操作所致。
    于是,正确的方案就摆在面前了。
    (相反,那些玩过界的manager们搞了20个线程同时到磁盘上瞎玩——想想情况会有多可怕吧)


    国外对这些区分的是非常清楚的。
    所谓manager,他只是资源的管理者——资源当然包括人力资源在内。
    但必须强调,他只是被赋予权力调度各种资源以尽可能高的效率完成工作,并不是engineer的上司:双方根本就不是同一个世界的人。

    在国外,从engineer世界转到manager世界的人,往往是会被真正的engineer鄙视的。因为这说明他根本就不喜欢或做不了engineer的工作,在engineer的世界里没有发展前途。

    (事实上,manager不过是个管家类型的人才;engineer才是真正可以被称作“学者”的。双方的社会声誉是相差极大的。不管做管理的做到什么程度,做技术的都能以一句“just a manager”噎得他喘不过气。)

    说这些,我的意思当然不是说做管理的都是些没文化的家伙。
    沟通是门艺术;在下就极不善于口头沟通,对嘴皮子厉害的人,当然是相当仰慕的。
    ——但艺术毕竟不是学术。

    要说水深,再没有比学术淹死的人更多的了。

    国内的情况大家都很了解,不多说了。

    但有一个极其严重的问题必须再提一提:由于官本位思想作祟,我们的manager太具侵略性了。
    这些家伙,和那个学会一横是“一”、两横是“二”、三横是“三”,便敢宣称“儿得矣”的小屁孩是一路货。

    小屁孩揽下个写“万”的任务,险些把自己累死。
    同样,我们刚学会“用VB编计算器”的manager也能搞20个线程把磁盘累死;搞个O(N^2)的算法处理千万级别的数据把数据库服务器累死;搞个大蜘蛛网把自己也累死——最后,倘若法律能更完善,被他们葬送的数千万恐怕已足以将此等人送上刑场;起码也得好几年牢坐。

    但没关系。这里是中国。这里是软件“白领”们正要用机器替代软件“蓝领”的中国。

    受这些脸皮足以挡子弹的家伙影响,准备先做几天技术然后转管理的家伙越来越多——原因嘛,管理人员早已远远过剩,能做技术的却是千百人中都挑不出一个(这是实话,没有半点夸张)。

    所以,即使学个半吊子,也能混上个技术工作;干上两年——我们很多公司根本就没给做技术的准备升职路线——自然就升职做管理了。

    另一方面,为避免其他做技术的抢到自己的位置,他们自然要大力宣传“蓝领”论,拼命打压真正做技术的。

    在这种浮躁的气氛下,大量涌现只会写一二三的小屁孩,也就不足为奇了。

    毕竟,死记硬背几条C++特性,要比彻底理解计算机软硬件原理、理解算法原理以及相关的数学甚至物理知识,要容易太多太多了。

    PS:Linus Torvalds骂的不是C++,而是学了几条C++基础知识就不知道天高地厚的小屁孩;是那些学会“用VB写计算器”就自以为是软件白领的人:

    C++是一种糟糕的(horrible)语言。而且因为有大量不够标准的程序员在使用而使情况更糟,以至于极容易产生彻头彻尾的垃圾(total and utter crap)。老实说,选择C就是为了把C++程序员踢出去。……我有这样的结论,任何喜欢用C++而不是C开发项目的程序员可能都是我希望踢出去的人,免得他们来搞乱我参与的项目。C++会导致非常非常糟糕的设计选择。你们这些C++程序员总是一上来就用语言的那些‘漂亮的’库特性比如STL、Boost和其他彻头彻尾的垃圾,这可能对你们的程序有所‘帮助’,但是却会导致:

    “——当库无法工作时无穷无尽的折磨(别跟我说什么STL尤其是Boost很稳定而且可移植性很好,那全是屁话,而且一点都不可笑)

    "——低效的抽象编程模型,可能在两年之后你会注意到有些抽象效果不怎么样,但是所有代码已经依赖于围绕它设计的‘漂亮’对象模型了,如果不重写应用程序,就无法改正。


    ”也就是说,使用优秀的、高效的、系统级的和可移植的C++的唯一方式,最终还是限于使用C本身具有的所有特性。项目限制只用C,意味着参与的人不会捣乱,也意味着会得到许多真正懂得底层问题,而不会折腾那些白痴‘对象模型’垃圾的程序员。

    "所以,我很抱歉,但是对于Git这样效率是主要目标的软件,C++的所谓优点只是巨大的错误。而我们将看不到这一点的人排除在外却成了一个巨大的附加优势。

    "如果你想要用C++写的版本控制系统,去玩Monotone吧。他们确实使用了‘真格的数据库’,使用了‘漂亮的面向对象库’、使用了‘漂亮的C++抽象’。可是说老实话,所有这些对某些计算机专业人士而言富于吸引力的设计决定,其最终结果确是一堆可怕、难以维护的垃圾。“


    尤其是这些:

    唯一真正重要的部分是设计。

    "你当然可以用任何语言编写糟糕的代码。但是,有些语言,尤其是带有一些心理(mental)包袱的语言本身就非常糟糕。你这样的新手跑来指出一些绝对无关紧要的补丁特性,用它们作为一种语言优越的论据(这些东西语言原作者都不喜欢),这一事实本身恰恰说明你满脑子都是糊涂概念,应该好好醒悟一下了。

    不仅指语言本身,还包括一种必需的心态(mentality)。C最大的优点之一,就是它不会使你认为程序是什么高层的东西。正是后一种心态会使你明显偏向其他语言,但实际上从Git的角度看来,所谓 ' 高层 ' 恰恰是错误的。"


    其实,不光是C++;面向对象、UML以及新近晋升为祸害的设计模式,不都是这样吗?

    举例来说:打开老爸的工具箱,你会发现钳子、扳手、刀子、电锯、机油甚至硫酸、剧毒杀虫剂和极易爆炸的氧炔焊接装置。

    你的心智,到能够继承它们的时候了吗?

    "我想,只用 C++ 写程序的人最容易犯的错误就是认为 C++ 对面向对象的支持的实现本身就是面向对象的本质。如果真的理解了面向对象,在特定需求下可以做出特定的结构来实现它。语言就已经是次要的东西了。"
    ===>
    我喜欢这句话.

    简洁清晰,一般来说我会这样要求自己设计。如果无法一目了然的了解程序在做什么。那么一般我会换一个方式来思考。unix其实设计得很精妙,设计模式说白了在很大程度上是在用面向对象的方式实现unix在几十年前所提出的那些关于软件设计的精神。

    为了避免误解,小心翼翼地, 我强调,这是指参与的讨论而言,而不是文章本身

    剥离了在应用和工程中创造的无数拗口生僻的名词, 所有的概念基础存在于学校课本里,存在于数学书中,学生应该掌握,

    面向对象谈论这么多年, 似乎工程师谈论的都是具体语言措施里的实现, 反复, 没有理论的一个概括.

    表面上, 面向对象方法,设计模式, 统称为“方法论”, 奇怪的名词,这些东西看似一半的理论,给人以无数著书立说的可能, 因为讨论方法嘛,没人说对错,

    对啊, 面向对象, GC...你为什么需要这些, 工程师们,处于一个缺乏理解的时代,

    编程模式越来越纯粹, 相关的理论基础早以存在,却一直没被工程师理解而已, 一切美好的东西, 淹没在工程的复杂性里面

    毫无疑问,这种几乎属于玄学的讨论,吸引了最多人的讨论, 因为很难有硬伤:)

    有一个问题,你的blog在屏幕上怎么只有那么一点点宽,能不调到0.618那个范围,不然看起来有一点儿不爽,不过blog内容还是有趣的

    你需要面向对象吗?
    你需要 GC 吗?
    你需要所有的类都有一个共同的
    你需要接口可以继承吗?
    你为什么需要这些?

    非得不用,非利不动,非危不>

    如果对程序设计而言,能够减少耦合,提高内聚,增加稳定性,减少代码量及资源占用的设计就是好设计。实现这些目标有N种方法,前人不断实践与总结,所以有了函数,结构,面向对象……各有各有好处,各有各的应用范围。最近做些东西,发现在C++中,有些实际情况坚持应用某一方法是不太理想的,或许得看需求,舍弃一些设计上的统一。我比较向往灵活性与安全性,但对资源占用并不很在意,除非项目明确规定,运行在某某平台上。因为软件要向前发展,硬件也是,如何让软件更上硬件发展的脚步,保持原来软件的灵活性很重要。基于这样的考虑,接口应用是一种不错的实现手段。方便,直观。而谈到设计层面来,似乎可以是语言无关的东东,当你设计完你的系统,再根据性能需求选个工具来开发,如果选了C++,再审视一下设计,发现有个GC不错,那么就做个GC吧。其实程序跑起来,硬件也只需知道数据在那,指令在那也就行了。N多方法只为了方便我们的开发与维护。明确目标选工具,而不是拿个工具来套目标。愚见!

    小平说, 先搞清楚什么是社会主义, 才能怎样建设社会主义;
    1. 贫穷不是社会主义;
    2. 两级分化更不是社会主义;

    不妨我们也应用一下:
    在我们阔谈如何"建设"类之前, 首先定义一下到底什么是"类":
    1. ????
    2. ????

    我对OO的理解和云风差不多,就是OB+多态,便于以相同方式遍历操作相似的东西。

    囧……有个问题……
    有多少人调查过到底有没有方形的或者非圆形的下水道盖子?

    @Zenberg

    GP愚以为并不是OO的补充,范型编程其重中之重是一个“范”字,编程已是其次,“范”明显的代表了抽象的意味,如果在C上去作泛型编程,大概亦只能用void*作接口,但丝毫不影响其实现。现在我比较喜欢用接口这个词,其本身就有一种抽象的意思。诚如贵言:“linux对待不同文件系统的实现的接口设计和collection framework抽象不同行为模式容器的接口设计实在是异曲同工。”,抽象出接口后,C写就的linux kernel和以Java写就的collection framework如何实现已不重要,本人愚见,这就是所谓的泛型编程(GP)。

    确实,一种语言会对程序员产生导向。但是作为一个程序员,不是应该把握好各种导向,站在更高层次,构思抽象,充分利用各种语言的特色吗?当然混合各种语言的编程是天方夜谭,但是可以充分利用接口的功用,结合不同的语言,例如C/C++与LUA脚本的结合,愚以为亦是GP的一种表现。

    我觉得,作为一个程序员应该去发挥各种语言的特色,而不是去矮化各种语言。

    @Cloud

    我也不想探讨设计模式,因为我实在没有发言权,我举例不过为了说明抽象的重要性,实现是其次的。

    “那么,怎么提取这些共性。”“重要的是理解你的需求,判别哪些是真正的需求,哪些是不必要的需求。”
    大概这些就是抽象的工作了,用什么实现这里一点都不重要。

    “举例,GP 也是一种抽象的手段,但 GP 不是 OO 。GP 的抽象主要解决的是在不同的地方用相同的算法去处理不同类别的东西。

    而 OO 做的是什么呢?OO 做的是把不同的东西归类在一起,在同一地方处理这些东西具有共性的部分。这就是多态。”

    按照我的理解,方法和数据都是有“接口”的,因为方法期望合法的数据,所以数据也需要提供访问的接口,在C语言中,你可以提供一个方法的接口或者一个数据的接口,但是在OO中,你可以拥有方法和数据的共同体。两者抽象的本质却是没有分别的。

    ===================
    本人才疏学浅,见笑了,还望指教。

    并非任何软件搭建都会强调抽象和封装的,软件危机的原因产生大概就在于此。

    OO的好处是强制你去进行抽象和封装,至少会让开发者去在这方面做思考。其实我们尽可以去用Java写Procedure形式的程序,也可以用C去OO,当然这并不是问题的关键。

    GP与OO并不矛盾,就如楼主所说,只是不同角度而已,我个人觉得GP是对OO非常好的一个补充。

    以本人有限的认识,我觉得以C写就的linux kernel和以Java写就的collection framework都是令人击节的OO实践杰作。

    linux对待不同文件系统的实现的接口设计和collection framework抽象不同行为模式容器的接口设计实在是异曲同工。

    再,我个人认为你对封装和抽象的理解有问题,并非问题粒度上的事情。

    抽象是为了对各类需求进行分门别类的管理。

    封装是为了隐藏实现的细节,进而达到抽象的目的。

    @joe wulf

    我举鼠标选取的例子,不是想探讨设计模式,我认为模式固然有用,但是大多数情况下会让我们忽略本质。这点我跟 ghost 的看法近似。

    那么我想说明什么?

    在这个例子里,我们的本质需求是:可以需要让不同的对象可以用统一的途径去检查是否捕获住鼠标。只要满足了这个,就可以把这些对象放在一起来处理。这就是 OO 在这个问题上作用。

    至于之后怎么办,无非是顺序、分支、循环而已。我们不需要看到什么模式,因为要处理的事务和数据的粒度已经足够粗了,基本的算法就能搞定它们。

    那么,怎么提取这些共性。COM 的 QueryInterface 就是描述的这个问题。字面的意义解释的恰到好处。至于如何实现它,用继承只是解决方案之一。当语言提供了某种便捷的语法手段时,我们就倾向于把实现手法偏向于它。所以语言本身就对程序员的思维造成了导向。

    对于一个特定问题,有一个解决方法。但是对于许多问题的集合,把逐个解决方法糅合到一起却十分危险。因为每针对一个问题的解决,结构的设计都有差别。把他们组合到一起,结构(数据结构和代码结构)就容易变成一团乱泥。

    我想,为了最好的解决问题。设计方法和模式都是次要的东西。重要的是理解你的需求,判别哪些是真正的需求,哪些是不必要的需求。(通常是因为你为了解决那些真正需求,铺了一条路。而后为了铺路而产生的新需求。)

    留下那些真正的需求,把问题简单化。

    @Zenberg,

    正文中列出哪些问题,是提醒自己和各位同学,在做每个项目前,都仔细考虑这几个问题。这些问题是个要解决的问题相关的,有时候我们需要,有时候我们不需要。而不是抛开要解决的问题,直接可以得到答案的。我绝对没有排斥这些的意思。但只有真的明确了我们必须用这些才能得到良好的设计,这个时候再动手。

    另,任何一种搭建软件的方法,都会强调抽象和封装,它并非 OO 特有。封装是为了减少要处理的问题的粒度。抽象是为了可以在封装过的粗粒度上进行操作。

    举例,GP 也是一种抽象的手段,但 GP 不是 OO 。GP 的抽象主要解决的是在不同的地方用相同的算法去处理不同类别的东西。

    而 OO 做的是什么呢?OO 做的是把不同的东西归类在一起,在同一地方处理这些东西具有共性的部分。这就是多态。

    先大略浏览了一遍,然后细看一遍评论,再细看一遍文章,我亦蠢蠢欲动,即使我才疏学浅。
    我学习编程亦不过五六载,不过代码量不上万行(其中COPY.PASTE的可能占50%),可能因为我比较向理论派倾斜。
    依我愚见,是否面向对象,亦只不过是实现的一种方式,而在实现之上,思想才是最重要的,COM正是如此,统一的接口体现的是一种抽象的思想,无论你是用C、C++或者PASCAL,只要你实现了相关的接口,就能向外界提供服务。
    说到接口,无不以JAVA为例,可以说JAVA的接口是多继承的一种模拟,实现接口就是告诉我们,我提供了这个方法可以供你调用。其实说到“程序 = 算法 + 数据结构”这个公式,不能不提一下,其实大家都知道算法是和数据结构分不开的,算法就是提供一个数据输入的接口,然后通过特定的处理,返回结果,而相关的数据结构也需要有对于的接口,否则访问不了其内部数据,二者就不能结合起来成为程序了。以我看来,却是无处不接口了。不知道接口是属于过程,还是属于对象的?
    另外提一个经典的问题:为什么下水道的井盖是圆的。其实很简单,因为井口是圆的,它提供了一个圆形的接口,所以它期望一个圆形的盖子,由此你不得不设计一个圆形的井盖。
    废话这么多,好像离题了。其实面向对象最重要的是其抽象的思想,而不是其多态的实现。近来也看了HEAD FIRST的设计模式,不过没看完。诚然,设计模式是一种思想(都是基于面向对象的,至少我没看见基于C的),它可以用C++、JAVA或者C#乃至任何面向对象的语言实现,足以证明实现是不重要的。每个语言都有自己的特征,实现不过是利用语言各自的特征作出最高效率的处理罢了。
    总结一点,免得太乱没人看懂我说什么:面向对象、面向过程不过是实现的一种特定的方法,没有最好的方法,只有最适合的。
    ========================
    Quote:
    比如,我实现一个 GUI 系统(或是一个 3d 世界)。需要实现一个功能——判断鼠标点选到了什么物件。这里,每个物件提供了一个方法,可以判断当前鼠标的位置有没有捕获(点到)它。

    这时最简单的时候方法是:把所有可以被点选的物件都放在一个容器中,每次遍历这个容器,查看是哪一个物件捕获了鼠标。

    例如这里,就要按你的思想来实现了,如果前者,你可以使用Observer,如果后者,你可以使用Iterator。实现是最低级的。
    ========================
    下面我来回答一下“面试题”:
    1。你需要面向对象吗?
    要的,不然我的设计模式没用处。
    2。至于GC,我完成没有相关的编程EXP,所以在此亦不过纸上谈兵而已。GC的重要性无容置疑,至于云风所提出的“你需要 GC 吗?”,我以为应该按需而论,就一个系统的完整性和强壮性来说,一套完善的GC无疑是一个很好的保障,假如你的程序只需要几个函数,一个结构,你却花80%的时间来实现一套PERFECT的GC,无疑就是大材小用了。
    3。你需要所有的类都有一个共同的基类吗?
    依据我的设计,应该是不需要的。
    4。你需要接口可以继承吗?
    JAVA中,我非常需要,因为实现一个接口意味我将重写一次代码。为什么没有多继承呢?

    以上皆为我一些浅见,如有错漏,欢迎批评指教,其他本人选择性失明。

    刚刚看到tinyfool说的C++缺乏一个动态链接的标准的事情。

    这说起来有点风马牛不相及了。即使有这个标准肯定跟C++本身也没有多大的关系,而是windows平台的事情了。

    Java的Interface与COM的Interface还是有区别的,Java的Interface其实是一个tag,类似于标签性质的东东,并非虚拟类一类的东东。

    win32平台本身是允许虚拟类放在动态库中继承使用的,但是标准我感觉难以制定,其实正是鉴于不同开发(语言)平台之间的呼叫方式的区别,MS才弄出来COM的safecall(是叫这个吧?),其实这也算是某种程度上的标准化吧。

    有点扯远了 莫怪

    是否应该考虑加入blog留言的编辑功能 :)

    贴完发现标签有问题,最后一句是:
    《HeadFirst.设计模式》中的部分解答

    >>面向对象的本质就是让对象有多态性,把不同对象以同一特性来归组,统一处理。至于所谓继承、虚表、等等概念,只是实现的细节。

    我完全不这么认为,OO的核心:(按重要性排序)

    抽象,封装,多态,继承

    个人感觉行为抽象和细节封装是OO的核心

    其中多态和继承多被用来制作大型类库,类库这玩意还是个好东西,很多时候我们学习的不是语言,而是类库。

    具体如何OO其实是有些准则的,如封装变化,多用组合,少用继承等(注意少用不是不用,这些都要具体问题具体分析)。

    这些准则其实跟语言本身是否是OO关系不大,譬如常说的针对接口不针对实现,使用c或cpp都可以实做出来。

    换言之,OO是我们看待事物的方式,具体OO的如何还要看OO的主体能力。

    另外,对于一些三五十人参与大型工程(这个大型不是指难度,而是指规模),OO的目的不是让你的系统有多好,而是让你的系统不至于多坏。

    p.s.我来回答下楼主的问题

    >>你需要面向对象吗?
    需要
    >>你需要 GC 吗?
    有无均可
    >>你需要所有的类都有一个共同的基类吗?
    这要看干什么
    >>你需要接口可以继承吗?
    需要
    >>你为什么需要这些?
    可以参看<Head.First.设计模式>中的部分解答。

    在这里又看到了对C++语言本身的争论。作为C++的忠实使用者。在这里为C++平反,C++的最初设计是很符合当时的需求的,从80年代末到90年代中后期的流行,可以看出来。我想,在这里的读者,多数还是从C++出身的吧。

    C++语言的特性是非常的多,所以对于学习和使用都是需要很长的时间。诸多的特性,使得使用者,在选择何种特性作为程序框架的基础的时候,就是一种考验。要做出最佳的选择,需要面向对象设计的知识和长期的积累的经验。所以,使用者在使用C++做项目的时候,失败一两次也不是不常见的。如果我没有记错的话,云风就有这样类似的经验:把一个用C++写的项目用C重写了一次。但是,这样的原因,大概是当初的基础框架没有设计好吧!所以,我猜测--云风大概对C++还是有点微词的。去年,有一场C/C++语言之争。最引人注目的应该是Linux之父对C++语言的贬低了。我听说(没有考证):他当初也想使用C++写操作系统,但是没有成功。

    我喜欢C语言是喜欢它的精巧灵活,最重要的一个原因,是使用C写的代码,给人一种可以掌握全局的感觉,完全在掌握范围之内,即使有错,需要重构,也是在平面内重构。不像C++的类层次结构带来的时立体的重构,无疑,平面比空间更容易掌握。

    当然,C++毕竟是一种在80年代发明的语言,与现在的java、C#是不能相比较的,更不要跟动态语言做比较。这样的比较明显是没有看到各个语言的适用范围。C++语言的各种设计优良的特性,还是被后来者吸收进去的。

    关于,C++没有GC。GC在C++标准化的时候并不是没有。关于,为什么C++标准化没有加入GC,大家可以到google查查。但是,因为没有GC这一点,时常作为攻击C++的理由之一。十年或者是二十年前的内存价格和现在的内存价格作比较的话,应该都得到一个C++不加GC的理由。而java语言有GC是SUN公司战略需要的。

    PS:云风这篇文章有鄙视C++的嫌疑:“C++ 提供了对面向对象的支持,但 C++ 所用的方法(虚表、继承、多重继承、虚继承、等等)只是一种在 C 已有的模型上,追加的一种高效的实现方式而已。” 这里的“只是”和“而已”。是否用得不是很恰当? -_-!!

    PS:我赞同楼上一位仁兄说的:4、90%情况下都不需要严格意义上的类——拿那些C++特性当甜点吧;必须明白,虽然你写的东西以class开头,但那不是类。

    在下最厌恶种类之一就是预言家。准确点说,是装B专家。
    比如说,当初俺的前“白领”大领导就预言俺等蓝领们要被机器取代,要被饿死——其实我想他恐怕也很明白,国企不可能一直这样下去;将来要被饿死的,很可能是他们这些“白领”而不是真能干活的“蓝领”。但这并不妨碍他现在利用职权牟点私利,比如摆出一副救世主的模样,骗骗乃至欺压欺压刚出校门的GGMM。

    近些年一直在网上鼓噪“软件蓝领论”的,恐怕多数都是这种“白领”。

    同样,作为真正做技术的软件“蓝领”,他们的社交范围绝不可能仅限于国内;甚至可以说,不会到国际社区学习求助的,不可能保持长时间高效率的工作,更不可能保持长时间的技术领先——或者说,不是一个合格的“蓝领”。

    这才是蓝领们真正意义上的朋友圈。

    换言之:真正在踏踏实实做着技术的人,他的未来不是其他阴阳怪气的家伙所能预言的。

    ————————
    另回jackyfire朋友:
    面向对象的根本目的,是让程序设计及结构更简单、更可靠而不是相反。

    老外有句话,原话忘了,大概意思就是说“比xxx(一件很难的事)更难的事情,就是让两个方法论专家闭嘴”。

    方法论专家总是向我们承诺,只要照他的流程如何如何,你就一定能得到一个设计良好的系统。
    但现实是:几乎任何两个方法论专家都无法取得共识。
    这些家伙,某些时候简直和原教旨主义者一样讨厌;虽然很多情况下,他们确实能帮助我们。

    有句话说的好,手里有把锤子,看到什么都是钉子。

    扩充工具箱,别老用锤子;让方法论专家一个一个给出建议,然后把他们一脚踢开——这才是正确的做法。

    回到C++等面向对象语言上,就是:如果高层抽象确实能归结于类,那么语言提供的便利不用白不用;就是语言没有提供支持,自己也要做一个出来。但如果抽象来抽象去,怎么看怎么都不对劲,那就别硬赶潮流。
    到实现时,语言甜点只要能简化编码,同样是不用白不用;但假如用了这些甜点便自以为是面向对象,那就错的太离谱了。

    = =!
    封装应该是增加操作的粒度,如果封装使操作粒度变小,那么使用者的工作就会比不封装时更琐碎……

    粒度差不多等于细度,细度影响复杂度,复杂度的结果就是难度,所以云风的表述应该是没有问题的。

    有一个错别字 粒度==>难度

    封装总是为了减少操作粒度,数
    改为
    封装总是为了减少操作难度,数

    纠正一个概念。

    面向对象和基于对象的区别不在于多态性。所基于的对象也同样可以具有多态性,比如FILE* 可以是一个磁盘文件也可以是控制台IO。

    从字面理解,Object Oriented指的是程序的分析、设计、实现是围绕对象为中心来展开的,以对象来分解问题域并提供相应的实现机制。语言提供了实现对象所需要的机制(如:属性、方法、继承、多态等等)即称为面向对象的语言。

    Object Base则是指使用对象,而不是设计和实现对象。

    当一个人的技术提升到超越周围的人太多时,甚至谈论的不是同一个层面,他的道路便到头了。

    他必须要换一个地方和另外一个同级别层次的人合作。

    这样他原先的鹤立鸡群便变成了和周围一群人是一样的了,甚至被领导,也就是泯然众人亦。

    可以说,要么你选错了工作,要么你选错了学习路径

    再次提升,就是面对更激烈的竞争和跟高的要求

    或许要从另一个平台的办事员开始做起和学起

    就看你的突破能力了

    或许你一直会呆在原地

    形而上的不错(此处独立)

    我估计你会宗教化或神学化或者哲学化(至少有一段时间)。

    这是本人对你的预言

    to ghost:
    “一种设计境界或着说是一种思考方式、一种大局观的体现……”这个只是面向对象方法论的一个方面,即所谓的OOA,说它就是面向对像,或者面向对象就是它有点片面了。虚函数表、继承、多态性当然和面向对象的本质无关,他们是面向对象方法论指导下,对语言层面的一种实践。

    @tinyfool

    COM 允许一个实现继承多个接口,因为那是隐藏起来的实现的细节。但不允许一个接口继承多个 COM 接口。这个就是暴露在外的设计规范了。

    所谓编程范式,设计模式乃至什么匈牙利命名法,驼峰命名法,注释,文档什么的不过是西方人以英语为思考模式弄出来的东西而已

    程序是数据+逻辑,这个我们都知道

    为了解放生产力,人类封装数据和逻辑,再将封装好的东西堆砌到一起。以前是用函数,然后有了类,又有了泛型,现在又流行函数式

    我是觉得程序设计,用现实中存在的,最自然的方式去做就行了,大可不必设计必谈模式

    ps:俺说的那个公司是个好几百开发人员的、据说过了cmm 3的中型企业;sz好多外包公司都在为它打工。

    个人觉得,面向对象是一种设计境界或着说是一种思考方式、一种大局观的体现……

    无论如何,它绝不是一种编程技法。

    比如,可以用构造/析构函数来做“自动的成对工作”;但这只是依赖C++具体实现的一种运用技巧。
    其他如虚函数表、继承乃至多态性,也都是如此。
    类似的技巧可以解决很多问题;但它们和面向对象只是碰巧走到了一起,或者是面向对象语言提供的外在的便利。
    根本意义上,这些东西和面向对象没有一星半点本质上的联系。


    去年“误入”一家垃圾公司,他们仗着和某国有部门关系密切,没有竞争对手而瞎搞——自然,他们的代码完全可以作为最垃圾代码的最杰出代表来看。
    也就是这些代码,让我想到了更多的东西,也看透了许多。

    比如说,他们做的一个网上购物系统——倘若照我们的思路,大概应该分为如下3个模块:一个记帐模块,相当于出纳,负责处理点卡、充值、付款等操作;一个连接管理/认证模块,相当于接待员,负责session的维护以及错误信息的翻译等工作;一个UI模块,类似橱窗,以web方式展示商品。之后就只是一些进一步细化、扩充的工作了。
    按这个设计思路走下去,该系统显然是非常灵活、容易扩充、容易重用的——总之,一切都很简单,没什么技术含量。

    知道他们是怎么做的吗?
    一个N多模块构成的巨大的蜘蛛网。
    一个模块,负责用户接入和心跳,同时还要把用户提交的订单里的地址用订单模块的数据库里的信息翻译成商品代码,重新构造订单后再发给订单模块——老大,有这么玩的吗?
    这个接入模块还有更倒霉呢(不好意思,负责实现这个倒霉模块的正是在下-_-|||):由于某种说不清道不明的原因,订单模块需要知道用户当前是否在线(如果不在线,接入模块要负责发短信通知用户登录:但这和订单模块有什么关系?);于是从接入模块又拉了条线过去,直接更新订单模块的数据库去了。
    这样直接访问其他模块数据库的行为还有好多——他们用数据库存储程序状态。
    它的点卡模块,不知道设计者吃了什么,居然搞了3个巨大的表玩联合查询,每个表都要出N多记录——这个N^2复杂度的BT原定要支持千万用户的,结果测试期刚插入1万条记录(不到1K用户),垮了:一次查询就要N分钟;一次统计要7、8小时(俺就是负责调查原因的倒霉蛋之一;轻松找出了原因,却被告知这几个表被很多模块共用,改不了了!)
    最搞笑的还是它的日志审计模块:为了分析每天预计数十G的日志并入库,这个模块让很多类紧密团结在以工厂模式为核心的、以观察者模式为指导思想的一锅粥大家庭里(此外,他还用了包括单建在内的NN个设计模式——光这点就比俺牛。设计模式俺看得快,忘得更快;以后说不定顺手似是而非的用出来时还以为是自己的发明,绝不可能做到如此的原汁原味)。
    等整理出主干一看,它同时启动20个线程读入20个日志文件,全部读完后到达第一个同步点;然后,同时开20个线程分析日志,全部分析完到达第二个同步点;于是同时开20个线程通过网络更新到数据库(全部更新结束为第三个同步点);然后,重复。
    嗯,想象下开20个线程暂不执行,然后一声令下,看它们争抢那点可怜巴巴的资源严重影响效率的可笑景象吧;更可笑的是,争完了,抢完了,刚才的热门资源就被晾在一边,看20个强盗去抢另一个热门——倘若磁盘和网络有思想,想必会发出很多极有哲理性的感叹。

    总之,这个系统就是这样一个蜘蛛网构成的模块构成的蜘蛛网;时不时还要在模块间走上几条飞线,组成个连设计者都有点不好意思提的地下暗网。
    俺当时的顶头上司很有正义感的告诉我们:他们软件白领花了好大的力气才能把这个蜘蛛网理清,然后俺们这些软件蓝领们才能把一条条的线织出来。然后语重心长地告诫我们,“你们这些照图织网的家伙很快就会被机器替代”。

    好吧,我的意思就是:
    1、某种意义上,C++(以及设计模式乃至任何面向对象语言)很垃圾。因为它很容易造就如上所述的那些走火入魔者——不幸的是,他们往往还掌握着话语权。
    2、依现实做出设计,为目标选择语言;若非必要,放弃任何“高级”特性乃至任何“特性”——C++很好,但我们先要学会放弃。
    3、设计类是极高难度的现实工作;评判一个类的好坏都至少需要你先彻底搞明白什么是“正交分解”——至于搞明白设计目标及应用场合,那是基础中的基础。
    4、90%情况下都不需要严格意义上的类——拿那些C++特性当甜点吧;必须明白,虽然你写的东西以class开头,但那不是类。
    5、C++给的好东西太多了,以至于如何克制自己不去滥用都成了学问——正如云风这篇文章。
    6、unix的泛文件设计是真正面向对象的,虽然他们用C;C基础库的QSort也已经有了点面向对象的味道,虽然它用了void *;而前面说的那个项目则连结构化的程度都没做到——虽然他们用的是以设计模式粘连的C++“类”。
    7、很多时候,你真的不知道和你说话的是什么。有的人,和他多说一个字都是浪费。
    8、如果哪位读者误入了类似的垃圾公司,哪怕像我一样还被压着一个月的工资——早点走吧。等被改造成笨蛋就不好玩了^_^


    很同意对于面向对象的看法,不过俺对COM不知,不发意见。不过在C语言里面,面向对象的开发已经流行很多年了,而且也有各种不同的方式.面向对象的实现细节的确不是那么重要了,不过现在有个思想的确已经很深入人心了,就是面向接口编程而尽量不是去通过继承实现来复用。

    不过我个人不太同意不是面向对象的程序不需要GC, GC应该并不绑定对象这个概念,虽然目前的大部分实现都是如此。GC本身,不考虑所谓便利性,很大程度是为了隐藏内存这个概念,一个比较“高级”的语言,无论它是不是“面向对象”的,如果他有内存管理,而这个内存管理并不希望用户能“看到”的话,就可能需要GC.

    面向对象只是一个所谓的编程范型,把东西组织为对象,也会出现所谓的对象悖论,人家本没有速度,完全就是你这个观测者强加的。不过现在流行的语言都是带有OO编程范型的支持的。不过随着其他范型比如FP的重新流行,OO不再是救命稻草了,看上去和其他范型混合的趋势越来越大。

    个人意见!

    以前我也认为C++ 对面向对象的支持的实现本身就是面向对象的本质。现在,也不再如此觉得,面向对象应该是一种看待事物的角度罢了,语言只是客观的一种实现工具,与人怎么去看待、思考问题毫无关系。

    最近也在想,“你需要所有的类都有一个共同的基类吗?你需要接口可以继承吗?你为什么需要这些?”这些问题,感谢云风分享这些思考,学习很多。

    对文中的思想我没有太大的意见,有几个细节商榷下
    1 这个其实是小问题,我觉得你说的基于对象跟一般概念说的基于对象不太一样,这不是啥大问题,我感觉一般说的基于对象更像是说利用一个面向对象的程序库去做程序.或者说,我觉得一般概念的基于对象,可以说是使用对象,而不是创造新对象.我觉得你用的基于对象这个词,比较像用对象这个概念来思考问题解决问题.

    2 如果我没有记错的话,Com是可以接口多继承的.也许是咱们理解的差异,但是我确实记得是可以的.C++的一个备受争议的点就是多继承,Com对此的策略是实现不能多继承,但是接口可以.Java也是如是.我觉得这是一个思路的进步,这个比较符合你的文章主题,就是实现的继承其实没有接口的继承那么重要.

    3 我对Com的认识在于,C++缺乏一个动态链接的标准,加上支持多继承,二进制层面无法提供一致性.比如在win平台下用C用不同的开发工具编写同一个函数,或者是不同函数的版本,只要函数名,参数和参数压栈顺序相同,那么DLL的接口一致性是可以保证的.但是由于前面说的C++缺乏一个动态链接的标准,加上支持多继承,如果你提供的接口是类,或者类的一个方法,接口一致性就无法保证.而保证接口一致性的方法其实不难,实际上就是接口要用纯虚基类.其实java的interface关键字跟这个也是一脉相承的,只要是纯虚基类,其实就可以保证二进制的一致性.所以在我看来COM的最核心就在于动态链接+纯虚基类继承,也就是接口继承.AddRef Release是个半自动化的内存管理,QueryInterface是一个动态的接口查询,他们都是辅助,不是核心内容.

    展开全文
  • 感兴趣的同学可以试试把上述离散二元特征当做连续变量使用,构造几个数据,计算其相关系数并进行主成分分析。发现其相关系数就是-1,主成分分析后自动就变成一个主成分了。可见 PCA对于连续特征与非连续特征都是去除...

    作者:龙心尘 &&寒小阳
    时间:2016年1月。
    出处:
    http://blog.csdn.net/longxinchen_ml/article/details/50493845
    http://blog.csdn.net/han_xiaoyang/article/details/50503115
    声明:版权所有,转载请联系作者并注明出处

    1. 剧情一:挑螃蟹的秘密

    李雷与韩梅梅的关系发展得不错,趁国庆休假一起来天津玩。今天,李雷十分神秘地请韩梅梅去一家餐馆吃螃蟹。韩梅梅大失所望,这个餐馆很不起眼,感觉就像路边的老食堂。菜单都用粉笔写在黑板上,一点都不高档。一看价格,满黄螃蟹120块钱一只!这也太贵了。

    李雷看到了韩梅梅的神情,笑着解释道:“这家店老板有一个绝活——会看螃蟹。他能保证120块的螃蟹就是满黄。如果拆开来不是,这个螃蟹就不要钱,再换一个。靠着老板的绝活,这家店已经是几十年的老店了,在当地非常有名气。郭德纲、赵丽蓉这些天津社会名流都来这家店吃过螃蟹。”

    韩梅梅将信将疑。拆开螃蟹,饱满的蟹黄喷薄欲出。韩梅梅边吃边惊叹:“从没有吃个这么好吃的螃蟹!”

    李雷接着说:“老板的绝活密不外传,几十年来都自己上货。虽说是一个大老板,一年到头满身海鲜味。而且他也不开分店。”

    韩梅梅说:“那是,这么高明的绝活只有他自己知道才能挣钱啊。”这时,韩梅梅拂面而笑,突然想考一考自己的相亲对象,说:“李大码农,你不是做机器学习的吗?如果要你去用机器学习挑满黄的螃蟹,你怎么做?”

    2. 初步划定特征的范围,获取特征

    李雷早就想过这个问题了。长期的职业素养让他对任何事情都想用机器学习的方法去鼓捣。李雷的基本思路是这样的,我们尽可能观察螃蟹更多的特征,从中找出与“螃蟹满黄”最相关的特征来,帮助我们去判断。当然特征有非常多,我们可以先头脑风暴一下:

    1. 一些直观的特征:包括蟹壳的颜色和光泽度、钳子的大小、肚脐的形状、螃蟹腿的粗细和长度、眼睛的大小和颜色光泽、螃蟹的品种、重量、体积、腰围等等……
    2. 一些需要在互动过程中观察到的特征:螃蟹钳子的力量,对外界刺激的反应,用筷子触碰螃蟹眼睛后的反应,螃蟹行动的速度……
    3. 还有一些外部环境的特征: 收获螃蟹的季节,培养螃蟹的水域……

    韩梅梅插话到:“这么多特征我头都大了,你还有完没完?”

    其实,如果真要穷举出所有的特征可能永远也举不完。但是我们目的很明确——判断螃蟹是否是满黄。所以我们只关心跟这个问题(“标签”)相关的特征,它们只占所有特征中很小一部分。怕就怕一些糊涂的需求方连目的都不明确就要求一通乱搞,即便出来了一堆结果,也不知道有什么用。

    头脑风暴完之后,很重要的一点就是找到对这个问题有长期经验的人,虚心向他们学习。人脑其实是一个很好的特征筛选器,这些经验可以给我们非常多的指导和启发,极大地减少我们试错的工作量。比如我们可以直接去找海鲜市场问螃蟹贩子,去田间地头找螃蟹养殖户,去海鲜饭店去问有经验的采购员和厨师……他们的最一线的经验是特征工程中的宝贵财富

    但这里需要考虑将经验转换成可量化的指标,才能便于机器学习。比如人们可能会说螃蟹很“活跃”、很“精神”,或者很“慵懒”。这些特征需要转换成一些可量化指标去衡量,具体怎么转换也有很大学问。

    接下来要考虑的问题是对这些特征的可用性进行简单的评估。比如:

    1. 特征获取、描述难度
    2. 数据的规模
    3. 特征的准确率
    4. 特征的覆盖率
    5. 其他

    我们通过明确目标,头脑风暴,咨询专家,特征量化,可用性评估等流程,就基本划定了特征范围

    3. 剧情二:“特征预处理”的门道

    李雷说完,便拿出自己的平板,给韩梅梅看自己某个项目中搜集的初始特征。这些特征被放在一张巨大的表里。

    韩梅梅看着这些密密麻麻的数字,心想:看李雷说得头头是道,但还是没告诉我怎么挑,不能让他轻易绕过去。

    于是她说:“我看你这些特征数据有大有小,有些就是几万上下浮动,有些仅仅是小数点后好几位的微小变化,有些就是在0或1这两种可能中变化,有些连值都没有。你这些数据能用吗?”

    李雷说:“不能,要转换成标准件。”

    韩梅梅:“标准件?”

    4. “特征标准件”

    如果把机器学习过程当做一个加工厂的话,那输入的数据(特征、标签)就是原材料,输出的模型和判断结果就是产品。并不是胡乱扔进去任何原材料都能加工出合格产品的。原材料需要一个“预处理”过程才能方便地被算法处理。这些预处理后的数据,李雷起了个不够规范的名字,叫做“特征标准件”

    以二分类问题为例,不同的算法对“特征标准件”的要求是不同的。比如逻辑回归和神经网络,比较喜欢归一化之后在[-1,1]区间内浮动的特征。而贝叶斯方法,喜欢因子化之后的{0,1}分布的二元特征,每个特征只有“是”和“不是”两种可能的状态。

    5. 连续特征与非连续特征

    特征可以分为两类:“连续特征”和“非连续特征”。

    “身高”、“体重”、“成绩”、“腰围”、“长度”、“宽度”、“体积”、“速度”等等,都是连续特征。连续特征能够比较方便地进行归一化。归一化的统一公式如下:

    x=xμS
    μ 为所有样本数据的均值, xμ 的步骤叫做去均值化

    1. S=xmaxxmin 时,经过处理的数据在区间[-1,1]之间。
    2. S=σ 时,经过处理的数据符合标准正态分布,即均值为0,标准差为1

    另一方面:“是否高富帅”、“是否白富美”、“螃蟹的品种”、“螃蟹所在的水域”、“收获螃蟹的季节”等等,都是非连续特征。非连续特征能够比较方便地进行因子化,或者它本身就是二元特征。方法如下:

    特征“收获螃蟹的季节”:{春,夏,秋,冬} 因子化后的结果为:

    • 特征“是否春”:{是,否}
    • 特征“是否夏”:{是,否}
    • 特征“是否秋”:{是,否}
    • 特征“是否冬”:{是,否}

    6. 两类特征的相互转化

    连续特征可以当非连续特征来用,非连续特征可以当连续特征来用

    连续特征可以离散化非连续特征。比如“年龄”作为一个连续特征,假设它的取值范围是[0,100]。我们可以中间切一刀,比如选择60(岁)。大于等于60岁的就叫做“老年”,小于60岁的就是“非老年”,这样就转化成了一个二元特征了。怎么选择离散的分离边界也很有学问

    如果我们中间切两刀甚至更多刀,比如18(岁)和60(岁)。大于等于60岁的就叫做“老年”,18岁到60岁之间的就叫做“中青年”,小于18岁就叫做“未成年”。然后再把这3类因子化成3个二分类就够了:“是否老年”、“是否中青年”和“是否未成年”。

    等宽直方图

    非连续特征因子化成二元特征{0,1}后可以直接当做[0,1]之间的连续特征来用。我们之前文章《机器学习系列(3)_逻辑回归应用之Kaggle泰坦尼克之灾》就是这么使用的。

    7. 去除特征之间的共线性

    我们在对离散特征因子化过程中细分到二元特征为止即可。那对于二元特征本身能否因子化成两个特征?比如以下例子:

    特征“螃蟹的性别”:{公,母} ,可否转换为:

    • 特征“是否公螃蟹”:{是,否}
    • 特征“是否母螃蟹”:{是,否}

    这是不行的,因为这两个特征的信息完全一样,也叫做共线性。计算这两个特征之间的条件熵

    H(|)=0

    也可以用计算条件熵的方法去衡量两类离散特征的差异性,方便去除共线性关系的特征

    连续特征也有着共线性的情况,比如同一个品种的螃蟹腿的“长度”和“粗细”是共线性关系。也就是说,如果我们知道螃蟹腿的长度是 x 厘米,那么螃蟹腿的直径就是kx厘米, k 是一个稳定的常数。因此我们只需要螃蟹腿的“长度”这一个特征就够了。那么连续特征的共线性如何去除?

    可以计算两个变量(x,y)相关系数

    rxy=cov(x,y)σxσy=cov(x,y)cov(x,x)×cov(y,y)
    rxy 的取值范围是[-1,1],如果是0则统计独立,如果接近1则强相关。

    可以计算这些数据的协方差矩阵,进而求出相关系数矩阵。就可以比较任意两个特征了。

    协方差矩阵
    相关系数矩阵

    既然协方差矩阵都求了,那就干脆用主成分分析(PCA)吧,这样更省事。得到主成分,线性相关的那些量就直接被舍弃了。我们在前文《深度学习与计算机视觉系列(7)_神经网络数据预处理,正则化与损失函数》 对PCA有相关论述。

    感兴趣的同学可以试试把上述离散二元特征当做连续变量使用,构造几个数据,计算其相关系数并进行主成分分析。发现其相关系数就是-1,主成分分析后自动就变成一个主成分了。可见PCA对于连续特征与非连续特征都是去除共线性的通用方法

    8. 缺失值的处理

    这个问题现在才讲,但实际过程中应该在前期去处理。掌握以下三点就够了:

    1. 如果某个特征的缺失值比较多:可能就直接舍弃。
    2. 如果缺失值不是很多,而且是连续特征:可以考虑用回归方法去拟合,或者直接用众数、中位数、平均数等具体的值去替代即可。
    3. 如果缺失值不是很多,而且是非连续特征:可以尝试把缺失值当做一个新的类目去处理,可能也揭示了一定的客观现实。

    9. 离群点的分析

    对于连续特征,最好看看其在样本中的分布。如果某些值偏离了主要聚集区域,可能需要单独抽出来分析,里面可能包含了更多的信息,可以这样画图方便观察:

    离群点

    10. 特征预处理小结

    特征的预处理步骤比较多,相互之间的关系比较复杂。我们画了一张图以揭示它们之间的关系:

    特征预处理

    11. 剧情三:李雷另辟蹊径挑螃蟹

    韩梅梅长叹一口气:“终于听你叨逼叨逼说完了。”

    李雷说:“没办法啊,这块工作其实挺多的。我还要好多没说完……” “你打住”,韩梅梅赶紧说,“我算服了你了。但是李大码农,你还没有跟我说你怎么靠这些特征挑螃蟹呢。”

    李雷说:“不急,用逻辑回归……”韩梅梅说:“不要用逻辑回归,我已经从赵媒婆那知道了。你换个方法,用非连续特征来做。”韩梅梅存心想刁难她的相亲对象。

    李雷说:“那可以用贝叶斯。”

    12. 用贝叶斯方法挑螃蟹

    我们的标签用 Y= 来表示,相应的 Y= 。Xi表示所有离散化的二元特征,如 X1=X2=X3= 。于是在已知这些特征的情况下,该螃蟹“是满黄”的概率如下:

    P(Y|X1,X2,X3...)=P(X1,X2,X3...|Y)×P(Y)P(X1,X2,X3...)

    其实,可以直接判断 P(Y|X1,X2,X3...) 是否大于1/2即可。因为 P(X1,X2,X3...) 这一项算起来比较麻烦,我们用以下方法直接把它约掉。

    先求出螃蟹“不是满黄”的概率:

    P(Y|X1,X2,X3...)=P(X1,X2,X3...|Y)×P(Y)P(X1,X2,X3...)

    再两式相处,得到:

    P(Y|X1,X2,X3...)P(Y|X1,X2,X3...)=P(X1,X2,X3...|Y)×P(Y)P(X1,X2,X3...|Y)×P(Y)

    这样就约去了P(X1,X2,X3…)。只需要判断 P(Y|X1,X2,X3...)P(Y|X1,X2,X3...) 是否大于1即可。但是,工程上用除法不太方便,两边同时取对数log,得到:

    logP(Y|X1,X2,X3...)P(Y|X1,X2,X3...)=logP(X1,X2,X3...|Y)P(X1,X2,X3...|Y)+logP(Y)P(Y)

    左边是螃蟹“是满黄”的逻辑发生比,只需要判断其是否大于0即可

    到目前为止,以上都是等价变换

    接下来我们引入贝叶斯方法中常用的条件独立假设:

    P(X1,X2,X3...|Y)=P(X1|Y)×P(X2|Y)×P(X3|Y)...
    P(X1,X2,X3...|Y)=P(X1|Y)×P(X2|Y)×P(X3|Y)...

    将它们带入上式,就变成了:

    logP(Y|X1,X2,X3...)P(Y|X1,X2,X3...)=logP(X1|Y)P(X1|Y)+logP(X2|Y)P(X2|Y)+logP(X3|Y)P(X3|Y)+...+logP(Y)P(Y)

    于是我们得到了一个简单的求和式,只需要判断等式右边求和的结果是否大于0即可。而最关键的就是右边每一项都非常好求!假如训练集中所有的满黄螃蟹收集在一起,统计每一个特征出现的次数,除以满黄螃蟹的总数,就是其相应的条件(后验)概率了。再统计该特征在非满黄螃蟹集合中的条件(后验)概率,二者相除再取对数即可。

    13. 用贝叶斯方法进行特征有效性分析

    等式右边作为一个求和式,其中每个求和项 logP(Xi|Y)P(Xi|Y) 的绝对值越大,其对结果的影响越强烈,相对应的特征就是显著特征。而绝对值比较小的特征就是非显著特征,剔除掉也不会很明显地影响结果。这就完成了一个特征筛选的过程。

    我们再分析一下各个求和项的结构,里面的概率部分是后验概率,是特征相对于标签的后验概率。这个后验概率与我们上一篇文章《从白富美相亲看特征预处理与选择(上)》中的后验概率方向相反,不要搞混淆了。

    14. 贝叶斯与逻辑回归之间的关系

    我们继续看看这个求和项,是不是很像逻辑回归中的求和项?我们如果拿二元特征当做连续变量采用逻辑回归方法。其判别式如下:

    z=w1x1+w2x2+w3x3+...+b;xi{01}

    二者的表达式惊人地相似!莫非 logP(Xi|Y)P(Xi|Y)=wi ,二者一模一样?

    感兴趣的同学可以自己举个例子试一下,发现还是有区别的,二者求出来的权重不一样。产生这样差别的原因是什么呢?

    想必大家都猜到了。就是贝叶斯方法引入的两个条件独立假设。正因为这两个条件独立假设,贝叶斯方法直接跳过了逻辑回归中反复迭代用梯度下降法才能求出的各个权重。

    因此贝叶斯方法与逻辑回归的区别就是贝叶斯方法引入了一个更强的附加假设,而且可以直接通过统计结果求权重,而不必用梯度下降法。

    所以有些情况下贝叶斯方法求出来的结果不好,就可以考虑考虑是不是条件独立假设的原因

    因此,可以说“在某种假定下,可以证明:与朴素贝叶斯分类方法一样,许多神经网络和曲线拟合算法输出最大的后验假定。”——韩家炜:《数据挖掘:概念与技术(第三版)》

    15. 剧情四:李雷露馅儿了

    韩梅梅听完,十分感慨地说:“难怪机器学习能挑出正确的结果,难怪赵媒婆用机器学习方法从这么多人中能把你挑出里来。你还是有两下子嘛。”

    “废话,她是我干妈”,李雷志得意满,不小心说漏嘴。 韩梅梅:“什么?!”

    李雷后悔不已,尴尬地陪着笑脸说道:“梅梅,我错了,我不该瞒你这么久。”到了这个地步,李雷只能和盘托出了。

    16. 数据VS算法

    其实李雷早就知道韩妈妈要挑选相亲名单,如果按她的标准,李雷根本没法进入名单中。而李雷也猜想她会去找赵媒婆。他就早早地联系赵媒婆,跟她推销他的机器学习方法。赵媒婆终于被李雷忽悠动心了。李雷就帮她开发那个相亲算法。但其实赵媒婆的样本数量不够,特征数量却非常多,肯定会过拟合。李雷就跟她说他会多找一些相亲的数据。李雷能从哪里找啊,只能发动周围的同学,让他们找他们观察到的情侣案例。而在这些群体中,恰好中学、大学是同学的情侣比率非常高,而且很多男方是码农。而李雷刚好符合这个条件,李雷的评分就非常高了。

    因为样本选择本来就代表性不足,没能覆盖更多的青年群体,所以还是过拟合,只是偏向了李雷这边的概率而已

    可见,做机器学习虽然看起来比较炫酷的是算法,但真正关键的是数据。数据决定了你结果的上限,而算法只是尽可能逼近这个上限。而这点李雷并没有告诉赵媒婆。

    对样本情况的分析要在特征优化过程中尤其注意。整个流程图如下:

    特征处理

    17. 特征选择的局限性

    而且,李雷并不觉得感情这样复杂的东西能够用赵媒婆那些量化的指标衡量好的。房子、车子、学历、文凭这些并不能衡量两个人之间的感情。一些非常重要的特征是难以量化的,比如两个人的“三观”、两个人对待感情的态度、两个人相互相处的独一无二的经历、两个人刻骨铭心的情感体验、那种两个人相信能够一辈子都在一起的笃定的感觉……这些至关重要的特征极其复杂,却非常难以量化。所以对于这类问题,机器学习的能力还是很有限的。

    18. 剧情五:尾声

    韩梅梅听完李雷,既生气,又好笑,还有一点点小感动:这小子为了感情还是蛮拼的。

    一段沉默之后,韩梅梅笑着对李雷说:“好了好了,我不怪你了。”李雷长舒一口气。

    韩梅梅继续说:“问个挑螃蟹的问题。你刚才选了这么多特征。为什么不考虑用B超直接照一下,看看里面什么东西不就成了吗?”

    李雷一听,犹如当头一棒,整个脑子都被草泥马占满了:“我去,这么简单的方法我怎么想不到?!”

    韩梅梅这时已经笑得肚子痛了,根本说不上话。

    李雷吐槽到:“梅梅,你太厉害了。我觉得机器永远也学不到的两样东西就是人类的情感和脑洞啊!”

    19. 后记

    其实博主也没有丧心病狂到抓只螃蟹去照B超,只是自己被这个想法逗乐了,大家开心就好哈。O(∩_∩)O~~

    如果真要死磕,据说B超的穿透力比较弱,对骨骼、空气等很难达到深部,因此难以成像。但是通过声波的回声来判断,也是一个思路。就像有些人可以通过拍打西瓜听声音来判断它甜不甜的道理一样。

    如果不用机械波而用电磁波,比如X射线,估计哪怕能看到螃蟹满黄顾客也不会吃了。顾客也会担心放射残留的。CT应该好些,但是贵呀。一套设备下来,螃蟹估计也不止120块钱了吧。没玩过CT,不知道成本多少……总之还是要考虑获取特征的成本的。

    展开全文
  • 所以我们有了单元测试JUnit,相当于在每个上面挂上一大堆main函数,然后每个main函数对于里面需要进行测试的方法进行单独测试。这样子就可以将错误尽快处理掉,防止错误扩散之后不好找。 四. 其他的经验 4.1 ...
  • 软件中的对象,模块,函数等等)应该对于扩展是开放的,但是对于修改是封闭的。 合成 / 聚合复用原则 优先使用合成 / 聚合,而不是继承。 接口隔离原则 客户端不应依赖它不需要的接口。如果一个接口在实现时,...
  • 设计模式总结

    2021-06-20 18:13:53
    设计模式基于六大原则: 开闭原则:一个软件实体如、模块和函数应该对修改封闭,对扩展开放。 单一职责原则:一个只做一件事,一个应该只有一个引起它修改的原因。 里氏替换原则:子类应该可以完全替换父类。...
  • 常用设计模式

    2021-05-14 14:15:37
    1977 年,美国著名建筑大师、加利福尼亚大学伯克利分校环境结构中心主任克里斯托夫·亚历山大(Christopher Alexander)在他的著作《建筑模式语言:城镇、建筑、构造(A Pattern Language: Towns Building ...
  • 设计模式的学习与解析前言设计模式的六大原则开闭原则单一职责原则里氏替换原则依赖倒置原则迪米特法则接口隔离原则构建型模式...一个软件实体,如,模块,,函数,应该对修改封闭,对扩展开放 单一职责原则 一个
  • unity游戏开发学习笔记

    千次阅读 2020-09-02 21:08:12
    unity note: 游戏和三维互动内容开发工具,专业游戏引擎 游戏引擎: 一款游戏最核心的代码 包含: 渲染引擎,物理引擎、碰撞检测,音效、脚本引擎、动画系统 ... 对象 Scene 右键旋转 滚轮->前进后退 F键—>
  • Java基础

    2021-10-18 21:14:38
    Java是一门面向对象编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承、指针等概 念,因此Java语言具有功能强大和简单易用两个特征。极好地实现了面向对象理论,允许程序员以优雅的思维方式 ...
  • Python读书笔记

    2020-04-03 11:43:55
    math_func(type) : # 定义三个局部函数 … # 返回局部函数 if type == “square” : return square if type == “cube” : return cube else: return factorial 由于局部函数的作用域默认仅停留在其封闭函数,...
  • 设计模式基于六大原则: 开闭原则:一个软件实体如、模块和函数应该对修改封闭,对扩展开放。 单一职责原则:一个只做一件事,一个应该只有一个引起它修改的原因。 里氏替换原则:子类应该可以完全替换父类。...
  • 前言 最近准备好好回顾一下设计模式的知识,提高一下...聚性:模块各个元素之间相互结合的紧密程度的度量,聚性越强,独立性越高。通俗的来讲,一个人眼疾手快,身体各个部位配合十分到位,这是咱们所期望...
  • 设计模式

    2020-08-31 15:02:50
    开闭原则:一个软件实体如、模块和函数应该对修改封闭,对扩展开放 单一职责原则:一个只做一件事,一个应该只有一个引起它修改的原因 里式替换原则:子类应该可以完全替代父类。也就是说在使用继承时,只扩展...
  • 截止到公元 2017 年 11 月 7 日晚上 10 点,菜单、工具栏与快捷键依然是几乎所有带图形界面的软件的标配。我们已经习以为常,每天像杂货铺的老板一样清点着它们。...我将所有的 agn_xxx_alloc 函数更名为 agn_x...
  • 设计模式的世界丰富多彩,比如生产一个个「产品」的工厂模式,衔接两个不相关接口的适配器模式,用不同的方式做同一件事的策略模式,构建步骤稳定、根据构建过程的不同配置构建出不同对象的建造者模式等。...
  • 设计模式-构建型模式

    2020-10-23 13:48:07
    面向对象的特点是可维护、可复用、可扩展、灵活性好,它真正强大的地方在于:随着业务变得越来越复杂,面向对象依然能够使得程序结构良好,而面向过程却会导致程序越来越臃肿。 让面向对象保持结构良好的秘诀就是...
  • 最少承诺原则和单一职责原则 介绍 在这篇文章中,我想介绍单一责任原则( SRP )。 我认为这是任何干净且设计良好的... http://en.wikipedia.org/wiki/SOLID_ (面向对象的设计)这是Robert C. Martin撰写的SRP的PD...
  • 单一责任原则

    2020-04-19 07:58:05
    介绍 在这篇文章中,我想介绍单一责任原则( SRP )。 我认为这是任何干净且设计良好的系统的基础。... http://en.wikipedia.org/wiki/SOLID_ (面向对象的设计)这是Robert C. Martin撰写的SRP的PDF文件https://do...
  • 不过它的确很有价值,尽管它不是一颗“银弹”,却可以算是一把“银钳子”,可以帮你始终良好地控制自己的代码。重构是一个工具,它可以(并且应该)用于以下几个目的。 重构改进软件的设计 如果没有重构,程序的...
  • 不过它的确很有价值,尽管它不是一颗“银弹”,却可以算是一把“银钳子”,可以帮你始终良好地控制自己的代码。重构是一个工具,它可以(并且应该)用于以下几个目的。 重构改进软件的设计 如果没有重构,程序的...
  • 初识JAVA

    2021-03-29 14:05:18
    JDK8 新特性及更新修改: (我们经常使用的版本) Lambda表达式 函数式接口 方法引用和构造器调用 Stream API 接口中的默认方法和静态方法 新时间日期API 二主要应用领域与优势 1.应用领域 java自诞生以来,随着不断的...
  • 上图是我实验室与一家公司合作的项目,机器人可以开拉链、检查包裹、用钳子撤除炸弹等,都是可以实现的。现在的机器人,机械控制这一块已经很不错了,但这也不是完全管用。比如上面提到的波士顿动力学公司的机器人...
  • Python实现电子词典(图形界面)

    千次阅读 2019-10-02 15:08:31
    Handler:处理客户端请求以及客户端图形化界面 readme.txt # !/use/bin/env python # coding=utf-8 ''' module: settings.py name: no樂on date: Tue Aug 13 16:39:40 2019 account: ...
  • 而现在,上面既可以挂苹果,也可以挂梨子,甚至还可以挂香蕉(不同类型的对象),而我们的操作方法即可以用手摘,也可以用挂钩拉,还可以用钳子夹(作用于结构上的不同的操作),当然以后还可以包...

空空如也

空空如也

1 2 3
收藏数 57
精华内容 22
关键字:

内嵌子对象类构造函数