内存含义

  • 存储器:计算机的组成中,用来存储程序和数据,辅助CPU进行运算处理的重要部分
  • 内存:内部存储器,暂存程序/数据——掉电丢失,SRAM DRAM DDR DDR1 DDR2 DDR3
  • 外存:外部存储器,长时间保存程序/数据——掉电不丢失,ROM ERRROM FLASH(NAND,NOR) 硬盘 光盘
    内存是沟通CPU与硬盘的桥梁,暂时存放CPU中的运算数据,暂存与硬盘等外部存储器交换的数据

    扩展:电脑的系统属性里面,显示的安装内存8G 这部分就可以分成8*1024*1024*1024个Byte,每个Byte都有自己的编号,
    32位的系统,内存编号表示为无符号整形 unsigned int 占4个字节 最大代表2的32次方
    64位的系统,内存编号表示为__int64 占8个字节 最大代表2的64次方
    所以32位的系统如果安装上太大的内存条也没有特别大的提升,因为内存虽然大,但是可以编号的最大值卡死在了2的32次方上,剩余的内存无法编号便无法使用

物理存储器

就是实际存在的具体存储器芯片,比如主板上装插的内存条、显卡上的RAM芯片、各种适配卡上的RAM芯片和ROM芯片

存储地址空间

是对存储器编码的范围,我们在软件上常说的内存是指这一层含义
编码:对每一个物理存储单元(一个字节)分配一个号码
寻址:可以根据分配的号码,找到相应的存储单元,完成数据的读写

内存地址

将内存抽象成一个很大的一维字符数组
编码就是对内存的每一个字节分配一个32位或者64位的编号(与32位或者64位处理器有关)
这个内存编号我们称之为内存地址
内存中的每一个数据都会分配相应的地址
char 占一个字节分配一个地址
int 占4个字节,分配4个地址
float struct 函数 数组等

指针

内存地址打印:

int a = 0xaabbccdd;
printf("%p\n",&a);//打印结果:0x0059FE28
调试,根据内存地址查看值发现0x0059FE28 =》 dd0x0059FE29 =》 cc0x0059FE2a =》 bb0x0059FE2b =》 aa

问题:发现aabbccdd在内存里面存储情况,随着内存地址编号增大,好像数据是倒过来存的,不是先存aa而是先存dd
这是因为windows做数据存储是采用小端对齐,也就是变量a中,aa是高位,dd是低位,所以aa存储在内存地址编号高的地方,dd存储在内存地址编号低的地方,一般情况下,比如做嵌入式开发,从Linux拿出来数据显示在windows中,就需要做大小端数据转换

定义指针变量存储变量地址:

int a = 10;
int* p;          * 表示一种新的数据类型,就是指针数据类型
p = &a;             将a的地址赋值给p,p就是指向a的指针变量
printf("%p\n",&a);两个打印的结果是一样的
printf("%p\n",p);在内存中,变量a被存放在0x0021地址中,当定义int* p,并且将&a赋值给p时,在内存中有另外一块地址(0x0087)存放着p的值,
也就是a的地址值0x0021,所以此处p的值为0x0021,p的地址为0x0087,

扩展:如果此处再定义一个指针变量,用来存放p的地址,则这个指针变量叫做二级指针变量,一级指针存放变量内存地址,二级指针存放指针内存地址

通过指针间接修改变量的值:

int a = 10;
int* p;
p = &a;
*p = 100;          取地址运算符 * 代表从p指向的地址中获取到值
printf("%d\n",a);
printf("%d\n",*p);    两个打印的结果一样

指针的大小

int a = 10;
int* p = &a;
printf("%d\n",sizeof(int*));
printf("%d\n",sizeof(p));     两个打印结果是一样的

指针类型存储的都是内存地址,32位系统一个指针类型大小占4Byte,64位系统一个指针类型大小占8Byte
一个char类型的数据占内存是1Byte,但是指向他的指针占的内存是4Byte或者8Byte,这跟操作系统是多少位有关系
所以指针类型所占的大小跟是什么数据类型的指针没有关系,而跟系统是多少位的有关系
问题:既然指针变量都是无符号16进制整形数,都占4个字节大小,能不能这样写:

int a = 10;
int p = &a;

不用int*定义指针变量
答案:是可以这样写,而且也不会报错,但是这个时候p就是一个整形变量了,而不是一个指针变量,当用到取值符号取值的时候就该哭(报错)了
但是如果非要这么写也可以,需要这样:

*(int*)p = 100;

先将p强制转换成指针变量,这样p就成了一个指针变量了,再取值进行赋值操作就可以了

问题:既然指针变量都是无符号16进制整形数,都占4个字节大小,能不能这样写:

char ch = 97;
int* p = &ch;
printf("%p\n",&ch);
printf("%p\n",p);

答案:两者打印结果是一样的,都为变量ch的地址,这样写不会报错,但是当通过*p给ch重新赋值的时候就会出错,如下:

*p = 100;  给*p赋值为100
printf("%d\n",*p);
printf("%d\n",ch);

程序要么数据不对,要么会奔溃,这是因为int类型的指针p指向了char类型的变量,当用取值符*取值的时候,他会按照int类型的变量去读数据,读取4个
字节的数据,而我们的数据是char类型的,只占一个字节,所以数据会出错,进而导致奔溃。
所以在定义指针类型指向变量地址的时候,一定要和变量类型对应上

野指针

int* p = 100;   int类型的指针变量p,指向内存编号为100的地址,词句并不会报错
printf("%d\n",*p);    打印p所指向地址的值,报错了,因为操作系统将0-255作为系统占用空间,不允许访问操作

以上代码中指针变量p就是一个野指针,野指针表示指针变量指向一个未知的空间,未知表示不是自己申请的空间。
操作野指针对应的内存空间可能会报错,也可能不报错
程序中允许出现野指针
不建议直接将一个变量的值直接赋值给指针
通常写代码,定义一个指针,之后经过一系列操作,这个指针指向了另外一片未知的空间,他就成为了野指针,所以一般情况下写代码,操作完指针之后都需要将其置位NULL

空指针
空指针是指内存地址编号为0的空间

int* p = NULL;

空指针对应的地址不允许读和写,因为0对应的地址也包含在0-255,操作空指针对应的空间一定会报错
空指针可以用于条件判断

万能指针void*
万能指针可以指向任意变量的内存空间

int a = 10;
void* p = &a;  void*可以接收任意类型变量的内存空间
*p = 100;      这一行会报错,错误提示:非法的间接寻址,表达式必须是可修改的左值,这是为什么呢?
//打印两行内容看看
printf("%d\n",sizeof(void*));    打印结果为4  正常,因为地址值都是unsigned int 占4位
printf("%d\n",sizeof(void));   会报错,编译不通过,提示不允许使用不完整的类型,void是不完整的,

说明都没有办法获取sizeof(void)的大小,更别说去用取值符获取他的值了,连取几个字节的数据都不知道
想要通过万能指针修改变量的值时,需要找到变量对应的指针类型,也就是需要知道这个变量到底是什么数据类型的

*(int*)p = 100;    先把p强转成int类型的指针,然后再用取值符取值并且改变他的值。

万能指针一般用于作为函数的形参,并且同时形参还需要一个,告诉函数该万能指针指向的数据类型占多少个字节
万能指针可以赋值给任意一个指针变量:

int a = 10;
void* p1 = &a;
int* p2 = p1;

但是实际操作修改值的时候,还是要将原来的指针类型匹配上。

const修饰的指针类型

const常常用来修饰常量,其修饰的变量是不能直接被修改的,但是可以使用指针的方式来间接修改他的值

const int a = 10;
int* p = &a;
*p = 100;
printf("%d\n",a);打印结果为100

但是通过#define定义的常量是没办法修改的,这是因为const定义的常量是在栈区存储的,而#define定义的常量是存放在数据区的,
栈区的数据是可以修改的,数据区的数据是不能修改的
当然,const也可以修饰指针变量,有三种形式

1、const int* p = &a;
const修饰指针类型int*,可以修改指针变量的值,不可以修改指针指向内存空间的值*p2、int* const p = &a;
const修饰指针变量p,可以修改指针指向内存空间的值*p,不可以修改指针变量的值p3、const int* const p = &a;
const修饰指针类型int* 也修饰指针变量p,不可以修改指针变量的值,也不可以修改指针指向内存空间的值
但是之前说了,普通的常量,可以使用一级指针进行修改,同样的,一级指针常量,可以使用二级指针进行修改
int a = 10;
int b = 20;
const int* const p = &a;
int** pp = &p    定义一个二级指针pp,将指针p的地址赋值给二级指针变量pp
*pp = &b;       使用一个取值符*获取pp的值,也就是降维度成为一级指针,改变他的值为b的地址可以将*p的值改变为20
**pp = 100;      也可以直接使用两个取值符**,降维度成为变量,直接给变量赋值100 也可以

结论:通过二级指针可以修改一级指针的值,也可以修改一级指针指向内存空间的值,同样的道理,三级指针可以修改二级指针的值,也可以修改二级
指针指向内存空间的值。

指针和数组

数组名字是数组的首元素地址,但他是一个常量,所以数组名不能修改

int arr[] = {1,2,3,4,5,6,7,8,9,10};
int* p = arr;  因为arr本身就是一个地址,所以这样赋值是没有问题的,而且两者的地址也是一样的
printf("%p\n",p);
printf("%p\n",arr);
for(int i = 0;i < 10;i++){printf("%d\n",arr[i]);
}

可以看到,这里利用数组名arr和一个下标i就可以遍历整个数组的元素,那么p和arr一样,也是个地址,使用p和下标i是不是也能遍历所有的元素呢?

for(int i = 0;i<10;i++){printf("%d\n",p[i]); 也可以打印出来整个数组的元素
}

既然arr和p都是地址,那么这样写来获取数组的第一个元素也是可以的:

printf("%d\n",*arr);

使用地址arr和角标i就可以遍历整个数组中的元素,arr是个常量不能改变,变得只是i,那么我们可以认为i是一个偏移量,那么这样打印一下

printf("%d\n",*(arr+4));  发现打印的结果是5

那么:

printf("%d\n",*(arr+1));   打印的结果为1

也就是说 *(arr+1) 和arr[1]结果是一样的
所以数组中数组名arr和角标i的组合也可以写成地址p加偏移量i再用*取值
那么这里有一个问题,这里int类型的数组一个元素占用的内存空间是4个字节,我为什么地址+4的时候,直接跑到了第5个元素上去,而地址+1的时候却在第二个元素上?
这是因为:
指针变量+1 等同于 内存地址+sizeof(数据类型)
打印+1的地址来证明

int arr[] = {1,2,3,4,5,6,7,8,9,10};
int* p = arr;
p++;
printf("%p\n",arr);
printf("%p\n",p);
打印结果,p比arr多4

两个指针相减,得到的结果是两个指针的偏移量,步长,所有指针类型,相减的结果都是int类型
指针p和数组名arr的区别:p是变量,可以改变,arr是常量,不能改变
sizeof§和sizeof(arr)不同,p是一个指针,4个字节大小,arr是一个数组,40个字节大小(按照以上代码计算)
练习:写一个函数,参数是数组,打印数组的元素个数

void GetCount(int arr[]){int len = sizeof(arr)/sizeof(arr[0]);printf("len = %d\n",len);
}

调用该函数的时候,发现无论arr的元素个数是多少,最后打印的值都是1,
这是因为数组名本身既是一个数组名,也是一个指针,但当他作为函数的参数时,会退化为指针,丢失数组的精度
也就是说,一旦arr被传进这个函数中,无论arr里面元素个数是多少,sizeof(arr) = 4,sizeof(arr[0]) = 4 ,所以结果始终是1
所以一般情况下,数组名作为函数的参数时,同时会将数组元素个数也传进来,要不然进了函数里面没办法计算
学到这里,就应该明白例如冒泡排序的函数:

void BubbleSort(int arr[],int len){for(int i = 0;i < len; i++){for(int j = 0; j< len-1-i; j++){if(arr[j] > arr[j+1]){int temp = arr[j+1];arr[j+1] = arr[j];arr[j] = temp;}}}
}

这里面执行的arr[j]或者arr{j+1}里面的arr其实都是指针,而不是一个数组名了,我们知道使用arr[j+1]这样的方式也可以获取指针的值
所以上面的代码也可以写成:

void BubbleSort(int arr[],int len){for(int i = 0;i < len; i++){for(int j = 0; j< len-1-i; j++){if(arr[j] > arr[j+1]){int temp = *(arr+j+1);*(arr+j+1) = *(arr+j);*(arr+j) = temp;}}}
}

两份代码原理一样,但是一般不写这种,阅读性不强

指针加减运算

加法运算

指针相加不是简单的整数相加

如果是一个int*,+1的结果是增加一个int的大小
如果是一个char* +1的结果是增加一个char的大小

练习:字符串复制

void my_strcpy(char* dest, char* ch){int i = 0;while(ch[i] != '\0'){ 这一行判断中也可以写成ch[i] != 0 还可以写成ch[i]dest[i] = ch[i];i++;}dest[i] = 0;
}void main(){char ch[] = "hello world";char dest[100];my_strcpy(dest, ch);return 0;
}

方法二:指针操作,其实原理还是数组原理

void my_strcpy(char* dest,char* ch){int i = 0;while(*(ch+i)){*(dest+i) = *(ch+i);i++;}*(dest+i) = 0;
}

方法三:指针操作,真正意义上的指针操作

void my_strcpy(char* dest,char* ch){while(*ch){*dest = *ch;dest++;        指针+1,相当于指向数组下一个元素,内存地址变化了sizeof(char)大小ch++;}*dest = 0;
}

方法四:终极精简版本,原理和方法三一样,只不过更加简洁而已

void my_strcpy(char* dest,char* ch){while(*dest++ = *ch++);     这一行代码,包含的操作有:*dest = *ch;
}                                                     dest++; ch++;while(*dest!=0)

减法运算

例子:

int main(){int arr[] = {1,2,3,4,5,6,7,8,9,10};int* p = &arr[3];printf("%p\n",arr);       0x0028FF14printf("%p\n",p);       0x0028FF20return 0;
}

以上代码运行结果中,arr为数组的首元素的地址,p为下标为3的元素的地址,两者相差12,计算步长的话:

printf("step = %d\n",p - arr);   step= 3 因为计算的是int类型指针的步长

如果这样改一下代码:

int main(){int arr[] = {1,2,3,4,5,6,7,8,9,10};int* p = &arr[3];p--;p--;p--;printf("%p\n",arr);       0x0028FF14printf("%p\n",p);       0x0028FF14return 0;
}

打印的两个地址一样了,p–的执行过程就是指针向后退一格指针单位,具体指针单位是多少,和指针类型有关系
例如int类型的指针,自减运算之后指针向后偏移4字节,char类型的指针,自减运算之后指针向后偏移1字节
以上自减运算同样可以写为:指针变量 - 偏移量,例如在指针p指向下标为3的元素时,我想要获取下标为1的元素的值:

int main(){int arr[] = {1,2,3,4,5,6,7,8,9,10};int* p = &arr[3];printf("%d\n",*(p-2));return 0;
}

运行结果为2
目前结论为,指针可以进行+运算和-运算,但是仅限于偏移量
注意:指针变量和指针变量进行加法,减法,乘除,取余都是无意义的
指针变量之间可以进行比较大小操作,等于和不等于

指针数组

定义:他是数组,数组中的每个元素都是指针类型

int main(){int a = 10;int b = 20;int c = 30;int* arr[3] = {&a,&b,&c}; return 0;
}

遍历数组中的指针对应的值:

for(int i = 0;i < sizeof(arr)/sizeof(arr[0]);i++){printf("%d\n",*arr[i]);
}

例子:定义3个int类型的一维数组a,b,c,每个数组3个元素,a b c也就是3个int类型的指针变量,组成一个指针数组arr

int a[] = {1,2,3};
int b[] = {4,5,6};
int c[] = {7,8,9};
int* arr[3] = {a,b,c};

要求遍历arr中每个指针对应的值?

for(int i = 0;i < 3; i++){printf("%d\n",*(arr[i]));
}

打印结果为 1 4 7 分别为a[0],b[0],c[0],那么思考怎么给指针加一个偏移量呢?

printf("%p\n",arr[0]);         指针a
printf("%p\n",a);             数组a
printf("%p\n",&a[0]);         数组a的首元素地址
打印这三个值,结果相等

既然arr[0]指针和a相等,那么我要获取到a[1]的值,是不是可以使用arr[0][1]?

for(int i = 0;i < 3; i++){for(int j = 0;j < 3; j++){printf("%d ",arr[i][j]);}puts("");
}

结果可以打印出所有的元素,可以看出来,该遍历方式是以二维数组的思路去解的,其实指针数组实际上就是一个二维数组的特殊模型
上面说到arr[0]指针和a相等,a也是一个指针,那么可以使用指针加偏移量的方式来获取全部的元素

for(int i = 0;i < 3; i++){for(int j = 0;j < 3; j++){printf("%d ",*(arr[i]+j));}puts("");
}

结果也可以打印出所有的元素,那么再思考,j可以当做偏移量,那么i应该也可以:

for(int i = 0;i < 3; i++){for(int j = 0;j < 3; j++){printf("%d ",*(*(arr+i)+j)); arr是指向数组a的指针,加i偏移量之后取值,得到指针a,继续加偏移量取值获取到a[0]的值}puts("");
}

可以看到使用到了二级指针,其实指针数组对应于二级指针
所以这个时候就不能写:

int* p = arr;

这样写虽然没有问题,但是指针层级会有问题,arr是一个二级指针,p是一个一级指针

多级指针

C语言允许有多级指针存在,在实际的程序中,一级指针最常用,其次是二级指针,
二级指针就是指向一个一级指针的指针

int a = 10;
int* p = &a;
int** pp = &p;
int*** ppp = &pp;
以上代码中,存在如下关系
*ppp      ==    pp       ==      &p
(二级指针)    (二级指针)      (二级指针)**ppp     ==    *pp      ==      p       ==       &a
(一级指针)    ***ppp    ==    **pp     ==      *p      ==        a
(值)

C语言学习第008课——内存和指针相关推荐

  1. C语言学习笔记之动态内存分配结构体——辣鸡版学生管理系统

    @[C语言学习笔记] 小辣鸡自学C语言中,好多视频教程都有这个案例,小辣鸡自己边学边记,下边是代码. 如有错误,感谢告知!!! #include<stdio.h> #include< ...

  2. C语言学习(三)内存初识、数据在内存中的保存形式、程序载入内存

    (一)内存与存储(硬盘)   首先要明确一点,内存不同于存储.内存中的数据存储在内存条中.而一般的存储,数据则保存在硬盘中.这里我只给出一些表象的区别,如果还想更深入的了解,请自行搜索,此篇所展示的重 ...

  3. C语言学习第017课——C语言提高(一)

    typedef的用法 1.定义指针类型 定义两个char*的变量,p1和p2,使用C++代码打印一下他俩的类型: #include <iostream> #include<typei ...

  4. [编程神域 C语言浮游塔 第⑤期]内存地址——指针

    前言 嗨嗨,这里是渡枫,欢迎阅读第五期的C语言浮游塔. 在正式学习指针之前,我们先提出几个问题. 什么是指针? 什么决定了数据的长度? 指针是否有类型? &a是指针吗? 一:指针就是一个变量的 ...

  5. C语言调用 free 函数释放内存后指针指向及内存中的值是否改变的问题

    文章目录 1. 前言 2. 正文 2.1. "分配" 与 "释放" 2.2. 运行测试 2.2.1. VSCode 下使用 gcc 编译 2.2.2. VS20 ...

  6. STM32学习第三课:STM32 c语言学习基础3(内存操作、指针、结构指针)

    1.内存操作 在对内存操作头疼的时候我发现了这篇神奇的文章,拜读之后豁然开朗心生崇拜 数据指针 在嵌入式系统的编程中,常常要求在特定的内存单元读写内容,汇编有对应的MOV指令,而除C/C++以外的其它 ...

  7. golang语言学习第三课 条件语句

    if 是一个条件语句.if 语句的语法为: if condition { } 与其他语言不同, 其他语言 if后面有()括号,但是go语言是没有的. if 语句后面可以接可选的 else if 和 e ...

  8. C语言学习第八课(EasyX图形库)

    第八课[注意:该图形库只能在c++文件下运行] 1,窗口函数 ·initgraph(int width,int height,int flag =NULL);//创建窗口 //width 指定窗口宽度 ...

  9. c语言回调函数_C语言学习第26篇---函数与指针分析 回调函数

    函数类型的概念 1.C语言函数也是有类型的 2.函数类型由函数返回值,参数类型,参数个数共同决定 int add(int i,int j) 的类型是 int(int , int) 3.C语言中通过ty ...

最新文章

  1. ASP.NET 获取IIS应用程序池的托管管道模式
  2. iOS HTTP与 HTTPS
  3. 尝试远程添加Git存储库时收到“致命错误:不是git存储库”
  4. Docker学习笔记之二,基于Dockerfile搭建JAVA Tomcat运行环境
  5. Flow - JS静态类型检查工具
  6. HuaWeiCloud_model_arts
  7. 文件可视化编辑_高维数据可视化必备图形平行坐标图
  8. Jekyll 使用入门
  9. java 性能调优_Java性能调优调查结果(第四部分)
  10. 使用linux expect进行ssh和telnet自动化登录等操作
  11. 容器编排技术 -- Kubernetes kubectl create namespace 命令详解
  12. 【引用】别让理想毁了人生
  13. autocad哪个版本最好用_分享家用游戏用Win10哪个版本最好最稳定(个人见解篇)...
  14. iBATIS In Action(六)执行非查询语句
  15. gstreamer向appsrc发送数据的代码
  16. 学习笔记——直流电机调速器
  17. Code is far away from bug with the animal protecting
  18. 第一次电话面试失败之后
  19. Android Jetpack 六大架构组件全面了解
  20. Android之HorizontalScrollView(一)

热门文章

  1. 找出孤独的一个(IBM面试题)
  2. 如何同时对多张图片进行批量裁剪、批量旋转方向
  3. 体育世界杂志体育世界杂志社体育世界编辑部2022年第4期目录
  4. python如何绘制两点间连线_如何用 Python 绘制玫瑰图等常见疫情图
  5. Python基础课程-面向对象编程
  6. win11壁纸被锁定更换不了 win11更换壁纸教程
  7. 100多个经典常用的网站模板大全实例演示和下载
  8. 海外游戏代投需要注意的
  9. 11月编程排行榜来了,Python依旧占据榜首
  10. 婴幼儿办理护照的过程及注意事项(原创)