个人主页:PingdiGuo_guo
收录转栏:C++干货专栏
前言
本篇博客是讲解关于C++内存的一些知识点的。
文章目录
[1.3 memset函数](#1.3 memset函数)
1.内存函数
内存函数是在计算机程序中用来操作内存的一类函数。内存函数可以用于分配和释放内存,读取和写入内存中的数据,以及进行内存的复制和移动等操作。
在这里,我们主要介绍几种可以复制的内存函数。
1.1memcpy函数
memcpy函数是C++中的一个标准库函数,用来实现内存拷贝操作。它的原型如下:
void *memcpy(void *dest, const void *src, size_t n);
在C++中,也可以使用内存拷贝操作来复制数组元素。C++提供了memcpy函数,它与C的memcpy函数功能相同,但被包含在std命名空间中。
下面是使用memcpy实现数组元素拷贝的示例代码:
#include <iostream>
#include <cstring>
using namespace std;
int main() {
int srcArray[] = {1, 2, 3, 4, 5};
int destArray[5];
// 使用memcpy函数拷贝数组元素
memcpy(destArray, srcArray, sizeof(srcArray));// 思考:sizeof(srcArray)是什么?这个函数拷贝拷贝了多少元素?
memcpy(destArray, srcArray,20);//思考:这里可以拷贝多少元素?
//思考:这里是谁拷贝的谁?
// 打印目标数组的元素
for (int i = 0; i < sizeof(destArray) / sizeof(destArray[0]); i++) {
cout << destArray[i] << " ";
}
return 0;
}
1.sizeof(srcArray)是srcArray数组的字节大小,即sizeof(int) * 5,所以sizeof(srcArray)是20。
2.这个函数拷贝了5个元素。因为memcpy函数根据参数指定的字节数进行拷贝,sizeof(srcArray)指定了srcArray数组的字节大小,所以拷贝了整个srcArray数组的元素。
3.第二个memcpy函数使用了20作为拷贝的字节数。因为是5每个int占4个字节,所以这个函数可以拷贝5个元素。
4.这里是destArray拷贝了srcArray数组。
memcpy
函数在处理内存重叠问题时是未定义行为。也就是说,如果源内存块和目标内存块重叠,memcpy
函数可能会导致不可预测的结果。
输出结果:
1.2memmove函数
memmove与memcpy类似,但不同的地方是memmove是可以处理内存块的重叠的。它的函数原型为:
void *memmove(void *dest, const void *src, size_t count);
拷贝数组:
#include <bits/stdc++.h>
using namespace std;
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10};
memmove(arr1+2, arr1, 20);
for (int i = 0; i < 10; i++)
{
cout<<arr1[i]<<' ';
}
return 0;
}
输出结果:
这里出现了一个问题,那就是内存重叠了。
那么,我们如何处理这个内存重叠问题呢?
我们可以检查它们的内存是否重叠。
如果是,那我们就直接从后往前拷贝,否则,我们就从前往后拷贝。
图示:
src:
src
是memory source
的缩写,表示源地址,即需要被复制的内存块的起始位置。dest:
dest
是destination
的缩写,表示目标地址,即复制后的内存块的起始位置。
代码:
// 自定义 memmove 函数,解决内存重叠问题
void* me(void* d, const void* sr, size_t n) {
void* ret = d;
if (d <= sr || (char*)d >= ((char*)sr + n)) {
// 从前往后
while (n--) {
*(char*)d = *(char*)sr;
d = (char*)d + 1;
sr = (char*)sr + 1;
}
} else {
// 从后往前
d = (char*)d + n - 1;
sr = (char*)sr + n - 1;
while (n--) {
*(char*)d = *(char*)sr;
d = (char*)d - 1;
sr = (char*)sr - 1;
}
}
return ret;
}
1.3 memset函数
memset函数式将指定大小的内存块设置为给定的值。它的函数原型为:
void * memset ( void * ptr, int value, size_t num);
使用:
#include <iostream>
#include <cstring>
using namespace std;
int main() {
char str[10];
// 将str的前5个字节设置为字符 'A'
memset(str, 'A', 5);
cout << str << endl; // 输出 "AAAAA"
return 0;
}
运行结果:
2.各数据类型占用
我们可以用sizeof(数据类型)格式来计算个数据类型的占用内存大小。这里我们要了解一个知识点:
字节是计算机中的最小存储单位,通常用来表示一个字母、一个数字或者一个符号。一个字节等于8个二进制位,即8个0或1。字节是计算机中信息存储和传输的基本单位,用来表示各种数据类型和文件大小。
二进制位是计算机中的最小计数单位,用于表示数字的最基本形式。二进制位只能是0或1两种状态,用于表示八进制、十进制、十六进制等不同进制数系统的数值。计算机中的所有数据都是以二进制位的形式存储和处理的,二进制位的组合可以表示各种不同的数值和字符。8个二进制位组合在一起形成一个字节,即8位二进制位表示一个字节的数据。
2.1bool类型
代码:
cout<<sizeof(bool)<<endl;
占用一字节。
解释:
bool类型占用内存是一个字节。虽然大家可能觉得bool类型的取值范围只有true和false两种,占用内存应该很小,但是为了在内存中存储和处理bool类型的值,需要用一个字节来表示。这是因为计算机在内存中最小的存储单元就是一个字节,无法将一个布尔值存储在更小的存储单元中。因此,无论bool类型的值占用的实际位数是多少,它始终会占用1个字节的内存空间。
2.2char类型
代码:
cout<<sizeof(char)<<endl;
占用一个字节,及八位二进制位,图表:
|-----|-----|-----|-----|-----|-----|-----|-----|
| 1/0 | 1/0 | 1/0 | 1/0 | 1/0 | 1/0 | 1/0 | 1/0 |
其中,每一个二进制位的变化都可以表示一个不同的值,也就是2^8=256个值,只是当有符号和无符号时表示的范围并不相同,我们平时的所用的每一个字符在内存中都由8位2进制位来表示。
比如,字符'A'在ASCLL码中对应65,在内存则表示为:
|---|---|---|---|---|---|---|---|
| 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 |
2.3short、int、long类型及整数
short、int、long类型都是储存整数的,所以放到一块讲了。
short类型:
cout<<sizeof(short)<<endl;
占用二字节,也就是十六位二进制位,可以表示2^16=65536个值。
int类型:
cout<<sizeof(int)<<endl;
占用四字节,三十二位二进制位,可以表示2^32=4294967296个值。
long类型:
cout<<sizeof(long)<<endl;
也是占用四字节,表示三十二位二进制位,可以表示2^32=4294967296个值,和int类型一样。
整数:
整数的存储都是由原码、反码、补码来表示的:
对于整数:
原码:只要通过正负数判断即可获得原码,(正数1,负数0)
反码:在原码的基础上,对负数的各位取反。
补码:在反码的基础上,对负数的最低有效位加1。即将反码(符号位除外)加1得到补码。
注:正整数的原、反、补码是相同的。
在计算机内存中,整数通常用二进制补码的形式来储存。为什么呢?
因为计算机中通过补码运算可以实现加法、减法、乘法和除法等操作。在进行运算时,计算机会自动进行补码的转换和处理。
2.4float类型及double类型及浮点数
因为float和double都是储存浮点数的,所以归为一类了。
float类型
代码:
cout<<sizeof(float)<<endl;
占用四字节,三十二位二进制位。
这里需要了解一个表示方式,就是二进制的科学表示法:
± mantissa × 2 exponent
(mantissa:尾数,exponent:指数,均使用二进制表示)
它的储存采用了IEEE 754单精度浮点格式,存储方式如下:
|------------|-------------------|-----------------------------------------------|
| 1 bit(符号位) | 8 bit(指数位) | 23 bit(尾数位) |
| 0 | 0 1 1 1 1 1 0 0 0 | 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 |
| 30 | 31 23 | 22 0 |
这里的bit也是二进制位,是二进制位的缩写。如上图所示,该格式最高一位是符号位,0位正,1位负,后面8位为无符号整型数,表示范围为0~265,后面23位小数为,索引从22到0分别对应2^-1到2^-23。
double类型
代码:
cout<<sizeof(double)<<endl;
double型在内存中有八个字节,储存的数据较大,六十四位二进制位(bit)。他和float类型一样,都是采用的二进制的科学计数法。
它的储存采用了IEEE 754双精度浮点格式,储存方式如下:
|------------|-------------|-------------|
| 1 bit(符号位) | 11 bit(指数位) | 52 bit(尾数位) |
| 第一位为63 | 倒数第一位为52 | 倒数第一位为0 |
如上图所示,该格式最高位也为符号位,0位正,1位负,后面11位为无符号整型数,表示范围为0~2^11-1,后面52位小数为,索引从51到0分别对应2^-1到2^-52。
浮点数
浮点数的储存方式可看上面的两张图表,接下来讲一下浮点数如何转化为二进制。
步骤:
将浮点数分为整数部分和小数部分。例如,考虑浮点数12.375,整数部分为12,小数部分为0.375。
将整数部分转化为二进制。对于整数部分,可以使用短除法将其转化为二进制。例如,12转化为二进制是1100。对于短除法,这里就不过多讲述了,大家可以去查一查。
将小数部分转化为二进制。对于小数部分,可以使用乘2取整法将其转化为二进制。将小数部分乘以2,取整数部分,然后将小数部分的整数部分再乘以2,依此类推,直到小数部分为0或达到所需的精度。例如,0.375转化为二进制的过程如下:
0.375 x 2 = 0.75 -> 0//整数部分为0,继续乘2 0.75 x 2 = 1.5 -> 1//整数部分为1,舍弃整数部分,继续乘2 0.5 x 2 = 1.0 -> 1//乘得积为1,停止运算
所以,0.375转化为二进制是0.011。
4. 合并整数部分和小数部分的二进制。将步骤2和步骤3得到的二进制合并在一起,注意小数点的位置。对于上述例子,合并后的二进制是1100.011。
5. 二进制小数转化为十进制验算。比如,二进制小数1100.011=1*2**^2+0*2^2+1*2^2**+(0 * 2^-1) + (1 * 2^-2) + (1 * 2^-3) =12.375,正确。
3.学习内存有什么用
大家可能会有一些疑问,学内存知识有什么用呢?学内存有以下几个方面的作用:
- 内存管理:C++是一种低级语言,需要手动管理内存分配和释放。了解C++内存知识可以帮助我们正确地分配和释放内存,避免内存泄漏和野指针等问题,提高程序的健壮性和效率。
- 优化性能:理解C++内存模型和内存使用方式可以帮助我们优化程序性能。例如,了解内存对齐和缓存行的概念可以避免访问内存的延迟,提高程序的运行速度。
- 安全性和稳定性:内存相关的错误往往是导致程序崩溃和漏洞的主要原因之一。学习C++内存知识可以帮助我们避免常见的内存错误,提高程序的安全性和稳定性。
- 调试和错误排查:当程序出现内存相关的问题时,了解C++内存知识可以帮助我们更快地定位和修复问题,提高调试的效率。
总的来说,学习C++内存知识对于使用和开发C++程序非常重要,可以帮助我们进行内存管理、优化性能、提高安全性和稳定性,深入理解语言特性,以及进行调试和错误排查。
总结
本篇博客到这里就结束了,感谢大家的支持与观看,有好的建议欢迎留言,如果这篇博客对您有帮助,那请给PingdiGuo_guo一个免费的赞,谢谢大家啦!