【C++】C++入门 类与对象(一)

news2025/1/18 9:07:56

类与对象(一)

  • 一、类的引入
  • 二、类的定义
    • 1、类的两种定义方式:
    • 2、成员变量命名规则的建议:
  • 三、类的访问限定符及封装
    • 1、访问限定符
    • 2、封装
  • 四、类的实例化
    • 1、类的实例化概念
    • 2、类对象的大小的计算
  • 五、this指针
    • this指针的特性


一、类的引入

在C语言结构体中只能定义变量,但是在C++中,结构体内不仅可以定义变量,也可以定义函数。比如:

#include<iostream>
#include<stdlib.h>
using namespace std;
typedef int DataType;
struct Stack
{
	void Init(int _capacity=4)//缺省参数
	{
		DataType* tmp = (DataType*)malloc(sizeof(DataType) * _capacity);
		if (nullptr == tmp)
		{
			perror("malloc fail:");
			exit(-1);
		}
		_a = tmp;
		_Top = 0;
		_capacity = _capacity;
	}
	DataType* _a;
	int _Top;
	int _capacity;
}; 
int main()
{
	Stack s1;         //C++中struct定义的结构创建变量时不需要加struct
	s1.Init(10);
	s1._Top = 100;
	s1._capacity = 200;
	return 0;
}

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

二、类的定义

类的定义格式为:

class className
{
// 类体:由成员函数和成员变量组成
}; // 一定要注意后面的分号

class为定义类的关键字,ClassName为类的名字,{}中为类的主体,注意类定义结束时后面分号不能省略
类体中内容称为类的成员:类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者成员函数

1、类的两种定义方式:

  1. 声明和定义全部放在类体中(如类的引入中的代码),需注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理。
  2. 类声明放在.h文件中,成员函数定义放在.cpp文件中,注意:类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员时,需要使用 ::作用域操作符指明成员属于哪个类域,因此成员函数名前需要加类名 ::,一般情况下,更期望采用此种方式。

头文件:Test.h中
在这里插入图片描述
函数实现:Test.cpp中

在这里插入图片描述
主函数文件:2023.02.07.cpp中

在这里插入图片描述

2、成员变量命名规则的建议:

先看一个错误示范:

class Date
{
	void Init(int year)
	{
		// 这里的year到底是成员变量,还是函数形参?
		year = year;
	}
	int year;
};

实际上 = 号两边都是形参,因为局部变量优先,这里我们可以看出命名的重要性,为了避免与形参混淆,我们通常对成员变量前加一个"_"符号。

class Date
{
	void Init(int year)
	{
		_year = year;
	}
	int _year;
};

三、类的访问限定符及封装

1、访问限定符

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

class Stack
{
public:
	void Init(int _capacity = 4);
private:
	DataType* _a;
	int _Top;
	int _capacity;
};

【访问限定符说明】

  1. public修饰的成员在类外可以直接被访问
  2. protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)
  3. 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
  4. 如果后面没有访问限定符,作用域就到} 即类结束。
  5. class的默认访问权限为private,struct为public(因为struct要兼容C,C的struct是公有的)

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

#include"Test.h"
int main()
{
	Stack s1;
	s1.Init(10);//编译成功
	s1._Top = 100;//编译报错,无法访问
	return 0;
}

在这里插入图片描述

【面试题】
问题:C++中struct和class的区别是什么?

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

2、封装

面向对象的三大特性:封装、继承、多态。
在类和对象阶段,主要是研究类的封装特性,那什么是封装呢?
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
封装本质上是一种管理,让用户更方便使用类。

例如:对于电脑这样一个复杂的设备,提供给用户的就只有开关机键、通过键盘输入,显示器,USB插孔等,让用户和计算机进行交互,完成日常事务。但实际上电脑真正工作的却是CPU、显卡、内存等一些硬件元件。

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

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

再例如,我们使用C++实现栈与C语言实现栈进行对比:

  • C语言实现栈:
#include<stdio.h>
...
typedef int DataType;
typedef struct Stack
{
	DataType* a;
	int capacity;
	int Top;
}Stack;
void StackInit(Stack* ps){
	assert(ps);
    ...;
}
void StackPush(Stack* ps, DataType data){
	assert(ps);
    ...;
}
void StackPop(Stack* ps){
	assert(ps);
    ...;
}
  ....
  ....
int main()
{
	Stack s1;
	StackInit(&s);
	StackPush(&s, 3);
	StackDestroy(&s);
	return 0;
}
  • C++实现栈
#include<iostream>
using namespeace std;
...
typedef int DataType;
class Stack
{
public:
	void Init(Stack* ps) {
		assert(ps);
		...;
	}
	void Push(Stack* ps, DataType data) {
		assert(ps);
		...;
	}
	void Pop(Stack* ps) {
		assert(ps);
		...;
	}
	....
		....
private:
	DataType* a;
	int capacity;
	int Top;
};

int main()
{
	Stack s1;
	Stack.Init(&s);
	Stack.Push(&s, 3);
	Stack.Destroy(&s);
	return 0;
}

对比就会发现C语言可以随便访问数据结构本身,容易造成安全问题,而C++只允许使用成员函数来访问数据结构本身,避免了这样的安全性问题,而且使用起来更加简单方便。

C++把栈中用到的所有函数和变量放在一起,对外封装成一个类,这也是封装的一种体现。

四、类的实例化

1、类的实例化概念

我们在类中定义的变量其实是变量声明,与函数声明类似,在定义类时操作系统并不会给我们开辟空间,只有我们用类去定义对象时,操作系统才会给我们开辟空间,用类类型创建对象的过程,我们称为类的实例化

我们可以将类比作建筑图纸,别墅就是建筑图纸的实例化,那么对象就是类的实例化。

class test
{
	int a;//变量的声明
	int b;//变量的声明
};
int main()
{
	test t1;//用test类实例化了一个t1的对象
	return 0;
}

2、类对象的大小的计算

一个类可以实例化出多个对象,实例化出的对象 占用实际的内存空间,存储成员变量,那么我们如何计算类的大小呢?

废话少说,这里我们C++类的大小继承了C语言结构体大小的计算,也是按照结构体内存对齐规则

【结构体内存对齐规则】

  1. 第一个成员在与结构体偏移量为0的地址处。
  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。 VS中默认的对齐数为8
  3. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
  4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

请你计算一下32位平台下Date类的大小

class Date
{
public:
	void print()
	{
		cout<<_year<<"/"<<_month<<"/"<<_day<<"/"<<endl;
	}
private:
	int _year;
	int	_month;
	int _day;
};

正确答案是:12,三个变量的对齐数都是4,类的最大对齐数为4,从前到后中间没有多余空间依次排列,故为12。

你可能会说,为什么不是16呢?函数print用一个函数指针存储起来而且32位平台下一个函数指针刚好占用4个字节。能想出这个问题说明你对函数指针学习的很好,但是呢我首先明确告诉你在C++的类中,类的大小只计算成员变量,不计算成员函数,再其次我们来讨论为什么C++的类中不计算成员函数的大小?

我们看下面一段代码,我们调用的两个函数Init()是不是同一个函数呢?

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;
	Date d2;
	d1.Init(2023, 2, 9);
	d2.Init(2023, 2, 10);
	return 0;
}

答案是是的,我们调用的是同一个函数,如果我们把成员函数也算进类的大小中,那么我们每次创建每个对象时都要为函数指针分配空间,当我们创建许多对象时,它们调用函数时都用同一个地址都要去调用同一个函数,但是呢,每个对象要有一个指针存储去存放相应函数地址,这就造成了一个函数的地址被许多个指针存储,内存空间造成了很大的浪费

在这里插入图片描述

在这里插入图片描述

于是我C++就把成员函数全部放进一个叫公共区域,这个公共区域叫中叫做代码段,那个对象调用函数就去代码段中找相应的函数。
在这里插入图片描述在这里插入图片描述


扩展知识:(可以忽略)

C/C++程序内存分配的几个区域:

  1. 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结
    束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是
    分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返
    回地址等。
  2. 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分
    配方式类似于链表。
  3. 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。
  4. 代码段:存放函数体(类成员函数和全局函数)的二进制代码。

有了上面的知识后我们再计算两个类的大小

// 类中仅有成员函数
class A2 {
public:
	void f2()
	 {
	 }
};
// 类中什么都没有---空类
class A3
{
};

你可能认为它们是0,但是那也太奇怪了吧?定义一个对象但是对象不占用内存???

其实这种类中仅有成员函数或类中什么都没有的空类,都是比较特殊的,编译器给了这种类一个字节来唯一标识这个类的对象。

五、this指针

this是C++ 63个关键字中的一个。
对于它的理解我们先看一段代码

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, d2;
	d1.Init(2022, 1, 11);
	d2.Init(2022, 1, 12);
	d1.Print();
	d2.Print();
	return 0;
}

对于上述类,有这样的一个问题:
Date类中有 Init()与 Print() 两个成员函数,函数体中没有关于不同对象的区分,那当d1调用 Init 函数时,该函数是如何知道应该设置d1对象,而不是设置d2对象呢?

首先我们知道如果我们是C语言实现这段代码的同样功能,在我们调用Init()与 Print() 函数时会采用传指针的方式,这个指针就可以帮我们区分不同的对象。

typedef struct Date
{
	int _year; // 年
	int _month; // 月
	int _day; // 日
}Date;
void Init(Data*pd,int year, int month, int day)
{
	pd->_year = year;
	pd->_month = month;
	pd->_day = day;
}
void Print(Data*pd)
{
	cout <<pd-> _year << "-" << pd->_month << "-" << pd->_day << endl;
}
int main()
{
	Date d1, d2;
	Init(&d1,2022, 1, 11);
	Init(&d2,2022, 1, 12);
	Print(&d1);
	Print(&d2);
	return 0;
}

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

上面的标准回答翻译成大白话就是:C++的实现原理与C语言一样,对象d1调用函数的话,this指针就存储d1的地址,在传递参数时只不过我们不用自己传递指针了,是由编译器自动帮我们传递了指针。

C++实现的真实方式:

class Date
{
public:

	void Init(Date*this,int year, int month, int day)//此处只是为了演示,Date*this不用我们手动传递,
                                                    //编译器会自动传递,我们手动传递会报错
	{
		this->_year = year;//此处的this->可以省略
		this->_month = month;//此处的this->可以省略
		this->_day = day;//此处的this->可以省略
	}
	void Print()//同理,不再演示
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};
int main()
{
	Date d1, d2;
	d1.Init(2022, 1, 11);
	d2.Init(2022, 1, 12);
	d1.Print();
	d2.Print();
	return 0;
}

this指针的特性

  1. this指针的类型:类型* const,即成员函数中,不能给this指针赋值。
  2. 只能在“成员函数”的内部使用
  3. this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给this形参,所以对象中不存储this指针
  4. this指针是“成员函数”第一个隐含的指针形参,一般存在与栈中(因为它的本质是函数形参),VS通过ecx寄存器存储。
    在这里插入图片描述

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

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

相关文章

1.Redis【介绍与安装】

1.常用数据库介绍 mysql的表类型[表引擎.存储引擎],memory表结构和表数据分开存储的,表结构保存在硬盘中,表数据保存在内存中memcache是一款软件,可以使用键值对的格式保存数据到内存中redis是意大利的工程师开发的开源免费的告诉缓存数据库,需要注意的是作者本身只开发了linu…

1 Flutter UI Container和 Text 和图片组件

一 Text 组件Text 文本组件的一些属性如下body: const Text("this is leonardo fibonacci",// 文本对齐的方式textAlign: TextAlign.center,// 文本方向textDirection: TextDirection.rtl,// 字体显示最大的行数maxLines: 2,// 文字超出屏幕之后的显示方式 ellipsi…

Postgresql中null值和空字符串

NULL和空字符串不同数据库的表现 null和空字符串在不同的数据库中表现不一样&#xff0c;找了一张图&#xff0c;可以很清晰的对比了解。 首先null不是一个空字符串&#xff0c;也不是一个为零的值&#xff0c;上图&#xff0c;Oracle将NULL和空字符串都视为NULL。PostgreSQL…

UDP协议详解

目录 前言&#xff1a; 再谈协议 UDP协议 比较知名的校验和 小结&#xff1a; 前言&#xff1a; UDP和TCP作为传输层非常知名的两个协议&#xff0c;那么将数据从应用层到传输层数据是怎样进行打包的&#xff1f;具体都会增加一些什么样的报头&#xff0c;下面内容详细介绍…

洛谷——P1077 摆花

【题目描述】 小明的花店新开张&#xff0c;为了吸引顾客&#xff0c;他想在花店的门口摆上一排花&#xff0c;共 m 盆。通过调查顾客的喜好&#xff0c;小明列出了顾客最喜欢的 n 种花&#xff0c;从 1 到 n 标号。为了在门口展出更多种花&#xff0c;规定第 i 种花不能超过 …

快捷键被占用了,这能忍吗?赶紧使用 OpenArk 找出元凶并干掉它!!!

文章目录一、 问题&#xff1a;快捷键被占用了导致影响工作效率二、OpenArk2.1 OpenArk简介功能发布官方链接2.2 下载OpenArk2.3 运行OpenArk2.4 被占用的热键元凶到底是谁&#xff1f;三、总结一、 问题&#xff1a;快捷键被占用了导致影响工作效率 你是否遇到过&#xff0c;…

用ChatGPT构建网络设备表,并根据设备关系生成网络拓扑

构造一个数据表&#xff0c;存储包括交换机、路由器、防火墙、入侵检测、上网行为管理等设备的编号、序列号、IP、MAC、访问地址、用户名、密码、管理员、物理位置、上联设备ip等信息下面是一个示例数据表&#xff1a;Device IDSerial NumberIPMACAccess URLUsernamePasswordAd…

一文教会你如何选择远程桌面(五大主流远程软件全面讲解)

写在前面 作为程序员的我们&#xff0c;随时随地写代码改代码是我们的日常。刚回到家&#xff0c;就被老板、产品经理cue是常有的事。基于这种情况&#xff0c;一般都会随身携带电脑&#xff0c;随时备战&#xff0c;不过每天背着电脑上下班非常不方便。因此资深程序员的解决方…

高通开发系列 - linux kernel更新msm-3.18升至msm-4.9

By: fulinux E-mail: fulinux@sina.com Blog: https://blog.csdn.net/fulinus 喜欢的盆友欢迎点赞和订阅! 你的喜欢就是我写作的动力! 目录 linux kernel更新msm-3.18升至msm-4.9第一周处理的内容:第二周处理的内容第三周处理的内容linux kernel更新msm-3.18升至msm-4.9 第…

洗地机怎么选?洗地机品牌排行榜

洗地机的出现不仅能高效的清洁地面还能节省我们做家务的时间&#xff0c;对于上班族、有宠物的家庭以及宝妈来说简直不要太方便;目前市面上的洗地机有分有线款和无线款&#xff0c;无线款会比有线款操作更加方便;洗地机怎么选&#xff0c;其实洗地机的清洁能力主要是看吸力大小…

C++:map和set的认识和简单使用/关联式容器

关联式容器 关联式容器即是用来存储数据的&#xff0c;并且存储的是<Key&#xff0c;Value>结构的键值对&#xff0c;在数据检索时效率比序列式容器高。 序列式容器也就是vector、list、queue等容器&#xff0c;因为其底层为线性序列的数据结构&#xff0c;里面存储的是…

虹科案例 | 对症下药,零售行业BI解决方案及实操案例!

数据源多难以整合&#xff1f; 无法实时访问数据&#xff1f; 数据分析难、价值共享难&#xff1f; 面对大量的数据&#xff0c;企业愈发头疼&#xff0c;尤其是零售行业。虹科Domo商业智能分析工具“对症下药”&#xff0c;为零售企业海量数据问题提供解决方案。 一、零售行业…

如何使用CRM做好交付项目管理

在企业客户生命旅程中&#xff0c;项目实施期是最为关键的阶段之一。项目交付物是衡量实施过程质量的重要指标之一。项目交付物也是项目实施过程中体现项目成果的重要依据&#xff0c;项目交付物还是项目实施全生命周期过程管理的综合结晶&#xff0c;所以项目过程管理中项目交…

nslookup命令使用技巧

前言 nslookup是一个DNS查询工具&#xff0c;可以指定查询的类型&#xff0c;可以查到DNS记录的生存时间&#xff0c;还可以指定使用哪个DNS服务器进行解释。 一、缺省查询 不指定 dns-server&#xff0c;使用系统默认的 dns 服务器。 nslookup 域名 通过上述命令的执行结果…

设计「业务」与「技术」方案

三天研发&#xff0c;两天设计&#xff1b; 01【优先做设计方案】 职场中的那些魔幻操作&#xff0c;研发最烦的是哪个&#xff1f; 作为一个数年且资深的互联网普通开发&#xff0c;可以来说明一下为什么是&#xff1a;缺乏设计&#xff1b; 面对业务需求的时候&#xff0c…

使用Vuex实现商品列表的校验、添加、删除、统计

场景&#xff1a;使用Vuex实现一个商品列表的添加、删除以及利用Vuex中的getters属性计算商品列表的总数总价格 添加商品时判断当前商品列表中是否包含了相同的商品 添加商品时&#xff0c;对添加表单做了校验 Vuex的使用及原理已经在上篇文章中介绍过了 vue2.x中使用vuex_前端…

XLSX插件使用 — 导入导出(含中文表头)(React+Antd 对上传表格做数据格式验证)

需求说明 1.需要前端做数据导出&#xff08;非调用接口&#xff09; 2.需要对上传的表格数据做验证&#xff0c;不通过验证需要提示格式不正确&#xff0c;阻拦上传 技术栈介绍 ReactAntdesignXLSX js-xlsx 介绍 由SheetJS出品的js-xlsx是一款非常方便的只需要纯JS即可读取…

Kafka技术认知

文章目录概念理解名词解释基本架构工作流程Kafka的特性概念理解 Kafka是分布式的基于发布-订阅消息队列。是一个分布式、支持分区的、多副本的&#xff0c;基于 Zookeeper 协调的分布式消息中间件系统&#xff0c;它的最大的特性就是可以实时的处理大量数据以满足各种需求场景…

FISCO BCOS 搭建区块链,在SpringBoot中调用合约

一、搭建区块链 使用的是FISCO BCOS 和 WeBASE-Front来搭建区块链&#xff0c;详细教程&#xff1a; https://blog.csdn.net/yueyue763184/article/details/128924144?spm1001.2014.3001.5501 搭建好能达到下图效果即可&#xff1a; 二、部署智能合约与导出java文件、SDK证…

【C语言】程序环境和预处理

&#x1f307;个人主页&#xff1a;平凡的小苏 &#x1f4da;学习格言&#xff1a;别人可以拷贝我的模式&#xff0c;但不能拷贝我不断往前的激情 &#x1f6f8;C语言专栏&#xff1a;https://blog.csdn.net/vhhhbb/category_12174730.html 小苏希望大家能从这篇文章中收获到许…