内存
1.什么是内存?
-
内存也叫主存储器,用于临时存储数据和程序指令,便于CPU(也就是处理器)快速访问
-
读写非常快,对比硬盘等外存,一秒一般 \(10^8\) 次读写,下面是一个输出一秒大致多少次运算的程序
int main() {//clock_t 是一个用来表示CPU时钟的变量 clock()函数是获取程序运行时间的函数clock_t start_t=clock(); // 记录开始时间start_tclock_t time; //运行时间timelong long cnt = 0;while(true){clock_t end_time=clock(); //每层循环都记录一次时间time = end_time - start_time; //用开始的时间减去当前时间就是CPU运行时间if((double)(time)/CLOCKS_PER_SEC >= 1)break;//如果时间大于一秒,就break。CLOCKS_PER_SEC是一个单位,这个时间time单位还是毫秒cnt++;}cout<<(double)(time)/CLOCKS_PER_SEC<<"s"<<endl;//输出时间cout<<cnt<<endl;return 0; }
-
在电脑突然断电后,内存数据会丢失,数据库跑任务突然断电有丢失数据的危险,现在的数据库都有数据库管理系统来防止突然断电的情况
-
地址空间以字节为单位,每个字节都有自己独立的地址
-
这是内存的大致表示图,每个字节都是以十六进制表示出来的。0x
是一个前缀,代表后面的是十六进制的数
2.各个数据类型所占用的空间大小:
int
类型:4字节
long long
类型:8字节
double
类型:8字节
float
类型:4字节
bool
类型:1字节
char
类型:1字节
汉字字符类型:不同编码方式大小不同,UTF-8编码中占3字节,GBK编码中占2字节
cout<<sizeof(int)<<'\n';
cout<<sizeof(long long)<<'\n';
cout<<sizeof(double)<<'\n';
cout<<sizeof(float)<<'\n';
cout<<sizeof(bool)<<'\n';
cout<<sizeof(char)<<'\n';
cout<<sizeof("a")<<'\n';
sizeof函数:
获取大小
memset(arr,0,sizeof(arr));
memset(arr,0x3f,sizeof(arr));
//注意,初始化值是以字节为单位把每个字节初始化为0x3f
memset(arr,0x3f,sizeof(arr));
for(int i = 0;i < 100;i ++) cout<<arr[i]<<" ";
cout<<endl;
cout<<"0x3f3f3f3f代表的值为:"<<0x3f3f3f3f<<endl;
3. 内存分区以及程序运行过程中,各个分区占用情况
内存可以分为5个区域
-
栈区
用于放局部变量、参数、返回地址,在函数结束时释放
-
堆区
用于动态分配内存,由我们手动释放,比如malloc和new
-
数据段
存放已初始化的全局变量和静态变量
-
BSS段
存放未初始化的全局变量和静态变量
在程序加载时自动初始化为0
-
代码段
存储计算机需要执行的代码,也就是我们写的程序
如何动态分配内存?
在C语言中,我们学过用malloc动态分配内存,c++里面我们可以用new一段内存出来
int *x = new int; //为指针b分配了一个整型的空间
int *array = new int[100] //新建了一个数组大小为100的整型数组
如何释放分配的内存?
注意在不需要使用内存或者函数结束的时候需要我们手动释放内存,这就需要delete函数来释放内存了
delete x;
delete[] array;
指针
-
指针是用于存储其他变量的地址的变量
int a = 10; int *p = &a; //&取地址
-
* 符号 ,解引用
- 号的使用主要在两个地方,声明指针的时候和对指针解引用的时候
什么是解引用?
解引用就是访问指针所指向的地址的数据
-
通过指针解引用来访问被指向的变量或者变量的地址
int a = 10; int *p = &a; *p = 100; cout<<a<<endl;
-
指针也能用来指向一段内存空间
int *arr = new int[100]; arr[10] = 2;
-
为什么指针算数和数组是等价的
指针算数:
int p[100]; *(p + 10) = 2; cout<<*(p + 10)<<endl;
数组:
int p[100]; p[10] = 2; cout<<p[10];
等价的原因是在c++内部处理指针算数的方式
我们对一个指针变量+1,指针增加的数量等于它指向的变量类型的字节数,在数组中的表现就是从p[0] 增加了一个整型的空间 变成了p[1];
*(p + 10)就是增加了10个整型空间变成了p[10]
-
对数组取地址
int arr[100]; cout<<arr<<endl; //这个输出的arr[0] cout<<&arr<<endl; //这个输出的是整个数组的地址,虽然输出的地址和arr[0]的地址是一样的,在含义上它代表的是整个数组的地址
*arr指针的数据类型是int **
&arr的数据类型是 int (*) [10]
两者虽然输出一样但是含义不一样
-
野指针
野指针是指没有初始化的指针变量,没有指向合法的内存空间
-
悬空指针
悬空指针是指针指向的变量被delete掉了
int *p = new int;int *pp = p;delete p;
这里我们为p分配了一个int大小的空间,让pp指向指针p,然后我们把这段空间释放掉,pp就没有指向了,成为了悬空指针,也是一种野指针
-
指针数组
int* p[10]; int a = 100,b = 20,c = 30; p[0] = &a,p[1] = &b,p[2] = &c;
和int p[10] 一样,int* 表示的后面数组p[10]的数据类型,数组每一个位置都是一个指针
-
数组指针
数组指针就是指向数组的指针
int arr[20][20]; int (*p)[10] = arr;//意思是p这个指针指向的空间大小是10
这个与指针数组的区别就是在* p这里多了一个括号,但是含义就差别很大了
-
指针函数
指针函数是返回值是指针的函数,有了这个我们可以方便的动态分配内存给数组
int* getArray(int size) {int *arr = new int[size];return arr; }
-
函数指针
#include<iostream> using namespace std;int add(int a,int b) // 两数相加函数 {return a + b; }int subtract(int a,int b) //两数相减函数 {return a - b; }int multiply(int a,int b)//两数相乘函数 {return a * b; }int main() {int (*p)(int ,int) = NULL;char c;cin>>c;switch(c){case('-'):p = subtract;break;case('+'):p = add;break;case('*'):p = multiply;break;};int a,b;cin>>a>>b;cout<<p(a,b);return 0; }
函数指针定义格式是:
函数类型 ( 指针名) (指向的函数的数据类型)* 以函数int add(int a,int b)举例,指针就是 int ( p) (int, int) = add;*
和指针函数的区别:
- 和指针函数相比多了个括号 如果少了括号就容易被识别成函数指针
- 括号里面只有变量类型,没有变量名
引用
-
概念:引用是为已存在的变量取了一个别名,引用和引用的变量共用同一块内存空间**
例子:int &a = b;后面直接可以用a代替b;
-
引用的性质
- 引用在定义的时候必须初始化
- 一个变量可以有多个引用
- 一个引用也能有引用
- 引用一旦引用一个实体,不能再引用其他实体
- 可以对任意类型进行引用
引用在定义时必须初始化
int &b;int a = 10;b = a;
这种是错误的
int a = 10; int &b = a;
在定义的时候就要初始化
一个变量可以有多个引用
int a = 10; int &b = a; int &c = a;
一个引用也能有引用
int a = 10; int &b = a; int &c = b;
一个引用也能被引用
引用一旦引用一个实体,就不能再更改
int a = 10,c = 20; int &b = a; b = c;
这种是错误的
可以对任意类型进行引用
以函数的引用为例子,我们这里要引用一个add加法函数,它的两个参数都是int类型,我们定义函数引用的时候也要遵循函数的参数类型
int add(int a,int b) {return a + b; } int main() {int (&p)(int,int) = add;return 0; }
函数引用的定义方式和我们的函数指针类似,都是括号里面指针名或者引用名,后面再加上函数的变量类型