第八节 day06_C++学习内容

目录

1.const修饰成员变量

2.友元(类、成员函数、全局函数)

3.友元练习(电视类和遥控器类)

4.数组类封装强化

5.运算符重载

内容

#include <iostream>
using namespace std;
#include<string.h>
#include<stdlib.h>


/****************************************************************************************************
 * 43. const 修饰成员函数:
 *     用 const 修饰的成员函数时,const 修饰 this 指针指向的内存区域,成员函数体内不可以修改本类中的任何普通成员变
 * 量,当成员变量类型符前用 mutable 修饰时例外。
 *
 *     const 修饰对象(叫常对象):
 *     ① 常对象只能调用 const 修饰的成员函数;
 *     ② 常对象可访问 const 或非 const 数据成员,但是不能修改,除非成员用 mutable 修饰
 ****************************************************************************************************/
//const 修饰成员函数
class Person12{
public:
    Person12(){    // 无参构造函数对成员变量赋初始值
        this->mAge = 0;
        this->mID = 0;
    }
    //在函数括号后面加上 const,修饰成员变量不可修改,除了 mutable 变量
    void sonmeOperate() const{
        //this->mAge = 200; //报错:mAge 不可修改
        this->mID = 10;    // 可修改,因为有mutable修饰
    }
    void ShowPerson(){
        cout << "ID:" << mID << " mAge:" << mAge << endl;
    }
public:
    int mAge;
    mutable int mID;
};
void test39()
{
    /****** const 修饰成员函数 ******/
    Person12 person01;
    person01.sonmeOperate();
    person01.ShowPerson();

    /****** const 修饰对象 ******/
    const Person12 person02;
    //1. 可访问数据成员
    cout << "Age:" << person02.mAge << endl;
    //person02.mAge = 300; //不可修改
    person02.mID = 1001; //但是可以修改 mutable 修饰的成员变量
    //2. 只能访问 const 修饰的函数
    //person02.ShowPerson();
    person02.sonmeOperate();
}

/****************************************************************************************************
 * 44. 友元——类的主要特点之一是数据隐藏,即类的私有成员无法在类的外部(作用域之外)访问。但是,有时候需要在类的外部访问
 * 类的私有成员,怎么办?
 *     解决方法是使用友元函数,友元函数是一种特权函数,C++允许这个特权函数访问私有成员。
 *     这一点从现实生活中也可以很好的理解:
 *         > 比如你的家,有客厅,有你的卧室,那么你的客厅是 Public 的,所有来的客人都可以进去,但是你的卧室是私有的,
 *           也就是说只有你能进去,但是呢,你也可以允许你的闺蜜好基友进去。程序员可以把一个全局函数、某个类中的成员函
 *           数、甚至整个类声明为友元。
 *     友元的语法:friend 关键字只出现在声明处,其他类、类成员函数、全局函数都可声明为友元;
 *     友元全局函数:需要理解为:既是全局函数,也是某类的友元函数(某类的朋友),并非该类的成员函数,需要注意:
 *         ① 友元函数不是类的成员,不带 this 指针;
 *         ① 作用于哪个类(需要访问哪个类的私有数据),友元全局函数就需要在哪个类中声明,在全局定义(因为是全局函数);
 *     友元成员函数:需要理解为:既是某类的成员函数,也是另外一个类的友元函数(类的朋友),需要注意:
 *         ② 友元函数可访问对象任意成员属性,包括私有属性。
 *         ③ 作用于哪个类(需要访问哪个类的私有数据),友元成员函数就需要在哪个类中声明,并在归属的类中再次声明,说明
 *            归属性,最后在全局定义函数功能,因为在归属类内访问不到该函数友元类的私有数据;
 *     友元类:需要理解为:自己本身是一个类(友元类),也是另外一个类的友元类(本类),注意:
 *         ① 此时,本类中的所有成员(变量和函数)都可以访问被友元类中的成员函数所访问,
 *         ① 友元关系不能被继承。
 *         ② 友元关系是单向的,类 A 是类 B 的朋友,但类 B 不一定是类 A 的朋友。
 *         ③ 友元关系不具有传递性。类 B 是类 A 的朋友,类 C 是类 B 的朋友,但类 C 不一定是类 A 的朋友。
 *     思考: C++是纯面向对象的吗?
 *         如果一个类被声明为 friend,意味着它不是这个类的成员函数,却可以修改这个类的私有成员,而且必须列在类的定义
 *         中,增加 friend 关键字只是用来解决一些实际问题,因此友元是一个特权函数,所以C++不是完全的面向对象语言,而
 *         只是一个混合产品。毕竟C++设计的目的是为了实用性,而不是追求理想的抽象。
 ****************************************************************************************************/
class Building;    // 向前声明:先声明普通类,便于友元成员函数声明时识别该类

//定义Building的友元类Myfriend
class MyFriend{
public:
    //友元成员函数,在此声明是确定友元函数的归属性
    void LookAtBedRoom(Building& building);
    void PlayInBedRoom(Building& building);
};

class Building{    // 类的定义,注意两个必须一个声明一个定义,不能重复定义
    //全局函数做友元函数,在类中声明
    friend void CleanBedRoom(Building& building);

    //这是属于MyFriend类,作用于Building类的,友元成员函数,需要在Building类中声明
    friend void MyFriend::LookAtBedRoom(Building& building);

    //声明为Building的友元类,这个类在这段代码中并没有起多大作用
    friend class MyFriend;

public:
    Building();    // 这是类内无参构造函数,可以在类中声明在类外定义,也可以在类内声明并定义,此句用的是前者
//    Building(){    // 在类中声明并定义
//        this->mSittingRoom = "客厅";
//        this->mBedroom = "卧室";
//    }
public:
    string mSittingRoom;    // 客厅,公共空间
private:
    string mBedroom;    // 卧室,私有空间
};

//这是属于MyFriend类,作用于Building类的,友元成员函数,需要在类外定义
// 接上句,即该函数既是类Myfriend的成员函数,也是类Building的友元函数
void MyFriend::LookAtBedRoom(Building& building){
    cout << "其他类的成员函数作为友元参观了……" << building.mBedroom << endl;
}

// 注意:该函数只是类Myfriend的成员函数,并非类Building的友元函数,但是由于Myfriend是Building的友元类,
// 所以该函数才有权访问Building类中的私有成员。
void MyFriend::PlayInBedRoom(Building& building){
    cout << "其他类的成员函数作为友元在-" << building.mBedroom << "-玩耍了!" << endl;
}

//友元全局函数:该函数是全局函数,也是类Building的友元函数
void CleanBedRoom(Building& building){
    cout << "友元全局函数打扫了——" << building.mBedroom << endl;
}

Building::Building(){
    this->mSittingRoom = "我家客厅";
    this->mBedroom = "我的卧室";
}

void test40()
{
    Building building;
    MyFriend myfriend;
    CleanBedRoom(building);
    myfriend.LookAtBedRoom(building);
    myfriend.PlayInBedRoom(building);
}

/****************************************************************************************************
 * 随堂练习05:请编写电视机类,电视机有开机和关机状态,有音量,有频道,提供音量调节和操作转换的方法。由于电视机只能逐一
 * 调整频道,不能指定频道,增加遥控类,遥控类除了拥有电视机已有的功能外,再增加根据输入进行调台的功能。提示:遥控器可作
 * 为电视机类的友元类。
 ****************************************************************************************************/
class Remote;
class Television{
    friend class Remote;
public:
    enum{ On, Off }; //电视状态
    enum{ minVol, maxVol = 100 }; //音量从 0 到 100
    enum{ minChannel = 1, maxChannel = 255 }; //频道从 1 到 255
    Television(){    // 无参构造函数,给成员变量赋初始值,作为默认值
        mState = Off;    // 默认为关机
        mVolume = minVol;    // 默认为0
        mChannel = minChannel;    // 默认为1
    }
    //打开电视机
    void OnOrOff(){
        this->mState = (this->mState == On ? Off : On);
    }
    //调高音量
    void VolumeUp(){
        if (this->mVolume >= maxVol){ return; }
        this->mVolume++;
    }
    //调低音量
    void VolumeDown(){
        if (this->mVolume <= minVol){ return; }
        this->mVolume--;
    }
    //更换电视频道
    void ChannelUp(){
        if (this->mChannel >= maxChannel){ return; }
        this->mChannel++;
    }
    void ChannelDown(){
        if (this->mChannel <= minChannel){ return; }
        this->mChannel--;
    }
    //展示当前电视状态信息
    void ShowTeleState(){
        cout << "开机状态:" << (mState == On ? "开机" : "关机") << endl;
        if (mState == On){
            cout << "当前音量:" << mVolume << endl;
            cout << "当前频道:" << mChannel << endl;
        }
        cout << "-------------" << endl;
    }
private:
    int mState; //电视状态,开机,还是关机
    int mVolume; //电视机音量
    int mChannel; //电视频道
};
//电视机调台只能一个一个的调,遥控可以指定频道
//电视遥控器
class Remote{
public:
    Remote(Television* television){ pTelevision = television; }
public:
    void OnOrOff(){ pTelevision->OnOrOff(); }
    //调高音量
    void VolumeUp(){ pTelevision->VolumeUp(); }
    //调低音量
    void VolumeDown(){ pTelevision->VolumeDown(); }
    //更换电视频道
    void ChannelUp(){ pTelevision->ChannelUp(); }
    void ChannelDown(){ pTelevision->ChannelDown(); }
    //设置频道 遥控新增功能
    void SetChannel(int channel){
        if (channel < Television::minChannel || channel > Television::maxChannel){ return; }
        pTelevision->mChannel = channel;
    }
    //显示电视当前信息
    void ShowTeleState(){ pTelevision->ShowTeleState(); }
private:
    Television* pTelevision;
};
//直接操作电视
void test41(){
    Television television;
    television.ShowTeleState();
    television.OnOrOff(); //开机
    television.VolumeUp(); //增加音量+1
    television.VolumeUp(); //增加音量+1
    television.VolumeUp(); //增加音量+1
    television.VolumeUp(); //增加音量+1
    television.ChannelUp(); //频道+1
    television.ChannelUp(); //频道+1
    television.ShowTeleState();
}
//通过遥控操作电视
void test42(){
    //创建电视
    Television television;
    //创建遥控
    Remote remote(&television);
    remote.OnOrOff();
    remote.ChannelUp();//频道+1
    remote.ChannelUp();//频道+1
    remote.ChannelUp();//频道+1
    remote.VolumeUp();//音量+1
    remote.VolumeUp();//音量+1
    remote.VolumeUp();//音量+1
    remote.VolumeUp();//音量+1
    remote.ShowTeleState();
}

/****************************************************************************************************
 * 随堂练习06:强化训练(数组类封装)
 ****************************************************************************************************/
/****** MyArray01.h 文件中 ******/

//#ifndef MYARRAY01_H
//#define MYARRAY01_H

class MyArray{
public:
    //无参构造函数,用户没有指定容量,则初始化为 100
    MyArray();
    //有参构造函数,用户指定容量初始化
    explicit MyArray(int capacity);
    //用户操作接口
    //根据位置添加元素
    void SetData(int pos, int val);
    //获得指定位置数据
    int GetData(int pos);
    //尾插法
    void PushBack(int val);
    //获得长度
    int GetLength();
    //析构函数,释放数组空间
    ~MyArray();
private:
    int mCapacity; //数组一共可容纳多少个元素
    int mSize; //当前有多少个元素
    int* pAdress; //指向存储数据的空间
};

//#endif // MYARRAY01_H


/****** MyArray01.cpp 文件中 ******/

//#include"MyArray01.h"    // 引入头文件

MyArray::MyArray(){
    this->mCapacity = 100;
    this->mSize = 0;
    //在堆开辟空间
    this->pAdress = new int[this->mCapacity];
}
//有参构造函数,用户指定容量初始化
MyArray::MyArray(int capacity){
    this->mCapacity = capacity;
    this->mSize = 0;
    //在堆开辟空间
    this->pAdress = new int[capacity];
}
//根据位置添加元素
void MyArray::SetData(int pos, int val){
    if (pos < 0 || pos > mCapacity - 1){ return; }
    pAdress[pos] = val;
}
//获得指定位置数据
int MyArray::GetData(int pos){ return pAdress[pos]; }
//尾插法
void MyArray::PushBack(int val){
    if (mSize >= mCapacity){ return; }
    this->pAdress[mSize] = val;
    this->mSize++;
}
//获得长度
int MyArray::GetLength(){ return this->mSize; }
//析构函数,释放数组空间
MyArray::~MyArray(){
    if (this->pAdress != nullptr){ delete[] this->pAdress; }
}

/****** 测试文件中 文件中 ******/
//#include"MyArray.h

void test43(){
    //创建数组
    MyArray myarray(50);
    //数组中插入元素
    for (int i = 0; i < 50; i++){
        //尾插法
        myarray.PushBack(i);
        //myarray.SetData(i, i);
    }
    //打印数组中元素
    for (int i = 0; i < myarray.GetLength(); i++){
        cout << myarray.GetData(i) << " ";
    }
    cout << endl;
}

/****************************************************************************************************
 * 45.运算符重载(operator overloading):即对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。运算
 * 符重载只是一种“语法上的方便”,也就是说,它只是另一种函数调用的方式。
 *     在 C++中,可以定义一个处理类的新运算符。这种定义很像一个普通的函数定义,只是函数的名字由关键字 operator 及其
 * 紧跟的运算符组成。差别仅此而已。它的本质也是一个函数(同其他函数),当编译器遇到适当的模式时,就会调用这个函数。
 *     语法:定义重载的运算符就像定义函数,只是该函数的名字是 “operator运算符” 。函数的参数中,参数个数取决于两个因素:
 *         > 运算符是一元(一个参数)的还是二元(两个参数);
 *         > 运算符被定义为全局函数(对于一元是一个参数,对于二元是两个参数)还是成员函数(对于一元没有参数,对于二元是
 *           一个参数——此时该类的对象用作左耳参数)。
 *     [注意两个极端]。它确实是一个有趣的工具。但是应该注意:
 *         > 有些人很容易滥用运算符重载:它仅仅是一种语法上的方便而已,是另外一种函数调用的方式。从这个角度来看,只有
 *           在能使涉及类的代码更易写,尤其是更易读时(请记住,读代码的机会比我们写代码多多了)才有理由重载运算符。如果
 *           不是这样,就改用其他更易用,更易读的方式。
 *         > 对于运算符重载,另外一个常见的反应是恐慌:突然之间,C运算符的含义变得不同寻常了,一切都变了,所有 C 代码
 *           的功能都要改变!其实在C++中并非如此,对于内置的数据类型的表达式的运算符是不可能改变的。例如想重载 int类
 *           型数据的+号)。
 *     可以重载的运算符:几乎 C 中所有的运算符都可以重载,但运算符重载的使用时相当受限制的。特别是
 *         > 不能使用 C 中当前没有意义的运算符,例如用**求幂,因为C中求幂的运算符为^;
 *         > 不能改变运算符优先级;
 *         > 不能改变运算符的参数个数。
 *         > 最好不要改变运算符的本质意义,如把加法运算符重载成减法操作。
 *     这样的限制有意义,否则,所有这些行为产生的运算符只会混淆而不是澄清寓意。(具体哪些运算符可以重载,哪些不可以重载
 * 间下图)
 ****************************************************************************************************/

int main()
{
//    test39();
//    test40();
//    test41();
//    test42();
//    test43();
    return 0;
}

image

posted @ 2022-05-28 21:00  漠上  阅读(18)  评论(0编辑  收藏  举报