1.类的声明格式
访问修饰符
- 类的一个特征就是封装,public和private作用就是实现这一目的。用户代码(类外)可以访问public成员而不能访问private成员;private成员只能由类成员(类内)和友元访问。
- 类的另一个特征就是继承,protected的作用就是实现这一目的。protected成员可以被派生类对象访问,不能被用户代码(类外)访问。
类的继承方式
-
public继承
基类public成员,protected成员,private成员的访问属性在派生类中分别变成:public, protected, private -
protected继承
基类public成员,protected成员,private成员的访问属性在派生类中分别变成:protected, protected, private -
private继承
基类public成员,protected成员,private成员的访问属性在派生类中分别变成:private, private, private
private成员只能被本类成员(类内)和友元访问,不能被派生类访问;protected成员可以被派生类访问。
例子
下面以graphic为例,有长方形,正方形两个子类。
graphic|----rectangle
|----square
|----triangle
|----circle
这些形状分别有自己的边长(圆为半径)属性,颜色属性,计算周长、面积的函数。
- 对于graphic,定义如下
Class graphic{ public: graphic(){}; virtual void sayhello(){ cout << "I'm graphic!" << endl; }; virtual int calculate_circumference()=0; virtual int calculate_square()=0; virtual void print_attr()=0; };
- 对于rectangle,定义如下
Class rectangle: protected graphic{ public: rectangle(double l, double w){ length = l; width = w; }; int calculate_circumference(){ return 2*(length+width); }; int calculate_square(){ return length*width; }; void sayhello(){ cout << "I'm rectangle!" << endl; }; void print_attr(){ cout << "length:" << length << "width" << width << endl; }; protected: double length; double width; };
- 对于square,定义如下
Class square: public graphic{ public: square(double l){ x = l; }; int calculate_circumference(){ return x*4; }; int calculate_square(){ return x*x; }; void sayhello(){ cout << "I'm quare!" << endl; }; void print_attr(){ cout << "x:" << x << endl; }; private: double x; };
对上述定义有如下解释:
- 虚函数 virtual void sayhello();
在基类中使用关键字 virtual 声明的函数。在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数。我们想要的是在程序中任意点可以根据所调用的对象类型来选择调用的函数,这种操作被称为动态链接,或后期绑定。 - 纯虚函数 virtual int calculate_square()=0;
想要在基类中定义虚函数以便在派生类中重新定义该函数更好地适用于对象,但是在基类中又不能对虚函数给出有意义的实现,这个时候就会用到纯虚函数。 - 函数调用情况
运行结果:int main(){ rectangle re(3.13, 4.31);//correct cout << re.length << re.width <<endl; // error, cannot visit protected member cout << re.calculate_square() << endl; //correct re.sayhello();//correct square sq(5.19);//correct cout << sq.x <<endl; // error, cannot visit private member cout << sq.calculate_square() << endl;//correct sq.sayhello();//correct return 0;}
- 如果派生类包含了基类所有成员以及新增的成员,同名的成员被隐藏起来,调用的时候只会调用派生类中的成员。如果要调用基类的同名成员,可以用以下方法:
B public继承 A,且a在A,B中都是public member,则访问A,B各自的a的方法如下:B b(10); cout << b.a << endl; cout << b.A::a << endl;
2. 详细说明
- 构造函数
是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。 - 析构函数
是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行#include <iostream> using namespace std; class Line{ public: void setLength( double len ); double getLength( void ); Line(); // 这是构造函数声明 ~Line(); // 这是析构函数声明 private: double length; }; // 成员函数定义,包括构造函数 Line::Line(void){ cout << "Object is being created" << endl; } Line::~Line(void){ cout << "Object is being deleted" << endl; } void Line::setLength( double len ){ length = len; } double Line::getLength( void ){ return length; }// 程序的主函数 int main( ){ Line line; // 设置长度 line.setLength(6.0); cout << "Length of line : " << line.getLength() <<endl; return 0; } Output: Object is being created Length of line : 6 Object is being deleted
- 拷贝构造函数,常用于
- 通过使用另一个同类型的对象来初始化新创建的对象。
- 复制对象把它作为参数传递给函数。
- 复制对象,并从函数返回这个对象。
class Line{ public: int getLength( void ); Line( int len ); // 简单的构造函数 Line( const Line &obj); // 拷贝构造函数 ~Line(); // 析构函数 private: int *ptr; }; // 成员函数定义,包括构造函数 Line::Line(int len){ cout << "调用构造函数" << endl; // 为指针分配内存 ptr = new int; *ptr = len; } Line::Line(const Line &obj){ cout << "调用拷贝构造函数并为指针 ptr 分配内存" << endl; ptr = new int; *ptr = *obj.ptr; // 拷贝值 } Line::~Line(void){ cout << "释放内存" << endl; delete ptr; } int Line::getLength( void ){ return *ptr; } void display(Line obj){ cout << "line 大小 : " << obj.getLength() <<endl; } int main(){ Line line(10); display(line); return 0; } output: 调用构造函数 调用拷贝构造函数并为指针 ptr 分配内存 line 大小 : 10 释放内存 释放内存
- 友元函数
定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。尽管友元函数的原型有在类的定义中出现,但是友元函数并不是成员函数。友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。如果要声明函数为一个类的友元,需要在类定义中该函数原型前使用关键字 friend。class Box{ double width; public: double length; friend void printWidth( Box box ); friend class otherclassname; void setWidth( double wid ); };
- this指针
每一个对象都能通过 this 指针来访问自己的地址。this 指针是所有成员函数的隐含参数。因此,在成员函数内部,它可以用来指向调用对象。友元函数没有 this 指针,因为友元不是类的成员。只有成员函数才有 this 指针。 - 静态成员
可以使用 static 关键字来把类成员定义为静态的。当成员为静态时,无论创建多少个类的对象,静态成员都只有一个副本。 如果把函数成员声明为静态的,就可以把函数与类的任何特定对象独立开来。静态成员函数即使在类对象不存在的情况下也能被调用,静态函数只要使用类名加范围解析运算符 :: 就可以访问。静态成员函数只能访问静态成员数据、其他静态成员函数和类外部的其他函数。静态成员函数有一个类范围,他们不能访问类的 this 指针。class Box{ public: static int objectCount; // 构造函数定义 Box(double l=2.0, double b=2.0, double h=2.0) { cout <<"Constructor called." << endl; length = l; breadth = b; height = h; // 每次创建对象时增加 1 objectCount++; } double Volume() { return length * breadth * height; } private: double length; // 长度 double breadth; // 宽度 double height; // 高度 };
3.几个需要注意的点
- 一个派生类继承了所有的基类方法,但下列情况除外:
基类的构造函数、析构函数和拷贝构造函数。
基类的重载运算符。
基类的友元函数。 - 多继承,一个子类可以有多个父类,它继承了多个父类的特性。
class <派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>,…{ <派生类类体> };
-
函数重载
在同一个作用域内,可以声明同名函数,这些同名函数的形式参数(指参数的个数、类型或者顺序)必须不同,他们的返回类型可以不同,但注意不能仅通过返回类型的不同来重载函数。 -
运算符重载
重载的运算符是带有特殊名称的函数,函数名是由关键字 operator 和其后要重载的运算符符号构成的。与其他函数一样,重载运算符有一个返回类型和一个参数列表,形如 Box operator<operator>(const Box&);- 例子
Box operator+(const Box& b) { Box box; box.length = this->length + b.length; box.breadth = this->breadth + b.breadth; box.height = this->height + b.height; return box; }
- 不可重载的运算符有:
- . 成员访问运算符
- .*, ->*成员指针访问运算符
- ::域运算符
- sizeof:长度运算符
- ?:条件运算符
- #预处理符号
-
多态
指调用成员函数时,会根据调用函数的对象的类型来执行不同的函数 -
数据抽象
在 C++ 中,使用类来定义我们自己的抽象数据类型(ADT),使用访问标签来定义类的抽象接口。一个类可以包含零个或多个访问标签:- 使用公共标签定义的成员都可以访问该程序的所有部分。一个类型的数据抽象视图是由它的公共成员来定义的。
- 使用私有标签定义的成员无法访问到使用类的代码。私有部分对使用类型的代码隐藏了实现细节。
如果只在类的私有部分定义数据成员,编写该类的作者就可以随意更改数据。如果实现发生改变,则只需要检查类的代码,看看这个改变会导致哪些影响。如果数据是公有的,则任何直接访问旧表示形式的数据成员的函数都可能受到影响。数据抽象有两个重要的优势:
- 类的内部受到保护,不会因无意的用户级错误导致对象状态受损。
- 类实现可能随着时间的推移而发生变化,以便应对不断变化的需求,或者应对那些要求不改变用户级代码的错误报告。
-
数据封装
封装是面向对象编程中的把数据和操作数据的函数绑定在一起的一个概念,这样能避免受到外界的干扰和误用,从而确保了安全。数据封装引申出了另一个重要的 OOP 概念,即数据隐藏。数据封装是一种把数据和操作数据的函数捆绑在一起的机制,数据抽象是一种仅向用户暴露接口而把具体的实现细节隐藏起来的机制。 C++ 通过创建类来支持封装和数据隐藏(public、protected、private)。 -
接口
描述了类的行为和功能,而不需要完成类的特定实现。C++ 接口是使用抽象类来实现的,抽象类与数据抽象互不混淆,数据抽象是一个把实现细节与相关的数据分离开的概念。如果类中至少有一个函数被声明为纯虚函数(如1例子中的virtual int calculate_circumference()=0;),则这个类就是抽象类。
- 一个派生类继承了所有的基类方法,但下列情况除外:
参考:
https://www.runoob.com/cplusplus/cpp-polymorphism.html
https://www.cnblogs.com/tsingke/p/10052445.html