本章是核心章节(知识考核点占据百分五十以上),分五节进行解析。
本节内容涵盖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成员,编译失败。

