C/C++非专家级编程
0.关于定义,那是只有编译器才喜欢的语法——Peter Van Der Linden 《C专家编程》
需要注意的是指针运算符*是右结合的,你最好从右往左读
当个编译器..面对一个无比恶心的声明:
1.从左向右找到第一个标识符,表示“标识符是”
2.看标识符右边下一个符号,左方括号表示“是数组”、左圆括号表示“是函数”
3.看标识符左边下一个符号,*/const/volatile 表示“是指针(const的或volatile)”注意指针从右往左读是右结合的
4.如果左边是一个左括号,把已经处理的生命组合在一起,知道遇见对应的右括号,然后从第二步重新开始
test:
一个取自Telnet的声明:
char* const* (*next)();
next是一个指向函数的指针,这个函数参数是void,返回值是一个指针,指向一个类型为char的常指针
ANSI C中signal()的声明
void (*signal(int sig, void(*func)(int)))(int);
signal是一个函数,参数是int和void(*func)(int)——一个指向函数的指针,返回值void参数int。
signal的返回值是一个指向函数的指针,该函数返回void参数int
参考《C专家编程》第三章
1.指针和数组不是一回事
C/C++中指针和数组不是一回事,只有一种情况下除外——被当作函数形参的时候。【确切的说,访问指针会比访问数组多一次访存】。因为指针是一个变量,变量的内容是一个地址。
访问指针过程:在【符号表】找到指针变量地址,根据地址访问内存,从内存中取出地址再加上偏移量,再访问目标地址。(三次)
访问数组过程:在【符号表】找到数组起始地址,加上偏移量,访问目标地址。(二次)
如果一个数组被声明为:
extern char *p;
但原先定义却是:
char p[10];
当用p[i]这种形式提取这个【声明】的内容时,实际上得到的是一个字符,但是按照上面的方法,编译器却把它当作一个指针,把ASCII字符解释成地址显然是牛头不对马嘴。(《C专家编程》P86)
2. 注意继承中默认构造函数的行为,一般你需要在初始化列表中指定一个base class
C++中,继承自一个类的派生类会自动调用父类的默认构造函数,如果父类无默认构造函数,会导致编译失败:no appropriate default constructor available. 此时必须由子类通过初始化列表显式指定一个构造函数,如:
Child::Child(int pa):Father(pa, 11)
{
return ;
}
3.引用是没有语法指针的指针,又称为别名,可以用作对象的另一个名字。只能初始化一次
4.dynamic_cast 一般用于父类向子类的转换,此时父类必须要有虚函数,否则编译器会报错。
dynamic_cast一般是用来检测一个基类的指针指向的是不是一个特定的子类型的对象 ,它被用于安全地沿着类的继承关系向下进行类型转换。这就是说,你能用dynamic_cast把指向基类的指针或引用转换成指向其派生类或其兄弟类的指针或引用,而且你能知道转换是否成功。
—— MECPP
dynamic_cast 操作符是RTTI的一部分,它支持在运行时刻查询一个多态指针或引用所指向对象的实际类型
dynamic_cast 就是Java中常用的向下转型. 失败的对引用转换将抛出std::bad_cast异常 此类型被包含在 头文件<typeinfo>
static_cast 一般用于编译器隐式的类型转换,不能从表达式中去除const属性
const_cast 用于转换掉表达式中const或者volitileness属性(前提是最本质的属性是非const)
5.namespace 使用
6. inline 成员函数必须在每个调用它的文本文件中被定义,所以没有在类体定义的内联成员函数必须被放在类定义出现的头文件当中。此外inline是一种请求而不是必须,有可能编译器会拒绝inline一个成员函数。
7. C++异常抛出的是任意类型,对应捕捉的类型相同即可,传递的最好是引用以提高效率 。
8. C++中的多态和Java类似,主要指基类的【指针】或【引用】可以指向其任意派生类的能力。成员函数动态绑定的基础是它被声明为virtual,这样多态函数才会在执行器进行类型检查,否则将会直接调用父类的成员函数。
9. 类设计准则
类层次结构的公有接口应该提供哪些操作
这些操作之中,哪些应该被声明为虚拟的(以支持动态绑定)
单个的派生类需要哪些其他的操作
在抽象类中应该声明哪些数据成员
单个的派生类要求哪些数据成员
被所有子类共享的操作不必声明为虚拟函数,子类需执行特有操作的使用虚拟函数提供公共接口和动态绑定,每个派生类提供自己的实现。
继承而来的派生类虚拟函数实例,不再需要(但也可以)指定关键字virtual。编译器会比较函数的原型,从而识别出这个实例。
基类中的虚拟函数可以选择不实现,称之为纯虚拟函数。它被用作类层次结构公有结构中的一个占位符。它也不希望在程序中被调用,而每个后续的派生类都将提供一个实际的实例。
例如:
virtual void eval()=0;
这也意味着此基类不可被实例化,任何实例化此类的操作都会导致编译器报错中止:
base class is abstract:see declaration of base class
10. 继承得到的成员函数没有构成派生类中的重载函数。
C++初学者常见的一个误解是,希望基类和派生类的成员函数构成一个重载函数集,但是实际上这样理解是错误的。见<<C++ Primer 3rd>> p738
如果我们真的想提供一个重载函数集,可以使用一个inline函数套起来,里面手动调用基类的相应函数。或者在派生类函数定义区使用using指示符,引入所有基类相应的成员函数作为重载函数。
11. 构造函数的调用顺序如下:
1、基类构造函数。调用顺序是基类在派生表中的顺序 传递参数可以使用初始化列表指定需调用的基类构造函数。
2、成员类对象构造函数。调用顺序是对象在类中被声明的顺序,而不是他们出现在成员初始化列表中的顺序。
3、派生类构造函数。作为一般的规则,派生类构造函数应该不能直接向一个基类数据成员赋值,而是把值传递给适当的基类构造函数。否则,两个类的实现将编程紧耦合的,将更加难于正确的修改和实现基类的实现,而且这会调用拷贝构造函数,造成效率上的底下。(基类设计者的责任是提供一组适当的基类构造函数)
基类构造函数可以声明为protected以保证基类不会被实例化。
构造函数不会被继承,因为这样太容易引入“未初始化派生类成员”错误。正确的做法是通过初始化成员列表向基类传递构造函数参数(指定使用哪个构造函数,默认情况是无参构造函数)。
而且,派生类构造函数智能合法地调用其直接基类的构造函数。
12. 友元声明不能被继承。我觉得最好的情况是,干脆不要用友元,学学Java的做法。
13. 虚拟函数只在使用指针和引用时才会如预期般的起作用。只有通过基类指针或引用间接指向派生子类型时,多态性才会起作用。
14. 作为一般的规则,应将类层次结构的根基类(声明了一个或多个虚拟函数)的析构函数声明为虚拟的。但是,不像基类的构造函数,一般的基类的析构函数不应该是protected
15. C++中clone的用法。
对于要求“派生类成员函数的返回类型必须与其基类实例的返回类型完全匹配”有一个例外就是支持这种情况。如果虚拟函数的基类实例返回的是一个类类型(或指向类类型的指针或引用),则派生类实例可以返回一个“从基类实例返回的类公有派生出来”的类。《c++ primer 3rd》 P769
16. private继承是一种特殊的继承。
private继承,基类所有的公有接口在派生类中会成为private,一个private基类反映一种“并非基于子类型关系”的继承形式,并不是一种is-a关系,是一种has-a关系。
但是到底是用组合还是private继承来实现has-a关系有一定的准则(《C++ primer 3rd》P803)。在大多数情况下,我认为应该使用组合,这点也是Java的实现。
17. 多重继承可能导致一个基类在派生层次中多次出现,最典型的,它的构造函数将被调用两次,所以在这种情况下,继承该基类应该使用虚拟继承。《C++ primer 3rd》 P813
18. 使用RTTI的前提是基类中定义了虚函数.