# 基础知识 ## 不带指针的类 ```cpp #ifndef __TESTCLASS_NOPOINT__ #define __TESTCLASS_NOPOINT__ #include 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 ``` ## 带指针的类 ```cpp #ifndef __TESTCLASS_POINTER__ #define __TESTCLASS_POINTER__ #include #include #include 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(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; } ``` ```cpp delete[] ptr; ``` > delete数组时需要使用delete[] > 如果有中括号[],系统知道要删除多个 > 如果没有中括号,系统不知道要删除多个,因此数组中只有第一个执行了析构,其他的都没有析构 > 如果成员对象中没有new出来的指针,其实delete也可以,如果有就会内存泄漏 ![请添加图片描述](img/obj_1.png) ## sataic ```cpp #include 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; } ``` ## 继承—组合—委托 ```cpp #ifndef __Inheritance_Composition_Delegation__ #define __Inheritance_Composition_Delegation__ #include #include #include using namespace std; /** * Composition 复合 * MyQueue 拥有 deque 作为组件,MyQueue因此也拥有deque的功能,所以MyQueue的很多功能可以基于deque改装一下即可 * 构造由内到外:先调用Component的默认构造函数,然后再执行自己的 * 析构由外而内:首先执行自己析构函数,然后再调用Component的析构函数 */ template 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 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 ``` ## 转换函数 ```cpp #include #include 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 ```cpp /** * 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告诉编译器,不能发生相应的隐式类型转换,只能以显示的方式进行类型转换 ## 特化模板和模板偏特化 ```cpp #include #include using namespace std; /** * specialization 模板特化 * 代码来源:https://www.cnblogs.com/dongzhiquan/p/7726379.html */ template class Compare { public: static bool IsEqual(const T &lh, const T &rh) { std::cout << "normal compare" << std::endl; return lh == rh; } }; template <> class Compare { 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 { 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 class C { public: C() { std::cout << "normal special" << std::endl; } }; template class C { public: C() { std::cout << "pointer special" << std::endl; } }; // ---------------------------------- // 数量上不同的偏特化,比如这里我觉得vec对bool需要特殊处理,所以做了对bool偏特化 template class MyVec { public: MyVec() { std::cout << "normal vec" << std::endl; } }; template class MyVec { public: MyVec() { std::cout << "bool vec" << std::endl; } }; int main() { Compare::IsEqual(1.0, 1.0); Compare::IsEqual(1.0f, 1.0f); Compare::IsEqual(1, 1); C obj1; C obj2; MyVec vec1; MyVec vec2; system("pause"); return 0; } ``` ## 模板模板参数 ```cpp template class Container> class XCls { private: Container c; public: }; template using Vst = std::vector>; int main() { // 因为vector需要多个模板参数,平时只给一个是因为有默认模板参数,但是在模板模板参数中只传一个对vector来说会报错,所以用了Vst来代替 XCls mylist1; system("pause"); return 0; } ``` ## 不定参数模板 ```cpp template using Vst = std::vector>; void print() { } template 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 ```cpp std::vector 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; } ```