C++类和对象上

news2024/9/28 15:31:02

专栏:C/C++
个人主页:HaiFan.
专栏简介:本章为大家带来C++类和对象相关内容。

类和对象

  • 前言
  • 面向过程和面向对象
  • 类的引入
  • 类的定义
    • 对于类中成员的命名建议
  • 类的访问限定符及封装
    • 访问限定符
    • 封装
  • 类的作用域
  • 类的实例化
  • 如何计算类对象的大小
  • this指针
    • this指针存在哪里?
    • this指针可以为空吗?

前言

面向对象跟函数一样,是比较重要的内容。

那什么是对象呢?

现实生活中的对象:足球,人,手机,等一切物品都可以看作对象

程序中的对象:现实生活中具体的事物

那么把现实中的事物,转换成电脑程序的形式,所以在这里就提到了面向对象的概念

那么面向对象的好处是什么呢?

  1. 灵活性更高,如果代码出现问题,只需要更改出现问题的部分即可
  2. 易维护
  3. 易扩展

面向对象会涉及到:

  1. 对象
  2. 属性
  3. 方法
  4. 等等

对象:张三的手机,王五的手机。

手机就是具体的事物,而手机是对象的集合,那么就可以从对象中提取共同的特征,作为一个类别。

在这里的一个类别是手机类。

那么,能用手机干什么呢?

打电话,接电话,打游戏,刷视频等等,这些都是能用手机干的事。先称之为动作吧

手机还有品牌,颜色,大小,价格之分,这些都是通过特征去展示的。

可以通过具体的事物,来推出手机所具有共同特征和动作。

还可以分为人类。

动作:走,跑等等

特征:性别,年龄,身高,婚否等等

但是,在真正开发的时候,不会把所有的动作都列出来,而是根据需求,需要什么做什么就行了。

在程序中,把特征称为属性,把动作称为方法。

面向过程和面向对象

面向过程和面向对象是两种不同的编程范式。

面向过程编程是一种基于问题解决的编程思想,把问题看做过程或方法的集合,通过把一个大问题分解为一系列子问题的方法来解决它,刻意将问题和数据分开,重点关注数据的操作。面向过程编程中,数据和操作数据的方法是分离的,数据是被单独处理的。

而面向对象编程则是一种基于对象的编程思想,将问题看做对象之间的协作,表示成一个对象的集合,重点关注对象之间的交互和通信,以及对象的属性和方法。面向对象编程中,数据和操作数据的方法是相互关联的,它们被组合成了一个对象。

在面向对象编程中,数据和操作数据的方法总是一起的,它们封装在类的定义中,以提供更好的抽象和封装性。面向对象编程中,重点是把问题抽象成一个模型,并通过完善的类和对象来实现这个模型。

C语言就是面向过程的,关注的是过程的分析。

C++是面向对象的语言,关注的是对象。

类的引入

在C语言中,结构体是一种自定义的数据类型,可以包含不同类型的数据成员,这些数据成员将按照声明的次序在内存中依次存储。结构体可以帮助开发者组织复杂的数据结构,例如,一个由不同数据类型组成的实体对象。以下是一个简单的结构体声明:

struct student {
     int id;
     char name[32];
     float score;
};

在C++中,结构体也是自定义的数据类型,但是具有与类相同的功能。结构体可以包括数据成员、函数成员、继承等功能。在C++中,可以像使用类一样使用结构体,如下所示:

struct Stack
{
	void Init(int capacity)
	{
		return;
	}

	void Push(int data)
	{
		return;
	}

	int capacity;
	int stk[100];
	int _size;

};

类的定义

在C++中,可以通过定义类来创建新的数据类型。类定义了一个对象的特性、行为和方法。类可以包括数据成员、成员函数、构造函数和析构函数等。

那么类是如何定义的呢?

class className
{
	....
};

class关键字用于声明一个新的类。className是类的名称。 {}中 可以声明类的数据成员和成员函数。

{}中的内容称为类的成员,类中变量称为类的属性或成员变量,类中函数称为类的方法或者成员函数

类的两种定义方式

  1. 头文件中定义成员函数,实现文件中实现函数。

在这里插入图片描述

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

在这里插入图片描述

对于类中成员的命名建议

  1. 命名规则:采用驼峰命名法,即成员变量和成员函数的名称首字母小写,每个新单词的首字母大写,而类名和枚举类型的名称首字母大写。

  2. 成员变量命名:在命名成员变量时,建议使用名词来描述它所代表的数据类型,例如使用“m_”前缀来表示成员变量。

  3. 成员函数命名:在命名成员函数时,使用动词来描述它所执行的操作,例如使用“set”前缀来表示设定函数,“get”前缀来表示获取函数等。

  4. 避免使用简单的单词作为成员变量名,例如“i”、“j”等,因为这些名称可能具有多种含义,不易于理解和维护。同样的,避免使用变量名和关键字相同的名称,例如“int”、“float”,否则编译器可能无法识别变量名。

#include <iostream>
#include <cstring>

using namespace std;

class Dog
{
public:
	Dog(const char name[],const char sex[],int age)
	{
		m_name = new char[strlen(name) + 1];
		m_sex = new char[strlen(name) + 1];
		strcpy(m_name, name);
		strcpy(m_sex, sex);
		m_age = age;
	}

	void set_age()
	{
		int age;
		cin >> age;
		m_age = age;
	}

	int get_age()
	{
		return m_age;
	}

	//......

private:
	char* m_name = nullptr;
	char* m_sex = nullptr;
	int m_age = 0;
};

int main()
{
	Dog s("二狗", "公", 2);
	cout << s.get_age() << endl;
	return 0;
}

类的访问限定符及封装

访问限定符

类和对象中有三种限定访问符,分别是公有(public)、私有(private)和受保护的(protected)。它们用于控制类的成员变量和成员函数的访问范围。

  • 公有限定符(public)可以使得类的外部和派生类的成员函数能够直接访问到这些成员,被认为是类的“公共接口”。
  • 私有限定符(private)将类的数据成员和函数封装起来,只有类的成员函数及友元函数能够访问。私有访问符用于实现信息隐藏,确保程序的稳定性和安全性。
  • 受保护的限定符(protected)兼具私有和公有两者的特性,在子类中继承时可以被访问但不能被其他外部类访问,它适合在继承树中作为基类使用,被认为是类的“保护接口”。

默认情况下,如果不指定成员的访问修饰符,默认为 private 。

那么,如何使用呢?

  • public在一个类中,通常将一些对外公开的接口函数和数据成员放在public区域。
  • private区域中一般放置与类定义密切相关,但不希望公开给外界的函数和数据。
  • protected修饰符与private修饰符相似,被声明为protected成员的变量和方法仅限于派生类和其本身内的类成员之间的使用,但是不能被其他类所访问。
#include <iostream>

using namespace std;

class Person
{
public:
	int m_age;  // 声明了一个 public 权限的成员变量
	void eat(); // 声明了一个 public 权限的成员函数

protected:
	char* m_name;  // 声明了一个 protected 权限的成员变量
	void sleep();  // 声明了一个 protected 权限的成员函数

private:
	double m_weight; // 声明了一个 private 权限的成员变量
	void work();     // 声明了一个 private 权限的成员函数
};

m_age和eat函数是公共成员,可以在类外直接被访问

	Person person;
	person.m_age = 19;
	person.eat();

m_name和sleep函数则是保护成员,在类的派生类可以使用,但不能在其他地方进行访问

class Student: public Person {
    void study() {
        m_name = "Bob";  // 可以在派生类中使用
        sleep();         // 可以在派生类中使用
    }
};

Student student;
student.m_name = "Tom";  // 编译错误,无法访问受保护的成员
student.sleep();         // 编译错误,无法访问受保护的成

m_weight 和 work() 是私有成员,只有类本身的成员函数才能访问,外部无法访问到它们。

Person person;
person.m_weight = 60.0;  // 编译错误,无法访问私有成员
person.work();           // 编译错误,无法访问私有成员

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

封装

封装是指将数据和对这些数据的操作封装在一起,形成一个类。封装机制可以隐藏类内部的具体实现细节,只向外部暴露必要的接口,从而保证程序的稳定性和安全性。使用者无需关心类内部具体实现,只需要调用公共接口即可。

以下是一个简单的类和对象实现的栈封装

stack.h文件

#pragma once


#include <iostream>

using namespace std;

typedef int StkDataType;

class Stack
{
private:
	StkDataType* stk;
	int capacity;
	int top;

public:

	void Init(int size = 4);

	void push(StkDataType x);

	bool empty();

	int size();

	void pop();

	void destory();
};

stack.cpp文件

#define _CRT_SECURE_NO_WARNINGS 1

#include "stack.h"

void Stack::Init(int size)
{
	stk = new StkDataType[size];
	top = -1;
	capacity = size;
}

void Stack::push(StkDataType x)
{
	stk[++top] = x;
}

bool Stack::empty()
{
	return capacity == top + 1;
}

int Stack::size()
{
	return top + 1;
}

void Stack::pop()
{
	--top;
}

void Stack::destory()
{
	delete stk;
	capacity = 0;
	top = -1;
}

main.cpp文件

#define _CRT_SECURE_NO_WARNINGS 1


#include "stack.h"

int main()
{
	Stack s;
	s.Init();
	s.push(1);
	s.empty();
	s.size();
	s.pop();
	s.destory();
}

上面的代码,stack类私有成员变量为栈空间stk数组,栈顶元素top和栈容量capacity,公用成员函数包括empty,push等等。

将类定义保存再了头文件stack.h中,将类成员函数的实现写在了stack.cpp中,然后在main.cpp中只需要把栈类的头文件引入一下,在定义一个Stack s对象,就可以使用了。

注:只需要包含头文件即可,不用包含栈类的实现部分,因为在编译的时候需要把stack.cpp文件一起编译成目标文件来生成可执行文件。

类的作用域

类的作用域是指类定义的可见范围。一个类的作用域可以分为两个部分声明和定义。

类的作用域规则与其他作用域规则相同(都是通过 className::成员函数 ),在声明该类的作用域内,类名和成员函数,成员变量的的名称都是可见的,而在定义该类的作用域内,所有成员变量和成员函数都是可见的。

例如,在定义一个类时如果需要引用另一个类,则需要在类定义之前进行声明。另外,在不同的源文件中使用同一个类时,需要包含该类的头文件,以便编译器能够找到该类的定义。

比如上面封装内容里的栈类成员函数的实现文件中,是通过 ::来访问的,这样可以说明这个函数是这个类里的成员函数。

类的实例化

创建对象就是类的实例化

类是一个集合,比如手机类,手机有品牌,大小,颜色,型号之分,把这个公共的特征进行提取,可以封装为一个类。对象可以被看作是集合中的一个元素,是类的一个实例。通过手机类可以创建出多个不同的手机,这些创建出来的手机就是对象。

class Phone
{
private:
	char* m_brand = nullptr;
	double m_price = 0;

public:
	void Init(const char* brand = nullptr, double price = 0)
	{
		m_brand = new char[(strlen(brand) + 1)];
		strcpy(m_brand, brand);
		m_price = price;
	}
};

int main()
{
	Phone a;
	a.Init("C++", 999.99);

	return 0;
}

可以把类理解为一个模板或者蓝图,实例化就是根据这个模板或者蓝图造成了个东西。

比如上面的代码,Phone类当成一个模板,根据这个模板创建出了a对象,a对象能进行初始化,这个初始化是模板中存在的功能,对象创建成功后,也可以使用。

如何计算类对象的大小

C语言中结构体的大小是根据内存对齐规则来计算的,以空间换取时间,而C++兼容C,那么C++的类对象的大小是如何计算的呢?

class MyClass {
public:
    int num1;
    float num2;
    char ch;
    double num3;
};

int main() {
    cout << "Size of MyClass: " << sizeof(MyClass) << endl;
    return 0;
}

------>输出:24

这个代码计算出结构体大小并不算难,只是基本类型的计算和内存对齐。

如果在类中添加上一些成员函数呢?成员函数的大小怎么去计算呢?

class MyClass 
{
public:
    int num1;
    float num2;
    char ch;
    double num3;

    void SetNum()
    {
        num1 = 1;
        num2 = 1.1;
    }
};

依旧------>输出:24

这是因为成员函数不会像成员变量一样被存储在类对象中,相反,它们被存储在代码段中的特定位置,可被所有该类的对象所共享。当执行类的成员函数时,对象只会通过一个指针来调用该函数,这个指针指向代码段中存储该函数的位置。

this指针

this指针是一个关键字,它是一个指向当前对象(调用该成员函数的对象)的指针。当类的成员函数被调用时,系统会将调用函数的对象的地址作为参数传给成员函数,并将该地址存储在this指针中。

class Person
{
public:
	string name;
	int age;

	void Print()
	{
		cout << this << endl;
	}
};

int main()
{
	Person a, b;
	cout << &a << "<=====>";
	a.Print();
	return 0;
}

输出结果:00DBF9F8<=====>00DBF9F8

this就是对象a的地址,通过这个地址去访问Print。


class Person
{
public:
	string name;
	int age;

	void Print()
	{
		cout << "name:" << this->name << endl;
		cout << "age:" << this->age << endl;
	}
};

int main()
{
	Person a, b;
	a.age = 18;
	a.name = "小明";
	a.Print();
	return 0;
}

输出结果:name:小明 age:18

Print成员函数使用this指针访问了a的name和age成员。


注:this 指针是一个常指针,不能被赋值。而且,this 指针只有在非静态成员函数中才有意义,在静态成员函数中不能使用 this 指针。编译器会自动处理成员函数隐含的this指针,不需要用户自己传递。


this指针存在哪里?

this指针指向当前对象的指针,存在于成员函数的局部变量中,其类型与类的类型相同

this指针可以为空吗?

答案是可以为空,但使用的时候要小心,否则会造成程序崩溃。所以必须先确保当前对象是有效的。否则在访问对象的成员时会发生不可预知的错误,比如读取或写入未定义区域的值,导致程序崩溃。

比如下面的代码

class Data
{
public:
	void print()
	{
		cout << 1 << endl;
	}
};

int main()
{
	Data* p = nullptr;
	p->print();
	return 0;
}

这个代码能够正常运行。虽然p是null但是在成员函数print中,并没有任何对this进行的操作。

class Data
{
private:
	int _a;
public:
	void print()
	{
		cout << _a << endl;
	}
};

int main()
{
	Data* p = nullptr;
	p->print();
	return 0;
}

但是这个程序就会造成程序崩溃。

因为在调用 p 指针所指向的对象的 Print() 函数时,由于 p 是空指针,它不指向任何有效的对象,因此会产生未定义的行为,导致程序崩溃。

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

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

相关文章

Web自动化测试——XAPTH高级定位

XAPTH高级定位 一、xpath 基本概念二、xpath 使用场景三、xpath 相对定位的优点四、xpath 定位的调试方法五、xpath 基础语法&#xff08;包含关系&#xff09;六、xpath 顺序关系&#xff08;索引&#xff09;七、xpath 高级用法1、[last()]: 选取最后一个2、[属性名属性值 an…

ESP32设备驱动-PCF8575IO扩展器驱动

PCF8575IO扩展器驱动 文章目录 PCF8575IO扩展器驱动1、PCF8575介绍2、硬件准备3、软件准备4、驱动实现1、PCF8575介绍 PCF8575用于两线双向总线 (I2C) 的 16 位 I/O 扩展器专为 2.5-V 至 5.5-V VCC 操作而设计。 PCF8575 器件通过 I2C 接口 [串行时钟 (SCL)、串行数据 (SDA)]…

flask教程8:模板

文章目录 一、模板与自定义过滤器1 模板2 过滤器转义过滤器讲解 3自定义过滤器 二、表单1表单2表单扩展 三、创建表单模型类与模板使用3.1 表单模型类 四 、使用表单接受并检验参数五、模板宏的使用六 、宏定义在外部的使用七 &#xff1a;模板继承与包含继承包含include 八 、…

PVE 安装 windows10

pve 安装教程大家可以参考视频&#xff1a;pve 安装 pve 安装 Windows10 视频教程&#xff1a;pve 安装Windows10 在安装好 pve 后我们就可以进行虚拟机的安装了。当然我们可以自行决定是否有必要进行 win10 的安装。 准备工作 1. 下载 win10 镜像文件&#xff1a;https://…

数据结构与算法基础(王卓)(35):交换排序之快排【第二阶段:标准答案、初步发现问题】

目录 第二阶段&#xff1a;一分为二 整个快排算法的程序运行大框架&#xff1a; 做出的改动&#xff08;和原来程序的区别&#xff09;&#xff1a; Project 1: PPT标准答案&#xff1a; Project 1小问题&#xff1a; Project 1还存在着一个巨大的问题&#xff1a; 具体问…

嵌入式软考备考_8 软件测试

软件测试 测试&#xff1a;在规定的条件下操作程序&#xff0c;以发现错误&#xff0c;对软件质量进行评估。 对象&#xff1a;程序&#xff0c;数据&#xff0c;文档。 目的&#xff1a;发现错误&#xff0c;看是否满足用户需求&#xff0c;发现错误产生的原因&#xff08;…

汇编四、51单片机汇编指令2

1、机器码 (1)MOV A,#0x60对应机器码为7460 (2)7460对应二进制 0111 0100 0110 0000 0x74对应指令&#xff0c;0x60对应立即数。 (3)immediate data翻译为立即数。 (4)可人为查表把汇编转为机器码&#xff0c;也可通过编译器把汇编转为机器码。 2、汇编常见缩写 (1)Rn: n可…

leetcode-040-组合总和2

题目及测试 package pid040; /* 40. 组合总和 II 给定一个候选人编号的集合 candidates 和一个目标数 target &#xff0c;找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的每个数字在每个组合中只能使用 一次 。注意&#xff1a;解集不能包含重复的组合…

Vue中使用EasyPlayer播放H265视频流

需求说明 需要在Vue2的项目中使用EasyPlayer进行H265视频流的播放。使用官方的最新版本加载H265会有问题。一直处于加载中… 实现步骤 引入easyplayer,这里最开始引入了最新版会有问题&#xff0c;因此引入的是3.3.12版本&#xff0c;可参照官方文档进行配置。 EasyPlayer示…

HBase整合Phoenix

HBase整合Phoenix 创建软件目录 mkdir -p /opt/soft cd /opt/soft下载软件 wget https://dlcdn.apache.org/phoenix/phoenix-5.1.3/phoenix-hbase-2.5-5.1.3-bin.tar.gz解压 hbase tar -zxvf phoenix-hbase-2.5-5.1.3-bin.tar.gz修改 hbase 目录名称 mv phoenix-hbase-2.5…

(初)进程概念

目录 认识冯诺依曼系统 操作系统(Operator System) 设计OS的目的&#xff1a; 定位&#xff1a; 如何理解管理&#xff1a; 总结&#xff1a; 系统调用和库函数概念&#xff1a; 进程 基本概念 &#xff1a; 描述进程PCB task_struct - PCB的一种 task_struct内容分…

编译安装最新的Linux系统内核

现在还有不少机器是CentOS8 Stream系统&#xff0c;虽然上了贼船&#xff0c;不影响用就是了。8的编译和7大同小异&#xff0c;只是踩了更多的坑在这里记录一下&#xff0c;或许会帮到看到的朋友。 安装编译环境 CentOS8安装必要的包 yum groupinstall "Development Too…

【P13】JMeter 常数吞吐量定时器(Constant Throughput Timer)

文章目录 1、基于计算吞吐量&#xff1a;只有此线程2、基于计算吞吐量&#xff1a;所有活动线程3、基于计算吞吐量&#xff1a;当前线程组中的所有活动线程4、基于计算吞吐量&#xff1a;所有活动线程&#xff08;共享&#xff09;5、基于计算吞吐量&#xff1a;当前线程组中的…

【2023/05/08】雅卡尔织布机

Hello&#xff01;大家好&#xff0c;我是霜淮子&#xff0c;2023倒计时第3天。 Share The world puts off its mask of vastness to its lover. It becomes small as one song,as one kiss of the eternal. 译文&#xff1a; 世界对着它的爱人&#xff0c;把它浩瀚的面具揭…

已做过算法题总结2

20. 有效的括号 (括号匹配是使用栈解决的经典问题&#xff0c;这道题主要是记住三种不成立的情况) 给定一个只包括 (&#xff0c;)&#xff0c;{&#xff0c;}&#xff0c;[&#xff0c;] 的字符串&#xff0c;判断字符串是否有效。 有效字符串需满足&#xff1a; 左括号必须用…

解密链表元素移除:三种巧妙思路,轻松驱逐难缠结点

本篇博客会讲解力扣“203. 移除链表元素”的解题思路&#xff0c;这是题目链接。 老规矩&#xff0c;先来审题&#xff1a; 以下是一些示例&#xff1a; 以下是提示&#xff1a; 本题的思路还挺多的&#xff0c;不过都是链表的常规操作。 思路1 万能的尾插法。遍历链表&am…

[Golang] 爬虫实战-用多层嵌套结构体去接收多层嵌套数据

&#x1f61a;一个不甘平凡的普通人&#xff0c;致力于为Golang社区和算法学习做出贡献&#xff0c;期待您的关注和认可&#xff0c;陪您一起学习打卡&#xff01;&#xff01;&#xff01;&#x1f618;&#x1f618;&#x1f618; &#x1f917;专栏&#xff1a;算法学习 &am…

量子计算(10)编程实践2:隐形传态算法

目录 一、算法目的 二、算法原理 三、pyqpanda实现代码 四、测试结果 一、算法目的 量子隐形传态&#xff0c;又称量子遥传、量子隐形传输、量子隐形传送、量子远距传输或量子远传&#xff0c;是一种利用分散量子缠结与一些物理讯息的转换来传送量子态至任意距离的位置的技…

AutoSar CAN网络管理(CanNm)

文章目录 网络管理目的主动唤醒和被动唤醒状态管理1. 总线睡眠模式&#xff08;Bus-Sleep Mode&#xff09;2. 准备总线睡眠模式&#xff08;Prepare Bus-Sleep Mode&#xff09;3. 网络模式&#xff08;Network Mode&#xff09;3.1 重复报文状态(RepeatMessageState)3.2 常规…

基于springboot+mysql+jpa+html实现商品销售信息系统

基于springbootmysqljpahtml实现商品销售信息系统 一、系统介绍1、系统主要功能&#xff1a;2.涉及技术框架&#xff1a;3.本项目所用环境&#xff1a; 二、功能展示三、其它系统四、获取源码 一、系统介绍 1、系统主要功能&#xff1a; 订单管理模块 商品管理模块 品牌管理模…