c语言学习笔记(9)——指针
指针是c语言的灵魂
-----------------------------------------------------------------------------
# include <stdio.h>
int main(){
int *p; //p是变量名,int *表示p变量存放的是int类型变量的地址,p是一个指针变量
int i = 3;
//p = i; 这样写是错误的
//p = 4; 这样写是错误的
p = &i; //将i变量的地址给p变量
//p保存了i的地址,因此p指向i,修改p的值不影响i的值,修改i的值也不影响p的值
return 0;
}
-----------------------------------------------------------------------------
# include <stdio.h>
int main(){
int *p; //不表示定义了一个名字叫做 *p的变量
//应该这样理解:p是变量名,p变量的数据类型是 int *类型
//int *类型实际就是存放int变量地址的类型
int i = 3;
p = &i;
printf("*P = %d\n", *p);
printf("i = %d\n", i);
return 0;
}
1.如果p是个指针变量,并且p存放了普通变量i的地址,则p指向了普通变量
2.*P 完全等同于 普通变量i (有i出现的地方都可以替换成*p)
------------------------------------------------------------------------------
指针和指针变量的区别:
1.指针就是地址,地址就是指针
地址就是内存单元的编号,所以指针就是内存单元的编号
2.指针变量存放地址的变量
指针变量是存放指针的变量,也就是说指针变量是存放内存单元编号的变量
3.指针和指针变量是两个不同的概念
但是要注意通常我们叙述时会把指针变量简称为指针,实际它们的含义不一样
一、指针的重要性
1.表示一些复杂的数据结构
2.快速的传递数据
3.使函数返回一个以上的值(函数只能返回一个值)
---------------------------------------------------------
# include <stdio.h>
int f(int a, int b);
void g(int * p ,int * q);
int main(void){
int a = 100;
int b = 200;
// a = f(a, b);
g(&a, &b);
printf("a = %d, b = %d\n", a, b);
return 0;
}
//只能修改一个值
int f(int a, int b){
return 1;
}
//这样被调函数可以修改主调函数一个以上的值
void g(int * p ,int * q){
*p = 1;
*q = 2;
}
---------------------------------------------------------
4.能直接访问硬件
5.能够方便的处理字符串
6.是理解面向对象语言中引用的基础
总结:指针是c语言的灵魂,是和其他语言的区别
二、指针的定义
地址
内存单元的编号,地址是从零开始的非负整数。
范围:
控制总线
CPU <----> 数据总线 <-----> 内存条
地址总线
控制线控制数据传输的方向
数据线是传输数据
地址线是确定是控制哪个内存单元
cup<-----> 数据总线 <-----> 内存条
一根线控制两个 0和1
两根线控制四个
n根线控制2的n次方个单元(字节)(每个单元是8位)
32位机 2x10^32
1G=2X10^30B(字节)
2x10^32 ~= 2X10^30*4 所以内存最大4G
4G [0——4G-1]
指针
指针就是地址,地址就是指针,指针变量就是存放内存单元编号的变量。
指针和指针变量是两个不同的概念
指针本质就是一个操作受限(不能运算,只是编号)的非负整数(地址)
三、指针的分类
1.基本类型指针
//就是上面的代码
--------------------------------------------------------------------------
指针常见错误1
# include <stdio.h>
int main(void){
int *p; //p指向一个垃圾值的地址,也就是一个垃圾地址
int i = 5;
*p = i; // 就是将i的值给了一个不知道的地址,这样写不对
printf("%d\n", *p);
return 0;
}
常见错误2
# include <stdio.h>
int main(void){
int i = 5;
int *p;
int *q;
p = &i
//*q = p; 语法编译出错
//*q = *p; error q指向一个垃圾地址
q = p; // error 可以读 q里面的垃圾地址,但是不能读*q的值,没有控制权限。
printf("%d\n", *q);
return 0;
}
--------------------------------------------------------------------------
/*
一个经典指针程序
*/
# include <stdio.h>
void huhuan(int i, int j);
void zhizhenhuhuan(int * a, int * b);
void huhuan3(int * a, int * b);
int main(void){
int a = 3;
int b = 5;
// huhuan(a, b);
// zhizhenhuhuan(&a, &b);
huhuan3(&a, &b);
printf("a = %d,b = %d\n", a, b);
return 0;
}
void huhuan(int a, int b){ //不能完成互换,a,b是形参,单独分配内存
int t;
t = a;
a = b;
b = t;
}
void zhizhenhuhuan(int * a, int * b){ //不能完成互换,互换了指针的指向
int * t;
t = a;
a = b;
b = t;
}
void huhuan3(int * a, int * b){ //可以完成互换,传递的是地址,交换的是地址指向的值
int t;
t = *a;
*a = *b;
*b = t;
}
---------------------------------------------------------------------------
*的含义
1.乘法 c = a*b;
2.定义指针变量 int * p;
3.取值运算符 *p
---------------------------------------------------------------------------
2.指针和数组的关系
指针和一维数组
数组名
一维数组名是个指针常量
它存放的是数组第一个元素地址
----------------------------------------------------------------
int a[5];
int b[5];
a = b 是错误的 a,b都是常量
-----------------------------------------------------------------
# include <stdio.h>
int main(void){
int a[5];
printf("%#x\n", &a[0]);
printf("%#x\n", a);
return 0;
}
输出结果:0x12ff6c
0x12ff6c
----------------------------------------------------------------
下标和指针的关系
如果p是个指针变量,则
p[i]永远等价于 *(p+i)
确定一个一维数组需要两个参数
数组第一个元素的地址
数组的长度
-------------------------------------------------------------------------
//f函数可以输出任何一个一维数组的内容
# include <stdio.h>
void f(int * pArr, int len){
int i;
for (i=0; i<len; i++)
printf("%d ", *(pArr+i));
printf("\n");
}
int main(void){
int a[5] = {1, 2, 3, 4, 5};
int b[6] = {-1, -2, -3, 4, 5, -6};
int c[100] = {1, 99, 22, 33};
f(a, 5); //确定一个数组:数组首地址和长度
f(b, 6);
f(c, 100);
return 0;
}
-------------------------------------------------------------------------
# include <stdio.h>
void f(int * pArr, int len){
pArr[3] = 88; //pArr[3]等价于a[3]也等价于*(a+3)和*(pArr+3)
// *a==a[0]
}
int main(void){
int a[6] = {1, 2, 3, 4, 5, 6};
printf("%d\n", a[3]);
f(a, 6); // a和pArr都指向数组的第一个元素
printf("%d\n", *(a+3));
return 0;
}
-------------------------------------------------------------------------
指针变量的运算
指针变量不能相加,不能相乘,也不能相除(这些运算没有意义)
如果两个指针变量指向的是同一块连续空间中得不同的存储单元
则这两个指针才可以相减(这样减才有意义)
------------------------------------------------------------------------------
# include <stdio.h>
int main(void){
int i = 5;
int j = 10;
int * p = &i;
int * q = &j;
//此时p和q不能相减
int a[5];
p = &a[2];
q = &a[4];
printf("相减的结果为:%d\n", p-q);
//此时p和q可以相减,相减的值指p和q单元相隔的个数
return 0;
}
结果为:相减的结果为:-2
---------------------------------------------------------------------------------
一个指针变量到底占几个字节
预备知识:
sizeof(数据类型);或者 sizeof(变量名);
返回该数据类型所占的字节数
例如: sizeof(int) = 4 sizeof(char) = 1
假设p指向的char类型变量(1个字节)
假设p指向的int类型变量(4个字节)
假设p指向的double类型变量(8个字节)
p q r 本身所占的字节数是否一样
---------------------------------------------------------------------------
# include <stdio.h>
int main(void){
char ch = 'A';
int i = 90;
double x = 66.6;
char * p = &ch;
int *q = &i;
double *r = &x;
printf("%d %d %d\n", sizeof(p), sizeof(q), sizeof(r));
}
输出的结果:4 4 4
内存中一个字节为一个单元,所以double中有8个编号,
只用首字节的编号代表该变量的内存编号,用该变量类型
指明下面连续单元的个数
如: int * p; int i = 5; p = &i;
p------>地址1101011(i的首地址)
int-------表示有四个字节
----------------------------------------------------------------------------
指针和二维数组
3.指针和函数的关系
4.指针和结构体的关系
5.多级指针
----------------------------------------------------------------------------------
# include <stdio.h>
int main(void){
int i = 10;
int * p = &i;
int ** q = &p;
int *** r = &q;
printf("i = %d\n", ***r);
return 0;
}
结果: i = 10
-----------------------------------------------------------------------------------
专题:
动态内存分配
传统数组的缺点
1.数组的长度必须事先制定,且只能是常整数,不能是变量
例如: int len = 5; int a[len]; //error
2.传统形式定义的数组,该数组的内存程序员无法手动释放
------------------------------------------------------------------------------
# include <stdio.h>
void f(void){
int a[5] = {1, 2, 3, 4, 5};
//这二十个字节的存储空间程序员无法手动编程释放它
//只能在本函数运行完毕时由系统自动释放
}
int main(void){
return 0;
}
------------------------------------------------------------------------------
3.数组的长度不能再函数运行的过程中动态的扩充或缩小
4.A函数定义的数组,在A函数运行期间可以被其它函数使用,
但A函数运行完毕之后,A函数中的数组将无法在被其它函数使用
静态数组不能跨函数使用
-------------------------------------------------------------------------
# include <stdio.h>
void g(int *pArr, int len){
pArr[2] = 88;
}
void f(void){
int a[5] = {1, 2, 3, 4, 5}; //f运行期间g();函数可以使用
g(a, 5);
//当f运行完毕数组a空间被释放
printf("%d\n", a[2]);
}
int main(void){
return 0;
}
--------------------------------------------------------------------------
为什么需要动态分配
动态数组很好的解决了传统数组的上面四个缺陷
动态内存分配的举例——动态数组的构造
----------------------------------------------------------------------------
/*
2012年2月5日15:00:25
malloc 是 memory(内存) allocate(分配)的缩写
*/
# include <stdio.h.
# include <malloc.h> //头文件
int main(void){
int i = 5; //静态分配了四个字节
int * p = (int *)malloc(4); //把返回的地址强制转换为整形变量的地址
/*
1.要使用malloc函数,必须添加malloc.h这个头文件
2.malloc函数只有一个形参,并且是整型
3.4表示请求系统为本程序分配4个字节
4.malloc函数只能返回第一个字节的地址
5.上面一行代码分配了8个字节,p变量占4个字节,p所指向的内存也占4个字节
6.p本身所占的内存是静态分配的,p所指向的内存是动态分配的
*/
*p = 5; //*p代表整形变量,只不过*p这个整形变量的内存分配方式和i分配不同
free(p); //表示把p所指向的内存给释放掉
printf("同志们好!\n");
return 0;
}
----------------------------------------------------------------------------
# include <stdio.h>
# include <malloc.h>
void f(int * q){
*q = 200;
free(q); //把q指向的内存释放掉 和 free(p)等价
}
int main(void){
int * p = (int *)malloc(sizeof(int));
*p = 10;
printf("%d\n", *p); //10
f(p); //通过f()修改p指向内存的值
printf("%d\n", *p); //p指向的内存已经释放,所以会出错
return 0;
}
----------------------------------------------------------------------------
/*
2012年2月5日15:37:36
动态一维数组示例
*/
# include <stdio.h>
# include <malloc.h>
int main(void){
int a[5]; //包含20字节
int i;
int len;
int *pArr;
printf("请输入你要存放的元素个数:");
scanf("%d", &len);
pArr = (int *)malloc(4*len); //pArr指向前4个字节 类似于 int pArr[len];
// pArr+1 就指向第5到第8个字节
//动态的构造了一个int类型的一维数组,数组名 pArr
for (i=0; i<len; i++) //对一维数组进行赋值
scanf("%d", &pArr[i]);
printf("一维数组的内容是:\n");
for(i=0; i<len; i++)
printf("%d\n", pArr[i]);
return 0;
}
输出结果:
请输入你要存放的元素个数:5
1 2 3 4 5
一维数组的内容是:
1
2
3
4
5
----------------------------------------------------------------------------
静态内存和动态内存的比较
静态内存是由系统自动分配,由系统自动释放
静态内存是在栈中分配的
动态内存是由程序员手动分配,手动释放
动态内存是在堆分配的
跨函数使用内存的问题
----------------------------------------------------------------------------
# include <stdio.h>
void f(int ** q){
int i = 5;
//*q 等价于p q 和**q都不等价于p
*q = &i;
}
int main(void){
int * p;
f(&p); //访问完后释放内存
printf("%d\n", *p); //本语句语法没有问题, 但逻辑上有问题
//访问了不该访问的内存,f()函数结束后i的空间已经被释放
//静态变量,不能跨函数使用,当函数结束后变量不能被访问
return 0;
}
------------------------------------------------------------------------------
# include <stdio.h>
# include <malloc.h>
void f(int **q){
*q = (int*)malloc(sizeof(int)); //动态分配内存
//等价于 p = (int *)malloc(sizeof(int));
**q = 5;
}
int main(void){
int * p;
f(&p);
printf("%d\n", *p); //f()结束后,p指向的动态内存没有释放
return 0;
}
----------------------------------------------------------------------------------
c语言学习笔记(9)——指针的更多相关文章
- 《C语言学习笔记》指针数组及其应用
C语言中,最灵活但又容易出错的莫过于指针了.而指针数组,是在C中很常见的一个应用.指针数组的意思是说,这个数组存储的所有对象都为指针.除了存储对象为指针,即一个地址外,其它操作和普通数组完全一样. # ...
- C语言学习笔记--数组指针和指针数组
C 语言中的数组有自己特定的类型,数组的类型由元素类型和数组大小共同决定.(如 int array[5]类型为 int[5]) 1.定义数组类型 C 语言中通过 typedef 为数组类型重命名:ty ...
- 【C语言学习笔记】指针
用来存放一个变量地址的变量就叫指针变量.指针变量也是有类型约束的,一般什么类型的指针指向什么类型的变量. 指针之所以叫变量,是因为它里面所存放的变量的地址也是不断变化的,指针是可以移动的. 定义格式: ...
- Go语言学习笔记(6)——指针
指 针 指针: 存储另一个变量的内存地址的变量: Go语言的取地址符号也是& 1. 声明指针: var needle_name *type var b int = 10 var a *int ...
- C语言学习笔记--关于指针的一些小认知
int main() { int i; char *str = "hu mian yuan"; int length = strlen(str); printf("str ...
- Go语言学习笔记九: 指针
Go语言学习笔记九: 指针 指针的概念是当时学C语言时了解的.Go语言的指针感觉与C语言的没啥不同. 指针定义与使用 指针变量是保存内存地址的变量.其他变量保存的是数值,而指针变量保存的是内存地址.这 ...
- GO语言学习笔记(一)
GO语言学习笔记 1.数组切片slice:可动态增长的数组 2.错误处理流程关键字:defer panic recover 3.变量的初始化:以下效果一样 `var a int = 10` `var ...
- Go语言学习笔记十: 结构体
Go语言学习笔记十: 结构体 Go语言的结构体语法和C语言类似.而结构体这个概念就类似高级语言Java中的类. 结构体定义 结构体有两个关键字type和struct,中间夹着一个结构体名称.大括号里面 ...
- Go语言学习笔记七: 函数
Go语言学习笔记七: 函数 Go语言有函数还有方法,神奇不.这有点像python了. 函数定义 func function_name( [parameter list] ) [return_types ...
- Go语言学习笔记四: 运算符
Go语言学习笔记四: 运算符 这章知识好无聊呀,本来想跨过去,但没准有初学者要学,还是写写吧. 运算符种类 与你预期的一样,Go的特点就是啥都有,爱用哪个用哪个,所以市面上的运算符基本都有. 算术运算 ...
随机推荐
- AE地图查询
原文 AE地图查询 地图查询主要有两种查询:空间查询和属性查询 所用到知识点: 1 Cursor(游标)对象 本质上是一个指向数据的指针,本身不包含数据内容,提供一个连接到ROW对象或者要素对象(F ...
- Ehcache与Memcache的差别
ehcache是纯java编写的.通信是通过RMI方式,适用于基于java技术的项目. memcachedserver端是c编写的.client有多个语言的实现,如c.php(淘宝.sina等各大门户 ...
- APK瘦身记,怎样实现高达53%的压缩效果
作者:非戈@阿里移动安全,很多其它技术干货.请訪问阿里聚安全博客 1.我是怎么思考这件事情的 APK是Android系统安装包的文件格式.关于这个话题事实上是一个老生常谈的题目.不论是公司内部.还是外 ...
- Java反射学习总结四(动态代理使用实例和内部原理解析)
通过上一篇文章介绍的静态代理Java反射学习总结三(静态代理)中,大家可以发现在静态代理中每一个代理类只能为一个接口服务,这样一来必然会产生过多的代理,而且对于每个实例,如果需要添加不同代理就要去添加 ...
- php 静态方法和非静态方法的调用说明
1. php类中,静态方法调用当前类的非静态方法必须用self关键字,不能用$this 2. php类中,公有方法调用私有方法使用$this关键字,只能实例化调用 3. php类中,公有方法调用私有方 ...
- iOS开发之Quarz2D:九:图形上下文矩阵操作
#import "VCView.h" @implementation VCView - (void)drawRect:(CGRect)rect { // Drawing code ...
- SpringMVC+Spring+Mybatis+Mysql项目搭建
眼下俺在搭建一个自己的个人站点玩玩.一边练习.一边把用到的技术总结一下,日后好复习. 站点框架大致例如以下图所看到的: 眼下仅仅用到了SpringMVC+Spring+Mybatis+Mysql.把它 ...
- 撸代码--类QQ聊天实现(基于linux 管道 信号 共享内存)
一:任务描写叙述 A,B两个进程通过管道通信,像曾经的互相聊天一样,然后A进程每次接收到的数据通过A1进程显示(一个新进程,用于显示A接收到的信息),A和A1间的数据传递採用共享内存,相应的有一个B1 ...
- Windows Server 2012 R2 部署 Exchange 2013
我的环境在DC上 ,一般建议Exchange 增加DC 通过管理员权限执行PowerShell 来安装一些IIS组件, 安装命令例如以下: Install-WindowsFeature AS-HTTP ...
- css3 实现水晶按钮
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ee432e), color-sto ...