面向对象.md 18 KB

基础知识

不带指针的类

#ifndef __TESTCLASS_NOPOINT__
#define __TESTCLASS_NOPOINT__

#include <iostream>
using namespace std;

class TestClass // class head
// class body
{
public:                                                 // 访问级别 public(大部分函数)、private(一般数据放在这里)、proteted
    TestClass(int a = 0, int b = 0)                     // 构造函数重载、参数默认值
    : _a(a), _b(b)                                      // 构造函数的特殊赋值方法(初值列、初始列)这里严格来讲不是赋值而是初始化
    {
        // _a = a; _b = b; // 该方法与初值列赋值作用相同,但时间不同,一个在初始化、一个在赋值阶段。在这里赋值时间点会晚一点
    }        

    // 一般构造函数在public作用域,但也可以放在private中,经典案例就是单例模式
    // TestClass(): _a(0), _b(0) {}   该构造函数与上面的构造函数冲突了

    void setA(const int &a) {this->_a = a;}             // 尽可能多的使用引用传参,并搭配const使用,节省数据传递的消耗
    void setB(const int &b) {this->_b = b;}             // 引用传递一般四个字节,一般对象都大于四个字节所以大部分情况下传递引用可以节省时间

    inline int getA() const {return this->_a;}          // inline 内联函数 最终由编译器决定是否可以构成内联函数
    int getB() const {return this->_b;}                 // 记得在必要的时候加上const,只要没有修改值的函数建议都加上const;否则const对象无法调用非const函数

    /**
     * 友元
    **/

    friend ostream& operator<<(ostream &os, const TestClass &x) // 友元
    {
        os << '(' << x.getA() << " " << x.getB() << ')';
        return os;
    }

    // friend void set_show(int x, A &a);               // 友元函数
    // friend class FriendClass;                        // 友元类
    // friend void FriendClass::set_show(int x, A &a);  // 友元成员函数

    // 两个相同类的Object 互为友元,所以该函数中可以直接访问temp的private作用域数据
    void CloneFromOtherA(const TestClass &temp){
        this->_a = temp._a;
        this->_b = temp._b;
    }

    /**
     * 操作符重载 
    */

    inline TestClass& operator += (const TestClass &x)
    {
        return _doapl(this, x);
    }

protected:
    friend TestClass& _doapl(TestClass* ths, const TestClass& r);    // 返回一个相加的对象

private:
    int _a;
    int _b;
};

/**
 * 定义在类外的操作符重载 
 * https://www.runoob.com/cplusplus/cpp-overloading.html
 * 下面为不可重载操作符
    .:成员访问运算符
    .*, ->*:成员指针访问运算符
    :::域运算符
    sizeof:长度运算符
    ?::条件运算符
    #: 预处理符号
**/
TestClass& _doapl(TestClass* ths, const TestClass& r) 
{
    ths->_a += r._a;
    ths->_b += r._b;
    return *ths;            // 该对象不是在函数体中创建的临时对象,所以可以返回引用回去    
}

inline TestClass operator +(const TestClass &x, const TestClass &y)     // 使用环境为 A + B
{
    // 这里的返回值是函数体中的临时对象,所以返回值不能返回引用
    return TestClass(x.getA() + y.getA(), x.getB() + y.getB());
}

inline TestClass operator +(const TestClass &x, int y)                  // 使用环境为 A + 1
{
    return TestClass(x.getA() + y, x.getB());
}

inline TestClass operator +(int y, const TestClass &x)                  // 使用环境为 1 + A
{
    return TestClass(x.getA() + y, x.getB());
}

inline TestClass operator + (const TestClass &x)                        // 正号 正100
{
    return TestClass(x.getA(), x.getB());
}

inline TestClass operator - (const TestClass &x)                        // 负号 负100
{
    return TestClass(-x.getA(), -x.getB());
}

inline bool operator == (const TestClass &x, const TestClass &y)        // A == B
{
    return x.getA() == y.getA() && x.getB() == y.getB();
}

inline bool operator == (const TestClass &x, const int &y)              // A == 1
{
    return x.getA() == y && x.getB() == 0;
}

inline bool operator == (const int &x, const TestClass &y)              // 1 == A
{
    return x == y.getA() && y.getB() == 0;
}

#endif

带指针的类


#ifndef __TESTCLASS_POINTER__
#define __TESTCLASS_POINTER__

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
/**
 A = B 拷贝赋值           编译器自动生成
 A = String(B) 拷贝构造   编译器自动生成 
 如果Class不存在指针,编译器自动生成问题不大
 如果Class存在指针,则拷贝为浅拷贝,即两个Object的成员对象指针指向同一块地址空间
 所以如果Class带指针,最好不要使用编译器生成的拷贝构造和拷贝赋值
*/
class String
{
public:
    String(const char* cstr = nullptr);
    String(const String& str);
    String& operator =(const String &str);
    ~String();
    char* get_c_str() const {return m_data;}

private:
    char* m_data;
};

String::String(const char* cstr)
{
    if (cstr) {
        m_data = new char[strlen(cstr) + 1];
        strcpy(m_data, cstr);
    }
    else{
        m_data = new char[1];
        *m_data = '\0';
    }
}

String::~String(){
    delete[] m_data;
}

String::String(const String& str)
{
    m_data = new char[strlen(str.m_data) + 1];
    strcpy(m_data, str.m_data); 
}

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;
}

ostream& operator << (ostream& os, const String &str)
{
    os << str.get_c_str();
    return os;
}

/**
 * 建议理解new的底层操作:1.申请内存 2. 类型转换 3. 构造函数
    void *mem = operator new (sizeof(String)); // 底层调用malloc()
    pc = static_cast<String*>(mem);
    pc->String::String("hello");
 * delete的底层操作
    String::~String(pc);   // 调用析构函数
    operator delete(pc);   // 底层调用free()释放内存 
**/

#endif

int main()
{
    String s1("hello");
    std::cout << s1 << endl;
    system("pause");
    return 0;
}
delete[] ptr;

delete数组时需要使用delete[]
如果有中括号[],系统知道要删除多个
如果没有中括号,系统不知道要删除多个,因此数组中只有第一个执行了析构,其他的都没有析构
如果成员对象中没有new出来的指针,其实delete也可以,如果有就会内存泄漏

请添加图片描述

sataic

#include <iostream>
using namespace std;

#ifndef __STATIC_STU__
#define __STATIC_STU__

class StaticStuClass
{
  public:
    static int GetA(const StaticStuClass &x) { return x.m_a; }
    static int GetB(const StaticStuClass &x) { return x.m_b; }

    static void SetC(const int &val) { s_C = val; } // static函数只能处理static的成员对象,普通数据和普通函数不能调用
    static int GetC() { return s_C; }

    static StaticStuClass &getInstance()
    {
        static StaticStuClass self;
        return self;
    }

  public:
    void setup()
    {
    }

    StaticStuClass(int _a = 2, int _b = 2) : m_a(_a), m_b(_b)
    {
    }
    ~StaticStuClass()
    {
    }

    int getA() const { return this->m_a; }
    int getB() const { return this->m_b; }

  private:
    int m_a, m_b;

    static int s_C; // 不管存在多少个StaticStuClass对象,s_C数据只存在一份,m_a、m_b一个对象就有一个
};

int StaticStuClass::s_C = 0; // static的成员对象记得初始化,否则容易报错

#endif

int main()
{
    StaticStuClass cls(1, 2);
    StaticStuClass::SetC(1);
    StaticStuClass::getInstance().setup();
    std::cout << cls.getA() << " " << StaticStuClass::GetA(cls) << std::endl;
    system("pause");
    return 0;
}

继承—组合—委托


#ifndef __Inheritance_Composition_Delegation__
#define __Inheritance_Composition_Delegation__

#include <iostream>
#include <queue>
#include <deque>

using namespace std;

/**
 * Composition 复合 
 * MyQueue 拥有 deque 作为组件,MyQueue因此也拥有deque的功能,所以MyQueue的很多功能可以基于deque改装一下即可
 * 构造由内到外:先调用Component的默认构造函数,然后再执行自己的
 * 析构由外而内:首先执行自己析构函数,然后再调用Component的析构函数
*/
template <typename T>
class MyQueue
{
  public:
	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(); }

  protected:
	std::deque<T> c;
};

/**
 * delegation 委托 Composition By Reference
 * MyString拥有的是一个指针,而不是Component的实体
 * 一种用法:一个StringRep可以同时被多个MyString拥有(可以搭配copy on write写时复制,防止某个MyString对StringRep的修改影响了其他使用该StringRep的MyString)
 * 一种用法:单纯修改MyString中StringRep指针指向的对象就可以修改MyString的功能,而不用修改MyString对外暴露的接口
*/

class StringRep;

class MyString
{
  public:
	MyString();
	MyString(const char *s);
	MyString(const MyString &s);
	MyString &operation = (const MyString &s);
	~MyString();

  private:
	StringRep *rep;
};

class StringRep
{
	freind class MyString;
	StringRep(const char *str);
	~StringRep();
	int count;
	char *rep;
};

/**
 * Inheritance 继承 表示 is-a 的关系
 * 继承分为public、private、proteted三种,对应访问父类的作用域不同
 * 构造由内而外:首先调用基类的构造函数,然后才执行子类的构造
 * 析构由外到内:首先调用子类的析构函数,然后才执行基类的析构
 * 基类的析构函数必须是virtual,否则容易出现内存泄漏
 * non-virtual函数:不希望派生类override的函数
 * virtual函数:希望派生类override的函数,并且给出了默认定义
 * pure virtual 函数:希望派生类override的函数,并且没有给出默认定义
 * 
 * ————————————————————————————————————————————————————————————————
 * 
 * 一种用法:搭建框架,由基类定好一个基本框架,并将变更的地方设定为虚函数/纯虚函数,由使用者继承并实现
**/

class Animal // 基类
{
  private:
  public:
	Animal();
	Animal(int a, int b);
	virtual ~Animal();
	void dead();			// 普通函数
	virtual void run();		// 虚函数
	virtual void eat() = 0; // 纯虚函数
};

class Cat : public Animal // 子类
{
  private:
  public:
	Cat();							  // 调用基类默认的构造函数
	Car(int a, int b) : Animal(a, b); // 调用指定的构造函数
	virtual ~Cat();
};

#endif

转换函数

#include <iostream>
#include <cstring>
using namespace std;

#ifndef __conversion_function__
#define __conversion_function__

/**
 * 转换函数
 * 一般来说转换数据类型到时候不会修改数据内容,所以需要一般要加上const
 * 告诉编译器在需要将该类型转换成指定类型的时候使用该方法转换
 **/

class Fraction
{
  public:
    Fraction(int num, int den = 1) : m_numerator(num), m_denominator(den) {}
    // 转换函数,告诉编译器在需要将Fraction转换成double的时候使用该方法转换
    // 因为是转换函数,其内部的值不能发生修改,所以需要const
    operator double() const
    {
        return (double)(m_numerator * 1.0 / m_denominator);
    }
    operator string() const
    {
        char numerator[10];
        char denominator[10];
        memset(numerator, 0, sizeof(numerator));
        memset(denominator, 0, sizeof(denominator));
        itoa(m_numerator, numerator, 10);
        itoa(m_denominator, denominator, 10);
        return std::string(std::string(numerator) + std::string("/") + std::string(denominator));
    }

  private:
    int m_denominator; // 分母
    int m_numerator;   // 分子
};

#endif

int main()
{
    Fraction f(3, 5);
    // 编译器优先寻找operator (int, Fraction)的操作符重载,没有发现就再查找Fraction能否转成double
    std::cout << 1 + f << std::endl;
    std::cout << (std::string)f << std::endl;
    system("pause");
    return 0;
}

explicit


/**
 * non-explicit-one-argument 只要一个实参就够了的构造函数
 * 下面例子中:没有写oprator * (Fraction, int)的操作符重载也不会报错,因为编译器将4通过一个参数的构造变成Fraction(4, 1)类型
 * 当构造函数前加上explicit关键字时,编译器不会进行隐式构造函数将4变成Fraction(4, 1)
 * 
 * ——————————————————————————————————————————————————————————————————————————————
 * 如果构造函数不存在explicit,且有operator double()转换函数,编译器报错,因为存在二义性即两种方式都可以行得通且没有优先级这么一说
 * 如果构造函数存在explicit,且有operator double()转换函数,编译器不报错,因为只有一条路可走,将Fraction转换成double计算
 * 如果构造函数不存在explicit,且没有operator double()转换函数,编译器不报错,因为只有一条路可走,将4转换成Fraction计算
 **/

class Fraction
{
  public:
    // 这里构造函数只需要一个参数其实也够
    Fraction(int num, int den = 1) : m_numerator(num), m_denominator(den) {}
    //explicit Fraction(int num, int den = 1) : m_numerator(num), m_denominator(den) {}

    Fraction operator*(const Fraction &f)
    {
        return Fraction(f.m_numerator * this->m_numerator, f.m_denominator * this->m_denominator);
    }
    std::string getString()
    {
        char numerator[10];
        char denominator[10];
        memset(numerator, 0, sizeof(numerator));
        memset(denominator, 0, sizeof(denominator));
        itoa(m_numerator, numerator, 10);
        itoa(m_denominator, denominator, 10);
        return std::string(std::string(numerator) + std::string("/") + std::string(denominator));
    }

  private:
    int m_denominator; // 分母
    int m_numerator;   // 分子
};

int main()
{
    Fraction f(3, 5);
    Fraction f2 = f * 4;
    std::cout << f2.getString() << std::endl;
    system("pause");
    return 0;
}

explicit告诉编译器,不能发生相应的隐式类型转换,只能以显示的方式进行类型转换

特化模板和模板偏特化


#include <iostream>
#include <vector>
using namespace std;

/**
 * specialization 模板特化
 * 代码来源:https://www.cnblogs.com/dongzhiquan/p/7726379.html
 */

template <class T>
class Compare
{
  public:
    static bool IsEqual(const T &lh, const T &rh)
    {
        std::cout << "normal compare" << std::endl;
        return lh == rh;
    }
};

template <>
class Compare<double>
{
  public:
    static bool IsEqual(const double &lh, const double &rh)
    {
        std::cout << "float compare" << std::endl;
        return abs(lh - rh) < 10e-6;
    }
};

template <>
class Compare<float>
{
  public:
    static bool IsEqual(const float &lh, const float &rh)
    {
        std::cout << "double compare" << std::endl;
        return abs(lh - rh) < 10e-3;
    }
};

/**
 * partial specialization 模板偏特化
 **/

// 范围上不同的偏特化
// 普通类型和指针类型的特化

template <typename T>
class C
{
  public:
    C()
    {
        std::cout << "normal special" << std::endl;
    }
};

template <typename U>
class C<U *>
{
  public:
    C()
    {
        std::cout << "pointer special" << std::endl;
    }
};

// ----------------------------------
// 数量上不同的偏特化,比如这里我觉得vec对bool需要特殊处理,所以做了对bool偏特化

template <typename T, typename Alloc>
class MyVec
{
  public:
    MyVec()
    {
        std::cout << "normal vec" << std::endl;
    }
};

template <typename Alloc>
class MyVec<bool, Alloc>
{
  public:
    MyVec()
    {
        std::cout << "bool vec" << std::endl;
    }
};

int main()
{
    Compare<double>::IsEqual(1.0, 1.0);
    Compare<float>::IsEqual(1.0f, 1.0f);
    Compare<int>::IsEqual(1, 1);

    C<string> obj1;
    C<string *> obj2;

    MyVec<int, int> vec1;
    MyVec<bool, int> vec2;

    system("pause");
    return 0;
}

模板模板参数


template <typename T,
          template <typename U>
          class Container>
class XCls
{
  private:
    Container<T> c;

  public:
};

template <typename T>
using Vst = std::vector<T, allocator<T>>;

int main()
{
	// 因为vector需要多个模板参数,平时只给一个是因为有默认模板参数,但是在模板模板参数中只传一个对vector来说会报错,所以用了Vst来代替
    XCls<std::string, Vst> mylist1;
    system("pause");
    return 0;
}

不定参数模板

template <typename T>
using Vst = std::vector<T, allocator<T>>;

void print()
{
}

template <typename T, typename... Types>
void print(const T &firstArg, const Types &... args)
{
    cout << firstArg << endl;
    print(args...);
}

int main()
{
    print(1, 2, 3, 4, "123");

    system("pause");
    return 0;
}

ranged-base for

std::vector<int> v{1, 2, 3, 4, 5, 6};
for (auto elem : v)		// 值传递
{
    std::cout << elem << endl;
}
for (auto &elem : v)	// 引用方式取值 
{
    elem *= 2;
}
for (auto elem : v)
{
    std::cout << elem << endl;
}