一、指针是什么?
int *p;
定义一个指针变量p。
因为p是局部变量,所以也遵循C语言局部变量的一般规律(定义局部变量并且未初始化,则值是随机的),所以此时p变量中存储的是一个随机的数字。
在使用指针前,需进行绑定,才能解引用。
二、符号
1、星号 *
(1) C语言中*可以表示乘号,也可以表示指针符号。
(2)星号在用于指针相关功能的时候有2种用法:第一种是指针定义时,*结合前面的类型用于表明要定义的指针的类型;第二种功能是指针解引用,解引用时*p表示p指向的变量本身。
2、取地址符 &
使用时直接加在一个变量的前面,然后取地址符和变量加起来构成一个新的符号,这个符号表示这个变量的地址。
三、指针的定义并初始化
int a = 32; int *p = &a;
//或者
int a = 32; int *p; p=&a;
四、左值和右值
赋值运算符左边的叫左值,右边的叫右值。
当一个变量做左值时,编译器认为是这个变量所对应的那个内存空间;
当一个变量做右值时,编译器认为是这个变量的值。
五、野指针
指针指向的位置是不可知的,很可能触发运行时的段错误。野指针的错误来源是指针定义后没有初始化,也没有赋值,然后去解引用。
如何防止野指针?
第一点:定义指针时,同时初始化为NULL
第二点:在指针解引用之前,先去判断这个指针是不是NULL
第三点:指针使用完之后,将其赋值为NULL
第四点:在指针使用之前,将其赋值绑定给一个可用地址空间
六、NULL
在C++中 NULL是0; 在C中是强制类型转换为void * 的 0。
七、const
const是用来修饰变量,表示这个变量是常量。
第一种:const int *p; //p指向的变量是const的
第二种:int const *p; //p指向的变量是const的
第三种:int * const p; //p本身是const的
第四种:const int * const p; //p本身是const的,p指向的变量是const的
但const也可以通过骗过编译器来修改
int const a = 5;
int * p;
p = (int *) &a;
*p = 6;
printf(“%d\n”, a);
八、数组
从内存角度讲,数组变量就是一次分配多个变量,而且这多个变量在内存中的存储单元是依次相连接的。
数组中几个关键符号(a a[0] &a &a[0])的理解(前提是 int a[10])
(1)这4个符号搞清楚了,数组相关的很多问题都有答案了。理解这些符号的时候要和左值右值结合起来,也就是搞清楚每个符号分别做左值和右值时的不同含义。
(2)a就是数组名。a做左值时表示整个数组的所有空间(10×4=40字节),因为C语言规定数组操作时要独立单个操作,不能整体操作数组,所以a不能做左值;a做右值表示数组首元素(数组的第0个元素,也就是a[0])的首地址(首地址就是起始地址,就是4个字节中最开始第一个字节的地址)。a做右值等同于&a[0];
(3)a[0]表示数组的首元素,也就是数组的第0个元素。做左值时表示数组第0个元素对应的内存空间(连续4字节);做右值时表示数组第0个元素的值(也就是数组第0个元素对应的内存空间中存储的那个数)
(4)&a就是数组名a取地址,字面意思来看就应该是数组的地址。&a不能做左值(&a实质是一个常量,不是变量因此不能赋值,所以自然不能做左值。);&a做右值时表示整个数组的首地址。
(5)&a[0]字面意思就是数组第0个元素的首地址(搞清楚[]和&的优先级,[]的优先级要高于&,所以a先和[]结合再取地址)。做左值时是常量,不能做左值;做右值时表示数组首元素的地址。做右值时&a[0]等同于a。
(1)这4个符号搞清楚了,数组相关的很多问题都有答案了。理解这些符号的时候要和左值右值结合起来,也就是搞清楚每个符号分别做左值和右值时的不同含义。
(2)a就是数组名。a做左值时表示整个数组的所有空间(10×4=40字节),又因为C语言规定数组操作时要独立单个操作,不能整体操作数组,所以a不能做左值;a做右值表示数组首元素(数组的第0个元素,也就是a[0])的首地址(首地址就是起始地址,就是4个字节中最开始第一个字节的地址)。a做右值等同于&a[0];
(2)a[0]表示数组的首元素,也就是数组的第0个元素。做左值时表示数组第0个元素对应的内存空间(连续4字节);做右值时表示数组第0个元素的值(也就是数组第0个元素对应的内存空间中存储的那个数)
(3)&a就是数组名a取地址,字面意思来看就应该是数组的地址。&a不能做左值(&a实质是一个常量,不是变量因此不能赋值,所以自然不能做左值。);&a做右值时表示整个数组的首地址。
(4)&a[0]字面意思就是数组第0个元素的首地址(搞清楚[]和&的优先级,[]的优先级要高于&,所以a先和[]结合再取地址)。做左值时表示数组首元素对应的内存空间,做右值时表示数组首元素的值(也就是数组首元素对应的内存空间中存储的那个数值)。做右值时&a[0]等同于a。
总结:
1:&a和a做右值时的区别:&a是整个数组的首地址,而a是数组首元素的首地址。这两个在数字上是相等的,但是意义不相同。意义不相同会导致他们在参与运算的时候有不同的表现。
2:a和&a[0]做右值时意义和数值完全相同,完全可以互相替代。
3:&a是常量,不能做左值。
4:a做左值代表整个数组所有空间,所以a不能做左值。
九、数组和指针
以指针方式来访问数组元素
(1)数组元素使用时不能整体访问,只能单个访问。访问方式有2种:数组形式和指针形式。
(2)数组格式访问数组元素是:数组名[下标]; (注意下标从0开始)
(3)指针格式访问数组元素是:*(指针+偏移量); 如果指针是数组首元素地址(a或者&a[0]),那么偏移量就是下标;指针也可以不是首元素地址而是其他哪个元素的地址,这时候偏移量就要考虑叠加了。
(4)数组下标方式和指针方式均可以访问数组元素,两者的实质其实是一样的。在编译器内部都是用指针方式来访问数组元素的,数组下标方式只是编译器提供给编程者一种壳(语法糖)而已。所以用指针方式来访问数组才是本质的做法。
十、指针和数组类型的匹配问题
int *p; int a[5]; p = a; // 类型匹配
int *p; int a[5]; p = &a; // 类型不匹配。p是int *,&a是整个数组的指针,也就是一个数组指针类型,不是int指针类型,所以不匹配
&a、a、&a[0]从数值上来看是完全相等的,但是意义来看就不同了。从意义上来看,a和&a[0]是数组首元素首地址,而&a是整个数组的首地址;从类型来看,a和&a[0]是元素的指针,也就是int 类型;而&a是数组指针,是int ()[5];类型。
总结:指针类型决定了指针如何参与运算
(1)指针参与运算时,因为指针变量本身存储的数值是表示地址的,所以运算也是地址的运算。
(2)指针参与运算的特点是,指针变量+1,并不是真的加1,而是加1*sizeof(指针类型);如果是int 指针,则+1就实际表示地址+4,如果是char 指针,则+1就表示地址+1;如果是double *指针,则+1就表示地址+8.
(2)指针变量+1时实际不是加1而是加1×sizeof(指针类型),主要原因是希望指针+1后刚好指向下一个元素(而不希望错位)。
十一、指针与强制类型转换
(1)指针的本质是变量,指针就是指针变量。
(2)一个指针涉及2个变量:指针变量本身和指针变量指向的那个变量。
(3)int p; p(指针变量本身)是int 类型,* p(指针指向的那个变量)是int类型。
(4)指针数据类型转换,小的转换成大的不会出错(如 char * -> int * -> float *),但如果反过来大的转换成小的一定会出错。
十二、sizeof和strlen
sizeof是C语言的一个运算符(主要sizeof不是函数,虽然用法很像函数),sizeof的作用是用来返回()里面的变量或者数据类型占用的内存字节数。
strlen是一个C库函数,用来返回一个字符串的长度(注意,字符串的长度是不计算字符串末尾的’\0’的)。一定要注意strlen接收的参数必须是一个字符串(字符串的特征是以’\0’结尾)。
注意:
(1)32位系统所有指针长度都为4。
(2)sizeof测试一个变量本身,和sizeof测试这个变量的类型,结果是一样的。
(3)sizeof(数组名)的时候,数组名不做左值也不做右值,纯粹就是数组名的含义。那么sizeof(数组名)实际返回的是整个数组所占用内存空间(以字节为单位的)。
(4)函数传参,形参是可以用数组的
(5)函数形参是数组时,实际传递是不是整个数组,而是数组的首元素首地址。也就是说函数传参用数组来传,实际相当于传递的是指针(指针指向数组的首元素首地址)。
void fnc1(int a[]) //[]内数字可有可无,不影响
{ … }
void fnc2(int *a)
{ … }
fnc1和fnc2等价
十三、宏定义和typedef
#define dpChar char *
dpChar p1, p2; //展开 char *p1, p2;
typedef char *tpChar; //typedef用于用户自定义类型
tpChar p3, p4; //等价于 char *p3; char *p4;
十四、关于传参
char *str = “abcd”; //指针本身就是不可变的
传这种指针的时候我们需要在形参声明const。