1. 第一阶段:更好的C语言
1.1. 声明(declaration)
任何变量和函数在使用前必须实现声明。(定义也是一种声明方式。对于变量,使用extern关键字强调是声明不是定义;对于函数,可以不使用extern关键字,不带函数体的函数名连同参数等被认为是函数的声明。)
1.2. 命名空间(namespace)的概念
命名空间是为了解决C++中变量、函数命名的冲突而服务的,命名空间也是一种表示逻辑分组的机制。
1.3. 引用类型(reference)及其初始化
引用是某个对象的别名,它类似于一个指针常量。当创建一个引用时,引用必须被初始化指向一个存在的对象。如果引用作为函数的参数,则在函数调用时进行初始化。引用类型还可以作为函数的返回值。
1.4. 内联函数(inline function)
用inline关键字定义的函数叫做内联函数;内联函数可以降低函数调用带来的开销,但增加了函数的代码。需要注意的是,内联函数必须以定义的形式声明,函数体内不能有循环结构和switch语句。
1.5. 函数名过载(overload)
不同的函数(参数类型、个数不同)允许采用相同的函数名,编译过程中需要根据一定的匹配准则从一系列过载函数中寻找最匹配的函数(这些函数的内部命名其实是不相同的)。注意,由于仅根据返回值不能区别两个函数,所以只有返回值不同的两个函数采用相同的函数名是不合法的。
1.6. 函数的缺省参数值
函数声明中允许为一个或多个参数指定缺省的参数值,但要求所有没有缺省值的参数都放在函数参数列表的前面部分。下面一段代码中,由于无法对过载函数正确匹配而发生编译错误。
void fun(int a, double b = 1);
void fun(int a);
int main(int argc, char* argv[])
{
int x = 4;
fun(4);
return 0;
}
void fun(int a, double b)
{
return;
}
void fun(int a)
{
return;
}
1.7. const关键字
const是一个类型修饰符,const修饰一个类型得到该类型的派生类型,使用该派生类型定义的变量必须被初始化,而且其值不能再改变。
const char *p; //pointer to constant char
char* const p; //constant pointer
const double&r = R; //reference to constant
const关键字是左结合的,它修饰在它左边出现的类型描述符:T const,但是,当T是一个简单类型的时候,也可以写作const T,如const int x;如果函数参数为const,则函数内部不能改变其值。如果成员函数被const修饰,则该成员函数不能修改成员变量或调用另一个非const函数。如果一个对象被声明为const,则它只能调用被const修饰的成员函数。
1.8. 堆对象,new/delete操作符
堆对象在程序运行过程中根据需要被创建或删除。
创建堆对象:T* ptr = new T(参数列表);
创建堆对象数组:T* ptr = new T[NBR];
删除堆对象:delete ptr;
删除堆对象数组:delete [] ptr;
创建堆对象数组时,T类型必须有缺省构造函数。
1.9. 异常(exception)和异常处理
库的编写者可以预料到运行期间的错误,但不知道如何处理错误;库的使用者知道如何处理错误,但无法检测错误,于是引入了异常机制。
异常的抛出:throw表达式
异常的捕获:try语句体
异常的处理:catch语句体
1.10. 变量的定义
在C++中,变量的定义不再被限制到语句首。
1.11. 数组的初始化
Y类型数组的初始化可能有如下形式:Y y[] = {Y(1), Y(2), Y(3)};
1.12. 值返回和引用返回
值返回使用语句return expression或return variable,前者对expression求值并将该值存入一个临时变量中,后者直接将variable拷贝到临时变量,函数调用者可以访问这个临时变量。引用返回使用语句return variable,返回后不产生临时变量,函数调用者以引用的形式直接操作variable,因此,引用返回的对象不可以为局部的栈变量。
2. 第二阶段:数据抽象——将数据结构和行为捆绑在一起
2.1. 对象
一个对象具有状态(state)、行为(behavior)和标识(identity)。对象的状态包括它的属性以及这些属性的当前值;对象的行为包括可以进行的操作以及所伴随的状态变化;对象的标识用于区别其它的对象。
2.2. 类
类用于实现用户自定义类型。类在结构上分为接口部分和内部实现部分。前者包括类的共有成员声明及规范说明,后者包括成员函数的实现代码和需要的数据结构。
2.3. 类成员的访问控制描述符
public 公有成员描述符,公有成员可以在任何程序单元中被引用
private 私有成员描述符,私有成员只能在类的实现代码中被引用
protected 保护成员描述符,保护成员除了可以在类的实现代码中被引用外还可以在派生类的实现代码中被引用。
friend 友元描述符,友元不是类的成员,而是允许访问类私有成员、保护成员的其它类或函数。
(使用class关键字,其成员缺省为私有;使用struct关键字,其成员缺省为公有)
2.4. 隐藏实现的不完整性及解决方案
C++中的存取控制允许将实现与接口部分分离,但实现的隐藏是不完全的:私有成员对用户是可见的。这导致两个问题:用户虽然不能访问私有成员,但可以看见它;如果实现部分的改变要求私有成员发生变化,用户部分也因之要发生变化。
解决方案:在类的定义中只提供一个指向私有成员结构的指针。
2.5. 构造函数和析构函数
构造函数的作用是在对象被创建时采用特定的值构造对象,使其处于一个初始状态;析构函数完成对象被删除前的一些清理工作。构造函数和析构函数由编译器调用,不需要标识返回类型。一个对象建立时,他的所有成员对象也一同建立,然后才执行该对象的构造函数。以下是类T的构造函数实现,其中t1、t2、 t3是T的成员。
T::T(int k, int l, int m):t1(k), t2(l), t3(m) { ... }
一个类可以有多个不同的构造函数,其中没有任何参数的构造函数成为缺省构造函数。一个类也可以不定义构造函数,这时调用系统设计的缺省构造函数。
2.6. 拷贝构造函数(copy constructor):X::X(X& x)
拷贝构造函数使用一个已有对象初始化一个正在建立的同类型对象。在函数调用过程中,拷贝构造函数是实现值方式传递参数和返回用户定义对象的根本所在。如果没有定义拷贝构造函数,编译器采用缺省的原始行为:位拷贝(bitcopy)。
2.7. 静态成员
* 静态数据成员(类数据成员):静态数据成员具有静态生存周期;静态数据成员必须在程序的某处被初始化(定义);静态数据成员不是对象的成员,虽然也可以通过对象来引用;静态数据成员也有public、private、protected三种。
* 静态成员函数(类成员函数):静态成员函数不是对象的成员,虽然也可以通过对象来调用;静态成员函数的调用也可以完全不依赖于对象(不需要this指针);静态成员函数的实现部分不能引用类的非静态数据成员。
2.8. 操作符过载(overload)
可以过载的预定义操作符包括:+, -, *, /, %, ^, &, |, ~, !, =, <, >, +=, -=, *=, /=, %=, ^=, &=, |=, <<, >>, ==, !=, <=, >=, &&, ||, ++, --, [], ->, new, new [], delete, delete []。
两种形式:
* 作为全局函数(类的友元),一元操作符有一个参数;二元操作符有两个参数。
* 作为成员函数,一元操作符有一个参数(this指针),但不出现在参数列表中;二元操作符有两个参数,但参数表中之出现一个(另一个是this指针)。
定义方法:
* 返回类型 operator 操作符(参数列表)
* 返回类型 类名::operator 操作符(参数列表)
赋值操作符过载:“=”只能作为成员函数被过载。如果没有定义operator =,编译器会自动创建一个,如同创建缺省拷贝构造函数一样。
2.9. 自动类型转换
方法一:利用构造函数实现 class U {public : U{T t};}(目的类执行转换:T->U)
方法二:类型转换成员函数 class U {public : operator T();} (源类执行转换:U->T)
3. 第三阶段:继承、组合、抽象基类、虚函数
3.1. 继承(inheritance)
C++中的继承方式
* 公有继承:class B : public A {...}; 基类的公有成员和保护成员作为派生类的成员时,依然保持原有的访问控制特性。
* 私有继承:class B : private A {...}; 基类的公有成员和保护成员都作为派生类的私有成员。
* 多继承:class C : public A, private B {...};
继承的原则:若逻辑上B是A的“一种”,并且A的所有功能和属性对于B而言都有意义。
公有继承的作用:第一是实现以代码复用为目的的类继承;第二是实现以接口复用为目的的接口继承。
私有继承的作用:私有继承所得的派生类不具备父类特性,但可以重用父类代码,从而实现代码复用。(实现与组合相同的功能)
某些观点认为应该避免单纯以代码复用为目的而设计的继承。
3.2. 构造函数的调用次序
首先调用基类构造函数,初始化基类子对象;然后调用各成员对象构造函数,初始化各成员对象;最后调用类构造函数,初始化当前对象。格式如下所示:
B::B(int l, int n):A(l):b(n) {...}
其中B由A派生,b是B的成员。
3.3. 重载(override)
派生类中若重定义基类的同名函数,则基类中的同名函数在派生类中被覆盖,不再可用。
3.4. 向上映射原则
如果一个函数的参数为类类型、类指针或类引用类型,则这个函数也可以接受该类的派生类作为其参数,但不能接受该类的基类作为其参数。
3.5. 渐增式开发
继承的有点之一在于它允许开发者在已经存在的代码中引入新代码,而不会给原代码带来错误,如果发现错误,这个错误也只与新代码有关。
3.6. 虚函数(virtual function)
虚函数的出现:基类和派生类中有一个同名的操作,但其实现并不相同。对于一个与基类接口通信的函数,编译器无法确定对象属于基类还是派生类,若该对象引用了一个在基类和派生类中有不同定义的同名操作,编译器必须将该操作束定到某个具体函数上。
* 静态束定:在编译过程中将其束定到基类操作上了。
* 动态束定:若基类中该操作为虚函数,则在运行过程中动态束定。
3.7. 抽象基类和纯虚函数
纯虚函数的声明:virtual void f() = 0;(纯虚函数不需要定义)
包含有纯虚函数的类称为抽象基类,抽象基类仅仅提供一个公共接口,但并不给出其实现,试图创建一个抽象类的对象会引发编译错误。当希望通过公共接口操作一组类时,就创建一个抽象基类。
3.8. 模板(template)
函数模板:
template <typename 类型标志符> 函数定义
template <class 类型标志符> 函数定义
类模板
* 类定义:
template <typename 类型标志符> 类定义
template <class 类型标志符> 类定义
* 类成员函数定义:
template <typename 类型标志符> 类名::函数名 { 函数体 }
template <class 类型标志符> 类名::函数名 { 函数体 }
补充说明:一个模板是允许多个类型参数的。
3.9. 组合(composition)
组合的概念允许用户利用已定义的类实现更复杂的类,这种复杂的类的对象由多个成员对象构成。
组合的原则:若在逻辑上A是B的一部分,则不允许B从A派生,而是要用A和其他东西组合出B。
3.10. 多态
一个程序段可以处理多种类型的数据。
* 过载多态:通过函数名的过载和操作符的过载实现。
* 强制多态:通过类型强制转换实现。
* 包含多态:通过子类型化实现。(一个程序段既能处理类型T的对象,也能处理T的子类型的对象,同名的行为因子类型的不同而有不同的实现)
* 类型参数化多态:通过类模板和函数模板实现。
下载全文
认识C++
最新推荐文章于 2024-10-01 23:10:03 发布