C++学习笔记——核心篇(四)

前言:从本部分开始,就进入了C++语言学习笔记核心篇中关于类与对象的相关知识,内容重要性高,其中包括面向对象编程的三大特征,以及一些重要的其他知识包括浅拷贝与深拷贝,this关键字等等。而,本部分的主要内是C++语言面向对象编程三大特征之一的“继承”的相关知识。

目录

1、面向对象简介

 2、封装

        2.1、封装的意义

                    2.1.1、封装意义一

            2.1.2、封装意义二

             2.1.3、struct 和 class 的区别

    2.2、对象初始化和清理

            2.2.1、构造函数和析构函数的简介

                2.2.1.1、构造函数

                2.2.1.2、析构函数

        2.2.2、构造函数的分类及调用

        2.2.3、构造函数的调用规则

            2.2.3.1、构造函数调用规则

        2.2.4、深拷贝与浅拷贝


1、面向对象简介

    C++面向对象的三大特征为:封装、继承、多态

    C++认为万事万物皆可为对象,对象上有其属性和行为
    eg、
        1、人可以作为对象,属性有姓名、年龄、身高、体重...,行为有走、跑、跳、吃饭、唱歌...
        2、车也可以作为对象,属性有轮胎、方向盘、车灯...行为有载人、放音乐、放空调...
        3、具有相同性质的对象,我们可以抽象地称之为类,人属于人类,车属于车类

 2、封装

        2.1、封装的意义

                    封装是C++面向对象的三大特征之一

                    封装的意义:
                        1、将属性和行为作为一个整体,表现生活中的事物
                        2、将属性和行为加以权限控制

                    2.1.1、封装意义一

                                在设计类的时候,属性和行为写在一起,表现事物

       语法:class 类名{

                            访问权限:    属性 / 行为    

                   };

//设计一个圆类,求圆的周长
//圆周率
const double PI = 3.14;
//圆的周长公式:2 * PI * 半径

//class 代表设计一个类,类后面紧跟着的就是类名称
class Circle
{
    //访问权限
    //公共权限
    public:

    //属性(常常用变量)
    int m_r;    //半径


    //行为(常常用函数)
    double calculateZC()    //获取圆的周长
    {
            return 2 * PI * m_r;
    }
};

int main()
{
    //通过圆类 创建具体的圆(对象)
    //实例化 (通过一个类 创建一个对象的过程)
    Circle c1;
    //给圆对象 的属性进行赋值
    c1.m_r = 10;

    cout << "圆的周长为:" << c1.calculateZC() << endl;
}

                            2、

//设计一个学生类,属性有姓名和学号,
//可以给姓名和学号赋值,可以显示学生的姓名和学号

//设计一个学生类
class student
{
    public:
        //属性
        string m_name;    //姓名
        string m_id;        //学号
    
        //行为
        //显示姓名和学号
        void showStudent()
        {
            cout << "姓名: " << m_name << endl;
            cout << "学号: " << m_id << endl;
        }
                            
        //可以通过行为来给姓名赋值
        void setname(string name)
        {
            m_name = name;
        }

        //给学号赋值
        void setid(string id)
        {
            m_id = id;
        }
};

int main()
{
    student s1;
    s1.setname("张三");
    s1.setid("1");

    s1.showStudent();
    return 0;
}

                    TIPS:    1、类中的属性和行为 我们统称为 成员
                                 2、属性 又称:成员属性    、成员变量
                                 3、行为 又称:成员方法    、成员函数

            2.1.2、封装意义二

                    类在设计时,可以把属性和行为放在不同的权限下,加以控制

访问权限有三种:
         1、public        公共权限    //成员    类内可以访问  类外也可以访问
         2、protected    保护权限    //成员    类内可以访问  类外不可以访问  儿子可以访问父亲的保护内容
         3、private        私有权限    //成员    类内可以访问  类外不可以访问  儿子不能访问父亲的私有内容

             2.1.3、struct 和 class 的区别

            在C++中 struct 和 class 唯一的区别就在:默认的访问权限不同

            区别:
                1、struct 默认权限为  公共
                2、class  默认权限为  私有        ··将成员属性设置为私有

                优点1:将所有的成员属性设置为私有,可以自己控制读写权限
                优点2:对于写权限,我们可以检查数据的有效性         

class Person()
{
    public:
        //设置姓名
        void setName(string name)
        {
            m_Name = name;
        }
        
        //获取姓名
        void getName()
        {
            return m_Name;
        }

        //获取年龄
        int getAge()
        {
            m_Age = 10;//初始化为10岁
            return m_Age;
        }
        
        //设置年龄
        void setAge(int age)
        {
            if(age<0 || age>150)
            {
                m_Age = 0;
                cout << "年龄输入错误!" << endl;
                
                return;
            }
             
            m_Age = age;
        }
                            

        //设置情人
        void setLover(string lover)
        {
            m_Lover = lover;
        }
                        
    private:
        //姓名    可读可写
        string m_Name;
        //年龄    可读可写    如果想修改(年龄范围必须是 0~150 之间)
        int m_Age;
        //情人    只写
        string m_Lover;

};

    2.2、对象初始化和清理

            生活中我们买的电子产品都基本会有出厂设置,在某一天我们不用的时候也会删除一些自己的信息数据,保证安全
            C++中的面向对象来源于生活,每个对象也都会有初始化设置以及对象销毁前的清理数据的设置

            2.2.1、构造函数和析构函数的简介

                对象的初始化和清理也是两个非常重要的安全问题:
                    一个对象获变量没有初始化状态,对其使用后果是未知
                    同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题
                #  C++利用构造函数和析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。
                #  对象初始化和清理是编译器强制我们要做的事,因此如果我们不提供构造和析构,编译器会提供
                #  编译器提供的构造函数和析构函数是空实现。

                2.2.1.1、构造函数

                主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无需动手调用

            语法:类名(){ }
                     1、构造函数,没有返回值也不用写void
                     2、函数名称与类名相同
                     3、构造函数可以有参数,因此可以发生重载
                     4、程序在调用对象时会自动调用构造函数,无需动手调用,而且只会调用一次


                2.2.1.2、析构函数

                作用在于对象销毁前,系统自动调用,执行一些清理工作

              语法:1、析构函数,没有返回值也不写void
                         2、函数名称与类名相同,在名称前要加上符号 ~ 
                         3、析构函数不可以有参数,因此不可以发生重载
                         4、程序在对象销毁前会自动调用析构,无需动手调用,而且只会调用一次

class Person
{
    public:
        //构造函数
        Person()
        {
            cout << "调用Person构造函数!" << endl;
        }
        
        //析构函数
        ~Person()
        {
            cout << "调用Person析构函数!" << endl;
        }

};
                            
//构造和析构都是必须实现的,如果我们自己不提供,编译器会提供一个空实现的构造和析构
void test01()
{
    Person p;    //在栈上的数据,test01执行完毕后,释放这个对象
}

void main()
{
    test01();
}

        2.2.2、构造函数的分类及调用

      两种分类方式:
                按参数分类:有参数构造  和  无参数构造(无参构造又称为默认构造函数)
                按类型分类:普通构造  和  拷贝构造

                    拷贝构造函数的写法:
                        函数名( const 用引用的方式写欲拷贝的类 )//Person( const Person &p)
                        {
                            //将传入的类身上的所有属性,拷贝到该类上 // age = p.age; 
                        }

            三种调用方式:
                1、括号法
                2、显示法
                3、隐式转换法          

void test01()
{
    //1、括号法
    Person p1;//默认构造函数调用
    Person p2(10);//有参构造函数
    Person p3(p2);//拷贝构造函数

    cout << "p2的年龄: " << p2.age << endl;
    //因为p3为拷贝构造函数,拷贝p2,所以两者输出的值会一样
    cout << "p3的年龄: " << p3.age << endl;
                            
/*TIPS:调用默认构造函数时,不要加(),否则会无反应;因为编译器会认为该代码是一个函数的声明,不会认为是在创建对象*/


    //2、显示法
    Person p1;
    Person p2 = Person(10);//有参构造    
    Person p3 = Person(p2);//拷贝构造

/*TIPS:Ⅰ、等号右侧的  Person(10) 和 Person(p2)  是匿名对象;而等号的左侧就是它的名; 特点:若只有匿名对象,为给其名,则在当前行执行完后,系统会立即回收掉匿名对象*/
//Ⅱ、不要使用拷贝函数初始化匿名函数
Person(p3);//p3为拷贝函数    
//编译器会人为  Person(p3)   === Person p3;    编译器会认为是对象的声明,会导致重复定义而报错


    //3、隐式转换法
    Person p4 = 10; //相当于写了  
    Person p4 = Person(10);  有参构造
    Person p4 = p5; //拷贝构造 
}


        ··拷贝调用函数使用时机
            C++中拷贝构造函数调用时机通常有三种情况
                1、使用一个已经创建完毕的对象来初始化一个新对象
                2、值传递的方式给函数参数传值
                3、以值方式返回局部对象

class Person
{
    public:
        Person()
        {
            cout << "Person默认构造函数调用" << endl;
        }

        Person(int age)
        {
            m_Age = age;
        }

        Person(const Person &p)
        {
            cout << "Person拷贝构造函数调用" << endl;
            m_Age = p.m_Age;
        }

        ~Person()
        {
            cout << "Person析构函数调用" << endl;
        }

        int m_Age;
};

//1、使用一个已经创建完毕的对象来初始化一个新对象
void test01()
{
    Person p1(20);
    Person p2(p1);

    cout << "p2的年龄是: " << p2.m_Age << endl;
}

//2、值传递的方式给函数参数传值
void doWork(Person p)    //实参会将 p 拷贝一个临时副本传到形参中;所以不会影响实参的值
{

}
                
void test02()
{
    Person p;
    doWork(p);
}

//3、以值方式返回局部对象
Person doWork2()
{
    Person p1;
    cout << (int*)&p1 << endl;    //通过查看地址是否相同,可以知道 p1 和 p 是否是同一个

    //返回的时候会根据 p1 创建一个新的对象,然后返回给 test03() ;即返回的是 p1 的拷贝函数
    return p1;    
}

void test03()
{
    Person p = doWork2();
    //通过查看地址是否相同,可以知道 p1 和 p 是否是同一个;结果是不同的,所以两者是不同的对象
    cout << (int*)&p << endl;    
}
    
    
void main()
{
    test01();
}

        2.2.3、构造函数的调用规则

            ···默认情况下,C++编译器给每一个类添加至少3个函数
                1、默认构造函数(无参,函数体为空)
                2、默认析构函数(无参,函数体为空)
                3、默认拷贝构造函数,对属性进行值拷贝

            2.2.3.1、构造函数调用规则

                1、如果用户定义有参构造函数,C++不再提供默认无参构造函数,但会提供默认拷贝函数
                2、如果用户定义拷贝构造函数,C++不会再提供其他构造函数

        2.2.4、深拷贝与浅拷贝

            TIPS:深拷贝是面试经典问题,也是常见的一个坑
            ···浅拷贝:简单的赋值拷贝操作
            ···深拷贝:在堆区重新申请空间,进行拷贝操作
              

class Person
{
    public:
    //无参(默认)构造函数
        Person()
        {
            cout << "Person 的默认构造函数调用!" << endl;
        }

    //有参构造函数
        Person(int age, int height)
        {
            m_Age = age;
            m_Height = new int(height);
            cout << "Person 的有参构造函数调用!" << endl;
        }

    //自己实现拷贝构造函数,解决浅拷贝带来的问题
        Person(const Person &p)
        {
            cout <<"Person 拷贝构造函数调用!" << endl;
            m_Age = p.m_Age;
            //m_Height = p.m_Height;编译器默认实现该行代码
            //深拷贝操作:利用深拷贝在堆区创建新内存
            m_Height = new int(*p.m_Height);
        }

        ~Person()
        {
            //析构代码,将堆区开辟的数据做释放操作
            if(m_Height != NULL)
            {
                delete m_Height;
                m_Height = NULL;    //防止出现野指针
            }
            cout << "Person 的析构函数调用!" << endl;
        }

        int m_Age;        //年龄
        int *m_Height;    //身高
};

void test01()
{
    Person p1(18);
    cout << "p1 的年龄为:" << p1.m_Age << " 身高为: " << p1.m_Height << endl;
    //会报错。原因:若利用编译器提供的拷贝构造函数,会做浅拷贝操作,会出现浅拷贝的问题
    Person p2(p1);    

    //而浅拷贝带来的问题:堆区的内存重复释放
    //解决浅拷贝的问题:利用深拷贝进行解决
                    
}

void main()
{
    test01();
}

            TIPS:若属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题

    尾声:以上是我学习是做的笔记,可供大家参考学习,若有不准确的地方,欢迎指出!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值