主页
搜索
最近更新
数据统计
赞助我们
申请密钥
系统公告
1
/
1
请查看完所有公告
面向对象学习笔记
最后更新于 2025-06-20 17:13:58
作者
Lovely_Elaina
分类
个人记录
复制 Markdown
查看原文
删除文章
更新内容
这是一个关于 C++ 面向对象的学习笔记。 ## 前言 ### 面向对象是什么? > 面向[对象](https://baike.baidu.com/item/对象?fromModule=lemma_inlink)(Object Oriented)是[软件开发方法](https://baike.baidu.com/item/软件开发方法/971447?fromModule=lemma_inlink),一种编程范式。面向[对象](https://baike.baidu.com/item/对象?fromModule=lemma_inlink)的概念和应用已超越了[程序设计](https://baike.baidu.com/item/程序设计/223952?fromModule=lemma_inlink)和[软件开发](https://baike.baidu.com/item/软件开发/3448966?fromModule=lemma_inlink),扩展到如[数据库系统](https://baike.baidu.com/item/数据库系统/215176?fromModule=lemma_inlink)、[交互](https://baike.baidu.com/item/交互/6964417?fromModule=lemma_inlink)式界面、应用结构、应用平台、[分布式系统](https://baike.baidu.com/item/分布式系统/4905336?fromModule=lemma_inlink)、[网络管理](https://baike.baidu.com/item/网络管理/5903609?fromModule=lemma_inlink)结构、[CAD](https://baike.baidu.com/item/CAD/990?fromModule=lemma_inlink)技术、[人工智能](https://baike.baidu.com/item/人工智能/9180?fromModule=lemma_inlink)等领域。面向对象是一种对现实世界理解和[抽象](https://baike.baidu.com/item/抽象/9021828?fromModule=lemma_inlink)的方法,是计算机编程技术发展到一定阶段后的产物。 > > 面向对象是相对于[面向过程](https://baike.baidu.com/item/面向过程/9957246?fromModule=lemma_inlink)来讲的,[面向对象方法](https://baike.baidu.com/item/面向对象方法/216078?fromModule=lemma_inlink),把相关的数据和方法组织为一个整体来看待,从更高的层次来进行系统[建模](https://baike.baidu.com/item/建模/814831?fromModule=lemma_inlink),更贴近事物的自然运行模式。 > > from [Baidu](https://baike.baidu.com/item/%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1/2262089?fr=aladdin). ### 这篇文章讲了什么? C++ 在 C 语言的基础上增加了面向对象编程,类是 C++ 的核心特性。 面向对象在复赛不大会考,但是初赛也就是笔试题,在 CSP、蓝桥杯等比赛中出现过。 本篇文章,将会用尽可能简洁明了的语言讲解面向对象。 每个操作建议看完就自己实践一下。 ## 正文 ### 类的声明 类的声明和结构体很像,只要这样就声明了一个类。 ```cpp #include <iostream> using namespace std; // 声明一种叫做 kaka 的类 class kaka{ }; int main(){ return 0; } ``` 然后我们往 class 里面一些变量。 ```cpp #include <iostream> using namespace std; class kaka{ string kind; int age; bool is_boy; }; int main(){ kaka wk; wk.kind = "wind"; wk.age = 14; wk.is_boy = true; return 0; } ``` 我们把这些变量叫做:`属性`。 但是我们这个时候编译,会爆一堆错: ```data $ g++ app.cpp -o app && ./app app.cpp: In function ‘int main()’: app.cpp:13:8: error: ‘std::string kaka::kind’ is private within this context 13 | wk.kind = "wind"; | ^~~~ app.cpp:5:12: note: declared private here 5 | string kind; | ^~~~ app.cpp:14:8: error: ‘int kaka::age’ is private within this context 14 | wk.age = 14; | ^~~ app.cpp:6:9: note: declared private here 6 | int age; | ^~~ app.cpp:15:8: error: ‘bool kaka::is_boy’ is private within this context 15 | wk.is_boy = true; | ^~~~~~ app.cpp:7:10: note: declared private here 7 | bool is_boy; ``` > error: ‘std::string kaka::kind’ is private within this context 大意可以理解为:联系上下文,kaka 的 kind 属性是私密的。 这就引出了 class 种的三个属性: - `public`:公有,都可以调用; - `private`:私有,只有这个 class 里面可以调用; - `protected`:保护,等同于私有,但是可以继承。 什么是继承,后面会提到。 在默认情况下,class 里的属性都是私有的,我们需要手动改成公有。 ```cpp #include <iostream> using namespace std; class kaka{ public: string kind; int age; bool is_boy; }; int main(){ kaka wk; wk.kind = "wind"; wk.age = 14; wk.is_boy = true; return 0; } ``` 好的,下面我们讲 `方法`。 `方法` 其实就是在 class 里面写函数。 ```cpp #include <iostream> using namespace std; class kaka{ public: string kind; int age; bool is_boy; void hello(){ cout << "hello,i am " << kind << "_kaka,now " << age << " years old" << endl; } void shout(){ cout << "kaka!!!" << endl; } }; int main(){ kaka wk; wk.kind = "wind"; wk.age = 14; wk.is_boy = true; wk.hello(); wk.shout(); wk.shout(); wk.shout(); return 0; } ``` 输出: ```data $ g++ app.cpp -o app && ./app hello,i am wind_kaka,now 14 years old kaka!!! kaka!!! kaka!!! ``` 然后我们了解了最基本的 class。 如果我们有一些私有的东西,我们需要更改,可以提供公有的方法,比如: ```cpp #include <iostream> using namespace std; class kaka{ public: string kind; bool is_boy; void set_age(int x){ age = x; } void hello(){ cout << "hello,i am " << kind << "_kaka,now " << age << " years old" << endl; } void shout(){ cout << "kaka!!!" << endl; } private: int age; }; int main(){ kaka wk; wk.kind = "wind"; wk.set_age(14); wk.is_boy = true; wk.hello(); return 0; } ``` 这样,我们还能对传入的数据进行处理: ```cpp #include <iostream> using namespace std; class kaka{ public: string kind; bool is_boy; void set_age(int x){ if(x < 0){ cout << "kaka!(angry" << endl; return ; } age = x; } void hello(){ cout << "hello,i am " << kind << "_kaka,now " << age << " years old" << endl; } void shout(){ cout << "kaka!!!" << endl; } private: int age; }; int main(){ kaka wk; wk.kind = "wind"; wk.set_age(-11); wk.is_boy = true; wk.hello(); return 0; } ``` 但是我们设置传入的形参变量名,一般和我们要更改的属性的变量名相同,那该怎么办? 答案是 `this`。 ```cpp #include <iostream> using namespace std; class kaka{ public: string kind; bool is_boy; void set_age(int age){ if(age < 0){ cout << "kaka!(angry" << endl; return ; } this->age = age; } void hello(){ cout << "hello,i am " << kind << "_kaka,now " << age << " years old" << endl; } void shout(){ cout << "kaka!!!" << endl; } private: int age; }; int main(){ kaka wk; wk.kind = "wind"; wk.set_age(-11); wk.is_boy = true; wk.hello(); return 0; } ``` 如果加了 `this`,那么 `this` 后面那个只会是创建的这个类里面的 属性。 那么我们可以理解为,class 其实是个抽象的模版,我们每次 kaka 一个,都是从这个模版这个印了一个走。 要是我们再 kaka 一个 $fk$,我们在使用 $fk$ 的时候,`this` 后面的将会是 $fk$ 的属性。 ### 堆的声明 接下来,要插入一段 C++ 的底层知识。 我们经常说爆栈了,但是这个栈是什么? 首先我们要知道,我们执行函数的时候,我们会先将 main 函数压入栈,然后按顺序执行,如果有执行其他函数,那么就将那个函数加入栈,开始处理。 有时候我们递归层次太多了,也有可能会爆栈。 假设我们电脑里面有这样一片内存。  如果我们建一个局内变量,那么每次执行完这个变量就会被栈“弹出”,会指向一块可能是不可用的空间。 ```cpp #include <iostream> using namespace std; int *f(){ int ret = 0; return &ret; } signed main(){ ios::sync_with_stdio(0); cin.tie(NULL); int *p = f(); cout << *p << endl; return 0; } ``` 会爆这样一个错: ```data Segmentation fault (core dumped) ``` 机翻:分段错误(核心转储)。 可以理解为我们访问了一片没有的空间。 但是呢,我们可以在堆里面申请空间。 那就是,new 一个。 ```cpp #include <iostream> using namespace std; int *f(){ int *ret = new int(114); return ret; } int main(){ int *p = f(); cout << *p << endl; return 0; } ``` 会输出 $114$。 可是有个注意的,就是我们最好最后将其 delete 掉,不然可能会发生内存泄漏。 ```cpp #include <iostream> using namespace std; int *f(){ int *ret = new int(114); return ret; } int main(){ int *p = f(); cout << *p << endl; delete p; return 0; } ``` ### 函数的使用 好的,我们了解了一点堆,了解这个干什么呢? 我们通常会给类专门申请空间,这样,我们就能触发 `构造函数`。 那么我们声明的就成了一个指针,用什么都要用 `->`。 ```cpp #include <iostream> using namespace std; class kaka{ public: string kind; bool is_boy; void set_age(int age){ if(age < 0){ cout << "kaka!(angry" << endl; return ; } this->age = age; } void hello(){ cout << "hello,i am " << kind << "_kaka,now " << age << " years old" << endl; } void shout(){ cout << "kaka!!!" << endl; } private: int age; }; int main(){ kaka *wk = new kaka(); wk->kind = "wind"; wk->set_age(-11); wk->is_boy = true; wk->hello(); return 0; } ``` 在默认情况下,就像上面,系统就会提供一个空的构造函数,就是说我们什么都不用传。 我们可以在里面写一个构造函数。 ```cpp #include <iostream> using namespace std; class kaka{ public: kaka(string kind){ if(kind == ""){ this->kind = "wild"; return ; } this->kind = kind; } // 空 kaka(){} void set_age(int age){ if(age < 0){ cout << "kaka!(angry" << endl; return ; } this->age = age; } void gender(bool is_boy){ this->is_boy = is_boy; } void hello(){ cout << "hello,i am " << kind << "_kaka,now " << age << " years old" << endl; } void shout(){ cout << "kaka!!!" << endl; } private: string kind; bool is_boy; int age; }; int main(){ kaka *wk = new kaka("wind"); wk->set_age(14); wk->gender(true); wk->hello(); return 0; } ``` 这个时候,我们提供了构造函数,那么系统不会提供空的构造函数,需要我们自己手写,就是上面注释为 `空` 的那个函数。 这里,我们又可以知识点,方法的重载: - 方法名(函数名)相同; - 形参个数或类型不同; - 自动选一个适合的方法; - 普通方法和构造方法都可以重载。 接下来,我们讲一下 `解析函数`。 解析函数,就是我们在销毁一个 class 时,会触发的。 创建方法就是在类名前面加一个 `~`。 ```cpp #include <iostream> using namespace std; class kaka{ public: kaka(string kind){ if(kind == ""){ this->kind = "wild"; return ; } this->kind = kind; } kaka(){} // 解析函数 ~kaka(){ cout << "kaka~(sad" << endl; } void set_age(int age){ if(age < 0){ cout << "kaka!(angry" << endl; return ; } this->age = age; } void gender(bool is_boy){ this->is_boy = is_boy; } void hello(){ cout << "hello,i am " << kind << "_kaka,now " << age << " years old" << endl; } void shout(){ cout << "kaka!!!" << endl; } private: string kind; bool is_boy; int age; }; int main(){ kaka *wk = new kaka("wind"); wk->set_age(14); wk->gender(true); wk->hello(); return 0; } ``` ### 类的继承 我们可以新建一个类让其继承另一个类的一切,但是私有的无法继承,这个就叫做 `派生类`。 继承方法,就是新建的类名后面加一个冒号再加上要继承的类名,有三种继承方式。 我们几乎不使用 protected 或 private 继承,通常使用 public 继承。当使用不同类型的继承时,遵循以下几个规则: - 公有继承(public):当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有和保护成员来访问 - 保护继承(protected): 当一个类派生自保护基类时,基类的公有和保护成员将成为派生类的保护成员 - 私有继承(private):当一个类派生自私有基类时,基类的公有和保护成员将成为派生类的私有成员 ```cpp class fkaka: public kaka{ public: // 构造 & 私有不能继承 fkaka(string kind){ this->kind = kind; } fkaka(string kind,int age){ this->kind = kind; this->age = age; } // 重写 // 子类重写父类的方法(覆盖) void set_age(int age){ this->age = age; } }; ``` 上面继承的 kaka 类有点改动,就是把 kind 和 age 换成保护。 同时,有多种类继承: ```cpp // 模版 class fkakaf: public kakaf,public fkaka{ }; ``` 以及 class 支持运算符重载,同 struct。 | 可重载运算符 | 符号 | | -------------- | ------------------------------------------------------------ | | 双目算术运算符 | `+`,`-`,`*`,`/`,`%` | | 关系运算符 | `==`,`!=`,`<`,`>`,`<=`,`>=` | | 逻辑运算符 | `||`,`&&`,`!` | | 单目运算符 | `+`,`-`,`*`,`&` | | 自增自减运算符 | `++`,`--` | | 位运算符 | `|`,`&`,`~`,`^`,`<<`,`>>` | | 赋值运算符 | `=`,`+=`,`-=`,`*=`,`/=`,`%=`,`&=`,`|=`,`^=`,`<<=`,`>>=` | | 空间申请与释放 | `new`,`delete`,`new[ ]`,`delete[]` | | 其他运算符 | `()`(函数调用),`->`(成员访问),`,`(逗号),`[]`(下标) | | 不可重载运算符 | 符号 | | ------------------ | ----------- | | 成员访问运算符 | `.` | | 成员指针访问运算符 | `.*`,`->*` | | 域运算符 | `::` | | 长度运算符 | `sizeof` | | 条件运算符 | `?:` | | 预处理符号 | `#` | 同时,还有虚函数,也就是 `virtual`。 ```cpp class kaka{ virtual void show()=0; }; ``` 然后在继承的时候,再定义。 ```cpp class fkaka: public kaka{ public: virtual void show(){ cout << "Hello" << endl; } }; ``` 如果没有 `virtual` 时,为 `静态多态` 或 `静态链接`,函数调用在程序执行前就准备好了,这也被称为 `早绑定`。 基类中使用关键字 `virtual` 声明的函数,叫 `虚函数`,在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数,根据所调用的对象类型来选择调用的函数,这种操作被称为 `动态链接` 或 `后期绑定`。 ## 结语 作者太菜了,所以只会这些。 望大佬指出错误。
正在渲染内容...
点赞
3
收藏
0