指针与数组的实质:
对编译器而言,一个数组就是一个地址,一个指针就是一个地址的地址。
数组:
c语言中只有一维数组,而且数组的大小必须在编译期就作为一个常数确定下来
对于数组,我们只能够做两件事:确定数组的大小,获得指向该数组下标为0的元素的指针,其余实际上都是通过指针进行的。任何一个数组下标运算都等同于一个对应指针的运算。
对于数组结尾之后的下一个元素,取它的地址是合法的。数组中实际不存在的“溢界”元素的地址位于数组所在内存之后,这个地址可以用于进行赋值和比较。如果引用该元素,就是非法的
数组的‘&’符号相关问题
- a[0]是一个元素,a是整个数组,虽然&a[0]和&a的值一样,但意义不一样,&a[0]是数组首元素的首地址,&a是数组的首地址。
- &a+1:取数组a的首地址,该地址的值加上sizeof(a)的值,即下一个数组的首地址。
- &a[0]+1:数组下一个元素的首地址,即a[1]
- a+1:数组下一个元素的首地址,即&a[1]
指针:
char *p,*q;
p="xyz";
//p实际是一个指向 'x' 'y' 'z' '\0' 4个字符组成的数组的起始元素的指针
q=p;
q和p是两个指向内存中同一地址的指针。 复制指针并不同时指向复制指针所指向的数据。
指针的赋值:
任何指针 都是 指向某种类型的变量。
每个实参都应该具有自己的类型,这样它的值就可以赋给与它所对应的的形参类型的对象(该对象的类型不能含有限定符)。
两个操作数都是指向有限定符或无限定符的相容类型的指针,左边指针所指向的类型必须具有右边指针所指向类型的全部限定符。
char *cp;
const char *cpp;
cpp = cp;
cp = cpp;//不能进行赋值,会产生编译警告
类型为char**的实参与类型为const char**的形参时不相容的。(前者指向char*,后者指向const char *)
指针的运算:
对指针加1操作,得到的是下一个元素的地址,而不是原有地址值直接加1.
在c语言里,赋值符号=两边的数据必须是相同的,如果不同则需要显示或隐式的类型转换。
指针变量与一个整数相加减并不是用指针变量里的地址直接加减这个整数,这个整数的单位不是字节而是元素的个数。
struct Test
{
int Num;
char *pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
//假设p的值为0x100000
p+0x1//0x100014 结构体字节数为20 0x100000+sizeof(Test)*0x1
(unsigned long)p+0x1//任何数值一旦被强制转换,其类型就改变了
//这个表达式就是一个无符号的长整形数加上另一个整数,其值为0x100001
(unsigned int*)p+0x1//0x100000+sizeof(unsigned int)*0x1
//0x100004
空指针:
#define NULL 0 (NULL是一个宏,值为0)
空指针并非空字符串 编译器保证由常数0转换而来的指针不等于任何有效的指针。常用NULL代替。除此外,在c语言中将一个整数转换为一个指针,最后得到的结果都取决与具体的c编译器实现。
当我们将0赋值给一个指针变量时,绝对不能企图使用该指针所指向的内存中存储的内容。
NULL指针并不指向任何对象。除了用于赋值或比较运算,出于其他任何目的使用NULL指针都是非法的。
如何将数值存储到指定的内存地址
现在需要将往内存地址0x12ff7c上存入一个整形数0x100
int *p=(int *)0x12ff7c;
*p=0x100;
const修饰的指针
const修饰指针
const int *p; //p可变,p指向的对象不可变
int const *p; //p可变,p指向的对象不可变
int * const p; //p不可变,p指向的对象可变
const int * const p; //指针p和p指向的对象都不可变
规则:先忽略类型名,const离谁近就修饰谁
指针和数组的转换
在c语言中,我们没有办法可以将一个数组作为函数参数直接传递。如果我们使用数组名作为参数,那么数组名会被立刻转换为指向该数组第一个元素的指针。(将数组作为函数参数毫无意义。c语言中会自动地将参数的数组声明转换为相应的指针声明。)
数组的声明就是数组,指针的声明就是指针,两者不可混淆。但数组在语句或表达式中引用时,数组总是可以写成指针的形式,两者可以互换。
在一个地方定义为指针,在别的地方也只能声明为指针;
在一个地方定义为数组,在别的地方只能声明为数组。
当书写extern char*p,然后用p[3]来引用其中的元素时,
编译器将会:
1:取得符号表中p的地址,提取存储于此处的指针。
2:把下标所表示的偏移量与指针的值相加,产生一个地址。
3:访问上面这个地址,取得字符。
把p声明为指针,那么不管p原先是定义为指针还是数组,都会按照上面所示的三个步骤进行操作,但是只有当p原来定义为指针时这个方法才是正确的。
p被声明为extern char *p;而它原来的定义却是char p[10];这种情形,当用p[i]这种形式提取这个声明的内容时,实际上得到的是一个字符。但按照上面的方法,编译器却把它当成一个指针,把ASCII字符解释为地址显然是不对的。
这种错误可能会污染程序地址空间的内容,并在将来出现莫名其妙的错误。
实参 | 所匹配的形式参数 |
数组的数组 char arr[ ][ ] | char ( * )[10] |
指针数组 char *p [ ] | char * *p |
数组指针 char(*p) [ ] | char (*p) [ ] |
指针的指针 char * *p | char * *p |