指针 数组
1.int *p其实应该写成int* p更合适,int*是一个模子,一个整体,一个类型。
2.指针的加减是对单元数量(比如int*,则加1表示增加4个字节)的加减,不是单纯的数学实数加减。
3.强制转换:
struct Test
{
int Num;
char *pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
假设p的值为0x100000。 如下表表达式的值分别为多少?
p + 0x1 = 0x100014 ?
(unsigned long)p + 0x1 = 0x100001?
(unsigned int*)p + 0x1 = 0x100004?
4.凡是在指针定义处赋值的操作,都是针对指针变量本身的,而不是针对指针所指向的地址的内容的,
比如:int *p = NULL 和*p = NULL 有什么区别?
很多初学者都无法分清这两者之间的区别。我们先看下面的代码:int *p = NULL;
这时候我们可以通过编译器查看p的值为0x00000000。这句代码的意思是:定义一个指针变量p,其指向的内存里面保存的是int类型的数据;在定义变量p的同时把p的值设置为0x00000000,而不是把*p的值设置为0x00000000。这个过程叫做初始化,是在编译的时候进行的。
明白了什么是初始化之后,再看下面的代码:
int *p;
*p = NULL;
同样,我们可以在编译器上调试这两行代码。第一行代码,定义了一个指针变量p,其指向的内存里面保存的是int类型的数据;但是这时候变量p本身的值是多少不得而知,也就是说现在变量p保存的有可能是一个非法的地址。第二行代码,给*p赋值为NULL,即给p指向的内存赋值为NULL;但是由于p指向的内存可能是非法的,所以调试的时候编译器可
能会报告一个内存访问错误。这样的话,我们可以把上面的代码改写改写,使p指向一块合法的内存:
int i = 10;
int *p = &i;
*p = NULL;
在编译器上调试一下,我们发现p指向的内存由原来的10变为0了;而p本身的值, 即内存地址并没有改变。
再比如:需要往内存0x12ff7c地址上存入一个整型数0x100。
int *p = (int *)0x12ff7c; // P是指针变量,存储的内容为内存地址0x12ff7c
*p = 0x100; // 向内存地址0x12ff7c处写入0x100
上述代码等同如下:*(int *)0x12ff7c = 0x100;
a[0],a[1]等为a的元素,但并非元素的名字。数组的每一个元素都是没有名字的。
sizeof(a)的值为sizeof(int)*5,32位系统下为20。
sizeof(a[0])的值为sizeof(int),32位系统下为4。
sizeof(a[5])的值在32位系统下为4。并没有出错,为什么呢?我们讲过sizeof是关键字不是函数。函数求值是在运行的时候,而关键字sizeof求值是在编译的时候。虽然并不存在a[5]这个元素,但是这里也并没有去真正访问a[5],而是仅仅根据数组元素的类型来确定其值。所以这里使用a[5]并不会出错。
sizeof(&a[0])的值在32位系下为4,这很好理解。取元素a[0]的首地址。
sizeof(&a)的值在32位系统下也为4,这也很好理解。取数组a的首地址。
a有两个含义:数组名、数组a[0]的地址(等于&a[0])。
&a则表示数组的地址,值与a相等。
这里&a[0]和&a到底有什么区别呢?a[0]是一个元素,a是整个数组,虽然&a[0]和&a的值一样,但其意义不一样。前者是数组首元素的首地址,而后者是数组的地址。举个
例子:湖南的省政府在长沙,而长沙的市政府也在长沙。两个政府都在长沙,但其代表的意义完全不同。这里也是同一个意思。
a作为左值与作为右值
x=y。
左值:在这个上下文环境中,编译器认为x的含义是x所代表的地址。这个地址只有编译器知道,在编译的时候确定,编译器在一个特定的区域保存这个地址,我们完全不必考虑这个地址保存在哪里。
右值:在这个上下文环境中,编译器认为y的含义是y所代表的地址里面的内容。这个内容是什么,只有到运行时才知道。
a作为右值时其意义与&a[0]是一样,代表的是数组首元素的首地址,而不是数组的地址。
a不能作为左值。
编译器会认为数组名作为左值代表的意思是数组a的地址,但是这个地址开始的一块内存是一个总体,我们只能访问数组的某个元素而无法把数组当一个总体进行访问。所以我们可以把a[i]当左值,而无法把a当左值。其实我们完全可以把a当一个普通的变量来看,只不过这个变量内部分为很多小块,我们只能通过分别访问这些小块来达到访问整个变量a的目的。
指针和数组之间没有任何关系!指针就是指针,指针变量在32位系统下,永远占4个byte,其值为某一个内存的地址。指针可以指向任何地方,但是不是任何地方你都能通过这个指针变量访问到。
数组就是数组,其大小与元素的类型和个数有关。定义数组时必须指定其元素的类型和个数。数组可以存任何类型的数据,但不能存函数。
相似点
例子A) 定义了一个指针变量p,p本身在栈上占4个byte,p里存储的是一块内存的首地址。这块内存在静态区,其空间大小为7个byte,这块内存也没有名字。对这块内存的访问完全是匿名的访问。 比如现在需要读取字符‘e’,我们有两种方式:1),以指针的形式:*(p+4)。先取出p里存储的地址值,假设为0x0000FF00,然后加上4个字符的偏移量,得到新的地址0x0000FF04。然后取出0x0000FF04地址上的值。2),以下标的形式:p[4]。编译器总是把以下标的形式的操作解析为以指针的形式的操作。p[4]这个操作会被解析成:先取出p里存储的地址值,然后加上中括号中4个元素的偏移量,计算出新的地址,然后从新的地址中取出值。也就是说以下标的形式访问在本质上与以指针的形式访问没有区别,只是写法上不同罢了。例子B)
定义了一个数组a,a拥有7个char类型的元素,其空间大小为7。数组a本身在栈上面。对a的元素的访问必须先根据数组的名字a找到数组首元素的首地址,然后根据偏移量找到相应的值。这是一种典型的“具名+匿名”访问。比如现在需要读取字符‘5’,
我们有两种方式:1),以指针的形式:*(a+4)。a这时候代表的是数组首元素的首地址,假设为0x0000FF00,然后加上4个字符的偏移量,得到新的地址0x0000FF04。然后取出0x0000FF04地址上的值。2),以下标的形式:a[4]。编译器总是把以下标的形式的操作解析为以指针的形式的操作。a[4]这个操作会被解析成:a作为数组首元素的首地址,然后加上中括号中4个元素的偏移量,计算出新的地址,然后从新的地址中取出值。
由上面的分析,我们可以看到,指针和数组根本就是两个完全不一样的东西。只是它们都可以“以指针形式”或“以下标形式”进行访问。一个是完全的匿名访问,一个是典型的具名+匿名访问。一定要注意的是这个“以XXX的形式的访问”这种表达方式。
注:偏移量的单位是元素的个数而不是byte数。
&a认为数组a是一个整体,不论对其进行+/-,都是作为一个整体进行的,比如定义为int a[5]的数组,&a+1就表示下一个a[5]数组的地址,即&a是int[5]型的变量地址。 a有两种含义(见上),对其进行+/-,含义表示数组元素a[0]的地址,a+1就表示a[1]的地址。 int *p; *p=0x10; 定义了一个变量p(变量p本身也会有一个存储他的地址),变量p存储了一个地址0x12345678,地址为0x12345678的内存存储着0x10这个数。 int a[1]; a[0]=0x11; 定义了一个名字为a的数组(不存在变量),保存数组数据的起始地址为0x87654321,地址为0x87654321的内存存储着0x10这个数。 访问0x10这个数的流程是,首先找到变量p,取出p的内容0x12345678,通过0x12345678找到0x10; 访问0x11这个数的流程是,直接找到0x87654321,然后通过0x87654321找到0x11; 所以能很好的解释如下的问题: 问题1: 文件1中定义如下:char a[100]; 文件2中声明如下: extern char *a; 这是错误的。 问题2: 文件1
char *p = “abcdefg”;
文件2
extern char p[]; 这是错误的。 总结:
《C语言深度剖析》
- 上一篇:没有了
- 下一篇:没有了