第4章 指针和数组

1. int *p=NULL 和 *p=NULL 有什么区别

int *p = NULL;

  第一句代码的意思是:定义一个指针变量p,其指向的内存里面保存的是 int类型的数据;在定义变量的同时把p的值设置为 0x00000000,而不是把 *p的值设置为 0x00000000,这个过程称为初始化,是在编译的时候进行的。

int *p;
*p = NULL;

  第一行代码,定义了一个指针变量p,其指向的内存里面保存的是 int类型的数据,但是这时候p本身的是多少不得而知;第二行代码,给 *p赋值为NULL。

2.如何往内存地址 0x12ff7c 上存入一个整形数 0x100?

  •   第一种方法
  • int *p = (int *)0x12ff7c; //p
    *p = 0x100;

     

  •   第二种方法
  • *(int *)0x12ff7c = 0x100;

3.数组的内存布局

int a[5] ;

  a作为右值时,代表数组首元素的首地址,而非数组的首地址。

  a[0],a[1]等为a的元素,但并非元素的名字,数组的每一个元素都是没有名字的。

  sizeof(a[5])的值在32为系统下为4,为什么没有出错?

其实函数求值是在运行的时候,而sizeof求值是在编译的时候。虽然并不存在a[5]这个元素,但是这里也并没有真正访问a[5],而是仅仅根据数组元素的类型来确定其值。所以没有出错。

4.&a[0]和&a的区别

  前者是数组首元素的首地址,而后者是数组的首地址。

5.数组名a作为左值和右值的区别

x = y;

  左值:在这个上下文环境中,编译器认为x的含义是x所代表的地址。这个地址只有编译器知道,在编译的时候确定

  右值:在这个上下文环境中。编译器认为y的含义是y所代表的地址里面的内容。这个内容运行的时候确定。

  a作为右值时其意义与 &a[0] 是一样的,代表的是数组首元素的首地址,而不是数组的首地址。

  a不能作为左值!编译器认为数组名作为左值代表的意思是a的首元素的首地址,但是这个地址开始的一块内存是一个总体,我们只能访问数组的某个元素,而无法把数组当成一个总体来进行访问。

6.以指针的形式访问指针和以下标的形式访问指针

char *p = "abcdef";
  • 以指针的形式:*(p+4)。先取出p里面的地址值,假设为0x0000ff00,再加上4个字符的偏移量,得到新的地址0x0000ff04;然后再取出地址上的值。
  • 以下标的形式:p[4]。编译器总是把下标的形式的操作解析为以指针的形式的操作。先取出p里存储的地址值;再加上4个元素的偏移量,计算出新的地址;然后再从新的地址中取出值。

7.以指针的形式访问数组和以下标的形式访问数组

  对a的元素的访问必须先根据数组的名字a找到数组首元素的首地址,然后再根据偏移量找到相应的值。这是一种典型的“具名 + 匿名”的访问。

8.指针数组和数组指针

  指针数组:储存指针的数组

  数组指针:指向数组的指针

int *p1[10]; //指针数组

  p1先与“[]”结合,构成一个数组的定义,数组名为p1,int *修饰的是数组里的每个元素。也就是说,这是一个数组,其包含10个指向int类型数据的指针,即指针数组

int (*p2)[10]; //数组指针

  “*”和p2构成一个指针的定义,指针变量名为p2,int修饰的是数组的内容。数组在这里没有名字,是个匿名数组。也就是说,p2是一个指针,它指向一个包含10个int类型数据的数组,即数组指针。

9.再论a和&a之间的区别

1 int main()
2 {
3 char a[5] = {'A','B','C','D'};
4 char (* p3)[5] = &a;
5 char (* p4)[5] = a;
6 return 0;
7 }

  编译器会给出警告。因为p4这个定义“=”两边的的数据类型不一致。左边的数据类型是指向整个数组的指针,右边的数据类型是指向单个字符的指针。

10.地址的强制转换

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

 

  第一个表达式p + 0x1的值为 0x100000+sizeof(Test)*0x1。Test的大小为20字节

  第二个表达式其实就是一个无符号的长整数加上另一个整数

  第三个表达式的p被强制转换为指向无符号整型的指针

11.数组参数和指针参数

  C语言中,当一维数组作为函数参数的时候,编译器总是把它解析成一个指向其首元素首地址的指针。

  函数的返回值也不能是一个数组,而只能是指针。

void fun(char *p)
{
char c = p[3];
}
void fun(char a[])
{
char c = a[3];
}

两组代码等效,但第二个形式较好。

  main函数内的变量不是全局变量,而是局部变量,只不过它的生命周期和全局变量一样长而已。全局变量一定是定义在函数外部的。

11.二维数组参数和二级指针参数

  C语言中,当一维数组作为函数参数的时候,编译器总是把它解析成一个指向其首元素首地址的指针。所以下面两个函数声明是一样的:

  void fun(char a[3][4]),void fun(char a[][4]) 和 void fun(char (*p)[4])

  void fun(char *p[4])和void fun(char **p)  这是因为*p[4]是一个包含4个指针的一位数组,把这个一位数组也写成指针的形式,就得到第二种写法。

12.函数指针

  函数指针就是函数的指针,它是一个指针,指向一个函数。

char * fun1(char * p1,char * p2);

fun1是函数名,p1,p2是参数,其类型为char *类型,函数的返回值是char *

char * * fun2(char * p1,char * p2);

与上例唯一不同的是,函数返回值类型为 char **

char * (* fun3)(char * p1,char * p2);

fun3是一个指针变量,它指向一个函数。这个函数有两个指针类型的参数,函数的返回值也是一个指针。

13.函数指针的例子

 1 #include<stdio.h>
 2 char * fun(char * p1,char * p2)
 3 {
 4 }
 5 int main()
 6 {
 7 char * (* pf)(char * p1,char * p2);
 8 pf = &fun;
 9 (* pf)("aa","bb");
10 return 0;
11 }

  给函数指针赋值时,可以用&fun或字节使用函数名fun。

14. *(int *)&p

 1 void fun()
 2 {
 3 }
 4 int main()
 5 {
 6 void (* p)();
 7 *(int *)&p = (int)fun;
 8 (*p)();
 9 return 0;
10 }

  void (* p)();这行代码定义了一个指针变量p,p指向一个函数,这个函数的参数和返回值都是void。

 &p求指针变量p本身的地址。
 (int)fun表示将函数的入口地址转换成指向int类型的指针
  *(int *)&p = (int)fun;表示将函数的入口地址赋值给指针变量p
 (*p)()就是对函数的调用。
15.(*(void(*)())0)()
void(*)(),这是一个函数指针类型
(void(*)())0,将0强制转换为函数指针类型。
(*(void(*)())0),取0地址开始的一段内存内存里面的内容,其内容就是保存在首地址为0的一段区域内的函数
(*(void(*)())0)(),函数的调用
16.函数指针数组
char * (*pf[3])(char *p);

  这里定义了一个函数指针数组。它是一个数组,数组名为pf,数组内存储了3个指向函数的指针,这些指针指向一些返回值类型为指向字符的指针,参数为一个指向字符的指针的函数。

17.函数指针数组指针

char * (*(*pf)[3])(char * p)

  这里的函数指针数组指针不就是一个指针嘛。只不过这个指针指向一个数组,这个数组里面存的是指向函数的指针。