第五节 day03_C++学习内容


目录

1.课堂练习02-长方体类

2.课堂练习03-点和圆类

3.类的大小

4.类的成员函数

5.构造函数和析构函数

6.构造函数的分类及应用


内容

#include 
using namespace std;


/****************************************************************************************************
 * 课堂练习02:设计长方体类(Cube),求出长方体的表面积(2ab+2ac+2bc)和体积(a*b*c),分别用全局函数和成员函数判断两个
 * 长方体是否相等。(10分钟)
 ****************************************************************************************************/
//长方体类
class Cub{
public:
    void setL(int l){ mL = l; }    // 设置(写入)函数的功能一定要单一,便于操作某个错误输入的值
    void setW(int w){ mW = w; }
    void setH(int h){ mH = h; }
    int getL(){ return mL; }    // int getL(void){ return mL; };    // 两种写法都一样
    int getW(){ return mW; }
    int getH(){ return mH; }
    //int getL()const{ return mL; }    // 常引用,表示该函数不可修改,用在外部函数中的操作中
    //int getW()const{ return mW; }
    //int getH()const{ return mH; }

    //计算长方体表面积
    int caculateS(){ return (mL*mW + mL*mH + mW*mH) * 2; }

    //计算长方体体积
    int caculateV(){ return mL * mW * mH; }

    //成员方法:比较两个长方体是否完全相同
    bool CubCompare(Cub& c){
        if (getL() == c.getL() && getW() == c.getW() && getH() == c.getH()){return true;}
        return false;
    }
private:
    int mL; //长
    int mW; //宽
    int mH; //高
};
//全局函数:比较两个长方体是否完全相等
bool CubCompare(Cub& c1, Cub& c2){
    /**************************************************************************
     * ① (Cub& c1, Cub& c2)使用引用而不使用普通形参(Cub c1, Cub c2)可节省内存空间。
     * ② 函数可定义为:bool CubCompare(const Cub& c1, const Cub& c2){...},此时
     * Cub类内的getL()、getW()、getH()三个函数也要分别改为getL()const、getW()const、
     * getH()const,此处加上const是为了限制该函数对对象内容可修改的权限,即在该函数内不可
     * 修改对象内容。
     **************************************************************************/
    if (c1.getL() == c2.getL() && c1.getW() == c2.getW() && c1.getH() == c2.getH()){return true;}
    return false;
}
void test22(){
    Cub c1, c2;
    c1.setL(10);
    c1.setW(20);
    c1.setH(30);
    c2.setL(20);
    c2.setW(20);
    c2.setH(30);
    cout << "c1 面积:" << c1.caculateS() << " 体积:" << c1.caculateV() << endl;
    cout << "c2 面积:" << c2.caculateS() << " 体积:" << c2.caculateV() << endl;

    // 比较两个立方体是否相等
    // 1. 全局函数比较
    if (CubCompare(c1, c2)){
        cout << "c1 和 c2 相等!" << endl;
    }else{
        cout << "c1 和 c2 不相等!" << endl;
    }
    // 2. 类的成员函数比较
    if (c1.CubCompare(c2)){
        cout << "c1 和 c2 相等!" << endl;
    }else{
        cout << "c1 和 c2 不相等!" << endl;
    }
}

/****************************************************************************************************
 * 课堂练习03:点和圆的关系——设计一个圆形类(Circle)和一个点类(Point),计算点和圆的关系。假如圆心坐标为(x0, y0),
 * 圆的半径为r,点的坐标为(x1, y1):
 *     1)点在圆上:(x1-x0)(x1-x0) + (y1-y0)(y1-y0) == r;
 *     2)点在圆内:(x1-x0)(x1-x0) + (y1-y0)(y1-y0) < r;
 *     3)点在圆外:(x1-x0)(x1-x0) + (y1-y0)(y1-y0) > r;
 *
 * 25.类的大小:类的大小(类占用空间的大小)主要是衡量类内成员变量占用的空间大小的总和,类内函数所占空间不作为衡量类占
 * 用空间大小的一部分。这是因为函数一般都是存放在代码区,它是通过函数名和参数共同来决定调用哪个函数,而成员变量存储在哪
 * 个存储区是由程序员决定的:
 *     ① 局部变量——栈区;
 *     ② new和define定义的变量——堆区;
 *     ③ 全局对象或全局变量——全局区;
 *     ④ const修饰的全局对象或变量——文字常量区。
 *
 * 26.类的成员函数,在类内声明,在类外定义:这是一种常用方法,因为在大型程序开发过程中,成员函数的功能内容会很多,如果
 * 写在类内,会造成该定义类拖很长,不便于查看和编辑,所以把成员函数定义在类外(在开发过程中一般),便于编辑查看。
 *     ① 该在类内声明、类外定义的函数和在类内声明并定义的函数没有区别,在类内声明和定义的函数的能实现的功能,它都可以
 * 实现;
 *     ② 另外,在类内声明和定义的函数怎么实现功能和访问私有成员变量,它就怎么实现和访问;
 *     ③ 调用该在类内声明在类外定义的函数和调用类内声明并定义的函数一样,都是“类名.函数名”;
 *     ④ 注意:在类内声明格式为:“void func03();”,在类外定义格式为:“void 类名::func03(){...函数功能...}”。
 *     友情提示:(QtCreator有快捷键,在类内声明函数后,光标定位在该函数后“Alt+Enter”可以直接生成类内外函数。)
 *
 * 27.了解内容——在程序开发过程中的规范写法:
 *     ① 全局函数一般声明在“.h”,即头文件中,定义在“.cpp”文件中,注意:若有默认参数值,要放在函数声明处才能起作用;
 *     ② 类也有自己专门的Header file(头文件)和Source file(源文件),其中类名称和头文件名称、源文件名称是一样的,
 * 此时,类的定义(成员变量的定义、成员函数的声明)一般在“头文件”中,而成员函数的定义在“源文件”中。
 ****************************************************************************************************/
//点类
class Point{
public:
    void setX(int x){ mX = x; }
    void setY(int y){ mY = y; }
    int getX(){ return mX; }
    int getY(){ return mY; }
private:
    int mX;
    int mY;
};
//圆类
class Circle{
public:
    void setP(int x,int y){
        mP.setX(x);
        mP.setY(y);
    }
    void setR(int r){ mR = r; }
    Point& getP(){ return mP; }
    int getR(){ return mR; }

    //判断点和圆的关系
    void IsPointInCircle(Point& point){
        int distance = (point.getX() - mP.getX()) * (point.getX() - mP.getX())
                + (point.getY() - mP.getY()) * (point.getY() - mP.getY());
        int radius = mR * mR;
        if (distance < radius){
            cout << "点(" << point.getX() << ", " << point.getY() << ")在圆(x-"<< mP.getX() <<
                    ")^2+(y-" << mP.getY() << ")^2=" << mR <<"^2内!" << endl;
        } else if (distance > radius){
            cout << "点(" << point.getX() << ", " << point.getY() << ")在圆(x-"<< mP.getX() <<
                    ")^2+(y-" << mP.getY() << ")^2=" << mR <<"^2外!" << endl;
        } else {
            cout << "点(" << point.getX() << ", " << point.getY() << ")在圆(x-"<< mP.getX() <<
                    ")^2+(y-" << mP.getY() << ")^2=" << mR <<"^2上!" << endl;
        }
    }

    // 26.在类内声明一个成员函数
    void func03();
private:
    Point mP; //实例化一个点作为圆心
    int mR; //半径
};

//26. 在类外定义该函数的功能
void Circle::func03()
{
    cout << "该圆的表达式为:(x-" << mP.getX() << ")^2+(y-" << mP.getY() << ")^2=" << mR <<"^2" << endl;
}

// 全局函数判断点是否在圆上
void isPointInCircle(Circle& c, Point& p)
{
    int distance = (p.getX() - c.getP().getX()) * (p.getX() - c.getP().getX())
            + (p.getY() - c.getP().getY()) * (p.getY() - c.getP().getY());
    int radius = c.getR() * c.getR();
    if (distance < radius){
        cout << "点(" << p.getX() << ", " << p.getY() << ")在圆(x-"<< c.getP().getX() <<
                ")^2+(y-" << c.getP().getY() << ")^2=" << c.getR() <<"^2内!" << endl;
    } else if (distance > radius){
        cout << "点(" << p.getX() << ", " << p.getY() << ")在圆(x-"<< c.getP().getX() <<
                ")^2+(y-" << c.getP().getY() << ")^2=" << c.getR() <<"^2外!" << endl;
    } else {
        cout << "点(" << p.getX() << ", " << p.getY() << ")在圆(x-"<< c.getP().getX() <<
                ")^2+(y-" << c.getP().getY() << ")^2=" << c.getR() <<"^2上!" << endl;
    }
}
void test23(){
    // 25.评估类的大小
    cout << "Point类由2个char型成员变量,4个成员函数,则类的大小:" << sizeof (Point) << endl;
    cout << "Circle类由1个char型成员变量,1个Point类,5个成员函数,则类的大小:" << sizeof (Circle) << endl;

    //实例化圆对象
    Circle circle;
    circle.setP(20, 20);
    circle.setR(5);
    circle.func03();    // 调用该在类内声明在类外定义的函数

    //实例化点对象
    Point point;
    point.setX(25);
    point.setY(20);
    circle.IsPointInCircle(point);
    isPointInCircle(circle, point);
}

/****************************************************************************************************
 * 27.了解内容:
 *     对象的初始化和清理也是两个非常重要的安全问题,一个对象或者变量没有初始时,对其使用后果是未知,同样的使用完一个
 * 变量,没有及时清理,也会造成一定的安全问题。所以当我们创建对象的时候,这个对象应该有一个初始状态,当对象销毁之前应该
 * 销毁自己创建的一些数据。
 *     C++中的 “构造函数和析构函数” 给我们提供这种问题的解决方案,这两个函数将会被编译器自动调用,完成对象初始化和对象
 * 清理工作。这个工作是编译器强制我们要做的事情,即使你不提供初始化操作和清理操作,编译器也会给你增加默认的操作,只是这
 * 个默认初始化操作不会做任何事。既然是必须操作,那么自动调用会更好,如果靠程序员自觉,那么就会存在遗漏初始化的情况出现。
 * 所以编写类就应该顺便提供初始化函数。
 *
 * 28.构造函数和析构函数:
 *     ① 构造函数:主要用在创建对象时为对象的成员属性赋值,它由编译器自动调用,无须手动调用。对象实例化时系统自动调用。
 * 函数语法:构造函数的函数名和类名相同,没有返回值,不能有void,但可以有参数,可以重载,定义格式为:ClassName(){};
 *     ② 析构函数:主要用在对象销毁前系统自动调用,执行一些清理工作。对象释放的时候系统自动调用。析构函数语法:析构函
 * 数的函数名是在类名前面加”~”组成,没有返回值,不能有void,不能有参数,不能重载,定义格式为:为:~ClassName(){}。
 *     ③ 默认情况下,C++编译器至少为我们写的类增加3个函数:
 *         i> 默认构造函数(无参,函数体为空);
 *         ii> 默认析构函数(无参,函数体为空);
 *         iii> 默认拷贝构造函数,对类中非静态成员属性简单值拷贝。如果用户自己定义了拷贝构造函数,C++将不会再提供任
 *     何默认构造函数,如果用户定义了普通构造(非拷贝),C++不在提供默认无参构造,但是会提供默认拷贝构造。
 ****************************************************************************************************/
#include
#include
class Person03{
public:
    // 类的构造函数,用于初始化数据
    Person03(){
        cout << "构造函数调用!" << endl;
        pName = (char*)malloc(sizeof("John"));    // 向系统申请大小为“John”字符串的存储空间
        strcpy(pName, "John");    // 给成员变量赋初值
        mTall = 150;
        mMoney = 100;
    }
    // 构造函数可以重载,但是重载的构造函数好像不能被自动调用
    Person03(int i){cout << i << "构造函数的重载!"<< endl;}
    // 类的析构函数,用于清理使用痕迹
    ~Person03(){
        cout << "析构函数调用!" << endl;
        if (pName != NULL){
            free(pName);    // 释放申请的存储空间
            pName = NULL;
        }
        // ? 为什么mTall和mMoney不需要释放呢?
    }
public:
    char* pName;
    int mTall;
    int mMoney;
};
void test24(){
    Person03 per03;    // 实例化对象系统自动调用构造函数和析构函数
    cout << "姓名:" << per03.pName << endl;
    cout << "性别:" << per03.mTall << endl;
    cout << "财富值:" << per03.mMoney << endl;
    // 上面输出完,系统自动调用了类的析构函数并打印输出结果。
}

/****************************************************************************************************
 * 29.构造函数的分类及调用:
 *     29.1.构造函数的分类:
 *         ① 按参数类型:分为无参构造函数和有参构造函数;
 *         ② 按类型分类:普通构造函数和拷贝构造函数(复制构造函数)。
 *     29.2.无参构造函数调用:
 *         ① 隐式调用,系统自动调用;
 *         ② 显示调用,人为手动调用;
 *         ③ 错误调用:Person04 per04_2();。
 *     29.3.有参构造函数调用:
 *         ① 括号法,2种,都比较常用;
 *         ② 隐式转换调用有参构造函数,此法只用于有一个成员变量的类,直接传参。
 *         ③ 匿名对象(显示调用构造函数);
 *     29.4.注意:
 *         ① 构造函数和析构函数在同一作用域下调用顺序是相反的,这涉及到出入栈的关系。
 *         ② 匿名对象:没有实例名的对象,生命周期非常短,匿名对象调用结束,该匿名对象的构造函数和析构函数也就执行完了。
 *         ③ 注意: 使用匿名对象初始化判断调用哪一个构造函数,要看匿名对象的参数类型。
 *     29.5.拷贝构造函数的定义与调用:
 *         ① 拷贝构造主要的作用是进行赋值操作;
 *         ② 函数定义格式:类名(const 类名& 形式对象名){...函数功能...};
 *         ③ 其实系统也会提供拷贝构造函数,我们可以直接用:类名 对象名2 = 对象名1,即对象2是对象1的复制对象,即单纯
 *            的整体复制,是一种浅copy;
 *         ④ 旧对象初始化新对象时才会使用拷贝构造函数。注意区分拷贝构造函数和对象的复制,其实作用效果相差不大,都是浅
 *           copy;
 *         ⑤ 注意:不能调用拷贝构造函数去初始化匿名对象,一般我们也不会出这种错!;
 *         ⑥ 如果用户提供了拷贝构造函数,系统将屏蔽默认构造函数和默认拷贝构造函数,即即使手动调用这两个函数,系统也会
 *           抛出错误。
 *     29.6.若b为A的实例化对象,A a=A(b); 和 A(b)的区别?
 *         当 A(b) 有变量来接的时候,那么编译器认为他是一个匿名对象,当没有变量来接的时候,编译器认为你 A(b) 等价于
 *         A b;。
 *     29.7.注意:隐式转换的问题——explicit关键字
 *         C++提供了关键字explicit,禁止构造函数通过隐式转换的方式进行调用,即声明为explicit修饰的构造函数不能进行
 *         隐式转换这种方法调用。
 *         [explicit 注意]:只针对单参数的构造函数(或者除了第一个参数外其余参数都有默认值的多参构造)使用的。
 ****************************************************************************************************/
class Person04{
public:
    // 无参构造函数
    Person04(){
        cout << "no param constructor!" << endl;
        mAge = 0;
    }
    // 有参构造函数,相当于构造函数的重载
    Person04(int age){
        cout << "1 param constructor!" << endl;
        mAge = age;
    }

    /********************************************
    // 29.7.有explicit修饰的构造函数,此时外部函数中的语句“Person04 per04_6 = 12;”,就会报错:
    // error: no viable conversion from 'int' to 'Person04'
    // 报错是因为系统不理解此句到底是单纯的变量赋值,还是构造函数的隐式转换调用。
    explicit Person04(int age){
        cout << "1 param constructor!" << endl;
        mAge = age;
    }
    **********************************************/
    //拷贝构造函数(复制构造函数) 使用另一个对象初始化本对象
    Person04(const Person04& per){
        cout << "copy constructor!" << endl;
        mAge = per.mAge;
    }
    //打印年龄
    void PrintPerson(){ cout << "Age:" << mAge << endl; }
    // 析构函数
    ~Person04(){
        cout << mAge << "--析构函数调用!" << endl;
    }
private:
    int mAge;
};

void test25(){
    /*****1. 无参构造调用方式*****/
    cout << "自动调用:" << ends;
    Person04 per04_1;    // 1.1.隐式调用:调用默认构造函数或无参构造函数,对象实例化时自动调用。
    per04_1.PrintPerson();    // 调用类内一般成员函数

    cout << "手动调用:" << ends;
    Person04 per04_2 = Person04();    // 1.2.显式调用,构造函数
    per04_2.PrintPerson();

    // Person04 per04_3();    //1.3错误调用:无参构造函数错误调用方式

    /*****2. 调用有参构造函数*****/
    cout << "隐式调用:直接在对象后传入对应有参构造函数的参数调用:" << ends;
    Person04 per04_4(10);    //2.1.括号法1,最常用
    per04_4.PrintPerson();

    cout << "显式调用:直接在对象后传入对应有参构造函数的参数调用:" << ends;
    Person04 per04_5 = Person04(11);    //2.2.括号法2,常用
    per04_5.PrintPerson();

    Person04 per04_3(Person04(400)); //等价于 Person04 per04_6 = Person04(400);
    per04_3.PrintPerson();

    cout << "隐式转换调用:直接在对象后传入对应有参构造函数的参数调用:" << ends;
    Person04 per04_6 = 12;    //2.3.隐式转换调用:此时,12系统会自动转换成Person04(12)。
    per04_6.PrintPerson();

    cout << "匿名对象的使用方法:" << ends;
    Person04(13);   // 2.4.匿名对象:没有实例名的对象,生命周期非常短,此句结束,构造和析构函数就执行完了
    cout << "***********************************构造和析构都在此句之前执行了!" << endl;

    /*****3. 拷贝构造函数*****/
    cout << "隐式调用:拷贝构造函数的定义与调用,一般手动方法:" << ends;
    Person04 per04_7(per04_4);    //3.1.隐式调用拷贝构造函数
    per04_7.PrintPerson();

    cout << "显式调用:拷贝构造函数的定义与调用,一般手动方法:" << ends;
    Person04 per04_8 = Person04(per04_5);    //3.1.显式调用拷贝构造函数
    per04_8.PrintPerson();

    cout << "隐式转换调用拷贝构造函数,系统自带方法:" << ends;
    Person04 per04_9 = per04_6;    //3.2.隐式转换调用系统自带的拷贝构造函数。
    per04_9.PrintPerson();

    Person04 per04_10;
    per04_10.PrintPerson();
    per04_10 = per04_2;    //注意:此时只是对象的复制,并非拷贝构造函数起作用
}


int main(){
    //test22();
    //test23();
    //test24();
    //test25();
    return 0;
}