赞
踩
C语言的BOSS来了
为什么需要指针?
指针是什么?
类比C语言中的数组,指针这个概念也泛指一类数据类型。任何程序数据载入内存后,在内部都有他们的地址,这就是指针。而为了保存一个数据在内存中的地址,我们就需要指针变量。
因此:指针是程序数据在内存中的地址,而指针变量是用来保存这些地址的变量。
int p;//p是一个普通的整型变量
int *p;//p是一个返回整型数据的指针
int p[3];//p是一个由整型数据组成的数组
int *p[3];//p是一个由返回整型数据的指针所组成的数组
int (*p)[3];//p是一个指向由整型数据组成的数组的指针
int **p;//二级指针
int p(int);//返回值为整型的函数(有一个整型变量的参数)
int (*p)(int);//p是一个指向有一个整型参数且返回类型为整型的函数的指针
int *(*p(int))[3];//p是一个参数为一个整型数据且返回一个指向由整型指针变量组成的数组的指针变量的函数
指针的类型:把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型
指针所指向的类型:把指针声明语句里的指针名字和名字左边的指针声明符’*'去掉,剩下的部分就是指针所指向的类型。
指针的值(或者叫指针所指向的内存区或地址)
指针的值是指针本身存储的数值,这个值将被编译器当作一个地址,而不是一个一般的数值。在32位程序里,所有类型的值都是一个32位整数,因为32位程序里内存地址全都是32位长。指针所指向的内存区就是从指针的值所代表的那个内存地址开始,长度为sizeof(指针所指向的类型)的一片内存区。以后,我们说一个指针的值是XX,就相当于说该指针指向了以XX为首地址的一片内存区域;我们说一个指针指向了某块内存区域,就相当于说该指针的值是这块内存区域的首地址。
指针本身所占据的内存区
指针本身占了多大的内存?你只要用函数sizeof(指针的类型)测一下就知道了。在32位平台里,指针本身占据了4个字节的长度。指针本身占据的内存这个概念在判断一个指针表达式是否是左值时很有用。
运算符&和*
int a=10;
int *p=&a;
//定义整型指针变量p,初始化p的值为&a,p指向变量a
*p=30;//通过指针变量p引用a变量,改变a的值为30
char a[20];
int *ptr=(int*)a;//强制类型转换并不会改变a的类型
ptr++;
在上例中,指针ptr的类型是int*,它指向的类型是int,它被初始化为指向整型变量a.接下里的第3句中,指针ptr被加了1,编译器是这样处理的:它把指针ptr的值加上了sizeof(int),在32位程序中,是被加上了4,因为在32位程序中,int占4个字节。由于地址是用字节做单位的,故ptr所指向的地址由原来的变量a的地址向高地址方向增加了4个字节。由于char类型的长度是一个字节,所以,原来ptr是指向数组a的第0号单元开始的4个字节,此时指向了数组a中从第4号单元开始的四个字节。
int array[20]={0};
int *ptr=array;
for(i=0;i<20;i++){
(*ptr)++;
ptr++;
}
此例将整型数组中各个单元的值加1.由于每次循环都将指针ptr加1个单元,所以每次都能访问数组的下一个单元。
char a[20]="You_are_a_girl";
int *ptr=(int*)a;
ptr+=5;
加5后,ptr已经指向数组a的合法范围之外了。虽然这种情况在应用上会出问题,但在语法上却是可以的。
两个指针不能进行加法运算,这是非法操作。
指针相减:指针减指针的绝对值指的是两个指针之间元素的个数。
前提:两个指针必须指向同一空间(指向同一个数组中的元素)
eg: &arr[9]-&arr[0]
指针的关系运算
指针于指针之间比较大小
只有当两个指针指向同一个数组中的元素时,才能进行关系运算
eg: 指针p和q指向同一数组中的元素
编写程序将一个字符串反向输出
#include<stdio.h> int main(){ char str[50],*p,*s,c; printf("Enter string:"); gets(str); p=s=str;//将指针初始化 while(*p){ p++;//将p指向字符串最后 } p--; while(s<p){ c=*s; *s++=*p; *p--=c; } puts(str); return 0; }
指向空,或者说不指向任何东西。在C语言中,我们让指针变量赋值为NULL表示一个空指针,而C语言中,NULL实质是((void*)0),在C++中,NULL实质是0
换种说法:任何程序数据都不会存储在地址为0的内存块中,它是被操作系统预留的内存块。
下面代码摘自stdlib.h
#ifdef _cplusplus
#define NULL 0
#else
#define NULL ((void*)0)
#endif
void*类型指针
由于void是空类型,因此void*类型的指针只保存了指针的值,而丢失了类型信息,我们不知道他指向的数据是什么类型的,只指定这个数据在内存中的起始地址,如果想要完整的提取指向的数据,程序员就必须对这个指针作出正确的类型转换,然后再解指针。因为编译器不允许直接对void*类型的指针做解指针操作。
强制类型转换:前面的例子涉及到了,略
那可不可以把一个整数当作指针的值直接赋给指针呢?可以
unsigned int a;
TYPE *ptr;//TYPE是int,char或结构类型等
a=N;//N必须代表一个合法的地址
ptr=(TYPE*)a;//这里的TYPE*是把无符号整数a的值当作一个地址来看待。
//相反的,把指针指向的地址即指针的值当作一个整数取出来也可以
int a=123,b;
int *ptr=&a;
char* str;
b=(int)ptr;
str=(char*)b;
结构体指针变量
struct Student{
char* s_id;
char* s_name;
char* s_sex;
int* s_age;
};
结构体类型指针访问成员的获取和赋值形式:
实例:
#include <stdio.h>
struct Inventory{//商品
char description[20];//货物名
int quantity;//库存数据
};
int main ()
{
struct Inventory sta={"iphone",20};
struct Inventory* stp=&sta;
printf("%s %d\n",stp->description,stp->quantity);
printf("%s %d\n",(*stp).description,(*stp).quantity);
return 0;
}
指针数组:存放指针的数组(int* arr[]) 指针数组就是指针类型的数组
#include<stdio.h>
int main(){
int a=0;
int b=1;
int *p1=&a;
int *p2=&b;
int *arr1[]={p1,p2};//指针数组
int *arr2[]={&a,&b};//指针数组
return 0;
}
数组指针:指向数组的指针
//int(*)[] //应用:遍历整个二维数组 #include<stdio.h> void my_print(int(*p)[5],int x,int y){ int i=0; for(i=0;i<x;i++){ int j=0; for(j=0;j<y;j++){ printf("%d",*(*(p+i)+j)); //printf("%d",p[i][j]); //p[n]等同于*(p+n) //p[n][m]等同于(*(p+n)+m) } printf("\n"); } } int main(){ int arr1[3][5]={{1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7}}; my_print(arr1,3,5); return 0; }
#include<stdio.h>
int main(){
int arr[3]={1,2,3};
int* p=arr;
printf("sizeof(arr)=%d\n",sizeof(arr));
printf("sizeof(p)=%d\n",sizeof(p));
return 0;
}
这就是为什么我们将一个数组传递给一个函数时,需要用另外一个参数传递数组元素个数的原因了。
typedef struct{
char name[31];
int age;
float score;
}Student;
void show(const Student *ps){
printf("name:%s,age:%d,score:%.2f\n",ps->name,ps->age,ps->score);
}
我们只是在show函数中读取Student变量的信息,而不会去修改它,为了防止意外修改,我们使用常量指针去约束。另外我们为什么使用指针而不是直接传递Student变量呢?
从定义的结构可以看出,Student变量的大小至少是39个字节,那么通过函数直接传递变量,实参赋值数据给形参需要拷贝至少39个字节的数据,极不高效。而传递变量的指针却快很多。
因为在同一个平台下,无论什么类型的指针,大小都是固定的:x86指针4字节,x64指针8字节,远远比一个Student结构体变量小。
函数指针数组
定义:存放函数指针类型元素的数组
//应用:实现计算器 #include<stdio.h> int Add(int x,int y){ return x+y; } int Sub(int x,int y){ return x-y; } int Mul(int x,int y){ return x*y; } int Div(int x,int y){ return x/y; } void menu(){ printf("1.Add\n"); printf("2.Sub\n"); printf("3.Mul\n"); printf("4.Div\n"); printf("0.exit\n"); } int main(){ menu(); int input=0; printf("请选择:"); scanf("%d",&input); int ret=0; int(*pfarr[])(int,int)={0,Add,Sub,Mul,Div}; do{ if(input==0){ printf("退出\n"); break; } else if(input>=1&&input<=4){ int x=0; int y=0; printf("请输入两个操作数:"); scanf("%d%d",&x,&y); ret=pfarr[input](x,y); printf("结果是%d\n",ret); break; } else{ printf("选择错误!"); } }while(input); return 0; }
指向函数指针数组的指针
int(*(*parr)[4])(int)(int)=&parr
函数的指针
每一个函数本身也是一种程序数据,一个函数包含了多条执行语句,它被编译后,实质上是多条机器指令的合集。在程序载入到内存后。函数的机器指令存放在一个特定的逻辑区域:代码区。
既然是存放在内存中,那么函数也是有自己的指针的。
C语言中,函数名作为右值时,就是这个函数的指针。
#include<stdio.h>
void echo(const char* msg){
printf("%s",msg);
}
int main(){
void(*p)(const char*)=echo;//函数指针变量指向echo这个函数
p("Hello ");//通过函数的指针p调用函数,等价于echo("Hello ")
echo("World");
return 0;
}
如果const后面是一个类型,则跳过最近的原子类型,修饰后面的数据。(原子类型是不可再分割的类型,如int,short,char以及typedef包装后的类型)
如果const后面就是一个数据,则直接修饰这个数据
#include<stdio.h>
int main(){
int a=1;
int const*p1=&a;//const后面是*p1,实质是数据a,则修饰*p1,通过p1不能修改a的值
const int*p2=&a;//const后面是int类型,则跳过int,修饰*p2,效果同上
int* const p3=NULL;//const后面是数据p3,也就是指针p3本身是const
const int* const p4=&a;//通过p4不能改变a的值,同时p4本身也是const
int const* const p5=&a;//效果同上
return 0;
}
typedef包装后的类型
#include<stdio.h>
typedef int* pint_t;
//将int*类型包装为pint_t,则pint_t现在是一个完整的原子类型
int main(){
int a=1;
const pint_t p1=&a;
//同样,const跳过类型pint_t,修饰p1,指针p1本身是const
pint_t const p2=&a;//const直接修饰p2,同上
return 0;
}
野指针:不正确,指向位置随机的指针
野指针的危害
野指针的产生原因及解决方法
如何规避野指针?
指针变量也是有其对应地址的,那么既然有地址,就可以用另一个指针变量指向它的地址,也就是指向指针变量地址的指针,简称指向指针的指针(双重指针或二级指针)。而指向指针的指针也是有地址的,那又可以有指向其地址的指针,这就是多重指针了。
int a=111;//普通变量
int *p=&a;//普通指针(一级指针):指向普通变量的地址
int *p1=p;//同一级指针之间是相互赋值,而不是指向
int **q=&p;//二级指针(双重指针):指向一级指针的地址
int ***r=&q;//三级指针(三重指针):指向二级指针的地址
双重指针作为函数形参
一般来说函数的形参无法改变实参,除非形参是指针类型的。那么如果实参是一个指针,想要在一个函数中改变一个指针的指向应该怎么做?
例如:若定义了以下函数fun,如果p是该函数的形参,要求通过p把动态分配存储单元的地址传回主调函数,则形参p应当怎样正确定义?
形参指针p应该定义成二级指针,只有二级指针才能在函数中改变一级指针的指向。
void fun(int **p){
*p=(int*)malloc(10*sizeof(int));
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。