初识C++(2)

文章目录

十一、类和对象

1、类的定义

(1)、类定义格式

1.class为定义类的关键字,Stack为类的名字,{}中为类的主体,注意类定义结束时后面分号不能省略。类体中内容称为类的成员:类中的变量称为类的属性或成员变量;类中的函数称为类的方法或者成员函数
2.为了区分成员变量,一般习惯上成员变量会加一个特殊标识,如成员变量前面或者后面加_或者m开头,注意C++中并不是强制的,只是一些惯例,具体看要求。
但是在有些情况下,为了保证程序的可读性,还是需要加上特殊标识的

解析第2小点中的成员函数标识
#include<iostream>
using namespace std;

class Date
{
public:
    //成员函数
    void Init(int year, int month, int day)
    {
        year = year;
        month = month;
        day = day;
    }
private:
    //成员变量
    int year;
    int month;
    int day;
};

int main()
{
    return 0;
}

上述代码中成员变量没有采用特殊标记,导致成员函数中出现重名现象,影响代码可读性,所以加上特殊标记极大的方便了阅读代码。
修改后的代码:

#include<iostream>
using namespace std;

class Date
{
public:
    //成员函数
    void Init(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }
private:
    //成员变量
    //加上特殊标记
    int _year;
    int _month;
    int _day;
};

int main()
{
    return 0;
}

3.C++中struct也可以定义类,C++兼容C中的struct的用法,同时struct升级成了类,明显变化是struct中可以定义函数,一般情况下我们还是推荐用class定义类。
4.定义在类面的成员函数默认为inline,声明和定义都在类中的,是内联函数,声明和定义不同时在类中时,不是内联函数。

解析第4小点中的声明和定义分开
#include<iostream>
using namespace std;

class Date
{
public:
    //成员函数
    void Init(int year, int month, int day);
    //声明放在类中
private:
    //成员变量
    //加上特殊标记
    int _year;
    int _month;
    int _day;
};
//定义放在类外,要注意需要用到域作用限定符
void Date::Init(int year, int month, int day)
{
    _year = year;
    _month = month;
    _day = day;
}
int main()
{
    return 0;
}

(2)、访问限定符

1.C++一种实现封装的方式,用类将对象的属性与方法结合到一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用。
2.public修饰的成员在类外可以直接被访问protected和private修饰的成员在类外不能直接被访问,protected和private是一样的,在继承中才能体现它们的区别
3.访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止如果后面没有访问限定符,作用域就到},即类结束
4.class定义成员没有被访问限定符修饰时默认为private,struct默认为public
5.一般成员变量都会被限制为private/protected,需要给别人使用的成员函数会放为public。

#include<iostream>
using namespace std;

class xingguang
{
public://公有
    //成员函数
    int ADD(int a, int b)
    {
        return a + b;
    }
private://私有
    //成员变量
    int x = 1;
    int y = 2;
};

int main()
{
    xingguang xg;
    xg.ADD(1, 2);
    return 0;
}

(3)、类域

1.类定义了一个新的作用域,类的所有成员都在类的作用域中,在类体外定义成员时需要使用::作用域操作符指明成员属于哪个类域。
2.类域影响的是编译的查找规则,下面程序中的Init如果不指定类域Date,那么编译器就把Init当成全局函数,那么编译时,找不到_year等成员函数的声明/定义在哪里,就会报错。
指定类域Date,就是知道Init是成员函数,当前域中找不到的_year等成员变量,就会到类域Date中查找。
PS:命名空间域和类域只会影响变量适用的范围,不会影响它的生命周期。

2、类的实例化

类是对象进行一种抽象描述,是一个模型一样的东西,限定了类有哪些成员变量,这些成员变量只是声明,没有分配空间用类实例化处理的对象,才会分配空间(例:Date.d1)

#include<iostream>
using namespace std;

class Date
{
public:
    //成员函数
    void Init(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    
    void Print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }
private:
    //成员变量
    //加上特殊标记
    int _year;
    int _month;
    int _day;
};
int main()
{
    Date d1;
    Date d2;
    d1.Init(2024, 9, 1);
    d2.Init(2024, 9, 2);
    d1.Print();
    d2.Print();
    return 0;
}
2024/9/1
2024/9/2

C:\Users\Lenovo\source\repos\初识C++\x64\Debug\初识C++.exe (进程 18548)已退出,代码为 0。
按任意键关闭此窗口. . .

(1)、对象大小

分析一下类对象中哪些成员呢??
类实例化出的每个对象,都有独立的数据空间,所以对象中肯定包含成员变量,那么成员函数是否包含呢?首先函数被编译后是一段指令对象中没办法存储,这些指令存储在一个单独的区域(代码段)那么对象中非要存储的话,只能是成员函数的指针
再分析一下,对象中是否有存储指针的必要呢??
Date实例化d1和d2两个对象时,d1和d2都有各自独立的成员变量_year/_month/_day存储各自的数据,但是d1和d2的成员函数Init/Print指针却是一样的,存储在对象中就浪费了。如果用Date实例化100个对象,那么成员函数指针就重复存储100次,太浪费了。其实函数指针是不需要存储的,函数指针是一个地址,调用函数被编译成汇编指令[call地址],其实编译器在编译链接时,就要找到函数的地址,不是在运行时找,只有动态多态是在运行时找,就需要存储函数地址
PS:函数指针都会存在一个公共的地方,编译时就已经确定了

(2)、内存对齐规则

1.第一个成员在与结构体偏移量为0的地址处。
2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
3.注意:对齐数=编译器默认的一个对齐数 与 该成员大小的较小值
4.VS中默认的对齐数为8
5.结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
6.如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

#include<iostream>
using namespace std;

class A
{
public:
    void Print()
    {

    }
private:
    char _ch;
    int _i;
};

class B
{
public:
    void Print()
    {

    }
};

class C
{

};

int main()
{
    A a;
    B b;
    C c;
    cout << sizeof(a) << endl;
    cout << sizeof(b) << endl;
    cout << sizeof(c) << endl;
    return 0;
}
8
1
1

C:\Users\Lenovo\source\repos\初识C++\x64\Debug\初识C++.exe (进程 20760)已退出,代码为 0。
按任意键关闭此窗口. . .

上面的程序运行后,我们看到没有成员变量的B和C类对象的大小是1,为什么没有成员变量还要给1个字节呢??
因为如果一个字节都不给,怎么表示对象存在过,所以给一个字节,纯粹是为了占位标识对象存在。没有成员变量的类叫做空类,如仿函数。

为什么有内存对齐?

因为CPU在读取数据的时候,不是从任意的位置开始读取的,一次性从整数倍位置开始读,且读取的长度是规定不变的,对于上面的类A中,假设从0位置开始读,一次读取4个字节,
1.内存对齐的情况下:char _ch只需要读取第一个字节即可,int _i再读取一次就能完全读取。
2.内存未对齐的情况下:int _i在读取的时候,需要读取两次,第一次读取的是_i的前三个字节,第二次读取的是
_i的最后一个字节。

十二、this指针(指向对象的地址)

Date类中有Init 与Print 两个成员函数,函数体中没有关于不同对象的区分,那当d1调用Init和Print函数时,该函数是如何知道应该访问的是d1对象还是d2对象呢??
那么这里就要看到C++给了一个隐含的this指针解决这里的问题:
1.编译器编译后,类的成员函数默认都会在形参第一个位置,增加一个当前类类型的指针,叫做this 指针。比如Date类的Init的真实原型为, void Init(Date* const this, int year,int month, int day)
2.类的成员函数中访问成员变量,本质都是通过this指针访问的,如Init函数中给_year赋值, this -> _year =year。
3.C++规定不能在实参和形参的位置显示的写this指针(编译时编译器会处理),但是可以在函数体内显示使用this指针
4.非静态成员函数有this指针,静态成员函数没有this指针。

#include<iostream>
using namespace std;

class Date
{
public:
    //成员函数
    //void Init(Date* const this, int year,int month, int day)
    //const在*之前,修饰的是指向的对象,const在*之后,修饰的是指针本身
    void Init(int year, int month, int day)
    {
        // this -> _year = year;
         _year = year;
        //this -> _month = month;
        _month = month;
        //this -> _day = day;
        _day = day;
    }
    //void Print(Date* const this)
    void Print()
    {
        //cout << tihs -> _year << "/" << this -> _month << "/" << this -> _day << endl;
        cout << _year << "/" << _month << "/" << _day << endl;
    }
private:
    //成员变量
    //加上特殊标记
    int _year;
    int _month;
    int _day;
};
int main()
{
    Date d1;
    Date d2;
    //  d1.Init(&d1, 2024, 9, 1);
    d1.Init(2024, 9, 1);
    //d2.Init(&d2, 2024, 9, 2);
    d2.Init(2024, 9, 2);
    //d1.Print(&d1);
    d1.Print();
    //d2.Print(&d2);
    d2.Print();
    return 0;
}

还原被隐藏的this指针后可知,最先传的是的d1和d2的地址,这样就知道调用的是谁了。
this指针相当于是个形参。

1、易错点

1.this指针本身不能修改。
2.this指针指向的内容可以修改。
3.this指针可以访问,但是不能修改。
4.this指针存在内存的哪个区域??
一定不是存在在对象中。
存放在栈中(函数调用就会就会建立栈帧,参数和局部变量放在栈里,因为this指针相当于形参),也有的地方说this指针放在寄存器中。
ps:动态开辟的在堆里,静态数据在静态区,常量数据在常量区
ps:函数调用要压入参数,所以要建立栈帧

2、例1

#include<iostream>
using namespace std;

class A
{
public:
    void Print()
    {
        cout << this << endl;
        cout << "A::Print()" << endl;
    }
private:
    int _a;
};
int main()
{
    A* p = nullptr;
    p->Print();
    return 0;
}
0000000000000000
A::Print()

C:\Users\Lenovo\source\repos\初识C++\x64\Debug\初识C++.exe (进程 15840)已退出,代码为 0。
按任意键关闭此窗口. . .

为什么运行成功??
因为编译器编译之后,底层都会转换为汇编指令,
p->Print();转换为汇编指令是:call 地址,那这个地址在不在p对象里面??
因为前面说过成员函数的指针不存在于对象中
那为什么还需要对象进行调用??
因为通过p可以找到是A类域中的成员函数,就去A类域中找了。这是为了过语法这关。
为什么this显示的是0??
因为把p传给了this,p是空指针,那么this也是空指针。
//mov ecx p,ecx中存的是对象的地址,p就是对象的地址
或者可以理解为这样

#include<iostream>
using namespace std;

class A
{
public:
    void Print()
    {
        cout << "A::Print()" << endl;
        //PS:私有的成员变量,只有在成员函数中才能使用
    }
private:
    int _a;
};
int main()
{
    A* p = nullptr;
    (*p).Print();
    return 0;
}

A* p = nullptr;
(*p).Print();
return 0;

p是A类的指针,而*p是解引用后就是A,那么 (*p).Print();就是A.Print();这不就是调用A类中的Print()吗

3、例2

#include<iostream>
using namespace std;

class A
{
public:
    void Print()
    {
        cout << "A::Print()" << endl;
        cout << _a << endl;
        //PS:私有的成员变量,只有在成员函数中才能使用
    }
private:
    int _a;
};
int main()
{
    A* p = nullptr;
    p->Print();
    return 0;
}
A::Print()

C:\Users\Lenovo\source\repos\初识C++\x64\Debug\初识C++.exe (进程 23832)已退出,代码为 -1073741819。
按任意键关闭此窗口. . .

例2为什么就编译崩溃了??
因为cout << _a << endl;,访问_a是通过this访问的,因为this是空指针,所以就会崩溃。

十三、C++和C语言实现Stack对比

面向对象三大特性:封装、继承、多态,下面的对比我们可以初步了解一下封装。
通过下面两份代码对比,我们发现C++实现Stack形态上还是发生了挺多的变化,底层和逻辑上没啥变化。
1.C++中数据和函数都放到了类里面,通过访问限定符进行了限制,不能再随意通过对象直接修改数据,这是C++封装的一种体现,这个是最重要的变化。这里的封装的本质是一种更严格规范的管理,避免出现乱访问修改的问题。当然封装不仅仅是这样的,我们后面还需要不断的去学习。
2.C++中有一些相对方便的语法,比如Init给的缺省参数会方便很多,成员函数每次不需要传对象地址,因为this指针隐含的传递了,方便了很多,使用类型不再需要typedef用类名就很方便.
3.在我们这个C++入门阶段实现的Stack看起来变了很多,但是实质上变化不大。

十四、类的默认成员函数

默认成员函数就是用户没有显式实现,编译器会自动生成的成员函数称为默认成员函数。一个类,我们不写的情况下编译器会默认生成6个默认成员函数(构造函数、析构函数、拷贝构造、赋值重载、取地址重载),需要注意的是这6个中最重要的是前4个,最后两个取地址重载不重要,我们稍微了解一下即可。
构造函数主要完成初始化工作
析构函数主要完成清理工作
拷贝构造是使用同类对象初始化创建对象
赋值重载主要是把一个对象赋值给另一个对象
其次就是C++11以后还会增加两个默认成员函数,移动构造和移动赋值,默认成员函数很重要,也比较复杂,我们要从两个方面去学习:
第一:我们不写时,编译器默认生成的函数行为是什么,是否满足我们的需求??
第二:编译器默认生成的函数不满足我们的需求,我们需要自己实现,那么如何自己实现?

1、构造函数

构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象(我们常使用的局部对象是栈帧创建时,空间就开好了),而是对象实例化时初始化对象。构造函数的本质是要替代我们以前Stack和Date类中写的Init函数的功能,构造函数自动调用的特点就完美的替代的了Init
构造函数的特点:
1.函数名与类名相同
2.无返回值。(返回值啥都不需要给,也不需要写void,不要纠结,C++规定如此)
3.对象实例化时系统会自动调用对应的构造函数。
4.构造函数可以重载
5.如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数一旦用户显式定义编译器将不再生成

(1)、无参构造函数

①、自己定义
#include<iostream>
using namespace std;

class Date
{
public:
    //无参构造函数
    Date()
    {
        _year = 1;
        _month = 1;
        _day = 1;
    }
    void Print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};
int main()
{
    Date d1;
    d1.Print();
    return 0;
}
1/1/1

C:\Users\Lenovo\source\repos\初识C++\x64\Debug\初识C++.exe (进程 12384)已退出,代码为 0。
按任意键关闭此窗口. . .
②、在自己没有定义构造函数的情况下,系统会默认生成无参构造函数
#include<iostream>
using namespace std;

class Date
{
public:

    /*
    //无参构造函数
    Date()
    {
        _year = 1;
        _month = 1;
        _day = 1;
    }
    */

    void Print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};
int main()
{
    Date d1;
    d1.Print();
    return 0;
}
-858993460/-858993460/-858993460

C:\Users\Lenovo\source\repos\初识C++\x64\Debug\初识C++.exe (进程 42332)已退出,代码为 0。
按任意键关闭此窗口. . .

系统默认生成的无参构造,初始值不确定。

(2)、在有构造方式的情况下,不会默认生成无参构造

#include<iostream>
using namespace std;

class Date
{
public:
    //带参构造函数
    Date(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    void Print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};
int main()
{
    Date d1;
   //会报错
    d1.Print();
    Date d2(2024, 9, 1);
    d2.Print();
    Date d3(2024, 9, 3);
    d3.Print();
    return 0;
}
E0291	类 "Date" 不存在默认构造函数	初识C++	C:\Users\Lenovo\source\repos\初识C++\初识C++\源.cpp	25	

这里,我们写了一个带参数的构造方式,对于d1来说,因为已经有了带参数的构造,编译器就不会生成默认的无参构造了,所以d1会报错。

(3)、无参构造和带参构造

#include<iostream>
using namespace std;

class Date
{
public:
    //1.无参构造函数
    Date()
    {
        _year = 1;
        _month = 1;
        _day = 1;
    }

    //2.带参构造函数
    Date(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    void Print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};
int main()
{
    Date d1;
    //注意:Date d1();
    //无参的不能加括号,编译器无法区分是函数声明还是实例化对象
    d1.Print();
    Date d2(2024, 9, 4);
    d2.Print();
    return 0;
}
1/1/1
2024/9/4

C:\Users\Lenovo\source\repos\初识C++\x64\Debug\初识C++.exe (进程 2968)已退出,代码为 0。
按任意键关闭此窗口. . .

(4)、全缺省构造

全缺省就很好,包含了无参的和带参的两种情况

#include<iostream>
using namespace std;

class Date
{
public:
    /*
    //无参构造函数
    Date()
    {
        _year = 1;
        _month = 1;
        _day = 1;
    }
    */
    //全缺省构造函数
    //注意:
    //这里之前讲过,无参的和全缺省的不能同时存在,否则会有调用歧义
    Date(int year = 1, int month = 1, int day = 1)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    void Print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};
int main()
{
    Date d1;
    d1.Print();
    Date d2(2024);
    d2.Print();
    Date d3(2024, 9);
    d3.Print();
    return 0;
}
1/1/1
2024/1/1
2024/9/1

C:\Users\Lenovo\source\repos\初识C++\x64\Debug\初识C++.exe (进程 2388)已退出,代码为 0。
按任意键关闭此窗口. . .
易错点

无参构造和全缺省构造不能同出现,在调用时存在歧义。

编译器提供的无参构造

在没有自己手动写出构造函数的时候,,系统会自己生成一个无参构造函数。

#include<iostream>
using namespace std;

class Date
{
public:
    
    void Print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};
int main()
{
    Date d1;
    d1.Print();
    return 0;
}
-858993460/-858993460/-858993460

C:\Users\Lenovo\source\repos\初识C++\x64\Debug\初识C++.exe (进程 7284)已退出,代码为 0。
按任意键关闭此窗口. . .

可见,编译器默认提供的无参构造对于初始化赋值是不确定的。

6.无参构造函数、②全缺省构造函数、③我们不写构造时编译器默认生成的构造函数,这三个都叫做默认构造函数。但是这三个函数有且只有一个存在,不能同时存在无参构造函数和全缺省构造函数虽然构成函数重载,但是调用时会存在歧义。要注意很多同学会认为默认构造函数是编译器默认生成那个叫默认构造,实际上无参构造函数、全缺省构造函数也是默认构造,总结一下就是不传实参就可以调用的构造就叫默认构造(默认构造也可以被叫做无实参构造)
7.我们不写,编译器默认生成的构造,对内置类型成员变量的初始化没有要求,也就是说是是否初始化是不确定的,看编译器。对于自定义类型成员变量,要求调用这个成员变量的默认构造函数初始化。(前面我们说过,默认构造函数有三个,不一定是系统提供的无参构造函数),如果这个成员变量,没有默认构造函数,那么就会报错,我们要初始化这个成员变量,需要用初始化列表才能解决
PS:什么是内置类型和自定义类型??
C++把类型分为内置类型(基本类型)和自定义类型。内置类型就是语言提供的原生数据类型,如int/char/double/指针等,自定义类型就是我们使用的class/struct等关键字自己定义的类型

总结:大多数情况下,编译器提供的构造函数不能满足我们的要求,都需要我们自己去实现,少数情况下类似MyQueue且Stack有默认构造时,MyQueue自动生成就可以用。
对于构造函数来说:应写尽写。

2、析构函数

析构函数与构造函数功能相反,析构函数不是完成对对象本身的销毁,比如局部对象是存在栈帧的,函数结束栈帧销毁,他就释放了,不需要我们管,C++规定对象在销毁时会自动调用析构函数完成对象中资源的清理释放工作。析构函数的功能类比我们之前Stack实现的Destroy功能,而像Date没有Destroy,其实就是没有资源需要释放,所以严格说Date是不需要析构函数的。
析构函数的特点:
1.析构函数名是在类名前加上字符~
2.无参数无返回值。(这里跟构造类似,也不需要加void)
3.一个类只能有一个析构函数,因为析构函数不支持重载,若未显式定义,系统会自动生成默认的析构函数。
4.对象生命周期结束时,系统会自动调用析构函数
5.跟构造函数类似,我们不写编译器自动生成的析构函数对内置类型成员不做处理自定义类型成员会调用他的析构函数
6.还需要注意的是我们显示写析构函数,对于自定义类型成员也会调用他的析构,也就是说自定义类型成员无论什么情况都会自动调用析构函数
7.后定义的先析构,先定义的后析构
8.如果类中没有申请资源时析构函数可以不写,直接使用编译器生成的默认析构函数,如Date;如果默认生成的析构就可以用,也就不需要显示析构,如MyQueue,因为MyQueue调用了Stack的析构,释放了Stack内部的资源;如果有资源申请时一定要自己写析构,否则会造成资源泄漏,如Stack。
PS:如果显示写了MyQueue的析构函数,编译器也会自动调用Stack的析构!!!
MyQueue中包含两个自定义类型Stack,所以这两个自定义类型会自动调用Stack的构造函数和析构函数。
PS:析构函数,如果我们自己不写,编译器会帮我们写(编译器自己写的析构里面什么都不干)。
自定义类型,编译器定义的析构函数管,内置类型需要我们处理。
什么时候必须要我们自己写??
如果本类中一个成员变量是别的对象的指针,而且这个指针不是传进来的地址而是这个指针指向的对象,是在本类中(如果是栈里的定位分配,也不用考虑 内存)在堆中开辟的空间创建的。并且该指针没有进行过delete操作,那么就需要在析构方法中进行delete操作,此时我们就必须自己写析构函数 。

3、拷贝构造函数

如果一个构造函数第一个参数是自身类类型的引用,且任何额外的参数都有默认值,则此构造函数也叫做拷贝构造函数,也就是说拷贝构造是一个特殊的构造函数
拷贝构造的特点:
1.拷贝构造函数是构造函数的一个重载
2.拷贝构造函数的第一个参数必须是类类型对象的引用使用传值方式编译器直接报错,因为语法逻辑上会引发无穷递归调用。

(1)、使用传值方式,直接报错

#include<iostream>
using namespace std;

class Date
{

public:
    //普通构造函数
    Date(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    //拷贝构造函数
    Date(const Date d)   
    //拷贝构造函数的第一个参数必须是类类型对象的引用,使用传值方式编译器直接报错
    {
        _year = d._year;
        _month = d._month;
        _day = d._day;
    }
    void Print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }

private:
    int _year;
    int _month;
    int _day;
};
int main()
{
    //d1是普通构造
    Date d1(2024, 9, 5);
    d1.Print();
    //d2是拷贝构造,拷贝的是d1
    Date d2(d1);
    d2.Print();
    return 0;
}
E0408	类 "Date" 的复制构造函数不能带有 "Date" 类型的参数	
C2652	“Date”: 非法的复制构造函数: 第一个参数不应是“Date”	

(2)、使用传引用方式,正确

#include<iostream>
using namespace std;

class Date
{

public:
    //普通构造函数
    Date(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    //拷贝构造函数
    Date(const Date& d)   
    //const可以保护形参不受改变
    {
        _year = d._year;
        _month = d._month;
        _day = d._day;
    }
    void Print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }

private:
    int _year;
    int _month;
    int _day;
};

int main()
{
    //d1是普通构造
    Date d1(2024, 9, 5);
    d1.Print();
    Date d2(d1);
    d2.Print();
    return 0;
}
2024/9/5
2024/9/5

C:\Users\Lenovo\source\repos\初识C++\x64\Debug\初识C++.exe (进程 32400)已退出,代码为 0。
按任意键关闭此窗口. . .

为什么第一个参数是类类型的引用?
C++规定:传值传参要调用拷贝构造!!!!!
#include<iostream>
using namespace std;

class Date
{

public:
    //拷贝构造函数
    Date(const Date& d)   
    //const可以保护形参不受改变
    {
        _year = d._year;
        _month = d._month;
        _day = d._day;
    }
    void Print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }

private:
    int _year;
    int _month;
    int _day;
};

void Func1(Date d)
{
    cout << &d << endl;
    d.Print();
}

int main()
{
    //d1是普通构造
    Date d1(2024, 9, 5);
    d1.Print();

    //C++规定:传值传参要调用拷贝构造
    Func1(d1);
    //调用Func1,要先传参,就要先走入拷贝构造中,传参完成后,才会调用Func1!!!
    return 0;
}

如果第一个传的不是引用,而是传值的话: Date(const Date d)
对于d2而言,每次要调用拷贝构造函数之前要先传值传参,传值传参是一种拷贝,又形成一个新的拷贝构造,就形成了无穷递归。(只要传值就会调用拷贝构造)

但是,如果传的是引用的话: Date(const Date& d)
就不会出现无限递归的情况了,因为d 是d1的别名,传完一次后,很顺畅的就回来了,不会再形成新的拷贝构造了。
比如说自定义类型Stack,如果它采用传值传参的话,就会调用拷贝构造,而Stack中有资源申请,所以就要深拷贝,而深拷贝就会再额外开辟一块同等大小的空间,造成资源浪费,所以自定义类型还是要采用传引用比较好

(3)、const可以保护形参不受改变

例:

①、不加const
#include<iostream>
using namespace std;

class Date
{

public:
    //普通构造函数
    Date(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    //拷贝构造函数
    Date(Date& d)   
    {
        if (d._year = _year)
        {

       }
    }
    void Print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }

private:
    int _year;
    int _month;
    int _day;
};

int main()
{
    //d1是普通构造
    Date d1(2024, 9, 5);
    d1.Print();
    Date d2(d1);
    d2.Print();
    return 0;
}
2024/9/5
-858993460/-858993460/-858993460

C:\Users\Lenovo\source\repos\初识C++\x64\Debug\初识C++.exe (进程 27216)已退出,代码为 0。
按任意键关闭此窗口. . .
②、加const
#include<iostream>
using namespace std;

class Date
{

public:
    //普通构造函数
    Date(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    //拷贝构造函数
    Date(const Date& d)   
    {
        if (d._year = _year)
        {

       }
    }
    void Print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }

private:
    int _year;
    int _month;
    int _day;
};

int main()
{
    //d1是普通构造
    Date d1(2024, 9, 5);
    d1.Print();
    Date d2(d1);
    d2.Print();
    return 0;
}
C3490	由于正在通过常量对象访问“_year”,因此无法对其进行修改

由于符号错误,本来是==,却写成了=,不加const就会导致修改了要拷贝的值
这样系统就会提示,就不会对参数进行修改
就记住:传引用的时候,就加上const就对了!!

3.C++规定自定义类型对象进行拷贝行为必须调用拷贝构造,所以这里自定义类型传值传参传值返回都会调用拷贝构造完成
4.若未显式定义拷贝构造,编译器会生成自动生成拷贝构造函数自动生成的拷贝构造对内置类型成员变量会完成值拷贝/浅拷贝(一个字节一个字节的拷贝)(类似),对自定义类型成员变量会调用他的拷贝构造PS:这里和析构函数和构造函数不一样,它们两个对内置类型都不做处理,而拷贝构造对内置类型做处理。
5.像Date这样的类成员变量全是内置类型且没有指向什么资源,编译器自动生成的拷贝构造就可以完成需要的拷贝,所以不需要我们显示实现拷贝构造。像Stack这样的类,虽然也都是内置类型,但是_a指向了资源,编译器自动生成的拷贝构造完成的值拷贝/浅拷贝不符合我们的需求所以需要我们自己实现深拷贝对指向的资源也进行拷贝)。像MyQueue这样的类型内部主要是自定义类型Stack成员,编译器自动生成的拷贝构造会调用Stack的拷贝构造,也不需要我们显示实现MyQueue的拷贝构造。这里还有一个小技巧,如果一个类显示实现了析构并释放资源,那么他就需要显示写拷贝构造,否则就不需要。(这里是析构函数和拷贝构造的一个相似点,既然显示的写了析构函数,就说明这个类中有资源申请,就需要显示的写拷贝构造,进行深拷贝;反之,则不用。)

(4)、浅拷贝和深拷贝??

编译器自动提供的拷贝构造是浅拷贝,针对那些没有指向资源的内置类型。
对于那些有指向资源的内置类型,就需要我们手动添加拷贝构造,对指向的资源进行深拷贝。

(5)、对于Stack,使用编译器默认提供的拷贝构造出现问题??

因为Stack有资源的申请,所以当S2拷贝S1的时候,对于S1申请的空间,S2也会拷贝,且是同一块空间,缺点一就是一个Stack改变,Push数据,另一个Stack也和Push数据一样,缺点二就是在析构的时候,这块空间析构了两次,就会崩溃。

#include<iostream>
using namespace std;

class Date
{

public:
    //普通构造函数
    Date(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    //拷贝构造函数
    Date(const Date& d)
    //可以有多个参数,但一般是没有的,如果有的话,如:Date(const Date& d, int x = 0),参数必须有缺省值或者是默认值,否则不是拷贝构造    
    {
        _year = d._year;
        _month = d._month;
        _day = d._day;
    }
    void Print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }

private:
    int _year;
    int _month;
    int _day;
};
int main()
{
    //d1是普通构造
    Date d1(2024, 9, 5);
    d1.Print();
    //d2是拷贝构造,拷贝的是d1
    Date d2(d1);
    d2.Print();
    return 0;
}
2024/9/5
2024/9/5

C:\Users\Lenovo\source\repos\初识C++\x64\Debug\初识C++.exe (进程 29860)已退出,代码为 0。
按任意键关闭此窗口. . .

6.传值返回会产生一个临时对象调用拷贝构造,传值引用返回,返回的是返回对象的别名(引用)没有产生拷贝。但是如果返回对象是一个当前函数局部域的局部对象,函数结束就销毁了,那么使用引用返回是有问题的,这时的引用相当于一个野引用,类似一个野指针一样。传引用返回可以减少拷贝,但是一定要确保返回对象,在当前函数结束后还在,才能用引用返回

(6)、传值返回和传引用返回

传值返回
#include<iostream>
using namespace std;

class Date
{

public:
    Date& func2()
    {
        Date d1;
        return d1;
    }

private:
    int _year;
    int _month;
    int _day;
};

int main()
{
    Date d3;
    Date d2 = d3.func2();
    return 0;
}

传值返回不会返回d1,会返回d1的拷贝,拷贝的临时对象不受func2生命周期的影响,然后拷贝给到d2。

传引用返回
#include<iostream>
using namespace std;

class Date
{

public:
    Date& func2()
    {
        Date d1;
        return d1;
    }

private:
    int _year;
    int _month;
    int _day;
};

int main()
{
    Date d3;
    Date d2 = d3.func2();
    return 0;
}

返回的是d1的别名,但是这时候func2结束后,d1就会销毁了,所以这个返回的别名就成了野引用。

十五、赋值运算符重载

1、运算符重载

当运算符被用于类类型的对象时,C++语言允许我们通过运算符重载的形式指定新的含义。C++规定类类型对象使用运算符时,必须转换成调用对应运算符重载,若没有对应的运算符重载,则会编译报错。
1.运算符重载是具有特殊名字的函数,他的名字是由operator和后面要定义的运算符共同构成。和其他函数一样,它也具有其返回类型和参数列表以及函数体
2.重载运算符函数的参数个数和该运算符作用的运算对象数量一样多。一元运算符有一个参数,二元运算符有两个参数,二元运算符的左侧运算对象传给第一个参数,右侧运算对象传给第二个参数。
3.如果一个重载运算符函数是成员函数,则它的第一个运算对象默认传给隐式的this指针因此运算符重载作为成员函数时,参数比运算对象少一个
4.运算符重载以后,其优先级和结合性与对应的内置类型运算符保持一致
5.不能通过连接语法中没有的符号来创建新的操作符:比如operator@。
6. (.* ) (:😃 (sizeof) (?😃 (.) ,注意以上5个运算符不能重载。
(选择题里面常考,大家要记一重载操作符至少有一个类类型参数,不能通过运算符重载改变内置类型对象的含义,如:int operator+(int x,int y)
7.一个类需要重载哪些运算符,是看哪些运算符重载后有意义,比如Date类重载operator﹣就有意义,但是重载operator+就没有意义。

重载为全局的面临对象访问私有成员变量的问题,有四种解决方法:

(1)、成员放公有(不推荐)

#include<iostream>
using namespace std;

class Date
{

public:

    Date(int year = 1, int month = 1, int day = 1)
    {
        _year = year;
        _month = month;
        _day = day;
    }
   
    void Print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }

//private:
    int _year;
    int _month;
    int _day;
};

void operator==(const Date& d1, const Date& d2)//全局函数
{
    if (d1._year == d2._year && d1._month == d2._month && d1._day == d2._day)
    {
        cout << "日期相同" << endl;
    }
    else
    {
        cout << "日期不同" << endl;
    }
}

int main()
{
    Date d1(2024, 9, 5);
    d1.Print();
    Date d2(2024, 9, 5);
    d2.Print();
    Date d3(2024, 9, 6);
    d3.Print();
    operator==(d1, d2);
    d1 == d3;//编译器会转换成operator==(d1, d3);
    return 0;
}
2024/9/5
2024/9/5
2024/9/6
日期相同
日期不同

C:\Users\Lenovo\source\repos\初识C++\x64\Debug\初识C++.exe (进程 37636)已退出,代码为 0。
按任意键关闭此窗口. . .

(2)、Date提供getxxx函数

(3)、友元函数

(4)、重载为成员函数(推荐)

#include<iostream>
using namespace std;

class Date
{

public:

    Date(int year = 1, int month = 1, int day = 1)
    {
        _year = year;
        _month = month;
        _day = day;
    }
   
    void Print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }

    void operator==(const Date& d)//成员函数
    {
        if (_year == d._year && _month == d._month && _day == d._day)
        {
            cout << "日期相同" << endl;
        }
        else
        {
            cout << "日期不同" << endl;
        }
    }

private:
    int _year;
    int _month;
    int _day;
};



int main()
{
    Date d1(2024, 9, 5);
    d1.Print();
    Date d2(2024, 9, 5);
    d2.Print();
    Date d3(2024, 9, 6);
    d3.Print();
    d1.operator==(d2);
    d1 == d3;//编译器会自动转换成d1.operator==(d3);
    return 0;
}
2024/9/5
2024/9/5
2024/9/6
日期相同
日期不同
C:\Users\Lenovo\source\repos\初识C++\x64\Debug\初识C++.exe (进程 16320)已退出,代码为 0。
按任意键关闭此窗口. . .

2、运算符重载和函数重载

虽然都用了重载这个词,但是它们之间没有关系,函数重载是指的函数名相同,参数不同;运算符重载指的是重新定义运算符的行为,是用函数去定义,两个运算符重载的函数,可以构成函数重载,如:

Date operator+(int day)//d1 + 100
int operator+(const Date& d);//d1 + 100

这两个就是函数重载

3、赋值运算符重载

赋值运算符重载是一个默认成员函数,用于完成两个已经存在的对象直接的拷贝赋值,这里要注意跟
拷贝构造区分,拷贝构造用于一个对象拷贝初始化给另一个要创建的对象
赋值运算符重载的特点:
1.赋值运算符重载是一个运算符重载,规定必须重载为成员函数。赋值运算符重载的参数建议写成const 当前类类型引用,否则会传值传参会有拷贝,减少拷贝,防止改变形参。

(1)、赋值重载拷贝和拷贝构造

Date d1(2024, 9, 5);
Date d2(2024, 9, 6);
//赋值重载拷贝
d1 = d2;//d1和d2已经存在,所以是赋值重载拷贝
//拷贝构造
//初始化
Date d3(d2);
Date d4 = d2;//一个已经存在的对象,去先初始化一个新的对象(并没有创建对象,只是初始化),然后再拷贝
//d3和d4是新的,所以是拷贝构造

(2)、赋值重载拷贝

#include<iostream>
using namespace std;

class Date
{

public:

    Date(int year = 1, int month = 1, int day = 1)
    {
        _year = year;
        _month = month;
        _day = day;
    }
   
    void Print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }
    //赋值重载
    //d1 = d2
    void operator=(const Date& d)
    {
        _year = d._year;
        _month = d._month;
        _day = d._day;
    }

private:
    int _year;
    int _month;
    int _day;
};



int main()
{
    Date d1(2024, 9, 5);
    d1.Print();
    Date d2(2024, 9, 6);
    d2.Print();
    d1 = d2;
    d1.Print();
    return 0;
}
2024/9/5
2024/9/6
2024/9/6

C:\Users\Lenovo\source\repos\初识C++\x64\Debug\初识C++.exe (进程 39048)已退出,代码为 0。
按任意键关闭此窗口. . .

(3)、赋值重载传值会不会无穷递归??

不会,赋值重载先传参,因为传的不是引用,传的是值,会形成新的拷贝构造,调用完拷贝构造之后,回到赋值构造。
拷贝构造有无穷递归是因为它自身一直都在传值传参,回不去。

2.有返回值,且建议写成当前类类型引用,引用返回可以提高效率,有返回值目的是为了支持连续赋值场景。

(4)、用this的经典例子

连续赋值,这在C语言中常见
赋值表达式的返回值是左操作数

#include<iostream>
using namespace std;

int main()
{
    int i;
    int j;
    int k;
    i = j = k = 1;
    return 0;
}

这里i = j = k = 1;

右往左依次赋值,
先k等于1,返回值是k,再赋值给j,返回值是j,再赋值给i
对于类的连续赋值,
Date d1;
Date d2;
Date d3;
d3 = d2 = d1;
d1赋值给d2,d2再传给this,得有返回值,才能继续赋值给d3,所以:

#include<iostream>
using namespace std;

class Date
{

public:

    Date(int year = 1, int month = 1, int day = 1)
    {
        _year = year;
        _month = month;
        _day = day;
    }

    void Print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }
    //d2 = d1
    //d是d1,this是d3
    Date operator=(const Date& d)
    {
        _year = d._year;
        _month = d._month;
        _day = d._day;
        return *this;
    }

private:
    int _year;
    int _month;
    int _day;
};

int main()
{
    Date d1(2024, 9, 7);
    d1.Print();
    Date d2;
    Date d3;
    d3 = d2 = d1;
    d2.Print();
    d3.Print();
    return 0;
}
2024/9/7
2024/9/7
2024/9/7

C:\Users\Lenovo\source\repos\初识C++\x64\Debug\初识C++.exe (进程 39288)已退出,代码为 0。
按任意键关闭此窗口. . .

this就是d2
但是这种方式是不好的,因为传值返回,也会生成一个拷贝,且返回的是
this,operator函数结束后,*this(d2)不会消失,这就白白生成了一个拷贝,所以引用返回可以解决这个问题:

Date& operator=(const Date& d)
    {
        _year = d._year;
        _month = d._month;
        _day = d._day;
        return *this;
    }

指针和引用功能相似,但是指针不能替代引用!!!
综上:
在很多地方用引用是很方便的

3.没有显式实现时,编译器会自动生成一个默认赋值运算符重载,默认赋值运算符重载行为跟默认拷贝构造函数类似,对内置类型成员变量会完成值拷贝/浅拷贝(一个字节一个字节的拷贝),对自定义类型成员变量会调用他的赋值重载。
4.像Date这样的类成员变量全是内置类型且没有指向什么资源,编译器自动生成的赋值运算符重载就可以完成需要的拷贝,所以不需要我们显示实现赋值运算符重载。像Stack这样的类,虽然也都是内置类型,但是_a指向了资源,编译器自动生成的赋值运算符重载完成的值拷贝/浅拷贝不符合我们的需求,所以需要我们自己实现深拷贝(对指向的资源也进行拷贝)。像MyQueue这样的类型内部主要是自定义类型Stack成员,编译器自动生成的赋值运算符重载会调用Stack的赋值运算符重载,也不需要我们显示实现MyQueue的赋值运算符重载。这里还有一个小技巧,如果一个类显示实现了析构并释放资源,那么他就需要显示写赋值运算符重载,否则就不需要。
总结:
如果显示实现析构释放资源,需要显示实现拷贝构造和赋值重载,如果不显示实现析构函数,就不用显示实现拷贝构造和赋值重载。
5.重载++运算符时,有前置++和后置++,运算符重载函数名都是operator++,无法很好的区分。C++规定,后置++重载时,增加一个int形参,跟前置++构成函数重载,方便区分。
6.重载<<和>>时,需要重载为全局函数,因为重载为成员函数,this指针默认抢占了第一个形参位置,第一个形参位置是左侧运算对象,调用时就变成了对象<<cout,不符合使用习惯和可读性。重载为全局函数把ostream/istream放到第一个形参位置就可以了,第二个形参位置当类类型对象。

(5)、<<cout??

#include<iostream>
using namespace std;

class Date
{
public:
    Date(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    void operator<<(ostream& out)
    {
        out << _year << "年" << _month << "月" << _day << "日" << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};

int main()
{
    Date d1(2024, 9, 31);
    Date d2(2024, 9, 1);
    d1 << cout;//d1.operator<<(cout);
    d2 << cout;//d2.operator<<(cout);
    return 0;
}
2024931202491日

C:\Users\Lenovo\source\repos\初识C++\x64\Debug\初识C++.exe (进程 38060)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .

为什么会倒置??
因为operator<<是成员函数,第一个参数默认给的是d1的别名,而out在d1后面,所以反过来了。
看着不太舒服,如何改进??

改进1:把私有成员变量设为公有(不推荐)
#include<iostream>
using namespace std;

class Date
{
public:
    Date(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }
//private:
    int _year;
    int _month;
    int _day;
};

void operator<<(ostream& out, const Date& d)//全局函数!!!
{
    out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
}

int main()
{
    Date d1(2024, 9, 31);
    Date d2(2024, 9, 1);
    cout << d1;//opreator<<(cout, d1);
    cout << d2;//opreator<<(cout, d2);
    return 0;
}
2024931202491日

C:\Users\Lenovo\source\repos\初识C++\x64\Debug\初识C++.exe (进程 16232)已退出,代码为 0。
按任意键关闭此窗口. . .
改进2:友元函数
#include<iostream>
using namespace std;

class Date
{
    //友元函数声明
    friend void operator<<(ostream& out, const Date& d);
public:
    Date(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }
private:
    int _year;
    int _month;
    int _day;
};

void operator<<(ostream& out, const Date& d)//全局函数!!!
{
    out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
}

int main()
{
    Date d1(2024, 9, 31);
    Date d2(2024, 9, 1);
    cout << d1;//opreator<<(cout, d1);
    cout << d2;//opreator<<(cout, d2);
    return 0;
}
2024931202491日

C:\Users\Lenovo\source\repos\初识C++\x64\Debug\初识C++.exe (进程 36992)已退出,代码为 0。
按任意键关闭此窗口. . .

这样比较好,私有成员变量也可以访问,推荐。

(6)、流插入运算符是从左往右结合

例:cout<<d1<<d2
注意:返回值是左操作数!!!!!
所以还是cout

#include<iostream>
using namespace std;

class Date
{
    //友元函数声明
    friend ostream& operator<<(ostream& out, const Date& d);
public:
    Date(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }
private:
    int _year;
    int _month;
    int _day;
};
//因为返回值还是cout
ostream& operator<<(ostream& out, const Date& d)//全局函数!!!
{
    out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
    return out;
}

int main()
{
    Date d1(2024, 9, 31);
    Date d2(2024, 9, 1);
    cout << d1 << d2;
    return 0;
}
2024931202491日

C:\Users\Lenovo\source\repos\初识C++\x64\Debug\初识C++.exe (进程 5852)已退出,代码为 0。
按任意键关闭此窗口. . .

(7)、流提取运算符是从左往右结合

例:cin>>d1>>d2;

#include<iostream>
using namespace std;

class Date
{
    //友元函数声明
    friend istream& operator>>(istream& in, Date& d);
public:
    Date(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    void Print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};

istream& operator>>(istream& in, Date& d)//全局函数!!!
//注意:不能加const!!!
{
    cout << "请输入:" << "年/" << "月/" << "日" << ":";
    in >> d._year >> d._month >> d._day;
    return in;
}

int main()
{
    Date d1(2024, 9, 31);
    Date d2(2024, 9, 1);
    d1.Print();
    d2.Print();
    cin >> d1;//opreator<<(cout, d1);
    cin >> d2;//opreator<<(cout, d2);
    d1.Print();
    d2.Print();
    return 0;
}
2024/9/31
2024/9/1
请输入:年//日:2024 9 8
请输入:年//日:2024 9 7
2024/9/8
2024/9/7

C:\Users\Lenovo\source\repos\初识C++\x64\Debug\初识C++.exe (进程 14996)已退出,代码为 0。
按任意键关闭此窗口. . .

PS:流操作运算符不支持拷贝,所以必须要加引用!!!
istream& operator>>(istream& in, Date& d);
ostream& operator<<(ostream& out, const Date& d);
都有引用!!!

4、取地址运算符重载

(1)、const成员函数

1.将const修饰的成员函数称之为const成员函数,const修饰成员函数放到成员函数参数列表的后面。
const实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。const 修饰Date类的Print成员函数,Print隐含的this指针由 Date* const this
变为const Date* const this

①、解析

我们在初始化对象的时候,为了不对这个对象进行修改,让他不会改变,成为一个定值,就会加上const,如

cost Date d1(2024, 9, 8);

当const对象去调用普通对象的成员函数的时候,就会有风险,
比如:

void Print()
{
     cout << _year << "/" << _month << "/" << _day << endl;
}    

当d1去调用这个成员函数的时候

d1.Print();

就会把d1的地址传过去,而d1地址的类型是const Date* ,Print()中的指针默认是Date* const this
这就会造成一个权限放大问题,const Date* 传给Date*,就会导致可以修改d1的值,而我们要求的是不能修改,所以就要想办法在Date* const this前面加一个const,然后就发明出了const加在函数列表后面的方法。

void Print() cosnt; //本质上这个const就变成了 void Print(const Date* const this) const;

这样处理的妙用就在于,对于Print()这种不对对象中的成员函数做改变的成员函数来说,无论是:

cost Date d1(2024, 9, 8);

还是:

Date d1(2024, 9, 8);//非const对象可以调用Print(),只不过就是权限的缩小

都会确保不会对成员函数做出修改,即使不小心在成员函数中添加了可以修改成员变量的代码,编译器也会报错,这也算是一种保护机制。
PS:Date* const this中的const修饰的是指针本身,修饰指针本身的,不涉及权限放大的问题。
综上:
对于那些不对成员变量做修改的成员函数,比如说Print();,bool CheckDate();,bool operator<(const Date& d);,bool operator<=(const Date& d);,bool operator>(const Date& d);,bool operator>=(const Date& d);,bool operator!=(const Date& d);都可以在后面加const,但是要注意的是像Date(int year, int month, int day);构造函数这样对成员变量做出修改的成员函数就不能加const了。

(2)、取地址运算符重载

取地址运算符重载分为普通取地址运算符重载和const取地址运算符重载,一般这两个函数编译器自动生成的就可以够我们用了,不需要去显示实现。除非一些很特殊的场景,比如我们不想让别人取到当前类对象的地址,就可以自己实现一份,胡乱返回一个地址。

Date* opreator&()
{
    return this;
}

const Date* operator&()const
{
    return this;
}        

这两个一般情况下都不用我们自己写,编译器会提供,除非我们自己有特殊要求要自己写。
为什么要写两个??
普通对象调用Date* opreator&(),const对象调用const Date* operator&()const。

十六、再探构造函数

1.之前我们实现构造函数时,初始化成员变量主要使用函数体内赋值,构造函数初始化还有一种方式,就是初始化列表,初始化列表的使用方式是以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式

例1:

#include<iostream>
using namespace std;

class Date
{
public:
    Date(int year, int month, int day)
        :_year(year)
        , _month(month)
        , _day(day)
    {

    }
    void Print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};
int main()
{
    Date d1(2024, 9, 8);
    d1.Print();
    return 0;
}
2024/9/8

C:\Users\Lenovo\source\repos\初识C++\x64\Debug\初识C++.exe (进程 43340)已退出,代码为 0。
按任意键关闭此窗口. . .

2.每个成员变量在初始化列表中只能出现一次,语法理解上初始化列表可以认为是每个成员变量定义初始化的地方
3.引用成员变量和const成员变量,没有默认构造的类类型变量,必须放在初始化列表位置进行初始化,否则会编译报错。PS:引用必须初始化!!!

例2:const成员变量和引用成员变量(必须用初始化列表)

因为const变量只有一次初始化的机会

#include<iostream>
using namespace std;

class Date
{
public:
    Date(int& hour, int year, int month, int day)
        //初始化列表和大括号可以混着用
        :_year(year)
        , _month(month)
        , _day(day)
        ,_minute(1)
        ,_hour(hour)
    {

    }
    void Print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }
private:
    int _year;
    int _month;
    int _day;
    const int _minute;
    int& _hour;
};
int main()
{
    int hour = 5;
    Date d1(hour, 2024, 9, 8);
    d1.Print();
    return 0;
}

或者:

#include<iostream>
using namespace std;

class Date
{
public:
    Date(int& hour, int year, int month, int day)
        //初始化列表和大括号可以混着用
        :_minute(1)
        ,_hour(hour)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    void Print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }
private:
    int _year;
    int _month;
    int _day;
    const int _minute;
    int& _hour;
};
int main()
{
    int hour = 5;
    Date d1(hour, 2024, 9, 8);
    d1.Print();
    return 0;
}

例3:有默认构造的类类型变量(可以不用初始化列表)

#include<iostream>
using namespace std;

class Time
{
public:
    Time(int hour = 0)
        :_hour(hour)
    {
        cout << "Time()" << endl;
    }
private:
    int _hour;
};

class Date
{
public:
    Date(int& hour, int year, int month, int day)
        //初始化列表和大括号可以混着用
        :_minute(1)
        ,_hour(hour)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    void Print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }
private:
    int _year;
    int _month;
    int _day;
    const int _minute;//必须在初始化列表初始化
    int& _hour;//必须在初始化列表初始化
    Time _t;//如果Time有默认构造我们不写,还会不会调用Time类里的默认构造??
    //会,因为初始化列表是它定义的地方,每个成员都会走初始化列表,不写也会走一遍,
};
int main()
{
    int hour = 5;
    Date d1(hour, 2024, 9, 8);
    d1.Print();
    return 0;
}
Time()
2024/9/8

C:\Users\Lenovo\source\repos\初识C++\x64\Debug\初识C++.exe (进程 20188)已退出,代码为 0。
按任意键关闭此窗口. . .

但是如果没有默认构造:
错误:

例4:没有默认构造的类类型变量(必须用初始化列表)

class Time
{
public:
    Time(int hour)
        :_hour(hour)
    {
        cout << "Time()" << endl;
    }
private:
    int _hour;
};

class Date
{
public:
    Date(int& hour, int year, int month, int day)
        //初始化列表和大括号可以混着用
        :_minute(1)
        ,_hour(hour)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    void Print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }
private:
    int _year;
    int _month;
    int _day;
    const int _minute;//必须在初始化列表初始化
    int& _hour;//必须在初始化列表初始化
    Time _t;//如果Time有默认构造我们不写,还会不会调用Time类里的默认构造??
    //会,因为初始化列表是它定义的地方,每个成员都会走初始化列表,不写也会走一遍,
};
int main()
{
    int hour = 5;
    Date d1(hour, 2024, 9, 8);
    d1.Print();
    return 0;
}
“Time”: 没有合适的默认构造函数可用	

这时候只能在初始化列表中自己定义

#include<iostream>
using namespace std;

class Time
{
public:
    Time(int hour)
        :_hour(hour)
    {
        cout << "Time()" << endl;
    }
private:
    int _hour;
};

class Date
{
public:
    Date(int& hour, int year, int month, int day)
        //初始化列表和大括号可以混着用
        :_minute(1)
        , _hour(hour)
        , _t(1)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    void Print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }
private:
    //声明
    int _year;
    int _month;
    int _day;
    const int _minute;//必须在初始化列表初始化
    int& _hour;//必须在初始化列表初始化
    Time _t;//如果Time有默认构造我们不写,还会不会调用Time类里的默认构造??
    //会,因为初始化列表是它定义的地方,每个成员都会走初始化列表,不写也会走一遍,
};
int main()
{
    int hour = 5;
    Date d1(hour, 2024, 9, 8);
    d1.Print();
    return 0;
}
Time()
2024/9/8

C:\Users\Lenovo\source\repos\初识C++\x64\Debug\初识C++.exe (进程 43572)已退出,代码为 0。
按任意键关闭此窗口. . .

这种是在没有默认构造的情况下使用,或者是有默认构造,但是不想用,想自己写的情况。
有默认构造的时候,可以在初始化列表中写,也可以不写。

例5:初始化列表和函数体配合使用

#include<iostream>
using namespace std;

class Time
{
public:
    Time(int hour)
        :_hour(hour)
    {
        cout << "Time()" << endl;
    }
private:
    int _hour;
};

class Date
{
public:
    Date(int& hour, int year, int month, int day)
        //初始化列表和大括号可以混着用
        :_minute(1)
        , _hour(hour)
        , _t(1)
        ,_ptr((int*)malloc(12))
    {
        if (_ptr == nullptr)
        {
            perror("malloc fail");
        }
        else
        {
            memset(_ptr, 0, 12);
        }
        _year = year;
        _month = month;
        _day = day;
    }
    void Print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }
private:
    int _year;
    int _month;
    int _day;
    const int _minute;//必须在初始化列表初始化
    int& _hour;//必须在初始化列表初始化
    Time _t;//如果Time有默认构造我们不写,还会不会调用Time类里的默认构造??
    //会,因为初始化列表是它定义的地方,每个成员都会走初始化列表,不写也会走一遍
    int* _ptr;
};
int main()
{
    int hour = 5;
    Date d1(hour, 2024, 9, 8);
    d1.Print();
    return 0;
}
Time()
2024/9/8

C:\Users\Lenovo\source\repos\初识C++\x64\Debug\初识C++.exe (进程 36380)已退出,代码为 0。
按任意键关闭此窗口. . .

4.C++11支持在成员变量声明的位置给缺省值,这个缺省值主要是给没有显示在初始化列表初始化的成员使用的
5.尽量使用初始化列表初始化,因为那些你不在初始化列表初始化的成员也会走初始化列表,如果这个成员在声明位置给了缺省值,初始化列表会用这个缺省值初始化。如果你没有给缺省值,对于没有显示在初始化列表初始化的内置类型成员是否初始化取决于编译器,C++并没有规定。对于没有显示在初始化列表初始化的自定义类型成员会调用这个成员类型的默认构造函数,如果没有默认构造会编译错误

例6:_day不写初始化列表,还会走初始化列表吗??

#include<iostream>
using namespace std;

class Date
{
public:
    Date(int year, int month, int day)
        //初始化列表和大括号可以混着用
        :_year(year)
        , _month(month)
    {

    }
    void Print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};
int main()
{
    //对象定义
    Date d1(2024, 9, 8);
    d1.Print();
    return 0;
}
2024/9/-858993460

C:\Users\Lenovo\source\repos\初识C++\x64\Debug\初识C++.exe (进程 18000)已退出,代码为 0。
按任意键关闭此窗口. . .

答案:
会,因为初始化列表是每个成员变量定义的地方,但是有些变量可以不用初始化
但是这样产生的结果是不确定的
注意:

private:
    int _year = 1;//缺省值->给初始化列表用的,可以解决没有在初始化列表中写的成员变量,结果是随机值的情况
    int _month = 1;
    int _day = 1;
};

并非定义,开空间才叫定义!!!

例7:缺省值赋值给没有显示在初始化列表初始化的成员

#include<iostream>
using namespace std;

class Date
{
public:
    Date(int year, int month, int day)
        //初始化列表和大括号可以混着用
        :_year(year)
        , _month(month)
    {

    }
    void Print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }
private:
    int _year = 1;
    int _month = 1;
    int _day = 1;
};
int main()
{
    Date d1(2024, 9, 8);
    d1.Print();
    return 0;
}
2024/9/1

C:\Users\Lenovo\source\repos\初识C++\x64\Debug\初识C++.exe (进程 32200)已退出,代码为 0。
按任意键关闭此窗口. . .

定义时赋值,用法一:

这种方法可以确保在自己没有写参数构造的时候,编译器默认生成的构造不会对成员变量赋随机值。

#include<iostream>
using namespace std;

class Date
{
public:
    void Print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }

private:
    int _year = 1;
    int _month = 1;
    int _day = 1;
};
int main()
{
    Date d1;
    d1.Print();
    return 0;
}
1/1/1

C:\Users\Lenovo\source\repos\初识C++\x64\Debug\初识C++.exe (进程 39544)已退出,代码为 0。
按任意键关闭此窗口. . .

定义时赋值,用法二:

#include<iostream>
using namespace std;

class Time
{
public:
    Time(int hour = 1)
        :_hour(hour)
    {
        cout << "Time()" << endl;
    }
private:
    int _hour;
};

class Date
{
public:
    Date(int year, int month, int day)
        //初始化列表和大括号可以混着用
        :_year(year)
        , _month(month)
        ,_day(day)
    {

    }
    void Print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }
private:
    int _year = 1;
    int _month = 1;
    int _day = 1;
    int* _ptr = (int*)malloc(12);
    Time _t = 1;
};
int main()
{
    Date d1(2024, 9, 8);
    d1.Print();
    return 0;
}
Time()
2024/9/8

C:\Users\Lenovo\source\repos\初识C++\x64\Debug\初识C++.exe (进程 23960)已退出,代码为 0。
按任意键关闭此窗口. . .

综上:
每个构造函数都有初始化列表
最好走初始化列表。
每个成员都要走初始化列表
1、在初始化列表初始化的成员
2、没有在初始化列表的成员
(1)、声明的地方有缺省值就用缺省值
(2)、没有缺省值
①、内置类型,不确定,看编译器,大概率是随机值。
②、自定义类型,调用默认构造,没有默认构造就编译报错。
3、引用、const和没有默认构造的自定义必须在初始化列表初始化

注意缺省值的位置
#include<iostream>
using namespace std;

class Time
{
public:
    Time(int hour = 1)
        :_hour(hour)
    {
        cout << "Time()" << endl;
    }
private:
    int _hour;
};

class Date
{
public:
    Date(int year = 1, int month = 1, int day = 1)
        //初始化列表和大括号可以混着用
        :_year(year)
        , _month(month)
    {

    }
    void Print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }
private:
    int _year = 1;
    int _month = 1;
    int _day;
    int* _ptr = (int*)malloc(12);
    Time _t = 1;
};
int main()
{
    Date d1(2024, 9, 8);
    d1.Print();
    return 0;
}
Time()
2024/9/0

C:\Users\Lenovo\source\repos\初识C++\x64\Debug\初识C++.exe (进程 17748)已退出,代码为 0。
按任意键关闭此窗口. . .
Date(int year = 1, int month = 1, int day = 1)
//只管传不传参,传参就和它没关系了

private:
    int _year = 1;
    int _month = 1;
    int _day;
    int* _ptr = (int*)malloc(12);
    Time _t = 1;
    //和初始化列表息息相关

在构造函数中给缺省值,初始化列表中没有_day成员变量,最后会给随机值,缺省值位置不对。
4.初始化列表中按照成员变量在类中声明顺序进行初始化,跟成员在初始化列表出现的先后顺序无关。建议声明顺序和初始化列表顺序保持一致。

例8:

#include<iostream>
using namespace std;

class A
{
public:
    A(int a)
        :_a1(a)
        , _a2(_a1)
    {
       
    }

    void Print()
    {
        cout << _a1 << " " << _a2 << endl;
    }

private:
    int _a2 = 2;
    int _a1 = 2;
};

int main()
{
    A aa(1);
    aa.Print();
    return 0;
}
1 -858993460

C:\Users\Lenovo\source\repos\初识C++\x64\Debug\初识C++.exe (进程 15216)已退出,代码为 0。
按任意键关闭此窗口. . .

先走 , _a2(_a1),因为在private中,先定义的_a2,所以先去找它,因为_a1是随机值,所以_a2也是随机值,然后找_a1,调用参数a,所以_a1是1。
声明的顺序是在内存中存放的顺序。

十七、static成员

1.用static修饰的成员变量,称之为静态成员变量,静态成员变量一定要在类外进行初始化

例1:

#include<iostream>
using namespace std;

class A
{
public:
    A()
    {
        ++_scount;
    }

    A(const A& t)
    {
        ++_scount;
    }

    ~A()
    {
        --_scount;
    }
    static int GetACount()
    {
        return _scount;
    }
private:
    //类里面声明
    //类中不能给缺省值
    // static int _scount = 0;//报错
    static int _scount;
};

//类外面初始化
int A:: _scount = 0;

int main()
{
    cout << A::GetACount() << endl;
    cout << sizeof(A) << endl;
    return 0;
}
0
1

C:\Users\Lenovo\source\repos\初识C++\x64\Debug\初识C++.exe (进程 11240)已退出,代码为 0。
按任意键关闭此窗口. . .

PS:
1、静态成员变量可以理解为静态全局变量,只不过受类域的限制,只能在类中使用,但是它的生命周期是全局的。
2、也可以在类外使用,但是需要用域作用限定符,前提是它为公有的。
3、静态成员变量不会走初始化列表,因为它不是存在在对象中的

cout << sizeof(A) << endl;//结果为1,没有包含静态成员

2.静态成员变量为所有类对象所共享,不属于某个具体的对象,不存在对象中,存放在静态区
3.用static修饰的成员函数,称之为静态成员函数,静态成员函数没有this指针

例2:

#include<iostream>
using namespace std;

class A
{
public:
    A()
    {
        ++_scount;
    }

    A(const A& t)
    {
        ++_scount;
    }

    ~A()
    {
        --_scount;
    }
    static int GetACount()//静态成员函数
    {
        return _scount;
    }
private:
    //类里面声明
    //类中不能给缺省值
    // static int _scount = 0;//报错
    static int _scount;
};

//类外面初始化
int A:: _scount = 0;

int main()
{
    cout << A::GetACount() << endl;
    A a1, a2;//普通构造
    A a3(a1);//拷贝构造
    cout << A::GetACount() << endl;
    return 0;
}
0
3

C:\Users\Lenovo\source\repos\初识C++\x64\Debug\初识C++.exe (进程 29644)已退出,代码为 0。
按任意键关闭此窗口. . .
#include<iostream>
using namespace std;

class A
{
public:
    A()
    {
        ++_scount;
    }

    A(const A& t)
    {
        ++_scount;
    }

    ~A()
    {
        --_scount;
    }
    static int GetACount()
    {
        return _scount;
    }
private:
    //类里面声明
    //类中不能给缺省值
    // static int _scount = 0;//报错
    static int _scount;
};

//类外面初始化
int A:: _scount = 0;

int main()
{
    cout << A::GetACount() << endl;
    A a1, a2;//普通构造
    {
        A a3(a1);//拷贝构造
        cout << A::GetACount() << endl;
    }
    cout << A::GetACount() << endl;
    return 0;
}
0
3
2

C:\Users\Lenovo\source\repos\初识C++\x64\Debug\初识C++.exe (进程 14536)已退出,代码为 0。
按任意键关闭此窗口. . .

为什么又变成2??
因为域结束,对象就会销毁,就会调用析构函数,–_scount,所以变成2了。

4.静态成员函数中可以访问其他的静态成员,但是不能访问非静态的,因为没有this指针
5.非静态的成员函数,可以访问任意的静态成员变量和静态成员函数
6.突破类域就可以访问静态成员,可以通过类名::静态成员 或者 对象.静态成员来访问静态成员变量和静态成员函数。

十七、类型转换

单参数:

#include<iostream>
using namespace std;

class A
{
public:
    A(int a)
    {
        i = a;
    }

    A(const A& aa)
    {
        i = aa.i;
    }

    void Print()
    {
        cout << i << endl;
    }

private:
    int i;
};

int main()
{
    A aa1(1);
    aa1.Print();

    //隐式类型转换
    //构造一个A的临时对象,再用这个临时对象拷贝构造aa2
    //编译器遇到连续构造 + 拷贝构造 -> 优化为直接构造
    A aa2 = 2;
    aa2.Print();

    return 0;
}

为什么不是直接构造??

 A& raa1 = aa1;
    A& raa2 = aa2;
    A& raa2 = 2;//报错

因为类型转换会建立临时变量,而临时变量具有常性,所以A引用会导致权限放大,所以报错,
修改为const A& raa2 = 2;即可。

多参数:

#include<iostream>
using namespace std;

class A
{
public:
    A(int a1, int a2)
    {
        i = a1;
        j = a2;
    }
    A(const A& aa)
    {
        i = aa.i;
        j = aa.j;
    }

    void Print()
    {
        cout << i <<" " << j << endl;
    }

private:
    int i;
    int j;
};

int main()
{
    A aa1(1, 2);
    aa1.Print();

    //隐式类型转换
    //构造一个A的临时对象,再用这个临时对象拷贝构造aa2
    //编译器遇到连续构造 + 拷贝构造 -> 优化为直接构造
    A aa2 = {2, 3};
    aa2.Print();

    A& raa1 = aa1;
    A& raa2 = aa2;
    const A& raa3 = {4, 5};
    return 0;
}

如果不允许类型转换:
加一个explicit
例:explicit A(int a)

十八、例题:

C c;
int main()
{
	A a;
	B b;
	static D d;
	return 0;
}

A,B,C,D四个类的定义,程序中A,B,C,D构造函数调用顺序为 C A B D
解析:
static是运行到它的时候才会初始化
无论是内置类型,还是自定义类型都是一样的,它们是在第一次走到这里的时候才会初始化
而全局则会在main()之前初始化
注意:
A,B,C,D四个类的定义,程序中A,B,C,D析构函数调用顺序为 B A D C
解析:
C D的生命周期是全局的,所以一定是先析构局部的,C D靠后,
然后是后定义的先析构,所以A在B后面,
D生命周期虽然是全局,但是被限制在局部中,所以C在D之前
所以是 B A D C

十九、友元

1.友元提供了一种突破类访问限定符封装的方式,友元分为:友元函数和友元类,在函数声明或者类声明的前面加friend,并且把友元声明放到一个类的里面。
2.外部友元函数可访问类的私有和保护成员,友元函数仅仅是一种声明,他不是类的成员函数。
3.友元函数可以在类定义的任何地方声明,不受类访问限定符限制。
4.一个函数可以是多个类的友元函数
5.友元类中的成员函数都可以是另一个类的友元函数,都可以访问另一个类中的私有和保护成员。
6.友元类的关系是单向的,不具有交换性,比如A类是B类的友元,但是B类不是A类的友元。
7.友元类关系不能传递,如果A是B的友元, B是C的友元,但是A不是C的友元。
8.有时提供了便利,但是友元会增加耦合度,破坏了封装,所以友元不宜多用

二十、内部类

1.如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,跟定义在全局相比,他只是受外部类类域限制和访问限定符限制,所以外部类定义的对象中不包含内部类

#include<iostream>
using namespace std;

class A
{
private:
	static int _k;
	int _h = 1;
public:
	class B
	{
	public:
		void foo(const A& a)
		{
			cout << _k << endl;
			cout << a._h << endl;
		}
	private:
		int _b = 1;
	};
};
int main()
{
    A::B b;//前提是B类在A的public中,在private中就不行了
	cout << sizeof(A) << endl;
	return 0;
}
4

C:\Users\Lenovo\source\repos\初识C++\x64\Debug\初识C++.exe (进程 41340)已退出,代码为 0。
按任意键关闭此窗口. . .

可见A类中只有一个_h,不包含B类中的_b。
内部类的定义其实也就是分开定义两个类

class A
{
private:
	static int _k;
	int _h = 1;
public:
};

class B
{
public:
	void foo(const A& a)
	{
		cout << _k << endl;
		cout << a._h << endl;//因为是友元,所以可以访问A的私有
	}
private:
	int _b = 1;
};

这两个效果一样,不一样的仅仅是B受A的类域限制和访问限定符限制
2.内部类默认是外部类的友元类

#include<iostream>
using namespace std;

class A
{
private:
	static int _k;
	int _h = 1;
public:
	class B
	{
	public:
		void foo(const A& a)
		{
			cout << _k << endl;
			cout << a._h << endl;
		}
	private:
		int _b = 1;
	};
};

int A::_k = 1;

int main()
{
	A::B b;
	cout << sizeof(A) << endl;
	A aa;
	b.foo(aa);
	return 0;
}
4
1
1

C:\Users\Lenovo\source\repos\初识C++\x64\Debug\初识C++.exe (进程 42040)已退出,代码为 0。
按任意键关闭此窗口. . .

3.内部类本质也是一种封装,当A类跟B类紧密关联,A类实现出来主要就是给B类使用,那么可以考虑把A类设计为B的内部类,如果放到private/protected位置,那么A类就是B类的专属内部类,其他地方都用不了。

二十一、匿名对象

A aa1;//有名对象
A aa2();//不能这么定义,编译器无法识别是一个函数声明还是对象定义。
A();.//匿名对象(不传参)
A(1);//匿名对象(传参)

匿名对象怎么调用??

cout << A().Print() << endl;

匿名对象的生命周期只在那一行。
有名对象是为了在当前域里用,可以在域中长期用,匿名对象就是一次性的。

  • 11
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值