Snailclimb / JavaGuide

「Java学习+面试指南」一份涵盖大部分 Java 程序员所需要掌握的核心知识。准备 Java 面试,首选 JavaGuide!
https://javaguide.cn
Apache License 2.0
146.5k stars 45.57k forks source link

面向过程 :面向过程性能比面向对象高?? #431

Open ryouaki opened 5 years ago

ryouaki commented 5 years ago

面向过程 :面向过程性能比面向对象高。 因为类调用时需要实例化,开销比较大,比较消耗资源,所以当性能是最重要的考量因素的时候,比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发。

----》》 这个并不是根本原因,面向过程也需要分配内存,计算内存偏移量,Java性能差的主要原因并不是因为它是面向对象语言,而是Java是半编译语言,最终的执行代码并不是可以直接被CPU执行的二进制机械码。

而面向过程语言大多都是直接编译成机械码在电脑上执行,并且其它一些面向过程的脚本语言性能也并不一定比Java好。

guang19 commented 5 years ago

@ryouaki 感谢,学习了

guang19 commented 5 years ago

我觉得这个得分情况而定,c和c++就是很好的例子,两种语言执行效率差距其实并不大,但是两种语言的设计思想就不同。

ryouaki commented 5 years ago

@ryouaki 请教一下,是不是可以概括为:“程序的性能首先由编程语言的执行方式有关,其次才是设计范式”

设计范式和性能无关的。主要是编程语言的运行机制决定的。

ryouaki commented 5 years ago

我觉得这个得分情况而定,c和c++就是很好的例子,两种语言执行效率差距其实并不大,但是两种语言的设计思想就不同。

c和c++的运行机制是一样的。都是编译成机械码。

guang19 commented 5 years ago

感谢

321cto commented 5 years ago

收益了,感谢

LiuRuiXiang commented 5 years ago

长知识了

rockhu01 commented 4 years ago

学习了

Lotus-Blue commented 4 years ago

amazing gay

konigsbergg commented 4 years ago

good job

codingKings commented 4 years ago

到底是面向过程性能高还是面向对象性能高?这类问题很难回答,非要比较高低的话就必须设定一个场景,否则只能说是各有所长

peacecoder commented 4 years ago

另外,现在java 性能不一定低,变化很大了

ryouaki commented 4 years ago

另外,现在java 性能不一定低,变化很大了

上半年刚测的。

cjjMichael commented 4 years ago

感觉这个表述-"而是Java是半编译语言,最终的执行代码并不是可以直接被CPU执行的二进制机械码"不是很准确。最终执行代码不都是二进制码吗?(在此之前是低级语言代码,如汇编)个人觉得可以表述为编译过程的复杂程度的不同导致执行时间的不同(性能的主要度量)。第二,直接比较语言性能本来就是不恰当的。第三,现在的Java今非昔比,底层编译更加优化。第四,有限的测试不具有说服力,具体情况具体分析。

cjjMichael commented 4 years ago

修正一下是"中间语言代码"。

haichuanjiang commented 4 years ago

可以

yongmingcode commented 4 years ago

谢谢,学习了

zenglanyuan commented 4 years ago

讨论比回答更精彩。哈哈哈

makronyang commented 4 years ago

java 是解释语言 执行的时候 比直接 编译出来的 二进制执行码多了一个步骤

codeplayer-zzy commented 4 years ago

学习中

ttr5966 commented 4 years ago

性能肯定没有面向过程高 但是面相对象后期的维护 还有扩展性 比面向过程好一点------------------ 原始邮件 ------------------ 发件人: "zhangzuyi"notifications@github.com 发送时间: 2020年3月30日(星期一) 中午1:08 收件人: "Snailclimb/JavaGuide"JavaGuide@noreply.github.com; 抄送: "Subscribed"subscribed@noreply.github.com; 主题: Re: [Snailclimb/JavaGuide] 面向过程 :面向过程性能比面向对象高?? (#431)

学习中

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub, or unsubscribe.

iloooo commented 4 years ago

@Snailclimb “1. 面向对象和面向过程的区别”, 根据guide中解释的内容我觉得应该是面向对象和面向过程优缺点更准确些,而它们两个区别应该讨论OOP和POP解决问题的方式不同。 面向过程解决问题方式:把解决问题的过程,拆成一个个方法,通过一个个方法的执行解决问题。 面向对象解决问题方式:先抽象出对象,然后用对象执行方法的方式解决问题。 @ryouaki 我还想补充讨论下,首先面向过程和对象是一种编程思想,就像@cjjMichael 所言“直接比较语言性能本来就是不恰当的”,语言执行性能应该从他们最终执行方式上讨论。直接编译成机器码,然后执行的语言(比如C,编译器一次性编译为平台相关的机器码),从过程复杂度上,肯定比解释执行的语言(HTML,JavaScript,浏览器的解释器,把代码一行行解释为机器码)和 Java这种中间码(编译)+虚拟机(解释成机器码)的方式,要性能高的多。

yuanjinzhong commented 4 years ago

惊呆了

wangpeipei90 commented 4 years ago

同意。语言的执行方式和OOP/POP没有必然的关系。从原理上来讲,OOP的语言也可以直接编译成字节码,而POP的语言也可以解释执行。

907739769 commented 4 years ago

个人理解:JAVA为了一次代码,处处运行(也就是可移植性),有了一层中间码也就是字节码,字节码的作用就是为了可移植性,字节码转换成机器码的工作就交给了java虚拟机,由java虚拟机解释成机器能够运行的二进制机器码。而C语言是直接编译为机器码,机器只需要直接运行二进制机器码即可,而无需和java一样在运行时解释为机器码。从这个角度看,性能与面向过程或对象是无关的。(JAVA只是为了移植性舍弃了一部分性能。)

mjiuming commented 4 years ago

其实感觉编程范式的侧重点并不在于性能,在于“通过限制你可能出错的细节而给你更多的自由空间”,在Robert.C.Martin的《架构简洁之道》中第三章末尾有对编程范式进行总结

结构化编程对程序控制权的直接转移进行了限制和规范

面向对象编程对程序控制权的间接转移进行了限制和规范

函数式编程对程序中的赋值进行了限制和规范

每一种编程范式都从某一方面限制和规范了程序员的能力。没有一个范式是增加新能力的。

其实也可以这么理解。通过限制goto的使用,我们可以不必在意程序控制权的跳转细节,从而给我们带来了更多的灵活性。通过封装对象的状态,我们可以安全地控制对象所处状态的有效性,从而可以使用更多设计模式。通过限制属性的赋值,我们可以写出无状态的函数与服务,这样就不逼关心函数的可见性与数据一致性,从而更方便地扩展服务。

mjiuming commented 4 years ago

编程范式是与具体语言无关的,比如说下面这段代码是使用C语言实现的,这段代码是OOP的,因为它满足继承、封装、多态三个特性,但是它的所有语法使用的都是标准C99语法,执行性能与面向过程的C语言代码没有区别

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

struct Object
{
    int a;
    void (*setA)(struct Object *object, int a);
    int (*getA)(struct Object *object);
};

struct ObjectExtended
{
    struct Object *__super;
    void (*setA)(struct Object *object, int a);
    int (*getA)(struct Object *object);
};

static void _setA(struct Object *object, int a)
{
    object->a = a;
}

static int _getA(struct Object *object)
{
    return object->a;
}

static void _setA_extended(struct Object *object, int a)
{
    printf("ObjectExtended[%p] set A : %d\n", object, a);
    object->a = a;
}

static int _getA_extended(struct Object *object)
{
    printf("ObjectExtended[%p] get A : %d\n", object, object->a);
    return object->a;
}

struct Object *createObject()
{
    struct Object *object = malloc(sizeof(struct Object));
    object->setA = _setA;
    object->getA = _getA;
    return object;
}

struct ObjectExtended *createObjectExtended()
{
    struct Object *object = malloc(sizeof(struct Object));
    struct ObjectExtended *objectExtended = malloc(sizeof(struct ObjectExtended));
    objectExtended->__super = object;
    objectExtended->setA = object->setA = _setA_extended;
    objectExtended->getA = object->getA = _getA_extended;
    return objectExtended;
}

int main(int argc, char **argv)
{
    struct Object *object = createObject();
    struct Object *objectExtended = createObjectExtended();

    object->setA(object, 1);
    printf("Object[%p]->getA() = %d;\n", object, object->getA(object));

    objectExtended->setA(objectExtended, 2);
    printf("Object[%p]->getA() = %d;\n", objectExtended, objectExtended->getA(objectExtended));

    return EXIT_SUCCESS;
}

这是运行的输出结果

Object[0x7faeb6c017c0]->getA() = 1;
ObjectExtended[0x7faeb6c01800] set A : 2
ObjectExtended[0x7faeb6c01800] get A : 2
Object[0x7faeb6c01800]->getA() = 2;
shitsurei commented 4 years ago

学习了

wangbaojiao commented 4 years ago

JRocket、Hot Spot jit都是将字节码编译为机器语言,所以我觉的慢除了和解释执行有一定关系; 还和基于栈的指令集有关(机器指令集是基于寄存器的),基于栈的访问内存效率低于寄存器的; 还和设计范式有关(比如较强的内存模型)。

jerryqiang commented 4 years ago

关于关键词,应该是机器码,而不是机械码

mirror6Y commented 4 years ago

get!

kfw5264 commented 4 years ago

涨姿势了

womingzhiren commented 4 years ago

语言的性能与是否面向过程或者面向对象无关,大部分看语言的执行方式,我觉得性能大概是机器码(不用编译)>C,C++(编译)>java(半编译半解释)>html(解释)

mjiuming commented 4 years ago

硬件场合下决定是否使用某语言的关键因素并不是性能因素,而是这个语言是否是实时性的,而不是使用了何种编程范式。

对于C语言和C++语言来说,所有内存资源的申请与释放都是手动进行的,所有的指令都是以程序员期望的顺序以相同的指令周期顺序执行。

对于Java语言和Golang语言等带自动垃圾回收语言来说,程序会在不适当的时机暂停线程来执行垃圾回收。对于某些任务来说可能期望在几个指令周期内接受所有外部信号并处理完成,在这种情况下暂停线程并执行垃圾回收就会导致信号的丢失。

至于说面向对象与面向过程的性能差异,我上面给出了使用C语言实现面向对象的示例了,而在Linux内核中也大量使用OOP范式来实现对硬件资源的抽象,所以说性能差异是来自于这个语言的执行机制,而不是这个语言采用的编程范式。

jkchensc commented 4 years ago

学习了!

Shouheng-Wang commented 4 years ago

学习了,性能差异是来自于这个语言的执行机制,而不是这个语言采用的编程范式。

kebukeYi commented 3 years ago

比如来一段 是面向过程开发的代码 和面向对象开发的代码 直接对比看一下!

mjiuming commented 3 years ago

函数是否编译于main函数是否调用是没有关系的,比如考虑如下的C语言代码

void foo() {}
void bar() {}

int main(int argc, char **argv) {
    return 0;
}

在GCC10.2.0下使用O0优化编译出来汇编代码如下

    .file   "main.c"
    .text
    .globl  foo
    .type   foo, @function
foo:
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    nop
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size   foo, .-foo
    .globl  bar
    .type   bar, @function
bar:
.LFB1:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    nop
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE1:
    .size   bar, .-bar
    .globl  main
    .type   main, @function
main:
.LFB2:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movl    %edi, -4(%rbp)
    movq    %rsi, -16(%rbp)
    movl    $0, %eax
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE2:
    .size   main, .-main
    .ident  "GCC: (GNU) 10.2.0"
    .section    .note.GNU-stack,"",@progbits

实际上main入口并没有调用foo和bar,但是编译器依旧会对它进行编译,与你是否有使用过它是没有关系的

XSF notifications@github.com 于2021年1月24日周日 上午12:10写道:

别的地方我还不确定,但是面向对象的搞法内存开销一定是大过面向过程的,比如你上面的C语言面向对象的写法,假设有这么一个类,拥有很多接口,那他就需要很多个函数指针,而你只是想用其中几个接口,但是你在实例化的时候,这些指针变量都会被实例化出来的。以上是第一点,第二点,当你实例化出来你就要赋初值,也就是构造函数的工作,你需要给这些函数指针赋值,那相应地址的函数也会被编译。哪怕你本来不想用这个函数,但是构造函数把他赋值了那个函数就会被编译,同理这个对象中可能还有很多的私有或者公有变量你的程序也用不上。比如女性你只想要她的上半身的功能,但是只要你实例化个女人出来,她的下半身也会存在,在有资源的情况下你为了快速利用别人写好的类,你就直接实例化直接用就完事了多方便啊!但是没资源的情况,用面向过程,需要一个功能函数就调用一个功能函数,没被调用过的函数不会被编译,变量也是看情况声明,不会存在什么明明没用到也被声明出来的情况。linux只能给资源丰富的嵌入式设备用,哪怕是ulinux精简版,中低端单片机基本也用不起,这也是有相应的道理的。

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/Snailclimb/JavaGuide/issues/431#issuecomment-766111080, or unsubscribe https://github.com/notifications/unsubscribe-auth/AKYAXFD2R7GCVMOKRZLHQC3S3LYFVANCNFSM4IKID5KQ .

kebukeYi commented 3 years ago

试试 o1 o2 o3 编译呢;-O0 不进行优化处理;

------------------ 原始邮件 ------------------ 发件人: "Snailclimb/JavaGuide" <notifications@github.com>; 发送时间: 2021年1月29日(星期五) 中午11:46 收件人: "Snailclimb/JavaGuide"<JavaGuide@noreply.github.com>; 抄送: "193276660"<193276660@qq.com>;"Comment"<comment@noreply.github.com>; 主题: Re: [Snailclimb/JavaGuide] 面向过程 :面向过程性能比面向对象高?? (#431)

函数是否编译于main函数是否调用是没有关系的,比如考虑如下的C语言代码 void foo() {} void bar() {} int main(int argc, char **argv) { return 0; }

在GCC10.2.0下使用O0优化编译出来汇编代码如下 .file "main.c" .text .globl foo .type foo, @function foo: .LFB0: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 nop popq %rbp .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE0: .size foo, .-foo .globl bar .type bar, @function bar: .LFB1: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 nop popq %rbp .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE1: .size bar, .-bar .globl main .type main, @function main: .LFB2: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 movl %edi, -4(%rbp) movq %rsi, -16(%rbp) movl $0, %eax popq %rbp .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE2: .size main, .-main .ident "GCC: (GNU) 10.2.0" .section .note.GNU-stack,"",@progbits

实际上main入口并没有调用foo和bar,但是编译器依旧会对它进行编译,与你是否有使用过它是没有关系的

XSF notifications@github.com 于2021年1月24日周日 上午12:10写道:

别的地方我还不确定,但是面向对象的搞法内存开销一定是大过面向过程的,比如你上面的C语言面向对象的写法,假设有这么一个类,拥有很多接口,那他就需要很多个函数指针,而你只是想用其中几个接口,但是你在实例化的时候,这些指针变量都会被实例化出来的。以上是第一点,第二点,当你实例化出来你就要赋初值,也就是构造函数的工作,你需要给这些函数指针赋值,那相应地址的函数也会被编译。哪怕你本来不想用这个函数,但是构造函数把他赋值了那个函数就会被编译,同理这个对象中可能还有很多的私有或者公有变量你的程序也用不上。比如女性你只想要她的上半身的功能,但是只要你实例化个女人出来,她的下半身也会存在,在有资源的情况下你为了快速利用别人写好的类,你就直接实例化直接用就完事了多方便啊!但是没资源的情况,用面向过程,需要一个功能函数就调用一个功能函数,没被调用过的函数不会被编译,变量也是看情况声明,不会存在什么明明没用到也被声明出来的情况。linux只能给资源丰富的嵌入式设备用,哪怕是ulinux精简版,中低端单片机基本也用不起,这也是有相应的道理的。

— You are receiving this because you commented. Reply to this email directly, view it on GitHub

431 (comment),

or unsubscribe https://github.com/notifications/unsubscribe-auth/AKYAXFD2R7GCVMOKRZLHQC3S3LYFVANCNFSM4IKID5KQ .

— You are receiving this because you commented. Reply to this email directly, view it on GitHub, or unsubscribe.

mjiuming commented 3 years ago

即使是-O3也是同样的

    .file   "main.c"
    .text
    .p2align 4
    .globl  foo
    .type   foo, @function
foo:
.LFB0:
    .cfi_startproc
    ret
    .cfi_endproc
.LFE0:
    .size   foo, .-foo
    .p2align 4
    .globl  bar
    .type   bar, @function
bar:
.LFB4:
    .cfi_startproc
    ret
    .cfi_endproc
.LFE4:
    .size   bar, .-bar
    .section    .text.startup,"ax",@progbits
    .p2align 4
    .globl  main
    .type   main, @function
main:
.LFB2:
    .cfi_startproc
    xorl    %eax, %eax
    ret
    .cfi_endproc
.LFE2:
    .size   main, .-main
    .ident  "GCC: (GNU) 10.2.0"
    .section    .note.GNU-stack,"",@progbits

除非你对函数签名进行修饰

static void foo() {}
static void bar() {}

int main(int argc, char **argv) {
    return 0;
}

明确声明它是不会导出给外部进行调用的函数

    .file   "main.c"
    .text
    .section    .text.startup,"ax",@progbits
    .p2align 4
    .globl  main
    .type   main, @function
main:
.LFB2:
    .cfi_startproc
    xorl    %eax, %eax
    ret
    .cfi_endproc
.LFE2:
    .size   main, .-main
    .ident  "GCC: (GNU) 10.2.0"
    .section    .note.GNU-stack,"",@progbits

只要有非static的函数使用了static的函数,那么static的函数就会被重新编译(未使用O3,因为O3会将static函数内联,从而直接return

static void foo() {}
void bar() { foo(); }

int main(int argc, char **argv) {
    return 0;
}
    .file   "main.c"
    .text
    .type   foo, @function
foo:
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    nop
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size   foo, .-foo
    .globl  bar
    .type   bar, @function
bar:
.LFB1:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movl    $0, %eax
    call    foo
    nop
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE1:
    .size   bar, .-bar
    .globl  main
    .type   main, @function
main:
.LFB2:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movl    %edi, -4(%rbp)
    movq    %rsi, -16(%rbp)
    movl    $0, %eax
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE2:
    .size   main, .-main
    .ident  "GCC: (GNU) 10.2.0"
    .section    .note.GNU-stack,"",@progbits

当然你可以用static修饰所有不想暴露给别人的函数,但是不觉得这样做就与拿来别人的库来用相违背吗?如果我是给别人用的库,那么我就不可能用static修饰,那么不论我用什么构造函数或不用构造函数都会编译;如果我是给别人用的库,那么我用static修饰了,并且没有使用非static的函数来调用它,很开心,他没有编译,那么这个函数存在的意义是什么呢?

mjiuming commented 3 years ago

请使用阳间的方式沟通,总这么发真的没意思, 你说的这种方式无异于

int a;
int b;
int c;
int d(int){ /* do something */ return 0; }
inf e(int){ /* do something */ return 0; }
inf f(int){ /* do something */ return 0; }

既然你想比较内存优劣请先告诉我这种写法和你那种写法有什么区别,难道这么写就不会占用内存了?请不要和我说你可以不写其他的变量和函数弄成了

int a;
int d(int){ /* do something */ return 0; }

我也可以说为什么我相关的一系列操作不会使用下面的简洁方式呢?

typedef struct type {
    int a;
    int (*d)(int);
}

如果你认为你写的是4K Flash内存的上古芯片,那麻烦老老实实用汇编什么高级语言都别用,最好是没有任何过程调用没有栈帧分配。

除此之外,如果是想好好讨论问题请放在相同的问题域进行讨论,现在在讨论的是用任何语言都能实现面向对象范式,在实现相同功能的情况下大家的执行效率没有明显差异,而不是我使用了封装你非要插入无限多的无用字段然后跟我说:“看,你这个东西占用了无限大的内存,我的只需要一个变量就能完成”,在我眼里就跟有人在跟我说:“火箭这玩意得烧煤,最好还是水洗煤”。

XSF notifications@github.com 于2021年2月3日周三 下午2:41写道:

即使是-O3也是同样的

.file "main.c" .text .p2align 4 .globl foo .type foo, @function foo: .LFB0: .cfi_startproc ret .cfi_endproc .LFE0: .size foo, .-foo .p2align 4 .globl bar .type bar, @function bar: .LFB4: .cfi_startproc ret .cfi_endproc .LFE4: .size bar, .-bar .section .text.startup,"ax",@progbits .p2align 4 .globl main .type main, @function main: .LFB2: .cfi_startproc xorl %eax, %eax ret .cfi_endproc .LFE2: .size main, .-main .ident "GCC: (GNU) 10.2.0" .section .note.GNU-stack,"",@progbits

除非你对函数签名进行修饰

static void foo() {}static void bar() {} int main(int argc, char **argv) { return 0; }

明确声明它是不会导出给外部进行调用的函数

.file "main.c" .text .section .text.startup,"ax",@progbits .p2align 4 .globl main .type main, @function main: .LFB2: .cfi_startproc xorl %eax, %eax ret .cfi_endproc .LFE2: .size main, .-main .ident "GCC: (GNU) 10.2.0" .section .note.GNU-stack,"",@progbits

只要有非static的函数使用了static的函数,那么static的函数就会被重新编译(未使用O3,因为O3会将static函数内联,从而直接 return)

static void foo() {}void bar() { foo(); } int main(int argc, char **argv) { return 0; }

.file "main.c" .text .type foo, @function foo: .LFB0: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 nop popq %rbp .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE0: .size foo, .-foo .globl bar .type bar, @function bar: .LFB1: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 movl $0, %eax call foo nop popq %rbp .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE1: .size bar, .-bar .globl main .type main, @function main: .LFB2: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 movl %edi, -4(%rbp) movq %rsi, -16(%rbp) movl $0, %eax popq %rbp .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE2: .size main, .-main .ident "GCC: (GNU) 10.2.0" .section .note.GNU-stack,"",@progbits

当然你可以用static修饰所有不想暴露给别人的函数,但是不觉得这样做就与拿来别人的库来用相违背吗?如果我是给别人用的库,那么我就不可能用static修饰,那么不论我用什么构造函数或不用构造函数都会编译;如果我是给别人用的库,那么我用static修饰了,并且没有使用非 static的函数来调用它,很开心,他没有编译,那么这个函数存在的意义是什么呢?

求教一下,你定义一个结构体 struck xxx { int a; int b; int c; int (d)(int); int (e)(int); int (*f)(int); } 然后声明 xxx xxx1,但是实际上你又只用了一个d函数,d函数只和a变量相关,也就是说实际你只用到了a变量和b函数。但是你却声明了xxx1,xxx1占用的内存会比你实际使用的部分大,这应该没错吧?

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/Snailclimb/JavaGuide/issues/431#issuecomment-772274290, or unsubscribe https://github.com/notifications/unsubscribe-auth/AKYAXFARCLFQM6URG2WB7X3S5DVX5ANCNFSM4IKID5KQ .

q809935302 commented 3 years ago

请使用阳间的方式沟通,总这么发真的没意思, 你说的这种方式无异于

int a;
int b;
int c;
int d(int){ /* do something */ return 0; }
inf e(int){ /* do something */ return 0; }
inf f(int){ /* do something */ return 0; }

既然你想比较内存优劣请先告诉我这种写法和你那种写法有什么区别,难道这么写就不会占用内存了?请不要和我说你可以不写其他的变量和函数弄成了

int a;
int d(int){ /* do something */ return 0; }

我也可以说为什么我相关的一系列操作不会使用下面的简洁方式呢?

typedef struct type {
    int a;
    int (*d)(int);
}

如果你认为你写的是4K Flash内存的上古芯片,那麻烦老老实实用汇编什么高级语言都别用,最好是没有任何过程调用没有栈帧分配。

除此之外,如果是想好好讨论问题请放在相同的问题域进行讨论,现在在讨论的是用任何语言都能实现面向对象范式,在实现相同功能的情况下大家的执行效率没有明显差异,而不是我使用了封装你非要插入无限多的无用字段然后跟我说:“看,你这个东西占用了无限大的内存,我的只需要一个变量就能完成”,在我眼里就跟有人在跟我说:“火箭这玩意得烧煤,最好还是水洗煤”。

XSF notifications@github.com 于2021年2月3日周三 下午2:41写道:

即使是-O3也是同样的 .file "main.c" .text .p2align 4 .globl foo .type foo, @function foo: .LFB0: .cfi_startproc ret .cfi_endproc .LFE0: .size foo, .-foo .p2align 4 .globl bar .type bar, @function bar: .LFB4: .cfi_startproc ret .cfi_endproc .LFE4: .size bar, .-bar .section .text.startup,"ax",@progbits .p2align 4 .globl main .type main, @function main: .LFB2: .cfi_startproc xorl %eax, %eax ret .cfi_endproc .LFE2: .size main, .-main .ident "GCC: (GNU) 10.2.0" .section .note.GNU-stack,"",@progbits 除非你对函数签名进行修饰 static void foo() {}static void bar() {} int main(int argc, char argv) { return 0; } 明确声明它是不会导出给外部进行调用的函数 .file "main.c" .text .section .text.startup,"ax",@progbits .p2align 4 .globl main .type main, @function main: .LFB2: .cfi_startproc xorl %eax, %eax ret .cfi_endproc .LFE2: .size main, .-main .ident "GCC: (GNU) 10.2.0" .section .note.GNU-stack,"",@progbits 只要有非static的函数使用了static的函数,那么static的函数就会被重新编译(未使用O3,因为O3会将static函数内联,从而直接 return) static void foo() {}void bar() { foo(); } int main(int argc, char argv) { return 0; } .file "main.c" .text .type foo, @function foo: .LFB0: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 nop popq %rbp .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE0: .size foo, .-foo .globl bar .type bar, @function bar: .LFB1: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 movl $0, %eax call foo nop popq %rbp .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE1: .size bar, .-bar .globl main .type main, @function main: .LFB2: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 movl %edi, -4(%rbp) movq %rsi, -16(%rbp) movl $0, %eax popq %rbp .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE2: .size main, .-main .ident "GCC: (GNU) 10.2.0" .section .note.GNU-stack,"",@progbits 当然你可以用static修饰所有不想暴露给别人的函数,但是不觉得这样做就与拿来别人的库来用相违背吗?如果我是给别人用的库,那么我就不可能用static修饰,那么不论我用什么构造函数或不用构造函数都会编译;如果我是给别人用的库,那么我用static修饰了,并且没有使用非 static的函数来调用它,很开心,他没有编译,那么这个函数存在的意义是什么呢? 求教一下,你定义一个结构体 struck xxx { int a; int b; int c; int (d)(int); int (e)(int); int (*f)(int); } 然后声明 xxx xxx1,但是实际上你又只用了一个d函数,d函数只和a变量相关,也就是说实际你只用到了a变量和b函数。但是你却声明了xxx1,xxx1占用的内存会比你实际使用的部分大,这应该没错吧? — You are receiving this because you commented. Reply to this email directly, view it on GitHub #431 (comment), or unsubscribe https://github.com/notifications/unsubscribe-auth/AKYAXFARCLFQM6URG2WB7X3S5DVX5ANCNFSM4IKID5KQ .

请使用阳间的方式沟通,总这么发真的没意思, 你说的这种方式无异于

int a;
int b;
int c;
int d(int){ /* do something */ return 0; }
inf e(int){ /* do something */ return 0; }
inf f(int){ /* do something */ return 0; }

既然你想比较内存优劣请先告诉我这种写法和你那种写法有什么区别,难道这么写就不会占用内存了?请不要和我说你可以不写其他的变量和函数弄成了

int a;
int d(int){ /* do something */ return 0; }

我也可以说为什么我相关的一系列操作不会使用下面的简洁方式呢?

typedef struct type {
    int a;
    int (*d)(int);
}

如果你认为你写的是4K Flash内存的上古芯片,那麻烦老老实实用汇编什么高级语言都别用,最好是没有任何过程调用没有栈帧分配。

除此之外,如果是想好好讨论问题请放在相同的问题域进行讨论,现在在讨论的是用任何语言都能实现面向对象范式,在实现相同功能的情况下大家的执行效率没有明显差异,而不是我使用了封装你非要插入无限多的无用字段然后跟我说:“看,你这个东西占用了无限大的内存,我的只需要一个变量就能完成”,在我眼里就跟有人在跟我说:“火箭这玩意得烧煤,最好还是水洗煤”。

XSF notifications@github.com 于2021年2月3日周三 下午2:41写道:

即使是-O3也是同样的 .file "main.c" .text .p2align 4 .globl foo .type foo, @function foo: .LFB0: .cfi_startproc ret .cfi_endproc .LFE0: .size foo, .-foo .p2align 4 .globl bar .type bar, @function bar: .LFB4: .cfi_startproc ret .cfi_endproc .LFE4: .size bar, .-bar .section .text.startup,"ax",@progbits .p2align 4 .globl main .type main, @function main: .LFB2: .cfi_startproc xorl %eax, %eax ret .cfi_endproc .LFE2: .size main, .-main .ident "GCC: (GNU) 10.2.0" .section .note.GNU-stack,"",@progbits 除非你对函数签名进行修饰 static void foo() {}static void bar() {} int main(int argc, char argv) { return 0; } 明确声明它是不会导出给外部进行调用的函数 .file "main.c" .text .section .text.startup,"ax",@progbits .p2align 4 .globl main .type main, @function main: .LFB2: .cfi_startproc xorl %eax, %eax ret .cfi_endproc .LFE2: .size main, .-main .ident "GCC: (GNU) 10.2.0" .section .note.GNU-stack,"",@progbits 只要有非static的函数使用了static的函数,那么static的函数就会被重新编译(未使用O3,因为O3会将static函数内联,从而直接 return) static void foo() {}void bar() { foo(); } int main(int argc, char argv) { return 0; } .file "main.c" .text .type foo, @function foo: .LFB0: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 nop popq %rbp .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE0: .size foo, .-foo .globl bar .type bar, @function bar: .LFB1: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 movl $0, %eax call foo nop popq %rbp .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE1: .size bar, .-bar .globl main .type main, @function main: .LFB2: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 movl %edi, -4(%rbp) movq %rsi, -16(%rbp) movl $0, %eax popq %rbp .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE2: .size main, .-main .ident "GCC: (GNU) 10.2.0" .section .note.GNU-stack,"",@progbits 当然你可以用static修饰所有不想暴露给别人的函数,但是不觉得这样做就与拿来别人的库来用相违背吗?如果我是给别人用的库,那么我就不可能用static修饰,那么不论我用什么构造函数或不用构造函数都会编译;如果我是给别人用的库,那么我用static修饰了,并且没有使用非 static的函数来调用它,很开心,他没有编译,那么这个函数存在的意义是什么呢? 求教一下,你定义一个结构体 struck xxx { int a; int b; int c; int (d)(int); int (e)(int); int (*f)(int); } 然后声明 xxx xxx1,但是实际上你又只用了一个d函数,d函数只和a变量相关,也就是说实际你只用到了a变量和b函数。但是你却声明了xxx1,xxx1占用的内存会比你实际使用的部分大,这应该没错吧? — You are receiving this because you commented. Reply to this email directly, view it on GitHub #431 (comment), or unsubscribe https://github.com/notifications/unsubscribe-auth/AKYAXFARCLFQM6URG2WB7X3S5DVX5ANCNFSM4IKID5KQ .

不好意思哈,其实我前面发的刚写完我就都删掉了,因为我对自己回复的结论也不太确定,不知道为什么删掉了你能看见。既然你连着两次都看到了,那我这次不删了

我是这样想的,因为我们面向对象会复用别人或者自己已经写好的类,那在这种情况下,一个类中确确实实有很多你其实并不需要的部分,另外面向对象更改属性也需要通过函数,面向对象为了大型项目多人合作规矩多点,这种编程范式内存怎么想都会占用的大些吧,难道世上真有一个东西只有好处没有一点坏处???

我主要就是回复楼主,没道理两种编程范式性能一点差别都没有吧,要么面向对象性能强点,要么面向过程强点,反正不可能两个内存占用一毛一样吧

可能是我的错觉,我感觉你在不知不觉中认为我说面向过程更加优越,认为我是在贬低面向对象,其实不是这么回事,我只是实事求是的表达自己对两种语言的看法,优缺点,并没有个人喜好在里面,我看情况都会用。

你这一句也跑题了(现在在讨论的是用任何语言都能实现面向对象范式,在实现相同功能的情况下大家的执行效率没有明显差异)这楼难道不是哪个编程范式性能会高点吗?

4k flash的芯片不是上古啊,单片机中挺常见的,就算不是4k,20k也会优先面向过程吧,单片机领域真的很常见。

我回顾了下我的第一个回复,主要表述的就是内存占用确实会大些,编程范式确实会影响内存。其实这玩意不就像写作吗?写法都不同没理由长度还刚好一样吧。但是,但是,我再再再,强调一次,我没说面向过程比面向对象优越。纯粹的客观讨论,内存占用

再强调一下,大工程要我选我也用面向对象,我回复的是内存的占用吧,就算面向过程内存占用少性能高,我也不可能在大工程里用啊,我不可能给自己找麻烦吧。但是讨论问题事情总要实事求对吧,不可能因为我喜欢用面向对象,就说注重“阅读“”复用”“扩展”的面向对象的内存占用和更紧凑的面向过程也就是算法数据结构的内存占用一致是吧。

千万别跑题,我纯粹的再说内存占用,没有贬低面向对象。和你眼中的“火箭”之类的毛线关系都没有,根据工程来使用,资源少的单片机直接面向过程c,甚至迫不得已写汇编(老板特别抠就会出现这种结果),大工程面向对象。

q809935302 commented 3 years ago

硬件场合下决定是否使用某语言的关键因素并不是性能因素,而是这个语言是否是实时性的,而不是使用了何种编程范式。

对于C语言和C++语言来说,所有内存资源的申请与释放都是手动进行的,所有的指令都是以程序员期望的顺序以相同的指令周期顺序执行。

对于Java语言和Golang语言等带自动垃圾回收语言来说,程序会在不适当的时机暂停线程来执行垃圾回收。对于某些任务来说可能期望在几个指令周期内接受所有外部信号并处理完成,在这种情况下暂停线程并执行垃圾回收就会导致信号的丢失。

至于说面向对象与面向过程的性能差异,我上面给出了使用C语言实现面向对象的示例了,而在Linux内核中也大量使用OOP范式来实现对硬件资源的抽象,所以说性能差异是来自于这个语言的执行机制,而不是这个语言采用的

硬件场合下决定是否使用某语言的关键因素并不是性能因素,而是这个语言是否是实时性的,而不是使用了何种编程范式。

对于C语言和C++语言来说,所有内存资源的申请与释放都是手动进行的,所有的指令都是以程序员期望的顺序以相同的指令周期顺序执行。

对于Java语言和Golang语言等带自动垃圾回收语言来说,程序会在不适当的时机暂停线程来执行垃圾回收。对于某些任务来说可能期望在几个指令周期内接受所有外部信号并处理完成,在这种情况下暂停线程并执行垃圾回收就会导致信号的丢失。

至于说面向对象与面向过程的性能差异,我上面给出了使用C语言实现面向对象的示例了,而在Linux内核中也大量使用OOP范式来实现对硬件资源的抽象,所以说性能差异是来自于这个语言的执行机制,而不是这个语言采用的编程范式。

//========================================================= 我之前两条确实刚写完就删了,因为发完之后觉得自己这半吊子把内存占用多些说得肯定不好。

因为不名原因删除了你还能看见,所以干脆向你请教,纯学习讨论目的

我之前说的话也是结合自己用c去仿写面向对象类,然后我自己使用的过程中的疑惑。但是我并不是很确定以自己的结论(所以删了)。如果你有好的解决方法,还希望告知,我给项目中的单片机专门写了类库,写的还挺全面的,实际使用的时候才发现声明结构体的时候一些我并不打算用的功能也被一起声明了。但是确确实实这样以对象的形式写,很方便,用的时候一个声明,一个构造函数就可以随便用了。我要是面向对象的搞法,做成功能函数,形参会特别长,还要我自己去定义各种专门的数据搭配功能

纯粹学习请教,如何解决这类问题呢?

mjiuming commented 3 years ago
  1. 执行性能面向对象与面向过程基本没有区别 Link
  2. 实现相同功能集的情况下,面向过程与面向过程内存调用基本一致,不会出现面向过程不编译,面向对象就多编译了的情况Link
  3. 现在在讨论的是用任何语言都能实现面向对象范式,在实现相同功能的情况下大家的执行效率没有明显差异,大家指的就是面向对象和面向过程,因为最上面就有人用Java和C比较,所以要强调不止Java能实现面向对象,C也可以
  4. 即使是面向过程也会有使用结构体来组合多个对象的情况,面向过程的情况下是一个函数就能实现完整的功能,所以你可以为每个函数定义一个结构,就好像你都恰到好处的定义了你所需要的数据,但是即使面向过程也会出现第三方库有很多你不需要的数据的问题,以单片机引脚为例
    
    struct {
     int _1: 1;
     int _2: 1;
     int _3: 1;
     int _4: 1;
     int _5: 1;
     int _6: 1;
     int _7: 1;
     int _8: 1;
    } P;

// 单片机写法 void main() { P._1 = 1; // P1引脚高电平 }


依旧会出现你所说的我只需要P1引脚,但是我要创建整个实例的问题。这个例子没有说面向过程的优劣,只是说你说的这个问题是个通用的问题,不是说面向对象有,面向过程就没有了
mjiuming commented 3 years ago

硬件场合下决定是否使用某语言的关键因素并不是性能因素,而是这个语言是否是实时性的,而不是使用了何种编程范式。 对于C语言和C++语言来说,所有内存资源的申请与释放都是手动进行的,所有的指令都是以程序员期望的顺序以相同的指令周期顺序执行。 对于Java语言和Golang语言等带自动垃圾回收语言来说,程序会在不适当的时机暂停线程来执行垃圾回收。对于某些任务来说可能期望在几个指令周期内接受所有外部信号并处理完成,在这种情况下暂停线程并执行垃圾回收就会导致信号的丢失。 至于说面向对象与面向过程的性能差异,我上面给出了使用C语言实现面向对象的示例了,而在Linux内核中也大量使用OOP范式来实现对硬件资源的抽象,所以说性能差异是来自于这个语言的执行机制,而不是这个语言采用的

硬件场合下决定是否使用某语言的关键因素并不是性能因素,而是这个语言是否是实时性的,而不是使用了何种编程范式。 对于C语言和C++语言来说,所有内存资源的申请与释放都是手动进行的,所有的指令都是以程序员期望的顺序以相同的指令周期顺序执行。 对于Java语言和Golang语言等带自动垃圾回收语言来说,程序会在不适当的时机暂停线程来执行垃圾回收。对于某些任务来说可能期望在几个指令周期内接受所有外部信号并处理完成,在这种情况下暂停线程并执行垃圾回收就会导致信号的丢失。 至于说面向对象与面向过程的性能差异,我上面给出了使用C语言实现面向对象的示例了,而在Linux内核中也大量使用OOP范式来实现对硬件资源的抽象,所以说性能差异是来自于这个语言的执行机制,而不是这个语言采用的编程范式。

//========================================================= 我之前两条确实刚写完就删了,因为发完之后觉得自己这半吊子把内存占用多些说得肯定不好。

因为不名原因删除了你还能看见,所以干脆向你请教,纯学习讨论目的

我之前说的话也是结合自己用c去仿写面向对象类,然后我自己使用的过程中的疑惑。但是我并不是很确定以自己的结论(所以删了)。如果你有好的解决方法,还希望告知,我给项目中的单片机专门写了类库,写的还挺全面的,实际使用的时候才发现声明结构体的时候一些我并不打算用的功能也被一起声明了。但是确确实实这样以对象的形式写,很方便,用的时候一个声明,一个构造函数就可以随便用了。我要是面向对象的搞法,做成功能函数,形参会特别长,还要我自己去定义各种专门的数据搭配功能

纯粹学习请教,如何解决这类问题呢?

这个问题的最简单回答依旧还是"It depends"。为了实现某个功能,或解决某个问题,在对象的整个生命周期内,所有的字段/属性都被用到了,那就是没有浪费,实现相同的功能与面向过程是逻辑等价的,只是数据的组织形式发生了变化。如果在对象的整个生命周期内,某些字段/属性没有被用到,那就是浪费了,使用面向对象确实是会比面向过程浪费很多的内存。 至于说对象的生命周期的概念可以考虑说二叉树数据结构,不可能说只创建二叉树而不添加,不删除,不销毁。

struct TreeNode;
typedef struct TreeNode TreeNode; 
struct TreeNode {
    TreeNode *left;
    TreeNode *right;
    int element;
};
TreeNode *binary_tree_create();
TreeNode *binary_tree_add(TreeNode *, int element);
TreeNode *binary_tree_remove(TreeNode *, int element);
void binary_tree_destroy(TreeNode *);
TreeNode *binary_search_tree_create();
TreeNode *binary_search_tree_add(TreeNode *, int element);
TreeNode *binary_search_tree_remove(TreeNode *, int element);
void binary_search_tree_destroy(TreeNode *);

int main(int argc, char **argv) {
    /* something uses create/add/remove/destroy */
}

对应的面向对象方式

struct TreeNode;
typedef struct TreeNode TreeNode; 
struct TreeNode {
    TreeNode *left;
    TreeNode *right;
    int element;
    TreeNode *(*add)(TreeNode *, int element);
    TreeNode *(*remove)(TreeNode *, int element);
};

static TreeNode *binary_tree_add(TreeNode *, int element);
static TreeNode *binary_tree_remove(TreeNode *, int element);
static void binary_tree_destroy(TreeNode *);
TreeNode *binary_tree_create() {
    TreeNode *tree_node /* = something alloc memory */;
    tree_node.add = binary_tree_add;
    tree_node.remove = binary_tree_remove;
}

static TreeNode *binary_search_tree_add(TreeNode *, int element);
static TreeNode *binary_search_tree_remove(TreeNode *, int element);
static void binary_search_tree_destroy(TreeNode *);
TreeNode *binary_search_tree_create() {
    TreeNode *tree_node /* = something alloc memory */;
    tree_node.add = binary_search_tree_add;
    tree_node.remove = binary_search_tree_remove;
}

int main(int argc, char **argv) {
    /* something uses create/add/remove/destroy */
}
q809935302 commented 3 years ago

------------------ 原始邮件 ------------------ 发件人: "Snailclimb/JavaGuide" <notifications@github.com>; 发送时间: 2021年2月4日(星期四) 中午11:38 收件人: "Snailclimb/JavaGuide"<JavaGuide@noreply.github.com>; 抄送: "08叙事"<809935302@qq.com>;"Comment"<comment@noreply.github.com>; 主题: Re: [Snailclimb/JavaGuide] 面向过程 :面向过程性能比面向对象高?? (#431)

硬件场合下决定是否使用某语言的关键因素并不是性能因素,而是这个语言是否是实时性的,而不是使用了何种编程范式。 对于C语言和C++语言来说,所有内存资源的申请与释放都是手动进行的,所有的指令都是以程序员期望的顺序以相同的指令周期顺序执行。 对于Java语言和Golang语言等带自动垃圾回收语言来说,程序会在不适当的时机暂停线程来执行垃圾回收。对于某些任务来说可能期望在几个指令周期内接受所有外部信号并处理完成,在这种情况下暂停线程并执行垃圾回收就会导致信号的丢失。 至于说面向对象与面向过程的性能差异,我上面给出了使用C语言实现面向对象的示例了,而在Linux内核中也大量使用OOP范式来实现对硬件资源的抽象,所以说性能差异是来自于这个语言的执行机制,而不是这个语言采用的

硬件场合下决定是否使用某语言的关键因素并不是性能因素,而是这个语言是否是实时性的,而不是使用了何种编程范式。 对于C语言和C++语言来说,所有内存资源的申请与释放都是手动进行的,所有的指令都是以程序员期望的顺序以相同的指令周期顺序执行。 对于Java语言和Golang语言等带自动垃圾回收语言来说,程序会在不适当的时机暂停线程来执行垃圾回收。对于某些任务来说可能期望在几个指令周期内接受所有外部信号并处理完成,在这种情况下暂停线程并执行垃圾回收就会导致信号的丢失。 至于说面向对象与面向过程的性能差异,我上面给出了使用C语言实现面向对象的示例了,而在Linux内核中也大量使用OOP范式来实现对硬件资源的抽象,所以说性能差异是来自于这个语言的执行机制,而不是这个语言采用的编程范式。

//========================================================= 我之前两条确实刚写完就删了,因为发完之后觉得自己这半吊子把内存占用多些说得肯定不好。

因为不名原因删除了你还能看见,所以干脆向你请教,纯学习讨论目的

我之前说的话也是结合自己用c去仿写面向对象类,然后我自己使用的过程中的疑惑。但是我并不是很确定以自己的结论(所以删了)。如果你有好的解决方法,还希望告知,我给项目中的单片机专门写了类库,写的还挺全面的,实际使用的时候才发现声明结构体的时候一些我并不打算用的功能也被一起声明了。但是确确实实这样以对象的形式写,很方便,用的时候一个声明,一个构造函数就可以随便用了。我要是面向对象的搞法,做成功能函数,形参会特别长,还要我自己去定义各种专门的数据搭配功能

纯粹学习请教,如何解决这类问题呢?

这个问题的最简单回答依旧还是"It depends"。为了实现某个功能,或解决某个问题,在对象的整个生命周期内,所有的字段/属性都被用到了,那就是没有浪费,实现相同的功能与面向过程是逻辑等价的,只是数据的组织形式发生了变化。如果在对象的整个生命周期内,某些字段/属性没有被用到,那就是浪费了,使用面向对象确实是会比面向过程浪费很多的内存。至于说对象的生命周期的概念可以考虑说二叉树数据结构 struct TreeNode; typedef struct TreeNode TreeNode; struct TreeNode { TreeNode left; TreeNode right; int element; }; TreeNode binary_tree_create(); TreeNode binary_tree_add(TreeNode , int element); TreeNode binary_tree_remove(TreeNode , int element); void binary_tree_destroy(TreeNode ); TreeNode binary_search_tree_create(); TreeNode binary_search_tree_add(TreeNode , int element); TreeNode binary_search_tree_remove(TreeNode , int element); void binary_search_tree_destroy(TreeNode ); int main(int argc, char *argv) { / something uses create/add/remove/destroy */ }

对应的面向对象方式 struct TreeNode; typedef struct TreeNode TreeNode; struct TreeNode { TreeNode left; TreeNode right; int element; TreeNode (add)(TreeNode , int element); TreeNode (remove)(TreeNode , int element); }; static TreeNode binary_tree_add(TreeNode , int element); static TreeNode binary_tree_remove(TreeNode , int element); static void binary_tree_destroy(TreeNode ); TreeNode binary_tree_create() { TreeNode tree_node / = something alloc memory /; tree_node.add = binary_tree_add; tree_node.remove = binary_tree_remove; } static TreeNode binary_search_tree_add(TreeNode , int element); static TreeNode binary_search_tree_remove(TreeNode , int element); static void binary_search_tree_destroy(TreeNode ); TreeNode binary_search_tree_create() { TreeNode tree_node / = something alloc memory /; tree_node.add = binary_search_tree_add; tree_node.remove = binary_search_tree_remove; } int main(int argc, char *argv) { / something uses create/add/remove/destroy */ }

— You are receiving this because you commented. Reply to this email directly, view it on GitHub, or unsubscribe.

q809935302 commented 3 years ago

感谢回复,我终于明白为什么删掉了你还会看到了,居然会直接发送到邮箱里啊。 看来这个对象的生命周期中有所浪费是不可避免的,只能酌情使用了,目前使用的芯片20k的ram,比较吃紧,因为怕内存碎片化,所以我也没用动态内存。

OceanWild commented 2 years ago

区别在于编译过程的复杂度,像Java这种面相对象的编程语言中间多了一步编译字节码。

mjiuming commented 2 years ago

典型的思维误区,并且与问题无关。

这个回答还是两年前实习的时候写的。

有很多种中间代码无关的面向对象编译解决方案。比如golang和C++的封装通过object layout编译器生成对应的field offset,多态通过vlookup查虚表,所有的对象解构都是通过编译器传递receiver到方法的第一个参数,都可以在编译器去做这些事情,与是否有字节码没有任何关系。

Java的bytecode还有LLVM IR的本质要解决的是跨平台运行,不同的编译器前端表示和前端无关的目标代码优化。然而这些和面向对象编程范式是两个正交领域,是不该放在一起去讨论的。

OceanWild @.***>于2021年12月16日 周四下午3:18写道:

区别在于编译过程的复杂度,像Java这种面相对象的编程语言中间多了一步编译字节码。

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/Snailclimb/JavaGuide/issues/431#issuecomment-995505403, or unsubscribe https://github.com/notifications/unsubscribe-auth/AKYAXFAPKHIIXAX6I6OX2V3URGHFLANCNFSM4IKID5KQ . Triage notifications on the go with GitHub Mobile for iOS https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675 or Android https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub.

smallironman666 commented 2 years ago

我觉得这个得分情况而定,c和c++就是很好的例子,两种语言执行效率差距其实并不大,但是两种语言的设计思想就不同。

c和c++的运行机制是一样的。都是编译成机械码。

这里说的应该是,C是面向过程的,C++是面向对象的吧,两种语言的都是编译型语言