-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.json
1 lines (1 loc) · 106 KB
/
search.json
1
[{"title":"C++ note (10)","url":"//2023/06/10/C-note-10/","content":"继承和派生 Ⅲ类型兼容性赋值运算的类型兼容性\n\n可以将后代类的对象赋值给祖先类对象,反之不可。\n每个派生类对象包含一个基类部分,这意味着可以将派生类对象当作基类对象使用。--------- -----------| i | <- | i || j | <- | j || x_tmp | <- | x_tmp |--------- | nmember | obj1 ----------- obj2\nbase obj1;y1 obj2;obj1 = obj2; // 把obj2中基类部分的内容赋给obj1obj2 = obj1; // wrong\n\n赋值运算符必须是成员函数实现。因此,赋值运算总是使用左操作数类型的赋值运算。\n因此,子类对象赋值给父类变量是正确的,但会放弃派生扩展的内容。反之,必须从语法层面禁止父类对象赋值子类。\n如果需要完成 obj2 = obj1,必须显式给出 *(base *)(&obj2)= obj1\n\n\n\ny1继承base,且\nbase obj1\ny1 obj2\n\n指向基类对象的指针也可指向公有派生类对象base *p;y1 *p;p = &obj1; // okp1= &obj1; // wrongp = &obj2; // okp1= &obj2; // okp = p1; // ok\n只有公有派生类才能兼容基类类型(上述规则只适用于共有派生)\n\n举例// b.hclass base { public: void display();};class d1 : public base { public: void display();};class d2 : public d1 { public: void display();};\n// b.cpp#include \"b.h\"void base::display() { cout << \"base::display()\" << endl;}void d1::display() { cout << \"d1::display()\" << endl;}void d2::display() { cout << \"d2::display()\" << endl;}void fun(base *ptr) { ptr->display();}int main() { base b; // 声明base类对象 d1 d1; // 声明d1类对象 d2 d2; // 声明d2类对象 base *p;// 声明base类指针 p = &b; // base类指针指向base类对象 fun(p); p = &d1; //base类指针指向d1类对象 fun(p); p = &d2; //base类指针指向d2类对象 fun(p);}\nbase::display()base::display()base::display()// 解释:形参是指针类型,其基类名为base\n类的类型转换Upcasting and DowncastingUpcasting(向 上/基 类型转换)\n\nAssigning a pointer of a derived class type to a pointer of its base class type. Done implicitlygeometricObj *p = new circle(1);circle *p1 = new circle(2);p = p1;\nDowncasting(向 下/派生 类型转换)\nAssigning a pointer of a base class type to a pointer of its derived class type\nDone explicitly using dynamic_castp1 = dynamic cast<circle *>(p);\n\nDynamic CastingThe display function :\n// A function for displaying a geometric objectvoid display(Figure *p) { cout << \"The area is \" << p->get_area() << endl;}\n// A function for displaying a geometric objectvoid display(Figure *p) { Circle* circlePtr = p;//? cout << \"The area is \" << circlePtr ->get_area() << endl;}\nCastsstatic_cast\n\nUsed to convert one data type to another and hands all reasonable castsaverage = (float) hits / (float) at_bats;average = static_cast<float>(hits) / static_cast<float>(at_bats);\nconst_cast\nUsed to cast away constness.#include <iostream>using namespace std;int main() { const int i = 100; const int *p = &i; int *q = const_cast<int*>(p); int j = i; cout << i << endl << j<<endl << *p << endl << *q <<endl; return 0;}\n\n很坑,请按案例。\nConst 转为 非 const,\n(1)c语言中,const 默认解释为 常量或字面量,所以转后也无法修改;\n(2)解释为,编译生成一个temp可变量,给你修改\n\n\n\n#include <iostream>using namespace std;int main() { const int i = 100; const int &p = i; int &q = const_cast<int&>(p); int j = i; cout << i << endl << j << endl << p << endl << q <<endl; return 0;}\nreinterpret_cast\n\nconverts between unrelated types such as an integer to a pointer or a pointer to an unrelated pointer type.int *ip;char *cp;void *gp;cp=ip; ip=cp;cp=gp;gp=cp;cp=reinterpret_cast<char*>(gp);\n\n请打开代码中注释,编译去掉出错的语句,解释出错原因:\n(1)有类型指针间,不能隐式转换,除非向上转换;\n(2)有类型指针可隐式转为通用类型指针,反之不行;\n(3)其他需要显式转换,c++建议用 reinterpret 转指针\n\n\n\ndynamic_cast\n\nUsed for casting across or within inheritance.\nThis cast is used with classes having virtual functions.\n\n多重继承C++支持的多继承多重继承:派生类继承多个基类\n\n代表概念:C既 is a A 又 is a Bclass 派生类名 : 继承控制1, 基类名1, 继承控制2, 基类名2, ... { 成员声名;}\n重复继承:菱形继承\n多重继承特例,base A被派生两次以上\n\n举例——device\nclass device1 { public: device1() : volume(5), powerOn(false) {} device1(int vol, bool onOrOff) : volume(vol), powerOn(onOrOff) {} void showVol() { cout << \"Volume is \" << volume << endl; } protected: int volume; bool powerOn;};class device2 { public: device2() : talkTime(10), standByTime(300), power(100) {} device2(int newTalkTime,int newStandByTime, float powerCent) : talkTime(newTalkTime), standByTime(newStandByTime), power(powerCent) {} void showProperty() { cout << \"The property of the device : \"<< endl; cout << \"talk time: \" << talkTime << \" hours\" <<endl; cout << \"standbyTime: \" << standbyTime << \" hours\" <<endl; } void showPower() { cout <<\" Power: \" << power << endl; } protected: int talkTime; //可通话时间(小时) int standbyTime; //可待机时间(小时) float power; //剩余电量百分比};class deviceNew: public device1, public device2 { public: deviceNew() { weight = 0.56; } deviceNew(float newWeight, int vol, bool onOrOff, int newTalkTime, int newStandbyTime, float powerCent) : device2(newTalkTime, newStandbyTime, powerCent), device1(vol, onOrOff) { weight = newWeight; } float getWeight() { return weight; } private: float weight; // 重量(克)};\nint main() { deviceNew device(0.7, 3, false, 10, 250, 80); //声明派生类对象 // getWeight()函数是DEVICE_NEW类自身定义的 cout << \"The weight of the device : \" <<device.getWeight()<<endl; // showVol()函数是从DEVICE1类继承来的 device.showVol(); // showProperty()函数是从DEVICE2类继承来的 device.showProperty(); return (0);}\nThe weight of the device : 0.7Volume is 3The property of the device :talk time: 10 hoursstandbyTime: 250 hours\n虚基类继承基类时,在继承访问控制前添加保留字 “virtual”。 那么这个基类就是一个虚拟基类。虚拟基类用于共享继承。\n普通基类与虚基类之间的唯一区别只有在派生类重复继承了某一基类时才表现出来。\n若派生类有一个虚基类作为祖先类,则在派生类构造函数中需要列出对虚基类构造函数的调用(否则,调用虚基类的默认构造函数),且对虚基类构造函数的调用总是先于普通基类的构造函数。 \n创建后代类对象时,当该后代类列出的虚基类构造函数被调用,Virtual关键字保证了虚基类的唯一副本只被初始化一次。\n创建派生类对象时构造函数的调用次序:\n\n最先调用虚基类的构造函数;\n其次调用普通基类的构造函数,多个基类则按派生类声明时列出的次序、从左到右调用,而不是初始化列表中的次序;\n再次调用对象成员的构造函数,按类声明中对象成员出现的次序调用,而不是初始化列表中的次序\n最后执行派生类的构造函数。\n\n举例:\nclass base { public: int i;}; class base1: virtual public base { public: int j;};class base2: virtual public base { public: int k;}; class derived: public base1, public base2 { public: int sum;}; int main() { derived obj; // 声明一个派生类对象 obj.i = 3; // 正确:从base继承的i在derived中只有一份 obj.j = 5; // 正确:使用从base1继承的j obj.k = 7; // 正确:使用从base2继承的k return (0);}\n举例:\nclass baseA { public: baseA() { cout << \"This is baseA class.\" << endl; }};class baseB { public: baseB() { cout << \"This is baseB class.\" << endl; }};class derivedA : public baseB, virtual public baseA { public: derivedA() { cout << \"This is derivedA class.\" << endl; }};class derivedB : public baseB, virtual public baseA { public: derivedB() { cout << \"This is derivedB class.\" << endl; }};class derived : public derivedA, virtual public derivedB { public: derived() { cout << \"This is Derived class.\" << endl; }};int main() { derived obj; return (0);}\nThis is baseA class.This is baseB class.This is derivedB class.This is baseB class.This is derivedA class.This is derived class.\n构造顺序:\n\n先基类后成员\n先虚后实\n先左后右\n\n析构顺序:\n\n与构造顺序相反\n\n","categories":["C++","Inheritance and Derivation"],"tags":["C++"]},{"title":"C++ note (11)","url":"//2023/06/19/C-note-11/","content":"多态 Ⅰ虚函数及其意义概念虚函数是一个类的成员函数,前面有关键字 virtual\n\n作用:在公有继承层次中的一个或多个派生类中对虚函数进行重定义。当派生类的对象使用它基类的指针(或引用)调用虚函数时,将调用该对象的成员函数。\n\n“一旦为虚,永远为虚”\n\n含义1:在公有继承层次中,某个类的成员函数申明为 virtual。则其直接或间接派生类中的相同签名的函数,都是虚函数。不论是否用关键字 virtual 再次申明。\n含义2:任何虚函数,都有可能被其派生类再次重新定义,重新定义后依然是虚函数。因此,虚函数既可以被继承,也可以被重新定义。\n\n意义Override and Overwrite覆盖(override)- 修改基类函数定义\n\n基类或非直接基类,至少有一个成员函数被 virtual 修饰\n派生类虚函数必须与基类虚函数有同样签名,即函数名,参数类型,顺序和数量都必须相同。\n\n隐藏(overwrite)- 屏蔽基类的函数定义\n\n派生类的函数与基类的函数同名,但是参数列表有所差异。\n派生类的函数与基类的函数同名,参数列表也相同,但是基类函数没有virtual关键字。\n\n对象指针(引用)被向上转型到基类指针(引用)\n\n覆盖:调用该对象的虚函数\n隐藏:调用基类的函数派生类对象赋值到基类对象\n按基类行为调用该函数\n\n\n\n\n\n作用域\n是否虚函数\n函数名\n函数签名\n\n\n\n重载\n相同\n-\n相同\n不同\n\n\n隐藏/屏蔽\n不同\n否\n相同\n不同/相同\n\n\n覆盖\n不同\n是\n相同\n相同\n\n\n多态的概念概念程序语言中,多态特指一个标识符指代不同类型的数据。或者说,由具体的数据类型决定该标识符的语义。\n满足多态定义的标识符函数重载(Function Overload)- 函数名,它可为不同类型函数(注:函数类型声明为 f(t1,t2…))方法覆盖(Method Override)\n\n有虚函数基类的 指针或引用,它可指代派生类的对象\n\n泛型(Generics)/模板(Template),即“参数化类型”- 模板名,例如 vector 可泛化指代各种类型数据的数组多态标识符必须指派具体的函数或方法以实现规定的语义。\n静态绑定:如果在运行前由编译完成这个指派,称为静态绑定\n动态绑定:如果在运行期间完成这个指派,称为动态绑定\n多态-函数重载多态-动态模型静态类型与动态类型静态类型\n\n对程序进行编译时分析所得到的表达式的类型被称为表达式的静态类型。程序执行时静态类型不会更改。\n\n动态类型\n\n若某个泛左值表达式指代某个多态对象,则其最终派生对象的类型被称为其动态类型。struct B {virtual ~B(){}}; // 多态类型,至少包含一个虚函数struct D:B{} // 多态类型D d; // 最终派生对象B* ptr = &d;// (*ptr)的静态类型为B// (*ptr)的动态类型为D\n\ncppreference: type class\n\n\n\n静态类型——静态绑定重写一个相同函数名、相同参数的函数,会覆盖或隐藏之前继承而来的函数。\n基类指针指向派生类对象,这对于实现多态性的行为是至关重要的。\n动态类型——动态绑定仅需要在基类的成员函数前面加上virtual关键字,就能把一个函数声明为虚函数。该类及其子类都是多态类型。\n当多态类型指针(或引用)调用虚函数时,则产生多态现象,即调用指针所指向的对象的成员函数\n\n在面向对象编程中,多态与继承层次结构相关,使开发人员可以“用通用的方法编程”而不是局限于“用特殊的方法编程”。\n在现实中会经常用到多态,比如“移动move”这一行为在大象和猴子中有不同表现形式,“发薪水”这一行为在教授、副教授、讲师之中可能有不同的计算方式,“绘制draw”这一行为对于三角形、正方形也是不同的。\n特别是我们设计程序库时,根本不可能知道用户行为的具体实现方式。多态为适应这种变化和扩展提供了便利\n\n","categories":["C++","Polymorphism"],"tags":["C++"]},{"title":"C++ note (12)","url":"//2023/06/19/C-note-12/","content":"多态 Ⅱ抽象类纯虚函数与抽象类抽象类语法与语义\n能仅能作为基类指针或引用,因为不可能存在抽象对象\n不能申明抽象类的对象。例如:Animal a\n不能被显式转为抽象类对象。例如:(Animal)dog\n不能作为函数参数类型或者返回的值。例如:func(Animal)\n能申明为指针或引用,指代自己派生类对象\n\n为多态而生!\n抽象类与接口C++没有接口的概念和定义。\nJava接口是由一组函数申明和静态成员构成的特殊类。\nC++可以模仿Java的定义描述接口产生类似的效果\n\n成员函数都是纯虚类\n可以包含静态成员,不包含任何自己的数据\n有虚析构函数,但不需要构造函数\n被虚继承保证唯一,多继承也不会命名冲突\n\n接口的优势:\n\n被继承或被多重继承的派生类必须覆盖实现它的成员\n它自己没有数据和业务逻辑,可以无歧义地被多重继承\n即使没有任何成员,也可作为任意类型对象的特征。例如:SYSUer 抽象类可作为与中大有时空交集的人的共有特征,如学生,老师,毕业生,进修生 …\n\n虚函数没有定义是链接错误。\n虚析构你可能注意到,程序回避了 new 包含虚函数的类 这样的语句,因为\n\n在抽象类与接口案例中,多态类型指针必须动态转换为对象实际类型指针才能正确执行对象析构过程。\nEX1:修改 Abstract_type_interface.cpp,取消 dynamic_cast 观察对象是否被正确析构\n\nC++提供了虚析构函数,解决这个问题\n\n虽然析构函数是不继承的,但若基类声明其析构函数为 virtual,则派生的析构函数始终覆盖它。这使得可以通过指向基类的指针 delete 动态分配的多态类型对象\nEX2:在包含虚函数的基类添加虚析构函数,编译执行,观察多态指针析构过程任何包含虚函数的基类的析构函数必须为公开且虚,或受保护且非虚。否则很容易导致内存泄漏\n\nRTTI运行时类型识别机制RTTI (Run Time Type Identification) 即通过运行时类型识别,程序能够使用基类的指针或引用来检查着这些指针或引用所指的对象的实际派生类型。\n\nC 是一种静态类型语言。其数据类型是在编译期就确定的,不能在运行时更改。\n面向对象程序设计中多态性要求,C++中的指针或引用本身的类型,可能与它实际指代(指向或引用)的类型并不一致,需要在运行时将一个多态指针转换为其实际指向对象的类型。\n\nRTTI 提供了两个非常有用的操作符:typeid 和 dynamic_cast。\n\ntypeid 操作符,返回指针和引用所指的实际类型(type_info 对象);\ndynamic_cast 操作符,将基类类型的指针或引用安全地转换为其派生类类型的指针或引用。\n\n关键词:typeid 运算符查询类型的信息。用于必须知晓多态对象的动态类型的场合以及静态类型鉴别。\n\n在使用 typeid 前,必须包含头文件 \ntypeid 返回 std::type_info 对象,它常用的有 ==、!= 运算符 和 name() 成员\n\n语法1:typeid ( 类型 )\n\n指代一个表示 类型 类型的 std::type_info 对象。若 类型 为引用类型,则结果所指代的 std::type_info 对象表示被引用的类型。。\n\n语法2:typeid ( 表达式 )\n\n若 表达式 为标识某个多态类型(即声明或继承至少一个虚函数的类)对象的泛左值表达式,则 typeid 表达式对该表达式求值,然后指代表示该表达式动态类型的 std::type_info 对象。\n若 表达式 不是多态类型的泛左值表达式,则 typeid 不对该表达式求值,而是由编译静态推导表达式静态类型的 std::type_info 对象\n\n\ntypeid 仅对多态指针表达式求值。其他情况都在编译期完成,其结果可以作为 const 常量表达式。\n具体案例请见:https://qingcms.gitee.io/cppreference/20210212/zh/cpp/language/typeid.html\n高难度C++面试题,往往出在 sizeof 和 typeid 的表达式是否求值等知识点。\n\n关键词:dynamic_cast 类型转换运算符沿继承层级向上、向下及侧向,安全地转换到其他类的指针和引用。\n语法:dynamic_cast < 新类型 > ( 表达式 )\n若转型成功,则 dynamic_cast 返回 新类型 类型的值。若转型失败,\n\n且 新类型 是指针类型,则它返回该类型的空指针。\n且 新类型 是引用类型,则它抛出与类型 std::bad_cast 的处理块匹配的异常。\n\n\n\n侧向是中高级理论题和面试题。\n指针和引用不同行为,是理论题的常见考点。\n注意,抽象基类一般有虚析构\n\n关键字:final(C++11)指定某个虚函数不能在子类中被覆盖,或者某个类不能被子类继承。官方例子\nstruct Base { virtual void foo();}; struct A : Base { void foo() final; // Base::foo 被覆盖而 A::foo 是最终覆盖函数 void bar() final; // 错误: bar 不能为 final 因为它非虚}; struct B final : A { // struct B 为 final void foo() override; // 错误:foo 不能被覆盖,因为它在 A 中是 final}; struct C : B { // 错误:B 为 final};\n多态的实现原理","categories":["C++","Polymorphism"],"tags":["C++"]},{"title":"C++ note (13)","url":"//2023/06/19/C-note-13/","content":"模板泛型编程泛型编程(generic programming)\n\n独立于任何特定数据类型的编程,这使得不同类型的数据(对象)可以被相同的代码操作\n\n在 C++ 中,使用模板(template)来进行泛型编程,包括\n\n函数模板(Function template)\n类模板(Class template)\n\n当从通用代码创建实例代码时,具体数据类型才被确定\n泛型编程是一种编译时多态性(静态多态)。其中,数据类型本身是参数化的,因而程序具有多态性特征\n实例化实例化(Instantiation):由编译器将通用模板代码转换为不同的实例代码的过程称为实例化\n函数模板(Function template)概念用相同的处理过程,处理不同类型的数据\n\n减少代码\n甚至能处理编程时未知的数据类型\n已知函数,类,未知处理的数据类型,模板\n已知基类的虚函数,未知派生类的具体实现,覆盖(多态)\n已知函数功能,未知具体参数类型与组合,重载\n\n\n\n举例void swap(int& v1, int& v2) { int tmp; tmp = v1; v1 = v2; v2 = tmp;}\nvoid swap(double& v1, double& v2) { double tmp; tmp = v1; v1 = v2; v2 = tmp;}\nvoid swap(string& v1, string& v2) { string tmp; tmp = v1; v1 = v2; v2 = tmp;}\n\n此三个函数除了所处理对象的类型不同之外,代码几乎完全相同。即,三个函数功能类似。以下函数模板可以涵盖以上三函数的作用:\ntemplate<typename T>void swap(T& v1, T& v2) { T tmp; tmp = v1; v1 = v2; v2 = tmp;}\n\n注意:程序有些 bug。当 T 存在默认构造函数时,编译错误。\n正确的写法: T tmp = v1; 即使用 copy 构造,来避免使用默认初始化\n\n一般形式template<模板形参表> 返回值类型 函数名 (形参列表) { 函数体}\n\n\n模板形参表不能为空\n形参列表必须包含模板形参表中出现的所有形参\n\n模板形参表的形式:\n\ntypename 模板形参1, typename 模板形参2, …\n\n实例化(instantiation)函数模板的使用形式与普通函数调用相同。\nint main() { std::string s1(\"rabbit\"), s2(\"bear\"); int iv1 = 3, iv2 = 5; double dv1 = 2.8, dv2 = 8.5; // 调用函数模板的实例 swap(string&, string&) swap(s1, s2); // 调用函数模板的实例 swap(int&, int&) swap(iv1, iv2); // 调用swap的实例 swap(double&, double&) swap(dv1, dv2);}\n\n调用函数模板的过程:\n\n模板实参推断(template argument deduction):编译器根据函数调用中所给出的实参的类型,确定相应的模板实参\n函数模板的实例化(instantiation):模板实参确定之后,编译器就使用模板实参代替相应的模板形参,产生并编译函数模板的一个特定版本(称为函数模板的一个实例(instance))(注意:此过程中不进行常规隐式类型转换)\n\n显示(全)模板特化\n在模板当中有些特殊的类型,当想要针对特殊的类型进行一些特殊的操作,这时候就可以用模板的特化\n在正常的模板下面接着编写代码,写一个空的template<>然后写个具体的函数代码来补充\n如实例所示,当传入的实参类型是int类型,就执行模板的特化部分,而非int类型执行正常的模板推断\n\ntemplate <typename T> void swap(T& v1, T& v2) { T tmp; tmp = v1; v1 = v2; v2 = tmp;}template <> void swap(int& v1, int& v2) { int tmp; tmp = v1; v1 = v2; v2 = tmp; v1 += 10; v2 += 10;}\n\n模板重载对函数模板进行重载:\n\n定义名字相同而函数形参表不同的函数模板\n或者定义与函数模板同名的非模板函数(正常函数),在其函数体中完成不同的行为\n\n编译器是如何确定调用的是这么多同名函数中的哪一个呢?\n如何确定调用哪个函数函数调用的静态绑定规则(重载协议):\n\n如果某一同名非模板函数(指正常的函数)的形参类型正好与函数调用的实参类型匹配(完全一致),则调用该函数。否则,进入第2步\n如果能从同名的函数模板实例化一个函数实例,而该函数实例的形参类型正好与函数调用的实参类型匹配(完全一致),则调用该函数模板的实例函数。否则,进入第3步\n在步骤2中:首先匹配函数模板的特化,在匹配非指定特殊的函数模板\n\n\n对函数调用的实参进行隐式类型转换后与非模板函数再次进行匹配,若能找到匹配的函数则调用该函数。否则,进入第4步\n提示编译错误\n\n// 函数模板demoPrinttemplate <typename T>void demoPrint(const T v1, const T v2){ cout << \"the first version of demoPrint()\" << endl; cout << \"the arguments: \" << v1 << \" \" << v2 << endl;}// 函数模板demoPrint的指定特殊template <>void demoPrint(const char v1, const char v2){ cout << \"the specify special of demoPrint()\" << endl; cout << \"the arguments: \" << v1 << \" \" << v2 << endl;}// 函数模板demoPrint重载的函数模板template <typename T>void demoPrint(const T v){ cout << \"the second version of demoPrint()\" << endl; cout << \"the argument: \" << v << endl;}// 非函数模板demoPrintvoid demoPrint(const double v1, const double v2){ cout << \"the nonfunctional template version of demoPrint()\" << endl; cout << \"the arguments: \" << v1 << \" \" << v2 << endl;}/* 函数调用 */string s1(\"rabbit\"), s2(\"bear\");char c1('k'), c2('b');int iv1 = 3, iv2 = 5;double dv1 = 2.8, dv2 = 8.5;// 调用第一个函数模板demoPrint(iv1, iv2);// 调用第一个函数模板的指定特殊demoPrint(c1, c2);// 调用第二个函数模板demoPrint(iv1);// 调用非函数模板demoPrint(dv1, dv2);// 隐式转换后调用非函数模板demoPrint(iv1, dv2);\n/* 结果 */the first version of demoPrint()the arguments: 3 5the specify special of demoPrint()the arguments: k bthe second version of demoPrint()the argument: 3the nonfunctional template version of demoPrint()the arguments: 2.8 8.5the nonfunctional template version of demoPrint()the arguments: 3 8.5\n结论:函数模板是不进行隐式转换的,只有非函数模板才进行隐式转换\n类模板使用情景:定义可以存放任意类型对象的通用容器类\n\n定义一个栈(stack)类,既可用于存放int型对象,又可用于存放float、double、string…甚至任意未知类型的元素\n定义一个队列(queue)类,即可用于存放int型对象,又可用于存放float、double、string…甚至任意未知类型的元素\n\n实现方式:为类声明一种模板,使得类中的某些数据成员、某些成员函数的参数、某些成员函数的返回值,能取任意类型(包括基本类型和用户自定义类型)\n定义方式类模板的一般形式:\ntemplate <模板形参表>class 类模板名 { 类成员声明 ...}\n在类模板外定义成员函数的一般形式:\ntemplate <模板形参表>返回值类型 类模板名<模板形参名列表>::函数名(函数形参表) { 函数实现 ...}\n\n其中模板形参表的形式为:template <typename 类型参数1, typename 类型参数2, …>\n(注:模板形参每项是非类型形参、类型形参、模板形参之一。)\n\n示例:链表实现的栈类模板类模板的实例化\n类模板是一个通用类模型,而不是具体类,不能用于创建对象,只有经过实例化后才得到具体类,才能用于创建对象\n实例化的一般形式:\n类模板名 < 模板实参表 >\n\n\n模板实参是一个实际类型,如int,double等\n一个类模板可以实例化为多个不同的具体类\nStack stack_int\nStack stack_double\nStack stack_string\n\n\n\n类模板的文件组织形式\n一般而言,调用函数时,编译器只需要看到函数的声明即可。所以可以把函数的声明放在 .h 文件中,实现在 .cpp 的实现文件中,使用函数的地方#include 函数的 .h 文件即可\n\n对于模板则不同,要进行实例化,编译器必须能够访问模板定义的源代码\n\n为了在模板中也实现一般的声明定义分离,C++提供了两种模板的编译模型:\n\n包含编译模式(inclusion compilation model)\n\n要求:在函数模板或类模板成员函数的调用点,相应函数的定义对编译器必须是可见的\n实现方式:在头文件中用#include包含实现文件(也可将模板的实现代码直接放在头文件中)//genericStack.h#ifndef GSTACK_H#define GSTACK_H类模板的定义和实现代码#endif \n//client.cpp客户代码#include “genericStack.h”int main(){ Stack<int> stack;\t for (int i = 1; i < 9; i++) \tstack.push(i);}\n\n\n分离编译模式(separate compilation model)\n\n要求:声明和定义分离,程序员在实现文件中使用保留字export告诉编译器,需要记住哪些模板定义。\n不是所有编译器都支持该模式\n\n\n\n非类型模板形参\n两类模板形参:类型模板形参和非类型模板形参非类型模板形参:\n相当于模板内部的常量\n形式上类似于普通的函数形参\n对模板进行实例化时,非类型形参由相应模板实参的值代替\n对应的模板实参必须是编译时常量表达式\n\n\n\n示例1:以数组实现的栈类模板示例2:打印函数不使用非模板形参实现template <typename T>void printValues(T* arr, int N) { for (int i =0; i != N; ++i) cout<< arr[i] << endl;}int main(){ int intArr[6] = {1, 2, 3, 4, 5, 6}; double dblArr[4] = {1.2, 2.3, 3.4, 4.5}; // 生成函数实例printValues(int*, 6) printValues(intArr, 6); // 生成函数实例printValues(double*, 4) printValues(dblArr, 4); return 0;}\n","categories":["C++","Template"],"tags":["C++"]},{"title":"C++ note (14)","url":"//2023/07/28/C-note-14/","content":"STLSTL介绍\nC++标准库的一部分\n\nSTL的称呼是历史原因导致的,目前的标准中已经没有STL字眼\n\n在C++20标准中,STL指的是的如下三章所定义的库:\n\n\n\n容器库(Containers library, Chap. 26)\n迭代器库(Iterators library, Chap. 27)\n算法库(Algorithms library, Chap. 28)\n\nSTL的历史1993年,Alex Stepanov开发出STL的原型(Generic C++ Components)。后被C++标准委员会采纳为C++标准的一部分,采纳时的名称叫The Standard Template Library\nreference: http://stepanovpapers.com/Stepanov-The_Standard_Template_Library-1994.pdf\nreference: https://www.stroustrup.com/hopl-almost-final.pdf\nC++11关键字:auto 和 decltype基本概念:容器与迭代器容器顺序容器序列式容器类型序列式容器的构造函数容器的构造函数:实例访问序列式容器中的元素迭代器在序列式容器中插入元素在序列式容器中删除元素序列式容器的比较操作序列式容器的容量操作序列式容器的赋值和交换关联容器std::pairmap的构造函数\n向map中插入元素\n在map中查找元素\n在map中删除元素\n\nmultimapset适配器\n迭代器算法","categories":["C++","STL"],"tags":["C++"]},{"title":"C++ note (2)","url":"//2023/05/10/C-note-2/","content":"数据抽象和类 Ⅰ基本概念抽象Data Abstraction:只关心该数据“是什么”“如何使用”,而不关心其如何运作。Control Abstraction:只关心该行为能实现什么,而不关心其具体实现方法。\n抽象数据类型在程序中,称被抽象的数据,为抽象数据类型(Abstract Data Type, ADT)\n一种ADT应具有:\n\n说明部分:描述数据值的特征和作用于这些数据之上的操作,用户仅需明白其说明,无需知晓内部实现。\n实现部分。\n\n抽象数据类型转化把DATE设计为一种数据类型。\n\n内部包含年月日等数据以及在这些数据上可进行的操作。\n用户利用DATE就可以定义多个变量。\n用户可调用每个变量中公开的操作,但无法直接访问每个变量中被隐藏的内部数据。\n用户也无需关心变量中各操作的具体实现。\n于是DATE就是一种封装好的数据类型。这就达到了信息隐藏和封装的目的。\n\n结构体 类 对象C 中的结构体C 数组允许定义可存储相同类型数据项的变量,结构是 C 编程中另一种用户自定义的可用的数据类型,它允许您存储不同类型的数据项。结构用于表示一条记录,我们继续以上面的日期为例,我们可能会关心:\n\nYear\nMonth\nDay\n\n但C语言中的 struct 只能包含变量,不能包括实现ADT当中操作的函数\nstruct Date { int year; int month; int date;} date;void set (int, int, int);int getYear();int getMonth();int getDay();void print();void increment();void decrement();\nC++ 对象&类新增面向对象编程\nclass classname { Access specifiers: Date members/variables;//变量 Member fn() {}//方法};//分号结束一个类\n类的示例class Date { private: int year; int month; int day; public: void set(int, int, int); int flag; int getYear() const; int getMonth() const; int getDay() const; void print() const; void increment(); void decrement();};\n在{}中列出类的成员。类的成员包括:\n\n数据成员:一般说来,数据成员是需要隐藏的对象;即外部的程序是不能直接访问这些数据的,应该通过函数成员来访问这些数据。所以一般情况下,数据成员通过关键字 private 声明为私有成员(private member)。\n函数成员:通过关键字 public 声明为公有成员(public member)。外部程序可以访问共有成员,但无法访问私有成员。\n对于类的使用者(用户代码),只需要获得DATE.h,即可调用类对象的公有函数访问其内部的数据成员。使用者无法直接访问私有成员,无需知晓公有函数的内部实现。\n\n结构体 v.s. 类C 的 struct 只能包含变量,而 C++ 的 class 还可以包含函数。set() 是用来处理成员变量的函数,在 C 中,我们将它放在了 struct Date 外部,和成员变量是分离的;而在 C++ 中,我们将它放在了 class Date 内部,使它和成员变量聚集在一起,看起来更像一个整体。\n对象通过结构体定义出来的变量传统上叫变量,而通过类定义出来的变量有了新的名称,叫做对象(Object)。\nDate date;\n成员函数类的成员函数指,把定义和原型写在类定义内部的函数,就像类定义中的变量一样。\n类成员函数是类的一个成员,它可以操作类的任意对象,可以访问对象中的所有成员。\n成员函数可以定义在类定义内部,或者单独使用范围解析运算符 :: 来定义。在类定义中定义的成员函数把函数声明为内联的,即便没有使用 inline 标识符。\nclass Date { public: void set(int y, int m, int d) { year = y; month = m; day = d; }......};\nclass Date { public: void set(int, int, int);......};void Date::set(int y, int m, int d) { year = y; month = m; day = d;}\n预处理命令在 DATE.cpp 文件开头需要加入预处理命令\n#include \"DATE.hpp\"\n这是因为在 DATE.cpp 中要用到用户自定义的标识符 DATE ,而它的定义在 DATE.hpp 中。\n在 DATE.hpp 中,各函数原型是在 {} 中的。根据标识符的作用域规则,它们的作用范围仅在类定义中,而不包括 DATE.cpp 。因此在 DATE.cpp 中需要利用作用域解释运算符 :: 来指明这里的函数是类 DATE 里的成员函数。\nDATE.cpp 中有时还包括 DATE 内部要使用到的函数,例如daysInMonth。\n\n这种函数并非对外公开供用户使用,因此可以将其声明为私有成员。\n\n若在该函数中没有涉及该类的数据成员,则无需将它们声明为类的成员。\n\n\n调用成员函数和成员变量是在对象上使用点运算符(.),这样它就能操作与该对象相关的数据\ndate.flag = 1;date.set(2022,1,30);\n类的静态成员静态(static)成员是类的组成部分但不是任何对象的组成部分\n\n通过在成员声明前加上保留字 static 将成员设为static(\n在数据成员的类型前加保留字 static 声明静态数据成员;\n在成员函数的返回类型前加保留字 static 声明静态成员函数)\n\n\nstatic成员遵循正常的公有/私有访问规则。\nC++ 程序中,如果访问控制允许的话,可在类作用域外直接(不通过对象)访问静态成员(需加上类名和 :: )\n静态数据成员具有静态生存期,是类的所有对象共享的存储空间,是整个类的所有对象的属性,而不是某个对象的属性。\n与非静态数据成员不同,静态数据成员不是通过构造函数进行初始化,而是必须在类定义体的外部再定义一次,且恰好一次,通常是在类的实现文件中再声明一次,而且此时不能再用 static 修饰。\n静态成员函数不属于任何对象 \n静态成员函数没有 this 指针\n静态成员函数不能直接访问类的非静态数据成员,只能直接访问类的静态数据成员//date.hppclass Date { private: static int count; ... public: static void getCount(); ...};\n//date.cppint Date::count = 0;//必须在类外定义体的外部再定义一次void Date::getCount() { return count;}\n\n","categories":["C++","ADT and class"],"tags":["C++"]},{"title":"C++ note (3)","url":"//2023/05/27/C-note-3/","content":"数据抽象和类 ⅡC++ 新增新类型:boolC :\n\n没有 bool 类型#define true = 1#define false = 0\nC99 定义了 _Bool 类型,并通过 stdbool.h 实现与 C++ 兼容\n\nC++:\n\n定义了三个关键字:bool,true,false\n当显式(例 (bool)7)或隐式(例 bool b = 7;)转为 bool 类型时\n0 值转为 false\n非 0 值转为 truecout << true //输出 1cout << false //输出 0\n\n\n\n形参:voidC:\n\nfn(); 没有声明形参表示函数的形参不确定\n没有参数,则必须显式声明 fn(void);\n\nC++:\n\nfn(); 等价于 fn(void);\n使用 … 表示可变参数int printx(const char* fmt, ...);// 能以一个或多个实参调用:printx(\"hello world\");printx(\"a = %d b = %d\", a, b);\n\n新特性函数重载背景:\n\n在开发中,需要的函数功能类似,但参数数量或类型不同C 的解决:\n必须申明两个函数(不重名)C++ 的解决:\n声明同名函数,但参数类型不同:void swap(int& a, int& b);void swap(double& a, double& b);\n\nauto 用于函数重载void swap(int& a, int& b) { auto temp = a; a = b; b = temp;}void swap(double& a, double& b) { auto temp = a; a = b; b = temp;}int main(void) { int i = 0; int j = 1; double p =0.1; double q = 1.1; swap(i, j); swap(p, q);}\n让编译推导决定使用哪个函数\n事实上,编译器会将函数名、参数数量、参数类型编译为唯一的内部函数名\n\nswap_int_int\nswap_double_double\n\n是不同的函数签名\n默认实参C:\n\n不支持函数默认参数和值\n\nC++:\n\n默认参数只能定义在参数右边\n一个声明和实现可匹配多个函数\nfn() fn(int) fun(int, float) fn(int, float, char)void fn(int n = 1, float b = 1.2, char c = '@'); //实现void fn(int n, float b, char c){ cout << n << \", \" << b << \", \" << c << endl;}int main(){ fn(); //func(1,1.2,'@') fn(10); //func(10,1.2,'@') fn(20, 9.8); //func(20,9.8,'@') fn(30, 3.5, '#'); //func(30,3.5,'#') return (0);}\n\n\n\n字符串类型:stringC:\n\nC 字符串是 char* 类型,是以 '\\0' 字符结束的字符数组。\n在 C++ 中处理 C 字符串,使用 #include <cstring>\n\nC++:\n\nstring 是类\n作为区别,用 cstring 称 C 字符数组string s1; // 默认构造string s2 = \"c plus plus\"; // 用cstring构造string s3 = s2; // 用同类对象构造string s4 (5, 's'); // 用int,char作为参数构造\n\n访问控制公有成员\n公有成员在客户端可以任意访问。\n\n公有数据成员不需要通过公有函数成员访问,其优点是使用方便,缺点是可能会破坏封装的逻辑一致性。\n\n\n私有成员\n私有成员变量或函数在类的外部是不可访问的,甚至是不可查看的。只有类和友元函数可以访问私有成员。\n\n默认情况下,class的所有成员都是私有的。\n\n实际操作中,一般会在私有区域定义数据,在公有区域定义相关的函数,以便在类的外部也可以调用这些函数,保持对象内部状态一致\n\n\n构造/析构无参构造函数\n类的构造函数是类的一种特殊的成员函数,每次创建类的新对象时执行它完成初始化等逻辑。\n\n构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回 void。\n\n如果用户没有自定义构造函数,则编译会自动生成一个默认构造函数\n\nC++11 以后建议申明时写含参形式\nclass date { private: int year; int month; public: date(int = 2023, int = 1);};date::date(int y, int m) { year = y; month = m;}\n\n含参构造函数\n构造函数也可以带有参数。这样在创建对象时就可使用参数构造对象。\n\n用户一旦定义了构造函数,编译器就不再自动添加默认构造函数。这时调用无参构造函数会报错\n\n构造函数也能使用默认实参。这样可以减少构造函数重载的数量。\n\n\n析构函数\n类的析构函数是类的一种特殊的成员函数,它会在对象被释放前执行。\n\n析构函数的名称与类的名称是相同的,只是在前面加 ~ 作为前缀,不会返回任何值,也不能带有参数。\n\n析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。\n\n析构函数不能直接调用\n\n\nthis 指针注意点\n在 C++ 中,每一个对象都能通过 this 指针来访问自己的地址。this 指针是所有成员函数的隐含参数。因此,在成员函数内部,它可以用来指向调用对象。\n当成员函数参数与成员数据重名时,必须使用 this 访问成员数据。\n只有动态成员函数才有 this 指针。\n友元函数没有 this 指针,因为友元不是类的成员。\nstatic 成员不能使用 this,应使用 ::。\n-> 是指针取成员运算\n\n示例class box { public: // 构造函数定义 Box(double l = 2.0, double w = 2.0, double h = 2.0) { cout << \"Constructor called.\" << endl; length = l; width = w; height = h; } double volume() { return length * width * height; } int compare(Box box) { return this->Volume() > box.Volume(); } private: double length; double width; double height;};\nint main(void) { Box box1(3.3, 1.2, 1.5); // Declare box1 Box box2(8.5, 6.0, 2.0); // Declare box2 if(box1.compare(box2)) { cout << \"box2 is smaller than box1\" << endl; } else { cout << \"box2 is equal to or larger than box1\" << endl; } return (0); }\nOutput:\nConstructor called. Constructor called. Box2 is equal to or larger than Box1 \n","categories":["C++","ADT and class"],"tags":["C++"]},{"title":"C++ note (4)","url":"//2023/05/27/C-note-4/","content":"数据抽象和类 Ⅲ动态内存和类C/C++ 内存空间分布\n内存分区\n\n\n\n\nText–只读、共享,操作系统管理\n\n是对象文件或内存中程序的一部分,其中包含可执行指令(函数实现,库实现,字符串等资源)。文本段在堆栈的下面,是防止堆栈溢出覆盖它。\n\n通常代码段是共享的,对于经常执行的程序,只有一个副本需要存储在内存中;代码段是只读的,以防止程序以外修改指令。\n\n\n\nInitialized data\n\n通常称为数据段,是程序的虚拟地址空间的一部分,它包含程序员初始化的全局变量和静态变量以及常量,可以进一步划分为只读区域和读写区域。\n\n\nUnintialized data–内核初始化为0\n\n通常称为 bss 段\n\n\nHeap–程序员管理\n\n堆是动态内存分配通常发生的部分(动态变量(对象))\n\n内存分配由低到高,分配方式类似于数据结构的链表。堆区域从BSS段的末尾开始,并从那里逐渐增加到更大的地址。\n\n堆是由程序员自己分配的,或程序结束后由操作系统自动回收。堆区域由所有共享库和进程中动态加载的模块共享。(malloc和new从堆区分配内存)\n\n\n\nStack–编译器分配管理\n\n栈是存放自动变量,以及函数调用时保存的信息的部分(自动变量(对象)、函数参数)\n\n每当进行函数调用时,函数的实参和返回地址以及调用者的上下文环境会被存放在栈中;栈区由编译器自动分配,从高地址向低地址扩展。\n\n在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。静态变量是不入栈的。当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。\n char *s1 = \"Literal\"; // 文字在代码区,仅分配了字符指针// s1[0] = 'I'; // Segmentation fault \n char s2[] = \"Initial Literal\"; // 分配数组空间\n ptr_type* ptr_name = &((ptr_type){2,3}); // 分配结构空间及指针\n\n\n\n堆 栈 的性能区别\n写入方面:入栈比在堆上分配内存要快,因为入栈时操作系统无需分配新的空间,只需要将新数据放入栈顶即可。相比之下,在堆上分配内存则需要更多的工作,这是因为操作系统必须首先找到一块足够存放数据的内存空间,接着做一些记录为下一次分配做准备。\n\n读取方面:得益于 CPU 高速缓存,使得处理器可以减少对内存的访问,高速缓存和内存的访问速度差异在 10 倍以上!栈数据往往可以直接存储在 CPU 高速缓存中,而堆数据只能存储在内存中。访问堆上的数据比访问栈上的数据慢,因为必须先访问栈再通过栈上的指针来访问内存。\n\n\n因此,处理器处理和分配在栈上数据会比在堆上的数据更加高效。\n内存分区的意义不同区域存放的数据,赋予不同的生命周期,给编程更大的灵活性。\n\nC 动态变量(对象)管理正确使用堆空间\n必须使用 #include <stdlib.h>\nvoid* malloc(size_t) // 申请空间\nvoid free(void*) // 释放空间\n申请的空间必须释放,否则会导致内存泄漏\nfree 后再使用或释放指针,行为不可预测。typedef struct { int x; int y; } Point;int main(void) { // 分配变量或一维可变数组 Point* p1 = malloc(sizeof(Point) * 10); // 分配二维数组(n,m是常数) point (*p2)[2][3] = malloc(sizeof(Point) * 6); // 分配数组的数组 int n = 2; int m = 3; Point **p3 = malloc(sizeof(Point *) * n); for (size_t i = 0; i < n; i++) { p3[i] = malloc(sizeof(Point) * m); } // do somesthing free(p1); free(p2); // 必须先释放行数组 for (size_t i = 0; i < n; i++) { free(p3[i]); } free(p3); return (0);}\n\nnew 和 delete 、动态创建变量/数组、内存泄漏、 = defaultC++ note (1)\n","categories":["C++","ADT and class"],"tags":["C++"]},{"title":"C++ note (6)","url":"//2023/06/01/C-note-6/","content":"运算符重载 Ⅰ概念C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有返回值类型,函数名和参数列表。\n重载的运算符可以理解为带有特殊名称的函数,函数名是由关键字 operator 和其后要重载的运算符符号构成的。\nopand1 op opand2 -> op(opand1, opand2)\nA + B -> operator + (A, B) -> add(A, B)\n方法运算符重载的两种方法:\n\n类成员函数运算符重载\nreturn_type class_name::operator op(operand2) {}\n重载二元运算符时,成员运算符函数只需显式传递一个参数(即二元运算符的右操作数),而左操作数则是该类对象本身,通过 this 指针隐式传递。\n重载一元运算符时,成员运算符函数没有参数,操作数是该类对象本身,通过 this 指针隐式传递\n\n\n友元函数运算符重载\nreturn_type operator op(operand1, operand2) {}\n\n\n\nA + B -> A.operator + (B)\n-A -> A.operator - ()\n可重载运算符\n\n\n\n运算符\n操作符\n\n\n\n双目\n算术运算关系运算符逻辑运算位运算\n+(add), -(minus), *(times), /(divide), %(mod)==, !=, <(less), >(more), <=, >=||, &&, ! | , &, ~, ^, <<, >>\n\n\n\n单目运算\n+(pos), -(neg), *(ptr), &(addr)\n\n\n\n自增自减运算\n++, --\n\n\n\n赋值运算\n=, +=, -=, *=, /=, &=, |=, ^=, <<=, >>=\n\n\n\n其他运算\n(), ->, ,, []\n\n\n\n空间申请和释放\nnew, delete, new[], delete[]\n\n\n不可重载运算符::, ., .*(通过成员指针的成员访问), ?:, sizeof(alignof, typeid等), #(预处理符号)\n其他限制\n不能创建新运算符,例如 **、<> 或 &|。\n运算符 && 与 || 的重载失去短路求值\n重载的运算符 -> 必须要么返回裸指针,要么(按引用或值)返回同样重载了运算符 -> 的对象。\n不可能更改运算符的优先级、结合方向或操作数的数量。\n二元运算符中,左操作数为非对象的运算,必须用友元函数\n(5) 运算符重载不能改变该运算符用于内部类型对象的含义。它只能和用户自定义类型的对象一起使用,或者用于用户自定义类型的对象和内部类型的对象混合使用时\n=, (), [], -> 不能以友元方式重载,只能成员\n除了类属关系运算符.、成员指针运算符.*、作用域运算符::、sizeof运算符和三目运算符?:以外,C++中的所有运算符都可以重载\n\n双目/单目运算符、成员函数重载例子class Integer { private: int x; public: Integer(int x = 0) : x(x) {} Integer operator + (const Integer& Int) { return Integer(x + Int.x); } Integer operator - (const Integer& Int) { return Integer(x - Int.x); } Integer operator - () { return Integer(-x); } void print() { cout << x << endl; }};\nint main() { Integer a = 3, b = 4; a.print(); b.print(); Integer c = a + b; Integer d = a - b; Integer e = -a; c.print(); d.print(); e.print(); return (0);}\n347-1-3\n注意:\n\n左操作数:必须是 *this\n右操作数:类型任意,注意区分 T,const T&,T&,T&&,T*,const T* 的差别\n返回值:可以是任意类型,一般是左操作数类型的临时对象 T,或左操作数自身的引用 T&\n\n注意事项重载自增运算符重载自增运算符时需注意:\n\n若为前缀自增运算符,直接重载(以 ++ 举例):\nreturn_type class_name::operator ++() {}\n\n\n若为后缀自增运算符,该函数有一个 int 类型的虚拟形参,这个形参在函数的主体中是不会被使用的,这只是一个约定,它告诉编译器递增运算符正在后缀模式下被重载:\nreturn_type class_name::operator ++(int) {}class Integer { private: int x; public: Integer(int x = 0) : x(x) {} Integer& operator ++ () { cerr << \"prefix is invoked\" << endl; ++x; return *this; } Integer operator ++ (int) { cerr << \"suffix is invoked\" << endl; return Integer(x++); } void print() { cout << x << endl; }};\nint main() { Integer a = 3; a.print(); Integer b = ++a; a.print(); b.print(); Integer c = a++; a.print(); c.print(); return 0;}\n3prefix is invoked44suffix is invoked54\n\n\n\n重载赋值运算符class Integer { private: int x; public: Integer(int x = 0) : x(x) {} Integer& operator = (const Integer& Int) { x = Int.x; cout << \"function is invoked\" << endl; return *this; } void print() { cout << x << endl; }};\nint main() { Integer a = 3, b; a.print(); b = a; b.print(); return (0);}\n3function is invoked3\n而不重载赋值运算符时会有一个缺省赋值运算符,每个成员变量直接拷贝值\nclass Integer { private: int x; public: Integer(int x = 0) : x(x) {} void print() { cout << x << endl; }};\nint main() { Integer a = 3, b; a.print(); b = a; // default operator '=' b.print(); return (0);}\n33\n所以当成员变量包含指针类型的时候,要注意浅拷贝和深拷贝的区别\nclass IntArray { private: int *a, n; public: IntArray(int n = 1) : n(n) { a = new int[n]; } ~IntArray() { delete[] a; } int& operator [] (const int& i) { assert(0 <= i && i < n); return a[i]; } void print() { for (int i = 0; i < n; ++i) { cout << a[i] << \" \"; } cout << endl; }};// 未重载赋值运算符\nint main() { IntArray a(4), b; for (int i = 0; i < 4; ++i) { a[i] = i; } a.print(); b = a; // 默认赋值符 '=' 不是不行,但不推荐,有可能内存泄漏 b.print(); for (int i = 0; i < 4; ++i) { a[i]=-i; } a.print(); b.print(); // 与a一致 a.~IntArray(); // 直接调用析构,导致多次析构 b.print(); // b,a先后析构,造成segmentation fault return 0;}\n0 1 2 3 0 1 2 3 0 -1 -2 -3 0 -1 -2 -3 40316064 0 17039696 0 \n一种重载赋值运算符的正确写法\nclass IntArray { private: int *a, n; public: IntArray(int n = 1) : n(n) { a = new int[n]; } ~IntArray() { delete[] a; } int& operator [] (const int& i) { assert(0 <= i && i < n); return a[i]; } IntArray& operator = (const IntArray& A) { delete[] a; n = A.n; a = new int[n]; memcpy(a, A.a, sizeof(int)*n); return *this; } void print() { for (int i = 0; i < n; ++i) { cout << a[i] << \" \"; } cout << endl; }};\nint main() { IntArray a(4), b; for (int i = 0; i < 4; ++i) { a[i] = i; } a.print(); b = a; // deep copy is good b.print(); for (int i = 0; i < 4; ++i) { a[i] = -i; } a.print(); b.print(); // would be different from a //a.~IntArray(); b.print(); // nothing happend return 0;}\n0 1 2 3 0 1 2 3 0 -1 -2 -3 0 1 2 3 0 1 2 3 \n\n如果是 string 对象, new 数组会带来巨大对象构造成本。\n例如,作业中我们开辟 10000 个 account 数组,不仅每个对象构造成本巨大,而却 Account 通常是没有默认无参构造函数,因为没有账号ID的 account 不合理。\n\n重载移位运算符(类外定义)cin >> 和 cout << 的用法重载了 >> 和 <<\n同样,可以自行对定义的类做重载\nstruct Integer { int x;};istream& operator >> (istream& istrm, Integer& Int) { istrm >> Int.x; return istrm;}ostream& operator << (ostream& ostrm, const Integer& Int) { ostrm << Int.x; return ostrm;}\nint main() { Integer x; cin >> x; cout << x << endl; return (0);}\n要点:\n\n使用 struct 为了说明位移运算可以作为普通函数重载,而 = 运算只能作为成员函数重载\n第一操作数不是 *this 的,只能在类外定义\n要返回对象自身的引用\n\n展开讲 2在C++中,istream 和 ostream 是标准库中的类,用于读取和写入数据流。这两个类都是抽象类,不能直接实例化,需要使用其派生类进行实例化,例如iostream、ifstream、ofstream等。\n由于 istream 和 ostream 是标准库的类,我们不能在这些类的内部添加自定义的函数。因此,我们只能将重载的输入输出运算符定义为类的友元函数或类的非成员函数,并在类的外部进行定义。\n而,如果我们将这些函数定义为类的成员函数,则必须将其定义在类的内部,而不能在类的外部定义。\n当我们将重载的输入输出运算符定义为类的友元函数时,我们可以在类的内部声明这些函数,然后在类的外部进行定义。例如:\nclass Integer {public: friend istream& operator >> (istream& istrm, Integer& Int); friend ostream& operator << (ostream& ostrm, const Integer& Int);private: int x;};istream& operator >> (istream& istrm, Integer& Int) { istrm >> Int.x; return istrm;}ostream& operator << (ostream& ostrm, const Integer& Int) { ostrm << Int.x; return ostrm;}\n当我们将重载的输入输出运算符定义为类的非成员函数时,我们不需要将它们声明为类的友元函数,只需在类的外部进行定义。例如:\nclass Integer {public: int x;};istream& operator >> (istream& istrm, Integer& Int) { istrm >> Int.x; return istrm;}ostream& operator << (ostream& ostrm, const Integer& Int) { ostrm << Int.x; return ostrm;}\n无论采用哪种方式,都可以实现对Integer类的输入输出运算符的重载,并且将其定义在类的外部。\n展开讲 3istream& operator >> (istream& istrm, Integer& Int) 和 ostream& operator << (ostream& ostrm, const Integer& Int) 函数都返回它们的第一个参数,即输入流和输出流的自身引用。这是因为这些函数通过引用参数修改了流对象的状态,因此返回自身引用可以实现连续的输入/输出操作。\n对于输入运算符 >> 来说,函数需要将输入流对象的状态修改为已读取输入的值,而对于输出运算符 << 来说,函数需要将输出流对象的状态修改为已写入输出的值。因此,为了实现链式输入输出操作,这些重载函数通常返回它们的第一个参数,即输入输出流对象的引用,以便连续调用。\n如果我们不返回自身引用,那么我们就无法通过链式调用连续地对输入流进行操作,例如:\ncin >> x; // 正常调用cin >> x >> y; // 无法连续调用,因为第一个输入操作没有返回自身引用\n因此,返回对象的自身引用可以实现链式的输入输出操作,使代码更加简洁、易读。\n","categories":["C++","Operator overloading"],"tags":["C++"]},{"title":"C++ note (7)","url":"//2023/06/07/C-note-7/","content":"运算符重载 Ⅱ注意事项函数对象当定义了 operator() 的类的对象调用此操作符时,其表现形式如同普通函数调用一般,故取名函数对象,举例:\nclass cmp { public: bool operator () (const int& a, const int& b) { return a < b; }};\nint main() { cmp f; cout << f(1,2) << endl; return (0);}\n1\n函数对象实际上也是一个对象,所以可以拥有自己的成员变量,从而执行带谓词的函数执行:\n// greater.hclass greaterThan { private: int base; public: greaterThan(int x) : base(x) {} bool operator () (const int& x) { return x > base; }};\n#include \"greater.h\"int main() { greaterThan g(10); cout << g(15) << endl; return (0);}\n1\n所以,函数对象相比普通的函数有一个非常重要的用途,即 作为谓词函数(Predicate)。\n谓词函数通常用来对传进来的参数进行判断,并返回布尔值。标准库中有大量的函数都定义了多个重载版本,其中包含由用户提供谓词函数的,比如:find_if,remove_if,等等。\n现在假设我们有一串数字,要从中找出第一个大于10的数字:\n#include \"greater.h\"bool greaterThan10(const int& x) { return x > 10;}int main() { vector<int> a = {5, 10, 15, 20, 25}; // find_if return a iterator cout << *find_if(a.begin(), a.end(), greaterThan(10)) << endl; cout << *find_if(a.begin(), a.end(), greaterThan10) << endl; return (0); // 用函数对象,使得代码更具有可读性}\n友元函数问题引入Q:如何解决外部函数无法访问类的 private 成员?\nclass integer { int x;};istream& operator >> (istream& is, integer& Int) { is >> Int.x; // error: private, unavailable return is;}ostream& operator << (ostream& os, const integer& Int) { os << Int.x; // error: private, unavailable return os;}\nA:使用友元函数(friend function)\n概念\nfriend return_type function_name(parameter_type_list);\n\n将正常声明的函数放进类内部,并在前面加上 friend 关键字,那么这个函数虽然不属于类,但却可以访问类的私有变量以及私有函数。\nclass integer { int x; friend istream& operator >> (istream& is, integer& Int); friend ostream& operator << (ostream& os, const integer& Int);};istream& operator >> (istream& is, integer& Int) { is >> Int.x; // ok return is;}ostream& operator << (ostream& os, const integer& Int) { os << Int.x; // ok return os;}\n\n友元函数的声明可以放在类的私有部分,也可以放在公有部分,它们是没有区别的,都说明是该类的一个友元函数。\n\n类的成员函数也是一种函数,所以,其他类的成员函数也可以作为友元函数\nclass integer;struct cmp { bool operator () (const integer& a, const integer b);};class integer { private: int x; public: integer(int x = 0) : x(x) {} friend bool cmp::operator () (const integer& a, const integer& b); // declaration};bool cmp::operator () (const integer& a, const integer& b) { return a.x < b.x;}\n友元类有时候其他类的成员函数可能会很多,一个一个的声明为友元函数会比较麻烦。\n所以我们就可以直接声明友元类:\n\n一个类 A 可以将另一个类 B 声明为自己的友元,那么类 B 的所有成员函数就都可以访问类 A 对象的私有成员\nfriend class B; (在类 A 的内部)class integer;struct cmp { bool operator () (const integer& a, const integer b);};class integer { private: int x; public: integer(int x = 0) : x(x) {} friend cmp; // new declaration};bool cmp::operator () (const integer& a, const integer& b) { return a.x < b.x;}\n\n操作符重载限定必须是函数成员\n隐式转换问题引入对象隐式转换如果对象 T 存在构造函数 T(T1), 则 T1 类型对象(实参)可隐式转为 T 类型对象(形参)\nclass integer { int x; public: integer(int x = 0) : x(x) {} friend integer operator + (const integer& lhs, const integer& rhs) { return lhs.x + rhs.x; // 1. int -> integer } friend ostream& operator << (ostream& o, const integer& hs) { o << hs.x; return o; }};int main() { string s; s = \"Hello\"; // 2. const char* -> string cout << s << endl; integer i1(3), i2; i2 = 1.1 + i1; // 3. double -> int int -> integer cout << i2 << endl; return (0);}\n重载协议-const如果重载的函数参数一样,可以通过转换到某个重载函数,编译会如何哪个版本的选择?\n\n类型直接匹配的优先选择;\nconst 类型实参匹配 const 版本\n非 const 实参优先匹配非 const 版本。没有则隐式转换为 const 版本匹配class Integer { int x; public: integer(int x = 0) : x(x) {} friend ostream& operator << (ostream& o, const Integer& hs) { o << \"const \" << hs.x; return o; } friend ostream& operator << (ostream& o, Integer& hs) { o << \"no_const \" << hs.x ; return o; } };int main() { Integer i1(1); const Integer i2(2); cout << i1 << \",\" << 3 << \",\" << i2 << endl;}\nno_const 1,3,const 2\n练习:\n注释去除非 const 版本,编译运行\n注释去除 const 版本,编译\n\nnullptrC 语言零值常数有很多表示,如 0, NULL, ‘\\0’, C++ 右引入了 nullptr 表示空值指针字面量。\n下边的例子解释了 nullptr 的必要性\nclass Integer { int x;`public: Integer(int x = 0) : x(x) {} friend Integer operator + (const Integer& lhs, Integer rhs){ return lhs.x + rhs.x; } friend Integer operator+(const Integer& lhs, Integer* rhs){ if (rhs) { return lhs.x + rhs->x + 2000; } else { return lhs.x + 1000; } } friend ostream& operator << (ostream& o, const Integer& hs) { o << hs.x; return o; } };class Girlfriend {/* ... */};void kissGirlfriend(Girlfriend* gf) { cout << \"pointer\"<<endl;}void kissGirlfriend(int gfID) { cout << \"int\"<<endl;}int main() { kissGirlfriend(nullptr); // 指针类型字面量(viod *)0 kissGirlfriend(0); // 尝试 NULL 取代 0 Integer i1(1), i2; i2 = i1 + 0; // 尝试 1,nullptr,NULL,&i1 取代 0 cout << i2 << endl; return (0);}\n\n先进行类型匹配\n再进行类型转换\n0优先转指针了,不会执行integer类型转换\n运算符重载,0必须特殊处理。如采用显式转换(integer)0转指针了,不会执行integer类型转换\n\nexplicitC++ 关键字 explicit,用于关闭这种自动类型转化的特性。\n即被 explicit 关键字修饰的类构造函数,不能进行自动地隐式类型转换,只能显式地进行类型转换。\nclass Integer;struct cmp { bool operator () (const Integer& a, const Integer& b);};class Integer { int x; public: explicit Integer(cosnt char* s) : x(atoi(s)) {} Integer(int x = 0) : x(x) {} friend cmp;};bool cmp::operator () (const Integer& a, const Integer& b) { return a.x < b.x;}int main() { Integer a(\"3\"), b(\"4\"); cmp compare; cout << compare(a,b) << endl; cout << compare(Integer(\"1\"), Integer(\"2\")) << endl; // explicit construction, ok cout << compare((Integer)\"1\", (Integer)\"2\") << endl; // explicit conversion, ok cout << compare(\"1\", \"2\") << endl; // error cout << compare(\"a\", \"b\") << endl; // error return (0);}\n","categories":["C++","Operator overloading"],"tags":["C++"]},{"title":"C++ note (8)","url":"//2023/06/08/C-note-8/","content":"继承和派生 Ⅰ继承/派生概念派生类(derived class)是通过对基类(base class)进行扩充和修改得到的。基类的所有成员自动成为派生类的成员。在基类中除了自己定义的成员之外,还自动包括了派生类中定义的数据成员与成员函数,这些自动继承下来的成员称为基类的继承成员\nbase class / parent class / super class\nderived class / sub class\n继承代表了从属(is-a)关系\n提出继承/派生概念的原因\n是通常所说的oop的基础\n继承提供了一种通过修改(演化)一个或多个现有类来构造新类的方法\n如果只有一个类的概念,软件的可重用性、演化和相关的概念表示存在严重的不灵活问题。继承机制为软件可重用性、IS-A 概念表示和易于修改提供了解决方案\n\n图解 A ↗ ↖ B C单继承:派生类只有一个直接基类\n A B ↖ ↗ C多重继承:派生类具有两个及以上直接基类\n A ↗ ↖ B C ↖ ↗ D菱形继承:派生类两次或两次以上重复继承某个祖先类\n语法与访问控制继承关系语法单重继承的定义形式\nclass derivedClass_name : inheritanceControl baseClass_name { objectControl: objectDeclaration_list;};\n继承中的访问控制继承访问控制和成员访问控制均由保留public、protected、private来定义,缺省均为private\n\nprivate\n私有成员只能通过本类的成员函数来访问\n\npublic\n公有成员用于描述一个类与外部世界的接口,类的外部(程序的其它部分的代码)可以访问公有成员\n\nprotected\n受保护成员具有private与public的双重角色:\n对派生类的成员函数而言,它为public,而对类的外部而言,它为private。即:protected成员只能由本类及其后代类的成员函数访问\n\n\n影响继承成员(派生类从基类中继承而来的成员)访问控制方式的两个因素:\n\n定义派生类时指定的继承访问控制\n该成员在基类中所具有的成员访问控制\n\n\n\n无论采用什么继承方式,基类的私有成员在派生类中都是不可访问的。\n“私有”和“不可访问”有区别:私有成员可以由派生类本身访问,不可访问成员即使是派生类本身也不能访问。\n大多数情况下均使用 public 继承\n\n举例:\nclass base { public: base(); void get_ij(); protected: int i, j; private: int temp;};//公有派生:在y1类中,i、j是受保护成员class y1 : public base { public: void increment(); // get_ij()是公有成员,temp不可访问 private: float member;};base::base() : i(0), j(0), temp(0) {}void base::get_ij() { cout << i << \" \" << j << endl;}void y1::increament() { ++i; ++j;}\nint main() { base obj1; y obj2; obj2.increament(); obj2.get_ij(); obj1.get_ij(); return (0);}\n1 10 0\n保护派生:在y2类中,i、j是受保护成员。get_ij()变成受保护成员,temp不可访问\nclass y2 : protected BASE{ … };\n私有派生:在y3类中,i、j、 get_ij()都变成私有成员,temp不可访问\nclass y3 : private BASE{ … };\n在大多数情况下,使用public的继承方式;private和protected很少使用\n派生类对象的存储\n派生类的对象不仅存放了在派生类中定义的非静态数据成员,而且也存放了从基类中继承下来的非静态数据成员\n可以认为派生类对象中包含了基类子对象\n\n构造与析构顺序继承时的构造函数基类的构造函数不被继承,派生类中需要声明自己的构造函数\n派生类的构造函数中只需要对本类中新增成员进行初始化即可。对继承来的基类成员的初始化是通过编译在派生类构造函数初始化器中自动生成默认构造函数(默认拷贝构造)完成。\n\n如果基类没有默认构造(包括 = delete),则编译错误\n\n派生类的构造函数需要使用基类的有参构造函数时,必须显式地在初始化器列表中申明。\n\n注意:不能在构造函数内调用!\n\n构造函数的调用次序(创建派生类对象时)\n\n首先调用其基类的构造函数(调用顺序按照基类被继承时的声明顺序(从左向右))。\n然后调用本类对象成员的构造函数(调用顺序按照对象成员在类中的声明顺序)。\n最后调用本类的构造函数。\n\n继承时的析构函数撤销派生类对象时析构函数的调用次序与构造函数的调用次序相反\n\n首先调用本类的析构函数\n然后调用本类对象成员的析构函数\n最后调用其基类的析构函数\n\n//demo.h class C { public: C( ); ~C( );};class base { public: base( ); ~base( );}; \n#include “demo.h”//demo.cppC::C( ) { cout << \"Constructing C object.\" << endl;}C::~C( ) { cout << \"Destructing C object.\" << endl;}base::base( ) { cout << \"Constructing base object.\" << endl;}base::~base( ) { cout << \"Destructing base object.\" << endl;}\n// Derived.hclass derived : public base { public: derived() ~derived() private: C newObj;}; \n#include “Derived.h”// Derived.cppderived::derived() { cout << \"Constructing derived object.\" << endl;}derived::~derived() { cout << \"Destructing derived object.\" << endl;}\n#include “Derived.h” // Client.cppint main() { derived obj; // 声明一个派生类的对象 // 什么也不做,仅完成对象obj的构造与析构 return (0);}\nConstructing base object.Constructing C object.Constructing derived object.Destructing derived object.Destructing C object.Destructing base object.\n","categories":["C++","Inheritance and Derivation"],"tags":["C++"]},{"title":"C++ note (5)","url":"//2023/05/28/C-note-5/","content":"数据抽象和类 Ⅳ对象成员初始化非静态数据成员初始化方法:\n在构造函数的成员 初始化器列表 中。(C++11)\n通过 默认成员初始化器 ,它是成员声明中包含的 花括号 或 等号 初始化器。(C++11)\n构造函数体内进行赋值操作。(不要构造成员,除非特别熟悉 new 的用法)\n\n为什么需要初始化器列表和默认成员初始化器?\n对象初始化分两个阶段:先按声明顺序初始化成员、然后执行构造函数函数体;\n对于复杂对象成员,如 std::string name,必须先构造才能在构造函数中赋值。这会导致构造函数必须先构造一个 string 中间变量才能赋值给 name;\n对于引用类型成员,const 成员需要预初始化;\n对象成员初始化需要参数。\n\n\n为了提升初始化性能,C++ 引入了默认成员初始化器和初始化器列表\n\n语法与其他函数不同,构造函数除了有名字,参数列表和函数体之外,还可以有初始化器列表,初始化器列表以冒号开头,后跟一系列以逗号分隔的成员初始化器。\nDate(int y, int m) : year(y), month(m) {}\n要点必须使用初始化器列表的时候除了性能问题之外,有些时候初始化器列表是不可或缺的.以下几种情况时必须使用初始化器列表:\n\n常量成员,因为常量只能初始化不能赋值,所以必须放在初始化器列表里面\n引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要使用初始化器\n没有默认构造函数的类类型(class type),因为使用初始化器列表可以不必调用无参构造函数来初始化,而是直接用其他构造函数初始化\n\n成员变量声明的顺序成员是按照他们在类中声明的顺序进行初始化的,而不是按照他们在初始化器列表出现的顺序初始化的\nfoo(int x) : i(x), j(i) {}// 先初始化i,后初始化j\n一个好的习惯是,按照成员声明的顺序进行初始化\n示例/*member initialization*/struct S { int n; // 非静态数据成员 int& r = n; // 引用类型的非静态数据成员; =默认构造 int a[2] = {1, 2}; // 带默认成员初始化器的非静态数据成员(C++11) string s{'H','C'}; // 带默认成员初始化器 struct nestedS { string s; nestedS(std::string s = \"hello\") : s(s) {}; } d5; // 具有嵌套类型的非静态数据成员 const char bit : 2; // 2 位的位域, const初始化 S() : n(7), bit(3) {} // \" : n(7), bit(3)\" 是初始化器列表; \"{}\" 是函数体 S(int x) : n{x}, bit(3) {}};int main() { S s; // 调用 S::S() S s2(10); // 调用 S::S(int)}\n练习对象的内存布局C++内存格局见 {post_link C-note-4}\ndate area 存放全局变量,静态数据和常量\n\ncode area 存放所有类成员函数和非成员函数代码\n\nstack area 存放为运行函数而分配的局部变量、函数参数、返回数据、返回地址等(栈区)\n\n余下的空间都被称为 heap area(堆区)\n\n\n在类的定义时:\n类的成员函数被放在 code area\n类的静态成员变量在 data area\n非静态成员变量、虚函数指针、虚基类指针在类的实例内,实例在 stack area 或 data area\n\n类的实例\n如果是定义的类变量,则在 stack area\n如果是new出来的类指针,则在 heap area,同时引用会保存在 heap\n注意:\n对象中包含成员函数指针浪费内存\n与 C 库 struct 不兼容\n\nC++ 使用静态联编对象方法静态联编:\n指在编译阶段,就能直接使用代码段函数地址调用动态对象的方法。该方法仅需要向非静态成员函数传送this指针,即可用静态函数调用实现动态调用效果。\n优势:\n\n对象布局与 C 结构内存布局一致,使得内存中对象便于与其他语言程序库兼容\n高效率,高性能\n\n拷贝构造函数概念在定义语句中用同类型的对象初始化另一个对象\n//假定 C 为已定义的类C obj1; //调用 C 的(1)无参构造函数C obj1(1,2) //调用 C 的有参(2)普通构造函数//如无(1)(2)则调用默认构造函数/*调用 C 的(3)拷贝构造函数用对象obj1初始化对象obj2。如果有为 C 类明确定义拷贝构造函数,将调用这个拷贝构造函数;如果没有为 C 类定义拷贝构造函数,将调用默认拷贝构造函数。*/C obj2(obja); //或C obj2 = obja ; //两者等价(注意:不是赋值运算)\n语法\n用类类型(class type)本身作为参数\n该参数传递方式为按引用传递,避免在函数调用过程中生成形参副本。\n该形参声明为 const,以确保在拷贝构造函数中不修改实参的值C::C(const C& obj);\n例如:complex(const complex& other);\n注:\n\n\nC::C(C& obj) 不用 const 形式已过时。\nC::C(C&& obj) 形式称为移动构造函数(超纲)\n\n要点\n形参类型为该类类型本身且参数传递方式为按引用传递\n用一个已存在的该类对象初始化新创建的对象\n每个类都必须有拷贝构造函数:\n用户可根据自己的需要显式定义拷贝构造函数\n若用户未提供,则该类使用由系统提供的缺省拷贝构造函数(可用= default),也可用 = delete 弃置该函数\n缺省拷贝构造函数按照初始化顺序,对对象的各基类和非静态成员进行完整的逐成员复制,完成新对象的初始化。即逐一调用成员的拷贝构造,如果成员是基础类型,则复制值(赋值)\n\n\n\n隐式调用复制构造\n对象作为函数值参\n将一个对象作为实参,以按值调用方式传递给被调用的形参对象\n // 假定C为已定义的类,obj为 C 类对象 void fn(C tmp) {...} / / / / / fn(obj); // 用obj初始化tmp,如果有 C 类明确定义拷贝构造函数,将调用其;如果没有,将调用缺省拷贝构造函数 // obj传递给fn函数,创建对象tmp时,调用 C 的拷贝构造函数用对象obj初始化对象tmp,tmp生存期结束时调用析构函数2. 对象作为值从函数返回 生成一个临时对象,作为函数的返回结果: 当函数返回某一对象时,系统将自动创建一个临时对象来保存函数的返回值。当创建此临时对象时,调用拷贝构造函数;当函数调用表达式结束后,撤销该临时对象,调用析构函数 ```cpp C fn() { C t; ... return t }\n则\ntmp = fn();\nt -> temp_object -> tmp\n\n\n\n编译优化: C fn() { C tmp; ... return tmp;}\n 实际的gcc/g++会优化使得tmp和new C的地址是一样的\n\n示例:Example 1\n复制策略:拷贝构造函数自定义\n浅拷贝只复制成员指针的值,而不复制指向的对象实体,导致新旧对象成员指针指向同一块内存。但深拷贝要求成员指针指向的对象也要复制,新对象跟原对象的成员指针不会指向同一块内存,修改新对象不会改到原对象。\n\n\n对于不含指针成员的类,使用系统提供(编译器合成)的默认拷贝构造函数即可\n缺省拷贝构造函数使用浅复制策略,不能满足对含指针数据成员的类需要\n含指针成员的类通常应重写以下内容:\n\n\n构造函数(及拷贝构造函数)中分配内存,深复制策略\n= 操作重写,完成对象深复制策略\n析构函数中释放内存\n\n示例:Example 2\n","categories":["C++","ADT and class"],"tags":["C++"]},{"title":"C++ note (9)","url":"//2023/06/08/C-note-9/","content":"继承和派生 Ⅱ派生与构造函数类不可继承的成员类不可继承的成员有\n\n私有成员\n构造函数与析构函数class MyString : public std::string { public: MyString(const char* s): string(s) {};}; int main() { //ex1: 编译,并观看编译日志 MyString str1(\"继承 string(const *char)\"); str1 = \"继承 string::operator=(...)\"; cout << str1 << endl; MyString str2 = \"hello \"; cout << str2 + str1 << endl; //ex2: 取消第 6 行注释,编译并运行 return 0; }\n\n向基类构造函数传递实参若基类构造函数带参数,则定义派生类构造函数时,仅能通过初始化列表显式调用基类构造函数,并向基类构造函数传递实参。 \n带初始化列表的派生类构造函数的一般形式如下\n派生类名(形参表) : 基类名(实参表) { 派生类新成员初始化赋值语句;};\n举例:time类\n// SPECIFICATION FILE (time.h)class time { public: void set(int hours, int minutes, int seconds); // 将被修改 void increment(); // 将被继承 void write() const; time(int initHrs, int initMins, int initSecs); time(); private: int hrs; int mins; int secs;};\n// SPECIFICATION FILE ( extTime.h)#include “time.h”enum zoneType { EST, CST, MST, PST, EDT, CDT, MDT, PDT}; // 枚举类型class extTime : public time { public: extTime(int initHrs, int initMins, int initSecs, zoneType initZone); extTime(); void set(int hours, int minutes, int seconds, zoneType timeZone) ; // 扩展函数成员 void write() const; private: zoneType zone; // 增加数据成员};\n// IMPLEMENTATION FILE (extTime.cpp)extTime::extTime(int initHrs, int initMins, int initSecs, zoneType initZone, zone(initZone)) : time(initHrs, initMins, initSecs) { // 初始化基类成员 zone = initZone; // 初始化派生类成员}\n\n初始化器列表中写 zone(initZone) 更显 C++ 风格\n传递给基类构造函数\n基类构造函数在派生类构造函数之前调用//base class default constructor is called prior to the derived //class default constructor.extTime::extTime() { // 编译器默认添加 `:time()` 初始化基类成员 zone = EST; // 如果枚举值标识符崇明,使用类型::枚举值。例如:zoneType::EST}\nvoid extTime::set( int hours, int minutes, int seconds, zoneType timeZone) { time::set(hours, minutes, seconds); //调用基类函数。Why? zone = timeZone;}void extTime::write() const{ static string zoneString[8] ={ \"EST\", \"CST\", \"MST\", \"PST\", \"EDT\", \"CDT\", \"MDT\", \"PDT\" }; // 数组比switch语句好 time::write(); cout << ' ' << zoneString[zone];}\n\n派生与成员函数概念\n重载(overload)\n\n具有相同的作用域(即同一个类定义中);\n函数名字相同\n参数类型(包括const 指针或引用) ,顺序 或 数目不同\n\n\n覆盖(override)- 修改基类函数定义\n (记得加链接)\n\n隐藏(overwrite)- 屏蔽基类的函数定义\n\n派生类的函数与基类的函数同名,但是参数列表有所差异。\n派生类的函数与基类的函数同名,参数列表也相同,但是基类函数没有 virtual 关键字。\n\n\n继承(inheritance)\n\n没有被覆盖或隐藏的基类函数,包括在基类中重载的函数\n\n\n\n重载和隐藏的区别例如\n\ntime::set(int, int, int)\nexTime::set(int, int, int)\n注: int, int, const int 算不同的参数类型\n\nownership 还在函数调用点外部\n\nint 和 const int 算相同的参数类型\n\n传参已经 copy,不再管 constant modifier\n\n\n\n\n如果 set 在一个类中定义,则是重载\n\nset 的签名不一样\n\n如果同名函数出现在 baseClass 和 derivedClass 中,且满足隐藏的特征 1 或 2\n\nexTime 案例 set 满足特征 1\nexTime 案例 write 满足特征 2所以它们都属于隐藏\n\n隐藏的应用\n利用隐藏,实现在派生类中修改成员函数的功能,如 write\n利用隐藏,赋予派生类成员函数新的功能// IMPLEMENTATION FILE ( time.cpp )void time::set( int hours, int minutes, int seconds) { hrs = hours; mins = minutes; secs = seconds;}\n// IMPLEMENTATION FILE ( extTime.cpp )void extTime::set( int hours, int minutes, int seconds, zoneType timeZone) { time::set(hours, minutes, seconds); zone = timeZone;}\n// base.hclass base { public: // 没有默认构造 base(int p1, int p2); int inc1(); int inc2(); // 被继承 void display(); private: int mem1, mem2;};\n#include \"base.h\"// derived.hclass derived : public base { public: derived(int x1, int x2, int x3, int x4, int x5); int inc1(); int inc3(); // 新添成员 void display(); // 隐藏规则 2, 修改定义 private: int mem3; base mem4; // 类成员,注意初始化方法}\n#include \"base.h\"base::base(int p1, int p2) { mem1 = p1; mem2 = p2;}int base::inc1() { return ++mem1;}int base::inc2() { return ++mem2;}void base::display() { cout << \"mem1 = \" << mem1 << \", mem2 = \" << mem2 << endl; }\n#include \"derived.h\"derived::derived(int x1, int x2, int x3, int x4, int x5) : base(x1, x2), mem4(x3, x4) { // 基类、类成员初始化 mem3 = x5; // 基类、类成员 不能在这里初始化!}int derived::inc1() { return base::inc1();}int derived::inc3() { return ++mem3;}void derived::display() { base::display(); // 被隐藏函数成员调用 mem4.display(); cout << \"mem3 = \" << mem3 << endl;}\n#include \"derived.h\"int main() { derived obj(17, 18, 1, 2, -5); obj.inc1(); obj.display(); return (0);}\n\n存储结构(todo)\n改变访问控制恢复访问控制方式基类中的 public 或 protected 成员,因使用 protected 或 private 继承访问控制而导致在派生类中的访问方式发生改变,可以使用“访问声明”恢复为原来的访问控制方式\n访问声明的形式\nusing 基类名::成员名;(放于适当的成员访问控制后)\n使用情景\n\n在派生类中希望大多数继承成员为 protected 或 private,只有少数希望保持为基类原来的访问控制方式class base { public: void set_i(int x) { i = x; } int get_i() { return i; } protected: int i;};class derived : private base { public: using base::set_i; using base::i; void set_j(int x) { j = x; } int get_ij() { return i + j; } protected: int j;};\nint main() { derived obj; // 声明一个派生类的对象 obj.set_i(5); // set_i()已从private转为public obj.set_j(7); cout << obj.get_ij() << endl; return (0);}\n\n屏蔽基类成员目的:\n\n使得客户代码通过派生类对象不能访问继承成员。\n\n方法:\n\n使用继承访问控制protected和private(真正屏蔽)\n在派生类中成员访问控制 protected 或 private 之后,使用 “using 基类名::成员名”(非真正屏蔽,仍可通过使用“基类名::成员名”访问)\n\n用于继承对象的重命名目的:\n\n解决名字冲突。\n在派生类中选择更合适的术语命名继承成员。\n\n方法\n\n在派生类中定义新的函数,该函数调用旧函数;屏蔽旧函数。\n在派生类中定义新的函数,该函数的函数体与旧函数相同。\n\n使用基类构造函数目的:\n\n使得派生类对象直接使用基类的构造函数。\n\n方法:\n\n在派生类中使用 using 基类名::基类名\n\n继承机制的应用举例-图形的处理将圆看作是一种带有半径的点,将点看作是一种带有显示状态的位置\n//说明:类location以x和y坐标描述了计算机屏幕上的一个位置。#include <graphics.h>// basegraph.hclass location { public: location(int x, int y); // 构造函数,将当前位置设置为(x, y) int get_x(); // 返回当前位置的x坐标 int get_y(); // 返回当前位置的y坐标 protected: // 位置的内部状态,在LOCATION的派生类中需要访问 int x_pos, y_pos; };class point : public location { public: point(int x, int y); bool isVisible(); void show(); void hide(); void moveTo(int x, int y); protected: bool visible;};class circle : public point { public: circle(int x, int y, int r); void show(); void hide(); void moveTo(int x, int y); void expand(int delta); void contract(int delta); protected: int radius;};\n#include \"basegraph.h\"// location.cpplocation::location(int x, int y) { x_pos = x; y_pos = y;}int location::get_x() { return x_pos;}int location::get_y() { return y_pos;}point::point(int x, int y) : location(x, y) { visible = false;}bool point::isVisible() { return visible;}void point::show() { if(!isVisible()) { visible = true; putpixel(x_pos, y_pos, getcolor()); }}void point::hide() { if(isVisible()) { visible = false; putpixel(x_pos, y_pos, getbkcolor()); }}void point::moveTo(int x, int y) { hide(); x_pos = x; y_pos = y; show();}\n#include \"basegraph.h\"// circle.cppcircle::circle(int x, int y, int,r) : point(x, y), radius(r) {}void circle::show() { if(!isVisible()) { visible = true; circle(x_pos, y_pos); }}void circle::hide() { unsigned int tmpColor; if(isVisible()) { tmpColor = getcolor(); setcolor(getbkcolor()); visible = false; circle(x_pos, y_pos, radius); setcolor(tmpColor); }}void circle::moveTo(int x, int y) { hide(); x_pos = x; y_pos = y; show(); }void circle::expand(int delta) { hide(); radius = radius + delta; if(radius < 0) { radius = 0; } show(); }void circle::contract(int delta) { expand(-delta); }\n#include <conio.h> // 利用其中的getch()函数暂停#include \"basegraph.h\"// graphdemo.cppint main() { int graphdriver = DETECT, graphmode ; // 初始化图形系统所需变量 // 声明一个圆,圆心在(100, 200),半径为50 CIRCLE circle(100, 200, 50); initgraph(&graphdriver, &graphmode, \"\"); // 初始化图形系统 circle.show(); // 声明一个圆并显示它 circle.move_to(200, 250); // 移动圆 circle.expand(50); // 放大圆 circle.expand(50); circle.contract(65); // 缩小圆 circle.contract(65); closegraph(); // 关闭图形系统 return (0);} //注:此代码需要电脑事先安装好图形相关库环境\n继承对象的重定义派生类中修改继承成员函数的语义(即,修改函数体,而保持函数原型不变)\n派生类中的名字屏蔽基类中的名字\n// 说明:类point描述了某一个位置是隐藏的还是显示的。// 以public继承表示x_pos和y_pos在POINT中是protectedclass point : public location { public: : void show(); // 在当前位置显示点 void hide(); // 将点隐藏起来 // 将当前点移动到新位置(x, y)并显示它 void move_to(int x, int y); protected: :};// 说明:类circle描述了一个在屏幕上由point派生出来的圆。// 由point类派生,从而也继承了location类class circle : public point { public: : void show(); // 在屏幕上画出圆 void hide(); // 将圆隐藏起来 void move_to(int x, int y); // 将当前圆移动到新位置(x, y) : protected: :};\nvoid point::show() { if (!isVisible()) { visible = true; putpixel(x_pos, y_pos, getcolor()); }}void circle::show() { if (!isVisible()) { visible = true; circle(x_pos, y_pos, radius); }}\n#include \"basgraph.h\" // 基本图形元素的类界面 #include <conio.h> // 利用其中的getch()函数暂停 int main() { : // 声明一个圆,圆心在(100, 200),半径为50 point point( 20, 10 ); circle circle(100, 200, 50); circle.show(); // 显示圆 point.show(); // 显示点 circle.moveTo(200, 250); // 移动圆 point.moveTo(100,20); // 移动点 :}\n","categories":["C++","Inheritance and Derivation"],"tags":["C++"]},{"title":"Examples for C++ note (5)","url":"//2023/05/29/Examples-for-C-note-5/","content":"Example 1class foo { public: foo(int i) { cout << \"Constructing.\" << endl; member = i; } foo(const FOO& other) { cout << \"Copy constructing.\" << endl; member = other.member; } ~foo() { cout << \"Destructing.\" << endl; } int get() { return member; } private: int member;};void display(foo obj) { cout << obt.get() << endl;}foo get_foo() { int value; cout << \"Input an integer: \"; cin >> value; foo obj(value); return obj;}\nint main() { foo obj1(15); foo obj2 = obj1; display(obj2); obj2 = get_foo(); display(obj2); return (0);}\n返回:隐式调用复制构造\nExample 2返回:复制策略:拷贝构造函数自定义\n","categories":["C++","ADT and class"],"tags":["C++"]},{"title":"C++ note (1)","url":"//2023/05/10/C-note/","content":"new 和 delete 、动态创建变量/数组、内存泄漏、 = defaultnew 和 delete 关键字new 总结\nptr_type* ptr_name = new ptr_type; // 动态创建一个对象\nptr_type* ptr_name = new ptr_name;(初始化参数) ; // 动态创建一个对象,可不初始化\nptr_type* ptr_name = new ptr_type[arr_length] {Init_list}; // 用于动态分配数组,可没有初始化列表(string 类型就一定要有)\n\n\n类型可为基本类型,也可为类类型(class type)。\n若为类类型,则初始化参数相当于将实际参数传递给该类的构造函数\n\n\nnew 运算返回一个该类型指针(不是 **void***),指向分配到的内存空间\n若内存分配失败,抛出异常结束程序,而不是返回 NULL\n\ndelete 总结:\ndelete 变量名; //基本用法\ndelete []变量名; //用于释放数组\n\n\n如果动态分配了一个数组,但是却用delete p的方式释放,没有用[],则\n\n编译时没有问题,运行时也一般不会发生错误,\n但实际上会导致动态分配的数组没有被完全释放。\n\n\ndelete 释放的是指针所指对象占据的内存。\n\ndelete 对象指针,会调用该对象的析构函数。([] 将令其中所有元素都调用各自析构函数)\n用delete释放空间后,指针的值仍是原来指向的地址,但指针已无效(重复释放将出错,即非法指针访问)。\n\n\ndelete 本身会自动检查对象是否为空。如果为空,就不做操作,因此 delete 空指针不需要特判。\n\n为防止重复删除出错,最好删除后就把指针赋为空。\n\n\n动态创建变量/数组动态创建一个整型变量int *p = new int;cin >> *p;delete p;\n动态创建一个整型数组int *p;p = new int[length] {1, 2, 3};// 若没有初始化列表,则不确定值;若初始化列表长度不够,则自动补齐`0`for (int i = 0; i < length; i++) { cout << p[i] << \" \";}delete [] p;\n动态创建一个 string 数组int n = 3;string *p = new string[n] { string(5, 'a'), string(4, 'b'), string(3, 'c')};// 由于将决定分配多少长度,`string`创建时必须初始化p[1][2] = 'f';// 初始化后可以更改for (int i = 0; i < n; i++) { cout << p[i] << endl;}delete [] p;\n动态创建一个二维整型数组int row;int col;cin >> row >> col;int **a;a = new int *[row];for (int i = 0; i < row; i++) { a[i] = new int [col];}for (int i = 0; i < row; i++) { for (int j = 0; j < col; j++) { cin >> a[row][col]; }}for (int i = 0; i < row; i++) { delete [] a[i];}delete [] a;\n内存泄漏是指 new 的指针丢失导致占用内存无法释放\n\n指针赋值时int *p = new int(7);p = nullptr;// 内存泄漏\n指针离开作用域时void fn() { int *p = new int(7);}// 内存泄露\n异常导致程序或函数终止void fn() { int *p = new int(7); g(); delete p;}// 若 g() 抛出异常则内存泄漏\n\n显式默认化函数定义 = default用于恢复函数的默认定义当我们声明有参构造函数时,编译器就不会创建默认构造函数。为了使编译器创建该默认构造函数,可以在函数声明后指定 = default\n\n若以 = default 声明,则该函数不能写实现\n特殊成员函数包括:\n默认构造函数\n析构函数\n复制构造函数等\n\n\n\n例如:(有参时用前面的,无参时用默认,这时不能写A()的实现)\npublic: A(int x) { cout << \"constructed\"; } A() = default;\n","categories":["C++","ADT and class"],"tags":["C++"]},{"title":"How I solve the 'tag page is blank' problem","url":"//2023/05/28/How-I-solve-the-tag-page-is-blank-problem/","content":"创建 tags 页面时遇到的问题正常流程\n$ hexo new page \"tags\"\n编辑 index.md : title: tagtype: tagslayout: tags\n编辑主题配置文件 nav: nav: Posts: /archives Categories: Tags: /tags # 新增\n\n我的 tags 界面会\n依旧显示 Cannot GET /tags/\n正常打开但内容空白\n\n解决\n对于显示 Cannot GET /tags/,检查是否写了 layout: tag \n对于内容空白,查阅 themes/maple/layout/tag.ejs,发现作者写道:\n\nthis page will judge whether current page is ‘tag.’\nurl:’http://localhost:4000/tag/‘ return false.\nurl:’http://localhost:4000/tags/test/‘ return true.\nif you enter ‘tag’ page by click link will show all tags.\notherwise if you enter ‘tags’ by click post header link will show the single tag of this post.\nhow to add a tag page:1. hexo new page “tag”2. cd source/tag/index.md3. add layout pattern like this:title: tagdate: 2019-06-09 09:56:49tag: taglayout: tag\n\n\n方知要将 tags 改作 tag \n对于 category 也是同理\n","tags":["HEXO"]},{"title":"How to start a new post","url":"//2023/05/04/How-to-start-a-new-post/","content":"发布文章发布在首页进入 HEXO root dirction ,右键打开 GIT Bash Here创建 new post :\nhexo n \"NAME\" # n: name\n会在 source/_pots 文件夹中生成 NAME.md 以及一个名为 NAME.md 的文件夹,可以在该文件夹中放入需要使用到的图片。\n图片插入方法可见:How to use tags plugin\n仅发布文章hexo n page \"NAME\"\n\n会在 source 文件夹中生成 NAME\n页面不会出现在首页文章列表和归档中,也不支持添加 categories 和 tags 。\n\n渲染并部署运行以下代码来将 post 渲染并部署到 Github Pages 上:\nhexo g # 生成页面 g: generatehexo d # 部署发布 d: deploy\n\n若修改并部署后没有效果,使用 hexo clean 清理缓存后重新部署。\n\nhexo clean\n清除缓存文件 db.json 和已生成的静态文件 public.\n预览运行 hexo g 前可以先运行以下代码来获得网页预览效果:\nhexo s # 启动本地服务器,用于预览主题 s: sever\n\n预览的同时可以修改文章内容或主题代码,保存后刷新页面即可\n对 source 下的 _config.yml 的修改,需要重启本地服务器后才能预览效果\n\n草稿相当于私密 post \nhexo new draft \"NAME\"\n会在 source/_drafts 目录下生成一个 NAME.md 文件,但不被显示在页面上,也访问不到其链接。\n如果想把某一篇文章移除显示,可以把它移动到 _drafts 目录中。\n\n运行以下代码来把草稿变成 post :hexo publish [layout] NAME\n更改配置文件以强行预览草稿:render_drafts: true\n或用以下方式打开服务器:hexo s --drafts\n\n相关说明\n以上命令使用基于 Hexo 6.3.0\n更多命令用法请查询 https://hexo.io/zh-cn/docs/commands.html\n\n","tags":["HEXO"]},{"title":"How to use tags plugin","url":"//2023/05/27/How-to-use-tags-plugin/","content":"标签插件(Tag Plugins)标签插件不应该被包裹在 Markdown 语法中\n例如: []({% post_path lorem-ipsum %}) 是不被支持的。\n![name](position/pic_name.type)\n// on web\"pic\"pic_name\n\nhexo的处理方式:\n\n引用块(block quote)/引用(quote)在文章中插入引言,可包含作者、来源和标题。\n{% blockquote [author[, source]] [link] [source_link_title] %}content{% endblockquote %}\n引用文章引用其他文章的链接。\n{% post_path filename %}{% post_link filename [title] [escape] %}\n在使用此标签时可以忽略文章文件所在的路径或者文章的永久链接信息、如语言、日期。\n例如,在文章中使用 {% post_link how-to-bake-a-cake %} 时,只需有一个名为 how-to-bake-a-cake.md 的文章文件即可。即使这个文件位于站点文件夹的 source/posts/2015-02-my-family-holiday 目录下、或者文章的永久链接是 2018/en/how-to-bake-a-cake,都没有影响。\n默认链接文字是文章的标题,你也可以自定义要显示的文本。\n默认对文章的标题和自定义标题里的特殊字符进行转义。可以使用escape选项,禁止对特殊字符进行转义。\n嵌入图片自 hexo-renderer-marked 3.1.0 以后可以启用自动路径解析\n在 config.yml 内:\npost_asset_folder: truemarked: prependRoot: true postAsset: true\n启用后,资源图片将会被自动解析为其对应文章的路径\n例如:\nimage.jpg 位置为 /2020/01/02/foo/image.jpg ,这表示它是 /2020/01/02/foo/ 文章的一张资源图片, ![](image.jpg) 将会被解析为 \n<img src=\"/2020/01/02/foo/image.jpg\">\n参见:https://hexo.io/zh-cn/docs/asset-folders\n默认(无选项){% asset_img foo.jpg %}\n<img src=\"/2020/01/02/hello/foo.jpg\">\n自定义 class 属性{% asset_img post-image foo.jpg %}\n<img src=\"/2020/01/02/hello/foo.jpg\" class=\"post-image\">\n展示尺寸{% asset_img foo.jpg 500 400 %}\n<img src=\"/2020/01/02/hello/foo.jpg\" width=\"500\" height=\"400\">\ntitle 和 alt 属性{% asset_img logo.svg \"lorem ipsum'dolor'\" %}\n<img src=\"/2020/01/02/hello/foo.jpg\" title=\"lorem ipsum\" alt=\"dolor\">\n","tags":["HEXO"]},{"title":"Todo list (1)","url":"//2023/06/04/Todo-list-1/","content":"Todo\n备份hexo\n美化hexo制作的表格\n更换render,让hexo支持TeX\n解决主页面翻页键不显示问题\n\n","tags":["todo"]},{"title":"Linked List","url":"//2023/04/29/Linked-List/","content":"链表 Linked list引入链表是一种用于存储数据的数据结构,通过如链条一般的指针来连接元素。它的特点是插入与删除数据十分方便,但寻找与读取数据的表现欠佳。\n\n与数组的区别链表和数组都可用于存储数据。与链表不同,数组将所有元素按次序依次存储。不同的存储结构令它们有了不同的优势:\n链表因其链状的结构,能方便地删除、插入数据,操作次数是 $O(1)$。但也因为这样,寻找、读取数据的效率不如数组高,在随机访问数据中的操作次数是 $O(n)$。\n数组可以方便地寻找并读取数据,在随机访问中操作次数是 $O(1)$。但删除、插入的操作次数是 $O(n)$ 次。\n\n程序包含头文件#include <iostream>#include <cstdlib>using namespace std;\n\n创建一个链表\nTip\n构建链表时,使用指针的部分比较抽象,光靠文字描述和代码可能难以理解,建议配合作图来理解。\n\n单向链表单向链表中包含数据域和指针域,其中数据域用于存放数据,指针域用来连接当前结点和下一节点。\n\n\n实现\n C typedef struct Node_t{ struct Node_t* next; int key;} Node;\n\n C++ struct Node{ int value; Node* next;};\n\n\n图示\n\n\n\n双向链表双向链表中同样有数据域和指针域。不同之处在于,指针域有左、右(或上一个、下一个)之分,用来连接上一个结点、当前结点、下一个结点。\n\n实现\nstruct\n图示\n\n\n\n\n向链表写入数据单向列表\n流程\n初始化待插入的数据 node;\n将 node 的 next 指针指向 p 的下一个结点;\n将 p 的 next 指针指向 node。\n\n\n图示\n\n\n\n\n\n\n\n\n\n\n实现\n void insertNode (int temp, Node* p){ Node* node = new Node; node->value = temp; node->next = p->next; p->next = node;}\n\n单向循环链表将链表的头尾连接起来,链表就变成了循环链表。由于链表首尾相连,在插入数据时需要判断原链表是否为空:为空则自身循环,不为空则正常插入数据。\n\n流程\n初始化待插入的数据 node;\n判断给定链表 p 是否为空;\n若为空,则将 node 的 next 指针和 p 都指向自己;\n否则,将 node 的 next 指针指向 p 的下一个结点;\n将 p 的 next 指针指向 node。\n\n\n图示\n\n\n\n\n\n\n\n\n\n\n实现\n void insertNode (int temp, Node* p){ Node* node = new Node; node->value = temp; node->next = nullptr; if (p == nullptr){ p = node; node->next = node; } else { node->next = p->next; p->next = node; }}\n\n\n从链表中删除数据单向(循环)链表设待删除结点为 p,从链表中删除它时,将 p 的下一个结点 p->next 的值覆盖给 p 即可,与此同时更新 p 的下下个结点。\n\n流程\n将 p 下一个结点的值赋给 p,以抹掉 p->value;\n新建一个临时结点 t 存放 p->next 的地址;\n将 p 的 next 指针指向 p 的下下个结点,以抹掉 p->next;\n删除 t。此时虽然原结点 p 的地址还在使用,删除的是原结点 p->next 的地址,但 p 的数据被 p->next 覆盖,p 名存实亡。\n\n\n图示\n\n\n\n\n\n\n\n\n\n\n实现\n void deleteNode (Node* p){ p->value = p->next->value; Node *t = p->next; p->next = p->next->next; delete t;}\n\n","categories":["Data Structure","Linked List"],"tags":["Data Structure"]},{"title":"Use Mermaid in Hexo","url":"//2023/06/20/Use-Mermaid-in-Hexo/","content":"Mermaidhttps://blog.csdn.net/wzh0709zml/article/details/103310405\n","tags":["HEXO"]},{"title":"translation","url":"//2023/10/11/translation/","content":"为读论文更方便而建\nGA Genetic Algorithm\nNN eural network-based framework\nSVM support vector analysis\nCDG Coverage-Directed test Generation\nDUT design under test\nrule learning\nconstrain model\nProbabilistic model\n\n目标\n找到针对high-level features/patterns的ML算法以找到解决同一类verification问题(避免复杂分析)\n通常分为Test Set Redundancy Reduction\n与Test Complexity Reduction\n\n\n应用ML方法来解决EDA design中的”coverage”问题\n\nTest Set Redundancy Reduction具体方法列举digital design\nStatistical model\nSearch Methods\nRule learning\nCNN, SVM\nGCN\n\nanalog/RF design\nKNN, ONN\nRegression\n\nsemiconductor\nCNN\n\nTest Complexity Reduction具体方法列举digital design\nSVM, MLP, CNN\n\nanalog/RF design\nONN\nactive learning\n\n论文创新点\nGRIP (GRaph Inference Processor)用于使GNN更low-latency\nReAAP,可重构且面向算法的阵列处理器,用于加速各种深度学习工作负载\nGHOST,第一个基于硅光子的 GNN 加速器,可以加速各种 GNN 模型和图的推理(对于semicondector)\n解决以下问题:有效处理大图/动态图表是很有挑战性的。\n明确动态和流式图处理中不同概念的含义,分析了与图数据库领域以及流和动态图算法理论的联系。\n提供图流框架的第一个分类法,识别和分析其设计中的关键维度,包括数据模型和组织、并发执行、数据分布、目标架构等\n\n\n\n","categories":["article","EDA verification"],"tags":["EDA verification"]},{"title":"宿主机连接到VMWare","url":"//2024/03/11/%E5%AE%BF%E4%B8%BB%E6%9C%BA%E8%BF%9E%E6%8E%A5%E5%88%B0VMWare/","content":"参考https://www.p-chao.com/2020-10-06/ssh%E8%BF%9E%E6%8E%A5%E5%88%B0ubuntu%E8%99%9A%E6%8B%9F%E6%9C%BA/\n","categories":["VMWare"],"tags":["VMWare"]},{"title":"git pull保留本地修改","url":"//2024/03/12/git-pull%E4%BF%9D%E7%95%99%E6%9C%AC%E5%9C%B0%E4%BF%AE%E6%94%B9/","content":"用stash暂时保留本地代码git stash\n\n拉取远程代码覆盖本地git pull origin main\n注意这里本人使用\ngit pull origin master\n则会报错:\ngit pull origin master fatal: couldn't find remote ref master\n\n合并本地暂存代码git stash pop\n","categories":["git"],"tags":["git"]}]