面向对象编程入门:掌握C++类的基础(1/3)

news2024/11/23 15:10:31

面向对象编程(OOP)是一种编程范式,它使用“对象”来设计软件。在C++中,类是创建对象的蓝图。本文将介绍类的基本概念,帮助初学者理解如何在C++中使用类来实现面向对象编程。

​​​​​​​

1. 类的引入

在深入探讨类的引入之前,我们首先回顾一下编程发展的历史。最初,编程是以过程式的方式进行的,即通过一系列的函数或过程来操作数据。这种方式在处理简单问题时非常有效,但随着软件系统变得越来越复杂,过程式编程的局限性开始显现。代码的维护和更新变得困难,因为数据和操作这些数据的函数散布在整个程序中,缺乏组织性。

面向对象编程(OOP)的引入,是为了解决这些问题。OOP通过将数据和操作数据的函数封装在一起,形成一个紧密相关的单元——对象,来提高代码的重用性、灵活性和可维护性。类则是创建这些对象的蓝图

C++ 基于面向对象 的, 关注 的是 对象 ,将一件事情拆分成不同的对象,靠对象之间的交互完
成。
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 << s.Top() << endl;
 s.Destroy();
 return 0;
}

2. 类的定义

类的定义是面向对象编程(OOP)的核心。在C++等面向对象的编程语言中,类不仅仅是数据和方法的集合,它还定义了一种新的数据类型。理解类的定义对于掌握面向对象编程至关重要

如何定义一个类

在C++中,定义一个类涉及到指定类的名称、成员变量(属性)以及成员函数(方法)。这些成员变量和成员函数描述了该类的对象的状态和行为。以下是一个简单的类定义示例:

class MyClass {
public:    // 公有访问修饰符
    // 构造函数
    MyClass() {
        // 初始化代码
    }

    // 公有成员函数
    void myFunction() {
        // 函数实现
    }

private:   // 私有访问修饰符
    int myVariable; // 私有成员变量
};

类的两种定义方式:

1. 声明和定义全部放在类体中,需注意:成员函数如果 在类中定义 ,编译器可能会将其当成
联函数处理。

2. 类声明放在.h文件中,成员函数定义放在.cpp文件中,注意:成员函数名前需要加类名::

同时类就是作用域    
    当进行函数的声明与定义分离时, 我们要进行 ::  域值搜索
    这样才能做到函数分离

原理如下:

在C++中,使用作用域解析运算符::实现函数声明与定义分离的底层原理,涉及到编译器如何处理类的成员函数和如何在内存中表示这些函数。虽然具体的实现细节可能因编译器而异,但基本原理保持一致。以下是一些关键点,用以解释这一过程的底层原理:

1. 名称修饰(Name Mangling)

C++支持函数重载,即允许多个同名函数存在,只要它们的参数列表不同。为了在编译后的代码中区分这些同名函数,编译器进行名称修饰(或称为名称矫正),给函数名添加额外的信息。这些信息可能包括函数所属的类名、函数的参数类型等。因此,即使两个函数原本有相同的名称,经过名称修饰后,它们在编译器内部的表示是不同的。

2. 符号表(Symbol Table)

在编译过程中,编译器会维护一个符号表,记录变量、函数等标识符及其相关信息(如类型、作用域、内存地址等)。当使用作用域解析运算符::定义类的成员函数时,编译器会将该函数与其类的作用域关联起来,并在符号表中进行相应的记录。这确保了函数定义与声明的一致性,并允许编译器正确地解析函数调用。

3. 类的访问限定符及封装

C++中的类使用访问限定符publicprivateprotected来控制成员的访问权限。public成员在任何地方都能被访问,而private成员只能被类的成员函数访问。封装是OOP的一个核心概念,它隐藏了对象的具体实现,只通过一组公开的接口与外界交互。

类的访问限定符和封装是面向对象编程的基石之一。它们共同定义了如何在类的内部和外部访问成员变量和成员函数,从而控制了类的接口和实现的可见性和可访问性。深入理解这些概念对于设计健壮和易于维护的软件系统至关重要。

访问限定符

C++提供了三种访问限定符:publicprivateprotected,它们各自有不同的访问控制级别。

  • public成员:可以被任何外部代码访问。使用这个限定符的成员定义了类的外部接口。

  • private成员:只能被该类的成员函数、友元函数和友元类访问。这是封装的核心,保证了类的内部实现的隐藏和保护。

  • protected成员:可以被该类、派生类(子类)及友元类访问,但不能被其他外部代码直接访问。这个限定符在继承中尤其有用,允许子类访问和修改继承自父类的成员。

【访问限定符说明】
1. public 修饰的成员在类外可以直接被访问
2. protected private 修饰的成员在类外不能直接被访问 ( 此处 protected private 是类似的 )
3. 访问权限 作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
4. 如果后面没有访问限定符,作用域就到 } 即类结束。
5. class 的默认访问权限为 private struct public( 因为 struct 要兼容 C)
注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别

 C++structclass的区别是什么?

解答:C++需要兼容C语言,所以C++struct可以当成结构体使用。另外C++struct还可以用来 定义类。和class定义类是一样的,区别是struct定义的类默认访问权限是publicclass定义的类 默认访问权限是private。注意:在继承和模板参数列表位置,structclass也有区别,后序给大 家介绍

封装的原则和好处

封装不仅仅是限制对类成员的访问,它还涉及到如何组织数据和函数以定义类的行为。良好的封装可以带来以下好处:

  • 数据隐藏:通过将类的内部实现细节(如数据成员)设置为private,可以防止外部代码直接访问和修改这些数据,从而避免意外或恶意的干扰。

  • 接口和实现分离:公有接口(public成员函数)与私有实现(private数据成员和辅助函数)的分离,使得类的使用者和类的开发者能够独立工作,提高了代码的可维护性和灵活性。

  • 减少耦合:封装促进了类的独立性,减少了不同类之间的依赖关系(耦合)。这使得修改一个类的内部实现不会影响到使用该类的其他代码。

  • 增强安全性:通过严格控制对类成员的访问,可以防止数据被意外或恶意修改,增加了程序的稳定性和安全性。

封装的实现技巧

  • 最小权限原则:除非有充分的理由,否则应将类成员的访问权限设置为最严格的级别。通常,数据成员应该是private,而只有那些需要被外部访问的成员函数才设置为public

  • 使用访问函数:对于需要被外部访问的私有数据成员,提供公有的访问函数(如gettersetter)来控制对这些数据的访问,同时可以在这些函数中加入必要的逻辑检查。

  • 友元函数和友元类:当确实需要在类外部访问其私有成员时,可以考虑使用友元函数和友元类。但需谨慎使用,以避免破坏封装性。

4. 类的作用域

类的作用域决定了其中声明的变量和函数的可见性。类的成员函数可以访问同一类中的所有成员,包括私有成员。

在C++中,类的作用域是一个独立的命名空间,它包含了类的所有成员定义(包括变量、函数、类型等)。类的作用域开始于类定义的左花括号{,结束于相应的右花括号}。类内定义的成员可以在整个类作用域内被访问。

成员函数内的作用域

当在类的成员函数内部时,可以直接访问类的其他成员(包括私有成员和保护成员),无需使用任何特殊的前缀。这是因为成员函数自动获得了对类作用域内所有成员的访问权限。例如:

class MyClass {
public:
    void setVal(int val) { this->value = val; }
    int getVal() const { return value; }
private:
    int value;
};

在上述代码中,setValgetVal函数可以直接访问私有成员value,因为它们都在MyClass的作用域内。

静态成员的作用域

静态成员变量和静态成员函数属于类本身,而不是类的任何特定对象。因此,它们在类的所有实例之间共享。静态成员可以通过类名直接访问(即使从类外部),只要它们是公有的。例如:

class MyClass {
public:
    static int staticValue;
    static void printStaticValue() {
        std::cout << staticValue << std::endl;
    }
};

int MyClass::staticValue = 10;

// 从类外部访问静态成员
MyClass::printStaticValue(); // 输出: 10

 

5. 类的实例化

类的实例化是面向对象编程中的一个核心概念。在C++中,实例化是指根据类定义创建对象的过程。类本身只是一个模板,而通过实例化,我们创建了一个具体的实体,这个实体包含了类定义的所有属性和方法。理解类的实例化对于有效使用面向对象的编程范式至关重要。

如何实例化一个类

在C++中,类可以通过多种方式实例化,最直接的方式是在栈上创建对象:

MyClass obj;

这里,MyClass是类名,obj是根据MyClass类模板创建的对象。这种方式会自动调用类的默认构造函数(如果定义了的话)来初始化对象。

使用构造函数实例化

构造函数是一种特殊的成员函数,它在对象被创建时自动调用,用于初始化对象。如果类定义了构造函数,可以在实例化时传递参数给构造函数:

class MyClass {
public:
    MyClass(int a) : value(a) {}
private:
    int value;
};

MyClass obj(10);

 在这个例子中,MyClass有一个接收int类型参数的构造函数,因此在创建obj对象时,我们传递了一个整数值10作为参数。

实例化的内部过程

当一个类的对象被实例化时,C++编译器会在内存中为对象分配空间,足够存储其所有的数据成员。如果对象是在栈上创建的,其生命周期会被限制在声明它的作用域内;如果对象是通过new在堆上创建的,它会一直存在,直到使用delete显式删除。

对于每个非静态成员变量,C++编译器会按照类定义中的顺序初始化它们。如果成员变量是对象(即类的实例),则会调用相应的构造函数进行初始化。对于静态成员变量,它们在程序启动时初始化,直到程序终止

 

6. 类的对象大小的计算

类的对象大小取决于其非静态数据成员的大小。编译器可能会为了内存对齐而调整对象的实际大小,这可能导致空间的额外占用。

在C++中,类的对象大小是一个重要的概念,它直接关系到内存的使用效率。类的对象大小不仅取决于其成员变量的类型和数量,还受到编译器的内存对齐规则的影响。了解如何计算类的对象大小以及哪些因素会影响这个大小,对于优化程序性能和内存使用至关重要。

基本原则​​​​​​​

  1. 非静态数据成员:类的对象大小主要由其非静态数据成员的大小决定。静态数据成员不计入对象大小,因为静态成员属于类本身,而不是类的任何特定对象。

  2. 空类的大小:在C++中,一个空类的对象大小为1字节。这是为了确保同一个类的两个不同对象在内存中有不同的地址。

  3. 继承:如果一个类是从其他类继承来的,那么子类对象的大小至少等于所有基类对象大小的总和,加上子类自己的非静态成员变量大小。如果有虚继承,情况会更复杂。

  4. 虚函数:如果一个类有虚函数,那么每个对象中都会有一个指向虚函数表(vtable)的指针。这个指针的大小通常是一个指针的大小(在32位系统上是4字节,在64位系统上是8字节),但具体大小取决于平台。

    结论:一个类的大小,实际就是该类中 成员变量 之和,当然要注意内存对齐
    注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类的对象

 

7.3 结构体内存对齐规则
1. 第一个成员在与结构体偏移量为 0 的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
VS 中默认的对齐数为 8
3. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
【面试题】
1. 结构体怎么对齐? 为什么要进行内存对齐?
2. 如何让结构体按照指定的对齐参数进行对齐?能否按照 3 4 5 即任意字节对齐?
3. 什么是大小端?如何测试某台机器是大端还是小端,有没有遇到过要考虑大小端的场景

7. 类成员函数的this指针

this指针是一个特殊的指针,它指向调用成员函数的对象本身。this使得成员函数能够访问调用它的对象的成员。在成员函数内部,this指针是隐含的,不需要显式传递。

1. this 指针的类型:类类型 * const ,即成员函数中,不能给 this 指针赋值。
2. 只能在 成员函数 的内部使用
3. this 指针本质上是 成员函数 的形参 ,当对象调用成员函数时,将对象地址作为实参传递给
this 形参。所以 对象中不存储 this 指针
4. this 指针是 成员函数 第一个隐含的指针形参,一般情况由编译器通过 ecx 寄存器自动传
递,不需要用户传递

this指针的基本用法

this指针与对象的关系

通过this指针,成员函数可以访问对象的所有成员,包括私有成员、保护成员和公有成员。这使得成员函数能够操作对象的状态,实现对象的行为。this指针是实现面向对象特性如封装和多态的基础之一。

this指针的底层实现

在C++编译的过程中,this指针作为成员函数的一个隐式参数被传递。对于成员函数的调用,编译器在调用点自动将对象的地址作为this指针传递给函数。这意味着成员函数内部对this的使用实际上是对调用该函数的对象的操作。

  • 访问成员变量:当成员变量与局部变量或参数名称冲突时,可以使用this指针来区分它们。
    class MyClass {
    public:
        MyClass(int value) { this->value = value; }
        int getValue() const { return this->value; }
    
    private:
        int value;
    };
    

    在上述代码中,构造函数和getValue成员函数使用this->value来访问对象的成员变量value,以区别于任何同名的局部变量或参数。

  • 实现链式调用:通过返回对象的引用或指针,this指针可以用于实现链式调用
    class MyClass {
    public:
        MyClass& setValue(int value) {
            this->value = value;
            return *this;
        }
        int getValue() const { return value; }
    
    private:
        int value;
    };
    
    MyClass obj;
    obj.setValue(10).getValue(); // 链式调用
    

    this指针的特性

  • 隐式性this指针是隐式传递给每个非静态成员函数的。它不需要在函数调用时显式提供。

  • 常量性:在常量成员函数中,this指针的类型是指向常量的指针,即const ClassName* const this。这防止了常量成员函数修改对象的状态。

  • 不可赋值this指针是不可赋值的。尝试修改this指针的值会导致编译错误。

通过学习类的这些基础知识,你可以开始在C++中实践面向对象编程。面向对象编程不仅能让代码更加模块化、易于理解和维护,还能提高软件开发的效率和质量。


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

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

相关文章

【专利】专利缴费清单与汇款金额不一致的处理方法

缴纳专利年费时&#xff0c;很容易算错滞纳金。比如有个专利年费滞纳金应交690&#xff0c;结果我算成了660&#xff0c;报给财务转账660。第二天补录缴费信息时&#xff0c;汇款金额660&#xff0c;然后缴费清单填写总是690无法修改&#xff0c;导致无法进行下一步&#xff01…

Python 写网络监控

大家好&#xff01;我是爱摸鱼的小鸿&#xff0c;关注我&#xff0c;收看每期的编程干货。 网络监控是保障网络可靠性的一项重要任务。通过实时监控网络性能&#xff0c;我们可以及时发现异常&#xff0c;迅速采取措施&#xff0c;保障网络畅通无阻。本文将以 Python为工具&…

云性能监控有着至关重要的作用

随着云计算的蓬勃发展&#xff0c;云性能监控成为保障云应用顺畅运行的不可或缺的一环。本文将介绍云性能监控的主要作用&#xff0c;以便更好地理解为什么它对于云环境中的应用程序至关重要。 1. 实时性能反馈 云性能监控提供了对应用程序性能的实时反馈。通过监测关键性能指标…

压缩感知的图像仿真(MATLAB源代码)

压缩感知是一种用于高效获取和表示信号的技术&#xff0c;它可以显著减少数据的采样和传输量&#xff0c;同时保持对信号的高质量恢复能力。在压缩感知中&#xff0c;信号被表示为其在一个稀疏基中的稀疏线性组合。通过仅使用少量的随机投影测量&#xff0c;就能够捕捉信号的大…

区块链之光:揭秘Web3时代的创新契机

随着技术的不断演进&#xff0c;区块链技术正逐渐引领着数字化时代的新潮流。Web3作为区块链技术的核心&#xff0c;为我们带来了前所未有的创新契机。本文将深入揭秘Web3时代的创新契机&#xff0c;探讨区块链之光如何改变我们的世界。 1. 区块链技术的兴起 区块链技术作为一…

springcloud:1.Eureka详细讲解

Eureka 是 Netflix 开源的一个服务注册和发现工具,被广泛应用于微服务架构中。作为微服务架构中的核心组件之一,Eureka 提供了服务注册、发现和失效剔除等功能,帮助构建弹性、高可用的分布式系统。在现代软件开发领域,使用 Eureka 可以有效地管理和监控服务实例,实现服务之…

1660s部署ChatGLM3-6B

用Chatgpt一段时间了&#xff0c;最近在想有没有离线可以装在本机的chatgpt&#xff0c;这样哪天openai把咱们渠道堵得死死的之后&#xff0c;咱们还有东西可用。网上一搜还真有&#xff0c;比如这个ChatGLM3&#xff0c;我用的就是ChatGLM3-6B。 ​​​​​​​官网有详细的部…

鸿蒙视频播放器,主要包括视频获取和视频播放功能:

鸿蒙视频播放器&#xff0c;主要包括视频获取和视频播放功能&#xff1a; 1 获取本地视频或者网络视频。 2 通过media.createAVPlayer创建播放器AVPlayer&#xff0c;然后进行视频播放。 3 通过VideoController进行AVPlayerState的状态管理&#xff0c;如开始&#xff0c;停止&…

Dom节点继承树基本操作

遍历节点树 基于元素节点树的遍历 parcntElcrmcnt -> 返回当前元素的父元秦节点 (IE不兼容)children ->只返回当前元素的元素子节点node.childFlement(ount nodechildren.length当前元素节点的子元素(IE不兼容)firstElementChild->返回的是第一个元素节点(IE不兼容)l…

ONLYOFFICE编辑器升级大揭秘:v8.0版新特性实测与评价

ONLYOFFICE编辑器升级大揭秘&#xff1a;v8.0版新特性实测与评价 一个人简介二前言三操作步骤创建房间我的文档 四开发人员工具应用程序接口Javascript开发工具包插件SDK网络钩子 五测评总结六体验地址 一个人简介 &#x1f3d8;️&#x1f3d8;️个人主页&#xff1a;以山河作…

探索亚信安慧AntDB的数据库管理之道

AntDB数据库管理系统以其卓越的性能和稳定性成为众多用户的选择之一。这款系统采用了影响力大、社区繁荣、开放度高、生态增长迅速的PG内核&#xff0c;为用户提供了稳定可靠的数据库解决方案。 数据库的稳定性对于用户来说至关重要&#xff0c;特别是在面对大规模数据处理和高…

机房预约系统(个人学习笔记黑马学习)

1、机房预约系统需求 1.1系统简介 学校现有几个规格不同的机房&#xff0c;由于使用时经常出现“撞车“现象,现开发一套机房预约系统&#xff0c;解决这一问题。 1.2身份简介 分别有三种身份使用该程序 学生代表:申请使用机房教师:审核学生的预约申请管理员:给学生、教师创建账…

用Jmeter对数据库执行压力测试

&#x1f525; 交流讨论&#xff1a;欢迎加入我们一起学习&#xff01; &#x1f525; 资源分享&#xff1a;耗时200小时精选的「软件测试」资料包 &#x1f525; 教程推荐&#xff1a;火遍全网的《软件测试》教程 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1…

HarmonyOS—LocalStorage:页面级UI状态存储

LocalStorage是页面级的UI状态存储&#xff0c;通过Entry装饰器接收的参数可以在页面内共享同一个LocalStorage实例。LocalStorage也可以在UIAbility实例内&#xff0c;在页面间共享状态。 本文仅介绍LocalStorage使用场景和相关的装饰器&#xff1a;LocalStorageProp和LocalS…

Redis之缓存雪崩问题解决方案

文章目录 一、书接上文二、介绍三、解决方案1. 锁2. 不同的过期时间3. 缓存预热和定时任务 一、书接上文 Redis之缓存穿透问题解决方案实践SpringBoot3Docker 二、介绍 缓存雪崩&#xff0c;指大量的缓存失效&#xff0c;大量的请求又同时落在数据库。主要的一种诱因是key设…

接口请求重试的8种方法

转载文章&#xff1a;https://blog.csdn.net/qq_34162294/article/details/134131439 文章目录 1.前言2.几种方法 2.1循环重试2.2 使用Spring Retry库 2.2.1 添加 Maven 依赖2.2.2 添加 EnableRetry 注解启用重试功能2.2.3 在需要重试的方法上添加 Retryable 注解2.2.4. 还可以…

寒心,成立 25 年的车企迎来了首次裁员

车企 随着春节假期结束&#xff0c;各行各业也正式复工&#xff0c;但车企却未能迎来属于它们的"新年新气象"。 早在年前&#xff08;12 月&#xff09;&#xff0c;就有新闻爆出&#xff0c;知名传统车企「广汽本田」为了加快转向电动车市场&#xff0c;宣布解雇中国…

洛谷 P1075 [NOIP2012 普及组] 质因数分解

参考代码and代码解读 #include<bits/stdc.h>//万能头文件 using namespace std; int main() { int n; cin>>n;//输入这个数 for (int i2;i<n;i)//用for循环查找最小的因数 if (n%i0)//如果i是n的因数 { cout<<n/i…

如何在STM32微控制器中使用外部SDRAM

如何在STM32微控制器中使用外部SDRAM STM32微控制器因其卓越的性能、灵活的内存配置选项以及广泛的应用领域而受到工程师们的青睐。在许多高级应用中&#xff0c;如图像处理、大数据分析和复杂算法运算&#xff0c;内部存储资源可能不足以满足需求。这时&#xff0c;通过添加外…

Centos安装图形化桌面环境

1.使用root远程登录最小化安装的虚拟机 2.执行命令yum groupinstall "X Window System" 这是安装窗口系统 3.执行命令yum grouplist" 检查安装的软件可可以安装的软件 4.执行命令yum groupinstall "Server with GUI" 这是安装图形化界面 5.执行命令sy…