C指针的若干复杂问题

指针式真的可以说的C的核心了,很多人诟病指针,也有很多人觉得指针非常厉害和方便。不管怎么样,这玩意是真的难,感觉我宁愿去和女生聊天也不想碰指针。但是用指针操作数组,函数是真的方便。学习了两天才归纳了这些,算是勉励自己。


1.指针全称指针变量,和普通变量没啥本质区别;你需要明确内存的地址和空间这两个属性,只不过是指针存储的是其他变量地址,而普通变量存储的是数据;指针的类型是是其指向变量的类型。


2.指针的出现是为了实现变量的间接寻址,实质上是CPU的间接寻址,这是硬件决定的。像Java ,c#没有指针,是其封装来实现间接访问。


3.注意一个指针要是没有绑定,就去解引用,这非常危险。用指针三步:定义,绑定,解引用;int a;int *p=&a;


4.int   *p1,p2         //p1是整形指针,p2是普通变量。
  int*  p1,p2          //这和上面是一样的,显然上面的更好看,所以一般*p写在一起。
  
  这段代码:#define  dfpointer  (int *)                 //宏定义只是简单的变了张脸
           typedef  int*       tdpointer               //定义了一种新类型,是int *
            dfpointer a1,a2;                           //int *a1;int a2;
            tdpointer a3,a4;               //int  *a3;int *a4;
  

5.(1)左值与右值:放在赋值运算符左边的是左值,右边的是右值;
  (2) 当一个变量做左值时,编译器认为该变量符号为其代表的内存空间;当其做右值时,认为该变量符号为该变量的值。例如int a= 5,b=3;a=b;
  
6.野指针;指向位置不可知的指针;
          避免野指针:a.在定义指针时,初始化为NULL;
             b.在解引用指针前,将其绑定变量;
 c.在解引用指针时,判断其是否为NULL;                //一般写为if(NULL !=p)或者if(NULL == p);这个技巧记住呀。

 d.在指针使用完后,将其赋值为NULL. 


补充: #ifdef _cplusplus
      #define NULL (0)
 #else 
 #define NULL ((void *)0)                        //所以在C语言中,int *p; p=(int *)0可行;但p=0不可以
 #endif

 


7.指针与const,这一个是西门庆,一个潘金莲,遇到一起就没好事,宛如智障,当年学习C++的噩梦;顶层const与底层const,分不清楚哪个能不能拷贝:
const int *p;       //p不是const,但p指向的变量为const
int const *p;       //同上
int *const p;       //p是const,但p指向的变量为不是const
const int *const p; //都是const的。

在gcc环境下,该程序:
int main(void)
{
int const a= 5;
// a=6;                  //报错,常量不可修改
int *p=&a;           //若警告,在&a前加上(int *)   毕竟这里不是const int *p;
*p=6;
printf("a=%d.\n",a);
return 0;
}
得到结果:a=6;

那么const值就被改变了;事实上gcc把const类型的常量也放在了data段,其实和普通的全局变量放在data段是一样实现的,只是通过编译器认定这个变量是const的,运行时并没有标记const标志,所以只要骗过编译器就可以修改了。



补充:const只是用来告诉编译器和读者这个变量不能也不必修改,但是事实上出现错误也有可能是该变量被改变了。


8.指针与数组:
(1)数组内的各元素需要单独访问,相邻元素的地址是相邻的。
(2)int a[10],我们来区分a , &a , a[0] ,&a[0]:
1).a为数组名,做左值是表示10*4字节空间,但因为(1),所以不能做左值;做右值时表示首元素的首地址可与&a[0]替换;
2).&a为数组地址,为常量所以不可做左值,做右值时其值与a,&a[0]相同,但意义不同,参与运算时结构也不同。
3).a[0]首元素。
4).&a[0]为首元素首地址,做右值时与a相同。          //疑问,这不也是个常量吗?可以做左值?
(3).访问数组的两种方法:数组下标,指针;实际上数组只是语法糖,真正的都是用指针访问。
int a[10];
int *p=a(int *p;p=&a[0];)       //可以;a和&a[0]为元素指针
int *ptr=&a;                      //类型不匹配;ptr为int类型指针;但&a为数组指针,其类型为 int (*) [5]

在访问数组时p+1,p存的是地址,实际上操作的是地址,真正含义是  :p存的地址+sizeof(指针类型)。

补充:数组的两大缺陷:定义时需要指定数组大小;数组内元素类型要相同。


9.指针与强制类型转换:
(1).char ,short ,int 三种类型属于整形,在内存中存储方式相同,只是长短不同;%c,%d
 float 属于浮点型,存储方式与其他的不同     %f ; double 属于双精度浮点型,存储方式与其他的不同             实际上函数传参时会把float转换为double即lf。  
若存取类型不同,那么一定会出错;类型相同,小转大不会错,大转小会截取小范围。
(2).有一点需要分清楚: int *p;float *q;对p和q这两个指针他们的解析方法是一样的,都是以32位地址存储的(32位CPU);不同的是指向的变量的解析方式。



10.指针与sizeof
(1).sizeof(a)是一个运算符,不是函数;a可以是变量,也可以是类型;当a是函数名时,其既不做左值又不做右值,只表示数组大小;strlen()接受一个指针,返回有效长度。
(2).解析:char str[]="badmer"; char *p=str;
sizeof(str)          //7
sizeof(str[0])       //1
strlen(str)          //6
 
sizeof(p)            //4
sizeof(*p)           //1
strlen(p)            //6  

(3).
    1)函数参数为数组时,只能传递其首地址,无法传递大小,所以传任意长数组均可。所以一般:定义trans_array(char *a,int a_size),用时trans_array(badmer,sizeof(badmer)-1)
2)获得数组个数小技巧:int size=sizeof(badmer)/sizeof(badmer[0])  -1。

 


11.指针与函数参数
(1).参数为一般变量或较小的结构体时,直接用传值调用;为较大结构体或数组时,用传址调用,这样可以避免拷贝加快速率。
struct A a=
{
 .c=1,
 .b=2,
};
(2).参数为数组时,默认用传址调用,参数可以写成int *p,int p[随便的正整数],因为只传首地址,所以没关系呀。
(3).传参:
    传值调用:直接将变量的值作为参数,形参和实参是不同变量,然后形参=实参(拷贝一波);
    传址调用:将变量的地址作为参数,传递的是指针(一个地址),这样在内部函数可以改变传进来的外部函数的变量。

    自己认为:这两个不是一样的么,对于传址调用,不过是同样有两个不同的形参实参(指针),把实参值传给形参值,让他们指向相同变量而已。


补充:因为可以传变量的地址来改变变量的值,所以参数可以用来做函数输出,需要多个返回值时用用这样方法(一般函数返回值用来判断函数是否正常执行)。


对于那些是指针的参数,一般用作输入的,我们在程序中一般不需要去改而直接用来操作,那么就加上const修饰指针指的;一般用作输出的参数,我们是要改的,就不加const。


补充:我们定义数组时有两种方法撒:

char  str[ ]="badmer";

char  *str="badmer";            //这种方法定义的数组放在代码段中,是不能改变的。用const char *p访问才不会出错




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值