指针引用

指针也是一种变量,作为函数形参和返回值的时候也是值拷贝(拷贝的是一个地址) 使用指针引用的方式,代码更加简洁,逻辑更加清晰 释放掉指针指向的空间后,一定记得把指针置空 声明指针时就进行初始化或将其指向 NULL 是个好习惯 指针值拷贝: void test(int * a){ a = new int(20); //*a = 20; cout << "test point a = " << &a << endl; cout << "test:" << a << "\ta = " << *a << endl; } int main() { int *a = new int(10); cout << "main point a = " << &a << endl; cout << "main:" << a << "\ta = " << *a << endl; test(a); cout << "main:" << a << "\ta = " << *a << endl; return 0; } 输出:...

December 25, 2021 · 2 min · Rick Cui

抽象、封装、继承、多态

一、抽象、封装 数据抽象:是一种仅向用户暴露接口而把具体的实现细节隐藏起来的机制,是一种依赖于接口实现分离的设计技术 数据封装:是一种把数据和操作数据的函数捆绑在一起的机制 1. 好处 类的内部受到保护,不会因无意的用户级错误导致对象状态受损 类实现可能随着时间的推移而发生变化,数据抽象可以更好的应对不断变化的需求 2. 策略 通常情况下,我们都会设置类成员状态为私有(private),除非我们真的需要将其暴露,这样才能保证良好的封装性。 抽象把代码分离为接口和实现。所以在设计组件时,必须保持接口独立于实现,这样,如果改变底层实现,接口也将保持不变。在这种情况下,不管任何程序使用接口,接口都不会受到影响,只需要将最新的实现重新编译即可 3. 接口 接口描述了类的行为和功能,而不需要完成类的特定实现。如果类中至少有一个函数被声明为纯虚函数,则这个类就是抽象类 设计抽象类(通常称为 ABC)的目的,是为了给其他类提供一个可以继承的适当的基类。抽象类不能被用于实例化对象,它只能作为接口使用。因此,如果一个 ABC 的子类需要被实例化,则必须实现每个虚函数,如果没有在派生类中重写纯虚函数,就尝试实例化该类的对象,会导致编译错误。 可用于实例化对象的类被称为具体类 接口的好处实现了解耦合的作用。 可以将软件架构分为业务逻辑层、抽象层和实现层 二、继承 继承代表了 is a 关系。例如,哺乳动物是动物,狗是哺乳动物,因此,狗是动物,等等。一个类可以派生自多个类,这意味着,它可以从多个基类继承数据和函数。类派生列表以一个或多个基类命名 派生类可以访问基类中所有的非私有成员,同时,一个派生类继承了所有的基类方法,但下列情况除外: 基类的构造函数、析构函数和拷贝构造函数 基类的重载运算符 基类的友元函数 三、多态 虚函数:虚函数是在基类中使用关键字 virtual 声明的函数。在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链编到该函数。我们想要的是在程序中任意点可以根据所调用的对象类型来选择调用的函数,这种操作被称为动态链编,或后期绑定。 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数 一般要将父类的析构函数设置为虚函数,如果不把父类的析构函数设置为虚函数,在 delete 父类指针时就不会调用子类的析构了 类析构顺序:1)派生类本身的析构函数;2)对象成员析构函数;3)基类析构函数 若在基类中不能对虚函数给出有意义的实现,这个时候就会用到纯虚函数,在函数参数后直接加 = 0 告诉编译器,函数没有主体,这种虚函数即是纯虚函数 测试类: // 基类 Shape class Shape { protected: int width, height; public: Shape(int a = 0, int b = 0) { width = a; height = b; } virtual ~Shape() { cout << "Shape destructor" << endl; } // pure virtual function virtual int area() = 0; }; // 基类 PaintCost class PaintCost { public: int getCost(int area) { auto res = area * 70; cout << "PaintCost: " << res << endl; return res; } }; class Rectangle: public Shape, public PaintCost { public: Rectangle(int a = 0, int b = 0):Shape(a, b) { } ~Rectangle() { cout << "Rectangle destructor" << endl; } void printPro() { // 访问父类的成员变量(不能访问父类的私有成员) cout << "width: " << width << "\theight: " << height << endl; } int area () { auto area = width * height; cout << "Rectangle class area: " << area <<endl; return area; } }; class Triangle: public Shape { public: Triangle(int a = 0, int b = 0):Shape(a, b) { } ~Triangle() { cout << "Triangle destructor" << endl; } int area () { auto area = width * height / 2; cout << "Triangle class area: " << area <<endl; return area; } }; 多继承:...

December 24, 2021 · 2 min · Rick Cui

类的静态成员

使用 static 关键字来把类成员定义为静态的。静态成员在类的所有对象中是共享的,当我们声明类的成员为静态时,这意味着无论创建多少个类的对象,静态成员都只有一个副本 如果不存在其他的初始化语句,在创建第一个对象时,所有的静态数据都会被初始化为零 不能把静态成员的初始化放置在类的定义中,但是可以在类的外部通过使用范围解析运算符 :: 来重新声明静态变量从而对它进行初始化 如果把函数成员声明为静态的,就可以把函数与类的任何特定对象独立开来。静态成员函数即使在类对象不存在的情况下也能被调用,静态函数只要使用类名加范围解析运算符 :: 就可以访问 静态成员函数没有 this 指针,只能访问静态成员(包括静态成员变量和静态成员函数) 普通成员函数有 this 指针,可以访问类中的任意成员; class A { string name {"C++"}; int id {1024}; static int objCount; public: A(); friend void printA(const A &a); void printA(); static void printC(); }; // 静态成员类外初始化 int A::objCount = 0; A::A(){ objCount++; } void A::printA(){ cout << "objCount = " << this->objCount << "\tname = " << this->name << "\tid = " << this->id << endl; } void A::printC(){ // 静态函数内没有this指针 cout << "objCount = " << objCount << endl; // error: 'this' is unavailable for static member functions // cout << "objCount = " << this->objCount << endl; } void printA(const A &a){ cout << "objCount = " << a....

December 24, 2021 · 1 min · Rick Cui

友元函数与友元类

友元利弊: 友元不是类的成员但能访问类中的私有成员。友元的作用在于提高程序的运行效率,但也破坏了类的封装。 注意事项: (1)友元关系不能被继承; (2)友元关系是单向的,不具有交换性; (3)友元关系不具有传递性; 一、友元函数 类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员 尽管友元函数的原型有在类的定义中出现过,但是 友元函数并不是成员函数 this 指针是所有成员函数的隐含参数。因此,在成员函数内部,它可以用来指向调用对象 友元函数没有 this 指针,因为友元不是类的成员。只有成员函数才有 this 指针 1. 友元函数是全局函数 class A { friend void printA(const A &a); public: void printA(); private: string name {"C++"}; int id {1024}; }; void A::printA(){ cout << "name = " << this->name << "\tid = " << this->id << endl; } // 请注意:printA() 不是任何类的成员函数 void printA(const A &a){ cout << "name = " << a....

December 24, 2021 · 2 min · Rick Cui

构造、拷贝构造、赋值构造

结论: 拷贝构造函数是函数,赋值运算符是运算符的重载; 拷贝构造函数会生成新的类对象,赋值运算符不会; 拷贝构造函数是用一个已存在的对象去构造一个不存在的对象;而赋值运算符重载函数是用一个存在的对象去给另一个已存在并初始化过的对象进行赋值; 若接受返回值的对象已经初始化过,则会调用赋值运算符,且该对象还会调用析构函数,当对象中包含指针时,会使该指针失效,因此需要重载赋值运算符,使用类似深拷贝或移动构造函数的方法赋值,才能避免指针失效。 如果只有显示的构造函数,系统会提供默认的拷贝构造; 如果显示提供了拷贝构造,系统就不会提供默认的无参构造了,用户必需显示提供构造函数; 当既没有显式的构造函数,也没有拷贝构造时,系统才会提供默认的无参构造; 显示提供拷贝构造就必需显示提供构造函数; 显示提供赋值运算符重载就必需显示提供拷贝构造; 成员初始化列表 使用成员变量初始化列表,少了一次调用默认构造函数的过程,提高效率 常量成员,因为常量只能初始化不能赋值,所以必须放在初始化列表里面 引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里面 成员变量初始化的顺序跟在初始化列表的顺序无关,与变量声明的顺序有关 测试类 class A { public: A(){ cout << "default constructor" << endl; cout << "adrres: " << this << "\tpoint x: " << x << "\ty: " << y << endl; } A(int t){ x = new int(0); y = t; cout << "second constructor" << endl; cout << "adrres: " << this << "\tpoint x: " << x << "\ty: " << y << endl; } A(const A &a){ cout << "const copy constructor" << endl; cout << "adrres: " << this << "\tpoint x: " << x << "\ty: " << y << endl; this->x = a....

December 23, 2021 · 4 min · Rick Cui