10.华清嵌入式--指针
一.指针的基本用法
1.指针作用:
使程序简洁,紧凑,高效
有效的表示复杂的数据结构
动态分配内存
可得到多个函数返回值。
2.地址和变量定义:
在计算机内存中,每一个字节(Byte)都有一个编号,称为地址。(内存以字节为单位开始编号)内存单元的地址称为指针
变量是数据存储空间的抽象。
3.指针形式:
存储类型 数据类型 *指针变量名
char *p;//数据类型为指针目标的数据类型
指针就是存地址。
int *pa =&a; //指针赋初值
4.是变量就要占内存(指针本身也占内存),指针存地址指向一个变量
只要是指针就是四字节(32位系统,经常出题)
5.指针理解
指针的目标—》变量内存空间(快递员的目标—)顾客的地址)
*取内容,&用来取地址
int *px;
px à指针变量,内容是地址
*pxà指针所指向的对象,内容是数据
&pxà指针变量的地址
指针赋值,值必须是地址常量或指针变量,不能是普通整数(赋0除外)
6.两个指针可互相赋值
float *px,*py;
px=py;
重点:指针的概念,指针变量(存地址的变量)的说明和赋值
(内存中以字节为单位开始编址,这个地址在c语言中称为指针。存储地址的变量称为指针变量。)
7.思考:指针为什么都是4字节(32位系统)??
解释:4字节够表示所有地址了。(0~4G-1个地址)
内存数据要加载到处理器的内部
内存和处理器之间通过地址线相连。(32根地址线)
所以指针占几个字节由地址线决定,如果为64位系统,那么指针就占8个字节。
二.指针的运算
1.指针的运算实质是地址的计算。
指针的运算种类有限,只能进行赋值运算,算术运算和关系运算。(+, -,++,--.=,> ,<等)
px+n //指针向地址大的地方移动n个数据
注:移动的是n个数据,不是n个地址(px-1,px++ px--等都是移动的n个数据)
例:
Int a=10,*p;
p =&a;
Printf(“%p %p\n”,p,p+2);通过打印地址可以看出地址在p基础上移动了sizeof(int)*2个地址
P++ //得到p的值再加加
两个指针相减px-py的结果是两个指针地址位置之间相隔数据的个数(是数据的个数而不是地址的个数)
例:int a[5],*p,*q;
1(a[0]) |
2 |
3 |
4(a[3]) |
5 |
p指向a[1] 的地址(p = &a[0])q指向a[3]的地址(q=&a[3])
此时p-q=3, 而不是12(3*4字节)个字节。
注意:P++的使用;
例
Int a[3]={4,8,1}; int *p,*q;
p=a;
q =p++; //先赋值后++,则q还是p之前的值
则*p指向的数据是8,*q指向的数据是4;
2.指针关系运算符
指针之间的关系运算表示它们指向的地址位置之间的关系,
指针一般不和整数变量进行关系运算,但可以和零进行不等于等于的关系运算
判断指针是否为空。
int *p=NULL;
inta[]={5,8,7};
int y,*p=&a[1];
y=(*--p)++;
则y? a[0]?? //5,6(先使用后++)
int a[5];
int *p,I;
p =a;
for(i=0;i<5;i++) //让用户输入5个整数
scanf(“%d”,p++); //&a[i]
p=a;//防止指针指到别处,重定位一下
for(i=0;i<5;i++)
printf(“%d”,*p++);
注:
*p++ 注:虽然++的优先级高于*,并且也是++先和p结合,但是++是后缀,还是++前的值进行打印。
三. 指针与数组
数组名,代表了内存中的起始地址,任何一个元素也可以取地址
一维数组的数组 名 为一维数组的指针。
a[3] = *(p+3) = *(a+3) =p[i]
注:指针变量和数组在访问数组中元素时,其使用方法具有相同形式,因为指针变量和数组名都是地址量。但指针变量和数组名(数组的指针)在本质上不同,指针变量时地址变量,而数组的指针时地址常量。
使用时一定要注意,防止越界访问。
四.指针与二维数组
二维数组元素连续存储,按行优先存储。
二维数组可以理解为多个一维数组组成。
例:
定义一个二维数组
int a[3][2]; //(a[0][0],a[0][1], a[1][0],a[1][1], a[2][0],a[2][1]))
可以看作为三个一维数组,数组名为a[0],a[1],a[2]
a[0] = *a; a[1] =*(a+1) , a[2]=*(a+2) //可以这么转换
a[3][2] 的sizeof(a[0])=8(字节)
如何使用一级指针遍历二维数组??
p为一级指针,定义的数组为二维数组。
p=a这样直接赋值是错误的,类型不匹配。a是一行一行走
p =&a[0][0];这样赋值是正确的。
二维数组名代表数组的起始地址,数组名+1,是移动一行元素,因此
二维数组名常被称为行地址。
*(a+1)等价于a[1] 一维数组名(一级指针)
aà a+1 //一行一行走(a为二级数组名,为行指针)
a[1]àa[1]+1//一列一列的走
从(a+1)à*(a+1) *号改变了指针的性质(影响到移动)
可以理解为 a为行指针,a[0],a[1],a[2]为列指针
例:
int a[3][2];
printf(“%p %p”,a,a+1);//地址差8个字节(过去了一行)
printf(“%p %p”,a[0],a[0]+1)//地址差四个字节(过去了一列)
行指针(演化出的新概念)
行指针(又称数组指针)
怎么声明一个行指针?
int (*p)[3];//声明了一个指针,数据类型为int,步长为3.(列数)
指针+1,移动了三个数据
此时int a[2][3]; p=a;//这样赋值就没有问题了。
此时的p(变量)和a(常量),形式上可以等价提换。
a[i][j ] = p[i][j] =*(*(a+i)+j) =*(*(p+i)+j) //里面*改变性质,外部*取值。
重点:一级指针如何访问二级数组,行指针如何访问二维数组。
五.字符指针与字符串
字符指针的引入:
内存中字符串首地址赋值给指针,不是将字符串复制到指针中。(可变)
1.
char ch=’A’;
char * p;
p =&ch;
2.
char str[]=”Hello World”;
char *p =str;
当一个字符指针指向一个字符串常量时,不能修改指针指向的对象的值(易出错)
char *p =”Hello World”; //这里的hello world是一个字符串常量,p指向的是字符串常量的地址(静态存储区)
*p = ‘h’ //错误
举例:
char *p1 = “hello world!”;
char *p2 = “hello world!”;
通过打印可以发现,p1和p2指向内容的地址是一样的,指针本身的地址不同。
全局变量,static修饰的局部变量,还有字符串常量,这三种是放在静态存储区的。
静态存储区,在程序结束后,才释放内存。
相同的字符串常量在内存中只占一份内存。
char ch[50] = “welcome”;
char *p1=”hello world”;
strcpy(p1,ch); //错误,语法上没问题,但是会报错误,原因是字符串常量不能修改。
练习实现字符串连接功能。
char ch[100]=”welcome”;
char *p=”hello”;
int i=0;
while(*(ch+i) != ‘\0’)
i++;
while(*p != ‘\0’)
{
*(ch+i) = *p;
i++;
p++;
}
*(ch+i) = *p; //把\0补上
puts(ch);
重点:字符指针如何操作字符串以及字符串常量相关内容。
六.指针数组
用法:
指针数组:若干个具有相同存储类型和数据类型的指针变量构成的集合。
(指针数组是一个数组,这个数组里存的都是指针变量)
int * pa[3]; //pa[0] pa[1] pa[2]
int a[]={5,4,2,6,9};
pa[0] =a; //a为数组名等价于&a[0]
pa[1] =a+1;
pa[2]=a+2;
扩展
int a[2][3]={{1,4,6},{12,9,7}}; //二维数组名为a ,还有两个一维数组名a[0],a[1]
int *p[2];
p[0] =a[0];//&a[0][0]
p[1] =a[1];//&a[1][0]
以上赋值可以简化为:
int *p[]={a[0],a[1]};
printf(“”%d\n”,a[0][1]); //4
printf(“”%d\n”,*(a[0]+1));//4
printf(“”%d\n”,*(p[0]+1));//4
通过指针数组,遍历二维数组
int a[2][3]={{1,4,6},{12,9,7}};
int *p[2]={a[0],a[1]};
int I,j;
for(i=0;i<2;i++)
{
for(j=0;j<3;j++)
{
printf(“%d”,*(a[i]+j)); //*(p[i]+j)a[i]=>*(a+i)
}
}
指针数组名相当于什么样的指针??
假设 p为一个指针数组 int *p[2];
那么q=p;(p=>&p[0])//q的目标是p[0],也就是q指向p[0]地址。
则q 为int * *q;(一个int类型的指针,目标也是指针)
而p[0]也为int指针(q指向p[0],p[0]又指向一个int常量)
int * *q;(自己是一个指针,目标也是一个指针,这就是二级指针)。
七.多级指针
定义:一个指向指针变量的指针变量,称为多级指针变量。
例:
int m =10; int * p;
int* *q;
p=&m; //一级指针
q =&p;//二级指针
指针变量+1,向地址大的方向移动一个数据(目标)
int **p;
p+1;//移动一个一级指针,也就是移动4个字节
二级指针经常和指针数组一起使用原因。
int a[]={3,6,9};
int *p[2] = {&a[0],&a[1]};
int **q;
q=&p[0]//q=p;
则:a[0] =>*p[0] => * *q
a[1] => *p[1] =>**(q+1)
举例:打印出以下字符串
char *s[] ={“apple”,”pear”,”potato”};//字符串常量赋值给指针数组
char **p;//二级指针
int i,n;
n= sizeof(s) / sizeof(char *); //得到有几个元素
p =&s[0]; //p=s
while(i<n){
printf(“%s %s\n”,s[i], *(p+i));
}
八.void指针 和const修饰符(经常出题)
void指针是一种不确定数据类型的指针变量。
当我们想定义指针,想指向不确定类型的数据。
定义两个不同类型变量
int m=10; double n=3.14;
void *p,*q;
p = (void *)&m;//转换为void 型的指针
q =(void *)&n;
printf(“%d\n”,*((int*) p));
printf(“%d\n”,*((double*) p)); //使用时必须要强制转换
void指针在没有强制类型转换之前,不能进行任何指针的算数运算。
(不知道类型当然不知道移动多少了)
使用void指针遍历一维数组。
int a[]={1,2,3,4,5};
int i,n;
n =sizeof(a)/sizeof(int);
void *p;
p=a;
for (i=0;i<n;i++)
{
printf(“%d”,*( (int*) p + i)); //printf(“%d”,a[i]);
}
很多库函数中使用到void指针,应用比较广泛。
线程创建,内存分配等函数(特殊设计,使函数通用性更好)
const //使一个数据变为常量化。(修饰谁,谁就不能改)
const int m =10; //将m变为只读。
const int *p; //const放到*p前面,不能通过指针改目标
int * const q; //q为常量,q不能再赋值。
main 参数的含义
argc //参数个数
argv[]; //跟你要传的参数。(指针数组)