C++面向对象高级编程
概览
#ifndef COMPLEX_H_
#define COMPLEX_H_
class complex{
private:
double re, im;
public:
complex (double r = 0, double i = 0) : re (r), im (i) {} // 构造函数
complex& operator += (const complex&); // 函数声明
double real () const {return re;}
double imag () const {return im;}
friend complex& _doap1 (complex*, const complex&);
};
#endif // COMPLEX_H_
内联函数 (inline)
函数如果在class body中定义完成,那么就会成为incline函数候选者
double real () const {return re;}
- inline 只适用于函数体内代码简单的函数,不能包含复杂的结构控制语句,如while、switch,并且内联函数本身不能是递归函数
- inline函数仅仅是对编译器的建议,最后能否真正的内联,决定在编译器。
- inline关键字必须与函数定义体放在一起才能使函数成为内联。仅将inline放在函数声明前,不起任何作用。
建议:因为上一条的原因,最好将内联函数的定义放在头文件里。
访问级别
- public 部分调用函数 默认 (这是与struct 最大的不同 struct 是public的)
- private 所有的数据 内部使用的函数
- protect 子类可用
构造函数
complex (double r = 0, double i = 0) // 默认实参
: re (r), im (i) // initialization list 这种写法更高效 这里是初始化 后面是赋值
{
} // 构造函数
- 与类名相同
- 没有返回值
- 可以有多个(重载)
- 创建对象时自动调用
int main() {
complex c1(2, 1);
complex c2;
complex *p = new complex(4);
}
析构函数
不带指针的类大部分不需要析构函数
将构造函数放到
private
区域中
- 单例模式
class A {
public:
static A& getInstance();
void setup();
private:
A();
A(const A& rhs);
};
A& A::getInstance() {
static A a;
return a;
}
A::getInstance.setup();
const
不改变类内的数据的时候,应该+ const
double real () const {return re;}
double imag () const {return im;}
如果不加 const 的话
double real () {return re;}
double imag () {return im;}
如果使用者这样使用
int main() {
const complex c(2, 1);
cout << c.reaol();
cout << c.imag();
}
就会这样报错
main.cpp: 在函数‘int main(int, char**)’中:
main.cpp:8:18: 错误:passing ‘const complex’ as ‘this’ argument discards qualifiers [$
rg/onlinedocs/gcc/Warning-Options.html#index-fpermissive-fpermissive]
8 | cout << c1.real();
| ~~~~~~~^~
In file included from main.cpp:2:
complex.h:12:10: 附注:在调用‘double complex::real()’时
12 | double real () {return re;}
| ^~~~
参数传递
-
值传递
尽量不要传递值 -
引用传递
传引用相当于传指针,但是更加优雅
complex& complex::operator += (const complex& x);
operator << (ostream* os, const complex& x) {
return os << '(' << read(x) << ',' << imag(x) << ')';
}
返回值传递
-
值传递
如果允许,尽量不要传递值 -
引用传递
传引用相当于传指针,但是更加优雅
+=
可以返回引用
complex& complex::operator += (const complex& x) {
this->re += x.re;
this->im += x.im;
return *this;
}
+
就不可以返回引用
complex& complex::operator + (const complex& x) {
complex c(this->re + x.re, this->im + x.im);
return c; // 这样就会返回一个局部变量的引用 是无效的
}
complex.cpp:11:10: 警告:reference to local variable ‘c’ returned [$
Warning-Options.html#index-Wreturn-local-addr-Wreturn-local-addr]
11 | return c; // 这样就会返回一个局部变量的引用 是无效的
| ^
complex.cpp:10:11: 附注:在此声明
10 | complex c(this->re + x.re, this->im + x.im);
| ^
complex complex::operator + (const complex& x) {
complex c(this->re + x.re, this->im + x.im)
return c;
}
return by reference的好处还有传递着无需知道接收者是以reference形式接收
friend(友元)
friend complex& _doap1 (complex*, const complex&);
inline complex& _doapl (complex* ths, const complex& r){
ths->re += r.re;
ths->im += r.im;
return *ths;
}
同一个class的object之间互为友元
默认参数
在使用带有默认参数的函数时有两点要注意:
(1)如果函数的定义在函数调用之前,则应在函数定义中给出默认值。如果函数的定义在函数调用之后,则在函数调用之前需要有函数声明,此时必须在函数声明中给出默认值,在函数定义时可以不给出默认值。
(2)一个函数不能既作为重载函数,又作为有默认参数的函数。因为当调用函数时如果少写一个参数,系统无法判定是利用重载函数还是利用默认参数的函数,出现二义性,系统无法执行。
默认参数一般在函数声明中提供。如果程序中既有函数的声明又有函数的定义时,则定义函数时不允许再定义参数的默认值。
int a=1;
int fun(int);
int g(int x;fun(a)); //OK,允许默认值为函数
默认值不可以是局部变量,因为默认参数的函数调用是在编译时确定的,而局部变量的位置与值在编译时均无法确定。
int main()
{
int i;
void g(int x=i); // error C2587: “i”: 非法将局部变量作为默认参数
return 0;
}
- 由此设置默认参数需要注意2点
- 只能由后往前
- 不能对少于参数个数的函数进行重载
操作符重载与临时对象
c++要求重载函数具有不同的签名。返回类型不是函数签名的一部分。
- 在c++中,操作符其实是一种函数
- this 是一个
指针
#include "complex.h"
inline complex& complex::operator += (const complex& r) {
return _doapl(this, r);
}
inline complex& _doapl (complex* ths, const complex& r){
ths->re += r.re;
ths->im += r.im;
return *ths;
}
临时对象
inline double imag(const complex& x){
return x.imag();
}
inline double real(const complex& x) {
return x.real();
}
complex operator + (const complex& x, const complex& y) {
return complex(real(x) + real(y), imag(x) + imag(y));
}
complex operator + (const complex& x, const double y) {
return complex(real(x) + y, imag(x) + y);
}
complex operator + (const double x, const complex& y) {
return complex(x + real(y), x + imag(y));
}
三大函数(Big Three)
首先,我们来设计一个String
- 拷贝构造
- 拷贝赋值
- 析构函数
#ifndef STRING_H_
#define STRING_H_
class String
{
public:
String(const char* cstr = 0);
String(const String& str); // 拷贝构造
String& operator=(const String& str); // 拷贝复制
~String(); // 类中有指针的类一般需要析构
char* get_c_str() const { return m_data; }; // 对于无需修改数据的函数一定要加上 const !!!
private:
char *m_data;
};
#endif // STRING_H_
拷贝构造
inline String::String(const String& str) {
m_data = new char[strlen(str.m_data) + 1]; // 直接取另一个object的private(兄弟之间互为友元)
strcpy(m_data, str.m_data);
}
拷贝赋值
先将自己的内容清空,然后将目标内赋值给自己
inline String& String::operator=(const String& str) {
if (this == &str) { // 检测自我赋值
return *this;
}
delete[] m_data;
m_data = new char[strlen(str.m_data)+1];
strcpy(m_data, str.m_data);
return *this;
}
析构函数
inline String::~String() {
delete[] m_data;
}
堆 栈 与内存管理
stack
是存在于某个作用域的一块内存空间。例如当你调用函数,函数本身即会形成一个stack用来防止它所接收的参数,以及返回值地址
在函数本体内声明的任何变量,其所使用的内存块都来自于上述的stack
Complex c1(1, 2);
Heap
是由操作系统提供的一块global内存空间,程序可以动态分配
Complex *p = new Complex(3);
对象的生命周期
stack objects的生命期
c1 就是作所谓的stack object,其声生命在作用域结束之后结束,被称为auto object,在离开作用域的时候会自动调用析构函数
static local objects 的生命期
{
static Complex c2(1, 2);
}
c2是所谓的static object 其生命在作用域结束之后依然存在 直到整个个程序结束
global object
class Complex {};
Complex c3(1, 2);
int main() {}
c3就是所谓的global object 其生命在整个程序结束之后才结束。
heap object的生命期
class Complex{};
{
Complex* p = new Complex;
...
delete p;
}
p所知的便是heap object,其生命在它被delete之际结束
class Complex{};
{
Complex* p = new Complex;
}
以上程序出现内存泄露,因为当作用域结束,p所指向的heap object依然存在,但指针p的生命却结束了,作用域之外再也看不到p,也就没机会delete p
new
先分配内存,在调用构造函数
Complex *pc = new Complex(1, 2);
编译器可能会转化为下面这样
1 void* mem = operator new(sizeof(Complex)); // 分配内存
- operator new 内部会调用malloc
2 pc = static_cast<Complex*>(mem); // 转型
3 pc->Complex::Complex(1, 2); // 构造函数
delete
先调用析构函数,在释放memory
String *ps = new String("Hello");
delete ps;
可能转化为以下过程
String::~String(ps); // 析构函数
operator delete(ps); // 释放内存 内部调用free(ps)
动态分配所得的内存块(memory block)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9H8IF6Oh-1636511177779)(img/动态分配内存块.png)]
动态分配所得的array
new type[size] 要搭配 delete []
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WN2MajWT-1636511177781)(img/动态分配内存数组.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mssW51nI-1636511177783)(img/delete[].png)]
补充
static
complex c1, c2, c3;
cout << c.real(); // 相当于 cout << complex::real(&c1); &c1 其实就是this
当函数或数据加上static
关键字后,就脱离了对象,分配到静态数据区
static function 没有 this
class Account {
public:
static double m_rate;
static void set_rate(const double& x) {m_rate = x};
}
// 定义静态数据
double Account::m_rate = 0.022;
int main() {
Account::set_rate(0.033); //. 通过class name 调用
Account a;
a.set_rate(0.027); // 通过object调用
}
将构造函数放到private区
class A {
public:
static A& getInstance() {
return a;
}
private:
A();
A(const A& rhs);
static A a;
...
};
通过public static func 获取 private static object(位于静态区),以实现单例模式
更好的写法
class A {
public:
static A& getInstance();
private:
A();
A(const A& rhs);
...
};
A& A::getInstance() {
static A a; // 不调用就不会产生 一旦产生一直存在
return a;
}
cout
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4guhyqnm-1636511177789)(img/cout补充.png)]
class template 类模板
template<tempname T>
class complex
{
public:
complex(T r = 0, T i = 0) : re (r), im(i) {}
complex& operator += (const complex&);
T real() const { return re; }
T imag() const { return im; }
private:
T re, im;
friend complex& _doapl(complex, const complex&);
}
{
complex<double> c1(2.5, 1.5);
complex<int> c2(2, 6);
}
function template 函数模板
class stone
{
public:
stone(int w, int h, int we) : _w(w), _h(h), _weight(we) {}
bool operator< (const stone& rhs) const {
return _weight < rhs._weight;
}
private:
int _w, _h, _weight;
};
template <class T>
inline const T& min(const T& a, const T& b) {
return b < a ? b : a;
}
int main() {
stone s1(1, 2, 3);
stone s2(2, 3, 4);
stone s3;
r3 = min(r1, r2)
}
namespace
namespace std{
}
#include <iostream>
using namespace std;
int main() {
cin << ...;
}
#include <iostream>
using std::cout;
int main() {
std::cin >> ...;
cout << ...;
}
#include <iostream>
int main() {
std::cout << ...;
}
OOP
Object Oriented Programming
Comnposition 组合
将零件组合成新的产品 have a
template <class T, class Qequence = deque<T> >
class queue {
protected:
Sequence c; // 底层容器
public:
// 一下完全利用c的操作函数完成
bool empty() const {return c.empty();}
size_type size() const {return c.size();}
reference front() { return c.front();}
reference back() {return c.back();}
void push(const value_type& x) { c.push_back(x);}
void pop() { c.pop_front();}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8PpzB5PM-1636511177793)(img/queue组合.png)]
构造与析构
- 构造由内而外
Container::Container() : Component() {}
- 析构由外而内
Container::~Container() { ~Component(); }
注意,编译器只能帮我们调用default构造器,有需要的话需要自己实现
delegation 委托
也就是Composition by reference (Handle / Body)
class StringRep;
class String {
public:
String();
String(const char* s);
String(const String& s);
String &operator=(const String& s);
~String();
private:
StringRep *rep; // pimpl
};
namespace{
class StringRep {
friend class String;
StringRep(const char*);
~StringRep();
int count;
char* rep;
};
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cJPX6b8Y-1636511177795)(img/委托StringRep.png)]
也可以叫做 编译防火墙
这里还可以使用 写时复制
处理共享数据
Inheritance 继承
is a
struct _List_node_base
{
_List_node_base* _M_next;
_List_node_base* _M_prev;
};
template<typename _Tp>
struct _List_node : public _List_node_base
{
_Tp _M_data;
};
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zWIJCsyn-1636511177797)(img/继承构造.png)]
注意:
- base class 的析构函数必须是virtual,否则会出现
undefined behavior
虚函数
- non-virtual函数 不希望派生类重新定义(override 覆写)它
- virtual函数 希望派生类重新定义(override) 它,而且它已经有了默认定义
- pure virtual函数 希望派生类一定要重新定义(override) 它,而且它已经有了默认定义
class Shape{
public:
virtual void draw() const = 0;
virtual void error(const std::string& msg);
int objectID() const;
...
};
class Rectange: public Shape {...};
class Ellipse: public Shape {...};
通过子类调用父类方法,父类的方法再调用子类实现的虚函数(通过this)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8sgtvMUk-1636511177799)(img/虚函数调用过程.png)]
委托 + 继承
观察
class Subject
{
int m_value;
vector<Observer*> m_views;
public:
void attach(Observer* obs) {
m_views.push_back(obs);
}
void set_val(int value) {
m_value = value;
notify();
}
void notify(){
for (int i = 0;i < m_views;i++) {
m_views[i]->update(this, m_value); // 给所有的观察者传递消息
}
}
};
// 这个观察者可以继承
class Observer
{
public:
virtual void update(Subject* sub, int value) = 0;
};
复合
#include <vector>
using namespace std;
class Component
{
int value;
public:
Component(int val) { value = val;}
virtual void add(Component*) {}
};
class Primitive: public Component
{
public:
Primitive(int val):Component(val) {}
};
class Composite : public Component
{
vector <Component*> c;
public:
Composite(int val): Component(val) {}
void add(Component* elem) {
c.push_back(elem);
}
};
Prototype
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D9dHILGE-1636511177802)(img/prototype.png)]
#include <iostream>
using namespace std;
enum imageType{
LSAT,
SPOT,
};
class Image
{
public:
virtual void draw() = 0;
static Image* findAndClone(imageType);
protected:
virtual imageType returnType() = 0;
virtual Image *clone() = 0;
// As each subclass of Image is declared, it registers its protype
static void addProtoType(Image *image) {
_prototypes[_nextSlot++] = image;
}
private:
// addPrototype() saves each registered prototype here
// 声明
static Image* _prototypes[10];
static int _nextSlot;
};
// 定义
Image *Image::_prototypes[];
// 定义
int Image::_nextSlot;
Image *Image::findAndClone(imageType type) {
for (int i = 0;i < _nextSlot;i++) {
if (_prototypes[i]->returnType() == type) {
return _prototypes[i]->clone();
}
}
return nullptr;
}
class LandSatImage: public Image
{
public:
imageType returnType(){
return LSAT;
}
void draw() {
cout << "LSAT::draw" << _id << endl;
}
Image *clone() {
return new LandSatImage;
}
protected:
LandSatImage(int foo) {
_id = _count++;
}
private:
static LandSatImage _landSatImage;
LandSatImage() {
addProtoType(this);
}
int _id;
static int _count;
};
LandSatImage LandSatImage::_landSatImage; // 由于这个类的无参构造函数是私有的
// static obj构造时会注册当前类型
// 所以一旦有这个子类就会唯一一次地去注册这个新类型
int LandSatImage::_count = 1;