指针式真的可以说的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);这个技巧记住呀。
#define NULL (0)
#else
#define NULL ((void *)0) //所以在C语言中,int *p; p=(int *)0可行;但p=0不可以
#endif
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只是用来告诉编译器和读者这个变量不能也不必修改,但是事实上出现错误也有可能是该变量被改变了。
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]
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。
(1).参数为一般变量或较小的结构体时,直接用传值调用;为较大结构体或数组时,用传址调用,这样可以避免拷贝加快速率。
struct A a=
{
.c=1,
.b=2,
};
(2).参数为数组时,默认用传址调用,参数可以写成int *p,int p[随便的正整数],因为只传首地址,所以没关系呀。
(3).传参:
传值调用:直接将变量的值作为参数,形参和实参是不同变量,然后形参=实参(拷贝一波);
传址调用:将变量的地址作为参数,传递的是指针(一个地址),这样在内部函数可以改变传进来的外部函数的变量。
自己认为:这两个不是一样的么,对于传址调用,不过是同样有两个不同的形参实参(指针),把实参值传给形参值,让他们指向相同变量而已。
补充:因为可以传变量的地址来改变变量的值,所以参数可以用来做函数输出,需要多个返回值时用用这样方法(一般函数返回值用来判断函数是否正常执行)。
对于那些是指针的参数,一般用作输入的,我们在程序中一般不需要去改而直接用来操作,那么就加上const修饰指针指的;一般用作输出的参数,我们是要改的,就不加const。
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.
#define NULL (0)
#else
#define NULL ((void *)0) //所以在C语言中,int *p; p=(int *)0可行;但p=0不可以
#endif
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。
(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访问才不会出错