图形
矩形、正方形、圆形
计算面积、面积输出
·面向对象设计,类?
·实现功能?
·运行时多态性测试?
1 #include <iostream>
2 using namespace std;
3
4 class Shape
5 {
6 protected:
7 double area;
8 public:
9 virtual double getArea() const = 0;
10 virtual void showArea()
11 {
12 cout << area << endl;
13 }
14 // friend ostream& operator << (ostream& out, const Shape& s);
15 };
16
17 ostream& operator << (ostream& out, const Shape& s)
18 {
19 out << s.getArea();
20 return out;
21 }
22
23 class Rectangle : public Shape
24 {
25 private:
26 double x;
27 double y;
28 public:
29 Rectangle(double i = 0.0, double j = 0.0) : x(i), y(j)
30 {
31 area = x * y;
32 }
33 virtual double getArea() const
34 {
35 return area;
36 }
37 };
38
39 class Square : public Shape
40 {
41 private:
42 double x;
43 public:
44 Square(double i = 0.0) : x(i)
45 {
46 area = x * x;
47 }
48 virtual double getArea() const
49 {
50 return area;
51 }
52 };
53
54 class Circle : public Shape
55 {
56 private:
57 double r;
58 static const double PI;
59 public:
60 Circle(double i = 0.0) : r(i)
61 {
62 area = PI * r * r;
63 }
64 virtual double getArea() const
65 {
66 return area;
67 }
68 };
69
70 const double Circle::PI = 3.1415926;
71
72 int main()
73 {
74 Shape* p;
75 p = new Rectangle(4, 5);
76 cout << p->getArea() << endl;
77 p->showArea();
78 delete p;
79 p = new Square(5);
80 cout << p->getArea() << endl;
81 p->showArea();
82 delete p;
83 p = new Circle(5);
84 cout << p->getArea() << endl;
85 p->showArea();
86 delete p;
87
88
89 Rectangle r(6, 7);
90 Square s(7);
91 Circle c(9);
92 cout << r << endl;
93 cout << s << endl;
94 cout << c << endl;
95
96 return 0;
97 }
posted @
2011-06-16 11:46 unixfy 阅读(194) |
评论 (0) |
编辑 收藏
赋值运算符的重载,有不同的方法,Effective C++ 中有一个条款对此介绍。
1 #include <iostream>
2 using namespace std;
3
4 class MyString
5 {
6 private:
7 unsigned len;
8 char* data;
9 public:
10 MyString(const char* s = "");
11 MyString(const MyString& s);
12 MyString& operator = (const MyString& s);
13 ~MyString();
14 };
15
16 MyString::MyString(const char* s)
17 {
18 len = strlen(s);
19 data = new char[len + 1];
20 if (data != 0)
21 {
22 strcpy(data, s);
23 }
24 }
25
26 MyString::MyString(const MyString& s)
27 {
28 len = s.len;
29 data = new char[len + 1];
30 if (data != 0)
31 {
32 strcpy(data, s.data);
33 }
34 }
35
36 MyString& MyString:: operator = (const MyString& s)
37 {
38 // 第一种方法,需要检测自赋值,因为如果不检测,则会造成当自赋值时,就直接将该对象的 data delete 了,也就是 s.data 被 delete 了。这时 s.data 是个悬置指针,所致内存极可能无效
39 //
40 //if (this != &s)
41 //{
42 // delete [] data;
43 // len = s.len;
44 // data = new char[len + 1];
45 // if (data != 0)
46 // {
47 // strcpy(data, s.data);
48 // }
49 //}
50 //return *this;
51
52 // 另一种方法, 不需要检测自赋值
53 // 这种方式需要做一个备份,自赋值情况下,temp 保持了另一份备份,即便 delete 了 data 还是留有一份
54 // 非自赋值的情况下,对 s.data 所指的内容有了一个备份,然后 delete data,将 temp 赋予 data,这样有了两份 s.data,到达赋值的目的
55 len = s.len;
56 char* temp = new char[len + 1];
57 if (temp != 0)
58 {
59 strcpy(temp, s.data);
60 }
61 delete [] data;
62 data = temp;
63 return *this;
64
65 // 两种方法的代价分析
66 // 第一种方法,需要每次都要检测是不是自赋值了,对于自赋值的情况,虽然检测了,但是避免了备份,有利于自赋值的情况。但是对于非自赋值的情况,都需要额外的检测,这种检测是浪费的
67 // 第二种方法,不管是自赋值还是非自赋值都需要备份,这种方法对于自赋值的情况,较第一种方法代价高些,但是对于非自赋值的情况它不需要检测,也是做一个 copy 所以非自赋值的情况效率由于第一种方法
68 // 也就是说第一种方法对于自赋值的情况好,第二种方法对于非自赋值的情况好。一般情况下,自赋值的情况并不经常出现,所以第一种检测自赋值的操作很多情况下是多余的,所以相对第一种方法,第二种方法更好些。
69 }
70
71 MyString::~MyString()
72 {
73 len = 0;
74 delete [] data;
75 }
76
77 int main()
78 {
79 MyString a("C++ Programming"), c("Hello");
80 MyString b(a);
81 c = b;
82 cout << ". . ." << endl;
83 return 0;
84 }
posted @
2011-06-16 10:56 unixfy 阅读(238) |
评论 (0) |
编辑 收藏
Function 语意学
static member functions 不可能做的
·存取 nonstatic 数据
·被声明为 const
·virtual
·volatile
this
·T* const this
·const T* const this
名字的特殊处理 name mangling
名字_类名_参数链表
((Point3d*)0)->object_count();
virtual member functions
虚拟成员函数
任何问题都可以通过添加一个中间层来解决,实现多态就是通过添加了一个虚函数表和虚函数表指针这个中间层实现的
多重继承下的 virtual functions
thunk
address points
virtual base class
不要声明 nonstatic data member
构造函数的调用
指向 member function 的指针
double (Point::*pmf)();
指向 virtual member functions 的指针
ptr->z()
(ptr->*pmf)();
指向成员的指针实质是索引,而不是地址
在我看来不管是 virtual member functions 的指针还是 nonvirtual member functions 的指针
posted @
2011-06-01 13:45 unixfy 阅读(185) |
评论 (0) |
编辑 收藏
第三章 Data 语意学
class object 的大小
·支持语言特性(virtual)自动添加的 data members
·alignment 的需要
empty virtual base class
Data Member 的绑定
·成员完全可见,可以在后面
·类型定义应该放在最前面
Data Member 的布局
static const 成员初始化
const 成员初始化
Data Member 的存取
static data members
static data member 编码
name-mangling
nonstatic data members
origin._y = 0.0;
&origin + (&Point3d::_y - 1);
多态,指针或引用
但是如果是 (*p).foo() ?
继承与 Data Member
多态
虚拟继承
·virtual base class table
·virtual function table - offset
对象成员的效率
指向 Data Member 的指针
posted @
2011-05-28 13:22 unixfy 阅读(85) |
评论 (0) |
编辑 收藏
怎样把函数模板声明为类模板的友元
给类模板声明友元的函数模板有三种方式,分别为:
第一种方式,在模板类内部声明友元的函数模板
第二种方式,在模板类内部声明对应版本的友元函数模板实例化
需要前置声明
这种方式是最为合理的方式
第三种方式,在模板类内部直接声明友元函数,不涉及函数模板
这种情况只能在模板类内部一起把函数的定义写出来,不能在外部实现,因为外部需要类型参数,而需要类型参数就是模板了
其实这种情况相当于一般的模板类的成员函数,也就是相当于一个函数模板
第二种方式也是一个函数模板,他们保持函数的参数类型和该模板类的实例一个类型
第一种方式更为灵活,他不会要求参数类型与模板类实例是一个类型,但是一般情况下我们也是按照一个类型使用的。
1 #include <iostream>
2 using namespace std;
3
4 //// 第一种方式,在模板类内部声明友元的函数模板
5 //template <typename T>
6 //class C
7 //{
8 //private:
9 // T m[5];
10 //public:
11 // template <typename U>
12 // friend void foo(const C<U>& T);
13 //};
14 //
15 //template <typename T>
16 //void foo(const C<T>& t)
17 //{
18 // for (int i = 0; i < 5; ++i)
19 // {
20 // cout << t.m[i] << endl;
21 // }
22 //}
23
24 // 第二种方式,在模板类内部声明对应版本的友元函数模板实例化
25 // 需要前置声明
26 // 这种方式是最为合理的方式
27 template <typename T>
28 class C;
29
30 template <typename T>
31 void foo(const C<T>& t);
32
33 template <typename T>
34 class C
35 {
36 private:
37 T m[5];
38 public:
39 friend void foo<T>(const C<T>& t);
40 };
41
42 template <typename T>
43 void foo(const C<T>& t)
44 {
45 for (int i = 0; i < 5; ++i)
46 {
47 cout << t.m[i] << endl;
48 }
49 }
50
51 //// 第三种方式,在模板类内部直接声明友元函数,不涉及函数模板
52 //// 这种情况只能在模板类内部一起把函数的定义写出来,不能在外部实现,因为外部需要类型参数,而需要类型参数就是模板了
53 //// 其实这种情况相当于一般的模板类的成员函数,也就是相当于一个函数模板
54 //// 第二种方式也是一个函数模板,他们保持函数的参数类型和该模板类的实例一个类型
55 //// 第一种方式更为灵活,他不会要求参数类型与模板类实例是一个类型,但是一般情况下我们也是按照一个类型使用的。
56 //template <typename T>
57 //class C
58 //{
59 //private:
60 // T m[5];
61 //public:
62 // friend void foo(const C<T>& t)
63 // {
64 // for (int i = 0; i < 5; ++i)
65 // {
66 // cout << (t.m[i]) << endl;
67 // }
68 // }
69 //};
70
71 int main()
72 {
73 // C<int> c;
74 C<double> c;
75 foo(c);
76 return 0;
77 }
http://topic.csdn.net/u/20100619/21/c32066bb-dacd-4938-8f95-7345a522b0f6.html
http://topic.csdn.net/u/20100612/13/9365495d-b1d8-4e87-b704-23895acb1637.html
http://www.cnblogs.com/wswqwps/archive/2008/10/25/1319320.html
http://blog.csdn.net/dongzhongshu/archive/2011/02/22/6200466.aspx
posted @
2011-05-27 23:59 unixfy 阅读(1871) |
评论 (0) |
编辑 收藏
swap 到底做了什么
swap 交换两个内置数据类型的变量时,直接交换。
swap 交换自定义类型对象时,如果里面没有成员指针,直接交换各个对应成员。
如果自定义类型中有指针成员,则是交换两个指针的值,但是指针的指向的值得不到交换。
正是由于这个原因,可以用 swap 进行重载 operator = 时避免自赋值情况,而是生产一个临时对象,然后与本对象 swap 即可。
关于重载 operator = 自赋值的情况,更详细内容可以查看《Effective C++》
实验程序:
1 #include <iostream>
2 using namespace std;
3
4 class Str
5 {
6 private:
7 char* s_;
8 public:
9 Str(const char* s = "")
10 {
11 s_ = new char[strlen(s) + 1];
12 if (s_ == 0)
13 {
14 cout << "test" << endl;
15 exit(1);
16 }
17 strcpy(s_, s);
18 }
19 // 定义拷贝构造函数,这里会被用于 operator =,swap
20 Str(const Str& rhs)
21 {
22 s_ = new char[strlen(rhs.s_) + 1];
23 if (s_ == 0)
24 {
25 cout << "test" << endl;
26 exit(1);
27 }
28 strcpy(s_, rhs.s_);
29 }
30 ~Str()
31 {
32 clear();
33 }
34 //// 常规的 operator = 重载实现方式,必须检查自赋值
35 //// 因为如果不自赋值检验,对于自赋值现象如果不调用 clear,则 s_ 在 new 之后就改变,rhs 也改变,原来的丢失,后来的也不是合法内容
36 //// 如果调用 clear,不会内存泄露,但是 rhs 的内容被释放掉,rhs 的内容也不是合法内容。
37 //// 如果检验自赋值,而没有 clear,原来 *this 的那块内存会被丢失,造成内存泄露。
38 //Str& operator = (const Str& rhs)
39 //{
40 // if (this != &rhs)
41 // {
42 // clear();
43 // s_ = new char[strlen(rhs.s_) + 1];
44 // if (s_ == 0)
45 // {
46 // exit(1);
47 // }
48 // strcpy(s_, rhs.s_);
49 // }
50 // return *this;
51 //}
52
53 // 改进的 operator,先用一个 temp 保持 rhs,然后 swap
54 // 这种方式不怕自赋值,因为如果是自赋值,也有一个备份 temp,操作值相同的两个对象 *this 和 temp,直接交换不会影响结果
55 // 如果不是自赋值,不是交换 *this 和 rhs,而是交换 *this 和 rhs 的一个复制品 temp,最终 *this 得到的值就是 rhs 的一个副本,完成赋值
56 // 这种方式不用检验自赋值,所以可以省去每次调用时的自赋值检验,在基本上不会遇到自赋值检验的情况下,这种方法可以省去很多误用的检验
57 // 但是它会每次生成一个副本,这样做的效率与原来的非自赋值一样,而且还需要一个 swap,但是这种方式是异常安全的,用对象来管理资源,资源分配即初始化
58 Str& operator = (const Str& rhs)
59 {
60 cout << "test" << endl;
61 Str temp(rhs);
62 // swap(*this, temp);
63 // 这里会引起递归调用,因为 operator = 调用 swap,swap 内部又调用 operator = ,一直递归下去,直到栈溢出
64 swap(s_, temp.s_);
65 // Effective C++ 中提到,可以定义一个成员函数 swap,用于交换两个对象对应的数据成员。这样可以防止无限递归。
66 // 另一种好的方式是除定义一个成员函数 swap 外,传参类型为 值类型 T,这样就可以直接交换返回。
67 // 这些方法的前提都是要有定义拷贝构造函数的。
68 return *this;
69 }
70
71 void clear()
72 {
73 delete [] s_;
74 }
75 void foo()
76 {
77 cout << s_ << endl;
78 }
79 };
80
81 int main()
82 {
83 int a = 3, b = 5;
84 swap(a, b);
85 cout << a << endl;
86 cout << b << endl;
87
88 Str s1("abc");
89 Str s2("xyz");
90 s1.foo();
91 s2.foo();
92
93 swap(s1, s2);
94 // 这里输出两个 test,我们得知,有两个赋值操作
95 // 可以推测 swap 的内部实现是 T t(s2), s2 = s1, s1 = t;
96 s1.foo();
97 s2.foo();
98
99 s2 = s1;
100 s1.foo();
101 s2.foo();
102
103 return 0;
104 }
posted @
2011-05-27 22:14 unixfy 阅读(869) |
评论 (0) |
编辑 收藏
栈和堆是内存中的部分。安装地址的变化规则,栈是向下生长的,堆是向上增长的。
这里对栈和堆的地址生长情况做了一个实现。
更好的内容有 Computer System:A Programmer's Perspective 《深入理解计算机系统》
1 #include <iostream>
2 using namespace std;
3
4 int main()
5 {
6 int i = 1;
7 int j = 2;
8 int k = 3;
9 cout << &i << endl;
10 cout << &j << endl;
11 cout << &k << endl;
12 // 栈是向下生长的,所以地址递减
13
14 int a[3];
15 cout << &a[0] << endl;
16 cout << &a[1] << endl;
17 cout << &a[2] << endl;
18 // 栈还是向下生长的,但是对于一个数组来说从第一个元素到后面的元素
19 // 其每个元素的地址是递增的。所以最后一个元素是在最上面的,也就是
20 // 与前面在栈上定义的元素挨着,第一个元素在最下面
21
22
23 cout << a[3] << endl;
24 cout << a[5] << endl;
25 // 这里 a[5] 输出 3,a + 5 将达到变量 k 的地址 &k,所以 *(a + 5) = k
26
27
28 int* b = new int[3];
29 cout << &b[0] << endl;
30 cout << &b[1] << endl;
31 cout << &b[2] << endl;
32 // 堆是向上生长的,第一个元素在最下面
33
34 // 不管是在堆还是在栈上的数组,数组中的元素的地址都是随着元素的位
35 // 置递增而递增的。
36 // 只不过在栈上的元素顺序与栈的增长方向相反,在堆上数组的元素的顺
37 // 序与堆的增长方向相同。
38
39 return 0;
40 }
posted @
2011-05-27 01:00 unixfy 阅读(399) |
评论 (0) |
编辑 收藏
关于访问权限和继承方式
访问权限有三种:public、protected、private
继承也有三种:public、protected、private,这里不考虑 virtual 继承。
继承时的权限,不是针对本类内的访问权限,而是针对该派生类的客户端代码(包括其派生类)的访问权限。
即是,对于基类中的 public、protected 成员,如果一个派生类 private 继承自该基类,则这些成员还是可以在派生类中访问的,只是不能在该派生类的客户代码中被直接访问,或者在该派生类的派生类中直接访问。
也就是说,继承的方式是针对派生类的客户端代码来讲的,对派生类内部不起作用,不管是 public、protected、private 继承,派生类中总是可以访问基类中的 public、protected 成员,基类中的 private 成员永远不能在派生类中直接访问,不论通过哪种方式。
归纳一下:
成员访问权限 继承方式 派生类中能否访问 派生类的派生类中 派生类的客户端代码
public public 能 能 能
public protected 能 能 不能
public private 能 不能 不能
protected public 能 能 不能
protected protected 能 能 不能
protected private 能 不能 不能
private public 不能 不能 不能
private protected 不能 不能 不能
private private 不能 不能 不能
从这个表中,我们更能清除地看到成员访问权限和继承方式之间的组合,对派生类中的访问、派生类的派生类中的访问、派生类的客户端代码的访问控制情况。
继承的方式是在原有基类访问权限的基础上,给在派生类的访问权限又加了一个效应。取两个中最严格的那个权限,这个取得的权限是派生类成员的访问权限,而这种访问权限对派生类的客户端代码和派生类的派生类代码其访问控制作用。
posted @
2011-05-27 00:20 unixfy 阅读(451) |
评论 (0) |
编辑 收藏
队列的两个主要操作:入队列、出队列
栈的两个主要操作:入栈、出栈
入队列对应入栈
出队列是出最早的,出栈是出最晚的
使用 360 浏览器,有个不错的功能是可以恢复标签,你关闭一个标签,这个标签就会进入待恢复表,如果待恢复表慢了,新加标签,最早的标签会消失,这是 FIFO 队列。
但是如果点击恢复标签队列,会恢复最近关闭的标签,也就是最晚进入待恢复表中的标签,所以这又是一种 LIFO 栈。
待恢复表既具有添加标签的 FIFO 队列性质,又具有恢复标签并移除标签的 LIFO 栈性质。
实现一个数据结构,使其既具有 FIFO 队列的性质,又具有 LIFO 栈的性质。
由于标签有很多,这里使用循环表来实现这个数据结构,早期的标签会随着新加入的标签被覆盖。
注意连续关闭两个相同的标签,第二次关闭时,不会将这个标签存入待恢复表中。
这个表主要有三个操作
·入队列
·出队列
·出栈
没有入栈,其实入栈也就是入队列。
实现:
1 #include <iostream>
2 using namespace std;
3
4 class Table360
5 {
6 private:
7 int capacity_;
8 int* data_;
9 int size_;
10 int head_;
11 int tail_;
12 public:
13 Table360(int c = 10) : capacity_(c)
14 {
15 data_ = new int[capacity_];
16 if (data_ == 0)
17 {
18 exit(1);
19 }
20 memset(data_, 0, sizeof (int) * capacity_);
21 size_ = 0;
22 head_ = 0;
23 tail_ = -1;
24 }
25 Table360(const Table360& t) : capacity_(t.capacity_)
26 {
27 data_ = new int[capacity_];
28 if (data_ == 0)
29 {
30 exit(1);
31 }
32 memset(data_, 0, sizeof (int) * capacity_);
33 size_ = t.size_;
34 head_ = t.head_;
35 tail_ = t.tail_;
36 for (int i = 0; i < size_; ++i)
37 {
38 data_[(head_+i) % capacity_] = t.data_[(t.head_ + i) % t.capacity_];
39 }
40 }
41 void swap_(Table360& t)
42 {
43 swap(capacity_, t.capacity_);
44 swap(data_, t.data_);
45 swap(size_, t.size_);
46 swap(head_, t.head_);
47 swap(tail_, t.tail_);
48 }
49 Table360& operator = (const Table360& t)
50 {
51 Table360 temp(t);
52 swap_(temp);
53 return *this;
54 }
55 ~Table360()
56 {
57 delete [] data_;
58 capacity_ = 0;
59 size_ = 0;
60 head_ = 0;
61 tail_ = 0;
62 }
63 int size()
64 {
65 return size_;
66 }
67 bool empty()
68 {
69 return size_ == 0;
70 }
71 int top()
72 {
73 return data_[head_];
74 }
75 void enQueue(int item)
76 {
77 if (size_ >= capacity_)
78 {
79 deQueue();
80 }
81 tail_ = (tail_ + 1) % capacity_;
82 data_[tail_] = item;
83 ++size_;
84 //if (size_ >= capacity_)
85 //{
86 // head_ = (head_ + 1) % capacity_;
87 // --size_;
88 // tail_ = (tail_ + 1) % capacity_;
89 // data_[tail_] = item;
90 // ++size_;
91 //}
92 //else
93 //{
94 // tail_ = (tail_ + 1) % capacity_;
95 // data_[tail_] = item;
96 // ++size_;
97 //}
98 }
99 void deQueue()
100 {
101 head_ = ++head_ % capacity_;
102 --size_;
103 }
104 // 其实没有入栈操作,入栈即是入队列
105 void push(int item)
106 {
107 enQueue(item);
108 }
109 int pop()
110 {
111 int tmp = tail_;
112 tail_ = (tail_ + capacity_ - 1) % capacity_;
113 --size_;
114 return data_[tmp];
115 }
116 int stacktop()
117 {
118 return data_[tail_];
119 }
120 };
121
122 int main()
123 {
124 Table360 t(20);
125 cout << t.size() << endl;
126 for (int i = 0; i < 100; ++i)
127 {
128 t.enQueue(i);
129 }
130 cout << t.size() << endl;
131 // cout << t.top() << endl;
132 while (!t.empty())
133 {
134 // cout << t.pop() << ' ';
135 cout << t.stacktop() << ' ';
136 t.pop();
137 }
138 cout << endl;
139 return 0;
140 }
其他链接:
http://zh.wikipedia.org/wiki/%E9%98%9F%E5%88%97
http://zh.wikipedia.org/wiki/%E5%A0%86%E6%A0%88
http://zh.wikipedia.org/wiki/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84
http://student.zjzk.cn/course_ware/data_structure/web/zhanhuoduilie/zhanhuoduilie3.2.1.htm
http://student.zjzk.cn/course_ware/data_structure/web/zhanhuoduilie/zhanhuoduilie3.1.1.htm
http://student.zjzk.cn/course_ware/data_structure/web/main.htm
posted @
2011-05-26 00:48 unixfy 阅读(134) |
评论 (0) |
编辑 收藏
《深度探索 C++ 对象模型》读书笔记——第一章 关于对象
C++ 对象模型
·语言中直接支持面向对象程序设计的部分
·对于各种支持的底层实现机制
virtual table
理解底层实现模型的好处有
·写出效率较高的代码
·有更好的自信心
关于对象
布局、存取时间来自于
·virtual: virtual function, virtual base class
·derived class 与 base class 的转换
class data members: static, nonstatic
class member functions: static, nonstatic, virtual
1.简单对象模型 Simple Object Model
slots
指向成员的指针
2.表格驱动对象模型
data member table
member function table: slots
Member Table 对象模型
支持 virtual functions
3.C++ 对象模型
virtual table (vtbl)
vptr: constructor, destructor, copy assignment
type_info object for RTTI(运行时类型识别)
优点:空间和存取时间的效率
缺点:会导致重新编译
bptr
(*px->vtbl[2])(px);
(*px->vtbl[1])(px);
_delete(px);
vptr
virtual table
-----
address -> type_info for X
address -> X::~X()
address -> X::foo()
-----
pt->vtbl[0]
pt->vtbl[1]
pt->vtbl[2]
关键字所带来的差异
组合而非继承是把 C 和 C++ 结合在一起的唯一可行方法
conversion 运算符
operator C_point()
{
return _c_point;
}
对象的差异
C++ 程序设计模型直接支持三种程序设计典范 programming paradigms
·程序模型 procedural model
·抽象数据类型模型 abstract data type model
·面向对象模型 object-oriented model
C++ 支持多态的方法
·隐含转化 Shape* ps = new circle();
·virtual function ps->rotate();
·dynamic_cast, typeid
if (Circle* pc = dynamic_cast<Circle*>(ps))
{
...
}
class object 内存
·nonstatic data members
·alignment
·virutal(virtual functions, virtual base class)
dynamic_cast<Base*>
OB: object-based
OO: object-oriented
posted @
2011-05-25 18:48 unixfy 阅读(137) |
评论 (0) |
编辑 收藏