免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
论坛 程序设计 C/C++ c++
最近访问板块 发新帖
楼主: clzhana
打印 上一主题 下一主题

[C++] c++ [复制链接]

论坛徽章:
0
11 [报告]
发表于 2011-05-16 23:23 |只看该作者
C++/C经典教程(九)
第九章 构造函数、析构函数与赋值函数
构造函数、析构函数与赋值函数是每个类最基本的函数。它们太普通以致让人容易麻痹大意,其实这些貌似简单的函数就象没有顶盖的下水道那样危险。
         每个类只有一个析构函数和一个赋值函数,但可以有多个构造函数(包含一个拷贝构造函数,其它的称为普通构造函数)。对于任意一个类A,如果不想编写上述函数,C++编译器将自动为A产生四个缺省的函数,如
     A(void);                    // 缺省的无参数构造函数
     A(const A &a);                   // 缺省的拷贝构造函数
     ~A(void);                   // 缺省的析构函数
     A & operate =(const A &a); // 缺省的赋值函数

这不禁让人疑惑,既然能自动生成函数,为什么还要程序员编写?
原因如下:
(1)如果使用“缺省的无参数构造函数”和“缺省的析构函数”,等于放弃了自主“初始化”和“清除”的机会,C++发明人Stroustrup的好心好意白费了。
(2)“缺省的拷贝构造函数”和“缺省的赋值函数”均采用“位拷贝”而非“值拷贝”的方式来实现,倘若类中含有指针变量,这两个函数注定将出错。
         
对于那些没有吃够苦头的C++程序员,如果他说编写构造函数、析构函数与赋值函数很容易,可以不用动脑筋,表明他的认识还比较肤浅,水平有待于提高。
本章以类String的设计与实现为例,深入阐述被很多教科书忽视了的道理。String的结构如下:
     class String
     {
      public:
         String(const char *str = NULL); // 普通构造函数
         String(const String &other);     // 拷贝构造函数
         ~ String(void);                      // 析构函数
         String & operate =(const String &other); // 赋值函数
      private:
         char    *m_data;               // 用于保存字符串
     };
9.1 构造函数与析构函数的起源
         作为比C更先进的语言,C++提供了更好的机制来增强程序的安全性。C++编译器具有严格的类型安全检查功能,它几乎能找出程序中所有的语法问题,这的确帮了程序员的大忙。但是程序通过了编译检查并不表示错误已经不存在了,在“错误”的大家庭里,“语法错误”的地位只能算是小弟弟。级别高的错误通常隐藏得很深,就象狡猾的罪犯,想逮住他可不容易。
         根据经验,不少难以察觉的程序错误是由于变量没有被正确初始化或清除造成的,而初始化和清除工作很容易被人遗忘。Stroustrup在设计C++语言时充分考虑了这个问题并很好地予以解决:把对象的初始化工作放在构造函数中,把清除工作放在析构函数中。当对象被创建时,构造函数被自动执行。当对象消亡时,析构函数被自动执行。这下就不用担心忘了对象的初始化和清除工作。
         构造函数与析构函数的名字不能随便起,必须让编译器认得出才可以被自动执行。Stroustrup的命名方法既简单又合理:让构造函数、析构函数与类同名,由于析构函数的目的与构造函数的相反,就加前缀‘~’以示区别。
除了名字外,构造函数与析构函数的另一个特别之处是没有返回值类型,这与返回值类型为void的函数不同。构造函数与析构函数的使命非常明确,就象出生与死亡,光溜溜地来光溜溜地去。如果它们有返回值类型,那么编译器将不知所措。为了防止节外生枝,干脆规定没有返回值类型。(以上典故参考了文献[Eekel, p55-p56])
9.2 构造函数的初始化表
         构造函数有个特殊的初始化方式叫“初始化表达式表”(简称初始化表)。初始化表位于函数参数表之后,却在函数体 {} 之前。这说明该表里的初始化工作发生在函数体内的任何代码被执行之前。
         构造函数初始化表的使用规则:
u        u       如果类存在继承关系,派生类必须在其初始化表里调用基类的构造函数。
例如
     class A
     {…
         A(int x);     // A的构造函数
};   
     class B : public A
     {…
         B(int x, int y);// B的构造函数
     };
     B::B(int x, int y)
      : A(x)                // 在初始化表里调用A的构造函数
     {
      …
}   
u        u       类的const常量只能在初始化表里被初始化,因为它不能在函数体内用赋值的方式来初始化(参见5.4节)。
u        u       类的数据成员的初始化可以采用初始化表或函数体内赋值两种方式,这两种方式的效率不完全相同。
     非内部数据类型的成员对象应当采用第一种方式初始化,以获取更高的效率。例如
     class A
{…
     A(void);               // 无参数构造函数
     A(const A &other);     // 拷贝构造函数
     A & operate =( const A &other); // 赋值函数
};

     class B
     {
      public:
         B(const A &a);     // B的构造函数
      private:   
         A m_a;            // 成员对象
};

示例9-2(a)中,类B的构造函数在其初始化表里调用了类A的拷贝构造函数,从而将成员对象m_a初始化。
示例9-2 (b)中,类B的构造函数在函数体内用赋值的方式将成员对象m_a初始化。我们看到的只是一条赋值语句,但实际上B的构造函数干了两件事:先暗地里创建m_a对象(调用了A的无参数构造函数),再调用类A的赋值函数,将参数a赋给m_a。

B::B(const A &a)
: m_a(a)         
{
   …
}        B::B(const A &a)
{
m_a = a;

}
示例9-2(a) 成员对象在初始化表中被初始化      示例9-2(b) 成员对象在函数体内被初始化

对于内部数据类型的数据成员而言,两种初始化方式的效率几乎没有区别,但后者的程序版式似乎更清晰些。若类F的声明如下:
class F
{
public:
     F(int x, int y);       // 构造函数
private:
     int m_x, m_y;
     int m_i, m_j;
}
示例9-2(c)中F的构造函数采用了第一种初始化方式,示例9-2(d)中F的构造函数采用了第二种初始化方式。

F::F(int x, int y)
: m_x(x), m_y(y)           
{
   m_i = 0;
   m_j = 0;
}        F::F(int x, int y)
{
   m_x = x;
   m_y = y;
   m_i = 0;
   m_j = 0;
}
示例9-2(c) 数据成员在初始化表中被初始化     示例9-2(d) 数据成员在函数体内被初始化
9.3 构造和析构的次序
         构造从类层次的最根处开始,在每一层中,首先调用基类的构造函数,然后调用成员对象的构造函数。析构则严格按照与构造相反的次序执行,该次序是唯一的,否则编译器将无法自动执行析构过程。
一个有趣的现象是,成员对象初始化的次序完全不受它们在初始化表中次序的影响,只由成员对象在类中声明的次序决定。这是因为类的声明是唯一的,而类的构造函数可以有多个,因此会有多个不同次序的初始化表。如果成员对象按照初始化表的次序进行构造,这将导致析构函数无法得到唯一的逆序。[Eckel, p260-261]
9.4 示例:类String的构造函数与析构函数
         // String的普通构造函数
         String::String(const char *str)
{
     if(str==NULL)
     {
         m_data = new char[1];
         *m_data = ‘\0’;
     }   
     else
     {
         int length = strlen(str);
         m_data = new char[length+1];
         strcpy(m_data, str);
     }
}   

// String的析构函数
         String::~String(void)
{
     delete [] m_data;
// 由于m_data是内部数据类型,也可以写成 delete m_data;
         }
9.5 不要轻视拷贝构造函数与赋值函数
         由于并非所有的对象都会使用拷贝构造函数和赋值函数,程序员可能对这两个函数有些轻视。请先记住以下的警告,在阅读正文时就会多心:
u        u       本章开头讲过,如果不主动编写拷贝构造函数和赋值函数,编译器将以“位拷贝”的方式自动生成缺省的函数。倘若类中含有指针变量,那么这两个缺省的函数就隐含了错误。以类String的两个对象a,b为例,假设a.m_data的内容为“hello”,b.m_data的内容为“world”。
现将a赋给b,缺省赋值函数的“位拷贝”意味着执行b.m_data = a.m_data。这将造成三个错误:一是b.m_data原有的内存没被释放,造成内存泄露;二是b.m_data和a.m_data指向同一块内存,a或b任何一方变动都会影响另一方;三是在对象被析构时,m_data被释放了两次。

u        u       拷贝构造函数和赋值函数非常容易混淆,常导致错写、错用。拷贝构造函数是在对象被创建时调用的,而赋值函数只能被已经存在了的对象调用。以下程序中,第三个语句和第四个语句很相似,你分得清楚哪个调用了拷贝构造函数,哪个调用了赋值函数吗?
String a(“hello”);
String b(“world”);
String c = a;     // 调用了拷贝构造函数,最好写成 c(a);
c = b;  // 调用了赋值函数
本例中第三个语句的风格较差,宜改写成String c(a) 以区别于第四个语句。
9.6 示例:类String的拷贝构造函数与赋值函数
     // 拷贝构造函数
     String::String(const String &other)
     {   
// 允许操作other的私有成员m_data
     int length = strlen(other.m_data);   
     m_data = new char[length+1];
     strcpy(m_data, other.m_data);
}

// 赋值函数
     String & String:perate =(const String &other)
     {   
         // (1) 检查自赋值
         if(this == &other)
              return *this;
         
         // (2) 释放原有的内存资源
         delete [] m_data;
         
         // (3)分配新的内存资源,并复制内容
     int length = strlen(other.m_data);   
     m_data = new char[length+1];
         strcpy(m_data, other.m_data);
         
         // (4)返回本对象的引用
         return *this;
}   
     
     类String拷贝构造函数与普通构造函数(参见9.4节)的区别是:在函数入口处无需与NULL进行比较,这是因为“引用”不可能是NULL,而“指针”可以为NULL。
     类String的赋值函数比构造函数复杂得多,分四步实现:
(1)第一步,检查自赋值。你可能会认为多此一举,难道有人会愚蠢到写出 a = a 这样的自赋值语句!的确不会。但是间接的自赋值仍有可能出现,例如
     
// 内容自赋值
b = a;

c = b;

a = c;           // 地址自赋值
b = &a;

a = *b;

也许有人会说:“即使出现自赋值,我也可以不理睬,大不了化点时间让对象复制自己而已,反正不会出错!”
他真的说错了。看看第二步的delete,自杀后还能复制自己吗?所以,如果发现自赋值,应该马上终止函数。注意不要将检查自赋值的if语句
if(this == &other)
错写成为
     if( *this == other)
(2)第二步,用delete释放原有的内存资源。如果现在不释放,以后就没机会了,将造成内存泄露。
(3)第三步,分配新的内存资源,并复制字符串。注意函数strlen返回的是有效字符串长度,不包含结束符‘\0’。函数strcpy则连‘\0’一起复制。
(4)第四步,返回本对象的引用,目的是为了实现象 a = b = c 这样的链式表达。注意不要将 return *this 错写成 return this 。那么能否写成return other 呢?效果不是一样吗?
不可以!因为我们不知道参数other的生命期。有可能other是个临时对象,在赋值结束后它马上消失,那么return other返回的将是垃圾。
9.7 偷懒的办法处理拷贝构造函数与赋值函数
         如果我们实在不想编写拷贝构造函数和赋值函数,又不允许别人使用编译器生成的缺省函数,怎么办?
         偷懒的办法是:只需将拷贝构造函数和赋值函数声明为私有函数,不用编写代码。
例如:
     class A
     { …
      private:
         A(const A &a);                   // 私有的拷贝构造函数
         A & operate =(const A &a); // 私有的赋值函数
     };

如果有人试图编写如下程序:
     A b(a); // 调用了私有的拷贝构造函数
     b = a;        // 调用了私有的赋值函数
编译器将指出错误,因为外界不可以操作A的私有函数。
9.8 如何在派生类中实现类的基本函数
         基类的构造函数、析构函数、赋值函数都不能被派生类继承。如果类之间存在继承关系,在编写上述基本函数时应注意以下事项:
u        u       派生类的构造函数应在其初始化表里调用基类的构造函数。
u        u       基类与派生类的析构函数应该为虚(即加virtual关键字)。例如
#include <iostream.h>
class Base
{
public:
     virtual ~Base() { cout<< "~Base" << endl ; }
};

class Derived : public Base
{
public:
     virtual ~Derived() { cout<< "~Derived" << endl ; }
};

void main(void)
{
     Base * pB = new Derived; // upcast
     delete pB;
}

输出结果为:
         ~Derived
         ~Base
如果析构函数不为虚,那么输出结果为
         ~Base

u        u       在编写派生类的赋值函数时,注意不要忘记对基类的数据成员重新赋值。例如:
class Base
{
public:

     Base & operate =(const Base &other); // 类Base的赋值函数
private:
     int m_i, m_j, m_k;
};

class Derived : public Base
{
public:

     Derived & operate =(const Derived &other);     // 类Derived的赋值函数
private:
     int m_x, m_y, m_z;
};

Derived & Derived:perate =(const Derived &other)
{
     //(1)检查自赋值
     if(this == &other)
         return *this;

     //(2)对基类的数据成员重新赋值
     Base:perate =(other); // 因为不能直接操作私有数据成员

     //(3)对派生类的数据成员赋值
     m_x = other.m_x;
     m_y = other.m_y;
     m_z = other.m_z;

     //(4)返回本对象的引用
     return *this;
}

9.9 一些心得体会
有些C++程序设计书籍称构造函数、析构函数和赋值函数是类的“Big-Three”,它们的确是任何类最重要的函数,不容轻视。
也许你认为本章的内容已经够多了,学会了就能平安无事,我不能作这个保证。如果你希望吃透“Big-Three”,请好好阅读参考文献[Cline] [Meyers] [Murry]。

论坛徽章:
0
12 [报告]
发表于 2011-05-16 23:24 |只看该作者
C++/C经典教程(十)
第十章 类的继承与组合
对象(Object)是类(Class)的一个实例(Instance)。如果将对象比作房子,那么类就是房子的设计图纸。所以面向对象设计的重点是类的设计,而不是对象的设计。
对于C++程序而言,设计孤立的类是比较容易的,难的是正确设计基类及其派生类。本章仅仅论述“继承”(Inheritance)和“组合”(Composition)的概念。
注意,当前面向对象技术的应用热点是COM和CORBA,这些内容超出了C++教材的范畴,请阅读COM和CORBA相关论著。
10.1 继承
如果A是基类,B是A的派生类,那么B将继承A的数据和函数。例如:
         class A
{
public:
                   void Func1(void);
                   void Func2(void);
};

class B : public A
{
public:
                   void Func3(void);
                   void Func4(void);
};

         main()
{
                   B b;                           
                   b.Func1();           // B从A继承了函数Func1
                   b.Func2();           // B从A继承了函数Func2
                   b.Func3();
                   b.Func4();
}

这个简单的示例程序说明了一个事实:C++的“继承”特性可以提高程序的可复用性。正因为“继承”太有用、太容易用,才要防止乱用“继承”。我们应当给“继承”立一些使用规则。

l          l         【规则10-1-1】如果类A和类B毫不相关,不可以为了使B的功能更多些而让B继承A的功能和属性。不要觉得“白吃白不吃”,让一个好端端的健壮青年无缘无故地吃人参补身体。
l          l         【规则10-1-2】若在逻辑上B是A的“一种”(a kind of ),则允许B继承A的功能和属性。例如男人(Man)是人(Human)的一种,男孩(Boy)是男人的一种。那么类Man可以从类Human派生,类Boy可以从类Man派生。
         class Human
{
                   …
};
         class Man : public Human
{
                   …
};
         class Boy : public Man
{
                   …
};

u        u       注意事项
【规则10-1-2】看起来很简单,但是实际应用时可能会有意外,继承的概念在程序世界与现实世界并不完全相同。
例如从生物学角度讲,鸵鸟(Ostrich)是鸟(Bird)的一种,按理说类Ostrich应该可以从类Bird派生。但是鸵鸟不能飞,那么Ostrich::Fly是什么东西?
class Bird
{
public:        
         virtual void Fly(void);

};

class Ostrich : public Bird
{

};

例如从数学角度讲,圆(Circle)是一种特殊的椭圆(Ellipse),按理说类Circle应该可以从类Ellipse派生。但是椭圆有长轴和短轴,如果圆继承了椭圆的长轴和短轴,岂非画蛇添足?
         所以更加严格的继承规则应当是:若在逻辑上B是A的“一种”,并且A的所有功能和属性对B而言都有意义,则允许B继承A的功能和属性。
10.2 组合
l          l         【规则10-2-1】若在逻辑上A是B的“一部分”(a part of),则不允许B从A派生,而是要用A和其它东西组合出B。
例如眼(Eye)、鼻(Nose)、口(Mouth)、耳(Ear)是头(Head)的一部分,所以类Head应该由类Eye、Nose、Mouth、Ear组合而成,不是派生而成。如示例10-2-1所示。

class Eye
{
public:
void Look(void);      
};        class Nose
{
public:
void Smell(void);      
};
class Mouth
{
public:
void Eat(void);
};        class Ear
{
public:
void Listen(void);      
};
// 正确的设计,虽然代码冗长。
class Head
{
public:
                   void           Look(void) { m_eye.Look(); }
                   void           Smell(void)          { m_nose.Smell(); }
                   void           Eat(void)    { m_mouth.Eat(); }
                   void           Listen(void)        { m_ear.Listen(); }
private:
                   Eye         m_eye;
                   Nose       m_nose;
                   Mouth    m_mouth;
                   Ear          m_ear;
};
示例10-2-1 Head由Eye、Nose、Mouth、Ear组合而成
         
如果允许Head从Eye、Nose、Mouth、Ear派生而成,那么Head将自动具有Look、 Smell、Eat、Listen这些功能。示例10-2-2十分简短并且运行正确,但是这种设计方法却是不对的。

         // 功能正确并且代码简洁,但是设计方法不对。
class Head : public Eye, public Nose, public Mouth, public Ear
{
};
示例10-2-2 Head从Eye、Nose、Mouth、Ear派生而成

一只公鸡使劲地追打一只刚下了蛋的母鸡,你知道为什么吗?
因为母鸡下了鸭蛋。
很多程序员经不起“继承”的诱惑而犯下设计错误。“运行正确”的程序不见得是高质量的程序,此处就是一个例证。

论坛徽章:
0
13 [报告]
发表于 2011-05-16 23:25 |只看该作者
C++开发者快速学习Objective-C语言核心语法
本文将Objective-C讨论了语言的核心语法。将详述一些具体的语法。正如你期待的一样,涉及到了定义和类内存管理 等。
本文将Objective-C讨论了语言的核心语法。这部分开始详述一些具体的语法。正如你期待的一样,涉及到了定义和类。
类并不是特殊的
在Smalltalk中,类是具有一些特性的对象。在Objective-C中也一样。一个类是一个对象,对象回应消息。Objective-C和C++都分离了对象分配和初始化。
在C++中,对象分配通过新的操作。在Objective-C中,这样的操作是通过给类发送分配消息—调用malloc()或者一个等价。
C++中的初始化是通过调用一个与类同名的函数。Objective-C并没有区别初始化方法和其他方法,但出于惯例缺省的初始化方法就是初始化。
当你声明一个方法让实例去回应,声明通常已“-”开头,并且“+”用作类的方法。在文档中对这些消息使用一些前缀是很普遍的,所以你也可以说+alloc和-init来暗示alloc是传给一个类,init传给实例。
类在Objective-C中,正如在其他一些面向对象语言,都是对象工厂。大多数类不用自行实现+alloc,而是从他们的父类中继承。在NSObject中,父类在大多数Objective-C程序中,+alloc方法调用+allocWithZone:.使NSZone作为一个参数,一个C结构包含对象分配的一些策略。回顾19世纪80年代,当Objective-C用在NeXTstep来实现设备驱动和只有8MB内存25MHZ的CPU机器的GUI上面时,NSZone对优化非常重要。同时,这或多或少的被Objective-C程序员所忽视。(很有可能成为象NUMA构架一样流行,更普遍。)
众多优秀的特性之一就是对象创建语义是由库定义的并且语言不是类簇的思想。当你传一个-init消息给对象时,它返回一个初始化对象。这可能是你发送消息的那个对象,但不一定肯定就是。这和其他初始化程序一致的。很有可能一些公共类的特殊子类在不同数据上更有效。
实现这个特性的通用方法叫做isa-swizzling。正如我前述,Objective-C对象是C结构,这些结构第一个元素是指向类的指针。这个元素是可存取的,正如其他实例变量一样;你可以在运行时通过分配新值来改变对象的类。当然,如果你对对象的类设置在内存中有着不同的布局,这些设置可能严重错误。
然而,你可以通过一个父类来定义布局和通过子集的集合定义行为,举例来说,这个技术用在标准化字符串类(NSString),它对不同的文本字符集、静态事物和其它一些有着各种各样的实例。
因为类是对象,你可以象操作对象一样操作他们。举例来说,你可以把他们放在集合。当我有一些输入事件需要通过不同的类的实例来处理时我就使用这种格式。你需要创建一个目录映射事件命名到类,然后为每一个输入事件实例化一个对象。如果你在一个库中这么做,它允许代码的使用者轻松的注册属于他们自己的句柄。
类型和指针
Objective-C没有公开允许在堆栈上定义对象。但并不是真的—很有可能在堆栈上定义对象,但有些困难,因为它破坏了对内存管理的一种假设。结果,每一个Objective-C对象都是一个指针。一些类型由Objective-C定义;这些类型在头部定义作为C类型。
在Objective-C中最普遍的3种类型就是id,Class和SEL。id就是指向Objective-C对象的指针,它等价于C语言中的void*,你可以映射任何对象指针类型指向它并且映射他指向其它的对象指针类型。
你可以传任何消息给id,但如果不支持的话会返回一个运行时异常。
类是指向Objective-C类的指针。类是对象,所以也可以接收消息。类名是一种类型,不是可变的。标识符NSObject是一个NSObject实例的类型,但也可作为消息接受者。你可以获得一个类,如下:
1.        [NSObject class];  
2.         
发送一个+class消息给NSObject类,然后返回一个指向代表类的类结构指针。
这对我们回顾是非常有用的[FSAGE],正如我们在这个系列第二部分看到的一样。
第三种类型SEL,代表一个选择器—一个代表方法名的抽象。你可以在编译时通过@selector()直接创建,或在运行时通过C字符串调用运行时库函数,或用OpenStep NSSelectorFromString()函数,这个函数给Objective-C字符串一个选择器。这个技术允许你通过名字调用方法。你可以在C中通过使用类似dlsym(),但在C++中非常不同。在Objective-C中,你可以做的如下:
1.        [object perfomSelectorselector(doSomething)];  
2.         
这等价于如下:
1.        [object doSomething];  
2.         
显然,第二种格式速度稍微快些,因为第一种传送两个消息。后面,我们会看到通过选择器处理的一些细节。
C++没有与id相同的类型。因为对象总是可以类型化的。在Objective-C,你可以选择类型系统。下面的两种都是有效的:
1.        id object = @”a string”;  
2.         
3.        NSString *string = @”a string”;  
4.         
常量字符串实际上是NSConstantString类的一个实例,NSConstantString类是NSString的子类。将它引用到NSString* 使编译时对消息进行类型检查和存储公共实例变量(这在Objective-C从未使用过)。注意你可以通过如下改变这一设置:
1.        NSArray *array = (NSArray*)string;  
2.         
如果给数组发送消息,编译器将会检查NSArray能接收的消息。这并不是非常有用,因为对象是一个字符串。如果发送一个NSArray和NSString实现的消息,可能会有作用。如果你发送的消息NSString没有实现,一个异常将会抛出。
强调Objective-C和C++的不同的这件事看起来比较奇怪。Objective-C有类型-值语法,而C++有类型-变量语法。在Objective-C,对象类型是对象专有的一种属性。在C++,类型取决于变量的类型。
在C++中,当你指派一个指针指向一个对象到一个变量定义一个指向父类的指针,两个指针可能没有相同的数值(这可以通过多继承实现,而Objective-C不支持这种。)
定义类
Objective-C类定义有一个接口和一个实现部分。与C++有相似的地方,但两个稍微有些混。
Objective-C中的接口仅定义位并且明确的需要公开。对于实现的原因,这包括私有实例变量在大部分的实现中,因为你无法继承一个类除非你知道它多大。最近的一些实现,象Apple的64位运行时则没有这种限制。
Objective-C对象的接口如下:
1.        @interface AnObject : NSObject  
2.         
3.        {  
4.         
5.        @private  
6.         
7.        int integerivar  
8.         
9.        @public  
10.         
11.        id anotherObject;  
12.         
13.        }  
14.         
15.        + (id) aClassMethod;  
16.         
17.        - (id) anInstanceMethodNSString*)aString withid)anObject  
18.         
19.        @end  
20.         
第一行包含3个部分。标识符AnObject 是新类的名字。冒号后面的名字是NSObject。(这是可选的,但每一个Objective-C 对象都应拓展NSObject)。在括号内的名字是协议——与Java中的接口相似——通过类来实现。
正如C++实例变量(C++中的域)可以存取修饰符,不象C++,这些修饰符以@为前缀是为了避免与C标识符冲突。
Objective-C不支持多继承,所以只有一个父类。所以,对象第一部分的布局总是与父类实例的布局一致。这在过去常常定义为动态,意味着改变类中实例变量需要它所有子类重新编译。在较新的运行时这种限定并不要求,在存取实例实例变量上开支稍微大些。这种决策的另一个影响就是Objective-C其他特性中的一个。
1.        struct_AnObject  
2.         
3.        {  
4.         
5.        @defs(AnObject);  
6.         
7.        };  
8.         
@def表示着对特定对象所有域都插入这种结构,所以struct_AnObject 和AnObject类的实例有着相同的内存结构。举个例子来说,你可以通过这种规则可以直接存取实例变量。一个通常的用法就是允许C函数直接操作Objective-C对象,是基于性能原因。
正如我前面暗示的,与这个特性相关的另一件事就是可以在堆栈上创建对象。因为结构和对象在[FSAGE]内存布局中有着相同的结构,你可以简单的创建结构,设置他的指针指向正确的类,然后映射一个指针指向一个对象指针。然后你可以当做对象来使用,虽然你不得不小心没有什么可以保持指针不越界。(现实世界中我从没有使用这种方法,仅仅理论上可能。)
不象C++,Objective-C没有私有的或受保护的方法。Objective-C对象上的任何方法可以被其他对象调用。如果你在接口中没有声明方法,就是非正式私有的。将会得到运行时警告:对象不回应这种消息,但是你任然可以调用它。
接口和C中头部声明很相似。但它仍然需要一个实现,这并不奇怪,可以使用@implementation来定义。
1.        @implementation AnObject  
2.         
3.        + (id) aClassMethod  
4.         
5.        {  
6.         
7.        ...  
8.         
9.        }  
10.         
11.        - (id) anInstanceMethodNSString*)aString withid)anObject  
12.         
13.        {  
14.         
15.        ...  
16.         
17.        }  
18.         
19.        @end  
20.         
注意参数类型是特定的,在括号里。这是从C重用映射语法来展示值映射到类型;他们可能不是类型。准确来说当映射时应用相同的规则。这意味着映射在不兼容对象指针类型间会导致一个警告(不是错误)。
内存管理
传统的,Objective-C不提供任何内存管理。在早期版本中,对象类实现一个+new方法调用malloc()来创建一个新对象。当使用完这个对象,传一个-free消息。任何一个对象从NSObject继承回应一个-retain和-release消息。当你使用完这个对象,你传一个-free消息。OpenStep添加了参考计算。
每一个从NSObject继承的对象都回应-retain和-release消息。当你想要保留一个指向对象的指针,你可以发送一个-retain消息。当你使用完以后,你可以发送一个-release消息。
这个设计有个细微问题。通常你不需要保持一个指向对象的指针,但是你也不想释放。一个典型的例子在返回一个对象时候,调用者需要保持指向对象的指针,但你不想这么做。
这个问题的解决方案就是NSAutoreleasePool类。加上-retain和-release,NSObject也回应-autorelease消息。当你发送其中一个,和现前的自动释放池一同注册。当这个池对象被注销,它发送一个-release消息给每个对象,而对象在这之前先收到-autorelease消息。在OpenStep应用中,一个NSAutoreleasePool实例在循环开始的时候创建,在结束的时候销毁。你也可以创建属于你自己的实例来自动释放对象。
这个机制减少了一些C++所需的复制。其实也不值得这么做,在Objective-C,易变性是对象的属性,不是参考。在C++,有常量指针和非常量指针。不允许在常量对象上调用非常量方法。这保证不了对象不会被改变——仅仅因为你不想改变。
在Objective-C中,一个常态模式定义了一个不变的类和可变的子类。NSString就是一个典型例子;
它有一个可变的子类NSMutableString。如果你得到NSString并且想保存下来,你可以传一个-retain消息并且不用复制操作就可以保存指针。相反地,你可以传一个+stringWithString:message给NSString。不管这个参数是否可变都会检查并返回原始指针。
在Apple和GNU运行时,Objective-C都支持存储性的垃圾回收,这会避免对-retain和-release的需要。在现存的框架中对语言的附加并不总是很好的支持的,并且在用的时候需要格外小心。
总结
既然我们已经浏览了Objective-C语言的核心,在这部分的总结我们将会看到更多的一些高级话题。

论坛徽章:
0
14 [报告]
发表于 2011-05-16 23:26 |只看该作者
很多C++程序员,应该说是绝大多数,都学过C语言。也都知道,C++是完全兼容C的。在C++和C都作为面向过程开发语言时,这个“兼容”是有两层含义的:
第一,C中的所有代码复制到cpp文件里,编译肯定可以通过。
第二,C++代码,代码在C文件里是不一定允许编译的。
第二点是会慢慢被C++程序员所遗忘的,至少我是应当感到惭愧的其中之一。
下面一一列举一些,面向过程中易忽略的点(待续):
1、        C语言中不存在“引用”
请不要讲C++中引用的概念,想当然的带到C中,在C语言里没有引用这一说。符号“&”,很忠实地担当着取地址操作符这一独一无二的角色。
int f(int &para)
{
        return para;
}
以上这个函数声明在C中是绝对不合法的,要达到直接操作实参的效果,只能通过指针型参数来实现。

2、        强制类型转换操作符“()”
#include <stdio.h>
int main()
{
        int a=1;
        printf("%c\n",char(a));
        return 0;
}
在C中,以上这段代码是编译不通过的,C++程序员找这个错误是有点难的,大家可以想一想错在哪儿了,呵呵。

3、        C语言中没有函数模板,这个错在误编译时,会集中地暴露在class和typename这两个关键字上。

4、        C++中声明函数形参默认值的方式不能带到C中来

其实,早在C语言中,函数已经具有了多态特性,我们最常用的一个例子就是printf()函数。它的参数列表是可变的,但是这不是用简单的形参默认值实现的,它有专门的稍微复杂的方法实现。

5、        C语言中的前置自增和前置自减运算符不可做左值(栽过跟头)

int a=0;
++a=2;
这两行代码在cpp文件中编译时,是合法的,得到结果a=2。但是在C文件中会产生一个编译时错误,原因就在于C中的前置自增的结果不是左值的。

论坛徽章:
0
15 [报告]
发表于 2011-05-16 23:33 |只看该作者
这是C++信徒的摩西十戒,值得将其铭刻在显示器的边缘,供C++程序员们每日膜拜。我要将其铭刻在我的blog里,铭刻在我的记忆里,直到它们成为我思维的一部分。

第0条:不要拘泥于细节(了解哪些东西不应该标准化)

缩进:不必规定如何缩进,每个人遵从一个自己喜欢的规则即可。
行长:今天已经没有太大的必要限制80个字符了,当然,越有利于阅读越好。
命名:不要太严苛,除了宏应该全部大写外,别的只要遵从某种大家接受的风格即可。常见的风格有2种,一种是连字符连接全部小写的单词,另一种是单词首字母大写。如果需要使用各种第三方库,基本上很难保持一种风格。在一定的范围内保持一致,目标是使得阅读更容易即可。
注释:不要规定注释的格式。不过,使用doxygen语法的注释是个好主意,我一直用doxygen产生文档。
匈牙利记法:很高兴,我为自己厌恶的东西找到了支持我的同盟军。在C++语言中借助一点小伎俩来表达类型是无益的,C++压根就不需要这些,它只会带来混乱。
单入口单出口:在支持异常和确定性析构的C++世界,这是多余的。
在这里,旗帜鲜明地反对了两样东西:匈牙利记法和单入单出原则。

第1条:在高警告级别干净利落地编译
把编译器的警告级别开到最大,并且能够悄无声息地生成结果。对于那种视警告如无物的人,关门!放狗!也不要轻易的在源代码中关闭编译器警告。

对于第三方库,在包含文件周围加以屏蔽即可
未使用参数:不提供该参数名称即可
未使用的变量:插入一个该变量的表达式即可。这算是一个惯用法了,会比较多的用到。
变量未初始化:初始化。需要通过一个过程来初始化的例外,即形如:obj; init_obj(obj);
某些分支没有return返回值。在这些分支上加入断言:assert(false);再接一个return返回值。
有、无符号不匹配。如果无法避免,预先写好强制转换。个人认为,尽量避免无符号数,即使是处理理论上就没有符号的数据,有符号数适应性更好。几乎没有必要使用无符号数---除了某些位操作。
第2条:使用自动构建系统。
这个就是DailyBuild嘛!对一个团队项目而言,DailyBuild就是心跳,它应该可以通过一个按钮或是一条命令就能构建出整个系统。您的心跳正常吗?这里的关键是:只要一个操作就能完成所有工作。

第3条:使用版本控制系统
还有那个团队没有使用vcs?如果没有,“盲人骑瞎马,夜半临深池”,真是极好的写照。

第4条:在代码审查上投入
很多团队其实是没有有效的代码审查的。亮出自己的代码,阅读别人的代码,这也是熟悉整个项目的好方法。把代码投影在墙上,几个人坐下来一起评论也是有效的方法。

论坛徽章:
0
16 [报告]
发表于 2011-05-16 23:37 |只看该作者
OOP技术:面向对象及C++基础知识

  很长时间以来,就听说C++怎么怎么好,今天总算是开始学习了!拿到教材一看,我十分同意有些学友的看法,就是这些教材怎么就象是从外国话直接翻译过来似的,他们自以为说得很清楚了,而咱们一看,却总觉得那个跳得太快,由于专有名词的增加,文字看上去总不是那么顺畅,确实有点别扭,果真汉语就不适合表达科学性强的理论吧。(想想也不是,因为这些"科学"总是外国人发明出来的,所以只好这么将就着用中国话套了。)那么要看明白,并理解所有内容,这些名词总是必须先弄清楚和熟悉的,就要熟悉到象是看I Love you那么熟悉才好。
  学习C++,一般是要先学C语言的,但是书本上称不学C语言照样能学好而且学习C++更容易。但是对于这本教材,大家可别信,如果没学过C语言(或者别的什么语言)那肯定没办法学下去的。因为C++程序的许多基本形式如标识符的规范,表达式,语句,函数,等等这些概念都是同C一样的,而本教材按考试计划,认为我们已经学过C了,就简略了所有的基础知识,所以没学过C的朋友,还是要找来C语言先看一看,主要是数据类型、程序的语法等基本内容。(这些写教材的教授,把我们也想得太聪明了,其实我很笨的!)
  还有,学C++的朋友最好先学过<>,这是本课程的先行课程,学过之后,便于对计算机结构、软硬件概念有一个初步的了解。
  学好C++,对以后的课程设计有大大的好处喔,我们可以选C++来进行<>的"开发"了......
  准备好了么? 那我们就一起来学习吧。

面向对象及C++基础知识
  
本章主要是给我们学习C++打个基础。应当理解面向对象程序设计的思想和C++语言中的新思想。以后学到类时,就会更清楚的明白面向对象的含义了,而面向对象程序设计方法我们要了解对象的含义,对象就如同现实世界的各种实体,每个实体与别的实体既是分离的但又是可以相互作用和联系的,每个对象都有自己的内部状态和运动规律,当这些对象(实体)按一定的规律存在和相互作用时就构成了一个具有某种功能的系统。
  面向对象程序设计方法要求语言必须具备"抽象、封装、继承和多态性"这几个关键要素。
  比如在现实生活中,某一棵桃树可以看做是一个具体对象,一棵梨树也是一个对象,而"树"的形态和特征是两者共有的,世上并无"树"这样的东东,但却有"树'这样的"类型"。而在程序中,我们反过来为之,先把类型抽象出来,比如我们定义了一个类,说明哪些特征,然后可以定义一个完全符合这些特征的一个"变量",这个变量就是一个对象。所以,抽象就是站在更高一个层次来看待问题。
  那封装就是把一组数据和与其有关的操作集合组装在一起,形成一个能动的实体,也就是对象。就象电路设计,以前是用一个个晶体管的,后来把能完成某一功能的晶体管做在一起封装起来,并只提供几只引脚,外界只能通过这些引脚而不能通过别的途径来"访问"内部功能,这就成了集成电路。封装使得一个对象具备独立和明确的功能,并提供接口便于和其它对象作用,而其内部的代码和数据都是受保护的。好处不言自明。
  继承就是一个对象可以获得另一个对象的特性的机制,书上说得明白。
  多态性很有意思,不同的对象可以调用相同名称的函数,却能导致完全不同的行为的现象。这在现实生活中也会发生,比如"我"这个对象去调用"玩电脑"这个"函数"时,一般发生的是打字的行为,而"我表弟"去调用"玩电脑"这个函数时,总是发生"游戏大战"的行为。这是由于"玩电脑"这个函数它能根据不同的对象做出不同的反应。我们只管"玩电脑"就行了。
  以上一段要记住这个四要素,并能理解它们的意义。
  C++语言不是一种纯面向对象的语言(还用到main()这样的全局函数)但是应当尽可能的把变量和函数都限制在局部的类中。(类就是一种自定义的结构数据类型)
  C++程序与C相比的改进:
改进之处        C语言        C++
输入输出(综)        scanf 函数和 printf 函数,由于各种类型的控制令使用很不舒服,也容易出错        用cout<> 输入。用起来更方便。
注释(综)        /*....*/        //
(告别)宏定义(综)        #define         在变量前加const(constant,常量的意思),它可以修饰指针变量。
分三种情况要弄清
函数原形和缺省参数(综)        函数原形事实就如同C中的函数说明和定义,并且在先。        一定要先有定义或者说明,使得在编译时就能进行类型检查。
缺省参数就是在设定的最后的参数后加上缺省值; 这样,在使用这些参数时,可以不用给这些参数传递值,编译器会自动赋给它们缺省值。
动态内存分配函数(简)        malloc(),free(),sizeof()        new type(size) delete 这真是容易理解和使用呢。
换行符(综)        printf("\n");        <
内联函数(综)        用带参数宏实现        函数前加上 inline,既有参数宏的作用,又避免副作用。
引用(综)        没有这样的概念,一般用指针        引用就是给原来的变量再取个别名,其实是同一个家伙,取名的办法就是在新名字前加&(现称引用运算符)等于原变量。
引用只是一个别名,我们只能给变量起别名,但不能给"别名"再起个"别名",这就是说的"引用不是变量,不能说明引用的引用"的意思。 但是我们可以给指针变量起别名。(引用数组或指针)
嵌入指令        #include "filename"        可以加入路径,用单反斜杠\,
在程序中包含路径时得用双反斜杠\\。
宏定义        #define        常量或无参数的地方可用 const 代替,有参数时可用 inline 函数代替。
注意 与#define 一起使用的 # 和## 两个运算符的作用。
条件编译指令(领)        #ifdef #ifndef        #if #else #elif #endif #error define
  另外,改变习惯重新思考,我们要记一以下几点(记):
1.        用C++来编译C程序,了解C++的严格检查机制。
2.        重新设计C程序。掌握C++的新特性。
3.        外部变量说明放在头文件中(培养好习惯)
4.        少用预处理(#define)而用内联子过程(inline)和const(定义常量)
5.        要重视函数类型。任何函数都要定义其类型,而且是严格的。

(综)指应当达到综合应用层次,(领)指达到领会层次。(简)指简单应用,(记)指识记层次。

论坛徽章:
1
2015年迎新春徽章
日期:2015-03-04 09:49:45
17 [报告]
发表于 2011-05-16 23:47 |只看该作者
回复 4# nizvoo


    窃以为,注释的艺术在于没有注释。就跟代码一样,不存在的代码就是最好的代码{:3_189:}

论坛徽章:
0
18 [报告]
发表于 2011-05-17 16:04 |只看该作者
楼上的规则,只能作为参考。

不同的人,有不同的编程习惯。

大家看着明白,团队格式统一,自己用着舒服,这就OK了。

论坛徽章:
0
19 [报告]
发表于 2011-05-17 23:04 |只看该作者
Android深入浅出之Binder机制
一 说明
Android系统最常见也是初学者最难搞明白的就是Binder了,很多很多的Service就是通过Binder机制来和客户端通讯交互的。所以搞明白Binder的话,在很大程度上就能理解程序运行的流程。
我们这里将以MediaService的例子来分析Binder的使用:
ServiceManager,这是Android OS的整个服务的管理程序
MediaService,这个程序里边注册了提供媒体播放的服务程序MediaPlayerService,我们最后只分析这个
MediaPlayerClient,这个是与MediaPlayerService交互的客户端程序

下面先讲讲MediaService应用程序。
二 MediaService的诞生
MediaService是一个应用程序,虽然Android搞了七七八八的Java之类的东西,但是在本质上,它还是一个完整的linux操作系统,也还没有牛到什么应用程序都是JAVA写。所以,MS(MediaService)就是一个和普通的C++应用程序一样的东西。
MediaService的源码文件在:framework\base\Media\MediaServer\Main_mediaserver.cpp中。让我们看看到底是个什么玩意儿!
int main(int argc, char** argv)
{
//FT,就这么简单??
//获得一个ProcessState实例
sp<rocessState> proc(ProcessState::self());
//得到一个ServiceManager对象
    sp<IServiceManager> sm = defaultServiceManager();
    MediaPlayerService::instantiate();//初始化MediaPlayerService服务
    ProcessState::self()->startThreadPool();//看名字,启动Process的线程池?
    IPCThreadState::self()->joinThreadPool();//将自己加入到刚才的线程池?
}
其中,我们只分析MediaPlayerService。
这么多疑问,看来我们只有一个个函数深入分析了。不过,这里先简单介绍下sp这个东西。
sp,究竟是smart pointer还是strong pointer呢?其实我后来发现不用太关注这个,就把它当做一个普通的指针看待,即sp<IServiceManager>======》IServiceManager*吧。sp是google搞出来的为了方便C/C++程序员管理指针的分配和释放的一套方法,类似JAVA的什么WeakReference之类的。我个人觉得,要是自己写程序的话,不用这个东西也成。
好了,以后的分析中,sp<XXX>就看成是XXX*就可以了。
2.1 ProcessState
第一个调用的函数是ProcessState::self(),然后赋值给了proc变量,程序运行完,proc会自动delete内部的内容,所以就自动释放了先前分配的资源。
ProcessState位置在framework\base\libs\binder\ProcessState.cpp
sp<rocessState> ProcessState::self()
{
    if (gProcess != NULL) return gProcess;---->第一次进来肯定不走这儿
    AutoMutex _l(gProcessMutex);--->锁保护
    if (gProcess == NULL) gProcess = new ProcessState;--->创建一个ProcessState对象
return gProcess;--->看见没,这里返回的是指针,但是函数返回的是sp<xxx>,所以
//把sp<xxx>看成是XXX*是可以的
}
再来看看ProcessState构造函数
//这个构造函数看来很重要
ProcessState:rocessState()
    : mDriverFD(open_driver())----->Android很多代码都是这么写的,稍不留神就没看见这里调用了一个很重要的函数
    , mVMStart(MAP_FAILED)//映射内存的起始地址
    , mManagesContexts(false)
    , mBinderContextCheckFunc(NULL)
    , mBinderContextUserData(NULL)
    , mThreadPoolStarted(false)
    , mThreadPoolSeq(1)
{
if (mDriverFD >= 0) {
//BIDNER_VM_SIZE定义为(1*1024*1024) - (4096 *2) 1M-8K
        mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE,
mDriverFD, 0);//这个需要你自己去man mmap的用法了,不过大概意思就是
//将fd映射为内存,这样内存的memcpy等操作就相当于write/read(fd)了
    }
    ...
}
最讨厌这种在构造list中添加函数的写法了,常常疏忽某个变量的初始化是一个函数调用的结果。
open_driver,就是打开/dev/binder这个设备,这个是android在内核中搞的一个专门用于完成
进程间通讯而设置的一个虚拟的设备。BTW,说白了就是内核的提供的一个机制,这个和我们用socket加NET_LINK方式和内核通讯是一个道理。
static int open_driver()
{
    int fd = open("/dev/binder", O_RDWR);//打开/dev/binder
    if (fd >= 0) {
      ....
        size_t maxThreads = 15;
       //通过ioctl方式告诉内核,这个fd支持最大线程数是15个。
        result = ioctl(fd, BINDER_SET_MAX_THREADS, &maxThreads);    }
return fd;
好了,到这里Process::self就分析完了,到底干什么了呢?
打开/dev/binder设备,这样的话就相当于和内核binder机制有了交互的通道
映射fd到内存,设备的fd传进去后,估计这块内存是和binder设备共享的

接下来,就到调用defaultServiceManager()地方了。
2.2 defaultServiceManager
defaultServiceManager位置在framework\base\libs\binder\IServiceManager.cpp中
sp<IServiceManager> defaultServiceManager()
{
    if (gDefaultServiceManager != NULL) return gDefaultServiceManager;
    //又是一个单例,设计模式中叫 singleton。
    {
        AutoMutex _l(gDefaultServiceManagerLock);
        if (gDefaultServiceManager == NULL) {
//真正的gDefaultServiceManager是在这里创建的喔
            gDefaultServiceManager = interface_cast<IServiceManager>(
                ProcessState::self()->getContextObject(NULL));
        }
    }
   return gDefaultServiceManager;
}
-----》
gDefaultServiceManager = interface_cast<IServiceManager>(
                ProcessState::self()->getContextObject(NULL));
ProcessState::self,肯定返回的是刚才创建的gProcess,然后调用它的getContextObject,注意,传进去的是NULL,即0
//回到ProcessState类,
sp<IBinder> ProcessState::getContextObject(const sp<IBinder>& caller)
{
if (supportsProcesses()) {//该函数根据打开设备是否成功来判断是否支持process,
//在真机上肯定走这个
        return getStrongProxyForHandle(0);//注意,这里传入0
    }
}
----》进入到getStrongProxyForHandle,函数名字怪怪的,经常严重阻碍大脑运转
//注意这个参数的命名,handle。搞过windows的应该比较熟悉这个名字,这是对
//资源的一种标示,其实说白了就是某个数据结构,保存在数组中,然后handle是它在这个数组中的索引。--->就是这么一个玩意儿
sp<IBinder> ProcessState::getStrongProxyForHandle(int32_t handle)
{
    sp<IBinder> result;
    AutoMutex _l(mLock);
handle_entry* e = lookupHandleLocked(handle);--》哈哈,果然,从数组中查找对应
索引的资源,lookupHandleLocked这个就不说了,内部会返回一个handle_entry
下面是 handle_entry 的结构
/*
struct handle_entry {
                IBinder* binder;--->Binder
                RefBase::weakref_type* refs;-->不知道是什么,不影响.
            };
*/
    if (e != NULL) {
        IBinder* b = e->binder; -->第一次进来,肯定为空
        if (b == NULL || !e->refs->attemptIncWeak(this)) {
            b = new BpBinder(handle); --->看见了吧,创建了一个新的BpBinder
            e->binder = b;
            result = b;
        }....
    }
    return result; 返回刚才创建的BpBinder。
}
//到这里,是不是有点乱了?对,当人脑分析的函数调用太深的时候,就容易忘记。
我们是从gDefaultServiceManager = interface_cast<IServiceManager>(
                ProcessState::self()->getContextObject(NULL));
开始搞的,现在,这个函数调用将变成
gDefaultServiceManager = interface_cast<IServiceManager>(new BpBinder(0));
BpBinder又是个什么玩意儿?Android名字起得太眼花缭乱了。
因为还没介绍Binder机制的大架构,所以这里介绍BpBinder不合适,但是又讲到BpBinder了,不介绍Binder架构似乎又说不清楚....,sigh!
恩,还是继续把层层深入的函数调用栈化繁为简吧,至少大脑还可以工作。先看看BpBinder的构造函数把。
2.3 BpBinder
BpBinder位置在framework\base\libs\binder\BpBinder.cpp中。
BpBinder::BpBinder(int32_t handle)
    : mHandle(handle) //注意,接上述内容,这里调用的时候传入的是0
    , mAlive(1)
    , mObitsSent(0)
    , mObituaries(NULL)
{
   IPCThreadState::self()->incWeakHandle(handle);//FT,竟然到IPCThreadState::self()
}
这里一块说说吧,IPCThreadState::self估计怎么着又是一个singleton吧?
//该文件位置在framework\base\libs\binder\IPCThreadState.cpp
IPCThreadState* IPCThreadState::self()
{
    if (gHaveTLS) {//第一次进来为false
restart:
        const pthread_key_t k = gTLS;
//TLS是Thread Local Storage的意思,不懂得自己去google下它的作用吧。这里只需要
//知道这种空间每个线程有一个,而且线程间不共享这些空间,好处是?我就不用去搞什么
//同步了。在这个线程,我就用这个线程的东西,反正别的线程获取不到其他线程TLS中的数据。===》这句话有漏洞,钻牛角尖的明白大概意思就可以了。
//从线程本地存储空间中获得保存在其中的IPCThreadState对象
//这段代码写法很晦涩,看见没,只有pthread_getspecific,那么肯定有地方调用

论坛徽章:
0
20 [报告]
发表于 2011-05-17 23:06 |只看该作者
// pthread_setspecific。
        IPCThreadState* st = (IPCThreadState*)pthread_getspecific(k);
        if (st) return st;
        return new IPCThreadState;//new一个对象,
    }
   
    if (gShutdown) return NULL;
   
    pthread_mutex_lock(&gTLSMutex);
    if (!gHaveTLS) {
        if (pthread_key_create(&gTLS, threadDestructor) != 0) {
            pthread_mutex_unlock(&gTLSMutex);
            return NULL;
        }
        gHaveTLS = true;
    }
    pthread_mutex_unlock(&gTLSMutex);
goto restart; //我FT,其实goto没有我们说得那样卑鄙,汇编代码很多跳转语句的。
//关键是要用好。
}
//这里是构造函数,在构造函数里边pthread_setspecific
IPCThreadState::IPCThreadState()
    : mProcess(ProcessState::self()), mMyThreadId(androidGetTid())
{
    pthread_setspecific(gTLS, this);
    clearCaller();
mIn.setDataCapacity(256);
//mIn,mOut是两个Parcel,干嘛用的啊?把它看成是命令的buffer吧。再深入解释,又会大脑停摆的。
    mOut.setDataCapacity(256);
}
出来了,终于出来了....,恩,回到BpBinder那。
BpBinder::BpBinder(int32_t handle)
    : mHandle(handle) //注意,接上述内容,这里调用的时候传入的是0
    , mAlive(1)
    , mObitsSent(0)
    , mObituaries(NULL)
{
......
IPCThreadState::self()->incWeakHandle(handle);
什么incWeakHandle,不讲了..
}
喔,new BpBinder就算完了。到这里,我们创建了些什么呢?
ProcessState有了。
IPCThreadState有了,而且是主线程的。
BpBinder有了,内部handle值为0

gDefaultServiceManager = interface_cast<IServiceManager>(new BpBinder(0));
终于回到原点了,大家是不是快疯掉了?
interface_cast,我第一次接触的时候,把它看做类似的static_cast一样的东西,然后死活也搞不明白 BpBinder*指针怎么能强转为IServiceManager*,花了n多时间查看BpBinder是否和IServiceManager继承还是咋的....。
终于,我用ctrl+鼠标(source insight)跟踪进入了interface_cast
IInterface.h位于framework/base/include/binder/IInterface.h
template<typename INTERFACE>
inline sp<INTERFACE> interface_cast(const sp<IBinder>& obj)
{
    return INTERFACE::asInterface(obj);
}
所以,上面等价于:
inline sp<IServiceManager> interface_cast(const sp<IBinder>& obj)
{
    return IServiceManager::asInterface(obj);
}
看来,只能跟到IServiceManager了。
IServiceManager.h---》framework/base/include/binder/IServiceManager.h
看看它是如何定义的:
2.4 IserviceManager

class IServiceManager : public IInterface
{
//ServiceManager,字面上理解就是Service管理类,管理什么?增加服务,查询服务等
//这里仅列出增加服务addService函数
public:
    DECLARE_META_INTERFACE(ServiceManager);
     virtual status_t   addService( const String16& name,
                                            const sp<IBinder>& service) = 0;
};
DECLARE_META_INTERFACE(ServiceManager)??
怎么和MFC这么类似?微软的影响很大啊!知道MFC的,有DELCARE肯定有IMPLEMENT
果然,这两个宏DECLARE_META_INTERFACE和IMPLEMENT_META_INTERFACE(INTERFACE, NAME)都在
刚才的IInterface.h中定义。我们先看看DECLARE_META_INTERFACE这个宏往IServiceManager加了什么?
下面是DECLARE宏
#define DECLARE_META_INTERFACE(INTERFACE)                               \
    static const android::String16 descriptor;                          \
    static android::sp<I##INTERFACE> asInterface(                       \
            const android::sp<android::IBinder>& obj);                  \
    virtual const android::String16& getInterfaceDescriptor() const;    \
    I##INTERFACE();                                                     \
    virtual ~I##INTERFACE();     
我们把它兑现到IServiceManager就是:
static const android::String16 descriptor;  -->喔,增加一个描述字符串
static android::sp< IServiceManager > asInterface(const android::sp<android::IBinder>&
obj) ---》增加一个asInterface函数
virtual const android::String16& getInterfaceDescriptor() const; ---》增加一个get函数
估计其返回值就是descriptor这个字符串
IServiceManager ();                                                     \
virtual ~IServiceManager();增加构造和虚析购函数...
那IMPLEMENT宏在哪定义的呢?
见IServiceManager.cpp。位于framework/base/libs/binder/IServiceManager.cpp
IMPLEMENT_META_INTERFACE(ServiceManager, "android.os.IServiceManager");
下面是这个宏的定义
#define IMPLEMENT_META_INTERFACE(INTERFACE, NAME)                       \
    const android::String16 I##INTERFACE::descriptor(NAME);             \
    const android::String16&                                            \
            I##INTERFACE::getInterfaceDescriptor() const {              \
        return I##INTERFACE::descriptor;                                \
    }                                                                   \
    android::sp<I##INTERFACE> I##INTERFACE::asInterface(                \
            const android::sp<android::IBinder>& obj)                   \
    {                                                                   \
        android::sp<I##INTERFACE> intr;                                 \
        if (obj != NULL) {                                              \
            intr = static_cast<I##INTERFACE*>(                          \
                obj->queryLocalInterface(                               \
                        I##INTERFACE::descriptor).get());               \
            if (intr == NULL) {                                         \
                intr = new Bp##INTERFACE(obj);                          \
            }                                                           \
        }                                                               \
        return intr;                                                    \
    }                                                                   \
    I##INTERFACE::I##INTERFACE() { }                                    \
I##INTERFACE::~I##INTERFACE() { }                                   \
很麻烦吧?尤其是宏看着头疼。赶紧兑现下吧。
const
android::String16 IServiceManager::descriptor(“android.os.IServiceManager”);
const android::String16& IServiceManager::getInterfaceDescriptor() const
{  return IServiceManager::descriptor;//返回上面那个android.os.IServiceManager
   }                                                                     
android::sp<IServiceManager> IServiceManager::asInterface(
            const android::sp<android::IBinder>& obj)
    {
        android::sp<IServiceManager> intr;
        if (obj != NULL) {                                             
            intr = static_cast<IServiceManager *>(                          
                obj->queryLocalInterface(IServiceManager::descriptor).get());               
            if (intr == NULL) {                                         
                intr = new BpServiceManager(obj);                          
            }                                                           
        }                                                               
        return intr;                                                   
    }                                                                  
    IServiceManager::IServiceManager () { }                                    
    IServiceManager::~ IServiceManager() { }
哇塞,asInterface是这么搞的啊,赶紧分析下吧,还是不知道interface_cast怎么把BpBinder*转成了IServiceManager
我们刚才解析过的interface_cast<IServiceManager>(new BpBinder(0)),
原来就是调用asInterface(new BpBinder(0))
android::sp<IServiceManager> IServiceManager::asInterface(
            const android::sp<android::IBinder>& obj)
    {
        android::sp<IServiceManager> intr;
        if (obj != NULL) {                                             
            ....                                       
                intr = new BpServiceManager(obj);
//神呐,终于看到和IServiceManager相关的东西了,看来
//实际返回的是BpServiceManager(new BpBinder(0));                          
            }                                                           
        }                                                               
        return intr;                                                   
}                                 
BpServiceManager是个什么玩意儿?p是什么个意思?
2.5 BpServiceManager
终于可以讲解点架构上的东西了。p是proxy即代理的意思,Bp就是BinderProxy,BpServiceManager,就是SM的Binder代理。既然是代理,那肯定希望对用户是透明的,那就是说头文件里边不会有这个Bp的定义。是吗?
果然,BpServiceManager就在刚才的IServiceManager.cpp中定义。
class BpServiceManager : public BpInterface<IServiceManager>
//这种继承方式,表示同时继承BpInterface和IServiceManager,这样IServiceManger的
addService必然在这个类中实现
{
public:
//注意构造函数参数的命名 impl,难道这里使用了Bridge模式?真正完成操作的是impl对象?
//这里传入的impl就是new BpBinder(0)
    BpServiceManager(const sp<IBinder>& impl)
        : BpInterface<IServiceManager>(impl)
    {
    }
     virtual status_t addService(const String16& name, const sp<IBinder>& service)
    {
       待会再说..
}
基类BpInterface的构造函数(经过兑现后)
//这里的参数又叫remote,唉,真是害人不浅啊。
inline BpInterface< IServiceManager >::BpInterface(const sp<IBinder>& remote)
    : BpRefBase(remote)
{
}
BpRefBase::BpRefBase(const sp<IBinder>& o)
    : mRemote(o.get()), mRefs(NULL), mState(0)
//o.get(),这个是sp类的获取实际数据指针的一个方法,你只要知道
//它返回的是sp<xxxx>中xxx* 指针就行
{
//mRemote就是刚才的BpBinder(0)
   ...
}
好了,到这里,我们知道了:
sp<IServiceManager> sm = defaultServiceManager(); 返回的实际是BpServiceManager,它的remote对象是BpBinder,传入的那个handle参数是0。
现在重新回到MediaService。
int main(int argc, char** argv)
{
    sp<ProcessState> proc(ProcessState::self());
sp<IServiceManager> sm = defaultServiceManager();
//上面的讲解已经完了
MediaPlayerService::instantiate();//实例化MediaPlayerservice
//看来这里有名堂!

    ProcessState::self()->startThreadPool();
    IPCThreadState::self()->joinThreadPool();
}
到这里,我们把binder设备打开了,得到一个BpServiceManager对象,这表明我们可以和SM打交道了,但是好像没干什么有意义的事情吧?
2.6 MediaPlayerService

那下面我们看看后续又*什么?以MediaPlayerService为例。它位于framework\base\media\libmediaplayerservice\libMediaPlayerService.cpp
void MediaPlayerService::instantiate() {
defaultServiceManager()->addService(
//传进去服务的名字,传进去new出来的对象
            String16("media.player"), new MediaPlayerService());
}
MediaPlayerService::MediaPlayerService()
{
    LOGV("MediaPlayerService created");//太简单了
    mNextConnId = 1;
}
defaultServiceManager返回的是刚才创建的BpServiceManager
调用它的addService函数。
MediaPlayerService从BnMediaPlayerService派生
class MediaPlayerService : public BnMediaPlayerService
FT,MediaPlayerService从BnMediaPlayerService派生,BnXXX,BpXXX,快晕了。
Bn 是Binder Native的含义,是和Bp相对的,Bp的p是proxy代理的意思,那么另一端一定有一个和代理打交道的东西,这个就是Bn。
讲到这里会有点乱喔。先分析下,到目前为止都构造出来了什么。
BpServiceManager
BnMediaPlayerService
这两个东西不是相对的两端,从BnXXX就可以判断,BpServiceManager对应的应该是BnServiceManager,BnMediaPlayerService对应的应该是BpMediaPlayerService。
我们现在在哪里?对了,我们现在是创建了BnMediaPlayerService,想把它加入到系统的中去。
喔,明白了。我创建一个新的Service—BnMediaPlayerService,想把它告诉ServiceManager。
那我怎么和ServiceManager通讯呢?恩,利用BpServiceManager。所以嘛,我调用了BpServiceManager的addService函数!
为什么要搞个ServiceManager来呢?这个和Android机制有关系。所有Service都需要加入到ServiceManager来管理。同时也方便了Client来查询系统存在哪些Service,没看见我们传入了字符串吗?这样就可以通过Human Readable的字符串来查找Service了。
---》感觉没说清楚...饶恕我吧。
2.7 addService
addService是调用的BpServiceManager的函数。前面略去没讲,现在我们看看。
virtual status_t addService(const String16& name, const sp<IBinder>& service)
    {
        Parcel data, reply;
//data是发送到BnServiceManager的命令包
//看见没?先把Interface名字写进去,也就是什么android.os.IServiceManager
        data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
//再把新service的名字写进去 叫media.player
        data.writeString16(name);
//把新服务service—>就是MediaPlayerService写到命令中
        data.writeStrongBinder(service);
//调用remote的transact函数
        status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply);
        return err == NO_ERROR ? reply.readInt32() : err;
}
我的天,remote()返回的是什么?
remote(){ return mRemote; }-->啊?找不到对应的实际对象了???
还记得我们刚才初始化时候说的:
“这里的参数又叫remote,唉,真是害人不浅啊“
原来,这里的mRemote就是最初创建的BpBinder..
好吧,到那里去看看:
BpBinder的位置在framework\base\libs\binder\BpBinder.cpp
status_t BpBinder::transact(
    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
//又绕回去了,调用IPCThreadState的transact。
//注意啊,这里的mHandle为0,code是ADD_SERVICE_TRANSACTION,data是命令包
//reply是回复包,flags=0
        status_t status = IPCThreadState::self()->transact(
            mHandle, code, data, reply, flags);
        if (status == DEAD_OBJECT) mAlive = 0;
        return status;
    }
...
}
再看看IPCThreadState的transact函数把
status_t IPCThreadState::transact(int32_t handle,
                                  uint32_t code, const Parcel& data,
                                  Parcel* reply, uint32_t flags)
{
    status_t err = data.errorCheck();

    flags |= TF_ACCEPT_FDS;
   
    if (err == NO_ERROR) {
        //调用writeTransactionData 发送数据
err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);
    }
   
      if ((flags & TF_ONE_WAY) == 0) {
        if (reply) {
            err = waitForResponse(reply);
        } else {
            Parcel fakeReply;
            err = waitForResponse(&fakeReply);
        }
      ....等回复
        err = waitForResponse(NULL, NULL);
   ....   
    return err;
}
再进一步,瞧瞧这个...
status_t IPCThreadState::writeTransactionData(int32_t cmd, uint32_t binderFlags,
    int32_t handle, uint32_t code, const Parcel& data, status_t* statusBuffer)
{
    binder_transaction_data tr;

    tr.target.handle = handle;
    tr.code = code;
    tr.flags = binderFlags;
   
    const status_t err = data.errorCheck();
    if (err == NO_ERROR) {
        tr.data_size = data.ipcDataSize();
        tr.data.ptr.buffer = data.ipcData();
        tr.offsets_size = data.ipcObjectsCount()*sizeof(size_t);
        tr.data.ptr.offsets = data.ipcObjects();
    }
....
上面把命令数据封装成binder_transaction_data,然后
写到mOut中,mOut是命令的缓冲区,也是一个Parcel
    mOut.writeInt32(cmd);
    mOut.write(&tr, sizeof(tr));
//仅仅写到了Parcel中,Parcel好像没和/dev/binder设备有什么关联啊?
恩,那只能在另外一个地方写到binder设备中去了。难道是在?
    return NO_ERROR;
}
//说对了,就是在waitForResponse中
status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult)
{
    int32_t cmd;
    int32_t err;

while (1) {
//talkWithDriver,哈哈,应该是这里了
        if ((err=talkWithDriver()) < NO_ERROR) break;
        err = mIn.errorCheck();
        if (err < NO_ERROR) break;
        if (mIn.dataAvail() == 0) continue;
        //看见没?这里开始操作mIn了,看来talkWithDriver中
//把mOut发出去,然后从driver中读到数据放到mIn中了。
        cmd = mIn.readInt32();

        switch (cmd) {
        case BR_TRANSACTION_COMPLETE:
            if (!reply && !acquireResult) goto finish;
            break;
   .....
    return err;
}
status_t IPCThreadState::talkWithDriver(bool doReceive)
{
binder_write_read bwr;
   //中间东西太复杂了,不就是把mOut数据和mIn接收数据的处理后赋值给bwr吗?
    status_t err;
    do {
//用ioctl来读写
        if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)
            err = NO_ERROR;
        else
            err = -errno;
  } while (err == -EINTR);
//到这里,回复数据就在bwr中了,bmr接收回复数据的buffer就是mIn提供的
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP