第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)
这里的函数指针数组指针不就是一个指针嘛。只不过这个指针指向一个数组,这个数组里面存的是指向函数的指针。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:《C语言深度解剖》学习笔记之指针和数组 - Python技术站