目录·
cpu如何对内存条进行处理?
cpu与内存条间有三条线,分别为控制线,数据线,地址线。控制线控制数据传输的方向,数据线是数据传输的渠道,地址线是用来确定内存条单元位置。
32根地址线可以控制pow(2,32)个字节,pow(2,32)=pow(2,30)*pow(2,2)=1024 *4
1B(Byte)=8b(bit)
1K=pow(2,10)B
1M=pow(2,10)KB=pow(2,20)B
1G=1024M=pow(2,30)B
字长 == 数据总线长度 == 地址总线长度(但是实际中地址总线很多高位用不到,所以默认置0,省去了一些地址总线,总的长度是小于64的,只是64位的机器理论上可寻址的范围在2的64次方B)
指针
一、指针的重要性
表示一些复杂的数据结构
快速的传递数据
使函数返回一个以上的值
能直接访问硬件
能够方便的处理字符串
是理解面向对象语言中引用的基础
总结:指针是c语言的灵魂
二、指针的定义
1.地址
内存单元的编号
从零开始的非负整数
范围:32位->4G【0~4G-1】
64位->8G【0~8G-1】
2.指针
指针就是地址,地址就是指针
指针变量就是存放内存单元编号的变量,或者说指针变量就是存放地址的变量
指针和指针变量是两个不同的概念
但是要注意:通常我们叙述时会把指针变量简称为指针,实际他们含义并不一样
指针的本质就是一个操作受限的非负整数,地址只能进行相减运算
三、指针的分类
1、基本类型指针
代码如下:
#include<stdio.h>
int main()
{
//定义指针变量
int * p;/*p是变量名字,int *表示p变量存放的是int类型变量的地址
int * p:不表示定义了一个名字叫做*p的变量
int * p:应该这样理解:p是变量名,p变量的数据类型是int *类型
所谓int *类型就是存放int变量地址的类型
*/
int i = 3;
int j;
p = &i;
/*
1.p保存了i的地址,因此p指向i;
2.p不是i,i也不是p,更准确的说,修改p的值不影响i的值,反之亦然。
3.如果一个指针变量指向了某个普通变量,则
*指针变量 就完全等同于 普通变量
例子:
如果p是个指针变量,并且p存放了普通变量i的地址
则p指向了普通变量i
*p 就完全等同于 i
或者说: 在所有出现*p的地方都可以替换成i
在所有出现i的地方都可以替换成*p
*p 就是以p的内容为地址的变量
*/
j = *p;
printf("%d %d",*p,j);
return 0;
}
//指针就是地址,地址就是 指针
//地址就是内存单元的编号
//指针变量就是存放地址的变量
//指针和指针变量是两个不同的概念
//但是要注意:通常我们叙述时会把指针变量简称为指针,实际他们含义并不一样
附注:
*的含义
1. 乘法
2. 定义指针变量:int * p;
//定义了一个名字叫p的指针变量,int * 表示p只能存放一个int变量的地址
3.指针运算符(解地址符)
如何通过被调函数修改主调函数普通变量的值
1. 实参必须为该普通变量地址
2. 形参必须为指针变量
3. 在被调函数中通过对 *形参名 进行赋值的方式,就可以修改主调函数相关变量的值
2、指针和数组
指针和一维数组
一维数组名是一个指针常量,他存放的是一维数组第一个元素的地址
下标和指针的关系
如果p是一个指针变量,则p[i]永远等价于*(p+i)
确定一个一维数组需要几个参数
需要两个参数:
数组第一个元素的地址
数组的长度
代码如下:
#include<bits/stdc++.h>
using namespace std;
void f(int *Aprr,int len){
// for(int i=0;i<len;i++)
// cout<<*(Aprr+i)<<" ";
// cout<<endl;
Aprr[2] = 88;//Aprr[2]等价于*(Aprr+2),也等价于a[2],也等价于*(a+2)
}
int main(){
int a[3] = {1,2,3};
int b[5] = {34,23,43,12,34};
int c[100] = {12,12,234,34,12,23};
cout<<a[2]<<endl;
f(a,3);
// f(b,5);
// f(c,100);
cout<<a[2]<<endl;
return 0;
}
指针变量的运算
指针变量不能相加,不能相乘也不能相除
如果两个指针变量指向的是同一块连续空间中的不同存储单元,这两指针变量才可以相减
一个指针变量到底占多少字节
预备知识:
sizeof(数据类型)
功能:返回值就是该数据类型所占字节数
例子:sizeof(char) = 1; sizeof(int) = 4; sizeof(double) = 8;
假设p指向char类型变量(1个字节)
假设q指向int类型变量(4个字节)
假设r指向double类型变量(8个字节)
p,q,r所占字节数相同
验证代码如下:
#include<bits/stdc++.h>
using namespace std;
int main(){
char ch = 'a';
int i = 99;
double j = 99.6;
char * p = &ch;
int * q = &i;
double * r = &j;
cout<<sizeof(p)<<" "<<sizeof(q)<<" "<<sizeof(r)<<endl;
return 0;
}
总结:一个指针变量,无论它指向的变量占几个字节,该指针变量本身只占4个字节或8个字节(电脑32位或64位)。一个变量地址使用该变量首字节的地址来表示。
指针和二维数组
3、指针和函数
4、指针和结构体
5、多级指针
专题:动态内存分配【重点难点】
一、传统数组的缺点
1、数组长度必须事先制定,且只能是常整数,不能是变量。
例子:
int a[5]; // ok
int len = 5; int a[len]; //error
2、传统形式定义的数组,该数组的内存无法手动释放。
在一个函数运行期间,系统为该函数中数组所分配的空间会一直存在,直到该函数运行完毕时,数组的空间才会被系统释放。
3、数组的长度一旦定义,其长度就不能再更改。数组的长度不能再函数运行的过程 中动态的扩充或缩小
4、A函数定义的数组,在A函数运行期间可以被其他函数使用,但在A函数运行完毕之后,A函数中的数组将无法在被其他函数使用。即传统定义的数组不能跨函数使用。
二、为什么需要动态分配内存
动态数组很好地解决了传统数组这4个缺陷,传统数组也叫静态数组
三、动态内存分配举例_动态数组的构造
四、静态内存和动态内存的比较
静态内存是由系统自动分配,由系统自动释放
静态内存是在栈分配的
动态内存是由程序员手动分配,手动释放
动态内存是在堆分配的
五、跨函数使用内存问题
#include<bits/stdc++.h>
using namespace std;
void f(int ** q){
int i = 5;
*q = &i;
}
int main(){
int * p;
f(&p);
cout<<*p<<endl;//语法正确但逻辑错误
//在f函数运行结束之后,i的内存已经被释放了,主函数没有访问权限,此时p没有指向,是一个野指针
//此时还能输出5是因为这个内存内保存了5这个垃圾值
cout<<*p<<endl;//再次输出垃圾值已经消失
return 0;
}
#include<bits/stdc++.h>
using namespace std;
void f(int ** q){
*q = (int *) malloc(sizeof (int));
//q = 5;
//*q = 5等价于p = 5;
**q = 5;//*p = 5;
}
int main(){
int * p;
f(&p);
cout<<*p<<endl;
free(p);
cout<<*p<<endl;
return 0;
}
二进制全部为零的含义–00000000000000
1、数值零
2、字符串结束标记符’\0’
3、空指针 NULL
NULL表示的是零,而这个零不代表数字,而表示的是内存单元的编号零
计算机规定,以零为编号的存储单元内容不可读,不可写。