C++ 类和对象(上)

news2024/11/13 20:44:45

 面向对象的三大特性:封装,继承,多态

 C语言结构体中只能定义变量,在C++中,结构体内不仅可以定义变量,也可以定义函数。比如:
之前在数据结构初阶中,用C语言方式实现的栈,结构体中只能定义变量;现在以C++方式实现,
会发现struct中也可以定义函数。

C++当中的 struct 升级为了 类,在struct当中除了可以定义变量,还可以定义函数,而且以前C当中的struct 的玩法也可以实现:

struct Stack
{
	int* a;
	int top;
	int capacity;
};//没有进行重命名

int main()
{
	struct Stack st1;  //C++兼容 C的struct 语法
	Stack st2;        // 同时 升级为了 类

	return 0;
}

 我们现在来简单实现一个栈,就可以把栈的当中使用的变量,和函数都写到一个类当中去:

struct Stack
{
	void Init(size_t capacity = 4)
	{
		a = (int*)malloc(sizeof(int) * capacity);
		if (nullptr == a)
		{
			perror("malloc申请空间失败");
			return;
		}
		capacity = capacity;
		top = 0;
	}

	void Push(const int& data)
	{
		// 扩容····书写 这里是省略
		a[top] = data;
		++top;
	}

	int Top()
	{
			return a[top - 1];
	}

	void Destroy()
	{
		if (a)
		{
			free(a);
			a = nullptr;
			capacity = 0;
			top = 0;
		}
	}

	int* a;
	int top;
	int capacity;
};//没有进行重命名

int main()
{
	Stack std;
	std.Init();

	std.Push(1);
	std.Push(2);
	std.Push(3);
	std.Push(4);

	cout << "栈顶元素:" << std.Top() << endl;
	std.Destroy(); 

	return 0;
}

我们可以用 ” .  “来访问到类当中的函数。

而且上述例子,我们是把成员定义来 函数的下面的,但是我们发现并没有报错,因为C++当中的创建一个类就是创建一个域,而C++是把这个类看做是一个整体的。

 在C++当中 跟喜欢 把上述的 struct 的定义方式,用class 来代替,定义一个class和在C++当中定义一个 struct一样,都可以定义成员和函数,在最后都需要 加 分号 “;”。

 但是,如果我们直接把上述的 struct 换成 class 就编译不通过了:

 

 在C++当中 给了 三种访问修饰符:

 public 在类外可以访问,protected 和 pricate在此处都是一样的,只能再类内使用,后两者在继承当中才有区别。

 当我们加了 public修饰符之后,在类外就可以使用了:

class Stack
{
public:
	void Init(size_t capacity = 4)
	{
		a = (int*)malloc(sizeof(int) * capacity);
		if (nullptr == a)
		{
			perror("malloc申请空间失败");
			return;
		}
		capacity = capacity;
		top = 0;
	}

	void Push(const int& data)
	{
		// 扩容····书写 这里是省略
		a[top] = data;
		++top;
	}

	int Top()
	{
			return a[top - 1];
	}

	void Destroy()
	{
		if (a)
		{
			free(a);
			a = nullptr;
			capacity = 0;
			top = 0;
		}
	}
protected:
	int* a;
	int top;
	int capacity;
};

上述中  三个修饰符之后要交  “ : ” ,而且上述的public 修饰符修饰的范围是,下一个权限修饰符之前,或者是如果没有的话就是一直修饰到最后,如上述,public就修饰到 protected 修饰符的上一行。

 上述的访问修饰限定符,不仅仅可以在class 中使用,struct 也可以使用,而我们上述直接把 struct 换成 class 之后就会报错是因为,class,如果不使用 访问修饰限定符的话,他默认是private的。而struct默认是 public修饰的。

 但是我们不一般不用默认这个概念,一般都是使用 这个修饰符来修饰。

 【访问限定符说明】

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

 类的定义

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

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

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

类的两种定义方式:

 类也是可以声明和定义分离的:

 1.)声明和定义全部放在类体中,需注意:成员函数如果在类中定义,默认这个函数是内联的。

 

 如果我们像上述一样在类中去定义 成员函数,这个函数是 内联的,但是同样的,决定一个函数是否是内联的,决定权是在编译器,如果这个函数定义得 复杂,长,那么可能就不会是内敛函数。

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

 

 上述例子,在头文件中 定义 类的声明,在cpp文件中 定义类中函数的实现,我们用“类名::函数名”这个方式来表示,这个函数是在类中的函数,是成员函数的定义。

需要注意的是:我们一般在写的时候,喜欢把 类当中成员名前加一个 " _ "  ,原因是这样的:

class data
{
public:
	void Init(int year)
	{
		year = year;
	}

protected:
	int year;
	int month;
	int data;
};

 比如这个例子,在Init()函数中的  year = year 是哪个 year 赋值给 year 呢?

 其实是这样的赋值的:

 

 因为在成员的函数中的参数访问顺序是,先是 局部域 然后是 类域 最后是 全局域

 所以为了防止出现上述的情况,我们就把类当中的 成员加一个" _ "  来区分:

protected:
	int _year;
	int _month;
	int _data;

封装

 将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来
和对象进行交互。把数据和方法放到一起,不想给用户看的就是 私有的,想给用户看的就是 公有的。

封装本质上是一种管理,让用户更方便使用类,比如一个计算机,有键盘,鼠标,显示屏,开关机键,等等的可以让用户和计算机进行交互的硬件,但是,实际上在完成计算,和工作的是电脑里吗的cpu,显卡,内存等等的这些用户看不到的硬件。厂家在生产电脑的时候就会把这些cpu,显卡,内存等等的给包装起来,把键盘,显示器,开关机键这些给留在外面供用户和计算机交互。

上述就是封装的意义。

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


 不如我们在C中的数据结构,懂得封装的程序员一般是这样写的:

就是,哪怕 STTop()函数只有一行代码,也要写成函数来调用

 但是有一些就会 一些用封装,一些就直接访问,如下图:

 类的作用域

 类定义了一个新的作用域,类的所有成员都在类的作用域里面,在类体外定义成员的时候,需要使用 :: 作用域操作符指明成员属于那个域的。


class Person
{
public:
	void PrintPersonInfo();
private:
	char _name[20];
	char _gender[3];
	int _age;
};
// 这里需要指定PrintPersonInfo是属于Person这个类域
void Person::PrintPersonInfo()
{
	cout << _name << " " << _gender << " " << _age << endl;
}

这样也能理解,定义一个类就是一个新的作用域,意思就是我在一个类当中定义了一个 func()函数,我又在另一个类当中定义了一个 func()函数,如果我不指明是那个类当中的func()函数,编译器就不知道我是对哪一个 类 当中的函数进行定义了。

 对于函数的声明和定义很好区分,对于变量是声明还是定义,就要判断它开不开空间,如上述类当中的变量就是声明,也它没有开空间。

int main()
{
    Person p; // 整体定义

    return 0;
}

 如上,类当中的变量只是声明,他们是向上述一样整体进行定义的。

类的实例化

int main()
{
    Person p; // 整体定义

    return 0;
}

如我们之前写的这个例子,这就是一个类的实例化对象。

打个比方,我们要建房子,而类就像是一个 图纸一样,而图纸里面是不能建房子,住人的,他是一个模板,我们根据这个图纸就可以 开始建房子,而根据这个图纸建出来的房子就是这个类的实例化对象。这个对象才开辟了空间。

int main()
{
    Person p; 

    p::age = 0;  // error

    return 0;
}

像这样是不行的,因为我们这样是在访问 类当中的 top,而类当中的top是没有开辟空间的,也就是说图纸当中的 是不能住人的。

对象的大小

 以前在C当中计算结构体的大小很好计算,里面都是变量,只要注意内存对齐就行了,但是C++类当中的 不仅仅有成员,还有 成员函数,这怎么计算呢?

 计算类的大小也是要进行 内存对齐的,而其中的成员函数是不进行计算的,只算成员的大小。

 因为对象当中只存储了 成员,没有存储成员函数

 这就好比是,成员是一个家,成员函数是篮球场,健身房,这些东西没有必要在家里来建造,都建造的话很浪费,用公用就行了。

 此处的函数都是放在 一个 公共的区域,每一个这个类的对象,有的成员函数都在这个公共的区域里面。这个公共的区域编译器是知道在哪的,所以对象中不需要存储函数的地址。

class A
{
public:
	void printA()
	{
		printf("%d", _aff);
	}
//private:
	int _aff = 10;
 };

int main()
{
	A a;
	cout << sizeof(A) << endl; //4
	cout << sizeof(a) << endl; //4
	a._aff = 1; // 在 a 这个对象当中去访问
	a.printA(); // 在公共区域当中去访问
}

需要注意的是:如果这个类是空类,也就是没有成员变量的类,那么这个类的大小是 1 byte,这个字节是为了占位,表示这个对象的存在。

class A
{
public:
	void func()
	{

	}
};

class B
{

};

int main()
{
	cout << sizeof(A) << endl; //1
	cout << sizeof(B) << endl; //1

	return 0;
}

 内存对齐

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

 

为什么要内存对齐

 1) 平台移植:

不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

2) 性能原因:

数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。总体来说:结构体的内存对齐是拿空间来换取时间的做法。

 第一个 _a 只读了一次,而第二次 不对齐的情况, 读了 两次

使用  #pragme pack(8)可以设置 默认对齐数为 8 ,在VS当中,默认对齐数是8。

在使用  #pragme pack(8)  之后,再使用  #pragme pack(),可以取消设置的默认对其书,还原默认。

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

先来看这个例子,既然成员函数都是调用的是一个 公共的函数,那么为什么此处的两个对象 d1 和 d2 ,两个对象调用的同一个函数,打印的结果不一样呢?我们看函数也没有传参,此处的是怎么分辨Print()函数中 _year  _month _day 这些变量的?

 此处,我们看来是访问的是 本 对象当中的 变量:

 其实不是,因为这个只是一个声明,是没有存储空间的,其实这样的代码,编译器是会做一些处理的:

此处其实隐含了一个  this 指针,如果交给编译器,编译器会这样把这个函数进行修改:

	// 编译器对成员函数的修改
	void Print(Date* this)
	{
		cout <<this-> _year << "-" << this->_month << "-" << this->_day << endl;
	}

然后再函数调用的时候,也会传入这个对象的地址:

Print(&A):
Print(&B):

 但是上述只是编译器来写的,它在函数的形参和实参上面,只允许自己来创建  Date* this 这个形参,而不允许用户去 创建这个形参:

 如上图,报错了。

但是编译器又允许我们在函数内部去使用这个this:

 也就是说,这个this是编译器给我们创建好了的,我们可以在函数当中去使用这个this。

 这个this 是不能修改的:

 因为,其实此处的this 的类型这样的 : Date* const this ;  也就是说,此处的 const 修饰但是this ,这个this  是不能被修改的,但是 this  指向的内容是可以进行修改的。

 const  加在 * 的前面,这个指针指向的内容才不能被修改。

  空对象中的this

class A
{
public:
	void Print()
	{
		cout << "Print()" << endl;
	}
private:
	int _a;
};
int main()
{
	A* p = nullptr;
	p->Print();  //Print()
	return 0;
}

 上述例子是正常执行的,但是下述代码不是正常执行的:

class A
{
public:
	void PrintA()
	{
		cout << _a << endl;
	}
private:
	int _a;
};
int main()
{
	A* p = nullptr;
	p->PrintA();
	return 0;
}

 运行错误:

 首先一样的,创建了一个 p 空对象,然后对空对象 P 当中的print()函数进行访问,在Print()函数调用的时候,会把 p 的地址传进去,传递一个空指针不会报错。

 第一种不会报错是因为,没有对空对象当中的成员进行访问,他访问的成员函数,成员函数是存储在 公共区当中的,不是在对象当中,不会发生解引用。

 而在第二种当中,Print()函数对空对象当中的 _a  成员变量进行了访问,然后函数中就会把 _a这个变量之前加一个  this->  表示引用,这时候的引用就是空指针引用,就会报错。

 而上述   p->Print()   并没有解引用,因为 对象当中不存储 成员函数,成员函数存储在公共区当中,此处只是在公共区当中去找到这个函数,然后  call  (调用)这个函数。

 

 当然,使用  "  ::  "  来替代  "   ->   "  来使用也是不行的,因为 "  ::    "  前应该写的是 作用域,而类域当中的 成员变量和成员函数都是没有存储在其中的, 类只是一个模板,创建某一种对象的模板。

 

 this 存储 在 哪里?(对象里面?栈?堆?静态区?常量区?)

 首先不能在对象里面,因为之前我们在计算对象的大小的时候,是没有这this指针的大小的。

因为这个this 是作为函数的参数传入进去的,那么这个this 就要 压栈,那么这个this 指针是更普通参数引用 存储在函数栈帧当中的,也就是存储在栈上的。

在VS下,因为this要频繁的进行调用,所以VS对这个this 存储的位置进行了优化:

 他把 this 指针存储在了  ecx  当中,ecx是寄存器。

C语言和C++实现Stack的对比

 C语言实现:

  • 每个函数的参数都是  Stack*  ,都是通过指针来操作的,调用时候,必须传入Stack指针。
  • 每个函数第一步都需要判断,传入函数的  Stack*  等等这些参数是否为NULL。

 而且在主函数当中调用这些函数的时候,命名都是  数据结构的类型 + 函数操作:

 我们发现,C当中实现 Stack 都需要用指针来实现,在链表当中有得地方甚至要用要 二级指针,这样不管是在实现,还是在 使用的时候,都很容易出错。

 C++实现:

C++当中 是把 Stack 当中的 函数和 结构的变量定义到一起,如果把成员的访问权限打开,那么对于像 栈,顺序表这些甚至可以直接访问创建对象的当中的成员。在类当中,我们可对 类当中的方法和 成员,根据 我们想不想要用去去访问,利用访问权限进行封装:

 而且在主函数当中去 使用其中的方法的时候,显得很方便,不用再去 复杂的命名,而且 调用的时候不再使用指针:

int main()
{
Stack s;
s.Init();

s.Push(1);
s.Push(2);
s.Push(3);
s.Push(4);
printf("%d\n", s.Top());
printf("%d\n", s.Size());
s.Pop();
s.Pop();
printf("%d\n", s.Top());
printf("%d\n", s.Size());
s.Destroy();
return 0;
}

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

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

相关文章

springboot入门和yaml数据格式和读取yaml型数据和多环境配置和命令行启动参数设置

springboot入门 搞掉了手动的spring&#xff0c;mybatis&#xff0c;springmvc配置类&#xff0c;只需要创建一个控制类即可 控制类&#xff1a; package com.itjh.controller;import org.springframework.web.bind.annotation.*;RestController RequestMapping("/book…

KDYZ-YM压敏电阻测试仪

一、概述 晶闸管的伏安特性是晶闸管的基本特性&#xff0c;这项特性的好坏&#xff0c;直接影响到器件在整机上的正常使用。因此&#xff0c;检测晶闸管的伏安特性在晶闸管器件的生产、经销及使用过程中都是十分重要的。 该测试仪的测试方法符合国标JB/T7624-94《整流二极管测试…

AI:人工智能领域AI工具产品集合分门别类(文本类、图片类、编程类、办公类、视频类、音频类、多模态类)的简介、使用方法(持续更新)之详细攻略

AI&#xff1a;人工智能领域AI工具产品集合分门别类(文本类、图片类、编程类、办公类、视频类、音频类、多模态类)的简介、使用方法(持续更新)之详细攻略 导读&#xff1a;由于ChatGPT、GPT-4近期火爆整个互联网&#xff0c;掀起了人工智能相关的二次开发应用的热潮&#xff0c…

MySQL 的 Replace into 与 Insert into on duplicate key update 真正的不同之处

相同点&#xff1a; &#xff08;1&#xff09;没有key的时候&#xff0c;replace与insert .. on deplicate udpate相同。 &#xff08;2&#xff09;有key的时候&#xff0c;都保留主键值&#xff0c;并且auto_increment自动1。 不同点 有key的时候&#xff0c;replace是dele…

Python数据结构与算法-RAS算法(p96)

一、RSA加密算法简介 1、加密算法概念 传统密码: 加密算法是秘密的 现代密码系统:加密算法是公开的&#xff0c;密钥是秘密的&#xff1b;&#xff08;密钥可能是随机生成的&#xff0c;与他人不一致&#xff09; 对称加密—加密和解密用的同一个密钥 非对称加密—加密和解密用…

Kali下部署-Nessus漏扫工具

Nessus 是全世界最多人使用的系统漏洞扫描与分析软件。总共有超过75,000个机构使用Nessus 作为扫描该机构电脑系统的软件。 特点&#xff1a; 1、提供完整的电脑漏洞扫描服务&#xff0c;并随时更新漏洞库。 2、可以在本机或者是远端上进行遥控&#xff0c;进行系统的漏洞扫…

深入理解AMQP协议

一.AMQP 是什么 AMQP&#xff08;Advanced Message Queuing Protocol&#xff0c; 高级消息队列协议&#xff09;是一个提供统一消息服务的 应用层标准高级 消息队列协议&#xff0c;是 应用层协议的一个 开放标准,为面向消息的中间件设计&#xff0c;是一个进程间传递 异步消息…

线性模型的介绍

一、背景 在一个理想的连续世界中&#xff0c;任何非线性的东西都可以被线性的东西来拟合&#xff0c;所以理论上线性模型可以模拟物理世界中的绝大多数现象。 线性模型&#xff08;Linear Model&#xff09;是机器学习中应用最广泛的模型&#xff0c;指通过样本特征的线性组…

生产力提速增效的4大敲门砖

引言&#xff1a; 本文章将分四大板块介绍提高程序员生产力的方案&#xff0c;最大化利用你的IDE &#xff0c;其中Live Template篇&#xff0c;插件篇非常值的一看&#xff0c; 用好才能提速增效 Productity Guide篇 Postfix Completion篇 Live Template篇 插件篇 Product…

NGFW的protal认证实验

实验topo 用到工具&#xff1a;ensp&#xff0c;kali&#xff0c;cloud云的网段是192.168.43.0&#xff1b;连接cloud的g0/0/0地址就是你登录web&#xff0c;protal的地址 实验说明&#xff1a;建议不在真机上面配置直接用&#xff0c;因为真机不稳定。这里用kali当真机&#x…

【网络应用开发】实验5—— JDBC数据库访问与DAO设计模式

目录 JDBC数据库访问与DAO设计模式预习报告 一、实验目的 二、实验原理 三、实验预习内容 1. JDBC常用的类对象与接口有哪些&#xff1f;它们的功能如何&#xff1f; 2.使用数据源访问数据库的基本思想是什么&#xff1f;这样做有什么好处&#xff1f; 3.什么是DAO&am…

vscode使用虚拟环境

我的conda没有添加入path&#xff0c;每次打开总是报错 一、选择对应虚拟环境的解释器 1.点击vscode的右下角这里 2.点击后可能会在vscode上方出现下图样子&#xff0c;如果出现下图&#xff0c;则点击第二项Select at workspace level&#xff0c; 3.接着出现下图样式&#…

2022年营收破百亿,零跑汽车展现超强实力

此前&#xff0c;零跑已正式公布了2022年的财务数据。可以看到&#xff0c;零跑去年的营收破百亿&#xff0c;增速将近300%&#xff0c;这一成绩在汽车界是相当优越的。说到为何零跑能够实现如此快速的成长&#xff0c;那就不得不提其全域自研的核心优势。 如今&#xff0c;无论…

有始有终的编码原则

基本情况 在程序员的修炼之道之中&#xff0c;说到&#xff1a; 这个建议能简单地应用到大多数场合。简单说就是&#xff0c;分配资源的函 数或对象&#xff0c;对释放资源应负有责任。 这其实就是我们常说的谁分配的就谁负责释放&#xff0c;这也是内存释放的一个原则&#x…

微搭低代码实现投票功能

经常有一类需求&#xff0c;就是投票的功能&#xff0c;需要限制每一个选项每个人只可以投一票&#xff0c;投完之后需要统计票数。本篇教程我们讲解一下如何利用微搭低代码工具来实现投票功能。 1 设计数据源 我们需要设计一个数据源来记录用户的投票&#xff0c;如何限制用…

Docker网络模式详解

文章目录 一、docker网络概述1、docker网络实现的原理1.1 随机映射端口( 从32768开始)1.2 指定映射端口1.3 浏览器访问测试 二、 docker的网络模式1、默认网络2、使用docker run 创建Docker容器时&#xff0c;可以用--net或--network 选项指定容器的网络模式 三、docker网络模式…

代码审计实战3-android java

jks java keystore 作用&#xff1a;保证应用的唯一性 简介&#xff1a;可以理解为java的密钥库&#xff0c;是一个用来存放密钥和证书的仓库。 &#xff08;而keytool就是密钥和证书的管理工具&#xff0c;它把key&#xff08;密钥&#xff09;和certificate&#xff08;证…

一零五六、Jsp+mysql 实现学生选课系统(附源码及数据库)

目录 实现效果 项目代码 数据库 结语 实现效果 login.jsp index.jsp course_query.jsp course_selection.jsp course_withdraw.jsp selection_query.jsp 项目代码 checkSelectionStatus.jsp % page contentType"text/html;charsetUTF-8" language"java&q…

图像处理:均值滤波算法

目录 前言 概念介绍 基本原理 Opencv实现中值滤波 Python手写实现均值滤波 参考文章 前言 在此之前&#xff0c;我曾在此篇中推导过图像处理&#xff1a;推导五种滤波算法&#xff08;均值、中值、高斯、双边、引导&#xff09;。这在此基础上&#xff0c;我想更深入地研…

4月23号软件更新资讯合集.....

微软发布 Web 渲染引擎 Babylon.js 6.0 Babylon.js 是一个强大、简单、开放的游戏和 Web 渲染引擎&#xff0c;并被封装在一个友好的 JavaScript 框架中。 Babylon.js 6.0 带来了性能改进、渲染增强和一系列新功能。 新物理插件 Havok 团队通过一个特殊的新 WASM 插件和对 Ba…