Learning


  • 首页

  • 归档

  • 标签

  • 关于

Java中弱引用的理解

发表于 2016-12-08

弱引用也是用来描述非必需的对象,但是他的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前,当垃圾收集工作时,无论当前内存是否足够,都会回收掉被弱引用关联的对象,在JDK1.2之后,提供了WeakReference类实现弱引用。

看下面的代码

public class TestClient {
        public static void main(String[] args) throws Exception {
                int count = 5;
    System.out.println("before create object the current memory size = " +                 Runtime.getRuntime().freeMemory());
    Test test = new Test();
    System.out.println("after create object the current memory size = " + Runtime.getRuntime().freeMemory());
    WeakReference<Test> weakReference = new WeakReference<Test>(test);
    while(true) {
        count--;
        System.gc();
        Thread.sleep(5000);
        if(weakReference.get() == null) {
            System.out.println("the test has been recycled and the free memory is " + Runtime.getRuntime().freeMemory());
        } else {
            System.out.println("the test has not been recycled and the free memory is " + Runtime.getRuntime().freeMemory());
            if(count == 0) {
                test = null;
            }
        }
    }
    }
}
class Test  {
    byte data [] = new byte[10 * 1024 * 1024];
}

上述代码运行的结果如下

before create object the current memory size = 122179856
after create object the current memory size = 111694080
the test has not been recycled and the free memory is 112663384
the test has not been recycled and the free memory is 112666448
the test has not been recycled and the free memory is 113316792
the test has not been recycled and the free memory is 113316792
the test has not been recycled and the free memory is 113316792
the test has been recycled and the free memory is 123802584
the test has been recycled and the free memory is 123802440
the test has been recycled and the free memory is 123802440

我们可以发现我们通过WeakReference 引用到了 Test 对象,但是一旦我们把test这个强引用置为null之后,test对象马上就被回收了。

如果把弱引用改为软引用,那么会发现即使把test置为空,这个对象依然被软引用引用到了,不会不会被垃圾回收器回收。在Java虚拟机中,被软引用引用到的对象只会在内存不够用的情况下才会回收这一部分内存。

Android View测量模式

发表于 2016-10-15

今天看android源码的时候无意中发现了源码中关于测量模式的解释,简单的记录一下,一看就是恍然大悟了。

  int result = desiredSize;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize =  MeasureSpec.getSize(measureSpec);
switch (specMode) {
    case MeasureSpec.UNSPECIFIED:
        /* Parent says we can be as big as we want. Just don't be larger
           than max size imposed on ourselves.
        */
        result = Math.min(desiredSize, maxSize);
        break;
    case MeasureSpec.AT_MOST:
        // Parent says we can be as big as we want, up to specSize. 
        // Don't be larger than specSize, and don't be larger than 
        // the max size imposed on ourselves.
        result = Math.min(Math.min(desiredSize, specSize), maxSize);
        break;
    case MeasureSpec.EXACTLY:
        // No choice. Do what we are told.
        result = specSize;
        break;
}
return result;

看着代码中的注释应该很容易明白以后测量模式应该怎么用了。

C++多态

发表于 2016-10-08

在函数定义中有vitural关键字的函数就是虚函数,不能用在构造函数和静态函数上面。

虚函数只能借助于指针或者引用来达到多态的效果。

#include <iostream>
#include <cstring>

using namespace std;

class  A {

private:
    string name;    
public:

    A(string n):name(n) {

    }

    string getName() {
        return name;
    }

    void print() {
        cout<<"A"<<endl; 
    }

    virtual void hutrted() {
        cout<<name<<" get hurted"<<endl;
    }

    virtual void attack(A* a) {
        cout<<name<<"attack " << a->getName()<<endl;
        a->hutrted();
    }
};

class B:public A{

public:
    B(string n):A(n) {

    } 

     void hutrted() {
        A::hutrted();
        cout<<"this is b override hutrted"<<endl;
    }

     void attack(A* a) {
        A::attack(a);
        cout<<"this is b override attack"<<endl;
    }

};

int main() {
    A* a = new A("A");
    A* b = new B("B");

    b->attack(a);

    cout<<"---------------------"<<endl;
    a->attack(b); 

    delete a;
    delete b;
    return 0; 
} 

在上面的例子中就实现了多态,如果将基类A 方法中的virtual关键字去掉,那么这个时候将无法实现多态。

虚函数的作用在于实现了动态关联,即一个类的函数调用在编译的时候并不能确定而需要在程序运行的时候决定。

如果将A 中的virtual关键字去掉,那么函数的调用在编译的时候即决定。无法实现多态。

与java对比的话,在java当中只需要通过继承并且重写父类的方法即可实现多态

在构造函数和析构函数中不会形成多态,因为这个是在编译期间就确定了其行为。在构造函数和析构函数中调用虚函数,调用的是本类中定义的函数(如果没有定义就从父类中去找)。之所以这么设计是因为如果在父类的构造器中调用了虚函数,在构造子类的时候会先调用父类的构造器这个时候虚函数如果是多态,那么会调用子类的方法,但是这个时候子类还没有生成对象,这个时候调用会产生错误,析构函数同理,子类对象会先于父类对象被析构。这个时候如果在父类的析构函数中调用了虚函数,即在一个已经析构的对象上调用方法,也会产生错误

虚析构函数
看下面的这段代码

class Super{
public:
~Super(){
    cout<<"~Super invoked"<<endl;
} 
};

class Son : public Super {
    ~Son(){
        cout<<"~Son invoked"<<endl;
    } 
}; 

int main() {
    Super* son = new Son;
    delete son; 
    return 0;
} 

当我们动态的分配内存空间给son的时候,如果使用delete删除掉son,这个时候只会调用Super的析构函数,这个时候Son的构造函数并没有得到调用,那么这就有可能造成内存泄露,为了解决这种问题,就出现了虚析构函数

class Super{

public:
    virtual~Super(){
        cout<<"~Super invoked"<<endl;
    } 
};

class Son : public Super {
    ~Son(){
        cout<<"~Son invoked"<<endl;
    } 
}; 
int main() {
    Super* son = new Son;
    delete son; 
    return 0;
} 

即在在父类的析构函数前加上virtual关键字。这样当我们再次调用delete son的时候,会先调用Son的析构函数在调用Super的析构函数。

C++多态实现的原理
在C++当中,如果一个类中包含了虚函数,那么这个类中就会存在虚函数表。并且该类的任何对象中都存在这虚函数表的指针

class Super{
public:
    int a;
    virtual~Super(){
        cout<<"~Super invoked"<<endl;
    } 
};

所以当我们输出sizeof(Super)的时候,会输出16(在64位的dev C++中),这就说明了函数表中存在其他的内容,而这个内容就是虚函数表,多态的函数调用语句被编译成一系列根据基类指针所指向对象中存放的虚函数表的地址,在虚函数表中查找虚函数的地址,并调用虚函数的指令。

纯虚函数和抽象类
纯虚函数是指没有函数体的虚函数,那么包含了纯虚函数的类就是抽象类了,抽象类不能生成对象(但是可以使用抽象的指针指向基类),只能作为派生类的基类来使用,并且在类的构造方法和析构函数中不能调用纯虚函数。

一个子类只有实现了父类当中定义的所有纯虚函数才能变成非抽象类,否则还是一个抽象类。

class Super{
public:
    int a;
    virtual~Super(){
        cout<<"~Super invoked"<<endl;
    } 

    virtual void method() = 0; //纯虚函数
};

class Son : public Super {
    int b;
    ~Son(){
        cout<<"~Son invoked"<<endl;
    } 

    void method() { //纯虚函数实现

    }
}; 

int main() {
    Super* son = new Son;
    delete son; 
    return 0;
} 

C++ const

发表于 2016-09-25

在C++中可以使用const表示修饰常量。

使用一个const来定义一个对象的时候,那么这个对象是不可以修改的。

class ConstantTest {
public:
    int a;
    void func() const {
        cout<<a<<endl; 
    }
};    
const ConstantTest test; 
test.a = 100; //报错,无法修改常量对象的内容。

在常量对象上无法调用非常量成员函数,因为在非常量的成员函数中可能修改了成员变量,

可以使用在成员函数的参数列表后添加const关键字来声明一个常量成员函数。常量成员函数中不可以修改对象非静态.也不能调用同类中的非常量函数(静态成员函数除外)

Note
两个成员函数的函数名和参数列表都一样,但是一个有const 一个没有,形成重载

在C++中的const 分为两种,一种是顶层的const,一种是底层的const,

如果一个变量被顶层的const修饰,那么表示这个这变量,将不能改变其指向的对象,在Java中使用fianl 修饰的引用也是这样。

如果变量被一个底层的const修饰,那么这个对象的内容是不可变得。

如何区分两种const,有牛人总结如下

如果const右结合修饰的为类型或者*,那这个const就是一个底层const
如果const右结合修饰的为标识符,那这个const就是一个顶层const

测试代码如下

#include "Person.h"
#include <iostream>

using namespace std;

int main() {

    /////////// 底层 const//////
    Person person;
    Person person2;
    const Person*  p = &person;
    person.name = "Tom";
    //p->name = "liu"; 报错,底层const 不能改变内容
    p = &person2;

    cout<<person.name<<endl;
    cout<<p->name<<endl;

    ////////////顶层const/////////////
    Person person3;
    Person person4;
    Person* const p1 = &person3;
    p1->name = "Tom"; // 顶层const 可以修改内容
    //p1 = &person4; // 但是不能修改指针对象的指向
    cout<<p1->name<<endl;
}

C++函数学习

发表于 2016-09-25

内联函数
编译器编译的时候直接将函数的内容作为调用函数的一部分,避免了调用函数所产生的开销。

inline function(int x, int y) {
    cout<<x - y;
} 

函数的缺省值

//设置函数的缺省值 
void function(int x, int y = 2, int z = 3) {
    cout<<x+y+z;
}

构造函数

对象所占用的存储空间并不是由构造函数分配的,构造只是在对象的存储空间分配之后,来完成一写初始化的工作。

复制构造函数

只能有一个参数,即同类对象的引用。如果自己不写复制构造函数,系统将提供一个默认的复制构造函数,这儿构造函数的功能完成对对象的拷贝。
复制构造函数起作用的三种情况:

  • 用一个对象去初始化另一个对象。例如 Complex c2 = c1 //初始化语句
  • 如果某一个函数的形参是类A的对象,调用该函数的时候,类A的复制构造函数将被调用 用来 初始化形参。
  • 如果一个函数的返回值是类A的对象,那么函数返回的时候类A的复制构造函数将被调用。复制构造函数用来初始化作为返回值存在的类A对象,例如函数method()的返回值赋值给a, 那么类的A的复制构造函数用来初始化a.

析构函数
析构函数与构造函数相反是在一个对象被释放掉的时候调用,与Java中Object的finalize 方法相似。析构函数有以下几个调用的时机

  • 使用 delete 删除 new所分配的对象。
  • 当一个作用域结束的时候,其中对象的析构方法将被调用。
  • 临时对象的生存期结束。
  • 程序结束,并且存在全局或静态对象,对象的析构函数将被调用。
  • 显示的调用一个对象的析构方法时。

    使用析构函数时,如果一个类中都是对象和基本类型,那么在析构函数调用的时候将自动的调用这些对象和基本类型的析构函数,如果其中包含了自己手动申请的空间,那么需要自己手动的在析构函数中去释放这些空间。

    public:

    Book* book;
    
    MyClass(Book* book) {
        this->book = book;
    }
    ~MyClass() {
        book->~Book();
        cout<<"MyClass析构函数被调用"<<endl; 
    }
    

    };

    如果在MyClass的析构函数中没有手动去的去调用~Book(),那么book所指向的内存将不会释放。

C++引用学习笔记

发表于 2016-09-18

##理解##

在C++中可以声明一个变量的引用,相当于给变量起了一个别名。
具体的语法如下

T & n // T表示引用的类型,n表示引用的名字

引用需要注意的几点

  • 定义引用的时候一定要将其初始化成引用某个变量 ,否则编译的时候会报错
  • 初始化之后,该引用就会一直引用该变量,不会引用其他的变量了。这个时候如果在进行 = 操作只是把等号右边的值赋值给引用所指向的变量。
int a = 0; int b = 1; int & r = a; r = b; //执行之后,a = 1
  • 引用只能引用变量,不能引用常量和表达式
  • 声明一个引用并不是定义了一个变量,引用本身也不是一种数据类型,因此操作系统并不会给一个引用分配内存,因此对引用求地址就是对目标变量求地址 所以 &a = &r;
  • 引用没有对应的指针类型。但是可以声明一个引用指向指针。
    int p = &a;
    int
    & rp = p; // int 为数据类型, int& 表示int 的引用。
    cout <<
    rp<<endl;

##常引用##
使用const修饰的引用叫做常引用,常引用初始化之后就不能再指向别的值。

int b = 55;
const int& rb = b; 
rb = 8888; // 编译器报错,不能再修改其值。

cont T & 和 T & 是两种不同的类型
T& 类型的引用 或者T类型的变量可以用来初始化const T & 类型的引用。

const T类型的常量和const T & 类型的引用则不能用来初始化T & 类型的引用,除非进行强制类型转换。

##应用##(待补充)

  • 引用作为参数引用的一个重要作用就是作为函数的参数。以前的C语言中函数参数传递是值传递,如果有大块数据作为参数传递的时候,采用的方案往往是指针,因为这样可以避免将整块数据全部压栈,可以提高程序的效率。但是现在(C++中)又增加了一种同样有效率的选择(在某些特殊情况下又是必须的选择),就是引用。

C++结构体学习笔记

发表于 2016-09-05

对于结构体指针,使用-> 符号来访问变量。

#include<iostream>
using namespace std;
struct UserInfo{
    int id;
    int age;
};
int main() {
    UserInfo info;
    UserInfo* p = &info;
    info.id = 888;
    info.age = 25;
    cout << p->age;// 指针使用 -> 来访问成员变量 
}

匿名结构体,定义了一个匿名的结构体,并且声明了一个info变量。因为这个结构体是匿名的,所以没有办法用它再来声明其他的变量。

struct {
    int id;
    int age;
} info ; 

结构体存在对齐问题,所使用sizeof 计算一个结构体的大小的时候,得到的不一定是其中所有成员变量sizeof的值之和,详细的计算参考博客及博客

C++指针学习笔记(2)

发表于 2016-09-04

空指针
空指针不指向任何实际的对象或者函数。反过来说,任何对象或者函数的地址都不可能是空指针。
例如, int p = 0;
不能对空指针进行
操作

野指针
野指针不是空指针,是一个指向垃圾内存的指针。

野指针形成原因:

  1. 声明指针变量的时候没有初始化
  2. 指针被free或者delete之后,没有设置为NULL,让人误以为这是一个合法指针。free和delete只是把指针所指向的内存给释放掉,但并没有把指针本身给清理掉。这时候的指针依然指向原来的位置,只不过这个位置的内存数据已经被毁尸灭迹,此时的这个指针指向的内存就是一个垃圾内存。但是此时的指针由于并不是一个NULL指针(在没有置为NULL的前提下),在做如下指针校验的时候
  3. 指针操作超越了变量的作用范围。由于C/C++中指针有++操作,因而在执行该操作的时候,稍有不慎,就容易指针访问越界,访问了一个不该访问的内存,结果程序崩溃另一种情况是指针指向一个临时变量的引用,当该变量被释放时,此时的指针就变成了一个野指针,如下

使用指针需要注意的地方
(1) 声明指针的时候为空指针。
在函数内部可以通过判断来确定一个指针是否是空指针,从而避免异常的操作,但是并没有办法判断野指针。所以在使用的时候要避免野指针。

int main() {
    int* p; //野指针
    int* p1 = 0 ; //空指针 
}

在使用指针之前可以先先判断检查,是否为空指针。

int main() {
    int* p = 0; //空指针 
    if(p) {
        cout<<"you can user * operation";
    } else {
        cout<<"this is null point";
    }
}

运行上面的代码会输出 this is null point

但是如果使用野指针,那么在程序中就没办法判断了。

int main() {
    int* p; //空指针 
    if(p) {
        cout<<"you can user * operation"; //会执行这一句 
    } else {
        cout<<"this is null point";
    }
}

(2)防止使用指针的时候出现数组越界的异常。

int main() {
    int arr[4] = {1,2,3,4};
    int* p = arr;
    p = p+4;
    cout << *p;
   }

在上面的代码中就出现了数组越界的情况。这个时候在程序中会有巨大的隐患,要注意避免。

(3)防止出现指针所指向地址失效的情况。

using namespace std;
int a = 188;
int* test() {
    return &a;
}

int main() {
    int* p = test();
    cout <<*p;
}


这个时候并没有能够正常的输出188。因为变量a在程序运行的声明周期之内一直都在内存之中,所以这个时候访问是没有问题的。

但是代码改成下面这个样子就有问题了


using namespace std;
int* test() {
    int a = 188;
    return &a;
}
int main() {
    int* p = test();
    cout <<*p;
}

这个时候p指向的是一个临时变量,这个时候可能会形成野指针。

[注] 文章关于野指针的说明部分引用了博客

C++指针学习笔记

发表于 2016-09-03

int* p 定义一个int类型的指针(此处的额int 是作为一个类型符号存在)
\
p 指向了内存的一块,我们可以可以直接使用*p来操作其所指向内存的内容

那么实际上p只是指向了内存的某一个地址,打印出p的值的时候,会发现这就是一个普通的整数型的值

看下面的代码:

#include
using namespace std;
int main() {
int a = 888;
int p = &a;
cout<<p << endl; 打印p所指向的地址
cout<<
p; // 打印p所指向地址的值
return 0;
}

在C/C++中,数组的名字就是数组的内存的地址,他表示的是数组中的第一个元素在内存中位置,同时数组名本身就是一个指针类型。

在C/C++中数组是没有长度信息的,总是表示一个内存地址,所以我们在使用数组的时候需要一个额外的参数表示数组长度的信息。

使用指针来传参的优势

  1. 可以返回多个值(通过指针来完成多个输出参数)

  2. 效率上优势
    使用普通方式来传递参数的时候,实行的是按值传递,及对传入方法的参数先进行一个拷贝,然后把拷贝的值传入,这个时候实际上是在内存区域内再次申请了一块内存来存储这个值,然后在进行拷贝,如果这个参数值在占用内存比较大的情况下,会造成效率和内存空间的浪费,这个时候如果采用指针来传递参数,那么将会避免申请内存(可以直接读原来参数的内存) 和进行拷贝,有助于提升参数的效率。

使用const关键字
const关键字用来表示修饰的变量时不可以更改的,
const int a = 8;
这个时候,我们就无法在来更改a的值。

const 修饰指针的时候,表示指针所指向的内存区域是不可以修改的。

int a = 8;
int b = 9;
const int p = &a; p = 9; // 报错,不能修改指针所指向的内存
p = &b; // 不能修改指针所指向的内存,但是可以修改指针的指向。
cout << *p; // 输出 9。
a = 10; // 可以正常赋值
cout << a; //

BuildConfig.DEBUG的值

发表于 2016-09-02

今天在做项目的时候遇到一个很奇怪的问题:项目中子模块的BuildConfig 的值永远都是release模式下的值,在debug的时候,依然是这样.在我的项目中有这样一段代码

public int getPriority() {
    int priority = Log.VERBOSE;
    if (!BuildConfig.DEBUG) {
        priority = Log.INFO;
    }
    return priority;
}

这段代码用来控制日志的的输出级别,如果在debug的模式下,应该是输出所有的log,如果是在release的模式下,应该是输出INFO级别的log,但是在实际中发现,即使是debug下,BuildConfig.Debug的值永远都是false.

这是因为在gradle构建的过程当中,子模块的模式永远都是release模式,但是这个可以通过我们在gradle的配置中去更改。详细的gralde教程请看这里

下面说一下项目中解决办法
在我的项目中,依赖层级是这样的 app > lib_stub > lib_sdk ,因此在配置的时候三个模块的配置文件都需要修改

说明如下,app依赖lib_stub 那么需要在 dependencies 当中添加两种依赖,一种是release,一种是debug。
同时在lib_stub的defaultConfig当中添加两个配置项,defaultPublishConfig 和publishNonDefault(具体的含义在上面的提及的文档里面有详细的说面)
lib_stub 在依赖lib_sdk的时候同样需要添加两种依赖,并且在lib_sdk的defaultConfig中添加同样的配置。

主要的修改部分如下
build.gralde(app)

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile rootProject.ext.dependencies["junit"]
    debugCompile project(path:':lib_stub', configuration: "debug")
    releaseCompile project(path:':lib_stub', configuration: "release")
}

build.gradle(lib_stub)

android {
compileSdkVersion rootProject.ext.android["compileSdkVersion"]
buildToolsVersion rootProject.ext.android["buildToolsVersion"]

defaultConfig {
    minSdkVersion rootProject.ext.android["minSdkVersion"]
    targetSdkVersion rootProject.ext.android["targetSdkVersion"]
    versionCode rootProject.ext.android["versionCode"]
    versionName rootProject.ext.android["versionName"]
    defaultPublishConfig  "release"
    publishNonDefault true
    }
}

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    testCompile rootProject.ext.dependencies["junit"]
    debugCompile project(path:':lib_sdk', configuration: "debug")
    releaseCompile project(path:':lib_sdk', configuration: "release")
    }

build.gralde(lib_sdk)

android {
compileSdkVersion rootProject.ext.android["compileSdkVersion"]
buildToolsVersion rootProject.ext.android["buildToolsVersion"]

defaultConfig {
    minSdkVersion rootProject.ext.android["minSdkVersion"]
    targetSdkVersion rootProject.ext.android["targetSdkVersion"]
    versionCode rootProject.ext.android["versionCode"]
    versionName rootProject.ext.android["versionName"]
    defaultPublishConfig  "release"
    publishNonDefault true
    }
}

这样修改完成之后,就可以正确的使用BuildConfig.DEBUG 的值了。

1…345
Tom Liu

Tom Liu

41 日志
48 标签
© 2018 Tom Liu
由 Hexo 强力驱动
主题 - NexT.Muse