Abstract
This 章介绍的は一些基本的 EastWest of 类,具体用法方面没有什么太多的东西。
- 类是用户定义的类型,对象是类的实例。
1.抽象と类
1.1 访问Control
class 中有两个关键字:private 和 public。对于 private 部分只能通过成员函数或者 friend 函数进行访问,而无法通过 class 之外的其他函数进行访问。也就是 public 函数是程序和对象私有成员之间的桥梁,提供了对象和程序之间的接口。当然这些都是 编译层面的限制,在二进制文件调试时是可以自由访问的。
struct 关键字在 C++ 中也得到了 extend,可以 like 类一样添加成员函数,与 class 唯一的不同在于,struct 的默认属性是 public,而 class 的是 private。
class Good {
// the default attribute is private in class, so this private can be omitted.
private:
// this variable can only be access by the member function.
int a;
public:
// this function can be access by all.
void func1();
// declare a friend function which can access the private member,
//but it self is not the member of this class
friend void func2();
};
struct Very {
// the default attribute is public in struct, so this public can be omitted.
public:
private:
Good* pGood;
};
The 主要目标 of OOP(Object Orient Program)是隐藏数据,so 一个 recommended 用法是将数据放入到 private 部分,而接口的成员函数放入到 public 部分。
1.2 成员函数
这里唯一想要说明的是 成员函数 和 友元函数 在 declaration 和 definition 上面的区别。
- 成员函数在声明时只有在 public 的部分才能被外部访问,友元函数 则被添加在 public 部分。
- 添加到类中的 友元函数 只是在 类 中添加,并不能算作函数的声明,需要在类外进行额外的声明才行。
- 成员函数 中具有 this 指针,也就是其 calling convention 是 __thiscall in x86,而 友元函数 则没有 this 指针。其只是获得了访问 类中 private 成员的权限。
// the declaration in a .h file
class Good {
int a;
int b;
public:
// declaration of the member func1
void func1();
// add the func2 as frined, but this is not a declaration of func2
friend void func2();
};
// declaration the func2
void func2();
// the definition in a .cpp file
// the member funtion must use the namespace operator ::
void Good::func1() {
// have the this pointer
return a;
}
// friend function definition just as a normal function
void func2() {
// friend function don't possess the this pointer,
//but it can access the private part of the class
Good good;
return good.a + good.b;
}
下面简单介绍下 const 成员函数,它存在的意义与就是声明该成员函数不会对于类中的数据进行修改。下面的使用实例胜过千言万语。
class Good{
public:
// declaration a const member function
void func3() const;
};
//define the const member function
void Good::func3() const {
return;
}
2. Construct と Destruct
2.1 构造函数
- when:after the class 内存分配之后。
- functionality:initialize the 内存。
- 定义 Construct 函数
下面的代码演示了如何定义一个 class 的 构造函数,构造函数需要对类中的变量进行初始化,如果构造函数中接受到的参数与 class 中的数据重名,则需要使用 this 指针来使用自身的数据,because 数据作用域的匹配规则是 由近及远。
class Good {
int a;
public:
Good(int a) {
// reference class a need use the this pointer
//if don't add this pointer the same name variable will use the argument firstly
//rather than the class data
this->a = a;
return;
}
};
- 使用构造函数
构造函数使用时分为 隐式调用 和 显示调用,这两种方式在 二进制层面等价,都是调用相应的构造函数对于 class 中的数据进行初始化。C++ 中经常使用 new 运算符来动态分配类的内存,可以在申请时隐式的调用相应的构造函数。还有另一种用法,不会主动触发 构造函数,reinterpret_cast 关键字会将一块内存空间按照类的数据结构进行解析,也可以使用相应的成员函数对于数据进行处理,一般情况下不会这么使用,主要是在逆向分析时,方便动态调试。
// explict call the construct function
Good cl1 = Good(1);
// implict call the construct function
Good cl2(1);
// use the new operator to malloc the memory
Good* pcl = new Good(10);
// use the malloc to allocate the memory, this usage is not usual
//mainly use in the reverse dynamic analysis
void* p = malloc(sizeof(Good));
Good* pc2 = reinterpret_cast<Good>(p);
- 默认构造函数
所谓默认构造函数就是一个不带有任何参数的构造函数,也就是让Good a;
能够合法通过编译的函数。编译器 if and only if 没有定义任何任何构造函数时,才会提供默认构造函数。如果 Programmer 定义其他包含参数的构造函数(如:Good(int a)
),而没有定义默认构造函数,那么上述代码就会出错。这样做是编译器为了 防止出现未初始化的内存。有两种定义默认构造函数的方式:
- 给已知构造函数提供参数
// the default argument is 1
Good(int a = 1);
// so the usage is valid, and member a will be set to 1;
Good cl1;
- 重载一个默认构造函数
class Good {
int a'
public:
Good(int a) {
this->a = a;
}
// overload the construct function, and call a haing parameter construct function
Good() {
Good(0);
}
};
// valid usage,and member will be set to 0
Good cl1;
2.2 析构函数
- when:before the class 内存释放之前。
- functionality:释放其他资源。
- 定义析构函数
析构函数需要完成对于资源的释放,释放的资源主要是通过 new 或 malloc 申请到的堆空间(因为栈中的内存会被程序自身维护的很好)。
struct Very {
private:
Good* pGood;
public:
~Very() {
if(pGood != NULL) {
// release the memory
delete pGood;
pGood = NULL;
}
}
};
- 调用构造函数
调用构造函数的任务由 编译器完成 完成,不需要程序员去手动调用(千万不要去手动调用析构函数,会出现内存释放错误)。
- 栈中的类 会在函数结束时调用相应的析构函数,对于这些数据编译器会将析构函数的调用放置在函数的结尾处,所以不是 Programmer 不调用,而是编译器帮我们调用了。
- 堆中的类 会在使用 delete 运算符时调用相应的析构函数(应该不会有人用 malloc とfree 这对函数来控制类的内存吧)。
- 一句话概括析构函数:你不可以用,但是不能没有。
3. this 指针
this 指针用处很大,可记录下的内容却很少,所以这部分很简单,一句话就能够概括:this 指针用来区分你我。
下面的代码中就是 this 指针的常用方式,总之当需要区分你我时,就用 this 准没错。
class Goodvery {
int nVal;
public:
Goodvery(int nVal) {
// use the this to get the self data
this->nVal = nVal;
}
const Goodvery& Compare(const Goodvery& a) {
if(nVal > a.nVal)
// return self using this
return *this;
else
reutrn a;
}
};
4.类Scope
4.1 常量 in class scope
类作用域中的常量是由所有的类成员共享的,它的使用场景就是一个Value which所有的实例中到需要使用。可以使用两种方式来实现这个目标
- 枚举 enum
枚举实现的思路与 C/C++ 中的宏定义符号 #define 有着异曲同工的设计,并不会创建数据成员,而是在编译时使用相应的值进行替换。
class Calendar {
// there is not space allocate for Mouths to save
enum {Mouths = 12};
// the compiler will replace the Mouth symbol with 12
int Day[Mouths];
};
- 静态数据 static
使用 static 关键字修饰之后的变量会作为静态变量的一部分存放到程序的 .data 节中,使得其能够被所有的类实例所共享。当然如果你不加 const 进行修饰的话是可以被更改的,就如同一个普通的全局数据一样。
class Calendar {
// the Mouths will save with other static data in .data section
static const int Mouths = 12;
int Day[Mouths];
};
4.2 枚举 in class scope
C 语言中的枚举存在诸多问题:
- 名称冲突
下面代码如果交给编译器就会出现符号的多重定义,原因是 egg Small 和 t_shirt Small 位于相同的作用域。
enum egg {Small, Medium, Large, Jumbo};
enum t_shirt {Small, Medium, Large, Xlarge};
- 隐式转化
enum egg_old {Small, Medium, Large, Jumbo};
egg_old one = Medium;
int king = one; //implict type conversion for unscoped
C++ 中对于枚举则提供的作用域的支持,可以避免出现 名称冲突 和 隐式转化,其定义的方式就是在 enum 关键字的后面添加 class or struct 关键字即可完成转化。
// there is no collasion in enum name between two scope
enum class egg {Small, Medium, Large, Jumbo};
enum struct t_shirt {Small, Medium, Large, Xlarge};
t_shirt rolf = t_shirt::Large;
int ring = rolf; // not allowed, not implicit type conversion
如果需要的话,可以进行显示的类型转化。
int Frodo = int(t_shirt::Small); // Frodo set to 0
还可以通过 : type 的方式来指定底层的实现类型。当然底层类型必须是整形的,不然枚举就无法自行完成了。这样的指定是用来显示枚举数据的存储字宽。
enum class : short egg {Small, Medium, Large, Jumbo};
5.对象数组 and 抽象数据类型
5.1 对象数组
创建数组是一个非常常见的操作,对于 class 关键字创建的自定义类型也可以创建数组,只不过创建时会调用 构造函数 罢了。
class Example{
int a;
int b;
int c;
public:
Good(int a = 0, int b = 0, int c = 0) {
this->a = a;
this->b = b;
this->c = c;
}
}
- 对于数组的初始化,可以 like C 语言的结构体那样使用 {1, 2 ,3} 进行赋值,当然必须要有 与参数匹配的构造函数。
Example array1[3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
- 也可以使用显示调用构造函数进行赋值。
Example array2[3] = {Example(1, 2, 3), Example(4, 5 , 6), Example(7, 8 , 9)};
- 当然也可以进行部分初始化,但必须要有 默认构造函数。创建时会首先处理显示调用的构造函数,对于没有显示调用的则会隐式的调用默认构造函数完成初始化。
// the array3[0], array3[1], array3[2] will call the matched construct function
//array3[3-9] will be initialized using the default construct function
Example array3[10] = {Example(1, 2, 3), Example(4), Example()};
5.2 ADT(Abstract Data Type)
ADT 以通用的方式描述 数据类型,而没有引入语言或者实现细节。也就是对于数据结构进行封装。我这里直接照搬书中的例子,个人感觉需要与 模板 十分相似。
下面的程序在需要改变数据的长度时,只需要将 typedef 定义 alias 对应的具体类型更改即可,而不需要重新编写代码。
typedef unsigned long Item;
class Stack {
enum {MAX = 10};
Item items[MAX];
int top;
public:
Stack();
bool isempty();
bool isfull();
bool push(const Item& item);
bool pop(Item& item);
};