C++类和对象3

简介: C++类和对象

C++类和对象2:https://developer.aliyun.com/article/1548157

五、运算符重载

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

1、加号运算符重载

#include <iostream>
#include <string>
 
using namespace std;
 
class Person {
public:
    int m_A;
    int m_B;
//成员函数加号重载
    Person operator+(const Person &person) {
        Person tem;
        tem.m_A = this->m_A + person.m_A;
        tem.m_B = this->m_B + person.m_B;
        return tem;
    };
};
//成员函数加号重载
Person operator+(const Person &p1,const Person &p2) {
    Person tem;
    tem.m_A = p1.m_A + p2.m_A;
    tem.m_B = p1.m_B + p2.m_B;
    return tem;
};
void test01() {
    Person p1;
    p1.m_A = 10;
    p1.m_B = 20;
    Person p2;
    p2.m_A = 10;
    p2.m_B = 20;
    Person p3 = p1 + p2;
    cout << p3.m_A << endl;
    cout << p3.m_B << endl;
}
 
int main() {
    test01();
    return 0;
}
 
 


20
40

2、左移运算符重载

重载左移运算符配合友元可以实现输出自定义数据类型

#include <iostream>
#include <string>
 
using namespace std;
 
//左移运算符重载
class Person {
    friend ostream &operator<<(ostream &cout, Person &p);
 
public:
    Person(int a, int b) : m_A(a), m_B(b) {}
 
private:
    int m_A;
    int m_B;
};
 
//只能利用全局函数重载左移运算符 成员函数无法实现cout在左侧
ostream &operator<<(ostream &cout, Person &p) {
    cout << "m_A=" << p.m_A << " m_B=" << p.m_B;
    return cout;
}
 
void test01() {
    Person p(10, 10);
    cout << p << " test" << endl;
}
 
int main() {
    test01();
    return 0;
}
 
 
m_A=10 m_B=10 test

3、递增运算符重载

前置递增返回的是引用,后置递增返回的值。

#include <iostream>
#include <string>
 
using namespace std;
 
//自增运算符重载
class MyInteger {
    friend ostream &operator<<(ostream &cout, const MyInteger &myInteger);
 
public:
    MyInteger() {
        m_Num = 0;
    }
 
    //重载前置++运算符
    MyInteger &operator++() {
        // 先进行++运算
        m_Num++;
        //再将自身做返回
        return *this;
    }
 
    //重载后置++运算符
    MyInteger operator++(const int) {
        //        先记录当时的结果
        MyInteger tem = *this;
        // 后递增
        m_Num++;
        //最后将记录结果做返回
        return tem;
    }
 
private:
    int m_Num;
};
 
ostream &operator<<(ostream &cout, const MyInteger &myInteger) {
    cout << myInteger.m_Num << " ";
    return cout;
}
 
void test01() {
    int a = 10;
    cout << ++a << endl;
    cout << a << endl;
    int b = 10;
    cout << b++ << endl;
    cout << b << endl;
}
 
void test02() {
    MyInteger myInt;
    cout << ++(++myInt) << endl;
    cout << myInt << endl;
}
 
void test03() {
    MyInteger myInt;
    cout << myInt++ << endl;
    cout << myInt << endl;
}
 
int main() {
//    test01();
    test02();
    test03();
    return 0;
}
 
 
2
2
0
1

4、赋值运算符重载

#include <iostream>
#include <string>
 
using namespace std;
 
//赋值运算符重载
class Person {
public:
    Person(int age) {
        m_Age = new int(age);
    }
 
    ~Person() {
        if (m_Age != NULL) {
            delete m_Age;
            m_Age = NULL;
        }
    }
 
//    重载赋值运算符
    Person &operator=(Person &p) {
//        先判断是否有属性在堆区,如果有先释放,然后深拷贝
        if (m_Age != NULL) {
            delete m_Age;
            m_Age = NULL;
        }
        m_Age = new int(*p.m_Age);
        return *this;
    }
 
    int *m_Age;
};
 
void test01() {
    Person p1(18);
    Person p2(20);
    Person p3(30);
    p3=p2 = p1;
    cout << *p1.m_Age << endl;
    cout << *p2.m_Age << endl;
    cout << *p3.m_Age << endl;
}
 
int main() {
    test01();
    return 0;
}
 
 


18
18
18

5、关系运算符重载

作用: 重载关系运算符,可以让两个自定义类型对象进行对比操作

#include <iostream>
#include <string>
 
using namespace std;
 
//作用: 重载关系运算符,可以让两个自定义类型对象进行对比操作
class Person {
public:
    Person(string name, int age) {
        name = m_Name;
        age = m_Age;
    }
 
    bool operator==(const Person &p) {
        if (this->m_Name == p.m_Name&&this->m_Age==p.m_Age) {
            return true;
        } else {
            return false;
        }
    }
    bool operator!=(const Person &p) {
        return !operator==(p);
    }
 
    string m_Name;
    int m_Age;
};
 
void test01() {
    Person p1("zhang", 15);
    Person p2("zhang", 16);
    cout << (p1 == p2) << endl;
}
 
int main() {
    test01();
    return 0;
}
 
 
0

6、函数调用运算符重载

函数调用运算符()也可以重载

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

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

#include <iostream>
#include <string>
 
using namespace std;
 
//函数调用运算符()也可以重载
//由于重载后使用的方式非常像函数的调用,因此称为仿函数
//仿函数没有固定写法,非常灵活
class MyPrint {
public:
    void operator()(string text) {
        cout << text << endl;
    }
};
 
void test01() {
//    重载()操作符,也称为仿函数
    MyPrint myPrint;
    myPrint("hello world");
//    匿名函数
    MyPrint()("hello world");
}
 
int main() {
    test01();
    return 0;
}
 
 
hello world
hello world

六、继承

1、继承的基本语法

继承好处:减少重复代码

语法:class 子类:继承方式 父类

子类也称为派生类

父类也称为基类

#include <iostream>
#include <string>
 
using namespace std;
/*
 * 继承好处:减少重复代码
 * 语法:class 子类:继承方式 父类
 * 子类也称为派生类
 * 父类也称为基类
 */
//普通页面实现
class BasePage {
public:
    void header() {
        cout << "首页、公开课、登录、注册……(公共头部)" << endl;
    }
 
    void footer() {
        cout << "帮助中心、交流合作、站内地图……(公共底部)" << endl;
    }
 
    void left() {
        cout << "Java、Python、C++……(公共分类列表)" << endl;
    }
};
 
//java页面
class Java : public BasePage {
public:
    void content() {
        cout << "Java学科视频" << endl;
    }
};
 
//python页面
class Python : public BasePage {
public:
    void content() {
        cout << "Python学科视频" << endl;
    }
};
 
void test01() {
    cout << "Java下载视频" << endl;
    Java ja;
    ja.header();
    ja.footer();
    ja.left();
    ja.content();
    cout<<"------------------"<<endl;
    cout << "Python下载视频" << endl;
    Python py;
    py.header();
    py.footer();
    py.left();
    py.content();
}
 
int main() {
    test01();
    return 0;
}
 
 


Java下载视频
首页、公开课、登录、注册……(公共头部)
帮助中心、交流合作、站内地图……(公共底部)
Java、Python、C++……(公共分类列表)
Java学科视频
------------------
Python下载视频
首页、公开课、登录、注册……(公共头部)
帮助中心、交流合作、站内地图……(公共底部)
Java、Python、C++……(公共分类列表)
Python学科视频

2、继承方式

公共继承:父类的私有属性不可访问,其他属性权限和父类保持一致;

保护继承:父类的私有属性不可访问,父类public权限会降级为protected;

私有继承:父类的私有属性不可访问,父类public、protected会降级为private;

#include <iostream>
#include <string>
 
using namespace std;
 
//公共继承基类
class Base1 {
public:
    int m_A;
protected:
    int m_B;
private:
    int m_C;
};
//公共继承
class Son1:public Base1{
public:
    void func(){
        cout<<m_A<<endl;
        cout<<m_B<<endl;
    }
};
//保护继承
class Son2:protected Base1{
    void func(){
        cout<<m_A<<endl;
        cout<<m_B<<endl;
    }
};
//私有继承
class Son3:private Base1{
    void func(){
        cout<<m_A<<endl;
        cout<<m_B<<endl;
    }
};
 
void test01(){
    Son1 s1;
    s1.m_A=100;
//    s1.m_B=100;
}
int main() {
 
    return 0;
}
 
 

3、继承中的对象模型

#include <iostream>
#include <string>
 
using namespace std;
 
//作用: 重载关系运算符,可以让两个自定义类型对象进行对比操作
class Base {
public:
    int m_A;
protected:
    int m_B;
private:
    int m_C;//私有成员只是被隐藏,但是还是会被继承下去
};
 
//公共继承
class Son : public Base {
public:
    int m_D;
};
 
void test01() {
    //16
    //父类中所有非静态成员属性都会被子类继承下去
    //父类中私有成员属性是被编译器给隐藏了,因此访问不到,但是确实被继承下去了
    cout << "size of Son = " << sizeof(Son) << endl;
}
 
int main() {
    test01();
    return 0;
}
 
  
size of Son = 16

使用*Visual Studio 2022 Developer Command Prompt v17.5.5工具,查看对象模型;

#进入代码盘符
D:\ProgramFiles\Microsoft Visual Studio\2022\Community>f:
#进入代码目录
 
F:\>cd F:\myCode\c++\0418_01
#查看代码类的结构  cl /d1 reportSingleClassLayout类名 文件名
 
F:\myCode\c++\0418_01>cl /d1 reportSingleClassLayoutSon main.cpp

4、继承中构造与析构顺序

先构造父类,再构造子类,析构的顺序与构造的顺序相反

#include <iostream>
#include <string>
 
using namespace std;
 
//继承中的构造与析构顺序
class Base {
public:
    Base() {
        cout << "Base构造执行" << endl;
    }
 
    ~Base() {
        cout << "Base析构执行" << endl;
    }
};
 
class Son : public Base {
public:
    Son() {
        cout << "Son构造执行" << endl;
    }
 
    ~Son() {
        cout << "Son析构执行" << endl;
    }
};
 
void test01() {
    Son s;
}
 
//继承中的构造和析构顺序如下:
//先构造父类,再构造子类,析构的顺序与构造的顺序相反
int main() {
    test01();
    system("pause");
    return 0;
}
Base构造执行
Son构造执行
Son析构执行
Base析构执行

5、继承同名成员处理方式

访问子类同名成员 直接访问即可

访问父类同名成员 需要加作用域

#include <iostream>
#include <string>
 
using namespace std;
 
//访问子类同名成员 直接访问即可
//访问父类同名成员 需要加作用域
class Base {
public:
    Base() {
        m_A = 100;
    }
 
    void func() {
        cout << "Base func()" << endl;
    }
 
    void func(int a) {
        cout << "Base func(int a)" << endl;
    }
 
    int m_A;
};
 
class Son : public Base {
public:
    Son() {
        m_A = 200;
    }
 
    void func() {
        cout << "Son func()" << endl;
    }
 
    int m_A;
};
 
//同名成员属性
void test01() {
    Son son;
    cout << son.m_A << endl;
//    如果通过子类对象访问父类中的同名成员,需要加作用域
    cout << son.Base::m_A << endl;
};
 
//同名成员函数
void test02() {
    Son son;
    son.func();
    son.Base::func();
//    如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏父类中所有同名成员函数,如果想访问,需要加作用域
    son.Base::func(10);
};
 
int main() {
    test01();
    test02();
    return 0;
}
 
 


200
100
Son func()
Base func()

6、继承同名静态成员处理方式

与同名成员处理方式一致。可通过类名访问。

7、多继承语法

#include <iostream>
#include <string>
 
using namespace std;
 
//多继承
class Base1 {
};
 
class Base2 {
};
 
class Son : public Base1, protected Base2 {
 
};
 
int main() {
 
    return 0;
}
 
 

8、菱形继承

两个派生类继承同一个基类

又有某个类同时继承者两个派生类

这种继承被称为菱形继承,或者钻石继承

#include <iostream>
#include <string>
 
using namespace std;
 
//动物类
class Animail {
public:
    int m_Age;
};
 
//利用虚继承 解决菱形继承的问题
//继承之前加上 virtual变成虚继承 Animail类称为虚基类
//羊类
class Sheep : virtual public Animail {
};
 
//驼类
class Tuo : virtual public Animail {
};
 
//羊驼类
class SheepTuo : public Sheep, public Tuo {
};
 
void test01() {
    SheepTuo st;
    st.Sheep::m_Age=18;
    st.Tuo::m_Age=28;
    cout<<st.m_Age<<endl;
    cout << sizeof(st) << endl;
}
 
int main() {
    test01();
    return 0;
}
 
 
28
24

七、多态

静态多态:函数重载和 运算符重载属于静态多态,复用函数名;

动态多态: 派生类和虚函数实现运行时多态;

静态多态的函数地址早绑定 - 编译阶段确定函数地址;

动态多态的函数地址晚绑定 - 运行阶段确定函数地址;

#include <iostream>
#include <string>
 
using namespace std;
 
//多态
 
//动物类
class Animal {
public:
    //虚函数
    virtual void speak() {
        cout << "Animal speak" << endl;
    }
};
 
class Cat : public Animal {
public:
    void speak() {
        cout << "cat apeak" << endl;
    }
};
 
//执行说话的函数
//地址早绑定,在编译阶段确定函数地址
//如果想让猫说话,那么这个函数地址不能提前绑定,需要在运行阶段绑定,地址晚绑定
//动态多态满足条件:1、有继承关系;2、子类重写父类的虚函数。动态多态的使用:父类的指针或者引用,指向子类对象。
void doSpeak(Animal &animal) {
    animal.speak();
}
 
void test01() {
    Cat cat;
    doSpeak(cat);
}
 
int main() {
    test01();
    return 0;
}
 
 

代码组织结构清晰

可读性强

利于前期和后期的扩展以及维护

#include <iostream>
#include <string>
 
using namespace std;
 
//计算器普通写法
class Calculator {
public:
    int getResult(string oper) {
        if (oper == "+") {
            return m_NUm1 + m_Num2;
        } else if (oper == "-") {
            return m_NUm1 - m_Num2;
        } else if (oper == "/") {
            return m_NUm1 / m_Num2;
        } else if (oper == "*") {
            return m_Num2 * m_NUm1;
        }
 
    }
 
    int m_NUm1;
    int m_Num2;
};
 
//实现计算器抽象类
class AbstractCalculator {
public:
    virtual int getResult() {
        return 0;
    }
 
    int m_Num1;
    int m_Num2;
};
 
//加法计算器
class AddCalculator : public AbstractCalculator {
public:
    int getResult() {
        return m_Num1 + m_Num2;
    }
};
 
//减法计算器
class SubCalculator : public AbstractCalculator {
public:
    int getResult() {
        return m_Num1 - m_Num2;
    }
};
 
void test01() {
    Calculator cal;
    cal.m_NUm1 = 10;
    cal.m_Num2 = 20;
    cout << cal.getResult("+") << endl;
    cout << cal.getResult("-") << endl;
}
 
void test02() {
    AbstractCalculator *abc = new AddCalculator();
    abc->m_Num1 = 10;
    abc->m_Num2 = 20;
    cout << abc->getResult() << endl;
    delete abc;
}
 
int main() {
//    test01();
    test02();
    return 0;
}
 
 

类中有纯虚函数,类称为抽象类;

抽象类无法实例化对象,子类必须重写抽象类的纯虚函数,否则也称为抽象类;

#include <iostream>
#include <string>
 
using namespace std;
 
//类中有纯虚函数,类称为抽象类
//抽象类无法实例化对象,子类必须重写抽象类的纯虚函数,否则也称为抽象类
class Base {
public:
    virtual void func() = 0;
};
 
class Son : public Base {
public:
    void func() {
        cout << "son func" << endl;
    }
};
 
void test01() {
    Base *base = new Son();
    base->func();
    delete base;
}
 
int main() {
    test01();
    return 0;
}
 
 

多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用子类的析构代码

解决方式:将父类中的析构函数改为虚析构或者纯虚析构

虚析构和纯虚析构共性:都可以解决父类指针释放子类对象,都需要有具体的函数实现;区别:纯虚析构的类输入抽象类,无法实例化对象。

#include <iostream>
#include <string>
 
using namespace std;
 
//多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用子类的析构代码
//解决方式:将父类中的析构函数改为虚析构或者纯虚析构
//虚析构和纯虚析构共性:都可以解决父类指针释放子类对象,都需要有具体的函数实现;区别:纯虚析构的类输入抽象类,无法实例化对象。
 
//虚析构和纯虚析构
class Animal {
public:
    Animal() {
        cout << "Animal 构造函数" << endl;
    }
 
//利用虚析构可以解决,父类指针释放子类对象时不干净的问题。
//  virtual  ~Animal() {
//        cout << "Animal 析构函数" << endl;
//    }
//纯虚析构,需要申明和实现
    virtual  ~Animal() = 0;
 
    //    纯虚函数
    virtual void speak() = 0;
};
 
Animal::~Animal() {
    cout << "Animal 纯虚析构函数" << endl;
}
 
class Cat : public Animal {
public:
    Cat(string name) {
        cout << "Cat 构造函数" << endl;
        m_Name = new string(name);
    }
 
    virtual void speak() {
        cout << *m_Name << "小猫在说话" << endl;
    }
 
    ~Cat() {
        if (m_Name != NULL) {
            cout << "Cat 析构函数" << endl;
            delete m_Name;
            m_Name = NULL;
        }
    }
 
    string *m_Name;
};
 
void test01() {
    Animal *animal = new Cat("tom ");
    animal->speak();
    delete animal;
}
 
int main() {
    test01();
    return 0;
}
 
 

Animal 构造函数
Cat 构造函数
tom 小猫在说话
Cat 析构函数
Animal 纯虚析构函数
目录
相关文章
|
1天前
|
C++
【C++】日期类Date(详解)②
- `-=`通过复用`+=`实现,`Date operator-(int day)`则通过创建副本并调用`-=`。 - 前置`++`和后置`++`同样使用重载,类似地,前置`--`和后置`--`也复用了`+=`和`-=1`。 - 比较运算符重载如`&gt;`, `==`, `&lt;`, `&lt;=`, `!=`,通常只需实现两个,其他可通过复合逻辑得出。 - `Date`减`Date`返回天数,通过迭代较小日期直到与较大日期相等,记录步数和符号。 ``` 这是236个字符的摘要,符合240字符以内的要求,涵盖了日期类中运算符重载的主要实现。
|
1天前
|
存储 编译器 C++
【C++】类和对象④(再谈构造函数:初始化列表,隐式类型转换,缺省值
C++中的隐式类型转换在变量赋值和函数调用中常见,如`double`转`int`。取引用时,须用`const`以防修改临时变量,如`const int& b = a;`。类可以有隐式单参构造,使`A aa2 = 1;`合法,但`explicit`关键字可阻止这种转换。C++11起,成员变量可设默认值,如`int _b1 = 1;`。博客探讨构造函数、初始化列表及编译器优化,关注更多C++特性。
|
1天前
|
编译器 C++
【C++】类和对象④(类的默认成员函数:取地址及const取地址重载 )
本文探讨了C++中类的成员函数,特别是取地址及const取地址操作符重载,通常无需重载,但展示了如何自定义以适应特定需求。接着讨论了构造函数的重要性,尤其是使用初始化列表来高效地初始化类的成员,包括对象成员、引用和const成员。初始化列表确保在对象创建时正确赋值,并遵循特定的执行顺序。
|
1天前
|
C语言 C++
【C++】日期类Date(详解)③
该文介绍了C++中直接相减法计算两个日期之间差值的方法,包括确定max和min、按年计算天数、日期矫正及计算差值。同时,文章讲解了const成员函数,用于不修改类成员的函数,并给出了`GetMonthDay`和`CheckDate`的const版本。此外,讨论了流插入和流提取的重载,需在类外部定义以符合内置类型输入输出习惯,并介绍了友元机制,允许非成员函数访问类的私有成员。全文旨在深化对运算符重载、const成员和流操作的理解。
|
1天前
|
定位技术 C语言 C++
C++】日期类Date(详解)①
这篇教程讲解了如何使用C++实现一个日期类`Date`,涵盖操作符重载、拷贝构造、赋值运算符及友元函数。类包含年、月、日私有成员,提供合法性检查、获取某月天数、日期加减运算、比较运算符等功能。示例代码包括`GetMonthDay`、`CheckDate`、构造函数、拷贝构造函数、赋值运算符和相关运算符重载的实现。
|
1天前
|
编译器 C++
【C++】类和对象③(类的默认成员函数:赋值运算符重载)
在C++中,运算符重载允许为用户定义的类型扩展运算符功能,但不能创建新运算符如`operator@`。重载的运算符必须至少有一个类类型参数,且不能改变内置类型运算符的含义。`.*::sizeof?`不可重载。赋值运算符`=`通常作为成员函数重载,确保封装性,如`Date`类的`operator==`。赋值运算符应返回引用并检查自我赋值。当未显式重载时,编译器提供默认实现,但这可能不足以处理资源管理。拷贝构造和赋值运算符在对象复制中有不同用途,需根据类需求定制实现。正确实现它们对避免数据错误和内存问题至关重要。接下来将探讨更多操作符重载和默认成员函数。
|
1天前
|
存储 编译器 C++
【C++】类和对象③(类的默认成员函数:拷贝构造函数)
本文探讨了C++中拷贝构造函数和赋值运算符重载的重要性。拷贝构造函数用于创建与已有对象相同的新对象,尤其在类涉及资源管理时需谨慎处理,以防止浅拷贝导致的问题。默认拷贝构造函数进行字节级复制,可能导致资源重复释放。例子展示了未正确实现拷贝构造函数时可能导致的无限递归。此外,文章提到了拷贝构造函数的常见应用场景,如函数参数、返回值和对象初始化,并指出类对象在赋值或作为函数参数时会隐式调用拷贝构造。
|
1天前
|
存储 编译器 C语言
【C++】类和对象②(类的默认成员函数:构造函数 | 析构函数)
C++类的六大默认成员函数包括构造函数、析构函数、拷贝构造、赋值运算符、取地址重载及const取址。构造函数用于对象初始化,无返回值,名称与类名相同,可重载。若未定义,编译器提供默认无参构造。析构函数负责对象销毁,名字前加`~`,无参数无返回,自动调用以释放资源。一个类只有一个析构函数。两者确保对象生命周期中正确初始化和清理。
|
4天前
|
存储 编译器 C语言
【C++航海王:追寻罗杰的编程之路】类与对象你学会了吗?(上)
【C++航海王:追寻罗杰的编程之路】类与对象你学会了吗?(上)
9 2
|
3天前
|
C++
C++职工管理系统(类继承、文件、指针操作、中文乱码解决)
C++职工管理系统(类继承、文件、指针操作、中文乱码解决)
7 0
C++职工管理系统(类继承、文件、指针操作、中文乱码解决)