1、面向对象的核心概念:封装,继承,多态
2、结构变量的每个成员都会占据不同的内存单元,而联合变量的每个成员都共享同样的内存单元。即一个联合变量的大小实际上是他占内存最多的那个成员的大小。
3、malloc和free的不足:
- malloc参数是以字节计的内存大小,程序员需要自己计算待分配单元的字节数。
- malloc返回值是无类型指针void*,需要程序员自己转换成合适的指针类型。
使用new和delete的优点为:
- new参数是待分配单元的数目,它自动计算要分配类型的大小,不用给出字节数。
- new自动返回正确的指针类型,不必转换。
- 可以用new将分配的存储空间进行初始化。
4、带有参数的宏很受限,他是无类型机制,每个变元仅能被使用一次,有时产生不可期望的副作用。为此定义内联函数,既集合函数的复杂性,又体现宏的替换性。
5、静态数据成员属于类,而不属于对象。静态数据成员的定义在类内,在类外进行说明。
6、构造函数的初始化方式:
- 在构造函数内用赋值语句
- 用构造函数的初始化列表方式
注意:一些特殊成员,比如常量成员和引用成员,都必须在初始化列表中进行。
7、一个类如果有const成员或引用成员,就不能使用缺省的构造函数,必须用户自己定义。
8、一般情况下,拷贝构造函数都很好工作。但存在一些特殊情况,比如说函数中形参p的构造函数如果是缺省的话,会少掉一些操作,所以要显式拷贝构造函数。详细看C+书的P116。
9、一个成员函数的原型后面跟一个const,该函数称为const成员函数,特点是该函数不能修改this所指的对象的成员。
this指针主要用在运算符重载和自引用的场合。
10、指向类对象的指针做声明时,没有调用构造函数。
指向数据成员的声明:int INTEGER::*pnum
指向函数成员的声明:int (INTEGER::*pfun)(int)
11、友元关系总结:
- 友元函数不属于任何类,因此没有this指针。使用友元函数时参数必须指明操作对象。
- 使用友元函数能提高效率,使表达简洁;其次,在运算符重载的某些场合需要使用友元。
- 友元类提供不同类的对象之间合作的一种方式。
12、C+不允许初始化对象数组,要创建一个对象数组,则该类的构造函数必须满足下面其中一点:
- 没有构造函数
- 有构造函数但没有参数
- 有构造函数但只有缺省的参数
13、如果函数有了传值参的版本,则不能同时有一个传引用的版本。
传值参会引起形参的拷贝构造函数的调用,特殊情况下会引起问题,比如第8点。
14、类类型常量,如复数类Complex,则2+3i就是Complex类的类型常量。
INTEGER(5) 该语句就是先调用构造函数得到类类型常量。
15、为了减少参数传递过程中的内存分配次数和调用拷贝构造函数,提高程序的运行效率,参数建议以引用的方式传递。同时将参数声明为const,这样可以保护传递到函数内部的obj对象。因此一般参数的理想设置为:const Complex &obj
16、一元运算符重载:
成员函数重载 type Class_Name::operator@(){...}
友元函数重载 type operator@(Class_Name obj)
17、运算符重载为成员函数和友元函数的选择建议:
比如
成员函数声明Complex operator+(const Complex &obj);
友元函数声明friend Complex operator+(const Complex &obj1, const Complex &obj2);
main中有语句obj3=27+obj1,
如果是成员函数声明的话,obj3=27.operator+(obj1),但27不是Complex类型,因此不能调用Complex的operator+()重载运算符完成加法操作。
如果是友元函数声明的话,则27可以调用类的有一个参数缺省的构造函数来使27转换为Complex对象,进而可以运行obj3=27+obj1。
- 最后,若运算符的操作需要修改类对象的状态,则它应该是成员函数,而不是友元。
- 需要右值操作的运算符(=,+=,++)的重载最好使用成员函数。
- 如果运算符所需的操作数希望有隐式转换,则该运算符重载必须用友元。
18、重载++和--的前缀和后缀方式
前缀:
成员函数重载:Class_Name Class_Name::operator++();
友元函数重载:Class_Name operator++(Class_Nam &);
后缀:
成员函数重载:Class_Name Class_Name::operator++(int);
友元函数重载:Class_Name operator++(Class_Nam &int);
19、重载的运算符operator=不能被继承,而且它必须被重载为成员函数。一般格式:
Class_Name & Class_Name::operator = (const Class_Name & from){...}
所有赋值符号都会修改左值,因此左值不能被const修饰。如果是友元函数,则参数的左值不能被const修饰。
为了能够连续赋值,应该返回左值的引用,以便修改。
20、运算符( )和 [ ] 不能用友元函数重载,只能采用成员函数重载,因为通常直接对象调用,即obj(arg1,arg2)解释为obj.operator()(arg1,arg2);
而运算符<<只能使用友元函数进行重载,因为隐式调用方式为cout<<Class_obj;
若显示调用则解释为cout.operator<<(Class_obj);这里cout本身不是类类型Class_Name的对象。
综上,<<和>>的重载格式为:
friend ostream & operator <<(ostream & os,const Complex &c); //保证<<的连用性,重载函数返回&
friend istream & operator >>(istream & is, Complex &c);
21、指针悬挂问题:使用new申请的存储空间无法访问也无法释放。原因是对指向new申请的存储空间的指针变量进行赋值修改。
22、重载new和delete。这样做是为了有时希望使用某种特殊的动态内存分配方法。比如说用户希望控制某一片存储空间的分配。
void * operator new(size_t size)
{
return pointer_to_memory;
}
void operator delete(void *p)
{
//释放p指向的空间
}
注意局部重载时重载版本的new只适应特定的类,其他类使用默认的new;而全局重载时重载版本的new作用于全局。
23、指向临时对象的指针,在临时对象释放后,指针指向了不可预知的地方。
24、类型转换函数重载
类型转换函数只能定义为一个类的成员函数,而不能定义为类的友元。
标准类型-》标准类型
标准类型-》类类型
类类型 -》标准类型
类类型 -》类类型
比如:
integer::operator real()//integer转为real
{
real num; //定义目的real类
num()=(float) lval;//使用real类的重载运算符()来返回real类的数据成员,并用当前的integer类的数据成员赋值给它
//可以看作rval=lval
return num; //返回目标real类,此时
}
25、继承派生
公有派生:
- 派生类的对象可以直接赋值给基类的对象。
- 基类对象的引用可以引用一个派生类对象。
- 基类对象的指针可以指向一个派生类对象。此时基类指针访问的是派生对象拥有的基类部分,派生类自身部分不能被基类指针访问。如果希望用基类指针访问公有派生的特定成员,必须将基类指针用显示类型转换为派生类指针。
私有派生:
- 基类的公有成员和保护成员在私有派生中成为私有成员。
- 基类的私有成员和不可访问成员在私有派生中成为不可访问成员。(如果希望私有派生的基类部分的成员能够被类外或者再次派生的类使用,可以使用覆盖的方法。)
保护派生:
- 基类中命名为保护的成员,他们可以被派生类访问,但不能被其他类访问。
注意:static成员受段约束符的影响,基类和派生类会共享基类的static成员。
26、访问声明
假如A是基类,B是A的私有继承类,C是B的继承类,则C此时不能访问A的所有成员。这里C+引入访问声明来使C能够访问基类A。
27、多态性
静态多态性:函数重载
动态多态性:虚函数 virtual
那什么时候要把函数定义为虚函数呢?
- 如果成员函数所在的类在继承后有功能修改,则声明为虚函数。
- 如果是通过基类指针或引用去访问,则声明为虚函数。
- 如果基类比较抽象或不能确定,可以将基类定义为抽象类。(纯虚函数,只定义函数名字,没有函数体)
28、含有至少一个纯虚函数的类,称为抽象类。
- 抽象类不能建立对象。
- 抽象类不能作为返回类型或参数类型
29、其他一些注意事项
- 虚函数虚特性丢失问题:重载虚函数的时候,一定要完全一致,就只是缺少个virtual关键字。不然会丢失虚特性。
- 虚函数必须是类的成员函数。
- 析构函数可以是虚函数,但构造函数不能为虚函数。
- 声明为虚函数的函数,无论经过多少派生层,仍然保持虚特性。
- 程序中最好将所有的析构函数都声明为虚析构函数,这将使所有的派生类的析构函数也自动成为虚函数。比如base * pb=new derived;这语句先执行基类的构造函数,再到derived的构造函数,然后执行base的析构函数。如果此时把base的构造函数声明为虚析构函数,则先执行derived的析构函数,最后是base的析构函数。