指针
什么是指针?
指针其实是内存单元地址
什么是指针变量?
指针变量是用于存储内存单元地址的变量
指针变量存储的实质
顾名思义,指针变量存储的是内存单元的值,即存储的值其实指向的一个特定类型内存区域的起始地址。
如下例子,整型变量a的值为123,其内存单元地址为0x104,指针变量存储的值是变量a的内存单元0x104。
指针变量指向了内存地址起始为0x104,长度为4个字节的内存单元。p也可以对这个4个单元进行操作。
int a = 123;
int *p = &a;
指针变量的运算
c语言中,我们可以像普通整型数据一样,对指针变量进行运算。
指针变量的转换
普通类型的指针变量都可以直接赋值void *空类型指针,
但空类型指针需要强转才可以转成普通类型指针。
void 指针是一种特殊的指针,表示“无类型的指针”,因为其没有指定类型,所有它可以指向任何类型的数据,也就是说任何类型的指针可以直接复制给void指针。
void * vp;
int * ip;
vp = ip;
但是void指针赋值给其他类型的指针时,必须强制转换
void *vp;
int * ip;
ip = (void *)vp;
这是因为指定了类型的指针变量指向了内存的一块区域,但空类型指针无法确定指向内存区域的大小。
需要注意的是,不同类型指针变量相互转换时,需避免访问非法的内存区域,导致程序异常退出。
如下例子,chP指向了一段长度为1字节的变量a的内存区域,当其强制转换成int指针时,则超出了编译器分配的内存区域,程序会异常退出。
char a = 'a';
char * chP = &a;
int * iP = (int *)chP;
*iP = 123;
指针变量的算术运算
指针变量可以使用算术运算符实现自增减,如下例
int a = 123;
int * ip = &a;
printf("p = %p \n", ip);
char * charPtr = (char *)ip;
double * doublePtr = (double *)ip;
charPtr++;
doublePtr++;
printf("charPtr = %p \n", charPtr);
printf("doublePtr = %p \n", doublePtr);
假设&a地址为0x100,则上面例子的输出为
p = 0x100
charPtr = 0x101
doublePtr = 0x108
即使指针变量的算术运算为增减sizeof(数据类型)的大小。上例子中p的值为0x100,强转为char 后自增1,结果为0x101,强转为double 后自增1为,0x108(0x100+0x8)。
同理因为空指针类型无法得知其指向区域的长度,void *指针便无法进行增减操作。
数组
C语言中,数组与指针是一种非常暧昧的关系,因数组和指针经常可以相互的转换,所以经常会将其两者混淆。
真正的事实是,两者拥有不同的存储结构,但引指针的灵活性,两者可以相互的引用、转换。
数组的存储结构
与指针的存储结构相比,数组在内存中占据的是连续的字节单元。即指针存储的长度根据计算机不同,是一个固定的大小 (32位4个字节、64位8个字节),数组存储的是一块连续的内存区域。
int * p = ....;
int a[3] = { 1,2,3 };
printf("sizeof(p) :%d \n",sizeof(p)); //sizeof(p) :4
printf("sizeof(a) :%d \n", sizeof(a)); //sizeof(p) :12
为什么指针可以访问数组的元素?
那为什么指针可以访问数组中元素?
这是因为数组标识符表示的是该数组的第一个元素的地址,当指针指向数组标识符时,便可以通过指针访问数组中的各个元素。
int a[3] = {1,2,3};
int * p = a;
printf("a :%p \n",a);
printf("* p :%p \n", p);
printf("* a[1] :%p \n", *(p++)); //1
函数调用时数组作为参数时为地址传递
C语言的标准中规定:所有的数组在作为参数传递时,都转换成指向
数组起始地址的指针,其他参数均采用值传递。
采用地址传递好处是形参不存在存储空间,编译系统不为形参分配内存,数组名或者指针便是一组连续空间的首地址。
例如下面例子输出的size都为4。
void fp(char *arr)
printf("fp size : %d", sizeof(arr));
void fa(char arr[])
printf("fa size : %d", sizeof(arr));
int main()
char c[] = "Hello";
fp(c); // fp size :4
fa(c); // fa size :4
}
数组与指针的区别
数组和指针其实并不是一个相同概念,虽然在日常的使用中,经常可以使用指针代替数组,用于遍历数组的元素,例如
char array[6]="hello";
char *chPtr = array;
char charE = chPtr[1];
printf("%c \n",charE);
再来一个例子,可以证明数组并不是指针:
/* 文件 f1.c*/
int a[3] = {1,2,3};
/* 文件 main.c */
#include "f1.c"
int main()
extern int * a;
return 0;
}
执行上面代码会提示“a”: 重定义;不同的间接寻址类型
在使用extern int a时,编译器认为a是一个在外部声明的整型指针变量,但f1.c中,a是一个长度为3的整型数组,在32位系统系统下,int 长度为4字节,而int [3]长度为4*3 = 12字节。
再看一个例子
char * a = "hello";
char arr[6] = "hello";
if (a == arr)
printf("same");
else {
printf("different");
}
程序执行后打印different。这是因为a指向的是存储于数据段静态变量hello,arr则指向编译器分配的内存空间。两者的值并不一样。
对于数组而言,编译器已经为数组分配了一定的空间以及对应的地址,通过数组地址的偏移,可以访问该数组的元素。
而指针,编译器为其分配了空间,用于存储地址值。而对于存放在其中的值,只有在程序运行时才能知道。
使用指针和数组的注意点与建议
1. 使用指针前必须初始化,否则会指向错误的内存区域,导致程序异常。
2.使用NULL指针作为函数调用失败的返回值。类似malloc函数,当分配内存失败时返回NULL,用以表示为一种异常情况。
3.在不知内存区域具体类型情况下,避免对void 指针进行算术操作
例如,下例子对p任何的操作都会导致程序出错
4. 不同类型指针转换时,注意不超出编译器分配的内存区域。
如下例子,ip使用了超出了编译器分配的内存,会导致程序异常退出
char a = '2';
char * cp = &a;
int *ip = (int *)cp;
*ip = 123;
5.避免指针和整型数据的直接转换。
总结
1.指针变量是变量,存储内存地址的变量。
3.数组存储的是一段连续的内存区域
4.数组标识符存储了,一段内存区域的起始地址
5.数组作为参数传递时是地址传递,其他类型则为值传递