10.华清嵌入式--指针

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[]; //跟你要传的参数。(指针数组)