【C++】初识类和对象
创始人
2024-11-15 13:10:00
0

本篇介绍一下C++的自定义类型,类和对象。

1.类的定义

1.1 类定义格式

class 为定义类的关键字,Stack为类的名字,类名随便取,{}中为类的主体,类定义结束时后面的分号不可省略。类主体中内容称为类的成员:类中的变量称为类的属性或成员变量;类中函数称为类的方法或成员函数。

 与C语言的结构体的定义相似,第一个不同就是类成员还可以是函数,C语言的结构体里面没有函数。比如我们定义一个栈的类。

class Stack { 	void Push(int x)  //成员函数(类的方法) 	{ 		//... 	} 	void Pop() 	{ 		//... 	} 	int top() 	{ 		//... 	}  	int* _a;           //成员变量(类的属性)     int _top;     int _capacity; };

为了区分成员变量,一般习惯在成员变量上加一个特殊标识,如_或者m开头,有的也把_加在成员变量名后面,像int* a_;  ,但这并不是强制的。

C++中也可以用struct定义类,C++兼容C中的struct用法,同时struct升级成了类,明显变化是C++的struct中可以定义函数,像下面的日期类,class可以是struct。一般情况下还是推荐用class定义类。

struct Date { public: 	void Init(int year, int month, int day) 	{ 		_year = year; 		_month = month; 		_day = day; 	} private: 	int _year; 	int _month; 	int _day; };

在类里面直接定义的函数默认就是内联函数,但是声明和定义分离就不是内联了,比如在类里面声明,在类外面定义。

1.2访问限定符

C++一种实现封装的方式,用类将对象的属性(变量)和方法(函数)结合在一起,让给对象更完善,通过访问权限选择性的将其接口提供给外部的用户使用。

访问限定符有3个:public  private  protected。public修饰的成员在类外可以直接被访问;protected和private修饰的成员在类外不能直接被访问,protected和private目前是一样的,后面的学习中才能体现出他们的区别。

 访问权限作用域从该访问限定符出现的位置开始,到下一个访问限定符出现为止。如果后面没有访问限定符,作用域就到}即类的结束。

class Stack { 	void Push(int x)  //成员函数(类的方法) 	{ 		//... 	} public: 	void Pop() 	{ 		//... 	} 	int top() 	{ 		//... 	} private: 	int* _a;           //成员变量(类的属性) 	int _top; 	int _capacity; };

class定义成员没有被限定符修饰时默认为private,struct默认为public。一般来说,成员变量都会被限制为private/protected,需要给别人使用的成员函数会被设为public。 

第二个不同是类名就是类型,可直接用类名定义对象。比如这里随便弄个结构体来对比一下,类还是用前面那个Stack类举例。

struct S { 	int a; };
struct S s; //C语言结构体定义结构体变量 Stack st;   //类定义对象

类不需要写成class Stack st; 直接Stack st;  C语言的结构体就不能丢掉struct。

因为C++兼容C语言,传统的struct在C++可以用,而struct在C++中又升级成了类,所以在C++中,下面两种写法都可以

struct S s; S s;
struct ListNode  //C语言链表节点的表示 { 	int val; 	struct ListNode* next; }; struct ListNode  //C++链表节点的表示 { 	int val; 	ListNode* next; };

C语言的表示在C++也能用,C++就有两种表示方法 

当我们访问类成员时,跟结构体一样,用点(.)访问。

当我们引出成员变量时发现只有public限制的可以访问,Push没有访问限定符限制,默认为私有,成员变量也是私有。

这里访问限定符,可以重复设置,比如我们想把Push设为公有,可重复使用public,但是一般放在一起,这里只是说明一下可以重复。

class Stack { public: 	void Push(int x)  //成员函数(类的方法) 	{ 		//... 	} public: 	void Pop() 	{ 		//... 	} 	int top() 	{ 		//... 	} private: 	int* _a;           //成员变量(类的属性) 	int _top; 	int _capacity; };

1.3 类域

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

class Stack { public: 	void Push(int x);  //类内:成员函数的声明  private: 	int* _a;      //成员变量      	int _top; 	int _capacity; }; void Stack::Push(int x) //类外:函数的定义 { 	//... }

2.实例化

2.1 实例化概念

• 用类类型在物理内存中创建对象的过程,称为类实例化出对象。
• 类是对象进行一种抽象描述,是一个模型一样的东西,限定了类有哪些成员变量,这些成员变量只是声明没有分配空间,用类实例化出对象时,才会分配空间。
• 一个类可以实例化出多个对象,实例化出的对象占用实际的物理空间,存储类成员变量

打个比方:类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图,设计图规划了有多少个房间,房间大小功能等,但是并没有实体的建筑存在,也不能住人,用设计图修建出房子,房子才能住人。

 同样类就像设计图一样,不能存储数据,实例化出的对象分配物理内存存储数据。

2.2 对象大小

分析一下类对象中哪些成员呢?类实例化出的每个对象,都有独立的数据空间,所以对象中肯定包含成员变量,那么成员函数是否包含呢?

首先函数被编译后是一段指令,对象中没办法存储,这些指令存储在一个单独的区域(代码段),那么对象中非要存储的话,只能是成员函数的指针。再分析一下,对象中是否有存储指针的必要呢,Date实例化d1和d2两个对象,d1和d2都有各自独立的成员变量_year / _month / _day 存储各自的数据,但是d1和d2的成员函数Init /Print指针却是一样的,存储在对象中就浪费了。如果用Date实例化100个对象,那么成员函数指针就重复存储100次,太浪费了。这里需要再额外说一下,其实函数指针是不需要存储的,函数指针是一个地址,调用函数被编译成汇编指令[call地址],其实编译器在编译链接时,就要找到函数的地址,不是在运行时找,只有动态多态是在运行时找,就需要存储函数地址,这个我们以后会讲解。

上面一大段的意思就是对象的大小不计算成员函数,只看成员变量,成员函数会存在一个公共区域。

对象中的成员变量在内存中的存储和C语言时结构体在内存中的存储规则一模一样,要内存对齐,结构体内存对齐详解在【C语言】结构体详解-CSDN博客 ,内存对齐规则以及为什么要内存对齐都在这篇写过,不知道的可以先看结构体这篇。

我们还是举个例子回顾一下。下面这个A类对象有多大?

class A { public: 	void Ptint() 	{ 		cout << "void Ptint()" << endl; 	} private: 	char _ch; 	int _i; };

应该是8个字节 ,具体计算如下

 我们再来看两个,类B里面只有成员函数,类C里面没有成员,它们大小是多少呢?0吗?

class B { public: 	void Ptint() 	{ 		cout << "void Ptint()" << endl; 	} };  class C { };

 我们用类B实例化一个对象b,类C实例化一个对象c,看看它们的大小。

B b; //实例化对象 C c; cout << sizeof(b) << endl; cout << sizeof(c) << endl; 

我们可以看到类B和类C对象的大小是1个字节,为什么不是0?因为如果一个字节也不给,怎么表示对象存在过呢?所以就给1个字节,纯粹是为了占位,表示对象存在。

3. this指针

我们先看下面这个时间类

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; };

我现在实例化两个对象d1和d2

Date d1; Date d2;

 用这两个对象像下面这样引出里面的函数

d1.Init(2024, 8, 2); d1.Print();  d2.Init(2024, 8, 3); d2.Print();

运行,看结果

思考一下,d1和d2明明执行的是同一个函数,结果为什么不一样? 函数体中没有关于不同对象的区分,那么当d1调用Init函数和Print函数的时候该函数是如何知道访问d1对象还是d2对象?

这里就介绍一个C++给的一个隐含的this指针解决问题。

编译器编译后,类的成员函数默认都会在形参的第一个位置增加一个当前类类型的指针,叫this指针。所以Init的第一个形参并不是year,Print函数也并不是没有参数,前面的Print函数和Init函数实际上是像下面这样。

void Init(Date* const this, int year, int month, int day) { 	//... } void Print(Date* const this) { 	//... }

 所以传参的时候也会传相应的地址过去。

//d1.Init(&d1, 2024, 8, 2);   d1.Init(2024, 8, 2); //d1.Print(&d1)   d1.Print();  //d2.Init(&d2, 2024, 8, 3);   d2.Init(2024, 8, 3); //d2.Print(&d2)   d2.Print();

类的成员函数中访问成员变量,本质是通过this指针访问的。

void Init(int year, int month, int day) { 	this->_year = year; 	this->_month = month; 	this->_day = day; } void Print() { 	cout << this->_year << "." << this->_month << "." << this->_day << endl; }

 C++规定不能在实参和形参的位置显示的写this指针,编译时编译器会处理,但是可以在函数体内显示使用this指针。this指针是不能修改的。

一般this指针存在栈区,因为this指针其实是函数的形参,形参就存放在栈区,有的也会存放在寄存器里面,vs下就是把this指针放在了寄存器里。

这次就分享到到这里,拜拜~

相关内容

热门资讯

新手必备(aapoker)计算... 大家肯定在之前WePoKer或者WPK中玩过新手必备(aapoker)计算辅助(透明挂软件)透视(有...
笔记本服务器扮演着哪些关键角色... 笔记本服务器是一种特殊设计的笔记本电脑,主要用于提供服务器功能,如文件共享、网络服务和数据存储等。它...
电脑服务器与PC机在功能和性能... 电脑服务器和PC机在用途、性能、稳定性和扩展性方面有显著差异。服务器专为高效处理数据和提供网络服务设...
免打扰模式中的禁止所有人选项究... 免打扰禁止所有人是指开启该功能后,所有来电、短信和通知都会被静音或屏蔽,不会发出任何声音或震动提醒。...
第三方介绍(【WePoKe】)... 第三方介绍(【WePoKe】)德州俱乐部机器人(有科技)透视(有挂教学)明细教程(哔哩哔哩),亲,有...
为什么笔记本微信图标显示异常? 笔记本微信图标可能不显示的原因包括:网络连接问题、微信客户端故障、系统权限限制、应用程序冲突或软件更...
构建高效游戏服务器,关键配置要... 服务器架设游戏需要高性能CPU、大量RAM、高速SSD存储、强大GPU(对于图形密集型游戏)、稳定高...
剑网3的四合一服务器模式有何特... 剑三四合一服务器是指将《剑网3》游戏中的四个不同版本(如重制版、经典版等)合并到一个服务器中,玩家可...
主机与服务器,它们之间究竟有何... 主机和服务器的主要区别在于功能和使用场景。主机通常是指个人电脑,用于日常办公和娱乐。而服务器则是专为...
遇到vivo账号验证服务器错误... vivo账号验证服务器错误通常意味着在尝试登录或进行某些操作时,与vivo账号系统通信的服务器出现了...