第四章 指针和数组
4.1 指针
>在32位系统下,不论什么样的指针类型,其大小都为4byte.
>NULL 在系统中被宏定义为 0: #define NULL 0
>将一个数值存储到指定的内存地址: int *p = (int *) 0x12ff7c; *p = 0x01;
4.2数组
> 当我们定义一个数组a的时候,编译器根据指定的元素个数和元素的类型分配确定的大小的一块内存,并把这块内存的名字命名为a。
>a[0]、a[1]...为a的元素,但并非元素的名字。数组中的每个元素都是没有名字的。
>左值和右值
左值:在上下文环境中,编译器认为x的含义是x所代表的地址。这个地址只有编译器知道,在编译的时候确定,编译器在一个特定的区域保存这个地址。
右值:在上下文环境中,编译器认为y的含义是y所带边的地址里面的内容,这个内容是什么,只有在运行时才知道。
当 数组名作为右值的时候,代表的是数组首元素的首地址,而不是数组的首地址。
数组名不能作为在左值。
4.3 指针与数组
>char *p = "abcdef";
定义了一个指针变量p,p里面存储的是一块内存地址,这块内存在静态去,其空间大小是7byte,这块内存没有名字,对这块内存的访问完全是匿名的访问。
(1) *( p+ 4) :先取出p里存储的地址值,然后加上4个字符的偏移量,得到新的地址,再取出新的地址上的值。
(2) p[4] : 编译器总是把以下标形式的操作解析为以指针的形式 的操作。p[4]被解析成先取出p里存储的地址值,在加上方括号中4个元素的偏移量,计算出新的地址,再从这个新的地址中取出值。
上述两种访问方式只是写法不同,本质上没有区别。
>char a[] = "abcdef";
定义了一个数组a,a拥有7个char类型的元素。 数组a本身保存在栈上面。对a的元素的访问必须先根据数组的名字a找到数组首元素的首地址,然后根据偏移量找到相应的值。
>*(a + 4):a代表的是数组首元素的首地址,加上4个字符的偏移量,得到新的地址,再从新的地址中取出值。
>a[4]:编译器把这种形式的操作解析为以指针的形式的操作。
访问指针指向的内容属于”完全匿名访问“, 而访问数组中的内容属于“具名+匿名的访问“。
>a 与 &a 的区别
int a[5] = {1, 2, 3, 4, 5};//sizeof(a) = 20; sizeof(&a) = 4;
int *ptr = (int *)(&a + 1);
int *ptr2 = (int *)(a + 1);
printf("%d, %d\n", *(ptr2 - 1), *(ptr - 1)); //output:1, 5
对指针进行加1操作,得到的是下一个元素的地址,而不是原有的地址值直接加1.所以,一个类型为T的指针的移动,以sizeof(T)为移动单位。
&a + 1:取数组a的首地址,该地址的值加上sizeof(a)的值,得到“&a”这个指针的下一个元素的地址。(因为a的类型是"int [5]",所以它的下一个元素的类型也是int [5])。
(int *)(&a + 1):将上一步计算出来的地址强制转换成int *类型,赋值给ptr。
a + 1 :取数组a的首元素的首地址,该地址的值加上sizeof(a[0])。(因为a地 首元素的类型是int,所以它的下一个元素的类型也是int)。
ptr - 1:ptr是int * 类型的,取ptr中保存的地址,将这个地址减去sizeof(int)。
>如果定义时,用的是char a[] = "abcdef"; 而声明时,用的是extern char *a; 这样的声明,编译器会认为a是一个指针,它里面保存着一个指向char的地址,于是,编译器从&a这个地址开始,取出4个字节的内容,这4个字节的内容作为变量a中保存的那个指向char 的地址(编译器会把指针变量中的任何数据当作地址来处理)。这样明显不符合本意。
>反过来,如果定义时,用的是char *p = "abcdef", 而声明时用的是extern char p[];这样的声明,编译器会认为p是一个数组,它包含四个char型元素。
4.4 指针数组 和 数组指针
>在C语言里,赋值符号“=”两边的数据类型必须完全一致,如果不一致,则显式或隐式地进行类型转换。
#include <stdio.h>
int main()
{
char a[5] = {'A', 'B', 'C', 'D'};
char (*p3)[5] = &a;//p3的类型是一个char型数组的指针,&a的类型也是整个数组的指针
char (*p4)[5] = a;//p4的类型是一个char型数组的指针,而a的类型是数组首元素的首地址
printf("%s, %s, %s, %s\n", p3, p4, p3 + 1, p4 + 1);//output: ABCD, ABCD, ,
return 0;
}
#include <stdio.h>
int main()
{
char a[5] = {'A', 'B', 'C', 'D'};
char (*p3)[3] = &a;
char (*p4)[3] = a;
printf("%s, %s, %s, %s\n", p3, p4, p3 + 1, p4 + 1);//output:ABCD, ABCD, D, D
return 0;
}
#include <stdio.h>
int main()
{
char a[5] = {'A', 'B', 'C', 'D'};
char (*p3)[10] = &a;
char (*p4)[10] = a;
printf("%s, %s, %s, %s\n", p3, p4, p3 + 1, p4 + 1);//output:ABCD ABCD, 乱码, 乱码
return 0;
}
>地址的强制转换
struct Test
{
int Num;
char *pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
这时,p + 0x1 就相当于 p的地址 + sizeof(struct Test) * 0x1;
(unsigned long)p + 0x1 = p的地址(强制转换成无符号长整型) + 0x1;
(unsigned int *)p + 0x1 = p的地址(强制转换成指向无符号整型的指针) + sizeof(unsigned int) * 0x1;
int main()
{
int a[4] = {1, 2, 3, 4};
int *p1 = (int *)(&a + 1);
int *p2 = (int *)((int)a + 1);
printf("%x, %x\n", p1[-1], *p2);//output:4,2000000(小端机)
return 0;
}
4.5 多维数组 和 多级指针
>数组里面可以存放除了函数之外的任何数据
这样的定义:void ((*p)[5])(); 是错误的。
>
#include <stdio.h>
int main(int argc,char * argv[])
{
int a [3][2]={(0,1),(2,3),(4,5)};
int *p;
p=a [0];
printf("%d",p[0]);//输出是多少呢?答案:1。初始化数组时一定用的是花括号!
}
>
int main()
{
int a[5][5];
int (*p)[4];
p = a;
&p[4][2] - &a[4][2] = ?
return 0;
}
&a[4][2] = &a[0][0] + 4 * (5 * sizeof(int)) + 2 * sizeof(int);
&p[4][2] = &p[0][0] + 4 * (4 * sizeof(int)) + 2 * sizeof(int);
>二级指针
char **p;
A) p = NULL;
B) char *p2; p = &p2;
4.6 数组参数 和 指针参数
>形参:声明或定义函数时的参数; 实参:调用函数时主调函数传递过来的实际值。
>c语言中,当一维数组作为函数参数的时候,编译器总是把它解析成一个指向其首元素首地址的指针。
所有非数组形式的数据实参均以传值形式(对实参做一份拷贝并传递给被调用的函数,函数不能修改实参的实际变量的值,而只能修改传递给它的那份拷贝)调用。
函数的返回值也不能是一个数组,而只能是指针。(函数本身是没有类型的,只有函数的返回值才有类型)
>main函数内的变量不是全局变量,而是局部变量,只不过它的周期和全局变量的一样长而已。所以,main函数里定义的变量,无法在其它函数中使用,只能将main中的变量做一份拷贝传递给其他函数。
>不能把指针变量本身传递给一个函数(除了数组之外,都是传值方式)。
#include <stdio.h>
void GetMemory (char *p, int num)
{
p = (char *)malloc(num * sizeof(char));
}
int main()
{
char *str = NULL;
GetMemory(str, 10);
strcpy(str, "hello");
free(str);
return 0;
}
GetMemory函数中,malloc的内存的地址赋给了GetMemory 函数中的局部变量p,而没有给str(只是将str的值copy到p中了)。局部变量在函数调用完是要被回收的,当GetMemory函数调用完成后,p已经不存在了,而str并没有改变。
>>1.虽然p已经被回收了,但是malloc的内存并没有被回收掉,这块内存是可以通过他的地址来使用的。
return p;//将这块内存的地址返回到main函数中去使用。
>>2.虽然没有办法将str本身传递到GetMemory函数中,通过GetMemory函数去改变str的值,但是可以将str本身的地址传递到GetMemory函数中,在GetMenory函数中修改这个地址中的值来修改str的值
GetMemory(char **p, int num) { *p = (char *)malloc (num * sizeof(char)); }
GetMemory(&str, 10);
>二维数组参数 和 二维指针参数
数组参数 | 等效的指针参数 |
数组的数组 char a[3][4] | 数组的指针 char (*a)[4] |
指针数组 char *a[5] | 指针的指针 char **a |
4.7 函数指针
>
#include <stdio.h>
#include <string.h>
char *fun(char *p1, char *p2)
{
int i = 0;
i = strcmp(p1, p2);
if (0 == i)
{
return p1;
}
else
{
return p2;
}
}
int main()
{
char *(*pf)(char *p1, char *p2);
char *a = NULL;
pf = fun;//函数名被编译之后,其实就是一个地址
//pf = &fun;//与上一行等同
//a = (*pf)("aa", "bb");
a = pf("aa", "bb");//pf是一个指向函数的指针,这样做的依据是什么?
printf("%s\n", a);
return 0;
}
>(*(void(*) ())0)() 将0强制转换成函数指针类型,再调用这个函数。