【C++】 封装/重载/友元

news2024/11/24 14:54:26

文章目录

  • 一、内存分区、引用、函数
    • 1 内存分区模型
    • 2 引用(reference)(指针常量)
    • 3 函数默认参数
    • 4 函数占位参数
    • 5 函数重载
  • 二、封装
    • 1 struct和class区别
  • 三、对象的构造和析构
    • 1 构造函数的分类及调用
    • 2 拷贝构造函数调用时机
    • 3 构造函数调用规则
    • 4 深拷贝与浅拷贝
    • 5 构造函数初始化列表
    • 6 静态成员
  • 四、C++ 对象模型、this指针、友元、const、运算符重载
    • 1 C++对象模型
    • 2 this指针(非静态成员函数才有)
    • 3 const修饰成员函数
    • 4 友元(访问私有成员)
    • 5 运算符(operator)重载(overloading)
      • 5.1 加号运算符重载(operater+)
      • 5.2 左移运算符重载(operator<<)
      • 5.3 递增运算符重载(operator++)
      • 5.4 赋值运算符重载(operator=)
      • 5.5 关系运算符重载
      • 5.6 函数调用运算符重载(operator())

一、内存分区、引用、函数

1 内存分区模型

在这里插入图片描述

  • 栈区:由编译器自动分配和释放,存放函数的参数值,局部变量等
  • 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收
  • 全局区(全局/静态存储区):存放全局变量和静态变量(static)以及常量
  • 代码区:存放函数体的二进制代码,由操作系统进行管理的

C/C++中内存分5大区:栈,堆,全局/静态存储区,常量存储区,代码区
栈(stack):指那些由编译器在需要的时候分配,不需要时手动清除的变量所在的存储区,效率高,分配的内存空间有限,形参和局部变量分配在栈区,栈是向低地址生长的数据结构,是一块连续的内存
堆(heap):由程序员控制内存的分配和释放的存储区,是向高地址生长的数据结构,是不连续的存储空间,堆的分配(malloc)和释放(free)有程序员控制,容易造成二次删除和内存泄漏
全局/静态存储区(static):存放全局变量和静态变量的存储区,在程序结束后释放这块空间
常量存储区(const):字符串常量的存储区,只能读不能写,const修饰的全局变量存储在常量区(取决于编译器),const修饰的局部变量在栈区
程序代码区:存放源程序二进制代码

1、程序运行前
在程序编译后,生成了exe可执行程序,未执行该程序前分为两个区域:
(1)代码区:

  • 存放CPU执行的机器指令
  • 代码区是共享的,对于频繁被执行的程序,只需在内存中有一份代码即可
  • 代码区是只读的,防止程序意外地修改了它的指令

(2)全局区:

  • 全局变量(函数体外)和静态变量存放在此
  • 程序结束后由操作系统释放
  • 函数体内的变量为局部变量,函数体外定义的变量为全局变量,全局变量和局部变量的地址不在同一段内

2、程序运行后
(1)栈区:

  • 由编译器自动分配释放,存放函数的参数值(形参),局部变量等

注意事项:不要返回局部变量的地址(第一次有保留,第二次不行),栈区开辟的数据由编译器自动释放。

int *func(int b) // 形参(栈区)
{
    b = 100;
    int a = 10; // 局部变量(栈区)
    return &a;
} // 指针变量本质也是局部变量,放栈区(地址放栈上)

(2)堆区:

  • 利用new/delete在堆区开辟内存
  • 利用new创建的数据(返回指针),会返回数据对应的类型的指针(即地址)

在这里插入图片描述

int *func()
{
    // 开辟内存空间,存放数据为10
    int *p = new int(10);
    return p;
}
// 释放
delete p;
// 数组
int *arr = new int[10];
delete[] arr;

2 引用(reference)(指针常量)

  • 作用:给变量起别名
  • 语法: 数据类型 &别名=原名 //int &b=a;

1、引用注意事项

  • 引用必须要初始化  (int &b=a;)
  • 引用在初始化后,不可以改变 (b作为a的别名时就不可以做c的别名)

2、引用做函数参数

  • 作用:函数传参时,可以利用引用的技术让形参修饰(更改)实参(实参传给形参)
  • 优点:可以简化指针修改实参
void mySwap01(int a,int b)//1、值传递
{
    int temp = a;
    a = b;
    b = temp;
    cout << "Swap01a= " << a << endl;
    cout << "Swap01b= " << b << endl;
 
}
//2.地址传递
void mySwap02(int*a, int*b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
    cout << "Swap02a= " << *a << endl;
    cout << "Swap02b= " << *b << endl;
}
 
//3.引用传递
void mySwap03(int&a,int&b)
{
    int temp = a;
    a = b;
    b = temp;
    cout << "Swap03a= " << a << endl;
    cout << "Swap03b= " << b << endl;
}
 
int main()
{  
    int a = 10, b = 20;
    //mySwap01(a, b);//值传递,形参不会修饰实参
    //mySwap02(&a,&b);//地址传递形参会修饰实参
    mySwap03(a, b);//引用传递,形参会修饰实参
 
    cout << "a= " << a << endl;
    cout << "b= " << b << endl;
 
 
    system("pause");
    return 0;
}
  • 实参:传递给函数的值
  • 形参:用于接收传递值的变量

3、引用做函数返回值

  • 作用:引用是可以作为函数的返回值存在的
  • 注意:不要返回局部变量引用
  • 用法:函数调用作为左值(作为等号左边)
//引用做函数返回值
//1.不要返回局部变量的引用
int& test01()
{
    int a = 10;//局部变量 存放在四区中的 栈区
    return a;
 
}
 
//2.函数的调用可以作为左值
int& test02()
{
    static int a = 10;//静态变量,存放在 全局区,全局区上的数据在程序结束后系统释放
    return a;
}
int main()
{  
    int &ref = test01();
 
    cout << "ref= " << ref << endl;//第一次结果正确是因为编译器做了保留
    cout << "ref= " << ref << endl;//第二次结果错误因为a的内存已经释放
 
    int &ref2 = test02();
    cout << "ref2= " << ref2 << endl;
    cout << "ref2= " << ref2 << endl;
 
    test02() = 1000;//如果函数的返回值是引用,这个函数的调用可以作为左值
 
    cout << "ref2= " << ref2 << endl;
    cout << "ref2= " << ref2 << endl;
 
    system("pause");
    return 0;
}

4、引用的本质

  • 本质:引用的本质在C++内部实现是一个指针常量(指向不可以改,指向的值可以改)
//发现是引用,转换为int* const ref=&a
void func(int& ref)
{
    ref = 100;//ref是引用,转换为*ref=100
}
int main()
{  
    int a = 10;
 
    //自动转换为 int* const ref=&a;指针常量是指针指向不可以变,也说明为什么引用不可更改
    int& ref = a;
    ref = 20;//内部发现ref是引用,自动帮我们转换为*ref=20;
 
    cout << "a= " << a << endl;
    cout << "ref= " << ref << endl;
 
    func(a);
    cout << "a= " << a << endl;
    cout << "ref= " << ref << endl;
    
    system("pause");
    return 0;
}
  • 结论:C++推荐用引用技术,因为语法方便,引用本质是指针常量,但是所有的指针操作编译器都帮我们做了

5、常量引用(加const)

  • 作用:常量引用主要用来修饰形参,防止误操作
  • 在函数形参列表中,可以加const修饰形参,防止形参改变实参
//打印数据函数
void showValue( const int &val)
{
    //val = 1000; //加了const不允许修改,防止误操作
    cout << "val= " << val << endl;
}
int main()
{  
    //常量引用
    //使用场景:用来修饰形参,防止误操作
    //int &refs = 10;//会报错,引用必须引用一块合法的内存空间
 
    //加上const之后 编译器将代码修改 int temp=10; const int &ref=temp;
    //const int &ref = 10;
    //ref = 20;//报错,加入const之后变为只读状态,不可以修改
 
 
    int a = 100;
    showValue(a);
    cout << "a=" << a << endl;
 
    system("pause");
    return 0;
 
}

3 函数默认参数

  • 在C++中,函数的形参列表中的形参是可以有默认值的。
  • 语法:返回值类型 函数名 (参数=默认值){}
// 函数默认参数
// 如果我们自己传入数据,就用自己的,如果没有,那么用默认值
// 语法: 返回值类型  函数名称(形参=默认值){}
int func(int a, int b = 20, int c = 30)
{
    return a + b + c;
}
// 注意事项
// 1.如果某个位置已经有了默认参数,那么从这个位置往后,从左到右都必须有默认值
// 2.如果函数声明有默认参数,函数实现就不能有默认参数
// 声明和实现只能有一个默认参数
int func2(int a = 10, int b = 10);
int func2(int a = 10, int b = 10) // 报错,不能重定义默认参数
{
    return a + b;
}
int main()
{

    // cout << func(10) << endl;
    cout << func2(10, 10) << endl;
    system("pause");
    return 0;
}

4 函数占位参数

  • 形参列表里可以有占位参数,用来做占位,调用函数时必须填补该位置
  • 语法:返回值类型 函数名(数据类型){}

在现阶段函数的占位参数意义不大,但是后面的课程中会用到该技术。

//可以用于区分前置和后置递增
//前置 
MyInteger& operator++()
//后置 
MyInteger operator++(int)(只能用int
//占位参数
//返回值类型 函数名(数据类型){}
//占位参数,还可以有默认参数
void func(int a,int=10)//默认参数
{
    cout << "this is func " << endl;
}
 
int main()
{  
    func(10);
    system("pause");
    return 0;
}
void func(int a,int)
{
    cout << "this is func " << endl;
}
 
int main()
{  
    func(1010);//填补
    system("pause");
    return 0;
 
}

5 函数重载

  • 函数名可以相同,提高复用性

函数重载满足条件:

  • 同一个作用域下(全局作用域或其他)
  • 函数名称相同
  • 函数参数类型不同,或者个数不同,或者顺序不同,引用作为重载条件(有无const)
  • 注意:函数的返回值不可以作为函数重载的条件
// 函数重载
// 可以让函数名相同,提高复用性
// 函数重载的满足条件
// 1.同一个作用域下
// 2.函数名称相同
// 3.函数参数类型不同,或者个数不同,或者顺序不同
void func()
{
    cout << "func 的调用" << endl;
}

void func(int a)
{
    cout << "func(int a) 的调用" << endl;
}
void func(double a)
{
    cout << "func(double a) 的调用" << endl;
}
void func(int a, double b)
{
    cout << "func(int a,double b) 的调用" << endl;
}
void func(double a, int b)
{
    cout << "func(double a,int b) 的调用" << endl;
}

// 注意事项:函数的返回值不可以作为函数重载的条件
// int func(double a, int b)//报错,无法重载
//{
//   cout << "func(double a,int b) 的调用" << endl;
// }
int main()
{
    func();
    func(10);
    func(3.14);
    func(10, 3.14);
    func(3.14, 10);
    system("pause");
    return 0;
}

函数重载注意事项:

  • 引用作为重载条件(有无const)
  • 写函数重载不要加函数默认参数(容易出现二义性)
// 函数重载注意事项
// 1.引用作为重载的条件
void func(int &a) // int &a=10:不合法
{
    cout << "func(int &a) 调用" << endl;
}
// 防止形参修改实参
void func(const int &a) // const int &a=10:合法
{
    cout << "func(const int &a) 调用" << endl;
}

// 2.函数重载碰到默认参数
void func2(int a, int b = 10)
{
    cout << "func2(int a,int b)的调用" << endl;
}
void func2(int a)
{
    cout << "func2(int a)的调用" << endl;
}
int main()
{
    int a = 10;
    func(a);   // 调用无const
    func(10);  // 调用有const
    //func2(10); // 当函数重载碰到默认参数,会出现二义性。报错,尽量避免这种情况

    system("pause");
    return 0;
}

二、封装

  • C++面向对象的三大特性为:封装、继承、多态

  • C++认为万事万物都皆为对象,对象上有其属性和行为

  • 将属性和行为作为一个整体,表现生活中的事物。

  • 将属性和行为加以权限控制。

  • 属性:成员属性/成员变量

  • 行为(可以给行为 可以给属性赋值):成员函数/成员方法

访问权限有三种

  • public 公共权限
  • protected 保护权限
  • private 私有权限

1 struct和class区别

在C++中 struct和class唯一的区别就在于默认的访问权限不同:

  • struct  默认权限为公共
  • class  默认权限为私有

三、对象的构造和析构

  • 编译器自动调用,编译器强制我们做的事情,如果我们不提供,编译器会提供,编译器提供的构造函数和析构函数是空实现
  • 构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无需手动调用。
  • 析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。

构造函数语法:类名(){}

  1. 构造函数,没有返回值也不写void
  2. 函数名称与类名相同
  3. 构造函数可以有参数,因此可以发生函数重载
  4. 函数在调用对象时会自动调用构造,无需手动调用,而且只会调用一次

析构函数语法:~类名(){}

  1. 析构函数,没有返回值也不写void
  2. 函数名称与类名相同,在名称前加上符号~
  3. 析构函数不可以有参数,因此不可以发生重载
  4. 程序在对象销毁前会自动调用析构,无需手动调用,而且只会调用一次
int main()
{
	Person p;
	system("pause");//暂停在这,结束后才释放p(结束前瞬间调用析构)
	return 0;
}
  • 当其他类对象作为本类成员,构造时先构造类对象,再构造自身(栈先进后出)
  • 析构的顺序与构造相反

1 构造函数的分类及调用

两种分类方式

  • 按参数分为:有参构造和无参构造(默认构造)
  • 按类型分为:普通构造和拷贝构造
  • 拷贝构造调用形式 Person(const Person &p)

三种调用方式:

  • 括号法Person p2(10);//有参构造函数调用
  • 显示法Person p2 = Person(10);//有参构造
  • 隐式转换法 Person p4 = 10;
  • 默认构造可以用
Person p={};//赋值方式(沿用结构体)
Person p1;//默认构造函数调用

注意事项1:调用默认构造函数时,不要加()

//因为下面这行代码,编译器会认为是一个函数的声明,不会认为在创建对象
Person p1();
void func();

注意事项2:不要利用拷贝构造函数 初始化匿名对象 Person(p3);

Person(10);//匿名对象  特点:当前行执行结束后,系统会立即析构掉匿名对象
Person(p3);//不可以。编译器会认为Person(p3) == Person p3;看成对象声明

2 拷贝构造函数调用时机

  • 使用一个已经创建完毕的对象来初始化一个新对象
  • 值传递的方式给函数参数传值(值传递//值传递本身就是传递副本
  • 以值方式返回局部对象(值返回)//值的方式返回,拷贝新的对象返回
// 1、使用一个已经创建完毕的对象来初始化一个新对象
void test01()
{
    Person p1(20);
    Person p2(p1);
    cout << "p2的年龄为:" << p2.m_Age << endl;
}

// 2、值传递的方式给函数参数传值
void doWork(Person p)
{
}

void test02()
{
    Person p;  // 调用默认构造
    doWork(p); // 调用拷贝构造
               // 值传递本身就是传递副本
}
// 3、值方式返回局部对象
Person doWork2()
{
    Person p1; // 调用默认构造
    cout << (int *)&p1 << endl;
    return p1; // 值的方式返回,拷贝新的对象返回
}

3 构造函数调用规则

默认情况下,C++编译器至少给一个类添加3个函数

  1. 默认构造函数(无参数,函数体为空)
  2. 默认析构函数(无参数,函数体为空)
  3. 默认拷贝构造函数,对成员变量进行值拷贝(值传递、值返回)

构造函数调用规则如下

  • 用户定义有参构造函数,C++不在提供默认无参构造,但是会提供默认拷贝构造
  • 用户定义拷贝构造函数,C++不会再提供其他构造函数
Person()
{
    cout << "Person的默认构造函数调用" << endl;
}
Person(int age)
{
    m_Age = age;
    cout << "Person的有参构造函数调用" << endl;
}
Person(const Person &p)
{
    m_Age = p.m_Age; // 默认拷贝构造函数会有
    cout << "Person的拷贝构造函数调用" << endl;
}

4 深拷贝与浅拷贝

  • 浅拷贝:简单的赋值拷贝操作(编译器提供的等号赋值操作)
  • 深拷贝:在堆区重新申请空间,进行拷贝操作(自己创建内存空间)
  • 浅拷贝带来的问题就是堆区内存的重复释放
  • 总结如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题。
  • 指针放在栈区,指针保存的数据放在堆区
 // 自己实现拷贝构造函数,解决浅拷贝带来的问题
    Person(const Person &p)
    {
        cout << "Person的拷贝构造函数调用 " << endl;
        m_Age = p.m_Age;
        // m_Height = p.m_Height;//编译器默认实现就是这行代码,即浅拷贝

        // 深拷贝操作
        m_Height = new int(*p.m_Height); // 不需要重复声明int*,会报错
    }
  ~Person()
    {
        // 析构代码,将堆区开辟数据做释放操作
        if (m_Height != NULL) // 判断指针(地址)是否为空
        {
            delete m_Height;
            m_Height = NULL; // 防止野指针出现
            // 如果没有深拷贝会报错,浅拷贝带来的问题就是堆区内存的重复释放
        }
        cout << "Person的析构函数调用" << endl;
    }
    
    //指针放在栈区,指针保存的数据放在堆区
    int m_Age;//年龄
    int *m_Height;//身高(要放在堆区用指针)

在这里插入图片描述
在这里插入图片描述

5 构造函数初始化列表

  • 作用:C++提供了初始化列表语法,用来初始化属性。
  • 语法: 构造函数():属性1(值1),属性2(值2)....{}
// 传统初始化操作
Person(int a, int b, int c)
{
    m_A = a;
    m_B = b;
    m_C = c;
}

// 初始化列表初始化属性(30传入int a,int a传入m_A(a){};
Person(int a, int b, int c) : m_A(a), m_B(b), m_C(c){};

在这里插入图片描述

6 静态成员

  • 静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员。

静态成员分为

  • 静态成员变量
    • 所有对象共享同一份数据
    • 在编译阶段分配内存(全局区)
    • 类内声明,类外初始化(要有初始值)
  • 静态成员函数
    • 所有对象共享同一个函数实例(非静态成员函数也只有一份函数实例)
    • 静态成员函数只能访问静态成员变量。(静态成员函数不可以访问非静态成员变量)

有两种访问方式(对象访问、类名访问)

  • 通过对象访问
  • 通过类名访问(需要作用域::

  • 静态成员变量
// 静态成员变量
class Person
{
public:
    static int m_A;

// 静态成员变量也是有访问权限的
private:
    static int m_B;
};
int Person::m_A = 100; // 类外初始化
int Person::m_B = 200;
void test01()
{
    Person p;
    cout << p.m_A << endl;
    Person p2;
    p2.m_A = 200;
    cout << p.m_A << endl; // 所有对象都共享
}
void test02()
{

    // 1、通过对象进行访问(非静态只能创建对象访问)
    Person p;
    cout << p.m_A << endl;

    // 2、通过类名进行访问
    cout << Person::m_A << endl;
    // 报错,类外访问不到私有静态成员变量
    cout << Person::m_B << endl;
}
int main()
{
    // test01();
    test02();

    system("pause");
    return 0;
}

  • 静态成员函数
class Person
{
public:
    // 静态成员函数
    static void func()
    {
        m_A = 100; // 静态成员函数可以访问静态成员变量
        m_B = 200; // 会报错 静态成员函数不可以访问
                   // 非静态成员变量 无法区分到底是哪个对象的m_B属性
        cout << "static void func调用" << endl;
    }
    static int m_A; // 静态成员变量
    int m_B;

// 静态成员函数也是有访问权限的
private:
    static void func2()
    {
        cout << "static void func2的调用" << endl;
    }
};
int Person::m_A = 0; // 类外初始化


void test01()
{
    // 1、通过对象访问
    Person p;
    p.func();
    // 2、通过类名访问
    Person::func();

    Person::func2(); // 报错 类外访问不到私有的静态成员函数
}

int main()
{
    test01();

    system("pause");
    return 0;
}

四、C++ 对象模型、this指针、友元、const、运算符重载

1 C++对象模型

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

  • 只有非静态成员属性才属于类的对象上(在类上不是唯一的,对象上唯一)
  • 不属于类的对象上,为独一无二一份的
class Person
{
    // 结果4个字节
    int m_A; // 非静态成员变量  属于类的对象上

    // 结果4个字节
    static int m_B; // 静态成员变量   不属于类的对象上

    // 结果4个字节
    void func() // 非静态成员函数  不属于类的对象上
    {
    }

    // 结果4个字节
    static void func2() // 静态成员函数  不属于类的对象上
    {
    }
};

空对象

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

2 this指针(非静态成员函数才有)

  • 每一个成员函数(包括static)只会诞生一份函数实例,多个同类型的对象会共用一块代码。

C++通过提供特殊的对象指针(this指针),this指针指向被调用的成员函数所属的对象

  • this指针是隐含在每一个非静态成员函数内的一种指针
  • this指针不需要定义,直接使用即可。
    在这里插入图片描述

this指针(本质指针常量)的用途

  • 解决名称冲突(this代表自身)
    • 当形参和成员变量(属性)同名时,可用this指针来区分 this->age += p.age;
  • 返回对象本身用*this(自身)
    • 在类的非静态成员函数中返回对象本身,可使用return *this
lass Person
{
public:
    Person(int age)
    {
        // this指针指向被调用的成员函数所属的对象
        this->age = age; // 自身age
    }
    // 如果不返回引用而返回值 Person,会调用拷贝函数,创建新对象
    Person &PersonAddAge(Person &p)
    {
        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对象
    // 值返回是新对象p2'
    p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1); // 调用

    cout << "p2的年龄为: " << p2.age << endl;
}
int main()
{
    // test01();
    test02();

    system("pause");
    return 0;
}

空指针访问成员函数

  • 空指针可以调用成员函数的,但是也要注意有没有用到this指针。Person *p = NULL;
  • 如果用到this指针,需要加以判断保证代码的健壮性。if (this == NULL)
  • 属性都默认this指针指向的属性,表示当前对象的
// 空指针调用成员函数
class Person
{
public:
    void showClassName()
    {
        cout << "this is Person class" << endl;
    }
    void showPersonAge()
    {
        // this指向的对象为空
        if (this == NULL)
        {           // return跳出函数
            return; // 避免this的坑 提高代码健壮性
        }

        // 属性都默认this->m_Age,表示当前对象的属性(没有确定的对象会报错)
        cout << "age= " << m_Age << endl;
    }
    int m_Age;
};
void test01()
{                     // 指向对象的指针p,此时对象为空
    Person *p = NULL; // NULL为空

    p->showClassName(); // 调用成员函数
    p->showPersonAge(); // 报错
}

int main()
{
    test01();

    system("pause");
    return 0;
}

3 const修饰成员函数

常函数:

  • 成员函数后加const我们称这个函数为常函数void showPerson() const {}
  • 常函数内不可以修改成员属性
  • 成员属性声明时加关键字mutable后,可以修改

常对象

  • 声明对象前加const称该对象为常对象 const Person p;
  • 对象的属性不能改,有mutable可以改
  • 常对象只能调用常函数(因为普通成员函数可以修改属性)

在这里插入图片描述

// 常函数
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指向的值不可以修改
        // 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;
    // p.m_A = 100; //报错
    p.m_B = 100; // m_B是一个特殊值,在常对象下也可以修改

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

4 友元(访问私有成员)

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

友元的三种实现

  • 全局函数做友元(main外的) friend void goodGay(Building *building);
    • 全局函数类内实现(建议)——直接在类内声明友元friend即可,对象不可调用全局函数
    • 全局函数类外实现——需要提前让编译器知道全局函数的存在
  • 类做友元 friend class GoodGay;
  • 成员函数做友元 friend void GoodGay::visit();
  • 注意:类之间的编译过程有先后顺序,未提到必须提前声明

  • 全局函数做友元
// 建筑物类
class Building
{
    // goodGay为全局函数。为Building类的友元,可以访问Building中私有成员
    friend void goodGay(Building *building);

public:
    Building()
    {
        m_SittingRoom = "客厅";
        m_BedRoom = "卧室";
    }

public:
    string m_SittingRoom; // 客厅
private:
    string m_BedRoom;
};

// 全局函数(写在main外面)
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;
}

  • 类做友元
/ 类做友元
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; // 维护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;
}

5 运算符(operator)重载(overloading)

  • 对已有的运算符重新进行定义,赋予另一种功能,以适应不同的数据类型
  • 两种重载方式(成员函数、全局函数)

5.1 加号运算符重载(operater+)

  • 作用:实现两个自定义数据类型相加的运算
  • operater+ 编译器提供的函数名
  • 运算符重载,也可以发生函数重载
// 1、成员函数重载+号
Person operator+(Person &p)
{
    Person temp; // temp临时
    temp.m_A = this->m_A + p.m_A;
    temp.m_B = this->m_B + p.m_B;
    return temp;
}
// 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;
}

5.2 左移运算符重载(operator<<)

  • ostream类,cout对象
  • 作用:可以输出自定义数据类型
  • 只能利用全局函数来重载左移运算符

在这里插入图片描述

// 左移运算符重载
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)
{
    cout << "m_A= " << p.m_A << " m_B=" << p.m_B; // 本质 operator<<(cout,p)简化cout << p
    return cout;                                  // cout也可以改名字,因为引用的本身是起别名
}
void test01()
{
    Person p(10, 10);

    cout << p << endl;
}

int main()
{
    test01();

    system("pause");
    return 0;
}

5.3 递增运算符重载(operator++)

  • 前置递增返回的是引用,后置递增返回的是值
    在这里插入图片描述
// 重载递增运算符
class MyInteger
{
    friend ostream &operator<<(ostream &cout, MyInteger myint);

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

    // 2.重载后置++运算符
    // 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;
}

5.4 赋值运算符重载(operator=)

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

  • 默认构造函数(无参,函数体为空)
  • 默认析构函数(无参,函数体为空)
  • 默认拷贝构造函数,对属性进行拷贝
    • (默认调用拷贝赋值运算符)赋值运算符operator=,对属性进行拷贝

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

// 赋值运算符重载
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; // 用指针和new将属性开辟到堆区
};
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;
}

5.5 关系运算符重载

  • 作用:重载关系运算符,可以让两个自定义类型对象进行对比操作。
// 重载==号
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;
}

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

  • 函数调用运算()也可以重载
  • 由于重载后使用的方式非常像函数的调用,因此称为仿函数
  • 注意:匿名函数对象 MyAdd()(100, 106);执行结束后立即被释放

两种调用方式

  • 对象名调用
MyPrint myPrint;
myPrint("helloWorld");
  • 类名调用
MyAdd()(100, 106)//结束后立即释放

在这里插入图片描述

// 函数调用运算符重载
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;

    // 匿名函数对象(不能创建对象时)执行结束后立即被释放
    cout << MyAdd()(100, 106) << endl;
}
int main()
{
    test01();
    test02();

    system("pause");
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/89581.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

产品经理 - 产品设计方法论业务落地部分_包括流程产品文档方法论需求设计方法论

整体 - 产品设计方法论思维导图 个人整理&#xff0c;存在异议大家可以讨论下 业务落地方法论 在进行了需求收集以及需求分析后&#xff0c;针对收集到的需求以及对应的分析结论后&#xff0c;需针对当前的需求点进行开发落地&#xff0c;核心即为两点&#xff0c;需求设计…

大学生HTML期末作业网页:使用DIV+CSS技术制作一个简单的小说网站 (3个页面 登录+注册+首页 )

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

C# DotNet基本类库

统一的编程API&#xff1a;NET Framework 一 任何事物都是对象(类型转换) 1 任何事物都是object类的子类 ① 一个函数如果需要object参数&#xff0c;则可以代入任何参数&#xff1b; ② 任何对象都有以下方法&#xff1a; ToString() Equals() GetType() MemberwiseClone…

git 命令使用,和gitlab新建分支

一、gitlab 新建分支&#xff0c;并上传代码 在公司&#xff0c;会有项目管理&#xff0c;创建好master创库&#xff0c;在gitlab&#xff0c;个人需要创建个人分支&#xff0c;用于提交代码&#xff0c;并申请&#xff0c;合并到分支上&#xff08;一般会有第一个原始项目&…

SpringCloud项目实例2--服务治理、nacos安装

服务注册和服务发现 服务注册 比如订单微服务的实例运行在IP地址为192.168.1.122的7010端口和IP地址为192.168.3.41的7020端口上&#xff0c;菜品微服务的实例运行在IP地址为192.168.1.102的9009端口、IP地址为192.168.1.102的9010端口和IP地址为192.168.3.101的9020端口上。…

Nacos架构与原理

Nacos 生态 Nacos 几乎支持所有主流语言&#xff0c; 其中 Java/Golang/Python 已经支持 Nacos 2.0 长链接协议&#xff0c; 能 最大限度发挥 Nacos 性能。 阿里微服务 DNS&#xff08;DubboNacosSpring-cloud-alibaba/Seata/ Sentinel&#xff09; 最佳实践&#xff0c; 是 Ja…

02. 数据库基础

02. 数据库基础 数据库基础&#xff08;上&#xff09; /01 数据库基本概念 数据库基本概念 数据库 数据库&#xff08;database&#xff09;就是一个由一批数据构成的有序集合&#xff0c;这个集合通常被保存为一个或多个彼此相关的文件。 用户可以对文件中的数据进行新…

为什么宝宝睡着后,妈妈离开后他也能知道?雷达定位都没这么准确

经常听到一些妈妈说&#xff0c;照顾孩子真的太难了&#xff0c;完全失去了“人身自由”宝宝好像24小时都要挂在身上&#xff0c;即使睡着了&#xff0c;只要妈妈走开就像报警&#xff0c;宝宝马上就能知道并很快醒来&#xff0c;只要妈妈靠近&#xff0c;马上就会平静下来&…

Qt-Web混合开发-QtWebChannel实现Qt与Web通信交互(4)

Qt-Web混合开发-QtWebChannel实现Qt与Web通信交互&#x1f34f; 文章目录Qt-Web混合开发-QtWebChannel实现Qt与Web通信交互&#x1f34f;1、概述&#x1f353;2、实现效果&#x1f345;3、实现功能&#x1f95d;4、关键代码&#x1f33d;5、源代码&#x1f346;更多精彩内容&am…

JavaScript基础(一)

1、初始JavaScript 1.1、JavaScript 是什么 JavaScript 是世界上最流行的语言之一&#xff0c;是一种运行在客户端的脚本语言 &#xff08;Script 是脚本的意思&#xff09; 脚本语言&#xff1a;不需要编译&#xff0c;运行过程中由 js 解释器( js 引擎&#xff09;逐行来进…

牛客网开源Redis+MySQL核心架构手册,无意掀起Github浪潮

这次小编带来了两套笔记分别是&#xff1a; “Redis 深度历险&#xff1a;核心原理与应用实践”“MySQL DBA工作笔记&#xff1a;“数据库管理、架构优化与运维开发” 先从Redis开始&#xff0c;咱们跟着文章来看下吧~ Redis可以用来做什么? Redis 是互联网技术领域使用最为…

fiddler工具使用大全(全网最详细)

目录 Fiddler基础知识 HTTP协议 Fiddler的使用 总结&#xff1a; 重点&#xff1a;配套学习资料和视频教学 Fiddler基础知识 Fiddler是强大的抓包工具&#xff0c;它的原理是以web代理服务器的形式进行工作的&#xff0c;使用的代理地址是&#xff1a;127.0.0.1&#xff0…

基于微信小程序的每日签到打卡-计算机毕业设计

项目介绍 社会的发展和科学技术的进步&#xff0c;互联网技术越来越受欢迎。手机也逐渐受到广大人民群众的喜爱&#xff0c;也逐渐进入了每个用户的使用。手机具有便利性&#xff0c;速度快&#xff0c;效率高&#xff0c;成本低等优点。 因此&#xff0c;构建符合自己要求的操…

SpringSecurity[1]-SpringSecurity简介以及创建Spring Security第一个项目

主要内容 Spring Security 简介 第一个Spring Security项目 UserDetailsService详解 PasswordEncoder密码解析器详解 自定义登录逻辑 自定义登录页面 认证过程其他常用配置 访问控制url匹配 内置访问控制方法介绍 10.角色权限判断 11.自定义403处理方案 12.基于表达…

基于VUE框架的二手书交易网站

开发工具(eclipse/idea/vscode等)&#xff1a;idea 数据库(sqlite/mysql/sqlserver等)&#xff1a;mysql 功能模块(请用文字描述&#xff0c;至少200字)&#xff1a;用户功能如下&#xff1a; &#xff08;1&#xff09;二手书搜索&#xff1a;用户在输入框中输入二手书的名称等…

[附源码]Node.js计算机毕业设计翻转课堂微视频管理系统Express

5.1系统功能模块 翻转课堂微视频管理系统&#xff0c;在系统首页可以查看首页、课程信息、通知公告、微视频、试卷列表、翻转资讯、留言反馈、个人中心、后台管理等内容&#xff0c;如图5-1所示。 图5-1系统首页界面图 课程信息&#xff0c;在课程信息页面可以查看课程编号、课…

第二十一章 Prim算法与Kruskal算法(通俗证明与详细讲解)

第二十一章 Prim算法与Kruskal算法一、最小生成树二、prim算法1、算法思路2、算法模板&#xff08;1&#xff09;问题&#xff08;2&#xff09;模板&#xff08;3&#xff09;分析4、常见疑惑&#xff08;1&#xff09;与dijkstra算法的区别以及循环次数问题&#xff1a;&…

ASEMI肖特基二极管MBR20200FCT特征,MBR20200FCT应用

编辑-Z ASEMI肖特基二极管MBR20200FCT参数&#xff1a; 型号&#xff1a;MBR20200FCT 最大重复峰值反向电压&#xff08;VRRM&#xff09;&#xff1a;200V 最大RMS电桥输入电压&#xff08;VRMS&#xff09;&#xff1a;140V 最大直流阻断电压&#xff08;VDC&#xff09…

Visual Studio配置c环境

Visual Studio配置c环境 Visual Studio配置c环境 1 下载Visual Studio 下载Visual Studio软件可以直接在其内进行c的运行&#xff0c;不需要配置。官网&#xff0c;其中社区版免费。 2 安装Visual Studio 2.1 VS把我们想使用到的开发语言和应用都已经归类好&#xff0c;我们…

Python学习基础笔记四十九——类的命名空间

1、创建一个类就创建了一个类的名称空间&#xff0c;用来存储类中定义的所有名字&#xff0c;这些名字称为类的属性。而类中可以定义两种属性&#xff1a; 静态属性&#xff1a;就是直接在类中定义的变量。 动态属性&#xff1a;就是定义在类中的方法。 class Course:langua…