c++|类和对象(上)

news2024/9/21 8:04:22

目录

一、面向过程和面向对象初步认识

二、类的引入和定义

2.1类的引入

2.2类的定义

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

3.1访问限定符 

3.2封装 

四、类的作用域

五、类的实例化

六、类的对象大小的计算

6.1如何计算对象的大小

6.2类对象的存储方式 

七、类成员函数的this指针

7.1this指针的引出

7.2this指针的特性


一、面向过程和面向对象初步认识

 面向过程:关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。

例如:洗衣服

拿盆子——>放水——>放衣服——>放洗衣粉——>手搓——>换水——>手搓——>拧干——>晾衣服 

我们知道C语言面向的是过程,例如我们要得到两数之和的结果,那么得造一个和函数,先进行传实参,形参相加,得到参数之和,返回之和的值,这就是得到两数之和的一个过程。

面向对象:关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。

 还是洗衣服,有四个对象:人、衣服、洗衣粉、洗衣机 

那么洗衣服过程:人将衣服放入洗衣机、倒入洗衣粉,启动洗衣机,洗衣机就会完成洗衣过程,人不需要关注洗衣机是如何洗衣服的。

那么照样我们也要得到两数之和的结果,就不需要去实现函数了,而是直接用函数就行,这个函数是已经被实现的,这就是直接使用对象,不需要关注实现过程。

那么还有疑问的是,对象之间的操作怎么看起来也是一个过程,其实是不同角度产生的看法,那么对于同一件事情,洗衣服,从过程来看,这个衣服需要执行被洗的过程,在乎的是怎么洗的过程,而从对象来看,只需要用洗衣机完成洗衣服,怎么洗的过程不在乎。其实这就相当与面向对象是以更高的角度去看待面向过程。

我再从更高的角度去看待面向对象,那么面向对象又可以看成面向过程。所以对于任何实现从不同角度既可以看成面向过程,也可以看成面向对象

对于c++我们将其看成面向对象的语言,因为对标C语言,c++很多实现要比C语言简单的多,不用造轮子(自己实现没必要去浪费时间做的函数),当然你以更高的角度来看c++实现的函数也可以看成过程 ,但是你得在更高的角度弄出点东西来,不然也改变不了啥。

二、类的引入和定义

2.1类的引入

回顾C语言实现栈时,需要有一个结构体存放定义的变量,而具体的实现需要额外定义函数完成压栈、出栈、销毁栈等。那么在c++中,结构体不仅可以定义变量,也可以定义函数了,同样我们用栈来举个例子

#include <iostream>
using namespace std;

typedef int DataType;
struct stack
{
    //栈的初始化
    void Init(size_t capacity)
    {
        _array = (DataType*)malloc(sizeof(DataType*) * capacity);
        if (nullptr == _array)
        {
            perror("malloc fail");
            exit(-1);
        }

        _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(5);
    s.Push(1);
    s.Push(2);
    s.Push(3);
    cout << s.Top() << endl;
    s.Destroy();
    
    return 0;
}

运行结果:

 

 从上面代码可以看出,我们将变量的定义与函数的实现都放在结构体中,且这些变量可以在函数中使用,像这种将函数和变量都放在一个结构体中,在这种情况下,在c++中我们称之为类,也就是说,C语言中的结构体在c++中升级成了类,当然类依然支持C语言中的结构体用法,毕竟c++兼容C语言嘛。那么接下来讲解类定义的详细用法

注意:上述结构体的定义,在c++中更喜欢用class来代替,其实struct和class还是有区别的,在类的访问限定中会讲解。

2.2类的定义

形式规则:

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

};//一定要注意后面的分号

 class为定义类的关键字,className为类的名字,{}中为类的主体,注意类定义结束时后面分号不能省略。

类体中内容称为类的成员:类中的变量称为称为类的属性成员变量;类中的函数称为类的方法或者成员函数

以上是类定义的形式规则,但对于类中的成员函数定义和成员变量命名也有一定的规则。

成员函数的定义有两种方式:

1.成员函数的声明和定义都放在类体中,需要注意的是,编译器可能会将其当成内联函数处理。

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

//声明放在类的头文件fun.h中
#include <iostream>
using namespace std;

class Person
{
public:
	//显示基本信息
	void showinfo();
public:
	 char* _name;
	 char* _sex;
	 int _age;
};


//定义放在类的实现文件中
#include "fun.h"

void Person::showinfo()
{
	cout << _name << "-" << _sex << "-" << _age << endl;
}

int main()
{
	Person s;
	s._name = (char*)"张三";//由于张三是字面常量,类型为const char*而_name为char*类型
	s._sex = (char*)"男";
	s._age = 18;
	s.showinfo();
	return 0;
}

 运行结果:

一般情况下,最好采用第二种方式,但在之后的讲解中,为了方便,可能会使用方式一。但在以后的工作中的话,那尽量使用方式二 。

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

#include <iostream>
using namespace std;


class Date
{
public:
	void Init(int year)
	{
		//这里的year到底是成员变量还是函数形参,虽然知道左边的year是定义的变量,而右边的year是形参的year
		//但难免带来一种令人不友好的感觉
		year = year;

		//所以一般建议定义的变量加上前缀或者后缀,对于我而言习惯加上前缀
		int _year = year;
		int year_ = year;
		int myear = year;
	}
private:
	int year;

	int _year;
	int year_;
	int myear;
};

int main()
{
	Date d;
	d.Init(3);
	return 0;
}

注意:默认直接在类里面定义的函数就是inline函数,不需要加inline关键字,当然在编译时展不展开函数还是取决编译器 。

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

3.1访问限定符 

 在上述代码中,看到我们加了public、private这类符号,是什么?

像这类符号我们称之为访问限定符,其实,这是c++实现封装方式的必要手段,用类将对象的属性和方法结合在一块,让对象更加完善,再通过访问访问限定符选择性的将其接口提供给外部的用户使用。

那具体是啥意思,访问限定符有哪些?look

访问限定符,用来限定类外是否能访问类里面的成员,访问限定符分为三类,public(公有)protected(保护)和private(私有)

访问限定符说明:

1.public表示类外是可以访问类里面被public修饰的成员,

2.protected和private表示类外不能直接访问类里面被这两个修饰的成员,他们是类似的,但还是有区别的,在继承阶段会讲解。

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

4.上面我们提到了class和struct是有区别,对于class,其成员的默认访问权限为private,而struct为public(想想,虽然在c++中struct升级成了类,但依然支持C语言struct的用法,而C语言的struct成员都是可以直接被访问的,不需要限定符。所以在这里struct的默认访问权限是public)

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

3.2封装 

对于对象而言,无非就三大特性:封装、继承、多态。继承和多态后面的章节再说,在类和对象阶段,主要说的就是封装,那么什么是封装呢?

封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互,封装本质上是一种管理,让用户更方便实用类。说白了,不就是用类将他们包起来,再通过访问限定符是否对外提供接口来实现交互。

在现实中,这个道理也是一样的,像电脑,里面的零件都是被封装的,然后提供一个USB接口给用户使用。

四、类的作用域

在上面,我们提到并给出了代码,类中的函数长的声明和定义要分离,分离定义的函数怎么与类中声明的函数进行联系呢?跟命名空间一样,类也是一个作用域,类的所有成员在类的作用域中,类外定义成员时,需要使用::作用域操作 符指明成员属于那个类域

 

#include <iostream>
using namespace std;


class Person
{
public:
	//打印基本信息
	void printpersoninfo();
public:
	char _name[20];
	char _gender[3];
	int _age;
};

//指明函数属于哪个类域
void Person::printpersoninfo()
{
	cout << _name << "-" << _gender << "-" << _age << endl;
}

int main()
{
	Person s;
	strcpy(s._name , "李四");
	strcpy(s._gender , "女");
	s._age = 19;
	s.printpersoninfo();
	return 0;
}

运行结果:

 

五、类的实例化

 仅仅有一个类不能对里面的内容进行访问,而是要创建一个对象,通过对象来对类中的成员进行访问。用类类型创建对象的过程,称为类的实例化。

类是对对象进行描述的,是一个模型一样的东西,限定类有那些成员,定义出一个类并没有分配实际的内存空间来存储它。必须实例化出对象才占有实际的物理空间,存储类成员变量。且一个类可以实例化出多个对象。

比如:房子设计图,就是一个类,设计图描述了房子的各种信息,但是并没有为这些信息建造出一个实际的空间。而只有建造出了真正的房子才是实例化,才分配了空间,且通过设计图,也可以建造出多个房子,即实例化多个对象。再通过代码来比较。

#include <iostream>
using namespace std;


class Person
{
public:
	//显示基本信息
	void printpersoninfo();
public:
	char _name[20];
	char _gender[3];
	int _age;
};

//指明函数属于哪个类域
void Person::printpersoninfo()
{
	cout << _name << "-" << _gender << "-" << _age << endl;
}

int main()
{
	Person s;//创建对象,且实例化多个对象
	Person t;
	Person u;
	strcpy(s._name , "李四");
	strcpy(t._name , "王五");
	strcpy(u._name , "李六");

	strcpy(s._gender, "女");
	strcpy(t._gender, "男");
	strcpy(u._gender, "男");

	s._age = 18;
	t._age = 19;
	u._age = 20;
	
	s.printpersoninfo();
	t.printpersoninfo();
	u.printpersoninfo();
	return 0;
}

运行结果:

 

六、类的对象大小的计算

6.1如何计算对象的大小

对于类中既有成员函数和成员变量,那么计算大小时是不是他们都要算呢?look

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

int main()
{
	A a1;
	cout << sizeof(a1);//猜猜多大
	return 0;
}

  运行结果:

通过结果发现,大小只有1,只计算了成员变量_a 的大小。这是怎么回事呢?且看下面分析

6.2类对象的存储方式 

以正常的理解,当一个类实例化多个对象时,对象与对象之间的成员变量、成员函数是互不干扰的,但每个对象都会保存一份该成员函数的代码,相同的代码保存了多份,实例化了多份,就会形成空间的浪费。

所以给了对象的新的存储方式,对象中只保存成员变量,而对于成员函数,只保存一份放在公共代码区,每个对象调用该函数时,其实就是调用公共代码区的该函数。所以在上述计算大小时,就不会算上成员函数。

再来看几个例子:

空类:

#include <iostream>
using namespace std;

class A3
{};

int main()
{
	cout << sizeof(A3);
	return 0;
}

 运行结果:

为什么也是1呢,不应该是0吗?明明是一个空类。

 OK,空类其实也可以实例化对象,这是前提。既然可以实例化对象,那么对象就会在内存分配空间,该对象有地址存在,只不过不存储任何数据。如果,空类的大小为0表示该类没有任何成员或对象。这意味着该类不占用内存空间。所以,为了标识该对象存在,编译器规定该空类的大小为1

类中仅有成员函数:

#include <iostream>
using namespace std;

class A2 {
public:
	void f2() {}
};

int main()
{
	cout << sizeof(A2);
	return 0;
}

运行结果:

 

大小也是1,根据上面的解释成员函数是放在公共代码区,它们在内存中独立存在,所以在计算大小时, 相当于计算空类的大小。

注意:计算类的大小依然遵守结构体内存对齐规则 !!!

七、类成员函数的this指针

7.1this指针的引出

#include <iostream>
using namespace std;

class Date
{
public:
	void Init(int year = 2023, int month = 11, int day = 23)//给缺省值
	{
		_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(2023, 1, 11);
	d2.Init(2023, 1, 12);
	d1.print();
	d2.print();
	return 0;
}

运行结果:

对于上述类,实例化两个对象,在表面上两个对象都调用了Init函数时,但是在深层,函数怎么识别是谁调用?

c++引入了this指针来识别对象的调用:c++编译器给每个“非静态的成员函数”增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”的操作,都是通过该指针去访问。但要注意的是,this指针的所有操作对于程序员而言是透明的,即this指针不需要程序员来传递,编译器会自动完成(可以认为是祖师爷设定好的)。

 7.2this指针的特性

1.this指针的类型:类类型* const,例如Date* const this。由于this被const修饰,所以在成员函数中,不能给this指针赋值。

2.只能在“成员函数”的内容使用

3.this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象本身不存储this指针。

4.this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递。

其实上述代码的本质就是第二张图的形式,对应着上述规则。注意的是在调用函数传实参、以及函数的形参中不能显示的将this指针写出来,否则会报错,但在成员函数中,可以显示的写出。

【面试题】 

1.this指针存在哪里?

将上述代码,转到反汇编后,可以发现,d1对象和d2对象存放到rcx寄存器。由此this指针在VS下存放在栈区的寄存器中。

2.this指针可以为空吗 ?

先看代码1:下面运行结果? A、编译报错 B、运行崩溃 C、正常运行

#include <iostream>
using namespace std;

class A
{
public:
	void Print()
	{
		cout << "Print()" << endl;
	}
private:
	int _a;
};

int main()
{
	A* p = nullptr;
	p->Print();
	return 0;
}

运行结果:

答案是正常运行,疑问的是p是空指针,为什么可以访问Print函数?

其实这是一个误区,想想前面讲解的,首先成员函数不是放在对象中,而是在公共代码区,其次在这里p并没有去访问Print函数,但是,如果没有访问该函数,那又是怎么寻得该函数。在C语言中,就知道了,是在编译、链接阶段通过符号表来寻找函数名字,所以在这里p的作用就是告诉编译器它属于A类,然后在编译阶段,A类中寻找是否存在Print函数。

代码2:下面运行结果? A、编译报错 B、运行崩溃 C、正常运行

#include <iostream>
using namespace std;

class A
{
public:
	void Print()
	{
		cout << _a << endl;
	}
private:
	int _a;
};

int main()
{
	A* p = nullptr;
	p->Print();
	return 0;
}

运行结果:

运行结果是崩溃的,对比第一个代码,修改了成员函数的输出,且成员变量并没有初始化。本质上就是this->_a,这个就是空指针访问了成员变量,而空指针并不指向任何有效的对象,因此其中的成员变量也是未定义的。 

综上所述,虽然成员函数的代码本身放在公共代码区,但成员函数通常涉及对对象中的成员变量进行操作,这可能导致问题。因此最好不要让this指针为空,以避免潜在的未定义行为。

end~

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

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

相关文章

OpenCV快速入门:图像分析——傅里叶变换、积分图像

文章目录 前言一、傅里叶变换1.1 离散傅里叶变换1.1.1 离散傅里叶变换原理1.1.2 离散傅里叶变换公式1.1.3 代码实现1.1.4 cv2.dft 函数解析 1.2 傅里叶变换进行卷积1.2.1 傅里叶变换卷积原理1.2.2 傅里叶变换卷积公式1.2.3 代码实现1.2.4 cv2.mulSpectrums 函数解析 1.3 离散余…

LangChain的简单使用介绍

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️ &#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

解决kubernetes中微服务pod之间调用失败报错connection refused的问题

现象&#xff1a; 从这里可以看到是当前服务在调用product service服务是出现了连接拒绝connection refused 走读一下原始代码&#xff1a; 可以看到请求是由FeignClient代理发出的 &#xff0c;但问题在于为什么Feign请求的时候会产生connection refused错误&#xff1f; 上…

Python+jieba+wordcloud实现文本分词、词频统计、条形图绘制及不同主题的词云图绘制

目录 序言&#xff1a;第三方库及所需材料函数模块介绍分词词频统计条形图绘制词云绘制主函数 效果预览全部代码 序言&#xff1a;第三方库及所需材料 编程语言&#xff1a;Python3.9。 编程环境&#xff1a;Anaconda3&#xff0c;Spyder5。 使用到的主要第三方库&#xff1a;…

飞书+ChatGPT搭建智能AI助手并实现公网访问web界面

文章目录 前言环境列表1.飞书设置2.克隆feishu-chatgpt项目3.配置config.yaml文件4.运行feishu-chatgpt项目5.安装cpolar内网穿透6.固定公网地址7.机器人权限配置8.创建版本9.创建测试企业10. 机器人测试 前言 在飞书中创建chatGPT机器人并且对话&#xff0c;在下面操作步骤中…

时间序列分析算法的概念、模型检验及应用

时间序列分析是一种用于研究随时间变化的数据模式和趋势的统计方法。这类数据通常按照时间顺序排列&#xff0c;例如股票价格、气温、销售额等。时间序列分析的目标是从过去的观测中提取信息&#xff0c;以便预测未来的趋势。 以下是关于时间序列分析的一些重要概念、模型检验…

【深度学习】学习率及多种选择策略

学习率是最影响性能的超参数之一&#xff0c;如果我们只能调整一个超参数&#xff0c;那么最好的选择就是它。相比于其它超参数学习率以一种更加复杂的方式控制着模型的有效容量&#xff0c;当学习率最优时&#xff0c;模型的有效容量最大。本文从手动选择学习率到使用预热机制…

插入排序(形象类比)

最近在看riscv手册的时候&#xff0c;里面有一段代码是插入排序&#xff0c;但是单看代码的时候有点迷&#xff0c;没看懂咋操作的&#xff0c;后来又查资料复习了一下&#xff0c;最终才把代码看明白&#xff0c;所以写篇博客记录一下。 插入排序像打扑克牌 这是我听到过比较形…

RubyMine 2023:提升Rails/Ruby开发效率的强大利器

在Rails/Ruby开发领域&#xff0c;JetBrains RubyMine一直以其强大的功能和优秀的性能而备受开发者的青睐。现如今&#xff0c;我们迎来了全新的RubyMine 2023版本&#xff0c;它将为开发者们带来更高效的开发体验和无可比拟的工具支持。 首先&#xff0c;RubyMine 2023提供了…

IDEA安装教程

文章目录 1 下载IntelliJ IDEA2 安装3 IDEA配置4 创建项目 1 下载IntelliJ IDEA ​ 官方网站上下载最新版本的IntelliJ IDEA。官方网站提供了两个版本&#xff1a;Community版和Ultimate版。 Community版是免费的&#xff0c;适用于个人和非商业用途。Ultimate版则需要付费购…

ESP32之避障

ESP32之避障 图片 程序 int Led27;//定义LED 接口 int buttonpin4; //定义光遮断传感器接口 int val;//定义数字变量val void setup() { pinMode(Led,OUTPUT);//定义LED 为输出接口 pinMode(buttonpin,INPUT);//定义避障传感器为输出接口 } void loop() {Serial.begin(9600);…

JVMj之console Java监视与管理控制台

jconsole Java监视与管理控制台 1、jconsole介绍 jconsole (java monitoring and management console)是一款基于JMX (Java Management Extensions) 的可视化监视和管理工具。 2、启动jconsole 1、在linux和windwos下通过jconsole启动即可。 2、然后会自动搜索本机运行的…

【栈】不同字符的最小子序列

题目&#xff1a; /*** 思路&#xff1a;栈,使用数组记录每个字母出现的次数&#xff0c;再用一个数组标记字符是否在栈中* 遍历栈&#xff0c;存储字符时比较栈顶字符&#xff0c;若小于栈顶字符并且后面有重复的字符则* 栈顶元素出栈&#xff0c;否则入栈。** au…

超级利器!Postman自动化接口测试让你提升测试效率,节省宝贵时间!

Postman自动化接口测试 该篇文章针对已经掌握 Postman 基本用法的读者&#xff0c;即对接口相关概念有一定了解、已经会使用 Postman 进行模拟请求的操作。 当前环境&#xff1a; Window 7 - 64 Postman 版本&#xff08;免费版&#xff09;&#xff1a;Chrome App v5.5.3 …

数字乡村:科技赋能农村产业升级

数字乡村&#xff1a;科技赋能农村产业升级 数字乡村是指通过信息技术和数字化手段&#xff0c;推动农业现代化、农村经济发展和农民增收的一种新模式。近年来&#xff0c;随着互联网技术的飞速发展&#xff0c;数字乡村开始在全国范围内迅速兴起&#xff0c;为乡村经济注入了新…

CVE-2022-0543(Redis 沙盒逃逸漏洞)

简介 CVE-2022-0543是一个与Redis相关的安全漏洞。在Redis中&#xff0c;用户连接后可以通过eval命令执行Lua脚本&#xff0c;但在沙箱环境中脚本无法执行命令或读取文件。然而&#xff0c;攻击者可以利用Lua沙箱中遗留的变量package的loadlib函数来加载动态链接库liblua5.1.s…

tcp/ip协议2实现的插图,数据结构2 (19 - 章)

(68) 68 十九1 选路请求与消息 函rtalloc,rtalloc1,rtfree (69)

解决mv3版本浏览器插件,不能注入js脚本问题

文章目录 背景引入ifream解决ifream和父页面完全跨域问题参考链接 背景 浏览器插件升级mv3版本后&#xff0c;不能再使用content_script内容脚本向原浏览器&#xff08;top&#xff09;注入script标签达到注入脚本的目的。浏览器认为插入未经审核的脚本是不安全的行为。 引入…

从0开始学习JavaScript--JavaScript元编程

JavaScript作为一门灵活的动态语言&#xff0c;具备强大的元编程能力。元编程是一种通过操作程序自身结构的编程方式&#xff0c;使得程序能够在运行时动态地创建、修改、查询自身的结构和行为。本文将深入探讨JavaScript中元编程的各个方面&#xff0c;包括原型、反射、代理等…

揭秘周杰伦《最伟大的作品》MV,绝美UI配色方案竟然藏在这里

色彩在UI设计的基本框架中占据着举足轻重的位置。实际上&#xff0c;精心挑选和组合的色彩配色&#xff0c;往往就是UI设计成功的不二法门。在打造出一个实用的UI配色方案过程中&#xff0c;我们需要有坚实的色彩理论知识&#xff0c;同时还需要擅于从生活中观察和提取灵感。以…