【C++篇】深度解析类与对象(上)

news2024/11/25 14:43:12

目录

引言

一、类的定义

1.1类定义的基本格式

1.2 成员命名规范

1.3 class与struct的区别

1.4 访问限定符

1.5 类的作用域

二、实例化

2.1 类的实例化

2.2 对象的大小与内存对齐

三、this 指针

3.1 this指针的基本用法

3.2 为什么需要this指针?

3.3 this指针的限制

四、C++和C语言实现Stack的对比

4.1 C语言实现Stack

4.2 C++语言实现Stack

4.3 C++与C实现的对比总结

五、总结


引言

在C++中,类和对象是面向对象编程的基础组成部分。通过类,程序员可以对现实世界的实体进行模拟和抽象。类的基本概念包括成员变量、成员函数、访问控制等。本篇博客将介绍C++类与对象的基础知识,为后续学习打下良好的基础。

一、类的定义

在C++中,类通过将数据和行为封装在一起,模拟现实世界中的对象。类的定义通常包含成员变量(描述对象的状态)和成员函数(定义对象的行为)。类的定义使用class关键字,并以分号结束。

1.1类定义的基本格式

类的定义格式如下所示:

#include <iostream>
using namespace std;
​
class Stack {
public:
    // 初始化栈
    void Init(int n = 4) {
        array = new int[n];
        capacity = n;
        top = 0;
    }
​
    // 将元素推入栈
    void Push(int x) {
        array[top++] = x;
    }
​
    // 获取栈顶元素
    int Top() {
        if (top > 0) {
            return array[top - 1];
        }
        return -1; // 栈为空时返回-1
    }
​
    // 销毁栈
    void Destroy() {
        delete[] array;
        array = nullptr;
        top = capacity = 0;
    }
​
private:
    int* array;      // 栈数据数组
    size_t capacity; // 栈容量
    size_t top;      // 栈顶指针
};
​
int main() {
    Stack st;
    st.Init();
    st.Push(1);
    st.Push(2);
    cout << st.Top() << endl;
    st.Destroy();
    return 0;
}

在上述代码中:

  • class关键字用于定义类,Stack是类的名称。

  • public修饰的成员函数可以在类的外部访问,例如InitPushTopDestroy

  • private修饰的成员变量(如arraycapacitytop)只能在类的内部访问,无法在类外部直接使用。

1.2 成员命名规范

在C++中,通常会为类的成员变量使用特定的命名约定,以避免与函数参数或局部变量混淆。这些命名约定可以提高代码的可读性和维护性。例如:

  • 使用下划线前缀:如_year

  • 使用m_前缀:如m_month

  • 使用驼峰命名法:如dayOfMonth

1.3 class与struct的区别

C++中的classstruct的主要区别在于默认的访问权限:

  • class中,未标明的成员变量和成员函数默认是private

    struct ExampleClass {
        int a; // 默认 private
    };
  • struct中,未标明的成员变量和成员函数默认是public

    struct ExampleStruct {
        int a; // 默认 public
    };

1.4 访问限定符

访问限定符用于控制类的成员的可见性。C++支持三种访问限定符:

  • public:公共成员可以在类的外部访问。

  • private:私有成员只能在类的内部访问。

  • protected:保护成员只能在类内部或派生类中访问(会在继承中详细讲解)。

访问限定符从出现的位置开始生效,直到遇到下一个访问限定符或类定义结束为止。例如:

class Date {
public:
    void Init(int year, int month, int day) {
        _year = year;
        _month = month;
        _day = day;
    }
​
private:
    int _year;
    int _month;
    int _day;
};
​
int main() {
    Date d;
    d.Init(2024, 3, 31);
    // 无法直接访问 _year, _month, _day,因为它们是私有成员
    return 0;
}

在上述示例中,Init函数是公共的,可以在类外部调用;而_year_month_day是私有的,只能通过成员函数访问。

1.5 类的作用域

类的作用域决定了类成员的可访问性。当在类的外部定义成员函数时,需要使用作用域解析符::来指明成员函数所属的类。

#include <iostream>
using namespace std;
​
class Stack {
public:
    void Init(int n = 4);
private:
    int* array;
    size_t capacity;
    size_t top;
};
​
// 在类外定义成员函数
void Stack::Init(int n) {
    array = new int[n];
    capacity = n;
    top = 0;
}
​
int main() {
    Stack st;
    st.Init();
    return 0;
}

通过使用Stack::Init,编译器可以知道Init函数属于Stack类,并能在类的作用域中查找成员变量arraycapacitytop

二、实例化

2.1 类的实例化

实例化是指在物理内存中创建对象的过程。类提供了对象的结构和行为,但本身不占用物理空间,只有实例化后才会在内存中分配空间。

⼀个类可以实例化出多个对象 ,实例化出的对象 占⽤实际的物理空间,存储类成员变量。打个⽐⽅:类实例化出对象就像现实中使⽤建筑设计图建造出房⼦,类就像是设计图,设计图规划了有多少个房间,房间⼤⼩功能等,但是并没有实体的建筑存在,也不能住⼈,⽤设计图修建出房⼦,房⼦才能住⼈。同样类就像设计图⼀样,不能存储数据,实例化出的对象分配物理内存存储数据。
#include <iostream>
using namespace std;
​
class Date {
public:
    void Init(int year, int month, int day) {
        _year = year;
        _month = month;
        _day = day;
    }
​
    void Print() {
        cout << _year << "/" << _month << "/" << _day << endl;
    }
​
private:
    int _year;
    int _month;
    int _day;
};
​
int main() {
    Date d1;
    d1.Init(2024, 3, 31);
    d1.Print();
    return 0;
}

在上述代码中,Date d1实例化了一个Date对象,并调用了InitPrint成员函数。

2.2 对象的大小与内存对齐

对象的大小由成员变量决定,成员函数不影响对象的大小。

类实例化出的每个对象,都有独⽴的数据空间,所以对象中肯定包含成员变量,那么成员函数是否包含呢?⾸先函数被编译后是⼀段指令,对象中没办法存储,这些指令存储在⼀个单独的区域(代码段),那么对象中⾮要存储的话,只能是成员函数的指针。再分析⼀下,对
象中是否有存储指针的必要呢,Date实例化d1和d2两个对象,d1和d2都有各⾃独⽴的成员变量_year/_month/_day存储各⾃的数据,但是d1和d2的成员函数Init/Print指针却是⼀样的,存储在对象中就浪费了。如果⽤Date实例化100个对象,那么成员函数指针就重复存储100次,太浪费了。这⾥需要再额外哆嗦⼀下, 其实函数指针是不需要存储的,函数指针是⼀个地址,调⽤函数被编译成汇编指令[call 地址], 其实编译器在编译链接时,就要找到函数的地址,不是在运⾏时找 ,只有动态多态是在运⾏时找,就需要存储函数地址,这个我们以后会讲解。

C++规定类的对象也需要符合内存对齐的规则,以提高访问效率。

#include <iostream>
using namespace std;
​
class A {
private:
    char _ch; // 1 字节
    int _i;   // 4 字节
};
​
int main() {
    A a;
    cout << sizeof(a) << endl; // 输出8字节,因内存对齐
    return 0;
}

虽然_ch_i占用5字节,但由于内存对齐,实际大小为8字节。这样可以优化内存访问的性能。

结构体对齐详细介绍可参考我的另一篇博客 。

三、this 指针

this指针是C++中的一个隐含指针,指向调用成员函数的当前对象。它存在于每一个非静态成员函数中,用于区分成员变量和函数参数。当成员函数被调用时,this指针会自动传递给函数,使其能够访问调用该函数的对象的成员。

3.1 this指针的基本用法

在成员函数中,this指针用于访问当前对象的成员变量。例如:

class Date {
public:
    void Init(int year, int month, int day) {
        this->_year = year; // 使用 this 指针
        this->_month = month;
        this->_day = day;
    }
​
    void Print() {
        cout << this->_year << "/" << this->_month << "/" << this->_day << endl;
    }
​
private:
    int _year;
    int _month;
    int _day;
};
​
int main() {
    Date d1;
    d1.Init(2024, 3, 31);
    d1.Print();
    return 0;
}

在上述代码中,this->_year = year将参数year的值赋给当前对象的_year成员变量。this指针指向调用Init函数的对象(即d1),使得函数能够正确地操作对象的数据

3.2 为什么需要this指针?

this指针在以下情况下特别有用:

  1. 当成员变量和函数参数同名时,使用this可以避免命名冲突。

  2. 在链式调用中,返回*this可以实现对同一对象的连续操作。

class Person {
public:
    Person& SetName(const string& name) {
        this->name = name;
        return *this;
    }
​
    Person& SetAge(int age) {
        this->age = age;
        return *this;
    }
​
    void Display() {
        cout << "Name: "
​
 << name << ", Age: " << age << endl;
    }
​
private:
    string name;
    int age;
};
​
int main() {
    Person p;
    p.SetName("Alice").SetAge(30).Display();
    return 0;
}

在上述示例中,SetNameSetAge函数返回*this,使得可以进行链式调用,即p.SetName("Alice").SetAge(30).Display()

3.3 this指针的限制

this指针是只读的,无法修改其指向。此外,在静态成员函数中无法使用this指针,因为静态成员函数不与任何对象关联。

四、C++和C语言实现Stack的对比

C++和C的区别不仅仅在于语法,而是在编程思想上的转变。C++是面向对象的编程语言,其三大特性为封装、继承和多态。在本节中,我们将通过对比C和C++两种语言的Stack实现来初步了解封装特性的优势。

4.1 C语言实现Stack

在C语言中,Stack的实现需要使用struct来定义栈的数据结构,并通过一系列函数来操作栈。数据和函数是分开的,操作时需要手动传递结构体指针来访问数据。

C语言Stack的代码示例

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>

typedef int STDataType;

typedef struct Stack {
    STDataType* a;
    int top;
    int capacity;
} ST;

// 初始化栈
void STInit(ST* ps) {
    assert(ps);
    ps->a = NULL;
    ps->top = 0;
    ps->capacity = 0;
}

// 销毁栈
void STDestroy(ST* ps) {
    assert(ps);
    free(ps->a);
    ps->a = NULL;
    ps->top = ps->capacity = 0;
}

// 入栈
void STPush(ST* ps, STDataType x) {
    assert(ps);
    // 栈满时扩容
    if (ps->top == ps->capacity) {
        int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
        STDataType* tmp = (STDataType*)realloc(ps->a, newcapacity * sizeof(STDataType));
        if (tmp == NULL) {
            perror("realloc fail");
            return;
        }
        ps->a = tmp;
        ps->capacity = newcapacity;
    }
    ps->a[ps->top] = x;
    ps->top++;
}

// 检查栈是否为空
bool STEmpty(ST* ps) {
    assert(ps);
    return ps->top == 0;
}

// 出栈
void STPop(ST* ps) {
    assert(ps);
    assert(!STEmpty(ps));
    ps->top--;
}

// 获取栈顶元素
STDataType STTop(ST* ps) {
    assert(ps);
    assert(!STEmpty(ps));
    return ps->a[ps->top - 1];
}

// 获取栈的大小
int STSize(ST* ps) {
    assert(ps);
    return ps->top;
}

int main() {
    ST s;
    STInit(&s);
    STPush(&s, 1);
    STPush(&s, 2);
    STPush(&s, 3);
    STPush(&s, 4);
    while (!STEmpty(&s)) {
        printf("%d\n", STTop(&s));
        STPop(&s);
    }
    STDestroy(&s);
    return 0;
}

C语言实现的特点

1.数据与操作分离:数据和操作函数是分开的,需要通过传递结构体指针来操作数据。

2.手动内存管理:程序员需要显式地进行内存分配和释放(使用mallocreallocfree)。

3.没有封装性:所有数据都是公开的,容易被随意修改,缺乏保护机制。

4.2 C++语言实现Stack

在C++中,可以利用类的封装特性将数据和操作结合在一起,使得栈的实现更为简洁和安全。C++通过构造函数和析构函数自动管理内存,无需手动初始化和销毁栈。

C++实现Stack的代码示例

#include <iostream>
#include <cassert>
using namespace std;

typedef int STDataType;

class Stack {
public:
    // 构造函数:初始化栈
    Stack(int n = 4) {
        _a = new STDataType[n];
        _capacity = n;
        _top = 0;
    }

    // 析构函数:释放内存
    ~Stack() {
        delete[] _a;
        _a = nullptr;
    }

    // 入栈
    void Push(STDataType x) {
        if (_top == _capacity) {
            Expand(); // 栈满时扩容
        }
        _a[_top++] = x;
    }

    // 出栈
    void Pop() {
        assert(_top > 0); // 保证栈不为空
        --_top;
    }

    // 获取栈顶元素
    STDataType Top() const {
        assert(_top > 0);
        return _a[_top - 1];
    }

    // 检查栈是否为空
    bool Empty() const {
        return _top == 0;
    }

    // 获取栈的大小
    size_t Size() const {
        return _top;
    }

private:
    STDataType* _a;      // 动态数组存储栈元素
    size_t _capacity;    // 栈的容量
    size_t _top;         // 栈顶指针

    // 辅助函数:扩容栈
    void Expand() {
        size_t newCapacity = _capacity * 2;
        STDataType* newArray = new STDataType[newCapacity];
        for (size_t i = 0; i < _top; ++i) {
            newArray[i] = _a[i];
        }
        delete[] _a;
        _a = newArray;
        _capacity = newCapacity;
    }
};

int main() {
    Stack s;
    s.Push(1);
    s.Push(2);
    s.Push(3);
    s.Push(4);

    cout << "栈顶元素: " << s.Top() << endl; // 输出4
    s.Pop();
    cout << "栈顶元素: " << s.Top() << endl; // 输出3

    cout << "栈的大小: " << s.Size() << endl; // 输出3

    // 继续弹出栈中元素
    while (!s.Empty()) {
        cout << s.Top() << " ";
        s.Pop();
    }
    cout << endl;
    return 0;
}

C++实现的特点

1.数据与操作封装在一起:通过类的封装将数据和操作结合,使得操作更加安全和方便。

2.自动内存管理:利用构造函数和析构函数自动管理内存,无需手动调用初始化和销毁函数。

3.访问控制:可以使用private关键字将类的内部数据隐藏,防止外部直接访问,确保数据安全。

4.代码简洁:操作栈时不需要手动传递结构体指针,成员函数会自动使用this指针访问类的成员。

4.3 C++与C实现的对比总结

  1. 封装性:C++通过类的封装将数据和操作整合在一起,并且可以控制数据的访问权限(publicprivateprotected),从而提高了代码的安全性和可维护性。而在C语言中,所有数据成员都可以被外部随意修改,缺乏数据保护机制。

  2. 内存管理:C++使用构造函数和析构函数来管理资源,防止内存泄漏和资源浪费。而C语言需要手动管理内存,容易出现忘记释放资源的情况。

  3. 操作简便:C++使用面向对象的编程方式,使得操作对象更加直观。成员函数自动使用this指针,代码更加简洁。而在C语言中,操作数据时需要显式传递结构体指针。

  4. 代码扩展性:C++的类支持继承和多态,可以通过继承扩展类的功能,使得代码复用性和扩展性更强。而C语言没有这种机制,只能通过函数指针等手段来模拟多态。

五、总结

本文介绍了C++类与对象的基础知识,包括类的定义、访问限定符、类的作用域、实例化的概念、对象的大小、this指针的使用等内容。通过这些内容,我们初步了解了C++面向对象编程中的封装特性。C++中的类通过封装将数据和操作整合在一起,能够更好地保护数据的安全性并简化操作流程。同时,this指针的使用也为操作对象提供了便利。

尽管C++相较于C语言有诸多优点,但它的面向对象特性还包括继承和多态等内容,这些特性在构建复杂系统时显得尤为重要。后续的博客将深入探讨这些高级特性,帮助大家更好地掌握C++面向对象编程的精髓。希望这篇博客对你有所帮助,欢迎持续关注!

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

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

相关文章

Java毕业设计 基于SpringBoot发卡平台

Java毕业设计 基于SpringBoot发卡平台 这篇博文将介绍一个基于SpringBoot发卡平台&#xff0c;适合用于Java毕业设计。 功能介绍 首页 图片轮播 商品介绍 商品详情 提交订单 文章教程 文章详情 查询订单  查看订单卡密 客服   后台管理 登录 个人信息 修改密码 管…

成都爱尔胡建斌院长讲解年纪大眼花?小心黄斑变性!

中老年朋友觉得年龄增加后&#xff0c;眼睛出现模糊是常态&#xff0c;但是眼花不止“老花眼”一种&#xff0c;要小心的是眼底病变&#xff01; 眼花的形式有很多种&#xff0c;如果视线中间出现暗点视物变得模糊&#xff0c;很难看清周围的人脸&#xff0c;在看书看手机这种…

MATLAB(Octave)混电动力能耗评估

&#x1f3af;要点 处理电动和混动汽车能耗的后向和前向算法模型(simulink)&#xff0c;以及图形函数、后处理函数等实现。构建储能元数据信息&#xff1a;电池标称特性、电池标识符等以及静止、恒定电流和恒定电压等特征阶段。使用电流脉冲或要识别的等效电路模型类型配置阻抗…

jmeter学习(6)逻辑控制器-循环

循环执行 1、循环读取csv文件的值 2、foreach 读取变量&#xff0c;变量数字后缀有序递增&#xff0c;通过counter实现 ${__V(typeId${typeIdNum})} beansell断言 String typeIdNum vars.get("typeIdNum"); String response prev.getResponseDataAsString(); …

MAC 安装HomeBrew-亲自尝试,100%会成功

文章来自这里: https://zhuanlan.zhihu.com/p/620975942 安装指令&#xff1a; /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)"执行完成后&#xff0c;输入下列命令验证 brew --version

AcWing 875:快速幂

【题目来源】https://www.acwing.com/problem/content/877/【题目描述】 给定 组 &#xff0c;对于每组数据&#xff0c;求出 的值。【输入格式】 第一行包含整数 。 接下来 行&#xff0c;每行包含三个整数 。【输出格式】 对于每组数据&#xff0c;输出一个结果&#xff0…

初阶数据结构【3】--单链表(比顺序表还好的一种数据结构!!!)

本章概述 前情回顾单链表实现单链表彩蛋时刻&#xff01;&#xff01;&#xff01; 前情回顾 咱们在上一章博客点击&#xff1a;《顺序表》的末尾&#xff0c;提出了一个问题&#xff0c;讲出了顺序表的缺点——有点浪费空间。所以&#xff0c;为了解决这个问题&#xff0c;我…

计算机网络-RSTP快速生成树基础概念

一、STP概念复习 在之前的学习中我们已经学习了STP的概念与作用。参考文章&#xff1a;计算机网络-生成树基础 STP&#xff08;Spanning Tree Protocol&#xff0c;生成树协议&#xff09; 是一种用于在局域网中消除数据链路层物理环路的协议。主要作用是防止交换机冗余链路产生…

app端文章列表查询-详细教程(上)

app端文章列表查询 一、数据库方面 有关文章的表垂直拆分成了三张表&#xff1a;文章基本信息表&#xff08;字段有文章id、文章作者、文章标题、发布时间等&#xff09;、文章配置表&#xff08;字段有文章id、文章是否可评论、文章可转发、是否已下架、是否已删除等&#x…

MySQL 基础查询

1、DISTINCT select DISTINCT EMPLOYEE_ID ,FIRST_NAME from employees 按照ID去重&#xff0c;DISTINCT的字段要放在前面&#xff0c;不会再继续在FIRST_NAME上去重判断&#xff1b; 如果需要多字段去重&#xff0c;需要用到group by&#xff0c;这个后面讲&#xff1b; …

【Fargo】11: pacing 参数不生效:同步调整采集码率

发送侧参数改变 接收测没感觉到 还是2秒收到60个不变: 果然,发送侧的参数设置没生效 发送的码率终于正确了

【C++、数据结构】二叉排序树(二叉查找树、二叉搜索树)(图解+完整代码)

目录 [⚽1.什么是二叉排序树] [&#x1f3d0;2.构建二叉排序树] [&#x1f3c0;3.二叉排序树的查找操作] [&#x1f94e;4.二叉排序树的删除] [&#x1f3b1;5.完整代码] ⚽1.什么是二叉排序树 二叉搜索树又称二叉排序树&#xff0c;它或者是一棵空树&#xff0c;或者是…

【慕伏白教程】将 Windows11 装进口袋 -- 便携式 Windows 11 制作教程

目录 下载 Windows 11 镜像下载 Rufus开始安装 Windows 11 下载 Windows 11 镜像 打开微软 Windows 11 官方下载网站&#xff0c;找到 下载适用于 x64 设备的 Windows 11 磁盘映像 (ISO) 根据个人情况选择要下载的磁盘镜像&#xff0c;选择多版本 ISO 的话可在安装系统开始时进…

多IP连接

一.关闭防火墙 systemctl stop firewalld setenforce 0 二.挂在mnt mount /dev/sr0 /mnt 三.下载nginx dnf install nginx -y 四.启动nginx协议 systemctl start nginx 五.修改协议 vim /etc/nginx/nginx.conf 在root前加#并且下一行添加 root /www:&#xff08;浏…

基于图像拼接开题报告

选题的背景与意义 在日常生活中&#xff0c;使用普通相机获取宽视野的场景图像时&#xff0c;必须通过调节相机的焦距才可以提取完整的场景。由于相机的分辨率有限&#xff0c;拍摄场景越大&#xff0c;得到的图像分辨率就越低&#xff0c;因此只能通过缩放相机镜头减小拍摄的…

应对 .DevicData-X-XXXXXXXX 勒索病毒:防御与恢复策略

引言 随着信息技术的快速发展&#xff0c;网络安全问题愈发严峻。勒索病毒作为一种恶性网络攻击手段&#xff0c;已成为企业和个人面临的重大威胁之一。尤其是 .DevicData-X-XXXXXXXX 勒索病毒&#xff0c;其通过加密用户数据并勒索赎金&#xff0c;给受害者带来了巨大的经济损…

dolphinscheduler创建工作流及工作流中DataX的使用(简单操作)

一、在项目管理中创建项目&#xff1a;点击创建项目 用哪个用户登录的&#xff0c;所属用户就是哪个&#xff0c;直接输入项目名即可 二、点击项目&#xff0c;在项目中创建工作流&#xff0c;用DataX同步数据 按照图片的步骤依次填写完成&#xff0c;注意 图片中的第九步是写…

个税自然人扣缴客户端数据的备份与恢复(在那个文件夹)

一&#xff0c;软件能够正常打开&#xff0c;软件中的备份与恢复功能 1&#xff0c;备份 您按照下面的方法备份一下哦~ 进入要备份的自然人软件&#xff0c;点击左侧系统设置→→系统管理→→备份恢复&#xff1b; 在备份设置里&#xff0c;点击“备份到选择路径”&#xff0c;…

小白向的源码开发详解:直播带货系统与电商平台搭建指南

本篇文章&#xff0c;笔者将为小白们提供一份详细的源码开发指南&#xff0c;帮助你轻松搭建自己的直播带货系统和电商平台。 一、了解直播带货系统的基本构成 直播带货系统主要由以下几个部分组成&#xff1a; 1.前端界面 2.后端服务器 3.数据库 4.直播平台 二、技术选型…

【C++】— 一篇文章让你认识STL

文章目录 &#x1f335;1.什么是STL&#xff1f;&#x1f335;2.STL的版本&#x1f335;3.STL的六大组件&#x1f335;4.STL的重要性&#x1f335;5. 如何学习STL&#x1f335;6. 学习STL的三种境界 &#x1f335;1.什么是STL&#xff1f; STL是Standard Template Library的简称…