10其他内容补充

news2024/11/28 22:51:30

如何生成随机数+原理详细分析

文章目录

  • 如何生成随机数+原理详细分析
    • 原理
    • 如果使用相同的随机数种子,得到的随机数序列会是相同的吗
    • 示例
    • 为什么需要随机数种子
  • 动态内存管理
    • 前言
    • malloc函数
    • calloc函数
    • realloc函数
    • free函数 - 避免内存泄漏
    • 常见的动态内存错误

原理

说到如何生成一个随机数,可能当你百度后会看到这样一段代码。

srand((unsigned int)time(NULL));
int ret = rand();
12

那么一个随机数到底是如何生成的呢?我相信善于探索的你一定想知道这其中的原理,那么话不多说,进入正题把!
一个随机数到底是如何生成的:
说到生成随机数我们都知道要用到一个rand函数,那么这个函数究竟是如何运用的呢,我们可以打开MSDN看看这个函数的用法
在这里插入图片描述

这里的第一句说到rand函数会返回一个从0到RAND_MAX的整型,那么RAND_MAX的值是多少呢,我们可以将它复制到编译器中然后选中它右击鼠标点击转到定义,就可以看到这句话
在这里插入图片描述

其实RAND_MAX的值也就是0x7fff,转换为十进制也就是32767,所以说rand函数可以随机生成一个0到32767的整型,当你在编译器中尝试时,你会看到
在这里插入图片描述

当你天真的以为你已经成功的可以生成随机数的时候,你会发现当年再次运行该代码时,它生成的还是这些随机数,也就是第一次运行代码时代码生成了随机数,但是第二次运行时会生成相同的随机数。

这时我们应该想起对rand函数的描述中还有第二句话:在调用rand函数之前,我们要使用srand函数设置生成随机数的起点。我们又在MSDN中查查srand函数:
在这里插入图片描述

我们可以看到srand函数的参数是一个无符号整型并且无返回值,那么这时我们可以来测试一下,就随便给一个无符号整型传给srand函数
在这里插入图片描述

但是当我们再次执行程序时照样还是这些随机数,当我们把传入srand函数的改变时,发现所给随机数便改变了:
在这里插入图片描述

所以我们只要在每次执行程序的时候给srand函数传入一个与上一次不同的数即可,但是我们就是要生成一个随机数,现在又需要一个随机数,这不成死循环了吗?

这时我们想到在电脑上有一个东西是时刻在发生着变化的,那就是时间,这时我们需要介绍一个概念,那就是时间戳。
时间戳: 当前时间与计算机起始时间的差值,单位是秒。
计算机的起始时间:1970-01-01 08:00:00
在这里插入图片描述

每一秒的时间戳都不一样,所以我们只要把时间戳传入srand函数即可,这时我们就需要用到time函数,因为time函数的返回值就是时间戳。
在这里插入图片描述

这里我们可以看到time函数的参数是time_t型指针,返回值是time_t型,这里的time_t我们也可以把它放到编译器中右击鼠标,点击转到定义:
在这里插入图片描述

这里我们可以看到,其实time_t就是int型被typedef重定义了(也就是起了个别名)而已。
而我们也不需要向time函数传入什么指针,于是我们就向time函数传入一个空指针( NULL)即可,也就是time( NULL),但是srand函数的参数是unsigned int型,所以我们如果要将time函数的返回值传入srand函数,那么我们就需要将time函数的返回值强制性转化会unsigned int型,也就是( unsigned int )time( NULL),所以我们最终将代码写为:
在这里插入图片描述

这样,每次运行代码时所得到的就是真正意义上的随机数了。
如何生成规定位数的随机数:
生成两位随机数:
在这里插入图片描述

我们只需要将所得随机数对90取余数,那么我们得到的数就是0-89的数字,这时再加上10便是10-99的数字了。
生成三位随机数:
在这里插入图片描述

道理与生成两位随机数相同我就不再阐述了。

如果使用相同的随机数种子,得到的随机数序列会是相同的吗

是的,如果使用相同的随机数种子,并且使用相同的伪随机数生成器(PRNG)算法,在相同的情况下运行,将得到相同的随机数序列。

这是因为伪随机数生成器的工作方式是基于确定性算法。给定一个种子值,这些算法会按照预定的方式计算出一系列看似随机的数字。虽然这些数字在统计上满足随机性的一些性质,但它们实际上是可预测的。只要输入相同的种子,就会产生相同的输出。

这种特性对于调试和复现问题非常有用,因为如果你知道使用了什么种子,就可以再次生成相同的随机数序列来帮助诊断问题。然而,这也意味着为了保证每次运行程序时都能获得不同的随机数,通常需要使用当前时间或其他难以预测的值作为种子。

示例

在C语言中,可以使用rand()函数生成随机数。以下是一个简单的示例:

#include <stdio.h>
#include <stdlib.h> // 包含 rand() 和 srand()
#include <time.h>   // 包含 time()

int main() {
    // 使用当前时间作为随机数种子,确保每次运行时的随机性
    unsigned int seed = (unsigned) time(NULL);
    srand(seed);

    // 生成一个0到RAND_MAX之间的随机整数
    int random_number = rand();

    printf("Random number: %d\n", random_number);

    return 0;
}

这个程序首先包含必要的头文件:stdio.h用于输入输出操作,stdlib.h包含了rand()srand()函数,而time.h则包含了获取系统时间的time()函数。

接着,它使用time(NULL)来获取当前的时间(以秒为单位),并将这个值作为seed传递给srand()函数。这样,每次运行程序时,由于时间总是不同的,所以seed也会不同,从而使得rand()生成的随机数序列也不同。

然后,程序调用rand()函数生成一个随机数,并将其存储在random_number变量中。最后,它打印出这个随机数。

注意,rand()生成的随机数是介于0和RAND_MAX之间的整数,其中RAND_MAX是一个宏定义,其典型值为32767。如果你需要生成特定范围内的随机数,你可以通过取模运算和位移等方法进行调整。

例如,如果你想生成1到10之间的随机数,你可以这样做:

// 生成1到10之间的随机数
int random_number_in_range = (rand() % 10) + 1;

这里,我们先使用rand() % 10得到一个0到9的随机数,然后再加1,使其变为1到10的随机数。

为什么需要随机数种子

设置随机数种子的主要目的是为了保证程序每次运行时产生相同的随机数序列1。这在复现实验结果和调试代码时非常有用2。例如,在深度学习中,我们往往会用到随机向量,随机矩阵,这使得我们每次运行算法计算出来的结果是不一致的,会为我们调试算法带来麻烦1。基于随机种子来实现代码中的随机方法,能够保证多次运行此段代码能够得到完全一样的结果,即保证结果的可复现性1。

此外,随机种子的工作原理是这样的:所有的随机数算法在初始化阶段都需要一个随机“种子”(random seed),完全相同的种子每次将产生相同的“随机”数序列1。如果我们没有手动进行显式设置,系统则默认根据时间来选择这个值,此时每次生成的随机数因时间差异而不同1。所以,只要我们的运行环境一致(保证伪随机数生成程序一样),而我们设定的随机种子一样的话,那么我们就可以复现结果3。这样别人跑你的代码的时候也能够很好地复现出你的结果1。这就是为什么需要设置随机数种子的原因。希望这个解释对你有所帮助!

动态内存管理

前言

如果我们被问道:如何创建一个可以根据用户需求来开辟大小的数组?
可能有些博友会写出如下代码:

#include <stdio.h>
int main()
{
	int n = 0;
	scanf("%d", &n);
	int arr[n];
	return 0;
}
12345678

这个代码在C99标准下是可以运行的,但大多数编译器并不支持C99标准,所以这种代码缺乏了跨平台性(可移植性),那么我们有没有办法写出一个既可以满足题目要求,又可以在任何一个编译器下都编译得过去的代码呢?

答案是肯定的。这就和C语言中的动态内存的开辟有关了,动态开辟,即可以按照需求开辟内存的大小。

下面,我向博友们介绍几个操作动态内存的常用函数。

注:下面介绍的几个函数的操作对象都是堆区的内存。

扩展:局部变量存放在内存中的栈区;全局变量、静态变量(static修饰的变量)存放在内存中的静态区(也叫数据段)。

malloc函数

void *malloc( size_t size );
1

malloc函数的功能是开辟指定字节大小的内存空间,如果开辟成功就返回该空间的首地址,如果开辟失败就返回一个NULL。传参时只需传入需要开辟的字节个数。

假设我们要开辟一个可以存放10个整型的空间:

#include <stdio.h>
#include <stdlib.h>
int main()
{
	int* p = (int*)malloc(10 * sizeof(int));
	//因为malloc函数的返回值为void*,所以需要强制类型转换为对应类型。
	if (p == NULL)
	{
		printf("内存开辟失败\n");
	}
	else
	{
		printf("内存开辟成功\n");
		//使用...
		//使用结束,释放内存(后面介绍)
		free(p);
		p = NULL;
	}
	return 0;
}
1234567891011121314151617181920

注:malloc函数开辟好空间后,不对空间内容做任何初始化,所以空间内的数据为随机值。

calloc函数

void *calloc( size_t num, size_t size );
1

calloc函数的功能也是开辟指定大小的内存空间,如果开辟成功就返回该空间的首地址,如果开辟失败就返回一个NULL。但calloc函数传参时需要传入两个参数(开辟的内存用于存放的元素个数和每个元素的大小)。

calloc函数与malloc函数的用法也是大同小异:

#include <stdio.h>
#include <stdlib.h>
int main()
{
	int* p = (int*)calloc(10 , sizeof(int));
	//开辟一个可以存放10个整型的内存空间
	if (p == NULL)
	{
		printf("内存开辟失败\n");
	}
	else
	{
		printf("内存开辟成功\n");
		//使用...
		//使用结束,释放内存(后面介绍)
		free(p);
		p = NULL;
	}
	return 0;
}
1234567891011121314151617181920

注:calloc函数与malloc函数的最大区别在于:calloc函数开辟好内存后会将空间内容中的每一个字节都初始化为0。

realloc函数

void *realloc( void *memblock, size_t size );
1

realloc函数可以调整已经开辟好的动态内存的大小,第一个参数是需要调整大小的动态内存的首地址,第二个参数是动态内存调整后的新大小。

realloc函数与上面两个函数一样,如果开辟成功便返回开辟好的内存的首地址,开辟失败则返回NULL。

如果我们要将已开辟的动态内存大小做一些调整,我们会这样做:

#include <stdio.h>
#include <stdlib.h>
int main()
{
	int* p = (int*)calloc(10, sizeof(int));
	if (p == NULL)
	{
		printf("内存开辟失败\n");
	}
	else
	{
		printf("内存开辟成功\n");
		//使用...
		//扩展容量
		int* ptr = (int*)realloc(p, 100);
		//将空间扩展为100个字节大小
		if (ptr != NULL)
		{
			p = ptr;//开辟成功时
			//使用...
		}
		//使用结束,释放内存(后面介绍)
		free(p);
		p = NULL;
	}
	return 0;
}
123456789101112131415161718192021222324252627

realloc函数调整动态内存大小的时候会有三种情况:
假如我们要将一个大小为50个字节的空间调整为100个字节。
情况一:
在这里插入图片描述

此时,realloc函数直接在原空间后方进行扩展,并返回该内存空间首地址(即原来的首地址)。

情况二:
在这里插入图片描述

此时,realloc函数会在堆区中重新找一块满足要求的内存空间,把原空间内的数据拷贝到新空间中,并主动将原空间内存释放(即还给操作系统),返回新内存空间的首地址。

情况三:
在这里插入图片描述

此时,需扩展的空间后方没有足够的空间可供扩展,并且堆区中也没有符合需要开辟的内存大小的空间。结果就是开辟内存失败,返回一个NULL。

free函数 - 避免内存泄漏

void free( void *memblock );
1

free函数的作用就是将malloc、calloc以及realloc函数申请的动态内存空间释放,其释放空间的大小取决于之前申请的内存空间的大小。

其使用方式非常简单,就是在使用完后加上两个语句:

	free(p);//p为要释放的代码块的首地址
	p = NULL;//必不可少
12

我们也已经看到了,上面每一个开辟了动态内存的代码,在使用完该动态内存后,都将该内存空间释放了(即还给操作系统)。

如果在使用完动态内存后忘记将其空间释放,便会造成内存泄漏的问题:

  • 内存泄漏(MemoryLeak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
  • 内存泄漏缺陷具有隐蔽性、积累性的特征,比其他内存非法访问错误更难检测。因为内存泄漏的产生原因是内存块未被释放,属于遗漏型缺陷而不是过错型缺陷。此外,内存泄漏通常不会直接产生可观察的错误症状,而是逐渐积累,降低系统整体性能,极端的情况下可能使系统崩溃。

注意:

  • 在释放代码块后,必须将该代码块的首地址改为NULL,否则该指针将变为野指针,我们都知道,野指针非常危险。
  • 如果传入free函数的为空指针(NULL),则free函数什么也不做。

常见的动态内存错误

一、对NULL指针进行解引用操作
我们都知道,不能对NULL指针进行解引用操作,但是如果我们在调用了malloc、calloc以及realloc函数之后,没有检测返回的指针的有效性(即是否为NULL指针),那我们在后面使用该指针的时候就可能会导致对NULL指针进行解引用操作。

二、对动态开辟空间的越界访问
我们需要时刻注意,不能访问未申请的动态内存空间。比如你向动态内存申请了10个字节,那就绝不能访问第11个字节。

三、对非动态开辟的内存使用free释放
free函数只能释放动态开辟的内存空间。

四、使用free释放动态开辟内存的一部分
free函数只能从开辟好的动态内存空间的起始位置开始释放,所以使用free函数释放动态内存时,传入的指针必须是当时开辟内存时返回的指针。

五、对同一块内存多次释放
对同一块动态内存空间只能释放一次。避免这个问题的出现也很简单,我们只要记住在第一次释放完空间后立即将该指针置为NULL即可,因为当传入free函数的指针为NULL指针时,free函数什么也不做(也就不会出现对同一内存多次释放的问题)。

六、动态开辟内存忘记释放(内存泄漏)
一定要做到自己开辟的动态内存自己记得释放,也许你觉得这件事没什么,但当你需要从几十万甚至几百万行代码中找出一个因忘记释放动态内存而造成的内存泄漏问题时,你就会真正知道这件事的重要性。

free函数只能从开辟好的动态内存空间的起始位置开始释放,所以使用free函数释放动态内存时,传入的指针必须是当时开辟内存时返回的指针。

五、对同一块内存多次释放
对同一块动态内存空间只能释放一次。避免这个问题的出现也很简单,我们只要记住在第一次释放完空间后立即将该指针置为NULL即可,因为当传入free函数的指针为NULL指针时,free函数什么也不做(也就不会出现对同一内存多次释放的问题)。

六、动态开辟内存忘记释放(内存泄漏)
一定要做到自己开辟的动态内存自己记得释放,也许你觉得这件事没什么,但当你需要从几十万甚至几百万行代码中找出一个因忘记释放动态内存而造成的内存泄漏问题时,你就会真正知道这件事的重要性。

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

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

相关文章

实现TCP Connect的断线重连机制:策略与实践

&#x1f351;个人主页&#xff1a;Jupiter. &#x1f680; 所属专栏&#xff1a;Linux从入门到进阶 欢迎大家点赞收藏评论&#x1f60a; 断线重连机制&#xff0c;它成为确保应用在网络不稳定情况下仍能持续提供服务的关键技术之一。本文旨在深入探讨TCP&#xff08;传输控制协…

浅聊前后端分离开发和前后端不分离开发模式

1.先聊聊Web开发的开发框架Spring MVC 首先要知道&#xff0c;Spring MVC是Web开发领域的一个知名框架&#xff0c;可以开发基于请求-响应模式的Web应用。而Web开发的本质是遵循HTTP&#xff08;Hyper Text Transfer Protocol: 超文本传输协议&#xff09;协议【发请求&#xf…

仿RabbitMQ实现消息队列客户端

文章目录 客⼾端模块实现订阅者模块信道管理模块异步⼯作线程实现连接管理模块生产者客户端消费者客户端 客⼾端模块实现 在RabbitMQ中&#xff0c;提供服务的是信道&#xff0c;因此在客⼾端的实现中&#xff0c;弱化了Client客⼾端的概念&#xff0c;也就是说在RabbitMQ中并…

V2M2引擎源码BlueCodePXL源码完整版

V2M2引擎源码BlueCodePXL源码完整版 链接: https://pan.baidu.com/s/1ifcTHAxcbD2CyY7gDWRVzQ?pwdmt4g 提取码: mt4g 参考资料&#xff1a;BlueCodePXL源码完整版_1234FCOM专注游戏工具及源码例子分享

图解大模型计算加速系列:vLLM源码解析3,块管理器(BlockManager)上篇

vllm块管理器又分成朴素块管理器&#xff08;UncachedBlockAllocator&#xff09;和prefix caching型块管理器&#xff08;CachedBlockAllocator&#xff09;。本篇我们先讲比较简单的前者&#xff0c;下篇我们来细看更有趣也是更难的后者。 【全文目录如下】 【1】前情提要…

阿里巴巴开源的FastJson 1反序列化漏洞复现攻击保姆级教程

免责申明 本文仅是用于学习检测自己搭建的靶场环境有关FastJson1反序列化漏洞的原理和攻击实验,请勿用在非法途径上,若将其用于非法目的,所造成的一切后果由您自行承担,产生的一切风险和后果与笔者无关;本文开始前请认真详细学习《‌中华人民共和国网络安全法》‌及其所在…

Linux高级编程_29_信号

文章目录 进程间通讯 - 信号信号完整的信号周期信号的编号信号的产生发送信号1 kill 函数(他杀)作用&#xff1a;语法&#xff1a;示例&#xff1a; 2 raise函数(自杀)作用&#xff1a;示例&#xff1a; 3 abort函数(自杀)作用&#xff1a;语法&#xff1a;示例&#xff1a; 4 …

macos安装git并连接gitCode远程仓库

文章目录 资料地址下载和安装初始化配置本地全局配置&#xff0c;SSH公私密钥生成远程SSH key配置 新建代码仓库&#xff0c;并关联到本地 资料地址 git官网地址gitCode地址 下载和安装 打开git官网地址&#xff0c;直接下载。【不建议使用brew&#xff0c;因为本人实践&…

【Qt】控件概述(2)—— 按钮类控件

控件概述&#xff08;2&#xff09; 1. PushButton2. RadioButton——单选按钮2.1 使用2.2 区分信号 clicked&#xff0c;clicked(bool)&#xff0c;pressed&#xff0c;released&#xff0c;toggled(bool)2.3 QButtonGroup分组 3. CheckBox——复选按钮 1. PushButton QPushB…

B树系列解析

我最近开了几个专栏&#xff0c;诚信互三&#xff01; > |||《算法专栏》&#xff1a;&#xff1a;刷题教程来自网站《代码随想录》。||| > |||《C专栏》&#xff1a;&#xff1a;记录我学习C的经历&#xff0c;看完你一定会有收获。||| > |||《Linux专栏》&#xff1…

etcd 快速入门

简介 随着go与kubernetes的大热&#xff0c;etcd作为一个基于go编写的分布式键值存储&#xff0c;逐渐为开发者所熟知&#xff0c;尤其是其还作为kubernetes的数据存储仓库&#xff0c;更是引起广泛专注。 本文我们就来聊一聊etcd到底是什么及其工作机制。 首先&#xff0c;…

查找回收站里隐藏的文件

在Windows里&#xff0c;每个磁盘分区都有一个隐藏的回收站Recycle&#xff0c; 回收站里保存着用户删除的文件、图片、视频等数据&#xff0c;比如&#xff0c;C盘的回收站为C:\RECYCLE.BIN\&#xff0c;D盘的的回收站为D:\RECYCLE.BIN\&#xff0c;E盘的的回收站为E:\RECYCLE…

【解决方案】JVM调优:给定资源条件下减少Full GC频率

1 缘起 在一次其他团队技术分享时,有幸进行了旁听, 谈到一个应用场景,服务端在给定的资源下,频繁Full GC, 降低了服务请求处理能力以及任务处理能力,频繁Full GC,导致服务处理能力下降, 服务在Full GC期间无法处理用户请求以及其他任务,服务不稳定,可以理解为服务在…

【C++算法】9.双指针_四数之和

文章目录 题目链接&#xff1a;题目描述&#xff1a;解法C 算法代码&#xff1a;图解 题目链接&#xff1a; 18.四数之和 题目描述&#xff1a; 解法 解法一&#xff1a;排序暴力枚举利用set去重 解法二&#xff1a;排序双指针 从左往右依次固定一个数a在a后面的区间里&#x…

坐标系变换总结

二维情况下的转换 1 缩放变换 形象理解就是图像在x方向和y方向上放大或者缩小。 代数形式&#xff1a; { x ′ k x x y ′ k y y \begin{cases} x k_x x \\ y k_y y \end{cases} {x′kx​xy′ky​y​ 矩阵形式&#xff1a; ( x ′ y ′ ) ( k x 0 0 k y ) ( x y ) \be…

【C语言】数据在内存中的存储(万字解析)

文章目录 一、大小端字节序和字节序判断1.案例引入2.什么是大小端字节序3.大小端字节序判断 二、整数在内存中的存储以及相关练习1.整型在内存中的存储2.练习练习1&#xff1a;练习2练习3练习4练习5&#xff1a;练习6 三、浮点数在内存中的存储1.案例引入2.浮点数在内存中的存储…

uniapp+Android面向网络学习的时间管理工具软件 微信小程序

目录 项目介绍支持以下技术栈&#xff1a;具体实现截图HBuilderXuniappmysql数据库与主流编程语言java类核心代码部分展示登录的业务流程的顺序是&#xff1a;数据库设计性能分析操作可行性技术可行性系统安全性数据完整性软件测试详细视频演示源码获取方式 项目介绍 用户功能…

KVM虚拟化技术介绍

文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 前言 虚拟化技术是云计算的基础&#xff0c;什么是虚拟化&#xff1f;虚拟化技术的本质是什么&#xff1f;主流的虚拟化技术有哪些&#xff1f;本章将为您揭晓 一.虚拟化概述 虚拟化是一种将计…

【有啥问啥】领域自适应(Domain Adaptation, DA)详解

领域自适应&#xff08;Domain Adaptation, DA&#xff09;详解 引言 在机器学习和深度学习的广泛应用中&#xff0c;一个核心挑战在于模型往往在一个特定数据集&#xff08;源领域&#xff09;上训练后&#xff0c;难以直接应用于另一个不同但相关的数据集&#xff08;目标领…

通信工程学习:什么是ICMP因特网控制报文协议

ICMP&#xff1a;因特网控制报文协议 ICMP&#xff08;Internet Control Message Protocol&#xff0c;因特网控制报文协议&#xff09;是TCP/IP协议簇中的一个重要子协议&#xff0c;主要用于在IP主机和路由器之间传递控制消息。以下是关于ICMP协议的详细解释&#xff1a; 一…