C++ 核心

第一阶段:更好的C语言

声明(declaration)

任何变量和函数在使用前必须实现声明。(定义也是一种声明方式。对于变量,使用extern关键字强调是声明不是定义;对于函数,可以不使用extern 关键字,不带函数体的函数名连同参数等被认为是函数的声明。)

命名空间(namespace)的概念

命名空间是为了解决C++中变量、函数命名的冲突而服务的,命名空间也是一种表示逻辑分组的机制。

引用类型(reference)及其初始化

引用是某个对象的别名,它类似于一个指针常量。当创建一个引用时,引用必须被初始化指向一个存在的对象。如果引用作为函数的参数,则在函数调用是进行初始化。引用类型还可以作为函数的返回值。

内联函数(inline function)

用inline关键字定义的函数叫做内联函数;内联函数可以降低函数调用带来的开销,但增加了函数的代码。需要注意的是,内联函数必须以定义的形式声明,函数体内不能有循环结构和switch语句。

函数名过载(overload)

不同的函数(参数类型、个数不同)允许采用相同的函数名,编译过程中需要根据一定的匹配准则从一系列过载函数中寻找最匹配的函数(这些函数的内部命名其实是不相同的)。注意,由于仅根据返回值不能区别两个函数,所以只有返回值不同的两个函数采用相同的函数名是不合法的。

函数的缺省参数值

函数声明中允许为一个或多个参数指定缺省的参数值,但要求所有没有缺省值的参数都放在函数参数列表的前面部分。下面一段代码中,由于无法对过载函数正确匹配而发生编译错误。

  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;
  }

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函数。

堆对象,new/delete操作符

堆对象在程序运行过程中根据需要被创建或删除。

创建堆对象:T* ptr = new T(参数列表);
创建堆对象数组:T* ptr = new T[NBR];
删除堆对象:delete ptr;
删除堆对象数组:delete [] ptr;
创建堆对象数组时,T类型必须有缺省构造函数。

异常(exception)和异常处理

库的编写者可以预料到运行期间的错误,但不知道如何处理错误;库的使用者知道如何处理错误,但无法检测错误,于是引入了异常机制。

异常的抛出:throw表达式
异常的捕获:try语句体
异常的处理:catch语句体

变量的定义

在C++中,变量的定义不再被限制到语句首。

数组的初始化

Y类型数组的初始化可能有如下形式:Y y[] = {Y(1), Y(2), Y(3)};

值返回和引用返回

值返回使用语句return expression或return variable,前者对expression求值并将该值存入一个临时变量中,后者直接将variable拷贝到临时变量,函数调用者可以访问这个临时变量。引用返回使用语句return variable,返回后不产生临时变量,函数调用者以引用的形式直接操作variable,因此,引用返回的对象不可以为局部的栈变量。

第二阶段:数据抽象——将数据结构和行为捆绑在一起

对象

一个对象具有状态(state)、行为(behavior)和标识(identity)。对象的状态包括它的属性以及这些属性的当前值;对象的行为包括可以进行的操作以及所伴随的状态变化;对象的标识用于区别其它的对象。

类用于实现用户自定义类型。类在结构上分为接口部分和内部实现部分。前者包括类的共有成员声明及规范说明,后者包括成员函数的实现代码和需要的数据结构。

类成员的访问控制描述符

public		共有成员描述符,共有成员可以在任何程序单元中被引用
private		私有成员描述符,私有成员只能在类的实现代码中被引用
protected	保护成员描述符,保护成员除了可以在类的实现代码中被引用外还可以在派生类的实现代码中被引用。
friend		友元描述符,友元不是类的成员,而是允许访问类私有成员、保护成员的其它类或函数。

(使用class关键字,其成员缺省为私有;使用struct关键字,其成员缺省为公有)

隐藏实现的不完整性及解决方案

C++中的存取控制允许将实现与接口部分分离,但实现的隐藏是不完全的:私有成员对用户是可见的。这导致两个问题:用户虽然不能访问私有成员,但可以看见它;如果实现部分的改变要求私有成员发生变化,用户部分也因之要发生变化。

解决方案:在类的定义中只提供一个指向私有成员结构的指针。

构造函数和析构函数

构造函数的作用是在对象被创建时采用特定的值构造对象,使其处于一个初始状态;析构函数完成对象被删除前的一些清理工作。构造函数和析构函数由编译器调用,不需要标识返回类型。一个对象建立时,他的所有成员对象也一同建立。因此,构造函数的执行顺序为:首先执行其父类的构造函数——如果有的话、其次执行其成员对象的构造函数、然后才执行自身构造函数;对应的析构函数的执行顺序则为:自身析构函数、成员对象的析构函数、父类的析构函数。 一个类可以有多个不同的构造函数,其中没有任何参数的构造函数成为缺省构造函数。一个类也可以不定义构造函数,这时调用系统设计的缺省构造函数。

拷贝构造函数(copy constructor):X::X(X& x)

拷贝构造函数使用一个已有对象初始化一个正在建立的同类型对象。在函数调用过程中,拷贝构造函数是实现值方式传递参数和返回用户定义对象的根本所在。如果没有定义拷贝构造函数,编译器采用缺省的原始行为:位拷贝(bitcopy)。

静态成员

  • 静态数据成员(类数据成员):静态数据成员具有静态生存周期;静态数据成员必须在程序的某处被初始化(定义);静态数据成员不是对象的成员,虽然也可以通过对象来引用;静态数据成员也有public、private、protected三种。
  • 静态成员函数(类成员函数):静态成员函数不是对象的成员,虽然也可以通过对象来调用;静态成员函数的调用也可以完全不依赖于对象(不需要this指针);静态成员函数的实现部分不能引用类的非静态数据成员。

操作符过载(overload)

可以过载的预定义操作符包括:+, -, *, /, %, ^, &, |, ~, !, =, <, >, +=, -=, *=, /=, %=, ^=, &=, |=, «, », ==, !=, ⇐, >=, &&, ||, ++, –, [], →, new, new [], delete, delete []。

两种形式:

  • 作为全局函数(类的友元),一元操作符有一个参数;二元操作符有两个参数。
  • 作为成员函数,一元操作符有一个参数(this指针),但不出现在参数列表中;二元操作符有两个参数,但参数表中之出现一个(另一个是 this指针)。

定义方法:

  • 返回类型 operator 操作符(参数列表)
  • 返回类型 类名::operator 操作符(参数列表)

赋值操作符过载:“=”只能作为成员函数被过载。如果没有定义operator =,编译器会自动创建一个,如同创建缺省拷贝构造函数一样。

自动类型转换

方法一:利用构造函数实现 class U {public : U{T t};}(目的类执行转换:T→U)

方法二:类型转换成员函数 class U {public : operator T();} (源类执行转换:U→T)

第三阶段:继承、组合、抽象基类、虚函数

继承(inheritance)

C++中的继承方式

  • 公有继承:class B : public A {…}; 基类的公有成员和保护成员作为派生类的成员时,依然保持原有的访问控制特性。
  • 私有继承:class B : private A {…}; 基类的公有成员和保护成员都作为派生类的私有成员。
  • 多继承:class C : public A, private B {…};

继承的原则:若逻辑上B是A的“一种”,并且A的所有功能和属性对于B而言都有意义。

公有继承的作用:第一是实现以代码复用为目的的类继承;第二是实现以接口复用为目的的接口继承。

私有继承的作用:私有继承所得的派生类不具备父类特性,但可以重用父类代码,从而实现代码复用。(实现与组合相同的功能)

==== 某些观点认为应该避免单纯以代码复用为目的而设计的继承。 构造函数的调用次序 ====

首先调用基类构造函数,初始化基类子对象;然后调用各成员对象构造函数,初始化各成员对象;最后调用类构造函数,初始化当前对象。格式如下所示:

B:B(int l, int n):A(l):b(n) {...}

其中B由A派生,b是B的成员。

重载(override)

派生类中若重定义基类的同名函数,则基类中的同名函数在派生类中被覆盖,不再可用。

向上映射原则

如果一个函数的参数为类类型、类指针或类引用类型,则这个函数也可以接受该类的派生类作为其参数,但不能接受该类的基类作为其参数。

渐增式开发

继承的优点之一在于它允许开发者在已经存在的代码中引入新代码,而不会给原代码带来错误,如果发现错误,这个错误也只与新代码有关。

虚函数(virtual function)

虚函数的出现:基类和派生类中有一个同名的操作,但其实现并不相同。对于一段与基类接口通信的代码,编译器无法确定对象属于基类还是派生类,若该对象引用了一个在基类和派生类中有不同定义的同名操作,编译器必须将该操作束定到某个具体函数上。

  • 静态束定:在编译过程中将其束定到基类操作上了。
  • 动态束定:若基类中该操作为虚函数,则在运行过程中动态束定。
Q: 析构函数为什么要定义为虚函数?
A: 由于在编译时期无法判断delete操作符所操作的指针是否指向一个派生类对象,唯有采用动态绑定的方式才能调用正确的析构函数。 否则,如果一个类指针指向的是其派生类的对象,delete操作直接调用该类的析构函数,子类对象就无法被正确析构了。
Q: 构造函数可否定义为虚函数?
A: 不可以。首先没有必要,当使用new创建某个特定对象的时候,对象的类是确定的,其构造函数也是确定的,因此无须进行动态绑定;其次是不可能,因为在构造函数尚未被执行的时候,动态绑定的机制还没有建立。
Q: 构造函数中调用虚函数会怎样?
A: 构造函数的实现部分不采用动态绑定机制,构造函数中所调用的该类的所有成员函数——包括虚函数——都将静态绑定到该类对应的实现上。也可以这样理解,在构造函数退出之前,对象尚未完全生成,则动态绑定的机制尚未建立(虽然此时虚函数表指针可能已经挂接到正确的虚函数表上)。

抽象基类和纯虚函数

纯虚函数的声明:virtual void f() = 0;(纯虚函数不需要定义)

包含有纯虚函数的类称为抽象基类,抽象基类仅仅提供一个公共接口,但并不给出其实现,试图创建一个抽象类的对象会引发编译错误。当希望通过公共接口操作一组类时,就创建一个抽象基类。

模板(template)

函数模板:

template <typename 类型标志符> 函数定义
template <class 类型标志符> 函数定义

类模板

  • 类定义:
      template <typename 类型标志符> 类定义
      template <class 类型标志符> 类定义
  • 类成员函数定义:
      template <typename 类型标志符> 类名::函数名 { 函数体 }
      template <class 类型标志符> 类名::函数名 { 函数体 }

补充说明:一个模板是允许多个类型参数的。

组合(composition)

组合的概念允许用户利用已定义的类实现更复杂的类,这种复杂的类的对象由多个成员对象构成。

组合的原则:若在逻辑上A是B的一部分,则不允许B从A派生,而是要用A和其他东西组合出B。

多态

多态体现为同一个程序段可以处理多种类型的数据,目的是通过不同的方式执行相同的操作,以达到相同的目的。

  • 过载多态:通过函数名的过载和操作符的过载实现。
  • 强制多态:通过类型强制转换实现。
  • 包含多态:通过子类型化实现。(一个程序段既能处理类型T的对象,也能处理T的子类型的对象,同名的行为因子类型的不同而有不同的实现)
  • 类型参数化多态:通过类模板和函数模板实现。

RTTI

RTTI叫做“运行期间类型识别”,C++语言通过在对象的vtable中保存一个type_info指针指向描述该对象类型的type_info结构来实现RTTI。和RTTI相关的操作符主要有两个,其一是typeid,其二是dynamic_cast。

typeid返回一个描述对象类型的type_info引用,使用前需要包含<typeinfo>头。

typedef unsigned int UINT;
void foo()
{
    cout << typeid(UINT).name() << endl;    //"unsigned int"
    cout << typeid(string).name() << endl;    //"string"
}

dynamic_cast可以将一个父类的指针转化为某个派生类的指针,而如果父类指针指向的对象的类型并不是指定的派生类,则返回空。这一机制也是由RTTI来实现的。

使用g++编译器的话要打开-frtti来支持RTTI。必须关注RTTI代码和非RTTI代码混编的情况,假如A类编译的时候没有启动RTTI而继承自A类的B类在编译时启动了RTTI,则会出现找不到A类的type_info定义的错误。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值