C语言指针第一篇
指针
1、概述
指针是C语言灵魂所在。指针是灵活的,但是也是危险的。
指针:内存单元的编号,计算机中的内存单元不是按照比特来算的,而是按照字节来计算的,一个字节一个编号,这个编号就是地址;
指针就是地址,地址就是指针。
在最开始的入门程序中:
int i = 10;
这个i不是一个指针,但是有着指针的概念。i是变量,但是&i对应的值就是地址,也就是指针。
看看指针入门程序:
# include <stdio.h>
int main(void){
// int * 表示的是p变量存放的int类型变量的地址。也就是说p变量存放的是int类型变量的地址,通过这个地址,可以找到变量所能够存储的数据。
int * p;
// 表示的q变量存放整型变量的值。而这个值是存储在内存单元编号中的01代码
int q;
int i = 10;
return 0;
}
从上面可以看出来,变量存储的是内存单元中的01代码,而指针变量存储的是内存单元的编号;二者的关系是内存地址编号中存储着变量的数据,变量对应这些数据。
上面的关系对应的就是
# include <stdio.h>
int main(void){
int * p;
int i = 10;
p = &i;
return 0;
}
同种数据类型只能够存储同种类型的
错误写法如下:
double * p;
int i = 10;
p = &i; // doule和int不同
---------------
double j;
p = j; // 这里是01变量,而不是地址
那么这里的讲解和第一章中的写法就有问题了。因为在第一章中我写的是int i ,这里的i代表的就是变量中存储的01代码,而不是地址,但是通过&i,又可以拿到对应的地址值。
2、深入理解指针
# include <stdio.h>
int main(void){
int * p;
int i = 10;
p=&i;
return 0;
}
p指针保存了i变量的地址,但是修改了p的值不影响i的值,修改了i的值,不影响p的值。
指针变量p保存了q变量的地址,所以有了指向,也就是p指针变量指向了q变量的地址;*p表示的以p的内容作为地址的变量
修改了p的值,只是说p指针指向了别的地方,不影响q的值;修改了q的值,只是说将01代码给修改了,但是并不影响p指针指向的地址值;习惯上也说是指针是存放了变量的地址。
*p就是将p变量的内存单元中的值给出去来了,也就是将1000H的这个值给取出来了;变量q的内存单元地址也是1000H,这个内存单元中的值是01010101
所以说给变量q赋值就是说,拿到了q的内存地址,那么就可以向这个内存地址中放入对应的值了,也就是01代码;&i就是获取得到i变量的地址1000H;
同理,和指针也是一样的,&p也就是说拿到了指针的地址:123H;
操作这个变量q,就相当于是操作*p,所以这就提供了另外一种操作变量的方式。
这里的p变量和q变量是不同的概念,一种是指针,另外一种是变量。但是*p和q是一个概念。
int *是数据类型,而p是变量名字;
int是q的数据类型,,q是变量名字;
# include <stdio.h>
int main(void){
int * p;
int i = 10;
p=&i;
int k;
k = *p;
printf("k对应的值是:%d\n",k);
return 0;
}
输出的值是:10
再次改进:
# include <stdio.h>
int main(void){
int * p;
int i = 10;
p=&i;
int k;
k = *p;
printf("k对应的值是:%d\n",k); // 10
// 修改i的值
*p = 11;
printf("k对应的值是:%d\n",k); // 10
printf("i对应的值是:%d\n",i); // 11
return 0;
}
很简单的就可以来验证了。
指针的优势:
能够表示复杂的数据结构;数据结构能够理解。
快速的传递数据(函数参数)
使得方法返回一个以上的值;
能够直接访问硬件;获取得到变量的地址
能够方便的处理字符串;
是理解面向对象语言的基础;
C语言的灵魂!
3、指针分类
基本类型指针、指针和数组、指针和函数、指针和结构体、多级指针
地址:内存单元的编号;从0开始的非负整数,范围划分:
CPU和内存中是如何进行交互的,首先通过控制线传递CPU的指令,只读、只写、可读、可写?
然后通过地址线找到对应的数据,然后数据线来将数据读入到CPU的寄存器;或者是将CPU寄存器中的信息写到内存中去;
那么重点就是这个地址线。
地址线有多少根?
一根地址线就代表了当前的CPU能够访问到的状态是多少,一根只有两种状态,那么就是2,那么对于32位或者是64位的操作系统来说,交互太过于频繁,而且效率低下,所以增加地址线,增加到什么程度呢?取决于自己当前的CPU。
一般来说,32根的地址线,可以访问得到的状态是:2^32=4G,那么CPU来说,如果内存是4G的,只需要一次就可以定位到数据在内存条中的位置,速度极快;
64位的操作系统既然支持的话,那么地址线就应该是264,这个数字机器庞大,可以在一次交互的过程中,读取得到264位的数据到CPU中去,这个是非常恐怖的。
将读取后的位通过数据总线来进行传输,这个数据总线取决于我们能够读取到数据到CPU中去,一般来说CPU都具有缓存的概念。所以这里是否真的是按照对应的读入,也不一定,因为也有局部性原理和时间原理,不一定说按照CPU的缓存来读的。反正记住是很快速的来读就行了。
那么通过数据类型也能够确定读取多少数据到CPU中去,因为CPU很快,能够很快的定位到对应的数据存放的位置,然后将数据读入到CPU中去执行。
我当前的内存大小是8G,所以内存地址编号就是0~8G-1,这个范围也是极大的。
所以这里又对内存单元编号有了一个新的理解:
32根地址线,每根线是 0 1 状态,通过地址线来找内存单元地址,一共可以找到2的32次方个内存单元地址,并不是一个字节算一个地址的编号,是cpu的地址线的寻址能力决定内存单元的地址个数,不是反过来。
一个内存地址单元是存储一个字节的数据。
在32位cpu下,一共有2的32次方个内存单元地址,那么指针变量就必须要大于等于内存单元地址的总数,所以一个指针变量的长度就是32位。而1个内存单元地址里面存储一个字节的数据,所以内存的最大值就是2的32次方Bytes,也就是4gb。8位 16位 64位同理
那么也就是char类型的指针一次性可以定位到每一个内存单元地址;而int类型的指针可以定位到四个连续内存单元地址
所以说在不同的操作系统中,指针所占用的字节数也是不一样的。
那么这个内存内存单元也是通过电信号来进行连接的,相当于一个二维数组,找到对应的一行或者是一列来进行存储数据;按照行列式的方式来进行排列。
因为计算机能够操作的最小单元是8个比特,也就是一个字节;所以为了命名,我暂时将这个当做内存地址编号,每八个一位;
按照数据类型来划分多个字节占据一个,那么就可以直接取到多个字节的类型的数据;
我之前一直以为指针是占用一个字节的,原来不止!而是地址总线来的,地址总线决定了一个指针能够占用多少位。
那么64位的操作系统就决定了一个指针能够占用8个字节了。这个之后回来证明。
写了一个程序:
# include <stdio.h>
int main(void){
int * p;
int i = 10;
*p = i;
printf("*p对应的值是:%d\n",*p);
return 0;
}
查看输出控制台,发现什么都没有。
仔细分析一波,为什么?首先p指向的内存单元肯定是有值的,但是这个值指向的地址OS是否允许访问,这就是一个首要的问题了。
因为指针随便指向,我们对指针里面的内容进行操作,可能会导致其他的应用程序有着更大的安全隐患。
可能这块数据是我们不能够访问的或者说是没有对应的权限操作这块内存空间。也可以理解成是权限问题。
3.1、内存泄漏
首先讲一下内存泄漏的概念。内存泄漏也就是说使用完了内存之后,没有进行释放,这块内存对于操作系统来说,这块内存是在使用的。而正在使用的进程觉得这块我放在这里是没有问题的。那么利用极限思想,如果这种内存很多,最终导致了整个内存中都是这种,那么对于内存来说,相当于是没有使用内存,那么将会导致速率极慢,最终虚拟内存也堆积满了,系统就宕机了。所以对于操作系统来说,这是很危险的操作。
所以要求程序员在使用完成之后,就要及时对其进行释放内存。但是这里也要注意一个细节:
int * p,q,a,b,c;
p=a=b=c=q;
free(p);
这里的这个free代表的就是将p指向的内存给释放掉了,交换给操作系统了。但是如果再次操作free(q)的话,如果被编译器和操作系统检测出来还好,但是如果没有检测出来,这个时候将会导致其他进程使用的这个内存空间给释放掉了,可能会导致其他程序崩溃。
所以释放一次就可以达到一种释放的效果。
所以指针是非常灵活的,得合理的进行运用。释放少一个不行,多释放一个也不行;
3.2、测试
# include <stdio.h>
void swap(int x,int y){
int tem;
tem = x;
x = y;
y = tem;
}
int main(void){
int a = 3;
int b = 4;
printf("a对应的值是:%d,b对应的值是:%d\n",a,b);
swap(a,b);
printf("a对应的值是:%d,b对应的值是:%d\n",a,b);
return 0;
}
控制台输出:
a对应的值是:3,b对应的值是:4
a对应的值是:3,b对应的值是:4
--------------------------------
Process exited after 0.2064 seconds with return value 0
请按任意键继续. . .
可以发现,尽管在函数中已经对变量进行了修改,但是主函数中依然没有进行修改。为什么?
再看下面的代码发现问题:
# include <stdio.h>
void swap(int * x,int * y){
// 现用一个临时指针来保存
int * tem;
// tem中保存的就应该是x指针中的值。操作一个变量,就相当于是操作这个指针中的01代码
tem = x;
// 现在需要的是将x指针中保存的地址修改成y的地址
x = y;
// y指向了tem的地址
y = tem;
}
int main(void){
int a = 3;
int b = 4;
printf("a对应的值是:%d,b对应的值是:%d\n",a,b);
swap(&a,&b);
printf("a对应的值是:%d,b对应的值是:%d\n",a,b);
return 0;
}
控制台输出,得到结论:
a对应的值是:3,b对应的值是:4
a对应的值是:3,b对应的值是:4
--------------------------------
Process exited after 0.2064 seconds with return value 0
请按任意键继续. . .
结果发现,没有任何改变。但是从上面的结果中得出结论,操作一个变量,无论是普通变量还是指针变量,都相当于是操作变量里面保存的值;
普通变量直接操作的就是01代码数据,而指针变量直接操作的就是地址值;
在上面的操作中,新开辟了一个栈空间,只是交换了p和q变量中保存的值,这里交换的值是a和b的地址值,但是并没有对a、b地址值中保存的值进行修改。
这就是没有修改的原因的地方。
画个图来进行演示:
再实验:
# include <stdio.h>
void swap(int * x,int * y){
// 刚刚那种方式修改了p和q中的值,那么现在需要做的是修改各自指向的地址的值
int tem;
// tem = 3
tem = *x;
// x指向的地址中的01代码成了5的01代码
*x = *y;
// 3放入到y指向的地中的01代码
*y = tem;
}
int main(void){
int a = 3;
int b = 4;
printf("a对应的值是:%d,b对应的值是:%d\n",a,b);
swap(&a,&b);
printf("a对应的值是:%d,b对应的值是:%d\n",a,b);
return 0;
}
在上面的操作中可以看到,操作指针x,将指针x指向的内容,也就是3给取出来,然后交给tem保存,然后将y中保存的值(地址值),在经过*后操作之后,就取出来了这个y指针对应的地址空间空存储的01代码,然后赋值给指针x所指向空间的01代码,然后在进行交换。
对应的图如上所示:
所以在交换值的时候,是对立面的内存地址进行交换的。
对于方法来说,在C语言中可以先进行声明,其中,在声明中,不需要指定具体的方法名,只需要声明参数类型即可。
如下所示:
# include <stdio.h>
// 对函数进行声明
void swap(int *,int *);
// 对函数进行实现
void swap(int * x,int * y){
// 先保存下二者的值
int a ,b;
a = *x;
b = *y;
// 现用一个临时指针来保存
int * tem;
// tem中保存的就应该是x指针中的值。操作一个变量,就相当于是操作这个指针中的01代码
tem = x;
// 现在需要的是将x指针中保存的地址修改成y的地址
x = y;
// y指向了tem的地址
y = tem;
}
int main(void){
int a = 3;
int b = 4;
printf("a对应的值是:%d,b对应的值是:%d\n",a,b);
swap(&a,&b);
printf("a对应的值是:%d,b对应的值是:%d\n",a,b);
return 0;
}
4、方法参数是指针和非指针
这个是一个很重要的点。说实话,无论是在学java还是C语言的时候,都一直搞不懂这一块的东西,知道今天,才终于想起来总结一波这里的内容。
拿上面的案例来举列子:
# include <stdio.h>
void swap(int x,int y){
int tem;
tem = x;
x = y;
y = tem;
}
int main(void){
int a = 3;
int b = 4;
printf("a对应的值是:%d,b对应的值是:%d\n",a,b);
swap(a,b);
printf("a对应的值是:%d,b对应的值是:%d\n",a,b);
return 0;
}
在main函数中调用了swap函数,将main函数中的数据进行了传递。
首先入手的应该分析一波内存分配情况,这个是非常重要有助于理解的。
在内存中,因为有了int a,b的定义,所以向操作系统申请分配内存,在调用了方法swap方法的时候,因为x,y也属于变量,那么操作系统就重新分配了内存给x,y,所以方法调用和方法调用之间没有任何的关系。但是因为方法在进行调用的时候,将值传递了过去;如果不是地址值,那么将毫无关系;但是如果是地址值,那么将会导致,两个函数之间产生关系。
如果是地址值,那么修改了之后,因为指针变量有指向,但是把指向的地址空间的内容给改了,再次从指针变量中进行获取的时候,发现里面的值是已经被修改过的值。
5、总结
1、指针是以内容为地址的变量;
2、修改了指针的值与指向的变量的值没有关系;修改了变量的值与指针的值也没有关系;
3、操作一个变量,就是操作这个变量的值。其实可以把每个变量都看成是一个容器,操作容器名字,就相当于是操作容器内的值;
4、指针所占字节是以当前的操作系统的地址总线决定的;
5、操作指针要注意先进行指向,而不应该直接进行赋值。因为直接赋值可能会造成权限不够或者是权限问题。
6、方法调用方法,其实两个方法中的变量是毫无关系的;取决于传递是普通变量还是地址值,如果是地址值,两个函数中都有指向,一个修改了,其他的地方也跟着修改了;
原文地址:https://www.cnblogs.com/likeguang/p/15110515.html
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Android自定义底部弹出框ButtomDialog
- Android使用SoundPool播放音效实例
- 安装OpenLDAP和客户端
- 【- Flutter 桌面篇 -】 FlutterUnit win版闪亮登场
- Android实现视频弹幕功能
- flutter 中监听滑动事件
- 如何通过Cloudera Manager页面自定义图表
- 【-Flutter组件篇- 】1.20新增组件InteractiveViewer
- Android使用SoundPool实现播放音效
- 【- FlutterUnit重大更新 -】Flutter要点集录.md
- Android实现美团外卖底部导航栏动画
- Kudu遇到的问题
- 【 -Flutter自定义组件- 】Wrapper组件,包裹装饰你的一切
- Android Shape属性创建环形进度条
- Ranger同步ldap组问题