美文网首页@future
C++面向对象

C++面向对象

作者: 不返y | 来源:发表于2023-12-08 21:51 被阅读0次

    二、C++面向对象

    c++内存的四个区域

    1.代码区

    存放函数体的二进制的代码,由操作系统管理,两个特点 共享 可读。

    2.全局区

    存放全局变量、静态变量以及常量。

    常量包括const修饰的全局常量和字符串常量

    (全局变量和局部变量不会放在同一个内存区域中)

    (全局变量和静态变量放在同一个内存区域中)

    3.栈区

    由编译器自动分配,存放函数参数d,局部变量,

    注意:不要返回局部变量的地址。函数返回局部变量地址后第一次使用可以正常使用,这是因为编译器做了保留,第二次就不能调用了。(不要返回局部变量地址)

    4.堆区

    由程序员分配,也由程序员释放,如果程序员不释放,会在程序结束后由操作系统自动回收。

    4.1 在堆中开辟空间new,如:

    int * p = new int(10);

    4.2 释放内存delete

    delete p; --p是指向堆中内存的指针。

    如果p是一个数组指针 ,使用delete[] p;

    引用(相当于起 别名

    1.引用格式&

    int b = 10;
    int& a = b;
    

    等同于a和b指向同一块空间。

    2.注意事项

    1. 引用必须初始化
    2. 引用定义后,不可修改引用

    3.用处

    1. 引用传递&,相当于指针传递;代码更加简洁
    #include <iostream>
    using namespace std;
    //交换两个数字
    void function(int& a,int& b)
    {
        int temp = a;
        a = b;
        b = temp;
    }
    int main()
    {
        int a = 1;
        int b = 2;
        function(a,b);
        cout << a << b << endl;
    }
    
    1. 返回引用
    #include <iostream>
    using namespace std;
    //把b的值加到a上并返回a
    int& add(int& a, int& b)
    {
        a = a + b;
        return a;
    }
    int main()
    {
        int a = 1;
        int b = 2;
        int& temp = add(a, b);
        cout << temp << endl;
    }
    

    注意:不要返回局部变量的引用(局部变量出了作用域会自动释放)

    下面的代码虽然可以的得到正确的结果,但是他是不安全的,因为它访问了理论上已经被释放的变量temp。在部分编译器上可能无法得到正确运行。。

    #include <iostream>
    using namespace std;
    //返回局部变量
    int& function()
    {
        int temp = 1;
        return temp;//不安全的,不要这样写
    }
    int main()
    {
        int& temp = function();
        cout << temp << endl;
        cout << temp << endl;
    }
    

    函数返回引用后,函数可以作为左值,即就当成是个变量。可进行如下的赋值操作,如:

    #include <iostream>
    using namespace std;
    int& function(int& a)
    {
        a = 1;
        return a;
    }
    int main()
    {
        int a = 0;
        function(a) = 2;  //相当于有 a = 2;
        cout << a << endl;
    }
    

    程序执行结果为2。

    4.引用的底层原理

    引用传递的底层实际上是一个指针,其取地址和解引用操作是自动的。方便写程序。

    int&引用的实际类型为int* const ref指针常量,这也是为什么引用不能修改的原因,因为指针的指向不可改变。

    特殊写法(引用常量):

    const int& a = 123;

    解释:实际上就相当于 是

    int temp = 123;

    const int& ref = temp

    这两行代码

    函数

    1.函数默认参数

    参数有默认值时允许不传值,如果传了值,就不使用默认使用传递的及具体值。

    基础代码(不用默认值)

    //返回三个数的和
    int add(int a, int b,int c)
    {
        return a + b + c;
    }
    int main()
    {
        int re = add(1, 2, 3);
        cout << re << endl;
        system("pause");
        return 0;
    }
    

    使用默认值的代码

    int add(int a, int b = 2,int c = 3) //使用了默认值b默认为2,c默认为3.
    {
        return a + b + c;
    }
    int main()
    {
        int re = add(1);  //此处调用,实际相当于add(1,2,3);
        cout << re << endl;
        system("pause");
        return 0;
    }
    

    书写规则,有默认值得参数一定要写在未提供默认值的参数后。

    也可以在声明里面写默认,但是和函数定义时的默认值不能重复(声明时写了,定义时就不能写)

        int add(int, int = 1, int = 2);//后面的占位参数写法
        int main()
        {
            int re = add(1);
            cout << re << endl;
            system("pause");
            return 0;
        }
        int add(int a, int b, int c)
        {
            return a + b + c;
        }
    

    2.函数的占位参数

    占位参数可以只写类型。目前了解,暂不涉及。

    如上个代码add函数的参数就是占位参数,而且占位参数也可以有默认参数。

    3.函数重载

    条件:

    1. 在一个作用域下
    2. 函数名一样
    3. 形参不一样(要么个数不一样,要么类型不一样,要么类型的顺序不一样)

    注意:对返回值没有要求。

    易错点:

    函数重载时尽量不要加默认参数,否则调用时可能出现二义性。尽量避免。

    类class

    1.使用

        const double PI = 3.1415926;
    
        //定义圆Circle类
        class Circle 
        {
        public:  //声明下列成员的权限为public
            double m_r;
            double getZC() {
                return 2 * m_r * PI;
            }
        };
    
        int main()
        {
            Circle cir; //创建圆类的对象
            cir.m_r = 2;   //给内部成员变量r半径赋值
            double re = cir.getZC();  //调用内部成员函数
            cout << re << endl;
            system("pause ");
            return 0;
        }
    

    2.修饰符

    也是三种

    public private protect

    关于struct与class的区,他们其实差不多,不同点也就是默认权限

    struct默认的权限public

    class默认的权限private

    3.初始化与清理

    构造函数与析构函数

    构造函数就是初始化的函数;析构函数是对象被释放时调用的函数(通常用来释放内部的其他内存,如堆上的未释放空间)。

    3.1 构造函数

    3.1.1语法
    类名 ()
    {
        //内容
    }
    
    //如:
    Circle()//圆的空构造函数
    {
        
    }
    

    函数名就是类名,没有返回值,也不写返回值。

    可以有参数,可以重载构造函数,程序创建对象时一定会调用构造函数,不用手动调用。

    3.1.2 析构函数

    ~类名 (){}

    与构造函数基本一致,不同的是它没有参数。只会在对象销毁的时候哦调用。只需在构造函数前加~.

    4.构造函数

    4.1 类型

    1. 按有无参数分

      1. 无参
      2. 有参
    2. 按构造方式分

      1. 普通构造

      2. 拷贝构造

        拷贝构造函数自动调用的时机:

        1. 以传参的方式把对象传给函数形参
        2. 以函数返回值的形式自动返回拷贝对象
        const double PI = 3.1415926;
        class Circle 
        {
        public:
            
            double m_r;//成员变量习惯上加 m_ 以区分。
    
            //无参构造
            Circle() {}
    
            
            //有参构造
            Circle(int r) 
            {
                this->m_r = r;    
            }
            
            //拷贝构造函数
            Circle(const Circle& circle) {
                this->m_r = circle.m_r;
            }
            
            double getZC() {
                return 2 * this->m_r * PI;
            }
        };
    

    4.2 调用构造函数

    三种方法,括号,显式,隐式,分别如下:

    const double PI = 3.1415926;
        class Circle 
        {
        public:
            
            double m_r;//成员变量习惯上加 m_ 以区分。
    
            //无参构造
            Circle() {}
    
            
            //有参构造
            Circle(int r) 
            {
                this->m_r = r;    
            }
            
            //拷贝构造函数
            Circle(const Circle& circle) {
                this->m_r = circle.m_r;
            }
            
            double getZC() {
                return 2 * this->m_r * PI;
            }
        };
        int main()
        {
            //1.括号
            Circle cir11;//无参
            cir11.m_r = 11;
    
            Circle cir12(12);//有参
            Circle cir13(cir11);//拷贝
    
            //2.显式
            Circle cir21 =  Circle();
            cir21.m_r = 21;
    
            Circle cir22 = Circle(22);
            Circle cir23 = Circle(cir21);
    
            //3.隐式
            Circle cir32 = 32;
            Circle cir33 = cir32;
    
            cout << cir11.m_r << " ";
            cout << cir12.m_r << " ";
            cout << cir13.m_r << endl;
            cout << cir21.m_r << " ";
            cout << cir22.m_r << " ";
            cout << cir23.m_r << endl;
            cout << cir32.m_r << " ";
            cout << cir33.m_r << endl;
            system("pause");
            return 0;
        }
    

    注:对于Circle(10);这行代码,没有用变量接收,属于一个匿名对象。特点:执行完当前行后立刻释放

    4.3默认实现构造函数

    默认提供的函数实现的功能

    无参构造(空);

    有参构造(所有值的赋值);

    拷贝构造(所有值的拷贝)(浅拷贝,深拷贝需要自己实现);

    不提供默认函数的规则:

    1. 如果提供有参构造,编译器不再提供无参构造;、
    2. 如果提供拷贝构造,编译器不再任何构造;

    4.4初始化列表

    另一种对象初始化方式。

    #include <iostream>
    using namespace std;
    #include<string>
    class student
    {
    public:
        string m_name;
        int m_age;
        string m_gender;
        student() :m_name("未知"), m_age(0), m_gender("未知")//默认初始化序列表
        {
    
        }
        student(string name, int age, string gender) :m_name(name), m_age(age), m_gender(gender) //有参初始化列表
        {
        
        }
        void print()
        {
            cout << "name: " << m_name << ",age: " << m_age << ",gender: " << m_gender << endl;
        }
    };
    int main()
    {
        student stu;
        stu.print();
    }
    

    5.对象内的成员对象

    对象内的一个成员变量是另一个对象,则这个对象称为成员对象

    构造·有成员对象的·对象时,会先去构建自身的成员对象,再去完成构造自己。(构造函数调用的先后顺序)

    销毁会先销毁成员变量,再销毁自己。(析构函数调用的先后顺序)

    6.静态成员

    6.1 静态成员变量

    所有对象共享同一内存(共享静态成员变量);

    一般可以类内定义,类外初始化。(注:初始化静态成员变量不受权限限制)

    用类名访问的方式:int 类名::静态成员变量

    6.2 静态成员函数

    静态成员函数只能访问静态成员变量。也可以通过类名直接访问。

    7.对象的内存

    c++会给每个空对象1个字节的空间为了区分,其占用的内存位置

    关于对象的内存,(记忆:共享的数据不属于对象的空间,如函数,和静态变量)

    class student
    {
        int m_A;//成员变量,属于类的对象上的空间
        static int m_B;//静态成员变量,不属于类的对象上的空间
        void func1()// 成员函数,不属于类的对象上的空间
        {
    
        }
        static void func2()//静态成员函数,不属于类的对象上的空间
        {
    
        }
    };
    

    结论:只有非静态成员变量才会占用对象的内存。

    8.this指针

    非静态函数内都有一个this指针,指向调用函数的对象地址。

    作用:

    1. 可以使用this->成员变量访问类内的成员变量。
    2. 可以使用return *this;返回调用对象自己,方便链式编程。

    c++编写成员变量习惯,成员变量写m_名字,用于区分

    c++中,空指针可以访问成员方法,但是如果成员方法里使用了this或者访问了空指针的内部成员变量,就会出现错误。

    可以再成员函数内判断if(this==NULL)return;

    提高代码的健壮性

    9.const修饰对象成员

    9.1 修饰成员函数

    const修饰的成员函数称为常函数

    特点:常函数不可以修改成员函数的值,除非成员函数声明时加了mutable关键字。 如mutable int m_A;

    9.2 修饰成员变量

    const修饰的成员函数称为常变量

    特点:常变量不能被修改,只能调用·常函数。

    10.friend友元(让其他元素可以访问类内私有的部分)

    1. 全局函数做友元(好朋友,可以访问私有内容)

    可以使全局函数访问类中的私有内容

    #include <iostream>
    using namespace std;
    
    class Bank_Card
    {
        friend void function(Bank_Card* bc);//使全局函数void function()可以访问类中的私有内容int password
    public:
        Bank_Card(int password)  //有参构造函数
        {
            this->m_password = password;
        }
    private:
        int m_password;//私有成员变量
    };
    void function(Bank_Card* bc)
    {
        cout << bc->m_password << endl;  //可以得到结果123456
    }
    int main()
    {
        Bank_Card bc(123456);
    
        function(&bc);
        
        system("pause");
        return 0;
    }
    
    1. 类做友元

    可以使另一个类访问该类的私有内容。

    #include <iostream>
    using namespace std;
    
    class Bank_Card
    {
        friend class Person;//使Person类可以访问类中的私有内容int password
    public:
        Bank_Card(int password)  //有参构造函数
        {
            this->m_password = password;
        }
    private:
        int m_password;//私有成员变量
    };
    
    class Person
    {
    public:
        void getPassword(Bank_Card* bc)
        {
            cout << bc->m_password << endl;  //可以得到结果123456
        }
    };
    
    int main()
    {
        Bank_Card bc(123456);
    
        Person person;
    
        person.getPassword(&bc); //通过person访问Bank_Card中的私有成员password
        
        system("pause");
        return 0;
    }
    
    1. 成员函数做友元

    可以使成员函数访问类中的私有内容(很难,因为c是按照顺序从上到下执行的,很多事情需要声明, 要注意顺序)

    #include <iostream>
    using namespace std;
    
    class Bank_Card; //1.先声明Bank_Card类,让Person类知道有这个类,不至于报错
    
    class Person
    {
    public:
        void getPassword(Bank_Card* bc);  //2.再声明Person中的getPassword函数,让Bank_Card知道他的存在
    };
    
    class Bank_Card
    {
        friend void Person::getPassword(Bank_Card* bc);//3.使Person中的getPassword函数可以访问类中的私有内容int password
    public:
        Bank_Card(int password)  //有参构造函数
        {
            this->m_password = password;
        }
    private:
        int m_password;//私有成员变量
    };
    
    
    
    void Person::getPassword(Bank_Card* bc)
    {
        cout << bc->m_password << endl;  //4.最后再实现getPassword内容,确保实现的时候已经定义过Bank_Card中的m_password成员变量
    }
    
    int main()
    {
        Bank_Card bc(123456);
    
        Person person;
    
        person.getPassword(&bc);
        
        system("pause");
        return 0;
    }
    

    11.在类外创建成员函数

    类名 :: 函数和初始化函数的格式一致。

    运算符重载

    1.加法+运算符

    函数的名字可以写成operator+

    这样可以实现 + 运算符 的重载,

    也可以在类内部的成员变量实现,相当于给类之间的运算增加重载。

    本质上还是调用函数,只不过是有一种简写的形式。

    1.1 类内重载

    #include <iostream>
    using namespace std;
    
    
    class Number {
    public:
        int m_a;
        int m_b;
    
        Number() {}
    
        Number(int a, int b) {
            m_a = a;
            m_b = b;
        }
    
        //加法+的重载,类内
        Number operator+ (Number& number) {
            return Number(number.m_a + m_a, number.m_b + m_b);
        }
    
    };
    int main()
    {
        Number n1(1, 2);
        Number n2(3, 4);
        Number n3 = n1 + n2;
        cout << n3.m_a << endl; //结果为: 4
        cout << n3.m_b << endl; //结果为: 6
        system("pause");
        return 0;
    }
    

    1.2函数外的重载

    #include <iostream>
    using namespace std; 
    
    
    class Number {
        public:
        int m_a;
        int m_b;
    
        Number(){}
    
        Number(int a, int b) {
            m_a = a;
            m_b = b;
        }
    };
    
    //类外的重载函数
    Number operator+ (Number& n1,Number& n2) {
        return Number(n1.m_a + n2.m_a, n1.m_b + n2.m_b);
    }
    
    int main()
    {
        Number n1(1, 2);
        Number n2(3, 4);
        Number n3 = n1 + n2;
        cout << n3.m_a << endl; //结果为: 4
        cout << n3.m_b << endl; //结果为: 6
        system("pause");
        return 0;
    }
    

    注:1。内置的数据类型运算是不可更改的。2.不要滥用重载。

    2.左移<<运算符重载

    一般用来指定自定义类的输出格式,一般不写在类内,即不用成员函数写,而用全局函数如:

    ostream& operator << (ostream& cout,Number& n) 
    {
        cout << "number.m_a = " << n.m_a << ",number.m_b = " << n.m_b;
        return cout;
    }
    

    注意,如果类中的变量是私有的可以使用友元去访问。

    3.递增++运算符重载

    #include <iostream>
    using namespace std;
    
    class Number {
    public:
        int m_a;
        int m_b;
    
        Number(int a, int b) {
            m_a = a;
            m_b = b;
        }
    
        Number(Number& n)
        {
            this->m_a = n.m_a;
            this->m_b = n.m_b;
        }
    
        //重载前置++运算符, 
        Number& operator++()
        {
            this->m_a++;      //实现内容:两个数字都自增
            this->m_b++;    
            return *this;     //细节:返回引用实现链式编程
        }
    
        //重载后置++运算符, 
        Number& operator++(int)//占位int,可以认为是后置++的要求写法
        {
            Number temp(*this); //临时存储,因为后置++,返回值为未操作的值(注意,这样实现不支持链式编程)
            this->m_a++;      //实现内容:两个数字都自增
            this->m_b++;
            return *this;     //细节:返回引用实现链式编程
        }
    };
    
    int main()
    {
        Number n(1, 2);
        ++n;
        cout << n.m_a << endl; //结果为: 2
        cout << n.m_b << endl; //结果为: 3
        n++;
        cout << n.m_a << endl; //结果为: 3
        cout << n.m_b << endl; //结果为: 4
        system("pause");
        return 0;
    }
    

    4.赋值运算符=重载

    c++的类中实际默认实现有提供4个函数,前三个 构造函数 拷贝构造 析构函数

    第四个是=的默认重载,和拷贝函数的默认实现一样。

    <span style="color:red">注意:深拷贝时要先判断,源对象在堆中是否有数据,然后再清除,再进行深拷贝。</span>

    5.关系运算符== 和!=重载

    和前面差不多,返回bool就行。

    6.函数调用运算符()重载

    因为类似函数调用,所以也叫仿函数

    了解:匿名函数对象

    下面的Myadd是实现了函数调用(括号)运算符重载的类。MyAdd()为构造对象的函数,后面的(100,100)为调用的括号()重载

    cout << MyAdd()(100,100) << endl;

    继承

    1.继承格式

    class 子类 : 继承方法 父类
    
    {
    
        //内容
    
    }
    
    //如:
    //Base是Son继承的父类
    class Son :public Base
    {
        
        //内容
        
    }
    

    继承方法有public、protected、private

    public:直接继承所有内容,最低访问权限为public(可以继承但是不能访问父类中的private内容)

    protected:继承所有内容,最低访问权限为protected(但是不能访问父类中的private内容)

    private:继承所有内容,最低访问权限为private(但是不能访问父类中的private内容)

    2.继承相关问题

    2.1 父类与子类的构造与析构顺序

    子类被创建前,一定会先创建父类对象。析构顺序相反。

    即 子类的创建与销毁顺序是:【父类构造】-->【子类构造】-->【子类析构】-->【父类析构】

    2.2 子类与父类的成员重名

    如果重名,直接调用子类的方法,而不是父类。

    如果想调用父类的,需要加上父类的作用域,如:

    son是一个Son的对象,Base是Son的父类

    son.Base::m_A; //这里访问的就是父类Base里的m_A

    如果子类中有与父类中的重名函数,则会将父类中的所有相同名字的成员函数全部隐藏(包括重载)。若想调用则需加作用域。

    同名静态成员,操作与普通成员一致。

    2.3 继承信息的查看工具

    继承信息的查看工具

    3.多继承语法

    3.1 格式

    class 子类 : 继承方法 父类,继承方法 父类,...
    
    {
    
        //内容
    
    }
    
    //如:
    // Base1等 都是Son继承的父类
    class Son :public Base1,public Base2,...
    {
        
        //内容
        
    }
    

    3.2 问题

    3.2.1 重名

    父类中出现了重名的内容需要使用作用域区分。

    3.2.2 菱形继承(重复继承问题)

    设有四个类,Class1,Class2,Class3,Class4

    其中Class2和Class3继承Class1 , Class4又继承了Class2和Class3 。

    这就是菱形继承,常见的重复继承,Class4等于继承了两次Class1。

    解决方案:虚继承 ::virtual 内部实际是继承 指针数据 保证数据只有一份。

    正常写代码时,不建议使用多继承的操作,仅仅作为了解

    格式:

    class 子类 :virtual 继承方法 父类
    
    {
    
        //内容
    
    }
    
    //如:
    //Base是Son继承的父类
    class Son :virtual public Base 
    {
        
        //内容
        
    }
    

    多态

    1.概念

    子类可以使用父类的名字。

    2.多态的问题

    2.1 调用的函数是父类的还是子类的

    静态多态(早绑定)、一定是调用者的函数

    动态多态(晚绑定):看调用者的实际类型所重写的函数(和java一致) (使用虚函数:virtual )

    动态绑定底层实际也是一个虚函数指针。原理:使用函数指针,从父类到子类的继承就是一个不断覆盖函数指针的过程。

    纯虚函数

    写法 :

    virtual void func() = 0;  //纯虚函数
    

    如果一个类中有一个纯虚函数,则这个类称为抽象类。(类似于java中的抽象函数与抽象类)

    特点:

    1. 抽象类无法实例化对象;
    2. 子类如果不重写纯虚函数,则子类也是抽象类。

    虚析构和纯虚析构

    虚析构和纯虚析构的存在是为了使子类可以释放自己的所有内存。主要是堆上的数据的释放。使用方法和虚函数和纯虚函数基本一致,细节如下:

    区别 有纯虚析构无法实例化对象,但是如果是有虚析构可以实例化对象(类内没有其他的虚函数)。

    但是纯虚析构一般需要代码实现,可以先声明函数=0;,再在函数外实现函数体。

    文件操作

    1.准备

    进行文件操作的头文件<fstream>

    文件类型

    1. 文本文件,以ASSCII码存储
    2. 二进制文件,以二进制存储

    操作文件的类型

    1. 读ifstream
    2. 写ofstream
    3. 读写fstream

    2.基本操作

    1. 包含头文件

      #include<fstream>

    2. 创建流对象

      流对象

    3. 打开文件

      流对象.open("文件路径",打开方式1|打开方式2);

      也可以在创建时直接传参,两步合一步

      打开方式

    注:如果是读,要判断文件是否打开成功。

    判断方式流对象.is_open() ,返回值为bool:成功为true,失败为false。

    1. 写或读

      流对象<<流对象>>

      非二进制的读文件的四种方式

      //第一种:
      char buf[1024] = {0};
      while(ifs>>buf)
      {
       cout<<buf<<endl;
      }
      
      //第二种
      char buf[1024] = {0};
      while(ifs.getline(buf,sizeof(buf)))
      {
       cout<<buf<<endl;
      }
      
      //第三种
      string buf;
      while(getline(ifs,buf))
      {
       cout<<buf<<endl;
      }
      
      //第四种
      char c;
      while((c=ifs.get())!=EOF)
      {
       cout<<c;
      }
      cout<<endl;
      

      二进制的读写

      流对象.read

      流对象.write

    2. 关闭文件

      流对象.close();

    补充:

    #pragma once          //防止头文件重复包含
    #include <iostream>       //包含输入输出流头文件
    using namespace std;      //使用标准命名空间
    

    相关文章

      网友评论

        本文标题:C++面向对象

        本文链接:https://www.haomeiwen.com/subject/ajahgdtx.html