C++学习笔记(八)(C++对象模型和this指针 ,友元)

本笔记主要来源于教程https://www.bilibili.com/video/av41559729?p=1

4.3 C++对象模型和this指针

4.3.1 成员变量和成员函数分开储存

在C++中,类内的成员变量和成员函数分开存储
只有非静态成员变量才属于类的对象上

//成员变量和成员函数分开存储的

class Person
{
    int m_A;//非静态成员变量  属于类的对象上

    static int m_B;//静态成员变量   不属于类的对象上

    void func()//非静态成员函数  不属于类的对象上
    {   
    }
    //int m_C;
    static void func2()//静态成员函数  不属于类的对象上
    {
    }

};
int Person::m_B = 0;
void test01()
{
    Person p;
    //空对象占用的内存空间为:1
    //C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置
    //每个空对象也应该有一个独一无二的内存地址
    cout << "size of p= " << sizeof(p) << endl;

}
void test02()
{
    Person p;
    cout << "size of p= " << sizeof(p) << endl;

}

int main()
{
    //test01();
    test02();

    system("pause");
    return 0;

}

4.3.2 this指针概念

通过4.3.1我们知道在C++中成员变量和成员函数是分开存储的
每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码。
那么问题是:这一块代码是如何区分哪个对象调用自己的呢?

C++通过提供特殊的对象指针,this指针,解决上述问题,this指针指向被调用的成员函数所属的对象

this指针是隐含每一个非静态成员函数内的一种指针
this指针不需要定义,直接使用即可。

this指针的用途:

  • 当形参和成员变量同名时,可用this指针来区分
  • 在类的非静态成员函数中返回对象本身,可使用return *this
class Person
{
public:
    Person(int age)
    {
        //this指针指向被调用的成员函数所属的对象
        this->age = age;    
    }

    Person& PersonAddAge(Person &p)//如果不返回引用而返回值 Person,会调用拷贝函数,创建新对象,结果仍为20
    {
        this->age += p.age;

        //this指向p2的指针,而*this指向的就是p2这个对象本体
        return *this;

    }

    int age;

};

//1、解决名称冲突
void test01()
{
    Person p1(18);
    cout << "p1的年龄为: " << p1.age << endl;

}

//2、返回对象本身用*this
void test02()
{
    Person p1(10);

    Person p2(10);

    //链式编程思想
    p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);

    cout << "p2的年龄为: " << p2.age << endl;

}
int main()
{
    //test01();
    test02();

    system("pause");
    return 0;

}

4.3.3 空指针访问成员函数

C++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针

如果用到this指针,需要加以判断保证代码的健壮性。

//空指针调用成员函数

class Person
{
public:

    void showClassName()
    {
        cout << "this is Person class" << endl;

    }
    void showPersonAge()
    {
        //报错原因是因为传入的指针为NULL
        if (this == NULL)
        {
            return;//避免this的坑 提高代码健壮性
        }

        cout << "age= " << m_Age << endl;//相当于this->m_Age

    }
    int m_Age;
};
void test01()
{
    Person *p = NULL;

    p->showClassName();

    p->showPersonAge();//报错
}

int main()
{
    test01();

    system("pause");
    return 0;

}

4.3.4 const修饰成员函数

常函数:

  • 成员函数后加const我们称这个函数为常函数
  • 常函数内不可以修改成员属性
  • 成员属性声明时加关键字mutable后,在常函数中仍然可以修改

常对象

  • 声明对象前加const称该对象为常对象
  • 常对象只能调用常函数
    示例:
//常函数
class Person
{
public:
    //this指针的本质是  指针常量  指针的指向是不可以修改的
    //Person *const this;
    //在成员函数后面加const,修饰的是this指针,让指针指向的值也不可以修改 const Person *const this;
    void showPerson() const
    {
        this->m_B = 100;
        //this->m_A = 100;
        //this = NULL;//this指针是不可以修改指向的

    }
    void func()
    {
        m_A = 100;
    }
    int m_A;
    mutable int m_B;//特殊变量,即使在常函数中,也可以修改这个值,加关键字mutable
};
void test01()
{
    Person p;
    p.showPerson();
}

//常对象
void test02()
{
    const Person p;//在对象前加const,变为常对象
    //p.m_A = 100;//报错
    p.m_B = 100;//m_B是一个特殊值,在常对象下也可以修改

    //常对象只能调用常函数
    p.showPerson();
    p.func();//报错,常对象不可以调用普通成员函数,因为普通成员函数可以修改属性
}
int main()
{


    system("pause");
    return 0;

}

4.4 友元

生活中你的家有客厅(Public),有你的卧室(Private)
客厅所有来的客人都可以进去,但是你的卧室是私有的,也就是说只有你能进去
但是呢,你也可以允许你的好闺蜜好基友进去

在程序里,有些私有属性也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术

友元的目的就是让一个函数或者类,访问另一个类中私有成员

友元的关键字为friend
友元的三种实现:

  • 全局函数做友元
  • 类做友元
  • 成员函数做友元

4.4.1 全局函数做友元

//建筑物类
class Building
{
    //goodGay全局函数是 Building好朋友,可以访问Building中私有成员
    friend void goodGay(Building *building);
public:
    Building()
    {
        m_SittingRoom = "客厅";
        m_BedRoom = "卧室";
    }


public:
    string m_SittingRoom;//客厅

private:
    string m_BedRoom;


};
//全局函数
void goodGay(Building *building)
{ 
    cout << "好基友全局函数 正在访问" << building->m_SittingRoom << endl;

    cout << "好基友全局函数 正在访问" << building->m_BedRoom<< endl;

}
void test01()
{
    Building building;
    goodGay(&building);
}
int main()
{
    test01();

    system("pause");
    return 0;

}

4.4.2 类做友元

//类做友元

class Building;
class GoodGay
{
public:
    GoodGay();
    void visit();//参观函数,访问Building中的属性

    Building *building;


};
class Building
{
    //GoodGay类是本类的好朋友,可以访问本类中私有的成员
    friend class GoodGay;
public:
    Building();
public:
    string m_SittingRoom;//客厅

private:
    string m_Bedroom;//卧室

};
//类外写成员函数
Building::Building()
{
    m_SittingRoom = "客厅";
    m_Bedroom = "卧室";
}

GoodGay::GoodGay()
{
    //创建一个建筑物对象
    building = new Building;

}

void GoodGay::visit()
{
    cout << "好基友类正在访问: " << building->m_SittingRoom << endl;
    cout << "好基友类正在访问: " << building->m_Bedroom<< endl;
}
void test01()
{
    GoodGay gg;
    gg.visit();
}
int main()
{
    test01();

    system("pause");
    return 0;

}

成员函数做友元

class Building;
class GoodGay
{
public:
    GoodGay();

    void visit();//让visit函数可以访问Building中私有成员
    void visit2();//让visit2函数不可以访问Building中私有成员


    Building *building;


};
class Building
{
    //告诉编译器  GoodGay类下的visit成员函数作为本类的好朋友,可以访问私有成员
    friend void GoodGay::visit();
public:
    Building();

public:
    string m_SettingRoom;//客厅

private:
    string m_BedRoom;//卧室


};
//类外实现成员函数
Building::Building()
{
    m_SettingRoom = "客厅";
    m_BedRoom = "卧室";

}
GoodGay::GoodGay()
{
    building = new Building;
}
void GoodGay::visit()
{
    cout << "visit函数正在访问:" << building->m_SettingRoom << endl;

    cout << "visit函数正在访问:" << building->m_BedRoom << endl;

}

void GoodGay::visit2()
{
    cout << "visit2函数正在访问:" << building->m_SettingRoom << endl;

    //cout << "visit2函数正在访问:" << building->m_BedRoom << endl;
}
void test01()
{
    GoodGay gg;
    gg.visit();
    gg.visit2();
}
int main()
{
    test01();

    system("pause");
    return 0;

}

4.5 运算符重载

运算符重载概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型

4.5.1 加号运算符重载

作用:实现两个自定义数据类型相加的运算

//加号运算符重载
class Person
{
public:
    //1、成员函数重载+号
    Person operator+(Person &p)
    {
        Person temp;
        temp.m_A = this->m_A + p.m_A;
        temp.m_B = this->m_B + p.m_B;
        return temp;

    }
    int m_A;
    int m_B;
};
//2、全局函数重载+号
Person operator+(Person &p1, Person &p2)
{
    Person temp;
    temp.m_A = p1.m_A + p2.m_A;
    temp.m_B = p1.m_B + p2.m_B;
    return temp;
}
//函数重载的版本
Person operator+(Person &p1, int num)
{
    Person temp;
    temp.m_A = p1.m_A + num;
    temp.m_B = p1.m_B + num;
    return temp;
}

void test01()
{
    Person p1;
    p1.m_A = 10;
    p1.m_B = 10;
    Person p2;
    p2.m_A = 10;
    p2.m_B = 10;

    //成员函数重载本质调用
    Person p3 = p1.operator+(p2);

    //全局函数重载本质调用
    Person p3 = operator+(p1, p2);

    Person p3 = p1 + p2;
    //运算符重载 也可以发生函数重载

    Person p4 = p1 + 100;//Person+int

    cout << "p3.m_A= " << p3.m_A << endl;
    cout << "p3.m_B= " << p3.m_B<< endl;

    cout << "p4.m_A= " << p4.m_A << endl;
    cout << "p4.m_B= " << p4.m_B << endl;
}


int main()
{
    test01();

    system("pause");
    return 0;

}
总结1:对于内置的数据类型的表达式的运算符是不可能改变的 总结2:不要滥用运算符重载

4.5.2 左移运算符重载

作用:可以输出自定义数据类型

//左移运算符重载
class Person
{
public:
    Person(int a, int b)
    {
        m_A = a;
        m_B = b;
    }
    friend ostream &operator<<(ostream &cout, Person &p);
private:
    //利用成员函数重载左移运算符 p.operator<<(cout) 简化版本p << cout
    //不会利用成员函数重载<<运算符,因为无法实现cout在左侧
    /*void operator<<(Person &p)
    {
    }
*/
    int m_A;
    int m_B;

};
//只能利用全局函数来重载左移运算符
ostream &operator<<(ostream &cout, Person &p)//本质 operator<<(cout,p)简化cout << p
{
    cout << "m_A= " << p.m_A << " m_B=" << p.m_B;
    return cout;//cout也可以改名字,因为引用的本身是起别名
}
void test01()
{
    Person p(10, 10);


    cout << p << endl;
}

int main()
{
    test01();

    system("pause");
    return 0;

}
总结:重载左移运算符配合友元可以实现输出自定义数据类型

4.5.3 递增运算符重载

作用:通过重载递增运算符,实现自己的整型数据

//重载递增运算符

//自定义整型
class MyInteger
{
    friend ostream &operator<<(ostream&cout, MyInteger myint);
public:
    MyInteger()
    {
        m_Num = 0;
    }
    //重载前置++运算符  返回引用是为了一直对一个数据进行递增操作
    MyInteger& operator++()//如果不返回引用每次递增之后会创建一个新的变量
    {
        m_Num++;
        return *this;
    }

    //重载后置++运算符
    //void operator++(int) int代表占位参数,可以用于区分前置和后置递增 只能用int
    MyInteger operator++(int)//后置要返回值,因为返回的是临时变量执行结束之后会释放
    {
        //先 记录当时结果
        MyInteger temp = *this;
        //后 递增
        m_Num++;
        //最后将记录结果做返回
        return temp;
    }
private:
    int m_Num;


};

//重载左移运算符
ostream &operator<<(ostream&cout, MyInteger myint)
{
    cout << myint.m_Num;
    return cout;
}
void test01()
{
    MyInteger myint;
    cout << ++myint << endl;
    cout << myint << endl;
}
void test02()
{
    MyInteger myint;
    cout << myint++ << endl;
    cout << myint << endl;
}

int main()
{
    test01();
    test02();

    system("pause");
    return 0;

}

4.5.4 赋值运算符重载

C++编译器至少给一个类添加四个函数

  1. 默认构造函数(无参,函数体为空)
  2. 默认析构函数(无参,函数体为空)
  3. 默认拷贝构造函数,对属性进行拷贝
  4. 赋值运算符operator=,对属性进行拷贝

如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题

//赋值运算符重载

class Person
{
public:
    Person(int age)
    {
        m_Age =new int(age);
    }
    ~Person()
    {
        if (m_Age != NULL)
        {
            delete m_Age;
            m_Age = NULL;
        }
    }
    //重载赋值运算符
    Person& operator=(Person &p)
    {
         //编译器是提供浅拷贝
        //m_Age=p.m_Age;

        //应该先判断是否有属性在堆区,如果有先释放干净,然后再深拷贝
        if (m_Age != NULL)
        {
            delete m_Age;
            m_Age = NULL;
        }
        //深拷贝
        m_Age = new int(*p.m_Age);

        //返回对象本身
        return *this;
    }
    int *m_Age;
};
void test01()
{
    Person p1(18);

    Person p2(20);

    Person p3(30);

    p3=p2 = p1;//赋值操作
    cout << " p1的年龄为: " << *p1.m_Age << endl;

    cout << " p2的年龄为: " << *p2.m_Age << endl;

    cout << " p3的年龄为: " << *p3.m_Age << endl;
}
int main()
{
    test01();

    system("pause");
    return 0;

}

4.5.5 关系运算符重载

作用:重载关系运算符,可以让两个自定义类型对象进行对比操作。

//重载关系运算符
class Person
{
public:
    Person(string name, int age)
    {
        m_Name = name;
        m_Age = age;
    }
    //重载==号
    bool operator==(Person &p)
    {
        if (this->m_Name == p.m_Name&&this->m_Age == p.m_Age)
        {
            return true;
        }
        return false;
    }

    bool operator!=(Person &p)
    {
        if (this->m_Name == p.m_Name&&this->m_Age == p.m_Age)
        {
            return false;
        }
        return true;
    }

    string m_Name;
    int m_Age;


};
void test01()
{
    Person p1("Tom", 18);

    Person p2("Tom", 14);
    if (p1 == p2)
    {
        cout << " p1和p2是相等的! " << endl;
    }
    else
    {
        cout << " p1和p2是不相等的! " << endl;
    }

    if (p1 != p2)
    {
        cout << " p1和p2是不相等的! " << endl;
    }
    else
    {
        cout << " p1和p2是相等的! " << endl;
    }
}
int main()
{
    test01();

    system("pause");
    return 0;

}

4.5.6 函数调用运算符重载

  • 函数调用运算符()也可以重载
  • 由于重载后使用的方式非常像函数的调用,因此称为仿函数
  • 仿函数没有固定写法,非常灵活。
//函数调用运算符重载

//打印输出类
class MyPrint
{
public:
    //重载函数调用运算符
    void operator()(string test)
    {
        cout << test << endl;
    }


};
void MyPrint02(string test)
{
    cout << test << endl;
}

void test01()
{
    MyPrint myPrint;

    myPrint("helloWorld");//由于使用起来非常类似于函数调用,因此称为仿函数

    MyPrint02("helloWorld");

}

//仿函数非常灵活,没有固定的写法
//加法类

class MyAdd
{
public:
    int operator()(int num1, int num2)
    {
        return num1 + num2;
    }
};

void test02()
{
    MyAdd myAdd;
    int ret=myAdd(100, 100);
    cout << "ret= " << ret << endl;

    //匿名函数对象  MyAdd()执行结束后立即被释放 类型+()
    cout << MyAdd()(100, 106 )<< endl;

}
int main()
{
    test01();
    test02();

    system("pause");
    return 0;

}

匿名函数对象 MyAdd()执行结束后立即被释放 类型+()

 
最后欢迎大家访问我的个人博客青蛙听禅的博客

发布了13 篇原创文章 · 获赞 1 · 访问量 214

猜你喜欢

转载自blog.csdn.net/qq_40694605/article/details/104536882