【C++】类和对象详解(类的使用,this指针)

news2024/10/2 20:34:10

文章目录

  • 前言
  • 面向过程和面向对象的初步认识
  • 类的引入
  • 类的定义
  • 类的访问限定符和封装性
    • 访问限定符
    • 封装性
  • 类的作用域
  • 类的实例化
  • 类对象模型
    • 如何计算类对象的大小
    • 类对象的存储方式猜测
    • 结构体内存对齐规则
  • this指针
    • this指针的引出
    • this指针的特性
  • 总结


前言

提示:这里可以添加本文要记录的大概内容:

在计算机编程领域,程序设计的方法论不断演化,从最初的面向过程到如今更为强大而灵活的面向对象。本文将深入探讨C++中关于类和对象的概念,为读者提供对面向对象编程的深刻理解。

提示:以下是本篇文章正文内容,下面案例可供参考

面向过程和面向对象的初步认识

在计算机编程的世界中,面向过程和面向对象是两种不同的编程范式,它们反映了程序设计的不同思想和方式。以下是对这两种范式的初步认识:

面向过程:

面向过程编程是一种以过程为中心的编程思想。在这种范式中,程序被看作一系列的函数或过程的集合,这些函数按照一定的次序依次执行,完成特定的任务。程序的执行流程主要由函数的调用关系来控制。

主要特点包括:

  • 程序的控制流程是线性的,从上到下逐行执行。
  • 重点在于解决问题的步骤和流程,以完成特定的任务。
  • 数据和函数分离,强调过程的执行。

面向对象:

面向对象编程是一种以对象为中心的编程思想。在这种范式中,程序被组织成一组对象,每个对象包含数据和相关的操作,通过这些对象之间的交互来完成任务。

主要特点包括:

  • 程序的控制流程不再是线性的,而是由对象之间的消息传递和方法调用构成。
  • 重点在于组织和管理对象,强调数据的封装和对象的行为。
  • 对象可以被看作是现实世界中的实体,有着状态和行为。

对比和选择:

面向过程和面向对象各有优缺点,选择合适的范式取决于问题的性质和解决方案的需求。面向过程适用于简单的、线性的问题,而面向对象更适用于复杂的、模块化的系统设计。

在实际开发中,很多编程语言提供了同时支持面向过程和面向对象的特性,如C++、Java等,使得程序员可以根据具体情况选择合适的编程方式。这也说明了在面向对象编程的潮流中,面向过程并没有被淘汰,而是与面向对象共同存在,相互补充,为编程提供了更灵活的选择。

类的引入

在c语言中,结构体内只能定义变量,在c++中,结构体内不仅可以定义变量,还可以定义函数

#include <iostream>
#include <cstring>  // 添加头文件以使用字符串库函数
using namespace std;

struct stu
{
    void getName()
    {
        cout << name << endl;
    }
    int age;
    int num;
    char name[20];
};

int main()
{
    struct stu s1;
    strcpy(s1.name, "dzj");  // 使用strcpy将字符串赋值给字符数组
    s1.getName();
    return 0;
}

但是在C++中,上述结构体的定义,更喜欢用class来代替

注意:C语言结构体中不能定义成员函数,C++兼容C的语法,并进行拓展,可以定义成员函数

类的定义

类名:由成员函数和成员变量组成,同时注意分号

class className
{
	//类名:由成员函数和成员变量组成
}

class是定义类的关键字,className是类的名称,{}中类的主体同时注意类定义结束时后面的分号。类中的元素是类的成员:类中的数据称为类的属性或者成员变量;类中的函数称为类的方法或者成员函数
类的两种定义方式:

  • 声明和定义全部放在类体中,需要注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理。
class person
{
public:
    void print()
    {
        cout << _age << _name << _weight << endl;
    }
private:
    int _age;
    char _name[10];
    int _weight;
};
  • 声明放在.h中,类的定义放在.cpp中

在这里插入图片描述
一般情况下,一般采用第二种定义和声明分离的方式,但是为了方便,在平时的练习时,可以采用第一种方式(声明和定义全部放在类体中)

补充:声明和定义的区别是什么?
声明是一种承诺,承诺要干什么,但是还没做,定义就是把这个事情执行了或者落实了

类的访问限定符和封装性

C++实现封装的方式:用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用。

访问限定符

  1. public(公有)
  2. protected(保护)
  3. private(私有)

【访问限定符说明】

  • public修饰的成员在类外可以直接被访问
  • protected修饰的成员类的内部和派生类的成员可以访问,但在类的外部无法直接访问.
  • private修饰的成员只能在本类中访问,在类外不能直接访问
  • 访问权限的作用域从该访问限定符出现的位置直到下一个访问限定符出现为止
  • class的默认访问权限是private,但是struct是public(因为struct要兼容c语言)

注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别

C++中struct和class的区别是什么?
C++需要兼容C语言,所以C++中struct可以当成结构体去使用。另外C++中struct还可以用来定义类。
和class是定义类是一样的,区别是struct的成员默认访问方式是public,class的成员默认访问方式是private。

封装性

面向对象的三大特性:封装、继承、多态。
在类和对象阶段,我们只研究类的封装特性,那什么是封装呢?
封装是面向对象编程(OOP)中的一个重要概念,它指的是将数据(变量)和操作数据的方法(函数)捆绑在一起的一种机制。封装有助于隐藏对象的内部实现细节,同时提供一组公共接口供外部使用。这样,对象的内部状态和行为对外部来说是不可见的,只有通过公共接口才能与对象进行交互。

封装的主要目的有以下几点:

  1. 数据隐藏: 将对象的实现细节隐藏起来,防止外部直接访问对象的内部数据,从而提高数据的安全性。

  2. 实现细节隔离: 允许对象实现者修改对象的内部实现细节而不影响外部使用者的代码。外部代码只需要关心对象的接口,而不必关心其具体实现。

  3. 代码组织: 将数据和操作数据的方法封装在一个单元中,使得代码更加模块化、可维护性更强。

在面向对象的语言中,封装通过类来实现。类封装了数据成员和成员函数,成员函数提供了访问和操作数据的接口。访问控制修饰符(如public、private、protected)用于控制成员的可见性和访问权限,实现封装的关键。

示例:

#include <iostream>

class Car {
private:
    // 私有数据成员
    int speed;

public:
    // 公有成员函数
    void setSpeed(int s) {
        if (s >= 0) {
            speed = s;
        }
    }

    int getSpeed() const {
        return speed;
    }
};

int main() {
    Car myCar;

    // 使用公有接口设置速度
    myCar.setSpeed(60);

    // 使用公有接口获取速度
    std::cout << "Current Speed: " << myCar.getSpeed() << " km/h" << std::endl;

    return 0;
}

在这个例子中,Car 类封装了一个私有的速度成员,并提供了公有的 setSpeedgetSpeed 方法来设置和获取速度。外部代码通过这些公有接口与 Car 对象进行交互,而不需要了解具体的实现细节。这就体现了封装的思想。

类的作用域

类定义了一个新的作用域,类的所有成员都在类的作用域中。在类外面定义成员,需要使用::作用域解析符来指明属于哪个类域

class person
{
public:
    void print();
private:
    int _age;
    char _name[10];
    int _weight;
};
void person::print()
{
    cout << _age << _name<<_weight << endl;
}

类的实例化

类的实例化指的是创建类的对象。在面向对象编程中,类定义了一种数据类型,而实例化则是根据这个数据类型创建具体的对象。对象是类的一个实例,具有类定义的属性和行为。

下面是一个简单的C++示例,演示了如何定义一个类并实例化对象:

#include <iostream>
#include <string>

// 定义一个简单的Person类
class Person {
public:
    // 公有成员函数,用于设置和获取姓名
    void setName(const std::string& n) {
        name = n;
    }

    std::string getName() const {
        return name;
    }

private:
    // 私有数据成员,姓名
    std::string name;
};

int main() {
    // 实例化Person类的对象
    Person person1;

    // 使用公有接口设置姓名
    person1.setName("Alice");

    // 使用公有接口获取姓名
    std::cout << "Person's Name: " << person1.getName() << std::endl;

    // 实例化另一个Person类的对象
    Person person2;

    // 使用公有接口设置姓名
    person2.setName("Bob");

    // 使用公有接口获取姓名
    std::cout << "Person's Name: " << person2.getName() << std::endl;

    return 0;
}

在这个例子中,Person 类定义了一个私有数据成员 name 和公有的成员函数 setNamegetName。通过实例化两个 Person 对象 person1person2,分别设置和获取了它们的姓名。

实例化是面向对象编程中的重要概念,它允许我们根据类的定义创建多个对象,每个对象都拥有自己的状态和行为。

类对象模型

如何计算类对象的大小

class A
{
public:
 	void PrintA()
 {
	 cout<<_a<<endl;
 }
private:
	 char _a;
};

问题:类中既可以有成员变量,又可以有成员函数,那么一个类的对象中包含了什么?如何计算一个类的大小?

答:对象中只存储成员变量,不存储成员函数

类对象的存储方式猜测

  • 对象中包含类中的各个成员(成员函数和成员变量)
    事实上,这样是不合理的,每个对象中成员变量是不同的,但是调用的却是同一个函数,如果按照这种方式存储,当一个类创建多个对象的时候,每个对象中都会保存一份代码,相同的代码保存了多次,浪费空间。
  • 只保存成员变量,成员函数保存在公共的代码段
    图解:
    在这里插入图片描述
    显然计算机采用的是第二种存储方式(类中对象仅仅存储成员变量)

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

结构体内存对齐规则

关于内存对齐可以看这篇博客

this指针

this指针的引出

#include <iostream>
using namespace std;
class Date
{
public:
	void setDate(int year,int month,int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void getInfo()
	{
		cout << _year << endl;
		cout << _month << endl;
		cout << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;
	Date d2;
	d1.setDate(2024, 1, 6);
	d1.getInfo();
	return 0;
}

问题:在上述代码中,有两个成员函数setDate和getInfo,函数体中没有关于类的划分,那么程序是怎么知道d1.setDate(2024, 1, 6)设置的就是d1的空间,而不是d2的空间

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

图解
在这里插入图片描述
这里编译器偷偷帮我们做了处理,但是我们自己并不知道,即在成员函数第一个参数添加 类名*this,并在函数调用的时候传递了对象的地址。

#include <iostream>
using namespace std;
class Date
{
public:
	void setDate(int year,int month,int day)//隐含的this指针Date* this
	{
		this->_year = year;
		this->_month = month;
		this->_day = day;
	}
	void getInfo()
	{
		cout << _year << endl;
		cout << _month << endl;
		cout << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;
	Date d2;
	d1.setDate(2024, 1, 6);//隐含的传递对象地址 d1.setDate(&d1,2024, 1, 6)
	d1.getInfo();
	return 0;
}

this指针的特性

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

this指针存储在哪里?

上面说到this指针本质是成员函数的第一个形参,那么肯定存放在进程地址空间中的栈区,但是在vs2022下是存放在ecx寄存器中,不同编译器和环境下可能会有差异

this指针可以为空吗?

通过一段代码来说明

#include <iostream>
using namespace std;
class A
{
public:
	void PrintA()
	{
		cout << _a << endl;
	}

	void Show()
	{
		cout << "Show()" << endl;
	}
private:
	int _a;
};
int main()
{
	A* p = nullptr;
	//p->PrintA();
	p->Show();
}

上面代码中,p->PrintA()会发生程序崩溃,但是p->Show()可以正常运行。因为在PrintA()中发生了隐含的this指针的解引用操作(对空指针的解引用),但是在Show()并没有解引用所以程序没问题

总结

通过本文的学习,我们深入了解了C++中类和对象的方方面面,从面向过程的初步认识到类的引入和定义,再到类的作用域、实例化、访问限定符及封装,以及类对象大小的计算和类成员函数的this指针。这些概念和技术不仅为我们提供了更灵活的程序设计手段,也为构建更为可维护和可扩展的软件系统奠定了基础。在今后的编程学习和实践中,希望读者能够充分运用这些知识,编写出更高效、健壮的C++程序。面向对象编程不仅仅是一种技术,更是一种思想,期待读者通过不断实践,能够更深刻地理解和运用面向对象的优秀编程范式。

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

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

相关文章

【DevOps-07-3】Jenkins集成Sonarqube

一、简要说明 Jenkins安装Sonarqube插件Jenkins安装和配置Sonar-Scanner信息Jenkins打包项目中,增加Sonar-Scanner代码质量扫描二、Jenkins安装Sonarqube插件 1、登录Jenkins管理后台,搜索安装Sonar-Scanner插件 Jenkins管理后台示例:http://192.168.95.131:8080/jenkins/

MySQL之视图内连接、外连接、子查询案例

目录 一.视图 1.1 含义 1.2 操作 二.案例 三.思维导图 一.视图 1.1 含义 虚拟表&#xff0c;查询方面和普通表一样使用。 1.2 操作 1.创建视图&#xff1a; create or replace view 视图名 as 查询语句&#xff1b; 2.视图的修改&#xff1a; 方式1 create or replace view …

HttpRunner的测试用例分层机制

测试用例分层介绍&#xff1a; 在接口自动化测试维护过程中&#xff0c;由于测试用例的增加和需求变更导致测试用例的调整&#xff0c;使自动化测试用例的维护非常麻烦&#xff0c;直接关系到自动化测试能否持续有效地在项目中开展。 概括来说&#xff0c;测试用例分层机制的核…

性能优化-OpenMP基础教程(三)

本文主要介绍OpenMP并行编程的环境变量和实战、主要对比理解嵌套并行的效果。 &#x1f3ac;个人简介&#xff1a;一个全栈工程师的升级之路&#xff01; &#x1f4cb;个人专栏&#xff1a;高性能&#xff08;HPC&#xff09;开发基础教程 &#x1f380;CSDN主页 发狂的小花 &…

MiniTab的宏基础知识

什么是宏&#xff1f; 宏是包含一系列 Minitab 会话命令的文本文件。可以使用宏自动执行重复性任务&#xff08;例如&#xff0c;生成月度报表&#xff09;或扩展 Minitab 的功能&#xff08;例如&#xff0c;计算特殊检验统计量&#xff09;。 Minitab 提供以下类型的宏&…

JetBrains Rider使用总结

简介&#xff1a; JetBrains Rider 诞生于2016年&#xff0c;一款适配于游戏开发人员&#xff0c;是JetBrains旗下一款非常年轻的跨平台 .NET IDE。目前支持包括.NET 桌面应用、服务和库、Unity 和 Unreal Engine 游戏、Xamarin 、ASP.NET 和 ASP.NET Core web 等多种应用程序…

IOS:Safari无法播放MP4(H.264编码)

一、问题描述 MP4使用H.264编码通常具有良好的兼容性&#xff0c;因为H.264是一种广泛支持的视频编码标准。它可以在许多设备和平台上播放&#xff0c;包括电脑、移动设备和流媒体设备。 使用caniuse查询H.264兼容性&#xff0c;看似确实具有良好的兼容性&#xff1a; 然而…

C#,入门教程(09)——运算符的基础知识

上一篇&#xff1a; C#&#xff0c;入门教程(08)——基本数据类型及使用的基础知识https://blog.csdn.net/beijinghorn/article/details/123906998 一、算术运算符号 算术运算符号包括&#xff1a;四则运算 加 , 减-, 乘*, 除/与取模%。 // 加法&#xff0c;运算 int va 1 …

海外分支访问国内服务器系统慢怎么办?

在全球业务不断扩张的今天&#xff0c;企业面临着海外分支访问国内总部服务器系统慢的问题。为了解决这一挑战&#xff0c;我们引入了lxway全球系统专网产品&#xff0c;为企业提供高效、安全的全球网络连接方案。通过解析技术瓶颈和专网的优势&#xff0c;本文将揭示如何借助先…

Linux第10步_通过终端挂载和卸载U盘

学习完“通过终端查看U盘文件”后&#xff0c;我们需要接着学习“通过终端挂载和卸载U盘”。主要是挂载U盘&#xff0c;它的用处很大&#xff0c;目的是通过命令来访问U盘。由于U盘的名字有很多种&#xff0c;为了便于访问&#xff0c;我们把将U盘的第一分区挂载到udisk目录下&…

Library Genesis (创世纪图书馆)最新镜像地址,可直接访问!不逊于 Z-Library 的优质电子书库

Library Genesis是一个提供免费访问数百万篇学术论文、书籍和其他研究资料的网站。它由一群图书馆员于1999年创立&#xff0c;并已成为全球学生、研究人员和学者最受欢迎的在线资源之一。 该网站提供各种材料&#xff0c;包括同行评审的期刊文章、会议论文、学位论文、论文、教…

【JAVA】volatile 关键字的作用

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a; JAVA ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 前言 正文 volatile 的作用&#xff1a; 结语 我的其他博客 前言 在多线程编程中&#xff0c;保障数据的一致性和线程之间的可见性是…

Python基础入门第八课笔记(自定义函数 lambda)

什么时候用lambda表达式&#xff1f; 当函数有一个返回值&#xff0c;且只有一句代码&#xff0c;可以用lambda简写。 2、lanbda语法 lambda 形参 : 表达式 注意&#xff1a; 1、形参可以省略&#xff0c;函数的参数在lambda中也适用 2、lambda函数能接收任何数量的参数但只能…

CSS基础笔记-03选择器

CSS基础笔记系列 《CSS基础笔记-01CSS概述》《CSS基础笔记-02动画》 前言 在前面两篇博客中&#xff0c;我实际上已经使用过了选择器。但到底什么是选择器、有什么作用&#xff0c;我反而不能表达出来。因此&#xff0c;决定记录了我的学习和思考。 什么是选择器 selector…

Joplin配合teracloud进行多版本客户端分别笔记同步

最近瞎搜索joplin&#xff0c;意外在github上搜到plugins&#xff0c;插件仓库&#xff0c;里面有一个思维导图的插件我还是蛮喜欢的&#xff0c;结果下载后安装发现&#xff0c;我当前的Jopin的版本如下 &#xff08;Joplin 2.7.15 (prod, win32) 同步版本: 3 配置文件版本: 4…

代码随想录day21 二叉搜索树进阶

530.二叉搜索树的最小绝对差 题目 给你一棵所有节点为非负值的二叉搜索树&#xff0c;请你计算树中任意两节点的差的绝对值的最小值。 示例&#xff1a; 思考 本题有一种笨办法&#xff0c;就是把二叉树的所有结点都存到一个vector里&#xff0c;因为二叉搜索树是左中右排序…

Typora 编辑器 讲解 包括使用方式 快捷键 附带下载地址 (免费破解)

CSDN 成就一亿技术人&#xff01; 今天来讲一下很好用的编辑器 Typora CSDN 成就一亿技术人&#xff01; 什么是Typora&#xff1f; 它是一个 Markdown 编辑器和阅读器&#xff0c;这意味着您可以使用简单的格式代码 &#xff08;Markdown&#xff09;是一种轻量级标记语言&…

MYSQL - SQL优化

插入数据优化 小批量数据 批量插入 最好插入500-1000条比较好 手动提交事务 主键顺序插入 大批量插入数据 主键优化 页分裂 页合并 主键优化设计原则 order by优化 group by优化 limit优化 count优化 count(1)里面不一定必须1&#xff0c;数字都可以 update优化 更新字…

ElasticSearch自定义算分排序(Function Score Query)

使用 function score query&#xff0c;可以修改文档的相关性算分 (query score)&#xff0c;根据新得到的算分排序。 目录 Function Score Query 案例 Function Score Query 几种默认的计算分值的函数&#xff1a; Weight:为每一个文档设置一个简单而不被规范化的权重Fie…

[C]jupyter中使用C

[C]jupyter中使用C 安装使用用处 安装 https://github.com/brendan-rius/jupyter-c-kernel 下拉找到3条命令&#xff0c;装就可以了 mac和linux可用 python3可用&#xff0c; 2不可以 第二条命令可以改为 : python3 install_c_kernel 小总结&#xff1a;如果有问题&#xff0…