目录

  • 1 绪论
    • 1.1 结构化程序设计的特点
    • 1.2 OOP的特点
  • 2 C++语言基本要素
    • 2.1 语言基本要素
  • 3 指针与引用
    • 3.1 值传递
    • 3.2 static修饰符
    • 3.3 指针
    • 3.4 引用
    • 3.5 const修饰符
  • 4 类与对象
    • 4.1 类与结构体
    • 4.2 static修饰类的属性
    • 4.3 初始化与构造
    • 4.4 析构
  • 5 两类多态
    • 5.1 静态多态
    • 5.2 动态多态
    • 5.3 函数间关系
    • 5.4 动态多态的应用
    • 5.5 习题
  • 6 高级面向对象思想
    • 6.1 可维护性与可复用性
    • 6.2 面向对象思想原则
    • 6.3 对象间的关系
    • 6.4 习题
  • 7 杂项
    • 7.1 两类内存泄漏
    • 7.2 RAII与ScopeGuard习语
    • 7.3 复杂模型设计1
    • 7.4 复杂模型设计2
    • 7.5 复杂模型设计3
类与结构体


本章是核心章节(知识考核点占据百分五十以上),分五节进行解析。

本节内容涵盖PPT中3-44页。

相对来说,本节内容最为简单,分两部分展开:

A 类的结构

typedef struct

{

  char* name;

  char* sno;

}Person;

这是经典的结构体定义(声明了一个类型)。而在2.2节中,我们了解到类的结构声明如下:

//class.h

class Person

{

   char* name;//姓名   //name数据成员声明

   char* sno;//学号   //sno数据成员声明

public://访问控制符

   char* getSno() const;  //getSno()成员函数声明,为防止定义误操作,使用了const只读修饰

   char* getName() const; //getName()成员函数声明,为防止定义误操作,使用了const只读修饰

};  //千万不要掉了分号,因为类型定义其实是声明语法

与传统的结构体声明相比,增加了红色部分,还有绿色部分。所以,类是增强升级版的结构体。

当定义了这个类型之后(类声明),我们在使用这个类型再定义变量时,就对应给予了变量的诸多分量定义。

继续如下:

  Person zhangsan;//定义了zhangsan变量,我们开始称之为对象

其实就定义了zhangsan.name和zhangsan.sno两个分量,也就是给出了对象的数据成员定义。

但还缺少成员函数的定义,为此需要增加cpp文件(当然可以一个文件,但多文件工程是软件工程编程规范)如下:

//class.cpp

#include "class.h"

char* Person::getSno()

{  return sno;  }

char* Person::getName()

{  return name; }

上面褐色部分是两个成员函数函数分量的定义。

B 成员的访问控制符

  上面的类例子中,类声明中绿色的部分,表示这些成员在编译时被附加的访问规则,我们称为成员访问属性(成员被访问的权限)、访问控制符。

C++中定义了三种访问控制符加一个友元例外(关于友元后面再讨论),JAVA中则定义了四种访问控制符加一个final终止子类修改。由此可见,对成员施加访问控制是常见的手段。

  联系最初的变量封装世界,我们结识了自定义类型struct结构体,它是不设防的:只要有声明,后面就可以定义该结构体类型的变量,继而访问变量的所有结构体分量;

  OO(Object Oriented)的对象封装世界,我们认识到了“内外、亲疏有别”,应该和现实世界一样,于是分public和private两种访问权限,也就是说:

1)声明为public(公有)的成员(数据成员、成员函数,以后统称为成员,不再赘述),相当于完全公开给别人访问;

2)声明为private(私有)的成员,只能在内部访问,对外界不公开。

于是我们得到启发,struct相当于完全public化的class!

  但现实世界中还有另一种有别的访问权限。除了“内外有别”,还有“亲疏有别”,比如父亲类的成员,他的继承子类(也就是它的派生类)应该可以访问。这种带有继承关系的跨类间的访问关系,也应该在模拟现实世界的程序世界中得到映射仿真。

3)声明为protected(保护)的父类的成员,在子类的内部代码也可以访问。(言外之意,父类外部,子类外部均不可访问)  

4)没有声明访问属性的成员,class默认是private,struct默认是public。

5)内部不设防,任何类内部的可执行代码,不受本类成员控制符限制。

于是我们得到三元模型(内外、直系亲属)。

在2.2节的末尾,我们强调了类内部和类外部的概念:

  类定义和类声明内部的代码,称为类内部。之所以这里也包括了类声明,因为我们经常写混杂体的类声明和定义(见2.2节),为方便描述而已(在.h内的也可能有类定义,在.cpp中的也可能有类声明。)

C 访问控制符举例

  例1:

//class.h 

class A

{   int x;

    void f()

    { x++; } //4

    A  *p;

public:

    int g(int y);

};

//class.cpp

#include "class.h"

int A::g(int y)

{ x+=y; p->x++;} //5

//main.cpp

#include "class.h"

void main()

  A obj;    //0

  obj.x = 0; //1

  obj.f();  //2

  obj.g(3);  //3

}


首先要明确,这三个文件组成的工程中,可执行代码除了main内的//0--//3四行,还有f在A中的隐式声明也是定义语句,都需要检查访问发生地时各个属性的访问授权是否足够大。

  对于访问语句//0:

    obj的首次出现,检查类型A声明,查找public无参构造函数,发现A声明中没有无参构造,就直接绑定系统默认无参构造,编译成功;

  对于访问语句//1:

    编译检查时查找obj.x的声明(每逢资源访问/调用,检查声明),发现obj所在的类型A声明,以及A声明内A:;x数据成员分量的声明,访问属性是空缺的,这就是取缺省的private属性。所以类A外部不可访问私有成员。

  所以,编译失败

  对于访问语句//2:

    类似//1,编译失败

  对于访问语句//3:

    编译器检查obj.g(3),发现obj所在的类型A声明,以及A声明内A::g(int)成员函数分量的声明,成员函数参数类型也适配成功,最后检查访问权限,是public,授权足够,编译成功;

  对于访问语句//4:

    内部访问了x分量,检查x的声明,发现是缺省属性private,但这是本类内部代码,不受访问控制符限制。编译成功。

  对于访问语句//5:

   尽管是内部代码,访问属性无论是啥都过关,但p指针没有指向就直接访问指向,相当凶险,编译错误。

  例2:

//class.h

class A

{private:  

  int x;

public:

  int y;

protected:

  int z;  };

class B:public A

{};

//main.cpp

#include "class.h"

void main()

{  

  B objb;//0

  objb.x++;//1

  objb.y++;//2

  objb.z++;//3  

}


观察两个文件,可执行语句都在在main函数内,都是类A和类B外部的代码。注意到class B没有增加一个函数或者成员,原样继承了A的所有。(public继承是使用最广泛的继承方式,也就是不改变的原样拷贝继承)

  对于访问语句//0:

    定义objb分量,需要检查B声明,发现B继承了A,检查A声明,并依次查找A和B的无参构造,发现都没有定义无参构造,遂依次绑定A和B的默认无参构造,编译成功;

  对于访问语句//1:

    访问obj.x分量,检查到objb的声明B中有继承来自A的private的A::x,也就是私有访问属性,可惜main是类A和类B外部的代码,授权级别private太小,编译失败;

  对于访问语句//2:

    访问objb.y分量,类似上面语句的编译过程,找到A::y是public属性,授权声明的权限足够,编译成功;

  对于访问语句//3:

    访问objb.z分量,类似上面语句的编译过程,找到A::z是protected属性,但访问发生地是两个类的外部的main代码,授权级别不够,编译失败。

  例3:

//class.h

class A

{private:  

  int x;

public:

  int y;

protected:

  int z;  };

class B:public A

{ 

public:

 void accesszFromB();

};

//class.cpp

#include "class.h"

void B::accesszFromB()

{ z++;//4 

}

//main.cpp

#include "class.h"

void main()

{  

  B objb;//0

  objb.x++;//1

  objb.y++;//2

  objb.accesszFromB();//3

  

}

  与例2相比,本例在执行语句部分仅//3和//4不同,这两句是为访问objb对象中来自A::z而设计!

  对于访问语句//0--//2同例2;

  对于访问语句//3:

    查找objb的声明,发现B声明内有accesszFromB分量的声明,函数类型绑定检查通过,继续检查访问属性,发现是public,授权可以在main等类外部绑定访问——编译成功;

  对于访问语句//4:

    检查当前对象的z成员声明,发现来自父类的A::z是protected访问属性可以在子类内部(B是A的子类)代码访问,编译成功。

  下面给两个题目大家做课堂练习:

    1)分析下面的//0-//4执行语句,找出潜在的各种错误。

//class.h

class A

{private:  

  int x;

public:

  int y;

protected:

  int z;  };

class B:public A

{public:

  int accessXfromB();

  int accessZfromB();

};

//class.cpp

#include "class.h"

int B::accessXfromB()

  return x--;//3

}  

int B::accessZfromB()

{

  return z--; //4

}  


//main.cpp

#include "class.h"

void main()

{ B objb;          //0

 objb.accessXfromB(); //1

 objb.accessZfromB(); //2 

}

编译错误发生在://3

  这道题中,我们将所有的可执行语句都用颜色标识了。错误的//3是因为x是来自内置基类对象的private属性成员,而在B的类内部代码//4中,可以访问内置的基类对象的z成员因为它是基类定义的protected属性成员。对于//1和//2,他们都是public访问属性的声明。

  2)在下面蓝色的可执行语句中,其中//1--//7执行语句哪些存在编译错误。

(改自2007Alibaba笔试题)

//class.h

class A

{  int a; 

protected:

  int b;

};

class B:public A

{

  A *pa; 

  B *pb;

  void foo(int);

public:

  B();

  ~B(); 

};

#include "class.h"

void B::foo(int x)

{  this->a = x; //2

  this->b = x; //3

  pb->a = x;  //4

  pb->b = x;  //5


  pa->a = x;  //6

  pa->b = x;  //7

}

B::B()

    pa = new A;   

    pb = new B;   

}

B::~B()

    delete pa; 

    delete pb;

}

#include "class.h"

void main()

{

  B objb; //objb.B()         

  objb.foo(10);  //1


编译错误发生在://1,//2,//4,//6,//7

  这里主要容易混淆的是//3、//5和//7的区别:

  protected访问属性是指一个大的派生类对象内部蕴含着一个内置的基类对象,这个基类对象的protected属性的成员,可以被派生类对象在本类内部访问。

  对于//3,this是一个当前的派生类对象,b是基类A的protected属性成员,所以this->b可以访问;

  对于//5,pb是一个B类型的指针,是指向一个派生类B对象,而这个访问发生地是派生类B的内部代码,因此pb->的这个派生类对象,可以访问内置的基类对象的protected属性的b成员;

  对于//7,pa是一个A类型的指针,是指向一个A对象,在A对象外部是不能访问protected和private成员,编译失败