当前位置: 首页>前端>正文

黑马C++视频笔记《封装、对象属性》

/* 封装
 * 意义:
 *  (1)将属性和行为作为一个整体,表现生活中的事物;
 *  (2)将属性和行为加以权限控制;
 *
 * 类中的属性和行为统一称为成员。属性又可称为成员属性、成员变量;行为又可称为成员函数,成员方法。
 *
 * struct和class都可以用来定义类,区别在于默认权限,其中struct的默认权限为公共权限,而class的默认权限为私有权限。
 *
 * 类中的属性方法访问权限有三种:
 *  (1)public,公共权限,类内类外都能访问;
 *  (2)protected,保护权限,类内和其继承类都能访问;
 *  (3)private,私有权限,仅类内有访问权限;
 *
 */

/* 类和对象
 *
 * 构造函数和析构函数
 *  (1)构造函数用于初始化,在创建对象时为对象的成员赋值,构造函数由编译器自动调用,无需手动调用;
 *  (2)析构函数在对象销毁前系统自动调用,用于清理完成内存回收。
 *  对象的初始化和清理工作是编译器强制要求我们做的事情,若我们不提供构造和析构函数,编译器会提供自己实现的构造函数和析构函数
 *  (编译器提供的构造函数和析构函数是空实现)。
 *
 * 构造函数
 *  语法:类名(){}
 *  (1)构造函数没有返回值也不写void;
 *  (2)函数名称与类名相同;
 *  (3)构造函数有参数,因此可以发生重载;
 *  (4)程序在调用对象(创建/初始化对象)时会自动调用构造函数,无需手动调用,而且只会调用一次。
 *
 * 构造函数的分类及调用
 *  两种分类方式:
 *      (1)按参数分为:有参构造和无参构造,即`类名(para){}`和`类名(){}`;
 *      (2)按类型分为:普通构造和拷贝构造,即`类名([有/无参数]){}`和`类名(const 类名 &c){}`。拷贝构造函数的参数应该为const引用方式传参;
 *
 *  三种调用方式:
 *      (1)括号法,如
 *          Person p1; // 调用默认构造函数,不能使用Person p1(),否则编译器会认为这是一个函数的声明
 *          Person p2(param); // 调用有参构造函数
 *          Person p3(p1); // 调用拷贝构造函数
 *      (2)显示法;
 *          Person p1; // 无参构造
 *          Person p2 = Person(10); // 有参构造,右边为匿名对象,左边相当于给匿名对象起了个名字以方便后续使用;
 *          Person p3 = Person(p1); // 拷贝构造,右边为匿名对象,左边相当于给匿名对象起了个名字以方便后续使用;
 *
 *          Person(10); // 匿名对象,特点:当前执行结束后,系统会立即回收掉匿名对象
 *          注:不要利用拷贝构造函数初始化匿名对象,编译器会认为`Person(p3) === Person p3`,自动转换为了默认(无参)构造函数;
 *      (3)隐式转换法;
 *          Person p4 = 10; // 有参构造,相当于写了`Person p4 = 10`
 *          Person p5 = p4; // 拷贝构造,相当于写了`Person p5 = p4`
 *
 *  拷贝构造函数的调用时机,通常有三种情况:
 *   (1)使用一个已经创建完毕的对象来初始化一个新对象,即 `Person p2(p1)`;
 *   (2)值传递的方式给函数参数传值, 即 `func(Person p)`,由于是值传递,因此在传进函数内部时,其实是对p这个对象进行了一次完整的拷贝;
 *   (3)以值方式返回局部对象,`void func(){Person p; return p;}`, 局部对象只存在于函数域内,函数执行完就回收了,因此返回的是局部对象的一个拷贝。
 *
 *  构造函数调用规则。默认情况下,c++编译器至少给一个类添加三个函数:
 *  (1)默认构造函数(无参,函数体为空);
 *  (2)默认析构函数(无参,函数体为空);
 *  (3)默认拷贝构造函数,对属性进行值拷贝;
 *  构造函数调用规则如下:
 *  (1)如果用户定义有参构造函数,c++不再提供默认无参构造,但是会提供默认拷贝构造;
 *  (2)如果用户定义拷贝构造函数,c++不会再提供其它构造函数。
 *
 * 深拷贝与浅拷贝:
 *  - 浅拷贝:简单的赋值拷贝操作;
 *  - 深拷贝:在堆区重新申请空间,进行拷贝工作。堆区对象不会自动进行回收,需要手动释放,否则要等到整个程序完了才会进行回收释放内存。
 * 因此,如果属性有在堆区开辟的,即 `int * attr =  new int(10)`, 类属性attr是一个指针,其值是一个地址。拷贝函数拷贝时候也是拷贝了这个地址,
 * 当一个拷贝类完成任务被释放析构时候会删除掉这个内存地址,后续被拷贝类实例被释放时就会出现错误,因为其属性attr已经不存在,两者释放的是同一个地址,报错。
 *
 * 初始化列表,可当做构造函数的一种写法,用来初始化属性。
 * 语法:构造函数(): 属性1(值1),属性2(值2)... {}.
 * 栗子:`Person(int a, int b, int c): m_A(a), m_B(b), m_C(c){}`.
 *
 * 当其他类对象作为本类成员,构造时先构造其他类对象,再构造自身,析构的顺序与构造函数相反。
 *
 * 析构函数
 *  语法:~类名(){}
 *  (1)析构函数没有返回值也不写void;
 *  (2)函数名称与类名相同,在名称前加上符号~;
 *  (3)析构函数不可以有参数,因此【不可以】发生重载;
 *  (4)程序在对象销毁前会自动调用析构函数,无需手动调用,而且只会调用一次。
 *
 *
 * 静态成员:静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员
 * 静态成员分为两种:
 * (1)静态成员变量
 *      - 所有对象共享同一份数据;
 *      - 在编译阶段分配内存;
 *      - 类内声明,类外初始化;
 * (2)静态成员函数
 *      - 所有对象共享同一个函数;
 *      - 静态成员函数只能访问静态成员变量。
 *      静态成员函数有两种访问方式:通过对象访问和通过类名访问(和python的静态函数是一样的)。
 *
 *
 * 类的成员函数和成员变量是分开储存的,其中:
 *  - 非静态成员变量,属于类的对象上(注:类对象即实例化的类);
 *  - 静态成员变量,不属于类对象上;
 *  - 非静态成员函数,不属于类对象上;
 *  - 静态成员函数,不输于类对象上。
 *
 * this指针
 * 引子:C++中成员变量和成员函数是分开储存的,则每一个非静态函数只会诞生一份函数实例,也即多个同类型对象会共用一块代码,
 * 那么这一块代码如何区分是哪个对象调用的自己?
 * 答:C++通过提供特殊的对象指针,this指针来解决此问题。this指针指向被调用的成员函数所属的对象。
 *
 * this指针是隐含在每一个非静态成员函数内的一种指针。this指针不需要定义,可直接使用。
 *
 * this指针的用途:
 *  (1)当形参和成员变量相同时,可用this指针来区分,否则编译器不会认为形参是成员变量;
 *  (2)在类的非静态成员函数中返回对象本身,可用`return *this`。
 *
 * C++中空指针可以访问成员函数,但是需要注意有没有用到this指针。若用到了this指针,需要加以判断保证代码的健壮性。
 *
 * 常函数(`void showPerson() const {}`)
 *  - const修饰的成员函数叫做常函数;
 *  - 常函数内不可以修改成员属性;
 *  - 成员属性声明时加关键字mutable后,在常函数中依然可以更改;
 *
 * 常对象(`const Person p;`):声明对象前加const称该对象为常对象,常对象只能调用常函数。
 *
 * 在非常函数中(也即一般类函数),this指针是一个指针常量(Person * const this),即其指向不能更改,但是其值可以更改。反向思考:this指向的是一个实例类,如果这个指针指向了另一个类,
 * 就变成了一个类实例调用函数,其实操作的是别的实例,这种做法是不允许的,所以this指针指向不能更改。而我们可以在非常函数内给类属性赋值,这相
 * 当于改变了this指向的某个属性的值,这是很普遍的操作,是被允许的。
 * 而对于常数函数(`const Person * const this`),this指针则是变成了双const 修饰,它既不能更改指向也不能更改其指向的值了(mutable成员属性除外)。
 *
 *
 *  友元
 * 有时候,我们想让类的一些私有属性可以被其他类或函数访问到,这时候就可以使用友元。友元的目的就是让一个函数或者类访问另一个类中的私有成员。
 * 关键字为 friend
 *
 * 友元的实现有三种:
 *  (1)全局函数做友元;
 *  (2)类做友元, 友元类(其中的函数)便可以访问其私有成员了;
 *  (3)成员函数做友元。

浅拷贝与深拷贝说明代码:

//
// Created by shexuan on 2021/1/4.
//

#include <iostream>
#include <string>
using namespace std;

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

    // 有参构造函数
    Person(int age, int height){
        m_age = age;
        m_height = new int(height);
        cout << "有参构造函数" << endl;
    }

//    // 拷贝构造函数1——浅拷贝
//    Person(const Person &p){
//        m_age = p.m_age;
//        m_height = p.m_height;
//    }

    // 拷贝构造函数1——深拷贝拷贝,重新开辟堆空间来存放数据,而不是直接复制内存地址
    Person(const Person &p){
        m_age = p.m_age;
        m_height = new int(*p.m_height);
    }

    //析构函数
    ~Person(){
        // 由于在堆中开辟了内存,堆中的内存需要手动释放
        if (m_height != NULL){
            delete m_height;
            m_height = NULL;
        }
        cout << "析构函数" << endl;
    }

    int m_age;
    int *m_height;
};

int main(){
    Person p1(18, 170);
    cout << "年龄:" << p1.m_age << " 身高(地址):" << p1.m_height << " 身高值:" << *p1.m_height << endl;
    Person p2(p1);
    cout << "年龄:" << p2.m_age << " 身高(地址):" << p2.m_height << " 身高值:" << *p2.m_height << endl;

}

输出:

有参构造函数
年龄:18 身高(地址):0x7fa417d00000 身高值:170
年龄:18 身高(地址):0x7fa417d00010 身高值:170
析构函数
析构函数

https://www.xamrdz.com/web/2j91882757.html

相关文章: