新微赢技术网
标题:
C++ FAQ from CSDN
[打印本页]
作者:
√金刚石猛男
时间:
2009-11-5 00:37
标题:
C++ FAQ from CSDN
woodhead 整理了一些FAQ,看了收获很大,我从CSDN也看到一些很实用的东西,稍加整理,拿来和大家一起分享,希望能对大家有用.
CONTENT:
#2:typedef的四个用途和两个陷阱
#3:内存管理原理
#4:C++中struct与class的区别是什么?
#5:static用法小结
#6:inclue <string> 和 include <string.h> 有什么区别
#7:为什么拷贝构造函数的参数是一个引用?
#8:什么是范型程序设计(GP),范性和模板的关系是什么
#9: 前面的namespace如何使用后面namespace的成员?(namespace的交叉引
用)
#10:关于"隐藏(hide)", "重载(overload)", "覆盖(override)"
#11:在C和C++中把标准输出重定向到指定文件
#16:VC++与gcc在异常处理方面有什么不同?
#17:返回对象和返回对象的引用有区别吗?
#18:this指针有什么用法?
#19:namespace没有名字有什么用?
#20:在多重继承中,虚函数表的结构是怎么样的?
#21:const常量、指向常量的指针和常量指针
作者:
火箭筒︻$▅
时间:
2009-11-5 00:37
Typedef的四个用途和两个陷阱
用途一:
定义一种类型的别名,而不只是简单的宏替换。可以用作同时声明指针型
的多个对象。比如:
char* pa, pb; // 这多数不符合我们的意图,它只声明了一个指向字符
变量的指针,
// 和一个字符变量;
以下则可行:
typedef char* PCHAR; // 一般用大写
PCHAR pa, pb; // 可行,同时声明了两个指向字符变量的指针
虽然:
char *pa, *pb;
也可行,但相对来说没有用typedef的形式直观,尤其在需要大量指针的
地方,typedef的方式更省事。
用途二:
用在旧的C代码中(具体多旧没有查),帮助struct。以前的代码中,声
明struct新对象时,必须要带上struct,即形式为: struct 结构名 对
象名,如:
struct tagPOINT1
{
int x;
int y;
};
struct tagPOINT1 p1;
而在C++中,则可以直接写:结构名 对象名,即:
tagPOINT1 p1;
估计某人觉得经常多写一个struct太麻烦了,于是就发明了:
typedef struct tagPOINT
{
int x;
int y;
}POINT;
POINT p1; // 这样就比原来的方式少写了一个struct,比较省事,尤其
在大量使用的时候
或许,在C++中,typedef的这种用途二不是很大,但是理解了它,对掌握
以前的旧代码还是有帮助的,毕竟我们在项目中有可能会遇到较早些年代
遗留下来的代码。
用途三:
用typedef来定义与平台无关的类型。
比如定义一个叫 REAL 的浮点类型,在目标平台一上,让它表示最高精度
的类型为:
typedef long double REAL;
在不支持 long double 的平台二上,改为:
typedef double REAL;
在连 double 都不支持的平台三上,改为:
typedef float REAL;
也就是说,当跨平台时,只要改下 typedef 本身就行,不用对其他源码
做任何修改。
标准库就广泛使用了这个技巧,比如size_t。
另外,因为typedef是定义了一种类型的新别名,不是简单的字符串替换
,所以它比宏来得稳健(虽然用宏有时也可以完成以上的用途)。
用途四:
为复杂的声明定义一个新的简单的别名。方法是:在原来的声明里逐步用
别名替换一部分复杂声明,如此循环,把带变量名的部分留到最后替换,
得到的就是原声明的最简化版。举例:
1. 原声明:int *(*a[5])(int, char*);
变量名为a,直接用一个新别名pFun替换a就可以了:
typedef int *(*pFun)(int, char*);
原声明的最简化版:
pFun a[5];
2. 原声明:void (*b[10]) (void (*)());
变量名为b,先替换右边部分括号里的,pFunParam为别名一:
typedef void (*pFunParam)();
再替换左边的变量b,pFunx为别名二:
typedef void (*pFunx)(pFunParam);
原声明的最简化版:
pFunx b[10];
3. 原声明:doube(*)() (*e)[9];
变量名为e,先替换左边部分,pFuny为别名一:
typedef double(*pFuny)();
再替换右边的变量e,pFunParamy为别名二
typedef pFuny (*pFunParamy)[9];
原声明的最简化版:
pFunParamy e;
理解复杂声明可用的“右左法则”:从变量名看起,先往右,再往左,碰
到一个圆括号就调转阅读的方向;括号内分析完就跳出括号,还是按先右
后左的顺序,如此循环,直到整个声明分析完。举例:
int (*func)(int *p);
首先找到变量名func,外面有一对圆括号,而且左边是一个*号,这说明
func是一个指针;然后跳出这个圆括号,先看右边,又遇到圆括号,这说
明(*func)是一个函数,所以func是一个指向这类函数的指针,即函数指
针,这类函数具有int*类型的形参,返回值类型是int。
int (*func[5])(int *);
func右边是一个[]运算符,说明func是具有5个元素的数组;func的左边
有一个*,说明func的元素是指针(注意这里的*不是修饰func,而是修饰
func[5]的,原因是[]运算符优先级比*高,func先跟[]结合)。跳出这个
括号,看右边,又遇到圆括号,说明func数组的元素是函数类型的指针,
它指向的函数具有int*类型的形参,返回值类型为int。
也可以记住2个模式:
type (*)(....)函数指针
type (*)[]数组指针
--------------------------------
-
陷阱一:
记住,typedef是定义了一种类型的新别名,不同于宏,它不是简单的字
符串替换。比如:
先定义:
typedef char* PSTR;
然后:
int mystrcmp(const PSTR, const PSTR);
const PSTR实际上相当于const char*吗?不是的,它实际上相当于char*
const。
原因在于const给予了整个指针本身以常量性,也就是形成了常量指针
char* const。
简单来说,记住当const和typedef一起出现时,typedef不会是简单的字
符串替换就行。
陷阱二:
typedef在语法上是一个存储类的关键字(如auto、extern、mutable、
static、register等一样),虽然它并不真正影响对象的存储特性,如:
typedef static int INT2; //不可行
编译将失败,会提示“指定了一个以上的存储类”。
作者:
風過aiq無痕
时间:
2009-11-5 00:37
内存管理原理
书里面说指针是C++内存管理编程的核心,请问指针与动态内存分配到底
是怎么一个关系?他们怎么结合在一起工作的呢?
动态内存分配是指程序在运行的时候分配内存,一般也就是在堆里面分配
内存
如我要在程序运行时才知道一个 数组的维数,因为他是有人工输入的
int m;
cin >> m;
int * p = new int[m];
delete [] p;
new返回的地址需要赋给一个指针,然后运用指针对这片堆里面的内存进
行操作
堆里面的内存用完以后需要释放掉,所以需要 delete [] p;
假如是 int * p = new int(1);的话
直接 delete p;就可以了
作者:
地上跑
时间:
2009-11-5 00:37
C++中struct与class的区别是什么?
首先,讨论这个问题应该仅从语法上讨论,如果讨论不同人之间编程风格
上的差异,那这个问题是没有答案的。毕竟不同的人偏好不同。
从语法上,在C++中(只讨论C++中)。class和struct做类型定义时只有
两点区别:
(一)默认继承权限。如果不明确指定,来自class的继承按照private继
承处理,来自struct的继承按照public继承处理;
(二)成员的默认访问权限。class的成员默认是private权限,struct默
认是public权限。
除了这两点,class和struct基本就是一个东西。语法上没有任何其它区
别。
不能因为学过C就总觉得连C++中struct和class都区别很大,下面列举的
说明可能比较无聊,因为struct和class本来就是基本一样的东西,无需
多说。但这些说明可能有助于澄清一些常见的关于struct和class的错误
认识:
(1)都可以有成员函数;包括各类构造函数,析构函数,重载的运算符
,友元类,友元结构,友元函数,虚函数,纯虚函数,静态函数;
(2)都可以有一大堆public/private/protected修饰符在里边;
(3)虽然这种风格不再被提倡,但语法上二者都可以使用大括号的方式
初始化:A a = {1, 2, 3};不管A是个struct还是个class,前提是这个类
/结构足够简单,比如所有的成员都是public的,所有的成员都是简单类
型,没有显式声明的构造函数。
(4)都可以进行复杂的继承甚至多重继承,一个struct可以继承自一个
class,反之亦可;一个struct可以同时继承5个class和5个struct,虽然
这样做不太好。
(5)如果说class的设计需要注意OO的原则和风格,那么没任何理由说设
计struct就不需要注意。
(6)再次说明,以上所有说法都是指在C++语言中,至于在C里的情况,C
里是根本没有“class”,而C的struct从根本上也只是个包装数据的语法
机制
最后,作为语言的两个关键字,除去定义类型时有上述区别之外,另外还
有一点点:“class”这个关键字还用于定义模板参数,就像“typename
”。但关键字“struct”不用于定义模板参数。
作者:
凤蔷¤蹁跹
时间:
2009-11-5 00:37
static用法小结
static关键字是C, C++中都存在的关键字, 它主要有三种使用方式, 其中
前两种只指在C语言中使用, 第三种在C++中使用(C,C++中具体细微操作不
尽相同, 本文以C++为准).
(1)局部静态变量
(2)外部静态变量/函数
(3)静态数据成员/成员函数
下面就这三种使用方式及注意事项分别说明
一、局部静态变量
在C/C++中, 局部变量按照存储形式可分为三种auto, static, register
(<C语言程序设计(第二版)>谭浩强, 第174-175页)
与auto类型(普通)局部变量相比, static局部变量有三点不同
1. 存储空间分配不同
auto类型分配在栈上, 属于动态存储类别, 占动态存储区空间, 函数调用
结束后自动释放, 而static分配在静态存储区, 在程序整个运行期间都不
释放. 两者之间的作用域相同, 但生存期不同.
2. static局部变量在所处模块在初次运行时进行初始化工作, 且只操作
一次
3. 对于局部静态变量, 如果不赋初值, 编译期会自动赋初值0或空字符,
而auto类型的初值是不确定的. (对于C++中的class对象例外, class的对
象实例如果不初始化, 则会自动调用默认构造函数, 不管是否是static类
型)
特点: static局部变量的”记忆性”与生存期的”全局性”
所谓”记忆性”是指在两次函数调用时, 在第二次调用进入时, 能保持第
一次调用退出时的值.
示例程序一
#include <iostream>
using namespace std;
void staticLocalVar()
{
static int a = 0; // 运行期时初始化一次, 下次再调用时, 不进行初
始化工作
cout<<"a="<<a<<endl;
++a;
}
int main()
{
staticLocalVar(); // 第一次调用, 输出a=0
staticLocalVar(); // 第二次调用, 记忆了第一次退出时的值, 输出
a=1
return 0;
}
应用:
利用”记忆性”, 记录函数调用的次数(示例程序一)
利用生存期的”全局性”, 改善”return a pointer / reference to
a local object”的问题. Local object的问题在于退出函数, 生存期即
结束,. 利用static的作用, 延长变量的生存期.
示例程序二:
// IP address to string format
// Used in Ethernet Frame and IP Header analysis
const char * IpToStr(UINT32 IpAddr)
{
static char strBuff[16]; // static局部变量, 用于返回地址有效
const unsigned char *pChIP = (const unsigned char *)&IpAddr;
sprintf(strBuff, "%u.%u.%u.%u", pChIP[0], pChIP[1], pChIP[2],
pChIP[3]);
return strBuff;
}
注意事项:
1. “记忆性”, 程序运行很重要的一点就是可重复性, 而static变量的
”记忆性”破坏了这种可重复性, 造成不同时刻至运行的结果可能不同.
2. “生存期”全局性和唯一性. 普通的local变量的存储空间分配在
stack上, 因此每次调用函数时, 分配的空间都可能不一样, 而static具
有全局唯一性的特点, 每次调用时, 都指向同一块内存, 这就造成一个很
重要的问题 ---- 不可重入性!!!
这样在多线程程序设计或递归程序设计中, 要特别注意这个问题.
(不可重入性的例子可以参见<effective C++ (2nd)>(影印版)第103-105
页)
下面针对示例程序二, 分析在多线程情况下的不安全性.(为方便描述, 标
上行号)
① const char * IpToStr(UINT32 IpAddr)
② {
③ static char strBuff[16]; // static局部变量, 用于返回地址有效
④ const unsigned char *pChIP = (const unsigned char *)&IpAddr;
⑤ sprintf(strBuff, "%u.%u.%u.%u", pChIP[0], pChIP[1], pChIP
[2], pChIP[3]);
⑥ return strBuff;
⑦ }
假设现在有两个线程A,B运行期间都需要调用IpToStr()函数, 将32位的IP
地址转换成点分10进制的字符串形式. 现A先获得执行机会, 执行
IpToStr(), 传入的参数是0x0B090A0A, 顺序执行完应该返回的指针存储
区内容是:”10.10.9.11”, 现执行到⑥时, 失去执行权, 调度到B线程执
行, B线程传入的参数是0xA8A8A8C0, 执行至⑦, 静态存储区的内容是
192.168.168.168. 当再调度到A执行时, 从⑥继续执行, 由于strBuff的
全局唯一性, 内容已经被B线程冲掉, 此时返回的将是192.168.168.168字
符串, 不再是10.10.9.11字符串.
二、外部静态变量/函数
在C中static有了第二种含义:用来表示不能被其它文件访问的全局变量
和函数。, 但为了限制全局变量/函数的作用域, 函数或变量前加static
使得函数成为静态函数。但此处“static”的含义不是指存储方式,而是
指对函数的作用域仅局限于本文件(所以又称内部函数)。注意此时, 对于
外部(全局)变量, 不论是否有static限制, 它的存储区域都是在静态存储
区, 生存期都是全局的. 此时的static只是起作用域限制作用, 限定作用
域在本模块(文件)内部.
使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义
的函数,是否会与其它文件中的函数同名。
示例程序三:
//file1.cpp
static int varA;
int varB;
extern void funA()
{
……
}
static void funB()
{
……
}
//file2.cpp
extern int varB; // 使用file1.cpp中定义的全局变量
extern int varA; // 错误! varA是static类型, 无法在其他文件中使用
extern vod funA(); // 使用file1.cpp中定义的函数
extern void funB(); // 错误! 无法使用file1.cpp文件中static函数
三、静态数据成员/成员函数(C++特有)
C++重用了这个关键字,并赋予它与前面不同的第三种含义:表示属于一
个类而不是属于此类的任何特定对象的变量和函数. 这是与普通成员函数
的最大区别, 也是其应用所在, 比如在对某一个类的对象进行计数时, 计
数生成多少个类的实例, 就可以用到静态数据成员. 在这里面, static既
不是限定作用域的, 也不是扩展生存期的作用, 而是指示变量/函数在此
类中的唯一性. 这也是”属于一个类而不是属于此类的任何特定对象的变
量和函数”的含义. 因为它是对整个类来说是唯一的, 因此不可能属于某
一个实例对象的. (针对静态数据成员而言, 成员函数不管是否是static,
在内存中只有一个副本, 普通成员函数调用时, 需要传入this指针,
static成员函数调用时, 没有this指针. )
请看示例程序四(<effective c++ (2nd)>(影印版)第59页)
class EnemyTarget {
public:
EnemyTarget() { ++numTargets; }
EnemyTarget(const EnemyTarget&) { ++numTargets; }
~EnemyTarget() { --numTargets; }
static size_t numberOfTargets() { return numTargets; }
bool destroy(); // returns success of attempt to destroy
EnemyTarget object
private:
static size_t numTargets; // object counter
};
// class statics must be defined outside the class;
// initialization is to 0 by default
size_t EnemyTarget::numTargets;
在这个例子中, 静态数据成员numTargets就是用来计数产生的对象个数的
.
另外, 在设计类的多线程操作时, 由于POSIX库下的线程函数
pthread_create()要求是全局的, 普通成员函数无法直接做为线程函数,
可以考虑用Static成员函数做线程函数.
作者:
大天使路西法
时间:
2009-11-5 00:37
inclue <string> 和 include <string.h> 有什么区别
为什么下面这段代码
#include <string.h>
void main()
{
string aaa= "abcsd d";
printf("looking for abc from abcdecd %s\n",
(strcmp(aaa,"abc")) ? "Found" : "Not Found");
}
不能正确执行,说是string类型没有定义
而下面:
#include <string>
using namespace std;
void main()
{
string aaa= "abcsd d";
printf("looking for abc from abcdecd %s\n",
(strcmp(aaa,"abc")) ? "Found" : "Not Found");
}
这里的string编译器就认识了,但是strcmp就不认识了呢?
一般一个C++的老的带“.h”扩展名的库文件,比如iostream.h,在新标
准后的标准库中都有一个不带“.h”扩展名的相对应,区别除了后者的好
多改进之外,还有一点就是后者的东东都塞进了“std”名字空间中。
但唯独string特别。
问题在于C++要兼容C的标准库,而C的标准库里碰巧也已经有一个名字叫
做“string.h”的头文件,包含一些常用的C字符串处理函数,比如提到
的strcmp。
这个头文件跟C++的string类半点关系也没有,所以<string>并非
<string.h>的“升级版本”,他们是毫无关系的两个头文件。
要达到的问题的目的,比如同时:
#include <string.h>
#include <string>
using namespace std;
或者
#include <cstring>
#include <string>
其中<cstring>是与C标准库的<string.h>相对应,但裹有std名字空间的
版本。
作者:
CHLOE
时间:
2009-11-5 00:37
为什么拷贝构造函数的参数是一个引用?
当一个对象需要以值方式传递时,编译器会生成代码调用它的拷贝构造函
数以生成一个复本。
如果类A的拷贝构造函数是以值方式传递一个类A对象作为参数的话,当需
要调用类A的拷贝构造函数时,需要以值方式传进一个A的对象作为实参;
而以值方式传递需要调用类A的拷贝构造函数;
结果就是调用类A的拷贝构造函数导致又一次调用类A的拷贝构造函数,这
就是一个无限递归。
作者:
_睡覺覺c◆s
时间:
2009-11-5 00:37
什么是范型程序设计(GP),范性和模板的关系是什么?
简单地说,泛型程式设计是一种「将资料型别叁数化」的思维模式。C++
的 template 机制就是泛型技术的一个具体载具。在 C++ 中,不论是
functions 或是 classes,皆可将其中所需要的资料型别以一个保留器(
placeholder)代表,这个保留器亦即所谓的 template 叁数。例如
function template 如下:
template<typename T1, typename T2)
void func(T1 param1, T2 param2) { /* ... */ }
或是 class template:
template<typename T1, typename T2)
class A { /* ... */ }
一旦程式中使用函式 func() 或 class A 时:
func(5, 2.3);
A<int, double> a;
编译器即根据 function template 的函式引数、或是明白标示的 class
template 引数,自动推导出一份函式实体或 class 实体。换言之这项具
现化动作在编译时期就完成,不会增加执行时期的成本。(关於
template 的语法与性能,请叁考任何一本「年轻的」C++ 全貌型书籍)
STL 是泛型概念的一套实作品。从学理上来说,它其实是一套严谨的
"concepts" 分类学。这里所谓的 concepts 有其严肃定义,意指「对某
种型别的一些条件需求」。满足这些条件者,称为该 concepts 的
models。STL concepts 并没有实际对应到 C++ 或其他语言的任何东西。
作者:
tp寶寶^ō^
时间:
2009-11-5 00:37
前面的namespace如何使用后面namespace的成员?(namespace的交叉引
用)
namespace ns1
{
//这里想使用在ns2中声明的类CAT,怎么做?
int fun(CAT&){return 0;} //目前无法编译
}
namespace ns2
{
class CAT{};
}
int main()
{
system("pause");
return 0;
}
可以通过采取先声明,后定义的方法实现
namespace ns2{
class CAT;
};
using namespace ns2;
namespace ns1
{
int fun(CAT&){return 0;}
}
namespace ns2
{
class CAT{};
}
int main()
{
system("pause");
return 0;
}
作者:
青苹果
时间:
2009-11-5 00:37
关于"隐藏(hide)", "重载(overload)", "覆盖(override)"
如果基类声明了一个成员函数f(int),并且派生类声明了一个成员函数f(float)(名称相同,但参数类型或数量不同),那么Base的f(int)被隐藏(hidden)而不是被重载(overloaded)或被重写(overridden)(即使基类的f(int)是虚的)
以下是你如何摆脱困境:派生类必须有一个被隐藏成员函数的using声明,例如:
class Base{
public:
void f(int);
};
class Derived:public Base{
public:
usingBase::f;//Thisun-hidesBase::f(int)
void f(double);
};
如果你的编译器不支持using语法,那么就重新定义基类的被隐藏的成员函数,即使它们是非虚的。一般来说这种重定义只不过使用::语法调用了基类被隐藏的成员函数,如,
class Derived:public Base{
public:
voidf(double);
voidf(inti){Base::f(i);}//There definition merely calls Base::f(int)
};
程序:
class Parent
{
public:
virtual void foo()
{
cout << "foo from Parent" << endl;;
}
void foo1()
{
cout << "foo1 from Parent" << endl;;
}
virtual void foo2()
{
cout << "foo2 from Parent" << endl;;
}
};
class Son : public Parent
{
public:
void foo()
{
cout << "foo from Son" << endl;;
}
void foo1()
{
cout << "foo1 from Son" << endl;;
}
void foo2(int i)
{
cout << "foo2 from Son" << endl;;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
Parent *p = new Son();
p->foo();
p->foo1();
p->foo2();
delete p;
system("pause");
return 0;
}
如果按照"隐藏"的说法,foo2()被隐藏的话,那么应该编译不过才对。然而VC的输出是
foo from Son
foo1 from Parent
foo2 from Parent
那么,隐藏的机制到底是不是标准C++?我的做法可以被看作overload吗?
隐藏是否只在foo1()中生效。即,隐藏也要看参数和类型完全一致,且父类的不能是虚函数?
int main()
{
Parent *p = new Son();
p->foo(); //覆盖 一、(1)
p->foo1(); //隐藏 二、(2)
p->foo2(); //隐藏 二、(1)
delete p;
// system("pause");
return 0;
}
一、覆盖是指派生类函数覆盖基类函数,特征是:
(1)不同的范围(分别位于派生类与基类);
(2)函数名字相同;
(3)参数相同;
(4)基类函数必须有virtual关键字。
二、"隐藏"是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。
---------------------------------------------------------------
其实这样来理解就比较容易搞懂了:
1.虚函数的使用有特殊性,在使用的时候会查询指针所指对象内的虚函数表来确定具体应该调用的函数地址。因此调用虚函数的时候优先使用指针所指对象的类函数,而不是其基类的类函数,如果找不到则才从其基类中找一个匹配的成员函数
比如:
p->foo();//因为p指向的是派生类Son的对象,因此优先使用Son的成员函数foo()
p->foo2();//Son类从不存在foo2(),但是从其基类中可以找到。因为没有重载,虚函数表中保存的是基类的成员函数foo2的指针。
2.非虚成员函数的调用则是直接调用指针所指类对象的成员函数,而不会动态编译。
比如:
p->foo1();//编译器直接找到Parent类型的成员函数进行静态编译。
---------------------------------------------------------------
隐藏的几个特征:
1 在基类和派生类之间
2 函数名字相同
如果参数类型不同的时候,有无virtual 关键字都为隐藏
如果参数类型相同,则,无virtual 关键字时为隐藏
顺便说说函数的重载与覆盖:
重载:
1 在同一个类里面
2 函数名字相同
3 函数参数不同
4 virtual 关键字有无均可
覆盖:
1 在基类和派生类之间
2 函数名字相同
3 参数类型相同
4 有virtual关键字
FORM<<高质量C++编程指南>>
覆盖 主要用于从基类访问派生类
而 隐藏 不行,只是隐藏掉了基类的函数,基类变量访问的只能是基类的那个函数
重载 只是在同一个类里面
---------------------------------------------------------------
foo()是经典的override,而foo1()和foo2()全是hide,也即,foo1()和foo2()在子类中不可见。这里要注意,大家通常不会把foo1()认错,而对foo2()也是hide的行为通常不敏感。
只要子类中有一个同名的方法,不管他的参数列表是不是对得上,也不管父类的方法是不是virtual的(实际上,在这个case中不考虑virtual反而不容易胡涂...记得只要没有override,虚表调用和非虚调用的结果并无区别,在子类中就不可能再看到父类中的所有同名方法。
欢迎光临 新微赢技术网 (http://bbs.weiying.cn/)
Powered by Discuz! X3.2