【C++ 学习 ②】- 类和对象(上)

news2024/12/26 1:18:35

目录

一、 面向对象的基本理念

1.1 - 什么是对象?

1.2 - 类和对象

1.3 - 面向对象的五条原则

1.4 - 面向过程 vs 面向对象

二、C++ 中的结构体

三、类的定义

3.1 - 类的两种定义方式

3.2 - 成员变量的命名规范

四、类的访问限定符和封装

4.1 - 访问限定符

4.2 - 封装

五、类的实例化

六、类的存储方式

七、this 指针 

7.1 - this 指针的引入

7.2 - this 指针的特性

7.3 - 练习


参考资料

  1. 浙江大学 C++ 翁恺老师。

  2. 【C++】什么是对象?什么是类?_c++ 里的 对象定义。

  3. 面向过程 VS 面向对象 - 知乎 (zhihu.com)。

  4. 一文带你入门C++类和对象【十万字详解,一篇足够了】。


一、 面向对象的基本理念

1.1 - 什么是对象?

  • Object = Entity

  • Object may be visible or invisible.

  • Object is variable in programming languages.

在现实世界中,任何事物都是对象。

它可以是有形的具体存在的事物,例如一张凳子、一台电脑、一个学生、一辆汽车等,也可以是无形的抽象的事物,例如一次演出、一场球赛等。

在程序设计语言中,对象实际上就是变量。

  • Objects = Atrributes + Services

    Data: the properties or status

    Operations: the functions

对象是描述其属性的数据以及对这些数据施加的一组操作封装在一起构成的统一体。

例如,一个学生就是一个对象,学生的学号、姓名和成绩等数据就是他的属性,输入或输出学号、姓名和成绩等就是对数据施加的一组操作。

  • Object send and receive messages(objects do things!)

    Messages are

    Composed by the sender

    Interpreted by the receiver

    implemented by the methods

    Message

    —May cause receiver to change state

    —May return results

每个对象都有自己的属性和行为,对象和对象之间通过方法来交互。

1.2 - 类和对象

  • Objects(cat)

    Represent things, events, or concepts

    Respond to messages at run-time

  • Classes(cat class)

    Define properties of instances

    Act like types in C++

类和对象之间的关系是抽象和具体的关系。类是多个对象进行综合抽象的结果,对象又是类的个体实物,一个对象是类的一个实例。

1.3 - 面向对象的五条原则

  1. Everything is an object.

  2. A program is a bunch of objects telling each other what to do by sending messages.

  3. Each objects has its own memory made up of other objects.

  4. Every object has a type.

  5. All objects of a particular type can receive the same messages.

第五条原则可以分别两个方面来理解。从正面理解,即一个特定类型的所有对象可以接受相同的消息;从反面理解,即所有可以接受相同消息的对象可以被认为是同一类型。

1.4 - 面向过程 vs 面向对象

面向过程(Procedure Oriented,简称 PO)是以 "事件" 为中心的编程思想,编程的时候首先把解决问题的步骤分析出来,然后用函数把这些步骤实现,在一步一步的具体步骤中再按顺序调用函数

以五子棋为例,面向过程的设计思路是首先分析解决这个问题的步骤,即:

(1) 开始游戏 (2) 黑子先走 (3) 绘制画面 (4) 判断输赢 (5) 白子再走 (6) 绘制画面 (7) 判断输赢 (8) 返回步骤(2) (9) 输出最后结果

然后用函数实现上面一个一个的步骤,在一步一步的具体步骤中再按顺序调用函数。

面向对象(Object Oriented,简称 OO)是一种以 "对象" 为中心的编程思想,把要解决的问题分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描述某个对象在整个解决问题的步骤中的属性和行为。

在五子棋的例子中,用面向对象的方法来解决的话,首先将整个五子棋游戏分为三个对象:

(1) 黑白双方,这两方的行为是一样的

(2) 棋盘系统,负责绘制画面

(3) 规则系统,负责判定犯规、输赢等

然后赋予每个对象一些属性和行为:

第一类对象(黑白双方)负责接受用户输入,并告知第二类对象(棋盘系统)棋子布局的变化,棋盘系统接受到了棋子的变化,并负责在屏幕上显示出这种变化,同时利用第三类对象(规则系统)来对棋局进行判定。


二、C++ 中的结构体

C 结构体中只能包含成员变量,而 C++ 结构体的成员不仅可以是变量,还可以是函数

C doesn't support the relationship between data and functions.

用 C 和 C++ 分别实现栈时,Stack.h 中的内容分别应该是

<C 版本>

#pragma once
​
#include <stdbool.h>
​
// 动态顺序栈
typedef int STDataType;
​
typedef struct Stack
{
    STDataType* data;
    int top;
    int capacity;
}Stack;
​
// 基本操作
void StackInit(Stack* pst, int default_capacity);  // 初始化
​
bool StackEmpty(Stack* pst);  // 判断是否为空栈
​
void StackPush(Stack* pst, STDataType x);  // 入栈
void StackPop(Stack* pst);  // 出栈
​
STDataType StackTop(Stack* pst);  // 返回栈顶元素
​
int StackSize(Stack* pst);  // 返回栈的有效元素个数
​
void StackDestroy(Stack* pst);  // 销毁

<C++ 版本>

#pragma once

typedef int STDataType;
​
struct Stack
{
public:
    void Init(int default_capacity = 5);  // 初始化
    bool Empty();  // 判断是否为空栈
    void Push(const STDataType& x);  // 入栈
    void Pop();  // 出栈
    STDataType Top();  // 返回栈顶元素
    int Size();  //返回栈的有效元素个数
    void Destroy();  // 销毁
private:
    STDataType* _data;
    int _top;
    int _capacity;
};

问题:

  1. public adj. 公开的;private adj. 私密的

    这两个关键字的作用是什么(大概可以知道,该结构体仅公开对外的接口,而隐藏内部的数据)?

  2. 成员变量名前面为什么要加一个下划线(_)?

  3. 在 C++ 中更喜欢用关键字 class 来代替 struct,那么 struct 和 class 的异同是什么?

  4. 成员函数又该如何定义?


三、类的定义

语法格式

class className
{
    // 类的成员,包括成员函数(类的方法)和成员变量(类的属性)
};  // 分号不能省略

3.1 - 类的两种定义方式

在 C++ 中,通常用一个 .h 和一个 .cpp 文件定义一个类(例如 Stack.hStack.cpp)。① 声明放在 .h 文件中,成员函数定义则放在 .cpp 文件中

由于类定义了一个新的作用域,即类作用域,所以在类体外定义成员函数,需要加作用域。例如:

Stack.cpp

#include "Stack.h"
#include <stdlib.h>
#include <assert.h>
​
// 初始化
void Stack::Init(int default_capacity)
{
    _data = (STDataType*)malloc(sizeof(STDataType) * default_capacity);
    if (nullptr == _data)
    {
        perror("initialization failed!");
        exit(-1);
    }
    _top = 0;
    _capacity = default_capacity;
}
​
​
// 判断是否为空栈
bool Stack::Empty()  
{
    return _top == 0;
}
​
​
// 入栈
void Stack::Push(const STDataType& x)
{
    if (_top == _capacity)
    {
        STDataType* tmp = (STDataType*)realloc(_data, sizeof(STDataType) * 2 * _capacity);
        if (nullptr == tmp)
        {
            perror("realloc failed!");
            return;
        }
        _data = tmp;
        _capacity *= 2;
    }
    _data[_top++] = x;
}
​
​
// 出栈
void Stack::Pop()
{
    assert(!Empty());  // 前提是栈非空
    --_top;
}
​
​
// 返回栈顶元素
STDataType Stack::Top()
{
    assert(!Empty());  // 前提是栈非空
    return _data[_top - 1];
}
​
​
//返回栈的有效元素个数
int Stack::Size()
{
    return _top;
}
​
​
// 销毁
void Stack::Destroy()
{
    free(_data);
    _data = nullptr;
    _top = _capacity = 0;
}

② 成员函数的定义也可以放在类体中,此时编译器可能会将其当成内联函数处理

3.2 - 成员变量的命名规范

#include <iostream>
using namespace std;
​
class Date
{
public:
    void Init(int year, int month, int day)
    {
        year = year;
        month = month;
        day = day;
    }
    void Display()
    {
        cout << year << "-" << month << "-" << day << endl;
    }
private:
    int year;
    int month;
    int day;
};
​
int main()
{
    Date d;
    d.Init(2023, 5, 1);
    d.Display();  // 随机值(说明初始化失败)
    return 0;
}

由此可以发现,当成员变量和形参同名时,是无法完成初始化的。类似于

#include <iostream>
using namespace std;
​
int g_val = 10;
​
int main()
{
    int g_val = 20;
    g_val = g_val;  // 该语句并不会将全局变量 g_val 的值赋值给局部变量 g_val
    cout << g_val << endl;  // 20
    return 0;
}

解决方案如下

class Date
{
public:
    void Init(int year, int month, int day)
    {
        // 一、加作用域
        /*
        Date::year = year;
        Date::month = month;
        Date::day = day;
        */
        
        // 二、使用 this 指针(后面会详解)
        /*
        this->year = year;
        this->month = month;
        this->day = day;
        */
        
        // 三、对成员变量名进行修改
        _year = year;
        _month = month;
        _day = day;
    }
    void Display()
    {
        cout << year << "-" << month << "-" << day << endl;
    }
private:
    int _year;  // 或者 mYear, m 即 member
    int _month;  // 或者 mMonth
    int _day;  // 或者 mDay
};


四、类的访问限定符和封装

4.1 - 访问限定符

  1. public 修饰的成员在类内和类外都可以直接被访问。

  2. protected 和 private 修饰的成员在类外不能直接被访问(此处 protected 和 private 是类似的)。

  3. 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止,如果后面没有访问限定符,作用域就到 } ,即类结束

  4. class 的默认访问权限是 private,struct 为 public

    因为 C++ 要兼容 C,所以在 C++ 中 struct 可以当作结构体使用,也可以用来定义类,和用 class 定义类是一样的,区别在于 struct 定义的类的默认访问权限是 public,class 定义的类的默认访问权限是 private。

    注意,在继承和模板参数列表位置,struct 和 class 也有区别,后续进行讲解

4.2 - 封装

面向对象的三大特性:封装、继承和多态

封装(encapsulation):将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口和对象进行交互

capsule n. 胶囊

封装本质上是一种管理,让用户更方便使用类。比如:对于电脑这样一个复杂的设备,提供给用户的就只有开关机键、通过键盘输入、显示器、USB 插孔等,让用户和计算机进行交互,完成日常事物。但实际上工作的却是 CPU、显卡、内存等一些硬件原件。

对于计算机使用者而言,不用关心内部核心部件,比如主板上路线是如何布局的,CPU 内部是如何设计的等,用户只需要知道,怎么开机、怎么通过键盘和鼠标与计算机进行交互即可。因此计算机厂商在出厂时,在外部套上壳子,将内部实现细节隐藏起来,仅仅对外提供开关、鼠标及键盘插孔等,让用户可以与计算机进行交互即可。

在 C++ 中实现封装,可以通过类将数据及操作数据的方法进行有机结合,通过访问权限来隐藏对象的属性和实现细节,控制哪些方法可以在类外直接被使用。


五、类的实例化

用类创建对象的过程,称为类的实例化

  1. 类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义一个类并没有分配实际的内存空间来存储它

  2. 一个类可以实例化出多个对象,实例化出的对象,占用实际的物理空间,存储类成员变量

做个比方,类实例化对象就像现实中使用建筑设计图建造出房子,类就像是设计图,只设计出需要什么东西,但是并没有实体的建筑存在,同样类也只是一个设计,实例化出的对象才能实际存储数据,占用物理空间。


六、类的存储方式

类对象的存储方式猜测

  1. 对象中包含类的各个成员

    显然,这种存储方式的一大缺陷在于,当创建多个对象时,每个对象的成员变量不同,但成员函数是相同的,因此会存在多份相同的代码,造成空间浪费

  2. 相同的代码只保存一份,在对象中存放代码的地址

  3. 只保存成员变量,成员函数存放在公共的代码段

实际上是采用第三种存储方式

#include <iostream>
using namespace std;
​
class A
{
public:
    void Init(char c1, int i, char c2);
    void Print();
private:
    char _c1;
    int _i;
    char _c2;
};
​
void A::Init(char c1, int i, char c2)
{
    _c1 = c1;
    _i = i;
    _c2 = c2;
}
​
void A::Print()
{
    cout << _c1 << " " << _i << " " << _c2 << endl;
}
​
int main()
{
    cout << sizeof(A) << endl;  // 12(注意:存在内存对齐)
    return 0;
}

注意:对于只有成员函数的类和空类,sizeof(classNmae) 为 1(编译器给一个字节来唯一标识这个类的对象)


七、this 指针 

7.1 - this 指针的引入

#include <iostream>
using namespace std;
​
class Date
{
public:
    void Init(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    void Display()
    {
        cout << _year << "-" << _month << "-" << _day << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};
​
int main()
{
    Date d1;
    Date d2;
    d1.Init(2023, 5, 1);
    d2.Init(2023, 5, 2);
    d1.Display();  // 2023-5-1
    d2.Display();  // 2023-5-2
    return 0;
}

问题:Date 类中有 InitDisplay 两个成员函数,函数体中没有关于不同对象的区分,那么当 d1 调用 InitDisplay 函数时,这两个函数是如何知道初始化和显示的对象是 d1,而不是其他,例如 d2,对象呢?

解答:C++ 编译器给每个 "非静态的成员函数" 增加一个隐藏的指针参数 this,让该指针指向当前对象(函数运行时调用该函数的对象)。在函数体中所有 "成员变量" 的操作,都是通过该指针去访问,只不过所有的操作对用户是透明的,即用户不需要传递,编译器自动完成

#include <iostream>
using namespace std;
​
class Date
{
public:
    void Init(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
        // 可以在成员函数内部显示地使用 this 指针
        cout << "this = " << this << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};
​
int main()
{
    Date d1;
    Date d2;
​
    cout << "&d1 = " << & d1 << endl;
    d1.Init(2023, 5, 1);
    // 输出的两个地址相同
​
    cout << "&d2 = " << & d2 << endl;
    d2.Init(2023, 5, 2);
    // 输出的两个地址也相同
    return 0;
}

7.2 - this 指针的特性

  1. this 为指针常量,即在成员函数中,不能改变 this 的指向。

  2. this 指针本质上是成员函数的形参,当对象调用成员函数时,将对象地址作为实参传递给 this 形参,所以对象中不存储 this 指针。

    d1.Init(2023, 5, 1);  // 可以视为 Date::Init(&d1, 2023, 5, 1);
    d1.Display();  // 可以视为 Date::Display(&d1);
    // 注意:不能显示地将对象的地址传递给 this 指针,
    // 所以 Date::Init(&d1, 2023, 5, 1); 和 Date::Display(&d1); 实际上是不被允许的
  3. this 指针是成员函数第一个隐含的指针形参,一般情况由编译器通过 ecx 寄存器自动传递,不需要用户传递

7.3 - 练习

#include <iostream>
using namespace std;
​
class A
{
public:
    void f() 
    {
        cout << "f()" << endl;
    }
private:
    int _i;
};
​
int main()
{
    A* pa = nullptr;
    pa->f(); 
    return 0;
}

问:上面程序编译运行的结果是()?

  1. 编译报错

  2. 运行崩溃

  3. 正常运行

pa->f(); 可以被视为 A::f(pa);,this 指针为空,但在函数内部没有对 this 指针进行解引用,所以并不会出现运行错误,而是正常输出 "f()"

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

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

相关文章

SPFA + 链式前向星建图【附Java模板】

SPFA算法是什么&#xff1f;它能解决什么样的问题&#xff1f; &#x1f337; 仰望天空&#xff0c;妳我亦是行人.✨ &#x1f984; 个人主页——微风撞见云的博客&#x1f390; &#x1f433; 数据结构与算法专栏的文章图文并茂&#x1f995;生动形象&#x1f996;简…

P3029 [USACO11NOV]Cow Lineup S 双指针 单调队列

“五一”小长假来了趟上海&#xff0c;在倒数第二天终于有时间做了一会儿题目&#xff0c;A了之后过来写一篇题解 【问题描述】 农民约翰雇一个专业摄影师给他的部分牛拍照。由于约翰的牛有好多品种&#xff0c;他喜欢他的照片包含每个品种的至少一头牛。 约翰的牛都站在一条沿…

[MAUI]模仿iOS应用多任务切换卡片滑动的交互实现

文章目录 原理创建布局创建分布函数创建动效创建绑定数据细节调整首张卡片的处理为卡片添加裁剪跳转到最后一张卡片 项目地址 看了上一篇博文的评论&#xff0c;大家对MAUI还是比较感兴趣的&#xff0c;非常感谢大家的关注&#xff0c;这个专栏我争取周更&#x1f609;。 App之…

华为OD机试真题(Java),数组拼接(100%通过+复盘思路)

一、题目描述 现在有多组整数数组,需要将它们合并成一个新的数组。 合并规则,从每个数组里按顺序取出固定长度的内容合并到新的数组中,取完的内容会删除掉,如果该行不足固定长度或者已经为空,则直接取出剩余部分的内容放到新的数组中,继续下一行。 二、输入描述 第一…

【Python入门篇】——PyCharm的基础使用

作者简介&#xff1a; 辭七七&#xff0c;目前大一&#xff0c;正在学习C/C&#xff0c;Java&#xff0c;Python等 作者主页&#xff1a; 七七的个人主页 文章收录专栏&#xff1a; Python入门&#xff0c;本专栏主要内容为Python的基础语法&#xff0c;Python中的选择循环语句…

Mysql Sharding-JDBC读写分离

0 课程视频 深入Sharding-JDBC分库分表从入门到精通【黑马程序员】_哔哩哔哩_bilibili 1 基本概念 1.1应用逻辑 1.1.1 msyql 多库 多表 多服务器 1.1.2 通过Sharding-JDBC jar包->增强JDBC 访问多数据源 -> 自动处理成一个数据源 1.1.3 使用数据的人 -> 使用Sh…

Python+Yolov5墙体裂缝识别

程序示例精选 PythonYolov5墙体裂缝识别 如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对<<PythonYolov5墙体裂缝识别>>编写代码&#xff0c;代码整洁&#xff0c;规则&#xff0c…

C++类的默认成员函数

文章目录 默认函数构造函数和析构函数构造函数析构函数 拷贝构造函数运算符重载赋值运算符重载赋值运算符重载和构造函数 默认函数 什么是默认函数&#xff1f; 默认函数就是当你使用这个类对象时,这个类会自动调用的函数C中有六个默认成员函数,并且作用各不相同,下面我们来一…

2 ROS2话题通讯基础

2 ROS2话题通讯基础 2.1 ROS2话题通讯介绍2.2 ROS2常用的消息类型介绍2.2.1 std_msgs消息类型2.2.2 geometry_msgs消息类型 2.3 使用C/C创建基础消息类型的话题通讯2.3.1 创建C/C发布话题信息的功能包并配置VSCode环境2.3.2 编写ROS2发布话题节点CPP文件2.3.3 配置C/C发布话题功…

数据结构学习记录——堆的插入(堆的结构类型定义、最大堆的创建、堆的插入:堆的插入的三种情况、哨兵元素)

目录 堆的结构类型定义 最大堆的创建 堆的插入 堆的插入的三种情况 代码实现 哨兵元素 堆的结构类型定义 #define ElementType int typedef struct HNode* Heap; /* 堆的类型定义 */ struct HNode {ElementType* Data; /* 存储元素的数组 */int Size; /* 堆中…

『python爬虫』08. 数据解析之bs4解析(保姆级图文)

目录 1. 安装bs4模块find()findall() 2. 爬取信息测试总结 欢迎关注 『python爬虫』 专栏&#xff0c;持续更新中 欢迎关注 『python爬虫』 专栏&#xff0c;持续更新中 1. 安装bs4模块 pip install bs4 pip install-i https://pypi.tuna.tsinghua.edu.cn/simplebs4如果遇到报…

[网络原理] HTTP协议

要珍惜时间呀 文章目录 1. HTTP协议概念2. HTTP协议格式2.URL3. GET与POST方法3.1 GET方法3.2 POST方法3.3 GET与POST的区别 1. HTTP协议概念 HTTP协议是应用层协议,TCP/IP协议为传输层协议,负责传输数据.而HTTP协议相当于对传输的数据据怎样处理和使用进行说明. 每次,我们访问…

Mininet+Ryu安装教程

最近要做一个Mininet的网络环境&#xff0c;网络设备由Mininet来模拟&#xff0c;SDN控制器用Ryu来做&#xff0c;为了避免每次重新做再去翻查资料&#xff0c;我在这里系统地整理一遍 硬件需求 我在 VMWare Workstation 16 Player虚拟机上运行的Ubuntu 22.04.1 硬件需求内存…

供应链 | 需求不确定情况下的物料需求规划: 基于随机优化的研究

作者&#xff1a;Simon Thevenin, Yossiri Adulyasak, Jean-Francois Cordeau​ 引用&#xff1a;Thevenin S, Adulyasak Y, Cordeau J F. Material requirements planning under demand uncertainty using stochastic optimization[J]. Production and Operations Management,…

react的项目实战 2

入口文件引入了app这个组件 app这个组件又引入了header这个组件。 然后外面引入这个组件 进行页面的显示 它不会影响到其他页面的组件的样式。 ​​​​​​​

面试必备:接口自动化测试精选面试题大全

目录 一、 请问你是如何做接口测试的&#xff1f; 二、接口测试如何设计测试用例&#xff1f; 三、接口测试执行中需要比对数据库吗&#xff1f; 四、接口测试质量评估标准是什么&#xff1f; 五、接口产生的垃圾数据如何清理 六、其他接口要先获取接口信息&#xff0c;如…

利用wenda实现本地多模态数据的知识获取和推理

近年来&#xff0c;大型语言模型&#xff08;LLM&#xff09;技术取得了令人瞩目的进展&#xff0c;为自然语言处理领域带来了巨大的变革&#xff0c;但是大多数LLM都面临着领域适应性的问题&#xff0c;因为它们使用的数据都是公开的数据&#xff0c;在国内&#xff0c;有很多…

Day960.架构现代化-微服务 -遗留系统现代化实战

架构现代化-微服务 Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于架构现代化-微服务的内容。 在自治气泡模式的基础上&#xff0c;通过事件拦截来实现数据同步&#xff0c;给气泡和遗留系统之间又加上 API 访问这个通信渠道。 这时的自治气泡就和真正的微服务差不…

Rust - 变量与数据的交互方式(move)

变量与数据的交互方式 - 移动 Rust 中的多个变量可以采用一种比较独特的方式和同一个数据进行交互&#xff0c;如下代码所示&#xff0c;将变量x的值赋给y&#xff1a; fn main() {let x 1;let y x; }我们大概可以推论出上述代码的原理&#xff1a;将1这个整数绑定给x变量&…

Mybatis读取和存储json类型的数据

目录 一、测试使用JSONObject来获取json二、设置TableName的autoResultMap为true&#xff0c;TableField的typeHandler为JacksonTypeHandler.class三、设置xml当中的resultMap四、JacksonTypeHandler讲解五、新增假如是JSONObject 不管数据库当中是以json还是longtext数据类型来…