第十章 指针
10.1 保险箱与钥匙ok
例1 有一个保险箱A,将钥匙A放在保险箱B;也就是通过变量地址间接访问,将变量比作保险箱,变量A在内存中的地址就是钥匙A,变量A的地址放到另一个变量B中,相当于将A钥匙放在B保险箱里
//关键代码
//去变量A的地址可以用取地址运算符&
//&A 表示变量A的地址
//需要存放在专门存放地址的变量里 可以这样定义:
int *B;
//这个存放地址的变量B称为指针变量 如果指针变量B中存放的是变量A的地址:
B=&A;
//通过指针变量B来访问变量A的值 可以使用取内容运算符*
//*B 表示变量B中存储的地址 所对应的变量 相当于变量A
#include <stdio.h>
int main(void)
{
int A =2017;
int *B; //定义指针变量B
B=&A; //指针变量B 存放变量A 的 内存地址
printf("输出变量A:\t\t\t%d\n",A);
printf("输出指针变量B对应的值*B:\t%d\n",*B);
printf("输出A的地址&A:\t\t\t%d\n",&A);
printf("输出B:\t\t\t\t%d\n",B);
return 0;
}
10.2 数据的直接访问与间接访问ok
1 内存地址ok
①内存有许多大小相同的存储单元,每个单元按顺序编了号,这个编号就是内存单元的地址
②只要给出了内存单元的地址,就能找到要访问或修改的数据
2 定义变量ok
①在程序中定义了一个变量,在编译时就会根据变量的类型为其分配一定数量的存储单元,并将分配的内存单元【首字节】的地址称为该变量的地址
②一般通过变量名对内存单元进行存取操作,变量名实际上就是给内存单元一个容易记忆的名字
③访问变量时首先根据变量名与内存单元之间的对应关系找到其内存地址,然后再进行数据的读写操作
3 直接访问ok
①有定义 int a 占4个字节,编译时分配了四个内存地址分别是00000001,00000002,00000003,00000004
②有语句 a=10 ,那么根据变量名与地址的对应关系,找到变量a的首地址00000001,然后将数值10保存在首地址及之后的四个地址中
③这种按变量名存取变量值的方式称为直接存取方式,或直接访问方式
4 间接访问ok
①定义了一个变量a_pointer ,用来存放整型变量的首地址:a_pointer=&a
②若要取变量a的值,可以先找到存放a地址的变量a_pointer,从中取出a的地址,然后到内存中相应位置取出a的值
③这种访问方式就称为间接访问方式
5 指针ok
①&:取地址运算符。&a:变量a的地址
②到内存中对应地址的位置,取出4个字节长度就是a的值
③这里的地址就称为指针,一个变量的地址可以称为该变量的指针
10.3 必备知识
1 变量的指针和指针变量ok
①【指针】实际上就是【地址】,【变量的指针】就是【变量的地址】
②如果有一个变量是专门用来存放另一变量的地址的,则它称为【指针变量】,也可以称为【地址变量】
③指针运算符 * 表示指向,a_pointer是一个指针变量,存放了变量a的地址 &a ,那么 *a_pointer就表示指向的变量,等价于a
④所以 *a_pointer=10 等价于 a=10
2 指针变量的定义ok
//1 定义整型指针变量
int *i_pointer;
//对指针变量进行类型定义,是为了确定从首地址往后取多少个字节长度
//2 使一个指针变量指向另一个变量,只需要把 被指向变量 的 首地址 赋值给指针变量
int i,*i_pointer;
i_pointer=&i;
//3 指针变量只能存放地址,不能存放整数,所以也不可以把地址直接写成整数赋值给指针变量
//4 如果想要将整型数赋值给指针变量,那么一定要加上强制转换
int *i_pointer=(int *)0x0012FF74;
//5 必须保证存放的是一个可以安全访问的地址
3 指针的运算ok
指针运算符ok
①&取地址:根据【变量】取出【地址】
②*:指针运算符 或 定义的时候表示指针变量的标志
int *i_pointer;//定义指针变量的标志
*i_pointer=10;//指针运算符
例2 输出a和b两个整数,以指针形式输出a和b
#include <stdio.h>
int main(void)
{
int *p1,*p2,a,b;//定义指针变量p1 p2 定义整型变量a b
printf("输入a和b:");
scanf("%d %d",&a,&b);
p1=&a;//指针变量p1指向变量a
p2=&b;//指针变量p2指向变量b
printf("a=%d b=%d",*p1,*p2);//将指针变量转化为值
return 0;
}
指针的加减运算ok
①指针加减一个整数值,或者两个指针值相减
②和整数加减使不一样的,表示读取下一个空间
例3 指针的加减运算
#include <stdio.h>
int main(void)
{
int a=1,b=2,c=3,i;
//自右向左的顺序分配存储空间
int *p=&c; //定义的同时赋值
for(i=0;i<3;i++)
{
printf("(%p)=%d\n",p,*p);//*p是取出地址对应的值
//这里 p是指针变量
//%p 表示输出指针变量的格式符 也就是输出地址
p++;
//p++ 表示下一个内存地址
}
return 0;
}
指针的关系运算ok
//== != 判断两个指针是否指向同一个内存地址
//>= <= > < 判断谁先谁后,先分配内存单元的小 后分配内存单元的大
//NULL 0 表示指针不指向任何变量
if(p==NULL)
if(p==0)
4 指针访问数组元素ok
①数组元素的存取,既可以使用下标方式,也可以使用指针方式,用指针更简洁,运行更快
②数组名表示数组在内存中存储的首地址,也就是第0个元素的地址,因此可以用数组名来初始化一个同类型的指针变量,并用这个经过初始化的指针变量来代替原来的数组名
p=&a[0];
p=a;
//这两条语句等价
//也可以在定义的时候赋初值
int *p=&a[0];//p的初值为a[0]元素的地址
int *p=a;
//指针变量指向数组元素后,可以通过指针引用数组元素
p=&a[3];
p=4;//相当于a[3]=4
例4 利用指针输出数组中各元素的地址和数值
#include<stdio.h>
int main(void)
{
int a[6]={5,3,4,1,2,6};
int i;
int *p=a;//将变量a的地址给p,*表示将地址转化为变量
for(i=0;i<6;i++)
{
printf("(%p) %d %d %d\n",p+i,a[i],p[i],*(p+i));
//p存放的是地址 p+1表示数组中的下一个元素的地址 而不是简单的+1
//所以p+i就表示下标为i的数组元素的内存地址
//a[i]就是下标为i的数组元素的值
//p[i] 等价于 *(p+i)
//*(p+i) *就是将地址转化为值
}
}
例5 利用一维数组实现一维数组元素的输入与输出
#include <stdio.h>
int main(void)
{
int a[6],i,*p;
p=a;
printf("请输入6个数字:\n");
for(i=0;i<6;i++)
{
scanf("%d",p);//获取用户输入
p++;
}
p=a;
for(i=0;i<6;i++)
{
printf("%d\t",*p++);//*p 将地址p转换成值
}
return 0;
}
5 字符指针ok
对字符串操作的两种方法ok
//1 使用字符数组存储
char str[]={"Welcome!"};
printf("%s",str);//Welcome!
printf("%s",str+3);//come! 首地址向后加3 直至扫到\0
printf("%s",str[1]);//输出单个字符e
//2 使用字符型指针变量
char *s="Welcome!";
//等价于
char *s;
s="Welcome!";
//s被定义为指针变量,指向字符型变量,只是将首地址赋值给指针变量s
//可以对字符指针赋值,使他指向其他的字符串,但是不可以对字符数组名命名
printf("%s",s);
//系统首先输出所指向的第一个字符,然后自动s+1,检测到不是\0就输出
例6 字符串数组中的字符反向输出
#include <stdio.h>
int main(void)
{
char str[6]={'a','b','c','d','e','f'};
char *p=&str[5];
printf("反向输出字符串为:");
while(p>=str)
{
printf("%c",*p);
p--;
}
printf("\n");
return 0;
}
例7 字符串拼接
#include <stdio.h>
#include <string.h>
int main(void)
{
char a[50]={"welcome to"};
char b[]={"C language!"};
char *pa,*pb;
pa=a;pb=b;
while(*pa!='\0')
{
pa++;//pa指针一直到数组a的最后\0处,要将其替换
}
while(*pb!='\0')
{
*pa=*pb;//将pb指向的值赋值给pa处
pa++;pb++;//同时向后++
}
*pa='\0';//让a数组最后为\0
pa=a;//pa指针回到a数组开头
printf("结果是:\n");
printf("%s\n",a);//直接输出数组a
printf("%s\n",pa);//pa是a的首地址,但是加上%s就是输出字符串
//如果是%p就是输出首地址
return 0;
}
6 指针作为函数参数ok
指针变量作为函数参数接收变量地址ok
当函数的形式是指针类型时,相应的实参应为同一类型的指针
函数调用时,将一个变量的地址传送给被调用函数的形参,就可以在被调用函数中通过该指针间接访问主函数中的变量值了
例8 输入两个整数值按从大到小排序
#include <stdio.h>
void swap(int *p1,int *p2)//这里*p表示输入的是int指针类型变量
//相当于 p1=&a p2=&b
{
int temp;
if(*p1<*p2)//这里*p表示指针p指向的变量值
{
temp=*p1;//这里都是具体值
*p1=*p2;
*p2=temp;
}
//因为是指向地址的,所以是在内存地址中交换了两者的位置
//变量值的变化会被保存下来,因为直接修改内存地址里面的值
//结束后p1 p2会被释放 会消失不见
}
int main(void)
{
int a,b;
printf("请输入a和b:\n");
scanf("%d %d",&a,&b);
swap(&a,&b);//调用输入地址
printf("max=%d min=%d\n",a,b);
return 0;
}
指针变量作为函数参数接收一维数组地址ok
①向函数传递一维数组的实质就是向函数传递一维数组首元素的地址
②可以将函数的形式参数定义为 指针变量 或 数组 来接收数组的首地址,那么调用函数的实际参数也应为一个同类型的地址值
例9 使用函数完成数组的复制
#include <stdio.h>
void arraycopy(int *s,int *d,int size)
//也可以定义成数组的形式
//(int s[],int d[],int size)
//*(s+i)等价于a[i],也可以写成s[i]
{
int i=0;
for(i=0;i<size;i++)
{
d[i]=s[i];
}
}
void out(int *p,int size)
{
int i=0;
for(i=0;i<size;i++)
{
printf("%d ",*(p+i));
}
printf("\n");
}
int main(void)
{
int a[]={3,5,1,7,0,5,8,6};
int b[8];
printf("sizeof(a)=%d\n",sizeof(a));
arraycopy(a,b,sizeof(a)/sizeof(int));
printf("a数组的数据是:");
out(a,sizeof(a)/sizeof(int));
printf("b数组的数据是:");
out(b,sizeof(b)/sizeof(int));
return 0;
}
7 返回值为指针类型的函数ok
指针能够作为函数的一种返回值类型
例10 求一维数组中的最大值
#include <stdio.h>
int *max(int array[],int size)//求一维数组的最大值
//max 函数名
//max前面是函数返回值类型,int*表明返回的是整型指针
{
int *m=array,i;
for(i=0;i<size;i++)
{
if(array[i]>*m)
{
m=&array[i];//*m指向数组中最大的元素
}
}
return m;
}
int main(void)
{
int a[]={6,4,2,1,7,9,18,5,0,8};
printf("最大值是:%d\n",*max(a,sizeof(a)/sizeof(int)));
//这里实际上返回的是地址,但是%d将其转化成int类型
return 0;
}
例11 编写一个函数,用于去掉字符串前面的空格
#include <stdio.h>
char *mytrim(char *s)
{
char *p=s,*q=s;
while(*p==' ')
{
p++;
}
while(*p!='\0')
{
*q=*p;
p++;
q++;
}
*q='\0';
return s;
}
int main(void)
{
char str[]=" ni hao wa";
printf("原始字符串:%s\n",str);
printf("截取空格之后的字符串:%s\n",mytrim(str));
return 0;
}