关于面向对象编程对于一个java程序员那是再熟悉不过了,不过对于C++而言相对java还是有很多不同点的,所以全面复习一下。
类
C++ 在 C 语言的基础上增加了面向对象编程,C++ 支持面向对象程序设计。类是 C++ 的核心特性,用户定义的类型。
咱们来新建一个头文件来定义一下类:
其访问修饰符具体含义如下:
-
private:可以被该类中的函数、友元函数访问。 不能被任何其他访问,该类的对象也不能访问。
-
protected:可以被该类中的函数、子类的函数、友元函数访问。 但不能被该类的对象访问。
- public:可以被该类中的函数、子类的函数、友元函数访问,也可以被该类的对象访问。
其中构造函数和析构函数目前还木有实现,其实可以在声明的时候就定义实现,如下:
也可以在cpp中进行实现,新建一个cpp文件:
咱们来使用一下它:
编译运行:
其中类的析构函数是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行(不需要手动调用)。
另外如果想通过构造方法来给成员变量赋值,当然可以跟java一样如下弄:
但是在cpp中还有它独特的地方就是在声明构造时就可以给成员变量进行赋值,表现如下:
此时实例化就得传两个参数了:
常量函数
函数后写上const,表示不会也不允许修改类中的成员。下面来看看:
声明一个给成员变量赋值的方法,没啥问题,但是!!如果给这个函数的后面加一个const修饰,情况就不一样啦,如下:
其错误提示为:
比较简单,也就是如果发现该函数只能读不会写,则可将其声明为常量函数。
友元
类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。
- 友元函数
这个是有别于Java的,假如咱们想通过这个函数来修改类中的私有成员,如下:
此时就需要将这个函数声明为友元函数,具体做法如下:
此时方法就不报错了:
咱们来使用一下:
编译运行:
- 友元类
下面来声明一个友元类:
此时就可以将Teacher声明为Student类中的友元类,如下:
也就是当Teacher声明为Student的友元类时,则Teacher中所有的函数及成员都是Student中的友元了。
静态成员
和Java一样,可以使用static来声明类成员为静态的,当我们使用静态成员属性或者函数时候 需要使用 域运算符 ::,下面咱们以单例模式为例:
在头文件中声明一个Instance类:
其中需要注意的是,对于静态成员是需要进行初始化的,可以在头文件中声明时就初始化,也可以在.cpp中进行,这里以cpp的方式来对成员变量进行初始化,如下:
下面来具体实现getInstance()方法:
我们知道对于Java来说这样写是会有线程安全问题,那对于C++11编译器来说会保证内部静态变量的线程安全的, 当然可以加锁,关于多线程的问题在之后会学习到,这里先不管多线程的情况,下面来使用一下这个单例:
重载函数
C++ 允许在同一作用域中的某个函数和运算符指定多个定义,分为函数重载和运算符重载。
- 函数重载
这个跟java一样,所以举个例子既可: - 操作符重载
这个是C++独有的,C++允许重定义或重载大部分 C++ 内置的运算符 ,函数名是由关键字 operator 和其后要重载的运算符符号构成的 ,重载运算符可被定义为普通的非成员函数或者被定义为类成员函数。
成员函数:
新建一个头文件:
假设要实现这样的一个操作,如下:
接下来操作符重载就是将不可能成为可能,具体做法如下:
此时定义了+号运算符重载之后,之前的对象相加操作就可以正常啦,看下结果:
但是!!!有一个疑问:我们在操作符重载是在栈上申请的一块空间,那出了方法应该就会被回收,如下:
那为啥我们还能在表达式中看到正常的结果呢?
这是因为对于这个表示式底层是存在对象拷贝操作的,如下:
那何以见得呢?咱们可以先定义一个拷贝函数来打印一下是否如猜想:
所以定义一个无参的构造方法:
此时咱们来运行看一下是否调用了两次拷贝构造:
呃~~翻车了么?其实是因为:
非成员函数:
由于程序是在mac上跑的,可见是对返回值是做了RVO优化,从而是看不到拷贝构造函数的调用了。 总之记住一点:本来是要进行两次对象拷贝的,但是编译器做了一些优化最终会减少或者完全看不到对象的拷贝迹象了。
新声明一个类:
接着来声明操作符重载,这里不需要定义到Test2类的内部了,直接在我们调用的外部中来声明,如下:
然后运行看一下结果:
其中允许重载的运算符如下:比如说重载new、delete如下:
void *operator new (size_t size) { cout << "新的new:" << size << endl; return malloc(size); } void operator delete(void *p) { //释放由p指向的存储空间 cout << "新的delete" << endl; free(p); }
继承
跟java一样,类也是存在继承的,范例:
class A:[private/protected/public] B
默认为private继承
A是基类,B称为子类或者派生类
下面来看下具体代码:
那下面来使用一下:
此时涉及到继承的访问修饰符了,由于默认情况下是private继承,所以既使父类的方法是public的子类也无法访问,要访问就得将继承修饰符改一下:
其中涉及到继承修饰符如下:
方式 | 说明 |
---|---|
public | 基类的public、protected成员也是派生类相应的成员,基类的private成员不能直接被派生类访问,但是可以通过调用基类的公有和保护成员来访问。 |
protected | 基类的公有和保护成员将成为派生类的保护成员 |
private | 基类的公有和保护成员将成为派生类的私有成员 |
另外有一点是跟java不同的,java只支持单继承,而c++是支持多继承的,如下:
下面来看一下打印:
此时调用子类的test()会打印啥呢?如下:
而如果子类想调用父类的方法该如何写呢?
编译运行:
那如果将子类的test()方法删掉又会怎样呢?
从错误提示来看是因为在父类中定义有多个test()方法,咱们删掉其中一个基类的test()方法:
此时就正常了,再次编译运行:
从这个就可以看出为啥刚才报基类有多个test()的异常,是因为如果子类没有实现父类的方法,那么就会调用父类的,但是父类定义了两个test()方法,所以编译器不知道调哪个了,所以就报错了。
多态
多种形态。当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。
静态多态(静态联编)是指在编译期间就可以确定函数的调用地址,通过函数重载和模版(泛型编程)实现,那下面来举个例子来体会一下静态多态的表现:
此时打印的是:
动态多态(动态联编)是指函数调用的地址不能在编译器期间确定,必须需要在运行时才确定 ,通过继承+虚函数 实现,下面来看下:
其调用方式不变:
编译运行:
此时就调用的真正类型的方法,基于此,有下面一个原则:
构造函数任何时候都不可以声明为虚函数。
析构函数一般都是虚函数,释放先执行子类再执行父类,如果不声明为虚函数,则在多态中只会调用父类的析构函数了。
另外还有一种纯虚函数,其意义跟java中的abstract抽象方法类似,具体如下:
此时如果运行就会报错:
也很容易理解,跟java的一样,纯虚函数是一定需要在子类中进行实现的,如下:
此时编译就不报错了。
模板
模板是泛型编程的基础
- 函数模板【类似于java中的泛形方法】
函数模板能够用来创建一个通用的函数。以支持多种不同的形參。避免重载函数的函数体反复设计。
下面来使用一下:
其中有个等价的写法:
- 类模板【类似于java中的泛型类】
为类定义一种模式。使得类中的某些数据成员、默写成员函数的參数、某些成员函数的返回值,能够取随意类型
常见的 容器比如 向量 vector 或 vector 就是模板类
基本上跟java类似~