精华内容
下载资源
问答
  • 头文件只是用来声明的,不参与编译,#include “1.h” 只是把1.h里的代码给复制到这个源文件里来,建议还是好好...1)不管变量还是函数先声明 或者直接定义才能使用,声明能声明n次,同一个作用域里面 定义只能定义...

    c++ 头文件的使用:https://blog.csdn.net/weixin_42018112/article/details/82357002

    头文件只是用来声明的,不参与编译,#include “1.h”  只是把1.h里的代码给复制到这个源文件里来,建议还是好好看看上面这个

    明确几个点:

    1)不管变量还是函数先声明 或者直接定义才能使用,声明能声明n次,同一个作用域里面 定义只能定义一次

    2) 根据C++标准的规定,一个变量声明必须同时满足两个条件,否则就是定义:  
     (1)声明必须使用extern关键字;(2)不能给变量赋初值    
     extern  int  a;  //声明 

     int  a;   //定义

     int  a  =  0; //定义

     extern  int  a  =0;   //定义

    3)一个全局变量的作用域默认是整个程序, 加了static 或者加了 const 则是这个源文件

    4)如果在多个源文件 包含同一个名字的 全局变量的 定义,就会引起重定义

     因此 要想在多个文件共用一个全局变量,我们只需在一个头文件里 声明(注意是声明)这个变量 : extern int i;

    然后在 其中一个源文件 只能是一个(通常就是头文件的同名源文件)里面 定义这个变量   int i =1;

    注意不能用 i =1;  要用 int i =1;

    最后在要使用这个变量的源文件里面  #include 头文件就行了

    但是 const 或者 static 的全局变量 你在头文件里定义就没啥错,因为 它的作用域只是当前文件,也就是包含了这个头文件的这个源文件里面,就是内部链接,所以const的全局变量通常放在头文件内

    所以如果在一个 头文件:

    int i =1;

    const int j = 2;

    static int n =3;

    然后在两个源文件都include了这个头文件,i是错的,重定义了,j和n没错

    例子:

    头文件:state.h   源文件:state.cpp       

     其它源文件:t1.cpp  t2.cpp  t3.cpp, 这些源文件都包含头文件state.h。

    需要定义一个全局变量供这些源文件中使用:方法如下

    1、在 state.h声明全局变量: extern int a;

    2、在state.cpp中定义该全局变量:int a =10;

    这样其它源文件就可以使用该变量啦

     

     

     

    C++编译模式

    通常,在一个C++程序中,只包含两类文件——.cpp文件和.h文件。其中,.cpp文件被称作C++源文件,里面放的都是C++的源代码;而.h文件则被称作C++头文件,里面放的也是C++的源代码。

    C++支持“分别编译”(separate compilation)。也就是说,一个程序所有的内容,可以分成不同的部分分别放在不同的.cpp文件里。.cpp文件里的东西都是相对独立的,在编译(compile)时不需要与其他文件互通,只需要在编译成目标文件后再与其他的目标文件做一次链接(link)就行了。比如,在文件a.cpp中定义了一个全局函数“void a() {}”,而在文件b.cpp中需要调用这个函数。即使这样,文件a.cpp和文件b.cpp并不需要相互知道对方的存在,而是可以分别地对它们进行编译,编译成目标文件之后再链接,整个程序就可以运行了。

    这是怎么实现的呢? 
    在文件b.cpp中,在调用“void a()”函数之前,先声明一下这个函数“void a();”,就可以了。这是因为编译器在编译b.cpp的时候会生成一个符号表(symbol table),像“void a()”这样的看不到定义的符号,就会被存放在这个表中。再进行链接的时候,编译器就会在别的目标文件中去寻找这个符号的定义。一旦找到了,程序也就可以顺利地生成了。

    注意这里提到了两个概念,一个是“定义”,一个是“声明”。简单地说,“定义”就是把一个符号完完整整地描述出来:它是变量还是函数,返回什么类型,需要什么参数等等。而“声明”则只是声明这个符号的存在,即告诉编译器,这个符号是在其他文件中定义的,我这里先用着,你链接的时候再到别的地方去找找看它到底是什么吧。定义的时候要按C++语法完整地定义一个符号(变量或者函数),而声明的时候就只需要写出这个符号的原型了。需要注意的是,一个符号,在整个程序中可以被声明多次,但却要且仅要被定义一次。试想,如果一个符号出现了两种不同的定义,编译器该听谁的?

    这种机制给C++程序员们带来了很多好处,同时也引出了一种编写程序的方法。考虑一下,如果有一个很常用的函数“void f() {}”,在整个程序中的许多.cpp文件中都会被调用,那么,我们就只需要在一个文件中定义这个函数,而在其他的文件中声明这个函数就可以了。一个函数还好对付,声明起来也就一句话。但是,如果函数多了,比如是一大堆的数学函数,有好几百个,那怎么办?能保证每个程序员都可以完完全全地把所有函数的形式都 准确地记下来并写出来吗?

    什么是头文件

    很显然,答案是不可能。但是有一个很简单地办法,可以帮助程序员们省去记住那么多函数原型的麻烦:我们可以把那几百个函数的声明语句全都先写好,放在一个文件里,等到程序员需要它们的时候,就把这些东西全部copy进他的源代码中。

    这个方法固然可行,但还是太麻烦,而且还显得很笨拙。于是,头文件便可以发挥它的作用了。所谓的头文件,其实它的内容跟.cpp文件中的内容是一样的,都是C++的源代码。但头文件不用被编译。我们把所有的函数声明全部放进一个头文件中,当某一个.cpp源文件需要它们时,它们就可以通过一个宏命令 “#include”包含进这个.cpp文件中,从而把它们的内容合并到.cpp文件中去。当.cpp文件被编译时,这些被包含进去的.h文件的作用便发挥了。

    举一个例子吧,假设所有的数学函数只有两个:f1和f2,那么我们把它们的定义放在math.cpp里:

    并把“这些”函数的声明放在一个头文件math.h中:

    在另一个文件main.cpp中,我要调用这两个函数,那么就只需要把头文件包含进来:

    这样,便是一个完整的程序了。需要注意的是,.h文件不用写在编译器的命令之后,但它必须要在编译器找得到的地方(比如跟main.cpp在一个目录下)。 main.cpp和math.cpp都可以分别通过编译,生成main.o和math.o,然后再把这两个目标文件进行链接,程序就可以运行了。

    include

    include 是一个来自C语言的宏命令,它在编译器进行编译之前,即在预编译的时候就会起作用。#include的作用是把它后面所写的那个文件的内容,完完整整地、 一字不改地包含到当前的文件中来。值得一提的是,它本身是没有其它任何作用与副功能的,它的作用就是把每一个它出现的地方,替换成它后面所写的那个文件的 内容。简单的文本替换,别无其他。因此,main.cpp文件中的第一句(#include “math.h”),在编译之前就会被替换成math.h文件的内容。即在编译过程将要开始的时候,main.cpp的内容已经发生了改变:

    不多不少,刚刚好。同理可知,如果我们除了main.cpp以外,还有其他的很多.cpp文件也用到了f1和f2函数的话,那么它们也通通只需要在使用这两个函数前写上一句#include “math.h”就行了。

    头文件中应该写什么

    通 过上面的讨论,我们可以了解到,头文件的作用就是被其他的.cpp包含进去的。它们本身并不参与编译,但实际上,它们的内容却在多个.cpp文件中得到了 编译。通过“定义只能有一次”的规则,我们很容易可以得出,头文件中应该只放变量和函数的声明,而不能放它们的定义。因为一个头文件的内容实际上是会被引入到多个不同的.cpp文件中的,并且它们都会被编译。放声明当然没事,如果放了定义,那么也就相当于在多个文件中出现了对于一个符号(变量或函数)的定 义,纵然这些定义都是相同的,但对于编译器来说,这样做不合法。

    所以,应该记住的一点就是,.h头文件中,只能存在变量或者函数的声明, 而不要放定义。即,只能在头文件中写形如:extern int a;和void f();的句子。这些才是声明。如果写上int a;或者void f() {}这样的句子,那么一旦这个头文件被两个或两个以上的.cpp文件包含的话,编译器会立马报错。(关于extern,前面有讨论过,这里不再讨论定义跟 声明的区别了。)

    但是,这个规则是有三个例外的。

    一,头文件中可以写const对象的定义。因为全局的const对象默 认是没有extern的声明的,所以它只在当前文件中有效。把这样的对象写进头文件中,即使它被包含到其他多个.cpp文件中,这个对象也都只在包含它的 那个文件中有效,对其他文件来说是不可见的,所以便不会导致多重定义。同时,因为这些.cpp文件中的该对象都是从一个头文件中包含进去的,这样也就保证 了这些.cpp文件中的这个const对象的值是相同的,可谓一举两得。同理,static对象的定义也可以放进头文件。

    二,头文件中可 以写内联函数(inline)的定义。因为inline函数是需要编译器在遇到它的地方根据它的定义把它内联展开的,而并非是普通函数那样可以先声明再链 接的(内联函数不会链接),所以编译器就需要在编译时看到内联函数的完整定义才行。如果内联函数像普通函数一样只能定义一次的话,这事儿就难办了。因为在 一个文件中还好,我可以把内联函数的定义写在最开始,这样可以保证后面使用的时候都可以见到定义;但是,如果我在其他的文件中还使用到了这个函数那怎么办 呢?这几乎没什么太好的解决办法,因此C++规定,内联函数可以在程序中定义多次,只要内联函数在一个.cpp文件中只出现一次,并且在所有的.cpp文 件中,这个内联函数的定义是一样的,就能通过编译。那么显然,把内联函数的定义放进一个头文件中是非常明智的做法。

    三,头文件中可以写类(class)的定义。因为在程序中创建一个类的对象时,编译器只有在这个类的定义完全可见的情况下,才能知道这个类的对象应该如何布局,所以,关于类的 定义的要求,跟内联函数是基本一样的。所以把类的定义放进头文件,在使用到这个类的.cpp文件中去包含这个头文件,是一个很好的做法。在这里,值得一提 的是,类的定义中包含着数据成员和函数成员。数据成员是要等到具体的对象被创建时才会被定义(分配空间),但函数成员却是需要在一开始就被定义的,这也就 是我们通常所说的类的实现。一般,我们的做法是,把类的定义放在头文件中,而把函数成员的实现代码放在一个.cpp文件中。这是可以的,也是很好的办法。 不过,还有另一种办法。那就是直接把函数成员的实现代码也写进类定义里面。在C++的类中,如果函数成员在类的定义体中被定义,那么编译器会视这个函数为 内联的。因此,把函数成员的定义写进类定义体,一起放进头文件中,是合法的。注意一下,如果把函数成员的定义写在类定义的头文件中,而没有写进类定义中, 这是不合法的,因为这个函数成员此时就不是内联的了。一旦头文件被两个或两个以上的.cpp文件包含,这个函数成员就被重定义了。

    头文件中的保护措施

    考虑一下,如果头文件中只包含声明语句的话,它被同一个.cpp文件包含再多次都没问题——因为声明语句的出现是不受限制的。然而,上面讨论到的头文件中的 三个例外也是头文件很常用的一个用处。那么,一旦一个头文件中出现了上面三个例外中的任何一个,它再被一个.cpp包含多次的话,问题就大了。因为这三个 例外中的语法元素虽然“可以定义在多个源文件中”,但是“在一个源文件中只能出现一次”。设想一下,如果a.h中含有类A的定义,b.h中含有类B的定义,由于类B的定义依赖了类A,所以b.h中也#include了a.h。现在有一个源文件,它同时用到了类A和类B,于是程序员在这个源文件中既把 a.h包含进来了,也把b.h包含进来了。这时,问题就来了:类A的定义在这个源文件中出现了两次!于是整个程序就不能通过编译了。你也许会认为这是程序 员的失误——他应该知道b.h包含了a.h——但事实上他不应该知道。

    使用”#define”配合条件编译可以很好地解决这个问题。在一 个头文件中,通过#define定义一个名字,并且通过条件编译#ifndef…#endif使得编译器可以根据这个名字是否被定义,再决定要不要继 续编译该头文中后续的内容。这个方法虽然简单,但是写头文件时一定记得写进去。

    头文件如何来关联源文件?

    这个问题实际上是说,已知头文件“a.h”声明了一系列函数(仅有函数原型,没有函数实现),“b.cpp”中实现了这些函数,那么如果我想在“c.cpp”中使用“a.h”中声明的这些在“b.cpp”中实现的函数,通常都是在“c.cpp”中使用#include “a.h”,那么c.cpp是怎样找到b.cpp中的实现呢?

    其实.cpp和.h文件名称没有任何直接关系,很多编译器都可以接受其他扩展名。 
    谭浩强老师的《C程序设计》一书中提到,编译器预处理时,要对#include命令进行“文件包含处理”:将headfile.h的全部内容复制到#include “headfile.h”处。 
    这也正说明了,为什么很多编译器并不care到底这个文件的后缀名是什么—-因为#include预处理就是完成了一个“复制并插入代码”的工作。

    程序编译的时候,并不会去找b.cpp文件中的函数实现,只有在link的时候才进行这个工作。我们在b.cpp或c.cpp中用#include “a.h”实际上是引入相关声明,使得编译可以通过,程序并不关心实现是在哪里,是怎么实现的。源文件编译后成生了目标文件(.o或.obj文件),目标文件中,这些函数和变量就视作一个个符号。在link的时候,需要在makefile里面说明需要连接哪个.o或.obj文件(在这里是b.cpp生成的.o或.obj文件),此时,连接器会去这个.o或.obj文件中找在b.cpp中实现的函数,再把他们build到makefile中指定的那个可以执行文件中。

    在VC中,一帮情况下不需要自己写makefile,只需要将需要的文件都包括在project中,VC会自动帮你把makefile写好。
    通常,编译器会在每个.o或.obj文件中都去找一下所需要的符号,而不是只在某个文件中找或者说找到一个就不找了。因此,如果在几个不同文件中实现了同一个函数,或者定义了同一个全局变量,链接的时候就会提示“redefined”.

     

    展开全文
  • 这是在一个论坛看到的问题,其实你不知道MultiDex到底有多坑。 解决和遇到的其它问题,请见下一篇文章:android MultiDex 原理下超出方法数的限制问题(三) 遭遇MultiDex 愉快地写着Android代码的总悟君往...


    android MultiDex 原理下遇见的N个深坑(二)


    这是在一个论坛看到的问题,其实你不知道MultiDex到底有多坑。

    不了解的可以先看上篇文章:android MultiDex multidex原理(一)

    解决和遇到的其它问题,请见下一篇文章:android MultiDex 原理下超出方法数的限制问题(三)



    遭遇multidex 

    愉快地写着Android代码的总悟君往工程里引入了一个默默无闻的jar然后Run了一下~~~~ 经过漫长的等待AndroidStudio构建失败了。
    于是总悟君带着疑惑查看错误信息。


                                                     

    看起来是:在试图将 classes和jar塞进一个Dex文件的过程中产生了错误。
    早期的Dex文件保存所有classes的方法个数的范围在0~65535之间。如果不清楚的请先看上篇文章:android MultiDex 原理(一)
    业务一直在增长,总悟君写(copy)的代码越来越长引入的库越来越多,超过这个范围只是时间问题。
    怎么解??太阳底下木有新鲜事,淡定先google一发,找找已经踩过坑的小伙伴。
    StackOverflow 的网友们对该问题表示情绪稳定,谈笑间抛出multiDex。
    这是Android官网对当初的短视行为给出的补丁方案。文档说,Dalvik Executable (DEX)文件的总方法数限制在65536以内,其中包括Android framwork method, lib method (后来总悟君发现仅仅是Android 自己的框架的方法就已经占用了1w多),还有你的 code method ,所以请使用MultiDex。 对于5.0以下版本,请使用multidex support library (这个是我们的补丁包!build tools 请升级到21)。而5.0及以上版本,由于ART模式的存在,app第一次安装之后会进行一次预编译(pre-compilation) ,如果这时候发现了classes(..N).dex文件的存在就会将他们最终合成为一个.oat的文件,嗯看起来很厉害的样子。
    同时Google建议review代码的直接或者间接依赖,尽可能减少依赖库,设置proguard参数进一步优化去除无用的代码。嗯,这两个实施起来倒是很简单,但是治标不治本,躲得过初一躲不过十五。 在Google给出这个解决方案之前,他们的开发人员先给了一个简陋简易版本的multiDex具体参看这里。(怀疑后来的官方解决方案就有这家伙参与)。简单地说就是:1.先把你的app 的class 拆分成主次两个dex。2.你的程序运行起来后,自己把第二个dex给load进来。看就这么简单!而且这就是个动态加载模块的框架! 然而总悟君早已看穿Dalvik VM 这种动态加载dex 的能力归根结底还是因为java 的classloader类加载机制。沿着这条道走,Android模块动态化加载,包括dex级别和apk级别的动态化加载,各种玩法层出不穷。参见这里123456。

    第一回合 天真的官方补丁方案

    还是先解决打包问题,回头再研究那些高深的动态化加载技术。偷懒一下咯考虑到投入产出比,决定使用Google官方的multiDex解决。(Google的补丁方案啊,不会再有坑了吧?后面才发现还是太天真) 该方案有两步:
    1.修改gradle脚本来产生多dex。
    2.修改manifest 使用MulitDexApplication。
    步骤1.在gradle脚本里写上:


                                                        

    步骤2. manifest声明修改

                                                       

    如果有自己的Application,继承MulitDexApplication。如果当前代码已经继承自其它Application没办法修改那也行,就重写 Application的attachBaseContext()这个方法。
                                                     

    run一下,可以了!但是dex过程好像变慢了。。。

    文档还写明了multiDex support lib 的局限。瞄一下是什么:
    1.在应用安装到手机上的时候dex文件的安装是复杂的(complex)有可能会因为第二个dex文件太大导致ANR。请用proguard优化你的代码。呵呵
    2.使用了mulitDex的App有可能在4.0(api level 14)以前的机器上无法启动,因为Dalvik linearAlloc bug(Issue 22586) 。请多多测试自祈多福。用proguard优化你的代码将减少该bug几率。呵呵
    3.使用了mulitDex的App在runtime期间有可能因为Dalvik linearAlloc limit (Issue 78035) Crash。该内存分配限制在 4.0版本被增大,但是5.0以下的机器上的Apps依然会存在这个限制。 
    4.主dex被dalvik虚拟机执行时候,哪些类必须在主dex文件里面这个问题比较复杂。build tools 可以搞定这个问题。但是如果你代码存在反射和native的调用也不保证100%正确。呵呵


    感觉这就是个坑啊。补丁方案又引入一些问题。但是插件化方案要求对现有代码有比较大的改动,代价太大,而且动态化加载框架意味着维护成本更高,会有更多潜在bug。所以先测试,遇到有问题的版本再解决。

    第二回合 啥?dexopt failed?

    呵呵,部分低端2.3机型(话说2.3版本的android机有高端机型么)安装失败!INSTALL_FAILED_DEXOPT。这个就是前面说的Issue 22586问题。
    apk是一个zip压缩包,dalvik每次加载apk都要从中解压出class.dex文件,加载过程还涉及到dex的classes需要的杂七杂八的依赖库的加载,真耗时间。于是Android决定优化一下这个问题,在app安装到手机之后,系统运行dexopt程序对dex进行优化,将dex的依赖库文件和一些辅助数据打包成odex文件。存放在cache/dalvik_cache目录下。保存格式为apk路径 @ apk名 @ classes.dex。这样以空间换时间大大缩短读取/加载dex文件的过程。
    那刚才那个bug是啥问题呢,原来dexopt程序的dalvik分配一块内存来统计你的app的dex里面的classes的信息,由于classes太多方法太多超过这个linearAlloc 的限制 。那减小dex的大小就可以咯。
    gradle脚本如下:  
                                                                          
    --set-max-idx-number= 用于控制每一个dex的最大方法个数,写小一点可以产生好几个dex。 踩过更多坑的FB的工程师表示这个linearAlloc的限制不仅仅在安装时候的dexopt程序里7,还在你的app的dalvik rumtime里。(很显然啊dvk vm的宿主进程fork自于同一个母体啊)。为了表示对这个坑的不满以及对Google的产品表示遗憾,FB工程师Read The Fucking Source Code找到了一个hack方案。这个linearAlloc的size定义在c层而且是一个全局变量,他们通过对结构体的size的计算成功覆盖了该值的内容,这里要特别感谢C语言的指针和内存的设计。C的世界里,You Are The King of This World。当然实际情况是大部分用户用这把利刃割伤了自己。。。别问总悟君谁是世界上最好的语言。。。
    为FB的工程师的机智和务实精神点赞!然而总悟君不愿意花那么多精力实现FB的hack方法。(dvk虚拟机c层代码在2.x 4.x 版本里有变更,找到那个内存地址太难,未必搞得定啊)我们有偷懒的解决方案,为了避免2.3机型runtime 的linearAlloclimit ,最好保持每一个dex体积<4M ,刚才的的value<=48000
    好了 现在2.3的机器可以安装run起来了!

    第三回合 ANR的意思就是Application Not Responding

    问题又来了!这次不仅仅是2.3 的机型!还有一些中档配置的4.x系统的机型。问题现象是:第一次安装后,点击图标,1s,2s,3s... 程序没有任何反应就好像你没点图标一样。
    5s过去。。。程序ANR! 
    其实不仅仅总悟君的App存在这个问题,其他很多App也存在首次安装运行后几秒都无任何响应的现象或者最后ANR了。唯一的例外是美团App,点击图标立马就出现界面。唉要不就算啦?反正就一次。。。不行,这可是产品给用户的第一印象啊太重要了,而且美团搞得定就说明这问题有解决方案。
    ANR了是不是局限1描述的现象??不过也不重要...因为Google只是告诉你说第二个dex太大了导致的。并没有进一步解释根本原因。怎么办?Google一发?搜索点击图标 然后ANR?怎么可能有解决方案嘛。ANR就意味着UI线程被阻塞了,老老实实查看log吧。
    adb logcat -v time > log.txt
    于是发现 是 install dex + dexopt 时间太长!
    梳理一下流程:
    安装完app点击图标之后,系统木有发现对应的process,于是从该apk抽取classes.dex(主dex) 加载,触发 一次dexopt。 
    App 的laucherActivity准备启动 ,触发Application启动, 
    Application的 onattach()方法调用,这时候MultiDex.install()调用,classes2.dex 被install,再次触发dexopt。
    然后Applicaition onCreate()执行。
    然后 launcher Activity真的起来了。
    这些必须在5s内完成不然就ANR给你看!
    有点棘手。首先主dex是无论如何都绕不过加载和dexopt的。如果主dex比较小的话可以节省时间。主dex小就意味着后面的dex大啊,MultiDex.install()是在主线程里做的,总时间又没有实质性改变。install() 能不能放到线程里做啊?貌似不行。。。如果异步化,什么时候install完成都不知道。这时候如果进程需要seconday.dex里的classes信息不就悲剧?主dex越小这个错误几率就越大。要悲剧啊总悟君。
    淡定,这次Google搜索MultiDex.install 。于是总悟君发现了美团多dex拆包方案。 读完之后感觉看到胜利曙光。美团的主要思路是:精简主dex+异步加载secondary.dex 。对异步化执行速度的不确定性,他们的解决方案是重写Instrumentation execStartActivity 方法,hook跳转Activity的总入口做判断,如果当前secondary.dex 还没有加载完成,就弹一个loading Activity等待加载完成,如果已经加载完成那最好不过了。不错,RTFSC果然是王道。 可以试一试。
    但是有几个问题需要解决:
    1.分析主dex需要的classes这个脚本比较难写。。。Google文档说过这个问题比较复杂, 而且buildTools 不是已经帮我们搞定了吗?去瞄一下主dex的大小:8M 以及secondary.dex 3M 。 它是如何工作的?文档说dx的时候,先依据manifest里注册的组件生成一个 main-list,然后把这list里的classes所依赖的classes找出来,把他们打成classes.dex就是主dex。剩下的classes都放clsses2.dex(如果使用参数限制dex大小的话可能会有classe3.ex 等等) 。主dex至少含有main-list 的classes + 直接依赖classes ,使用mini-main-list参数可以仅仅包含刚才说的classes。
    关于写分析脚本的思路是:直接使用mini-main-list参数获取build目录下的main-list文件,这样manifest声明的类和他们的直接依赖类搞定的了,那后者的直接依赖类怎么解?这些在dvk runtime也是必须的classes。一个思路是解析class文件获得该class的依赖类。还一个思路是自己使用Dexclassloader 加载dex,然后hook getClass()方法,调用一次就记录一个。都挺折腾的。
    2.由于历史原因,总悟君在维护的App的manifest注册的组件的那些类,承载业务太多,依赖很多三方jar,导致直接依赖类非常多,而且短时间内无法梳理精简,没办法mini化主dex。
    3.Application的启动入口太多。Appication初始化未必是由launcher Activity的启动触发,还有可能是因为Service ,Receiver ,ContentProvider 的启动。 靠拦截重写Instrumentation execStartActivity 解决不了问题。要为 Service ,Receiver ,ContentProvider 分别写基类,然后在oncreate()里判断是否要异步加载secondary.dex。如果需要,弹出Loading Acitvity?用户看到这个会感觉比较怪异。
    结合自身App的实际情况来看美团的拆包方案虽然很美好然但是不能照搬啊。果然不能愉快地回家看动漫了。

    第四回合 换一种思路

    考虑到刚才说的2,3原因,先不要急着动手写分析脚本。总悟君期望找到更好的方案。问题到现在变成了:既希望在Application的attachContext()方法里同步加载secondary.dex,又不希望卡住UI线程。如果思路限制在线程异步化上,确实不可能实现。于是发现了微信开发团队的这篇文章。该文章介绍了关于这一问题 FB/QQ/微信的解决方案。FB的解决思路特别赞,让Launcher Activity在另外一个进程启动!当然这个Launcher Activity就是用来load dex 的 ,load完成就启动Main Activity。
    微信这篇文章给出了一个非常重要的观点:安装完成之后第一次启动时,是secondary.dex的dexopt花费了更多的时间。认识到这点非常重要,使得问题又转化为:在不阻塞UI线程的前提下,完成dexopt,以后都不需要再次dexopt,所以可以在UI线程install dex 了!文章最后给了一个对FB方案的改进版。
    仔细读完感觉完全可行。
    1.对现有代码改动量最小。
    2.该方案不关注Application被哪个组件启动。Activity ,Service ,Receiver ,ContentProvider 都满足。(有个问题要说明:如细心网友指出的那样,新安装还未启动但是收到Receiver的场景下,会导致Load界面出现。这个场景实际出现几率比较少,且仅出现一次。可以接受。) 
    3.该方案不限制 Application ,Activity ,Service ,Receiver ,ContentProvider 继续新增业务。
    于是总悟君实现了这篇文章最后介绍的改进版的方法,稍微有一点点扩充。

    流程图如下

                                                                
    上最终解决问题版的代码!

    在Application里面(这里不要再继承自MultiApplication了,我们要手动加载Dex):

     
     
     

    这里使用了classes(N).dex的方式保存了后面的dex而不是像微信目前的做法放到assest文件夹。前面有说到ART模式会将多个dex优化合并成oat文件。如果放置在asset里面就没有这个好处了。
    Launcher Activity 依然是原来的代码里的WelcomeActivity。
    在Application启动的时候会检测dexopt是否已经完成过,(检测方式是查看sp文件是否有dex文件的SHA1-Digest记录,这里要两个进程读取该sp,读取模式是MODE_MULTI_PROCESS)。如果没有就启动LoadDexActivity(属于:mini进程) 。否则就直接install dex !对,直接install。通过日志发现,已经dexopt的dex文件再次install的时候 只耗费几十毫秒。
    LoadDexActivity 的逻辑比较简单,启动AsyncTask 来install dex 这时候会触发dexopt 。


     

    Manifest.xml 里面

     

    替换Activity默认的出现动画 R.anim.null_anim 文件的定义:

     

    如微信开发团队的这篇文章所说,application启动了LoadDexActivity之后,自身不再是前台进程所以怎么hold 线程都不会ANR。

    系统何时会对apk进行dexopt总悟君其实并没有十分明白。通过查看安装运行的日志发现,安装的时候packageManagerService会对classes.dex 进行dexopt 。在调用MultiDex.install()加载 secondary.dex的时候,也会进行一次dexopt 。 这背后的流程到底是怎样的?dexopt是如何在另外一个进程执行的?如果是另外一个进程执行为何会阻塞主app的UI进程? 官方文档并没有详细介绍这个,那就RTFSC一探究竟吧.

    源代码跟踪比较长,移步到这里看吧。

    最终章碎碎念

    MultiDex的问题难点在:要持续解决好几个bug才能最终解决问题。进一步的,想要仔细分辨且解决这些bug,就必须持续探索一些关联性的概念和原理
    耗费了这么多时间来解决了Android系统的缺陷是不是有点略伤心。这不应该是Google给出一个比较彻底的解决方案吗?
    FB的工程师们脑洞好大。思考问题的方式很值得借鉴。
    微信团队的文章提到逆向了不少App。哈!总悟君感觉增长知识拓宽视野的新技能加强。
    RTFSC是王道。
    在查看log的过程中发现一个比较有趣的现象。在App的secondary.dex加载之前居然先加载了某数字公司的dex!(手机没有root但是安装了xx手机助手)再加上之前看到的错误堆栈里Android framework的调用堆栈之间也赫然有他们的代码。总悟君恶意猜测该app利用了某种手段进行了提权,hook了系统框架代码,将自己的代码注入到了每一个应用app的进程里。嗯。。。有趣。。。
    嗯今晚已经没有时间看动漫了。。。

    【dex 分包是靠gradle 脚本完成的,如果是第一次安装并且没有点击App图标启动,在没有dexopt 的情况下,非Activity组件启动了Applicatiion就会导致LoadResActivity 出现。 
    文章中说的不用关注非Activity的组件被启动,更多的是指dexopt之后的每一次启动,不必要关注是否是从acitivity启动应用了。(毕竟dexopt只触发一次)。】

    22.png (58.99 KB, 下载次数: 0)

    22.png

    33.png (66.11 KB, 下载次数: 0)

    33.png

    4.png (47.88 KB, 下载次数: 0)

    4.png

    000.png (15.6 KB, 下载次数: 0)

    000.png

    展开全文
  • 1.shell中变量的定义和使用 你可在任何编程语言中使用变量,但是在脚本编程(shell)中它们是没有类型的,简称弱类型编程语言,在这个变量中可以保含一个数字,一个字符串,一个单词等。你并不需要声明这个变量,它会...

     1.shell中变量的定义和使用   

            你可在任何编程语言中使用变量,但是在脚本编程(shell)中它们是没有类型的简称弱类型编程语言,在这个变量中可以保含一个数字,一个字符串,一个单词等。你并不需要声明这个变量,它会在引用这个变量时创建它。 

           Linux Shell中的变量分为“系统变量”和“用户自定义变量”,可以通过set命令查看系统变量。用$输出变量

           老生常谈,变量名命名方法:第一个字符为字母,其余字符可以是字母,数字或下划线,编程都一样。
           变量定义:不需要事先定义,直接赋值定义新变量,直接赋值修改原变量的值

    0.变量引用:在变量名前加$号,代表变量内容
    PATHTOMCAT=/usr/local/apache-tomcat-5.5.23  #上来就是变量名 ,赋值。
    echo "path of tomcat is $PATHTOMCAT     #这里使用echo命令,回显。用$获取变量值
    执行结果: path of tomcat is /usr/local/apache-tomcat-5.5.23
    注:
    1.定义变量时等号两侧不允许有多余的空格,如果有空格会报错。
    [robot@hadoop103 ~]$ path1= 'abc'   #有空格,报错
    bash: abc: command not found...
    [robot@hadoop103 ~]$ path1='abc' 
    [robot@hadoop103 ~]$ echo $path1
    abc

    2.定义变量时等号右侧的字符串中含有空格或者制表符,换行符时,要用引号将打算赋值的字符串括起 ,双引号内允许变量替换,而单引号内就不可以
      即shell定义字符串变量时字符串可以用单引号,也可以用双引号,也可以不用引号

    单引号和双引号的区别

       str=yalong
      echo "The str is $str" >>  The str is yalong #双引号中的$str被其值yalong替换,即双引号允许变量替换
      echo 'The str is $str'  >>  The str is $str #单引号中的内容被原样输出,即单引号不允许变量替换。
      转义字符\
    echo  "hello world" >>  hello world  #这里面双引号就是双引号,不输出
    echo  \"hello world\" >> "hello world"  #输出双引号,这里用了转义字符\所以双引号失去了引号功能,成了字符串。

    3.将linux命令返回值赋给变量(重要常用)

    [robot@hadoop103 ~]$ echo aa=`date +%H`  #这里用的是反转义字符,esc下面的那个符号。
    aa=23
    [robot@hadoop103 ~]$ echo aa="date+%H" #这里用的是双引号,命令失效。

    aa=date+%H

    A=`ls -la` 反引号,运行里面的命令,并把结果返回给变量A
    A=$(ls -la) 小括号的功能等价于反引号  
    A=`pwd`  & echo $A   
    A=$(pwd)  $ echo $A

    4.变量使用的常见误区错误

    有时候变量名很容易与其他文字混淆,比如: num=2 echo "this is the $numnd"这并不会打印出"this is the 2nd",而仅仅打印"this is the ",
    因为shell会去搜索变量numnd的值,但是这个变量时没有值的。可以使用花括号来告诉shell我们要打印的是num变量: num=2 echo "this is the ${num}nd"
    这将打印: this is the 2nd ··
    

    注意这用的是{}来确定变量,不是(),用小括号不行,只能用花括号。括号的的功能等同于反引号``

    5.变量的设置和查询

    A=8       A=abc        定义变量A,可以多次定义,顺序执行
    unset A       撤销变量A,unset用来删除变量,unset 命令不能删除只读变量。
    变量被删除后不能再次使用。
    实例
    #!/bin/sh
    myUrl="http://www.google.com"
    unset myUrl
    echo $myUrl
    以上实例执行将没有任何输出。
    readonly B=2  声明静态的变量B=2,不能unset,这种叫只读变量,变量值不可修改。
    使用 readonly 命令可以将变量定义为只读变量,只读变量的值不能被改变。
    下面的例子尝试更改只读变量,结果报错:
    #!/bin/bash
    myUrl="http://www.google.com"
    readonly myUrl
    myUrl="http://www.baidu.com"
    运行脚本,结果出错如下:
    /bin/sh: NAME: This variable is read only.
    export 变量名  可把变量提升为全局环境变量,可供其他shell程序使用,同样用echo$变量名,查询变量值
    这也是环境变量要加export的原因,这样所有的用户都可以使用这个变量

    export JAVA_HOME=/usr/local/jdk-7.01

    6.位置参数变量和特殊变量(重要)

    1.位置参数变量基本语法
           $n    (功能描述:n为数字,$0代表命令本身,$1-$9代表第一到第九个参数,十以上的参数,十以上的参数需要用大括号包含,如${10})
           $*    (功能描述:这个变量代表命令行中所有的参数,$*把所有的参数看成一个整体)
           $@  (功能描述:这个变量也代表命令行中所有的参数,不过$@把每个参数区分对待)
           $#    (功能描述:这个变量代表命令行中所有参数的个数)
    2)案例计算输入的参数1和参数2的两个数的和,并输出到控制台
    vi aaa.txt,脚本内容如下,然后执行脚本sh aaa.sh  1 2   
    #!/bin/bash
    num1=$1
    num2=$2
    sum=$(( $num1 + $num2))
    #变量sum的和是num1加num2
    echo $sum
    #打印变量sum的值,执行脚本,输出的结果就是3.
    
    2)打印输入的参数总数、所有参数
    #!/bin/bash
    echo "A total of $# parameters"
    #使用$#代表所有参数的个数
    echo "The parameters is: $*"
    #使用$*代表所有的参数
    echo "The parameters is: $@"
    #使用$@也代表所有参数
    
    3)$*与$@的区别
    #!/bin/bash
    for i in "$*"
    #$*中的所有参数看成是一个整体,所以这个for循环只会循环一次
            do
            echo "The parameters is: $i"
            done
             x=1
            for y in "$@"
    #$@中的每个参数都看成是独立的,所以“$@”中有几个参数,就会循环几次
            do
                    echo "The parameter$x is: $y"
                    x=$(( $x +1 ))
            done
    
    a)$*和$@都表示传递给函数或脚本的所有参数,不被双引号“”包含时,都以$1 $2 …$n的形式输出所有参数
    b)当它们被双引号“”包含时,“$*”会将所有的参数作为一个整体,以“$1 $2 …$n”的形式输出所有参数;“$@”会将各个参数分开,以“$1” “$2”…”$n”的形式输
    出所有参数
    注意:脚本可以嵌套。
    ------------------------------------------------------------------------------------------------------
    2.预定义变量
    1)基本语法:
      $? (功能:最后一次执行的命令的返回状态。如果这个变量的值为0,证明上一个命令正确执行;如果这个变量的值为非0(具体是哪个数,由命令自己来决
    定,则证明上一个命令执行不正确了。)
      $$  (功能描述:当前进程的进程号(PID))
      $!  (功能描述:后台运行的最后一个进程的进程号(PID))
    2)案例
    #!/bin/bash
    #输出当前进程的PID,这个PID就是当前这个脚本执行时,生成的进程的PID
    echo "The current process is $$"
    #使用find命令在root目录下查找hello.sh文件,符号&的意思是把命令放入后台执行
    find /root -name hello.sh & 
    echo "The last one Daemon process is $!"
    echo "$?"
    展开全文
  • 一个数组越界的C++程序

    千次阅读 2013-11-27 09:59:41
    学生给我发了私信,一个程序运行了好久,在OJ就是提交不了。 题目是:Description输入10个整数,将其中最小的数与第一个数对换,把最大的数与最后一个数对换。写三个函数; ①输入10个数;②进行处理;③输出10个数...

      学生给我发了私信,一个程序运行了好久,在OJ就是提交不了。

      题目是:

    Description
    输入10个整数,将其中最小的数与第一个数对换,把最大的数与最后一个数对换。写三个函数; ①输入10个数;②进行处理;③输出10个数。

    Input
    10个整数

    Output
    整理后的十个数,每个数后跟一个空格(注意最后一个数后也有空格)

    Sample Input
    2 1 3 4 5 6 7 8 10 9
    Sample Output
    1 2 3 4 5 6 7 8 9 10 
    HINT
      主函数已给定如下,提交时不需要包含下述主函数

    /*  C/C++代码  */
    int main()
    {
        const int n=10;
        int a[n];
        input(a,n);
        handle(a,n);
        output(a,n);
        return 0;
    }


      学生的解答:

    #include<iostream>
    #include<cstdio>
    using namespace std;
    void input(int a[],int);
    void handle(int a[],int);
    void output(int a[],int);
    int main()
    {
        const int n=10;
        int a[n];
        //freopen("input.txt","r",stdin);
        input(a,n);
        handle(a,n);
        output(a,n);
        return 0;
    }
    void input(int a[],int n)
    {
        for(int i=0;i<n;i++)
        cin>>a[i];
    }
    void handle(int a[],int n)
    {
        int max,i=0,t,min,z,zd,zx;
        max=a[i];
        min=a[i];
        for(i=0;i<n;i++)
        {
            if(max<a[i])
            {
                max=a[i];
                zd=i;    //记录最大值的位置
            }
            if(min>a[i])
            {
                min=a[i];
                zx=i;    //记录最小值的位置
            }
        }
        t=a[9];
        a[9]=max;
        a[zd]=t;           //进行值交换
        z=a[0];
        a[0]=min;
        a[zx]=z;
    
    }
    void output(int a[],int n)
    {
        for(int i=0;i<n;i++)
        {
            cout<<a[i]<<" ";
        }
    }
    
      这个解答在CodeBlocks中编译通过,屡经测试数据,没有异常,然而,提交后提示“Runtime error”,具体是:

      Runtime Error:Segmentation fault
      辅助解释:Segmentation fault:段错误,检查是否有数组越界,指针异常,访问到不应该
    访问的内存区域

      这不科学!由“一定是我错了”的思维,转向了“可能是机器错了”,利用管理员帐号,看了测试数据,也没有问题。

      再逐句看程序,一个地方引起我的注意,会是25、26行引用a[i],但 i 没有赋值吗?不是,i 的初值是0,于算法没有问题。就在第24行,zd、zx两个变量,也应该是同步赋值的,它们表示最大、最小的数的下标,后面循环中,也及时对这两个数做了更新。

      问题逐渐明白了,在24行不给zd、zx赋初值的情况下,如果29和34行的if条件有一个始终未能为真,zd、zx就不会赋值,40行开始的交换,就会出现数组越界。

      在这个程序上小修改的办法有了:第24行,加上给zd、zx赋初值,即:int max,i=0,t,min,z,zd=0,zx=0;

      这个程序,没有用上单步执行。对这种小几率的问题,单步可能也不管用,思维要严密。

      这个程序写得稍显罗嗦,下面给出一个改写版。

    #include<iostream>
    #include<cstdio>
    using namespace std;
    void input(int a[],int);
    void handle(int a[],int);
    void output(int a[],int);
    
    int main()
    {
        const int n=10;
        int a[n];
        //freopen("input.txt","r",stdin);
        input(a,n);
        handle(a,n);
        output(a,n);
        return 0;
    }
    
    void input(int a[],int n)
    {
        for(int i=0;i<n;i++)
        cin>>a[i];
    }
    void handle(int a[],int n)
    {
        int i,t,zd=0,zx=0;//默认第0个为最大、最小
        for(i=1;i<n;i++)  //从第1个开始
        {
            if(a[zd]<a[i])
                zd=i;    //记录最大值的位置
            if(a[zx]>a[i])
                zx=i;    //记录最小值的位置
        }
        t=a[n-1];  //比用含有“神秘数”的a[9]好
        a[n-1]=a[zd];
        a[zd]=t;           //进行值交换
        t=a[0];
        a[0]=a[zx];
        a[zx]=t;
    }
    
    void output(int a[],int n)
    {
        for(int i=0;i<n;i++)
        {
            cout<<a[i]<<" ";
        }
    }
    





    展开全文
  • 1. 需求:python程序获取bash脚本中某个变量的值。...此时line6中的pVERNO字符串变量含有换行符'\n'。2.3 通过line10和line11将获取到的字符串首先转换为整数,然后将整数转换为字符串,目的是去掉换...
  • 统计一个含有英文单词的文本文件的单词个数。 2. public void getWordNumFile(String formfileName, String word)  统计指定的文件中含有指定单词的个数。 假设指定单词是 “You” 在程序开发中,经常需要对
  • 本文将介绍我曾经做过的一个项目的服务器架构和服务器编程的一些重要细节。 一、程序运行环境 ...该程序总共有17个线程,其中分为9个数据库工作线程D和一个日志线程L,6个普通工作线程W,一个主线程M。(以
  • 一个c程序

    万次阅读 2018-05-23 19:19:29
    认识第一个C语言程序: #inlcude &lt;stdio.h&gt; #include &lt;stdlib.h&gt; int main() { printf("hehe\n");//printf用于在标准输出下打印信息 } 一、在写程序的过程中我们通常需要...
  • ahk变量与内置变量

    千次阅读 2017-02-09 10:14:35
    然而, 只包含数字 (可以含有小数点) 的变量进行数学运算或比较时, 会被自动转换为数值. (为了提高性能, 在内部会对数字进行缓存以避免与字符串之间的转换.) 变量的作用域和声明: 除了函数中的 局部变量, 其他...
  • Linux Shell编程(14)——内部变量

    千次阅读 2014-09-30 07:49:06
    $BASHBash二进制程序文件的路径 bash$ echo $BASH /bin/bash$BASH_ENV该环境变量保存一个Bash启动文件路径,当启动一个脚本程序时会去读该环境变量指定的文件。$BASH_SUBSHELL一个指示子shell(subshell)等级的变量...
  • Shell:环境变量

    千次阅读 2016-05-04 21:59:38
    在大家初次接触到Linux系统...1.在Linux中,很多程序和脚本都通过环境变量来获取系统信息、存储临时数据和配置信息; 2.bash shell使用环境变量来存储有关shell会话和工作环境信息; 允许你在内存中存储数据,以便运行
  • 陈述问题: 输入一个含有十个整数的一维数组,并计算输入的正数与负数的所有和。 分析问题: 想要输入一个含有十个整数的一维数组,就要用到循环。 判断条件给出大于等于零时为正,否则就是否;因此还需要有循环...
  • #include int main(int argc, const char * argv[]) {   int n;  printf("请输入整数n");   scanf("%d",&n);   while (n!=0&&n%10!=5) ... n=n/10;  }   if (n==0)  
  • 静态变量 static

    千次阅读 2016-04-07 11:30:41
    静态变量一个非常重要的知识点,无论笔试面试,都是一个高频的考点!确实也十分重要,这里简单的总结一下,它C与C++中的作用!!以备后用! 变量的生命周期与作用域 静态变量属于静态存储方式,其存储空间为内存中...
  • 1.2PHP变量

    千次阅读 2017-12-14 00:24:58
    1、变量声明 2、变量命名 3、可变变量 4、变量的引用赋值 5、变量的类型
  • 本节书摘来自华章计算机《Python程序设计》书中的第2章,第2.1节,作者:[美]戴维 I.施奈德(David I. Schneider)著,更多章节内容可以访问云栖社区“华章计算机”公众号查看 第2章 核心对象、变量、输入和输出 ...
  • C语言中变量声明和变量定义的区别

    千次阅读 2019-04-24 10:11:49
    程序中,变量有且仅有一个定义。 变量声明:用于向程序表明变量的类型和名字。 定义也是声明,extern声明不是定义 定义也是声明:当定义变量时我们声明了它的类型和名字。 extern声明不是定义:通过使用exte.....
  • 一个一般的多项选择的小程序,该程序一个数据文件中读入问题,显示这些问题给用户,程序还能识别正确选项的编号和错误选项的编号。用户可以随时提交答案并计算得分,然后退出程序。----每个主题都在测验的主目录...
  • 【Linux】篇文章搞定环境变量

    千次阅读 多人点赞 2020-11-19 17:54:12
    Linux中环境变量详解
  • 一个完整的Installshield安装程序实例

    万次阅读 多人点赞 2015-12-16 22:06:20
    一个完整的Installshield安装程序实例—艾泽拉斯之海洋女神出品(一)---基本设置一 前言 Installshield可以说是最好的做安装程序的商业软件之一,不过因为功能的太过于强大,以至于上手和精通都不是容易的事情...
  • matlab全局变量和局部变量和子函数

    千次阅读 2020-05-23 17:09:04
    函数文件中的变量都是局部的,即一个函数文件中定义的变量不能被另一个函数文件或其它 M 文件使用 当函数调用完毕后,该函数文件中定义的所有局部变量都将被释放,即全部被清除 函数通过输入和输出参数与其它 M 文件...
  • 一个安全漏洞是他们编写或修改软件程序时犯了一个错误,并可能成为一个弱点。一个漏洞是一个可以被攻击者[9]利用的问题或一个可能导致威胁发生[2]的弱点。不幸的是,有许多脆弱的软件程序给机密数据设置很弱的访问...
  • c++程序初写(3适合初学者的小程序

    万次阅读 多人点赞 2018-04-03 16:04:24
    1.编写测试案例时可以使用不同的命名空间namespace,这样可以避免使用同一个变量,并且易于操作修改程序,看着也很舒服 2.在每个命名空间前添加此命名空间中所需要的头文件(不存在重复使用,c++内部能避免重复使用...
  • 常量和变量的总结

    千次阅读 2017-03-30 17:47:57
     任何一个C语言程序中处理的数据,无论是什么类型,都是以常量或变量的形式出现的,在程序设计中,常量可以作说明而直接引用,但是变量应遵循“先定义,后使用;先赋值,后引用”的原则。 常量 是指在程序运行中...
  • 在当前行,每个元素要同本行后面的元素比较一次(下面第一个循环控制变量p的for循环),然后同第i+1行及以后各行元素比较一次,这就是循环控制变量k和p的二层for循环。 #include <stdio.h> voi...
  • 【汇编语言】 变量

    千次阅读 多人点赞 2021-03-29 08:49:48
    变量的概念 二、变量的定义 (1)变量名 (2)变量定义伪指令 1.助记符:BYTE 2.助记符:WORD 3.助记符:DWORD (3)初值表 三、多字节数据的存储顺序 (1)字节编址的主存储器 (2)数据的存储顺序 (3)小端存储和大端存储...
  • shell之变量和引用

    万次阅读 2016-11-01 11:30:05
     变量:本质上是程序中保存用户数据的块内存空间,变量名就是内存空间地址。 Shell中:变量可有字母数字和下划线组成,只能以字母或下划线开头。 命名:PATH=/sbin  JAVA_HOME=”/usr/lib” (变量名区分大小...
  • 3.变量与常量

    千次阅读 2021-03-31 22:01:34
    变量与常量1.1 变量的基本概念1.2 变量的声明及赋值1.3 变量的作用域1.3.1 成员变量1.3.2 局部变量 1.1 变量的基本概念 变量本身被用来存储特定类型的数据,可以根据需要...变量的使用是程序设计中一十分重要的环节
  • 获取CGI环境变量

    千次阅读 2017-08-16 16:04:14
    CGI程序继承了系统的环境变量,CGI的环境变量,在CGI程序在启动时初始化,结束时销毁,当一个CGI程序不是被HTTP服务器调用时,其环境变量基本是系统的环境变量。当属于HTTP服务器调用时,他的环境变量就会多了以下...
  • 文章目录导航经典单方程计量 经济学模型:专门问题5.1虚拟变量模型、虚拟变量的引入二、虚拟变量的设置原则5.2滞后变量模型、滞后变量模型二、分布滞后模型的参数估计三、自回归模型的参数估计四、格兰杰因果...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 119,939
精华内容 47,975
关键字:

对于一个含有n个变量的程序