【C++从0到1-黑马程序员】类和对象(二)

本文详细介绍了C++中的类和对象特性,包括成员变量和成员函数的存储、this指针的作用、空指针处理、const修饰成员函数、友元的概念与实现以及运算符重载,如加法、左移、递增、赋值和关系运算符等。
摘要由CSDN通过智能技术生成

C++从0到1 - 黑马程序员 课程学习笔记

课程链接:32 类和对象-对象特性-成员变量和成员函数分开存储_哔哩哔哩_bilibili

3. C++对象模型和this指针

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

在C++中,类内的成员变量和成员函数分开存储

只有非静态成员变量才属于类.

空对象占用内存空间为:1 个字节

原因:C++编译器会给每个空对象分配一个字节空间,是为了区分空对象占内存的位置

每个空对象也应该有一个独一无二的内存地址

3.2 this指针概念

        每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象回共用一块代码,那么,这一块代码如何区分哪个对象调用自己呢?

C++通过提供特殊的对象指针——this指针,区分哪个对象调用成员函数代码,this指针指向被调用的成员函数所属的对象

this指针是隐含在每一个非静态成员函数的一种指针

this指针不需要定义,直接使用即可

this 指针用途:

  • 当形参和成员变量同名时,可用this指针来区分
  • 在类的非静态成员函数中返回对象本身,可以return *this
class Person
{
public:
    Person(int age)
    {
        // age = age;        // 形参名称与成员变量名称一致,无法区分
        this -> age = age;   // this指针指向被调用的成员函数所属的对象
    }
    Person& PersonAdd(Person &p)     // 返回本体应该使用引用
    // 如果返回的是值,则不是P2本体,而是调用拷贝构造函数重新构造了一个新的数据
    {
        this -> age += p.age;
        return *this;
    }
    int age;
};

// 解决名称冲突
void test01()
{
    Person p1(18);
}

// 返回对象本身return *this
void test02()
{
     Person p1(18); 
     Person p2(18); 
    // 链式编程思想
     p2.PersonAdd(p1).PersonAdd(p1);        // 返回值为空时,报错
}

3.3 空指针访问成员函数

C++中空指针也可以调用成员函数,但是要注意有没有用到this指针,若用到this指针,则需要加以判断保证代码的健壮性.

class Person
{
public:
    void showClassName()
    {
        cout << "Person Class" << endl;
    }
    void showPersonAge()
    {
        if(this == NULL)    // 防止空指针程序崩溃
        {
            return;        
        }
          cout << "Age" << m_Age << endl; // 默认为this->m_Age
    }
    int m_Age;
};

void test01()
{
    Person * p = NULL;
    p->showClassName();    // 不报错
    // p->showPersonAge();    // 报错 原因为传入指针为NULL
}

3.4 const修饰成员函数

常函数:

  • 成员函数后 加const修饰 -> 常函数
  • 常函数内不可以修改成员属性
  • 成员属性声明时加关键字mutable后,在常函数中依然可以修改

常对象:

  • 声明对象前 加const -> 常对象
  • 常对象只能调用常函数
class Person
{
public:
    // this指针的本质是 指针常量,指针的指向是不可以修改的
    // 再次添加const之后,指针指向的值也不可以修改
    // 在成员函数后加const,修饰的是this指针,让指针指向的值也不可以修改
    void showPerson()  const
    {
      // ( this -> ) m_A = 100;    
      m_B = 100;
    }
    void func(){}
    int m_A;
    mutable int m_B;    // 特殊变量 在常函数也可以修改
};

void test()
{
    const Person p;    // 对象前加const 变为常对象
    // p.m_A = 100;    // 常对象不可以修改
    p.m_B = 100;
    // p.func();    // 常对象只能调用常函数, 普通成员函数可以修改属性,常对象不可以
}

4. 友元

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

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

友元关键字是 friend

友元的三种实现:

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

4.1 全局函数做友元

在类内起始位置以friend前置声明 friend void goodGay(Building *building);

class Building
{
    // 表明友元函数
    friend void goodGay(Building *building);
public:
    Building()
    {
            m_SittingRoom = "客厅";
            m_BedRoom = "卧室";
    }
    string m_SittingRoom;
private:
    string m_BedRoom;
};

// 全局函数
void goodGay(Building *building)
{
    cout << "goodGay" << building->m_SittingRoom <<endl;
    cout << "goodGay" << building->m_BedRoom <<endl;
}

void test01()
{
    Building building;
    goodGay();
}

4.2 类做友元

在类内起始位置以friend前置声明类名 friend class GoodGay;

class Building;

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

// 类外写函数
GoodGay::GoodGay()
{
    building = new building;
}
void GoodGay::visit()
{
    cout << "goodGay" << building->m_SittingRoom <<endl;
    cout << "goodGay" << building->m_BedRoom <<endl;
}ds
class Building
{
    friend class GoodGay;
public:
    Building()
    {
            m_SittingRoom = "客厅";
            m_BedRoom = "卧室";
    }
public:
    string m_SittingRoom;
private:
    string m_BedRoom;
};

4.3 成员函数做友元

在类内起始位置以friend前置声明成员函数 friend void GoodGay::visit();

5. 运算符重载

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

5.1 加号运算符重载 +

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

  • 全局重载的优先级高于成员函数重载
  • 运算符重载也可以发生函数重载
// 成员函数重载
class Person
{
public:
    Person operator+(Person &p)
    {
        Person temp;
        temp.m_A = this->m_A + p.m_A;
        return temp;
    }    
    int m_A;
};
// 全局函数重载
Person operator+(Person &p2,Person &p2)
{
     Person temp;
     temp.m_A = p1.m_A + p2.m_A;
     return temp;
}

5.2 左移运算符重载

作用:输出自定义类型

// 利用成员函数重载左移运算符 p.operator<<(cout)简化版本p<<cout
// 不会利用成员函数重载左移运算符 因为无法实现cout在<<左侧
void operator<<(ostream cout)

// 全局函数重载左移运算符
ostream operator<<(ostream &cout,Person &p)    // 本质operator<< (cout,p) 简化 cout<<p
{ 
    return cout;
}

5.3 递增运算符重载 ++

作用:实现自己的整型数据

// 内置递增运算符
int a = 10;
cout << ++a << endl;//11
cout << a << endl; //11

int b = 10;
cout << b++ << endl;//10
cout << b << endl; //11
// 自定义整形
class MyInteger
{
    friend ostream operator<<(ostream &cout, MyInteger &myint) ;
public:
    MyInteger()
    {
        m_Num = 0;    
    }
    // 重载前置递增 返回为引用,为了一直对一个数据进行递增
    MyInteger& ostream operator++()
    {
        m_Num++;
        return *this;    
    }
    // 重载后置递增 int代表展位参数,用于区分前置和后置
    // 后置返回值
    MyInteger ostream operator++(int)
    {
        // 先记录当时结果
        MyInteger temp = *this
        // 后递增
        m_Num++;
        // 返回当时结果
        return temp;
    }
private:
    int m_Num;        
};

// 重载左移运算符
ostream operator<<(ostream &cout, MyInteger &myint)    // 本质operator<< (cout,p) 简化 cout<<p
{ 
    cout << muint.m_Num;
    return cout;
}

5.4 赋值运算符重载 =

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

  • 默认构造函数
  • 默认析构函数
  • 默认拷贝构造函数
  • 赋值运算符 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)
    {
        // 先判断是否有属性在堆区,如果有,先释放干净,再进行深拷贝
        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);
    p2 = p1;    // 赋值操作
    cout << "p1 age " << *p1.m_Age << endl;
    cout << "p2 age " << *p2.m_Age << endl;
}

5.5 关系运算符重载 > < == !=

作用:可以让两个自定义类型对象进行比较

bool operator==(Person &p)
{
    ...
    return true/false;
}

5.6 函数调用运算符重载 ()

由于重载后使用的方式非常像函数的调用,因此称为仿函数

仿函数没有固定写法,非常灵活

class MyPrint()
{
    void operator()(string test)
    {
        cout << test << endl;    
    }
}

int main()
{
    MyPrint myPrint;
    myPrint("hello world");    // 十分像函数调用 因此称为仿函数
    
    // 匿名函数对象
    cout << MyPrint()("hello") << endl; 
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值