重学C语言之指针

重学C语言之指针:

指针是一直是C的重点,重学阶段选择指针重点回顾

在C语言中,我们都知道函数名代表函数的入口地址,而区分指向函数的指针和返回值是指针很多人会在这里迷糊,我们先以两个例子看,

double (*fp)(int ,int *)

这里fp是一个指向函数的指针变量,这个函数必须是double类型,只有同类型的指针才能指向这个函数

double *fp(int ,int *)
这里 fp 就不是指针变量,这是一个函数,该函数的返回值类型是基类型为double的指针类型

#include "stdio.h"
#include "math.h"
double tran(double(* f1)(double), double(* f2)(double), double x)
{
	return (* f1)(x)/ (*f2)(x);
}
main()
{
	double y, v;
	v = 60 * 3.1416 / 180.0;
	y = tran(sin, cos, v);
	printf("tan(60)=%10.6\n", y);
	y = tran(cos, sin , y);
	printf("cot(60)=%10.6f\n", y);
	system("pause");
}

可以看一下这段代码,tran函数就使用了指针变量作为形参,(看一下初始化的方式还有没有忘)

什么是指针?

指针是包含另一变量的地址变量。

指针类型

int  *p         //p是一个指针,指向一个整形数。
int  *p()       //p是一个函数,该函数返回一个指向整数的指针。
int  (*p)()    	//p是一个指针,该指针指向一个函数,这个函数返回一个整数。
int  *(*p)()    //p是一个指针,该指针指向一个函数,这个函数返回一个指向整数的指针。

int  *p[]       //p是一个数组,该数组的每一个元素是指向整数的指针。
int  (*p)[]     //p是一个指针,该指针指向一个数组,这个数组的每一个元素是一个整数。


指针初始化实际上就是给指针赋一个地址,有这几种形式:通过符号&取变量(包括结构变量、数组第一个元素)的地址赋给指针;把数组名赋给指针;把函数名赋给指向函数的指针;动态分配内存

其中动态内存分配为

struct c{double r,i;};
struct c *p;
p=(struct c *)malloc(sizeof(struct c));

指针与数组、函数的关系
(1)对于一维数组 int a[i] 或指针 int *a
a+i 指向 a[i]
(2)对于字符串 char s[i] 或指针 char *s
s+i 指向第 i个字符 s[i]
(3)对于二维数组int a[i][j]
*a+j 指向 a[0][j]
*(a+i) 指向 a[i][0]
*(a+i)+j 指向 a[i][j]
例:对于 a[2][3]={1,2,3,4,5,6,}; 有 *(*(a+1)+1)=5;
(4)对于字符串数组char p[i][j] 或字符型指针数组char *p[i]
*p+j 指向第 0个字符串的第 j个字符
*(p+i) 指向第 i个字符串的第 0个字符
*(p+i)+j 指向第 i个字符串的第 j个字符
例:对于 *p[]={"ABC","DEF"}; 有 *(*(p+1)+1)='E';
例:对于 char p[][3]={"ABC","DEF"}; 有 *(*(p+1)+1)='E';
(5)对于指针数组int *a[i]
a[i] 指向 变量i
即 *a[i]=变量i 或 a[i]=&变量i
(6)对于结构struct XY

	{int x;int *y}*p;
p是指向结构XY的指针
(*p).x 或 p->x 是表示 x 的内容
(*p).y 或 p->y 是表示指针 y 的值(地址)
*(*p).y 或 *p->y 是表示 y 所指的内容
&(*p).x 或 &p->x 是表示 x 的地址
(7)指向函数的指针
对于 

void func(char *str)
	{…}; //定义了一个函数
	void (*p)(char*);//定义了一个函数指针
p=func; //让指针指向函数
则(*p)("…"); //用指针p可以调用函数func
(8)指向多个不同函数的指针数组
对于void function_1() {…};
	void function_4() {…}; //定义了四个函数
	typedef void(*menu_fcn)();//定义了指向函数的指针
	menu_fcn command[4]; //定义了指针数组
	command[0]=function_1;
	command[3]=function_4; //让指针数组指向四个函数
	则command[0](); //用指针数组中的一个元素调用一个函数
指针的分类
(1)近指针(near):
近指针为16位指针,它只含有地址的偏移量部分。近指针用于不超过64K 字节的单个数据段或代码段。在微、小和中编译模式下产生的数据指针是近指针(缺省状态);在微、小和中编译模式下产生的码指针(指向函数的指针)是近指针(缺省状态)。
(2)远指针(far)
远指针为32位指针,指针的段地址和偏移量都在指针内。可用于任意编译模式。每次使用远指针时都要重装段寄存器。远指针可寻址的目标不能超过64K ,因为远指针增减运算时,段地址不参与运算。在紧凑、大和巨模式下编译产生的数据指针是远指针(缺省状态)。
(3)巨指针(huge)
巨指针为32位指针,指针的段地址和偏移量都在指针内。可用于任意编译模式。远指针寻址的目标可以超过64K 。巨指针是规则化的指针。

指针的转换
远指针转换成巨指针,使用以下函数

void normalize(void far * * p)
{
*p=(void far *)(((long)*p&0xffff000f)+(((long)*p&0x0000fff00<<12));
}

指针的使用
(1)将浮点数转换成二进制数
float ff=16.5;
unsigned char *cc;
(float*)cc=&ff;
//此时cc的内容为"00008441"
//即cc第一个字节=0;第二个字节=0;第三个字节=0x84;第四个字节=0x41;
(2)将二进制数转换成浮点数
float ff;
unsigned char *cc;
cc=(unsigned char*)malloc(4);
cc=(unsigned char*)&ff;
*(cc+0)=0;
*(cc+1)=0;
*(cc+2)=0x84;
*(cc+3)=0x41;
//此时ff=16.5
free(cc);
什么是二级指针?

可以参照这几篇博客<http://blog.csdn.net/liaoxinmeng/article/details/5811097>

<http://7071976.blog.51cto.com/7061976/1362924>

<http://www.cnblogs.com/AndyJee/p/4630153.html>

<https://baike.baidu.com/item/%E5%A4%9A%E7%BA%A7%E6%8C%87%E9%92%88>

<http://www.cnblogs.com/chenyangyao/p/5222696.html>

<http://c.biancheng.net/cpp/html/85.html>

简单说,就是指向指针的指针,这里不细说

---------------------------------------捂脸 的 分 割 线----------------------------------------------------------

还有一部分在论坛看到的双重指针的讨论,具体出处忘了,只记下了笔记,便于阅读我改变了排版(感谢前辈的交流讨论留下的经验)

先看一段代码:

#include <stdio.h>
#include <stdlib.h>
typedef struct _tree
{
        int data;
        struct _tree * next;
}tree;

void InitTree1(tree *p)
{
        p = (tree*) malloc(sizeof(tree));
        p->data = 0;
        p->next = NULL;
        return ;
}
void InitTree2(tree **p)
{
    (*p) = (tree*)malloc(sizeof(tree));
    (*p)->data = 0;
    (*p)->next = NULL;
    return ;
}

int main()
{
        tree p1, *p2;
        InitTree1(&p1);
        InitTree2(&p2);
        return 0;
}

问:

1、这个代码中的p1和p2有什么区别?
2、这两种初始化p1和p2的方法有什么区别?
3、哪种方法更好一点?
4、为什么很多时候都是用的第二种方法而少用第一种方法?

1、InitTree1()  使用方式是错误的,在 main 主函数调用完毕 InitTree1 后,可以看到 p1 根本没有分配地址,因为在 InitTree1 函数中,是给实参的拷贝值分配了内存地址,调用完 InitTree1 后, p1 根本没有分配成功地址。
 InitTree2() 分配是正确的.
因为 InitTree2 是给实参 p2 的地址的内容(二级指针中存储的内容是一级指针的地址),给一级指针分配空间后,虽说这里传递过去的也是 p2 的值拷贝,但是已经改变了指向的内容的值,就是说 p2 和传递过去的 p2 的拷贝,指向的内容都被分配了空间,故 InitTree2 是正确的。


之所以看到都是使用 InitTree2,那是因为 InitTree2 是正确的用法。
记住C语言中,传递参数都是值拷贝,如果希望在函数中给指针分配地址空间,要么通过函数返回值,要么通过二级指针。
2、首先这两种形式都是对的,先说第一种:
      第一种是传入一个结构体实例的地址,所以这时需要在 main函数中实例化一个 tree t1,那么 InitTree1(&t1),传入的就是对象t1的地址,InitTree1 函数中就是对main中的 t1进行初始化,这种方式需要传入一个已存在的对象实例,如果只是定义一个 ree* p2,然后 InitTree1(p2),显然是有问题的,因为p2是一个野指针或者你定义成一个空指针,那么传进去的p2是一个地址,比如 0x0012ffc4 或者干脆是 0x00000000(NULL),形参p则是仅仅拷贝这个值,而后 p 又malloc,指向了另一个在 InitTree1 中实例化的tree,返回之后,p1 还是 0x0012ffc4 或者是 NULL,当然有一种方式可以这样写:

tree* InitTree1(tree *p)
{
        p = (tree*) malloc(sizeof(tree));
        p->data = 0;
        p->next = NULL;
        return p;
void InitTree1(tree *p)
{
        p->data = 0;
        p->next = NULL;
        return ;
}//tree t1; InitTree(&t1);

tree* InitTree1(tree *p)
{
        p = (tree*)malloc(sizeof(tree));
        p->data = 2;
        p->next = NULL;
        return p;
}//tree *p1; p1=InitTree1(p1);


}int main(){ tree* p2; p2 = InitTree1(p2); return 0;}


再说第二种,第二种是二重指针,其实也就是指向p2的指针,InitTree2(&p2)(指针变量p2本体的地址),形参复制的是指向p2地址的指针p,这个二级指针p经过解引用*p ,对指向的地址重新赋值为一个malloc的tree,那么main中实参p2所指的tree结构也就是刚刚malloc出来的tree。显然,这种无需返回。

补充说一点,如果你想在把一个实例化的对象t1传进InitTree1中,那么InitTree1中的malloc就需要注掉,因为一旦p指向了另一个在InitTree1中实例化的对象后,进一步初始化的就是这个子函数对象成员,而不是main中的那个对象成员。所以对于InitTree1写法,这里给出两种形式:

void InitTree1(tree *p)
{
        p->data = 0;
        p->next = NULL;
        return ;
}//tree t1; InitTree(&t1);

tree* InitTree1(tree *p)
{
        p = (tree*)malloc(sizeof(tree));
        p->data = 2;
        p->next = NULL;
        return p;
}//tree *p1; p1=InitTree1(p1);


3、

#include <stdio.h>
#include <stdlib.h>
typedef struct _tree
{
        int data;
        struct _tree * next;
}tree;

void InitTree1(tree *p)   //这个应该没有疑问
{
        p =  (tree* )malloc(sizeof(tree)); //这里不需要强制类型转换。
                //malloc(sizeof(T))返回T*型值,“=”左右两边类型一样,符合语法。
        p->data = 0;
        p->next = NULL;
        return ;
}
void InitTree2(tree **p)    //这说过了
{
    (*p) = (tree*)malloc(sizeof(tree));   //p内记录的是tree*型变量的地址,对其取*,即*p指的
        //就是tree* 变量,即指向tree型变量的指针。一句话(*p)相当于上边的InitTree1中的p。当然
        //这里也不需要强制类型转换。
    (*p)->data = 0;
    (*p)->next = NULL;
    return ;
}

int main()
{
        tree p1, *p2;    //p1为tree型变量,p2为指向tree型变量的指针。
        InitTree1(&p1);  //&p1  是取tree型变量的地址
        InitTree2(&p2);  //&p2  为取指向tree型变量的指针变量的地址,它的
                //类型为tree *型 根据 T* = &T语法规定,这里T为tree*型。所以形参我要
                //用一个tree** 型的指针来存这个值。
        return 0;
}

//综上,两个初始化是等价的。
//但在这个程序我建议用第一种,如果程序有添加其他函数,需具体问题具体对待。





//本人文章多转,摘,以及加上自己的见解与感悟供学习使用,若有错误欢迎互相讨论


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值