【C++】五一假期,我学会了C++类和对象 ( 看完这篇你也能学会 ~ )
温馨提示:这篇文章已超过396天没有更新,请注意相关的内容是否还可用!
-
🧑🎓个人主页:简 料
-
🏆所属专栏:C++
-
🏆个人社区:越努力越幸运社区
-
🏆简 介:简料简料,简单有料~在校大学生一枚,专注C/C++/GO的干货分享,立志成为您的好帮手 ~
C/C++学习路线 (点击解锁) ❤️C语言 ❤️初阶数据结构与算法 ❤️C++ ❤️高阶数据结构 ❤️Linux系统编程与网络编程 文章目录
- ☑️前言
- ✅类和对象【🏆上篇🏆】
- 一. 面向过程和面向对象初步认识
- 二. 与类的完美邂逅❤️
- 1. 类的引入
- 2. 类的定义
- 三. 增进与类的距离❤️
- 1. 类的访问限定符及封装
- 1.1. 访问限定符
- 1.2. 封装【⭐重大思想⭐】
- 2. 类的作用域
- 3. 类的实例化
- 四. 类对象模型
- 1. 如何计算类对象的大小
- 2. 类对象的存储方式猜测
- 3. 结构体内存对齐规则【⭐面试问题⭐】
- 五. 类成员函数的this指针
- 1. this指针的引出
- 2. this指针的特性
- ✅类和对象【🏆中篇🏆】
- 一. 类的6个默认成员函数【⭐重点掌握⭐】
- 1. 构造函数🌀
- 1.1. 概念
- 1.2. 特性
- 2. 析构函数🌀
- 2.1. 概念
- 2.2. 特性
- 3. 拷贝构造函数🌀
- 3.1. 概念
- 3.2. 特性
- 4. 【运算符重载】(日期类为例) 🌀
- 4.1. 运算符重载【⭐他在这⭐】
- 4.2. 前置++ 和 后置++ 重载
- 4.2. 赋值运算符重载【⭐这是默认的⭐】
- 5. 取地址及const取地址操作符重载🌀
- 二. const成员函数
- 三. 日期类的实现
- ✅类和对象【🏆下篇🏆】
- 一. 再谈构造函数
- 1. 构造函数体赋值
- 2. 初始化列表【⭐重点知识⭐】
- 3. explicit关键字
- 二. 锦上添花❀
- 1. static成员【⭐要注意⭐】
- 2. 友元
- 2.1. 友元函数
- 2.2. 友元类
- 3. 内部类
- 4. 匿名对象
- 三. 拷贝对象时编译器的一些优化【⭐留个心眼⭐】
- 四. 再次理解封装
- ☑️写在最后
☑️前言
🌀本章给大家带来的是C++类和对象!在面向对象编程中,类是用来描述某个类别对象通用属性和行为的模板,它是一种抽象的数据类型,包括成员变量和成员函数。而对象则是类的实例,是具体存在的数据,拥有自己的状态和行为。当我们创建一个对象时,就是根据类的定义来分配内存,创建对象实例,并初始化对象的成员变量以及相关状态。
🌀类具有封装、继承和多态等特性。封装性将数据和操作数据的行为封装在一个类的内部,并且只向外部提供必要的接口,以保证数据的安全性和一致性。继承机制允许我们从已有的类中派生出新的类,新的类可以沿用基类的数据和行为,也可以自定义扩展新的数据和行为。多态是指同一函数或方法在不同情况下表现出不同的行为,可以通过虚函数和接口来实现。(本章只涉及封装这一特性,继承和多态的讲解在后续文章会推出)
🌀类和对象这一节的知识点,贯穿了整个C++的学习,深刻理解类和对象和熟练使用类和对象是后面C++学习的坚实的基础。
-
本篇文章篇幅较长,建议通过目录精准定位阅读,或者使用电脑阅读体验更好噢~
-
接下来就进入【C++】类和对象的学习吧~
✅类和对象【🏆上篇🏆】
一. 面向过程和面向对象初步认识
-
面向过程和面向对象是两种不同的编程思想。
-
面向过程是一种以任务为中心的编程思想,它将问题分解为一系列的任务,并通过函数的方式来解决。在面向过程编程中,函数是主要的组织单元,其目的是尽可能地利用计算机的硬件资源,强调对计算机的直接控制,而不是对问题的分析和抽象。我们熟知的C语言就是面向过程的~
-
面向对象是以对象为中心的编程思想,它将问题分解为一系列相互作用的对象,并通过类的方式来描述。在面向对象编程中,类是主要的组织单元,其目的是将问题抽象成一组相互协作的对象,强调对问题的分析和抽象,而不是对计算机的直接控制。
-
面向过程和面向对象都是编程的思想,它们各有优缺点,适用于不同的场景。面向过程适用于解决简单的问题,有较高的效率,但不太适合大型复杂系统的开发。而面向对象适用于解决复杂的问题,具有非常好的可扩展性和可维护性,但它的效率可能不如面向过程。
-
在C++中,既可以面向过程编程,也可以面向对象编程,这取决于我们的编程需求和风格。
我们拿洗衣服这件事来看,面向过程和面向对象是怎样的呢?
- 面向过程:
- 面向对象:
总共有四个对象:人,衣服,洗衣粉,洗衣机。
整个洗衣服的过程:人将衣服放进洗衣机,倒入洗衣粉,启动洗衣机,洗衣机就会完成洗衣过程并且甩干。
整个过程主要是:**人,衣服,洗衣粉,洗衣机四个对象之间交互完成的,人不需要关心洗衣机具体是如何洗衣服,是如何甩干的。
面向对象思想还可以以送外卖这件事来说明【体会其中的思想】~
- 以外卖小哥和客户为例,我们可以使用面向对象的思想来描述他们之间的关系。
- 首先,我们可以定义一个外卖小哥类和一个客户类,用于描述他们的属性和方法。外卖小哥类中可以包含姓名、电话、所属餐厅等属性,以及送货、查询订单等方法;客户类中可以包含姓名、电话、收货地址等属性,以及下单、查询订单等方法。
- 然后,我们可以在程序中创建多个外卖小哥和客户对象,每个对象对应一个具体的外卖小哥或客户。此外,我们还可以使用继承、接口等机制来进一步封装和抽象类的功能,使程序更加简洁和易于扩展。
- 最后,我们可以通过对象之间的交互来模拟外卖小哥和客户之间的服务过程。例如,客户可以通过调用外卖小哥的送货方法来让外卖小哥送餐,外卖小哥可以通过调用客户的支付方法来完成订单的支付等。
- 通过使用面向对象的思想,我们可以更好地描述和设计对象之间的交互,提高程序的可读性和可维护性,使程序更加优雅和易于扩展。
二. 与类的完美邂逅❤️
1. 类的引入
C语言结构体中只能定义变量,在C++中,结构体内不仅可以定义变量,也可以定义函数(结构体被升级成了类)。
比如:之前在数据结构初阶中,用C语言方式实现的栈,结构体中只能定义变量;现在以C++方式实现,会发现struct中也可以定义函数。
如下:
typedef int DataType; struct Stack { void Init(size_t capacity) { _array = (DataType*)malloc(sizeof(DataType) * capacity); if (nullptr == _array) { perror("malloc申请空间失败"); return; } _capacity = capacity; _size = 0; } void Push(const DataType& data) { // 扩容 _array[_size] = data; ++_size; } DataType Top() { return _array[_size - 1]; } void Destroy() { if (_array) { free(_array); _array = nullptr; _capacity = 0; _size = 0; } } DataType* _array; size_t _capacity; size_t _size; }; int main() { Stack s; s.Init(10); s.Push(1); s.Push(2); s.Push(3); cout // 类体:由成员函数和成员变量组成 // 属性,方法,数据,行为 }; // 一定要注意后面的分号 public: // 这里先不用理解 // 展示基本信息 void showInfo() { cout public: void Init(int year) { // 这里的year到底是成员变量,还是函数形参? year = year; } private: int year; }; // 所以一般都建议这样 class Date { public: void Init(int year) { _year = year; } private: int _year; }; // 或者这样 class Date { public: void Init(int year) { mYear = year; } private: int mYear; }; // 其他方式也可以的,主要看公司要求。一般都是加个前缀或者后缀标识区分就行。 // 默认是public,可以直接访问 string name; int age; }; class Animal { // 默认是private,只有类内部成员才能访问 string type; int weight; }; public: void PrintPersonInfo(); private: char _name[20]; char _gender[3]; int _age; }; // 这里需要指定PrintPersonInfo是属于Person这个类域 void Person::PrintPersonInfo() { cout public: void PrintA() { cout public: void f1(){} private: int _a; }; // 类中仅有成员函数 class A2 { public: void f2() {} }; // 类中什么都没有---空类 class A3 {}; int main() { cout public: void Init(int year, int month, int day) { _year = year; _month = month; _day = day; } void Print() { cout Date d1, d2; d1.Init(2022,1,11); d2.Init(2022, 1, 12); d1.Print(); d2.Print(); return 0; } // 在里面写 this 是可以的 cout public: void Print() { cout A* p = nullptr; p-Print(); return 0; } // 正常运行,Print 函数内没有对 this 指针进行使用。 // 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行 class A { public: void PrintA() { cout A* p = nullptr; p-PrintA(); return 0; } // 运行崩溃,Print 里对 this 解引用,也就对 nullptr 解引用 }; public: void Init(int year, int month, int day) { _year = year; _month = month; _day = day; } void Print() { cout Date d1; d1.Init(2022, 7, 5); d1.Print(); Date d2; d2.Init(2022, 7, 6); d2.Print(); return 0; } public: // 1.无参构造函数 Date() {} // 可重载 // 2.带参构造函数 Date(int year, int month, int day) { _year = year; _month = month; _day = day; } private: int _year; int _month; int _day; }; void TestDate() { Date d1; // 调用无参构造函数 Date d2(2023, 5, 10); // 调用带参的构造函数 // 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明 // 以下代码的函数:声明了d3函数,该函数无参,返回一个日期类型的对象 // warning C4930: “Date d3(void)”: 未调用原型函数(是否是有意用变量定义的?) // Date d3(); } public: /* 如果用户显式定义了构造函数,编译器将不再生成 Date(int year, int month, int day) { _year = year; _month = month; _day = day; } */ void Print() { cout // 将Date类中构造函数屏蔽后,代码可以通过编译,因为编译器生成了一个无参的默认构造函数 // 将Date类中构造函数放开,代码编译失败,因为一旦显式定义任何构造函数,编译器将不再生成 // 因为d1调用的是无参构造,而类里面只有有参构造,显示有了,编译器将不会自动生成无参的 // 无参构造函数,放开后报错:error C2512: “Date”: 没有合适的默认构造函数可用 Date d1; return 0; } public: // Time 的默认构造 Time() { cout private: // 基本类型(内置类型) int _year; int _month; int _day; // 自定义类型 Time _t; }; int main() { Date d; return 0; } public: Time() { cout private: // 基本类型(内置类型) int _year = 2023; int _month = 5; int _day = 10; // 自定义类型 Time _t; }; int main() { Date d; return 0; } public: ~Time() { cout private: // 基本类型(内置类型) int _year = 1970; int _month = 1; int _day = 1; // 自定义类型 Time _t; }; int main() { Date d; return 0; } // 程序运行结束后输出:~Time() // 在main方法中根本没有直接创建Time类的对象,为什么最后会调用Time类的析构函数? // 因为:main方法中创建了Date对象d,而d中包含4个成员变量,其中_year, _month, _day三个是内置类 //型成员,销毁时不需要资源清理,最后系统直接将其内存回收即可;而_t是Time类对象,所以在d销毁时,要将 //其内部包含的Time类的_t对象销毁,所以要调用Time类的析构函数。但是:main函数中不能直接调用Time类 //的析构函数,实际要释放的是Date类对象,所以编译器会调用Date类的析构函数,而Date没有显式提供,则编 //译器会给Date类生成一个默认的析构函数,目的是在其内部调用Time类的析构函数,即当Date对象销毁时,要 //保证其内部每个自定义对象都可以正确销毁,main函数中并没有直接调用Time类析构函数,而是显式调用编译器 //为Date类生成的默认析构函数。 // 注意:创建哪个类的对象则调用该类的析构函数,销毁那个类的对象则调用该类的析构函数。 public: Date(int year, int minute, int day) { cout cout cout Date temp(d); return temp; } int main() { Date d1(2022,1,13); Test(d1); return 0; } public: Date(int year = 1900, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } //private: int _year; int _month; int _day; }; // 这里会发现运算符重载成全局的就需要成员变量是公有的,那么问题来了,封装性如何保证? // 这里其实可以用我们后面学习的友元解决,或者干脆重载成成员函数。 bool operator==(const Date& d1, const Date& d2) { return d1._year == d2._year && d1._month == d2._month && d1._day == d2._day; } void Test () { Date d1(2023, 5, 9); Date d2(2023, 5, 10); cout public: Date(int year = 1900, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } // bool operator==(Date* this, const Date& d2) // 这里需要注意的是,左操作数是this,指向调用函数的对象 bool operator==(const Date& d2) { return _year == d2._year; && _month == d2._month && _day == d2._day; } private: int _year; int _month; int _day; }; // 对自身进行修改 ++m_value; return *this; } MyClass temp(*this); // 复制自身,用于返回未修改对象的副本 ++(*this); // 修改自身 return temp; // 返回复制的对象 } public: Date(int year = 1900, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } // 前置++:返回+1之后的结果 // 注意:this指向的对象函数结束后不会销毁,故以引用方式返回提高效率 Date& operator++() { _day += 1; return *this; } // 后置++: // 前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载 // C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递 // 注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存一份,然后给this+1 // 而temp是临时对象,因此只能以值的方式返回,不能返回引用 Date operator++(int) { Date temp(*this); _day += 1; return temp; } private: int _year; int _month; int _day; }; public : Date(int year = 1900, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } Date(const Date& d) { _year = d._year; _month = d._month; _day = d._day; } // 赋值运算符重载 Date& operator=(const Date& d) { if(this != &d) { _year = d._year; _month = d._month; _day = d._day; } return *this; } private: int _year ; int _month ; int _day ; }; public: Date(int year = 1900, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } int _year; int _month; int _day; }; // 赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数 Date& operator=(Date& left, const Date& right) { if (&left != &right) { left._year = right._year; left._month = right._month; left._day = right._day; } return left; } // 编译失败: // error C2801: “operator =”必须是非静态成员 public: Time() { _hour = 1; _minute = 1; _second = 1; } Time& operator=(const Time& t) { cout _hour = t._hour; _minute = t._minute; _second = t._second; } return *this; } private: int _hour; int _minute; int _second; }; class Date { private: // 基本类型(内置类型) int _year = 2023; int _month = 1; int _day = 1; // 自定义类型 Time _t; }; int main() { Date d1; Date d2; d1 = d2; return 0; } public: Stack(size_t capacity = 10) { _array = (DataType*)malloc(capacity * sizeof(DataType)); if (nullptr == _array) { perror("malloc申请空间失败"); return; } _size = 0; _capacity = capacity; } void Push(const DataType& data) { // CheckCapacity(); _array[_size] = data; _size++; } ~Stack() { if (_array) { free(_array); _array = nullptr; _capacity = 0; _size = 0; } } private: DataType *_array; size_t _size; size_t _capacity; }; int main() { Stack s1; s1.Push(1); s1.Push(2); s1.Push(3); s1.Push(4); Stack s2; s2 = s1; return 0; } public : Date* operator&() { return this ; } const Date* operator&()const { return this ; } private : int _year ; // 年 int _month ; // 月 int _day ; // 日 }; public: Date(int year, int month, int day) { _year = year; _month = month; _day = day; } void Print() { cout cout Date d1(2022,1,13); d1.Print(); const Date d2(2022,1,13); d2.Print(); } int main() { Test(); return 0; } friend ostream& operator _year = year; _month = month; _day = day; assert(CheckDate()); } // 拷贝构造可以不用写,默认生成的就够用 Date(const Date& d) { _year = d._year; _month = d._month; _day = d._day; } // 赋值重载可以不用写,默认生成的就够用 Date& operator=(const Date& d) { if (this != &d) { _year = d._year; _month = d._month; _day = d._day; } return *this; } // 析构函数可以不用写,某人生成的就够用 ~Date() { // 没必要 _year = _month = _day = 0; } int GetMonDay(int year, int month) { static int day[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 }; if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))) { return 29; } return day[month]; } bool CheckDate() { if (_year = 1 && _month = 1 && _month return true; } return false; } void Print(); bool operator==(const Date& d) const; bool operator!=(const Date& d) const; bool operator(const Date& d) const; bool operator=(const Date& d) const; bool operator out in d._year d._month d._day; d.CheckDate(); return in; } cout return _year == d._year && _month == d._month && _day == d._day; } bool Date::operator!=(const Date& d) const { return !(*this == d); } bool Date::operator(const Date& d) const { if ((_year d._year) || (_year == d._year && _month d._month) || (_year == d._year && _month == d._month && _day d._day)) { return true; } return false; } bool Date::operator=(const Date& d) const { return *this d || *this == d; } bool Date::operator return !(*this = d); } bool Date::operator return !(*this d); } Date Date::operator+(int day) const { Date tmp(*this); tmp += day; return tmp; } Date& Date::operator+=(int day) { if (day
- 面向对象:
-





