C 指针详细总结

一指针的好处

提高程序效率(高效地传递数组和结构,指针偏移可以指向任何数组元素或者任何数据结构的成员)。

实现动态数组,对多个相似变量的一般访问(模拟数组)。

实现各种动态分配的数据结构


二指针的概念

内存中每一个字节有唯一编号,这就是“地址”。通过变量的地址来访问的这种间接访问的方式(相对于使用变量名)便是指针的意义所在,因为地址比较难于记忆。

指针是变量(且只能存放地址),用来存放其它变量地址的

指针的概念-误区:地址则是内存单元的编号,是一个常值。指针绝对不等同于地址,数组名是常地址。

指针的定义方式:类型*指针变量名;定义了指针变量之后,它只能指向同一个类型的变量。


三指针的使用

(1)关于运算:

和指针有关的运算符  :&:取址运算符(只能作用于变量)。*:指针运算符(间接访问运算符),或指针定义符(*p 代表i变量本身,可做左值).

指针有以下几种运算:赋值、*运算、&取地址、加/减一个整数、自增/减、求差、比较。也就是说指针可以进行的算术运算,只有以下几种:p±n, ++p/p++, --p /p--, p1-p2

比较特殊的是以下两种情况:

p±n:将指针从当前位置向前(+n)或回退(-n)n个数据单位(指针指向的类型),而不是n个字节。显然,++p/ p++和--p/p--是p±n的特例(n=1)。

p1-p2:两指针之间的数据个数,而不是指针的地址之差。

(2)一个小陷阱 :

 result = j /*p;   /*会被当做注释,虽然有点无聊,但有可能的是用记事本这种非IDE的时候或许会出现吧

(3)其他注意事项:

使用指针变量之前确保该指针变量指向一个合法可用的内存单元。

if( NULL == p )比if( p == NULL )的写法要好,防止意外赋值


四多级指针

二级指针:指向指针的指针

int i = 20;
int *pi = &i;
int **ppi =&pi


其实指针也是变量,本身当然也有地址,在使用二级指针的时候忘记那个指针,把他当变量来考虑,就行了。
为什么要使用二级指针:
先看下面例子(省略malloc是否分配成功的处理)

void getspace(char *pi)
{
    pi = (char*)malloc(10*sizeof(int));
}
int main()
{
    int i = 0;
    char *p = NULL;
    getspace (p);
    for(i = 0;i<10;i++) 
    p[i] = i;
}


从变量的角度看getspace并不能改变p的值,所以p并不能指向这块堆内存,修改方法如下

void getspace (char **pi)
{
*pi = (char*)malloc(10*sizeof(int));
}
int main()
{
    int i = 0;
    char *p = NULL;
    getspace (&p);
    for(i = 0;i<10;i++)
        p[i] = i;
}

多级指针—误区:
不要把多级指针和指向数组的指针定义混淆(始终记得这句话   类型 *指针变量名):
char **p1; 是普通二级指针
char (*p2)[5];  是指向char [5]数组的指针。
char *p3 [5]; 是以char* 为基本元素类型的指针数组
p1是二级指针,p2、p3级别等效为二级指针。
一个小实验:
main()
{
    char **p1;
    char a[10][5];
    char(*p2)[5] = a;
    p1 = a;
    //查看p1+1和p2+1
}
实验证明:可以让一个指向一维数组的指针去模拟二维数组的行为,把二维数组看成由大元素构成的一维数组。指向一维数组的指针和普通二维数组可以在一起使用。


五 指针( 的)数组
int *a[10];   //定义了一个一维指针数组,特殊在每个元素都是指针。
可让一个二级指针指向一维指针数组。二级指针和一维指针数组可以在一起使用。int **p = a; (a+1和p+1是多少?自己验证吧)
指针数组较适合用来指向若干个字符串,使字符串处理更加灵活。
加深理解:
int a[4][5]; //a+1?
int (*p1)[5];//p1+1?
int **p2;//p2+1?
int *p3[5];//p3+1?
可以让指针p1获得数组a的首地址,进而访问二维数组的各个元素,可以让p2获得指针数组p3的首地址,进而访问指针数组各个元素


六 指针与const
情况一:指向常量的指针   const int i = 40;  const int *pi = &i;
情况二:指向变量的指针常量(常指针) int i = 40 ;int * const pi = &i; 此时指针是常量,它忠心耿耿的指着i,记载i的地址,决不见异思迁。但是通过pi却可以修改i的值,因为i是变量。
情况三:指向常量的指针常量 const int i = 20;const int * const pi = &i; pi值不能改,也不能通过pi修改i的值。因为不管是*pi还是pi都是const的.
举个例子:
const int *p;
int *q;
p = q;  //ok!
q = p; //q破坏了p指向常量的特征
总结:两个操作数都是指向有限定符或无限定符的相容类型的指针,左边指针所指向的类型必须具有右边指针所指向的类型的全部限定符。----《C专家编程》


七 函数中的指针
指针在函数中出现的位置:
1、出现在返回值处:如:char*strcpy(char *, const char *);如果一个函数返回了指针,需要注意什么问题?
2、出现在函数名的位置:如:char* (*pstrcpy)(char *, const char *);定义了一个函数指针。
3、出现在形参表中 void fun(int *p);  作用:1. 是为非指针变量传址,如:fun(&i);2. 是为指针变量传值,如:fun(p); 当用数组作形参的时候,等价于用指针做形参。


八 函数指针的定义
函数的定义:
返回值类型 函数名(参数表)
{
函数体
}
编译后函数就是一段段的机器码,函数名就是这段机器码的首地址(函数入口地址),是个地址常量。函数名是地址,地址又可以给指针赋值,所以可以让指针指向函数。
函数指针的操作:
给函数指针赋值:  pf = 函数名
使用函数指针调用函数: (*pf)(实参列表);
其它对函数指针的操作都是没有意义的,例如:pf++,pf1 –pf2等等。
函数指针的用途
假设你在开发某软件过程中,上一级模块传入一段二进制数据,输入的参数为char* buffer和int length,buffer是数据的首地址,length表示这批数据的长度。数据的特点是:长度不定,类型不定,由第一个字节(buffer[0])标识该数据的类型,共有256种可能性。你的任务是必须对每一种可能出现的数据类型作处理(这里每一种处理都是用相同类型的函数)。如果用一个switch函数则需要写256个函数所以这时候可以使用函数指针,上面的情况可以这么解决:
void funtion0(void);

void funtion255(void); //函数声明
void (*fun[256])(void) = {function0, …, function255};
void MyFuntion( char* buffer, int length )
{
unsigned char nStreamType = buffer[0];
( *fun[nStreamType] )( );
}

结论:有很多相同类型的函数,根据不同的要求调用不同的函数,这时可使用函数指针数组,使程序简洁。


九各种各样的指针
int *ptr;  //指向int类型变量的指针
char *ptr; //指向char类型变量的指针
int **ptr; //指向指向int类型变量的指针变量的指针
int *ptr[3]; //有三个指向int类型变量的指针的数组
int (*ptr)[3]; // 指向含有三个int类型元素的数组的指针
int (*pfun)(int ,int); // 指向返回值为int类型,参数有两个,都是int类型的函数的指针
int* ptr[3][4];  //二维指针数组
int*(*ptr)[4]; // 指向整个一维指针数组的指针
void* (*ptr)(void*);  //指向函数的指针
int* pMove();  //返回指针的函数
int (*p[3])(int); //函数指针数组
int (*p)[3][4]; //指向整个二维数组的指针
int (*p[3])[4];  //指向一维数组的指针的数组


十 复杂指针解析的方法—分解法:
首先从未定义的标识符起往右看,当遇到圆括号时,就掉转方向往左看。解析完圆括号里面所有的东西,就整个给它一个命名。将命名与外面的东西再重复这个过程直到将整个声明解析完毕。外面的东西其实是类型
第一个例子:int (*func)(int *p, int (*f)(int*));
func被一对括号包含,且左边有个*号,说明func是个指针,给它一个命名,比如F。其右边也有个括号,那么func是个指向函数的指针,这类函数具有int *和int (*)(int*)形参,返回值为int类型。再来看形参int (*f)(int*),类似前面的解释,f也是一个函数指针,指向的函数具有int*类型的形参,返回值为int.
第二个例子:int (*func[5])(int *p);
*func[5]被一对括号包含,说明func是个指针数组,给它一个命名,比如F。再看F右边也有个括号,那么F是个指向函数的指针数组,这类函数具有int *形参,返回值为int类型
第三个例子char ( * ( * ( * q ) ( ) ) [5] ) ( );
char ( * ( *T )[5])( ) T代表( *q )( );
char ( *K)( ) K代表( * )[5]( *q )( );
结论:q是个指向函数的指针,该函数无形参,它的返回类型是指向整个一维数组的指针,该数组元素的类型是指向函数的指针,该函数无形参返回类型为字符型。
其实写第三个例子的纯粹是吃饱了撑的附加脑袋被门挤了,或者真的是大神?ORZ


十一 void类型的指针

void *p; 说明p是无类型的指针,该指针没保存类型信息,只是保存了个地址,就像个“地址存储器”
十二 关于“野指针”
“野指针”:是未指向合法内存的指针,此时对它的操作将导致未知结果。(又称:“指针悬空”)。
(1)“野指针”的产生通常有4种原因:
1 指针变量没有被初始化。指针变量刚被创建时不会自动成为NULL指针,此时它的值是垃圾值。
2 指针被free之后,没有置为NULL,让人误以为它仍然是个合法的指针。
3 使用NULL指针。
4 指针操作超越了变量的作用范围。
(2)如何避免“野指针”的产生:
使用指针前一定要保证它指向了有效的内存空间。(或者申请内存,或者指向一块已申请了的合法空间)
用free释放了内存之后,立即将指针设置为NULL,防止产生“野指针”。
用malloc申请内存之后,应该立即检查指针值是否为NULL。
避免数组或指针的下标越界,特别要当心发生”多1”或者”少1”操作


十三 来自前辈们的经验,常见错误举例
举例1:
int main( ) 

char *p;
  p = (char *)malloc(sizeof(char)*10);
  p[10] = ‘a’;
}

解释:1. 申请了但不见得成功  
    2. 指针超出了申请空间的范围  
    3.没有free 
    4.用完了没将p置成NULL


举例2:
void gimme() 

char *p; 
p = (char *)malloc(10);
  return;
 }
 int main() 
{
gimme(); 
return 0;
}

解释:1、未判断申请是否成功
            2、没有在适当的时机释放p的空间,将p置空,内存泄漏!
举例3
typedef struct ptrblock 
{
char *ptr; 
}PB; 
int main( ) 
{
PB *p;
p = (PB *)malloc( sizeof(PB) ); 
p->ptr =(char *) malloc (10);
... 
free(p);
free(p->ptr ); 
}

举例4:删除链表结点时,先释放,后连接


举例5:
code1
char *GetString1(void)
{
char str[] = "hello world";
return str; 
}
void main( )
{
char *str = NULL;
str = GetString1(); 
printf("%s",str);
}


code2
char *GetString2(void)
{
char *p = "hello world";
return p;
}
void main( )
{
char *str = NULL;
str = GetString2();
printf("%s",str);
}


对于code1编译器给warning  大概是这个样子 warning C4172: returning address of local variable or temporary在上面提到过,指向常量的指针是没什么问题的,可以返回。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值