首先有个问题:指针和数组有什么关系呢?
答案:什么关系都没有。
指针就是指针,在32位平台下,永远占4个字节,其值为某一个内存的地址。
数组就是数组,其大小与元素的类型和个数有关。定义数组时必须指定其元素的类型和个数。
指针数组:首先它是一个数组,数组的元素都是指针,数组占多少个字节由数组本身决定。它是“储存指针的数组”的简称。
int *a[10];//指针数组,“[]”的优先级比“*”要高,这是一个数组,其包含10个指向int类型数据的指针
即变量名与哪一标识符先结合,即为哪种类型。
数组指针:首先它是一个指针,它指向一个数组。在32位系统下永远是占4个字节,它是“指向数组的指针”的简称。
int(*p)[10];
//解释:p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个指针,指向一个数组,叫数组指针
那数组的地址如何来存储?
int arr[10] = {0};
int *p1 = &arr;//错误,&arr 类型为数组指针,p1的类型为int*,类型不匹配
int (*p2)[10] = &arr;//正确,数组指针
补充:若所指向大小不同,则类型也不同,如:
int (*p2)[2]=&arr;//错误
首先来了解数组和指针是如何传参的?
函数本身是没有类型的,只有函数的返回值才有类型
a.数组传参
数组传参时,编译器将其解析为指向其首元素首地址的指针。
实际传递数组的大小与形参指定数组的大小无关。
b.指针传参
指针传参时创建了临时变量。
当一个函数的参数部分为一级指针的时候,函数能接收什么参数?
比如:
void test1(int *p)
{}
//test1函数能接收什么参数?
//答案是:若int a[]={1,2};
//则能接受 &a,a[],*a;
举个栗子吧:看看有什么错误?
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);//free并没有起作用,内存泄漏
return 0;
}
原因:在运行strcpy(str,”hello”)语句的时候发生错误。这时候观察str的值,发现仍然为NULL。也就是说str本身并没有改变,我们malloc的内存的地址并没有赋给str,而是赋给了_str。而这个_str是编译器自动分配和回收的,我们根本就无法使用。
那么如何解决呢?
解决方案:
第一:用return。
char * GetMemory(char * p, int num)
{
p = (char *)malloc(num*sizeof(char));
return p;
}
int main()
{
char *str = NULL;
str = GetMemory(str,10);
strcpy(str,”hello”);
free(str);
return 0;
}
这样就复制成功了!
还有一个方法:
用二级指针。
void GetMemory(char ** p, int num)
{
*p = (char *)malloc(num*sizeof(char));
return p;
}
int main()
{
char *str = NULL;
GetMemory(&str,10);
strcpy(str,”hello”);
free(str);
return 0;
}
注意,这里的参数是&str而非str。这样的话传递过去的是str的地址,是一个值。所以malloc分配的内存地址是真正赋值给了str本身。
接下来进入主题:
c.函数指针
顾名思义,函数指针就是函数的指针。它是一个指针,指向一个函数。
我们用个代码来说明吧:
void test()
{
printf("hehe\n");
}
//下面pfun1和pfun2哪个有能力存放test函数的地址?
void (*pfun1)();
void *pfun2();
答案:pfun1可以存放。pfun1先和*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无参数,返回值类型为void。
来看个有趣的例子吧:
((void() ())0)()——这是什么?
乍一看,也太复杂了吧,what is it?
没有发狂吧?下面我们就来分析分析:
第一步:void(*) (),可以明白这是一个函数指针类型。这个函数没有参数,没有返回值。
第二步:(void(*) ())0,这是将0强制转换为函数指针类型,0是一个地址,也就是说一个函数存在首地址为0的一段区域内。
第三步:((void() ())0),这是取0地址开始的一段内存里面的内容,其内容就是保存在首地址为0的一段区域内的函数。
第四步:((void() ())0)(),这是函数调用。
char * (*pf[3])(char * p);
这是定义一个函数指针数组。它是一个数组,数组名为pf,数组内存储了3个指向函数的指针。这些指针指向一些返回值类型为指向字符的指针、参数为一个指向字符的指针的函数。
函数指针数组怎么使用呢?
#include
#include
char *fun1(char *p)
{
printf("%s\n",p);
return p;
}
char *fun2(char *p)
{
printf("%s\n",p);
return p;
}
char *fun3(char *p)
{
printf("%s\n",p);
return p;
}
int main()
{
char *(*pf[3])(char *p);
pf[0] = fun1; //可以直接用函数名
pf[1] = &fun2; //可以用函数名加上取地址符
pf[2] = &fun3;
pf[0]("fun1");
pf[0]("fun2");
pf[0]("fun3");
return 0;
}
看着这个标题没发狂吧?反正我是不好了…..
指向函数指针数组的指针是一个 指针 .
指针指向一个 数组 ,数组的元素都是 函数指针 ;
char *(*(*pf)[3])(char *p);
分析一下:
pf这个指针指向一个包含了3个元素的数组;这个数组里面存的是指向函数的指针;这些指针指向一些返回值类型为指向字符的指针、参数为一个指向字符的指针的函数。
说完了上面这些概念问题,接下来据多个例子讲解吧:
练习1:
intmain()
{
int a[4] = { 1, 2, 3, 4};
int *ptr1 = (int*)(&a + 1);
int *ptr2 = (int*)((int)a + 1);
printf( "%x,%x", ptr1[-1], *ptr2);
return 0;
}
讲解:
ptr1:将&a+1的值强制转换成int*类型,赋值给int* 类型的变量ptr,ptr1肯定指到数组a的下一个int类型数据了。ptr1[-1]被解析成*(ptr1-1),即ptr1往后退4个byte。所以其值为0x4。
ptr2:(int)a+1的值是元素a[0]的第二个字节的地址。然后把这个地址强制转换成int*类型的值赋给ptr2,也就是说*ptr2的值应该为元素a[0]的第二个字节开始的连续4个byte的内容。
其内存布局如下图:
练习2:
int main()
{
int a[5] = { 1, 2, 3, 4, 5};
int *ptr = (int *)(&a + 1);
printf( "%d,%d", *(a + 1), *(ptr - 1));//输出2,5
return 0;
}
//程序的结果是什么?
练习3
structTest
{
int Num;
char *pcName;
short Date;
char cha[2];
short Ba[4];
}*p;
假设p 的值为0x100000。 如下表表达式的值分别为多少?
p + 0x1= 0x___ ?
(unsigned long)p + 0x1= 0x___ ?
(unsigned int*)p + 0x1= 0x___ ?
解析:p + 0x1= 0x100014;
p+0x1的值为0x100000+sizof(Test)*0x1。至于此结构体的大小20byte。
(unsigned long)p + 0x1=0x100001;
这个表达式其实就是一个无符号的长整型数加上另一个整数。
(unsigned int*)p + 0x1= 0x100004;
p被强制转换成一个指向无符号整型的指针;
练习4:
注意有坑哦!
#include
int main(int argc, char * argv[])
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };//花括号内为小括号,逗号表达式,相当于int a [3][2]={ 1, 3,5};
int *p;
p = a[0];
printf( "%d", p[0]);//输出 1,3,5
}
练习5:
int main()
{
int a[5][5];
int (*p)[4];//p是指向一个包含4个元素的数组的指针
p = a;
printf( "a_ptr=%#p,p_ptr=%#p\n", &a[4][2], &p[4][2]);
printf( "%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
return 0;
}
那么 FFFFFFFC是什么呢:
-4原码:
1000 0000 0000 0000 0000 0000 0000 0100
反码
1111 1111 1111 1111 1111 1111 1111 1100
F F F F F F F C
练习5:
int main()
{
char *c[] = { "ENTER", "NEW", "POINT", "FIRST" };
char **cp[] = { c + 3, c + 2, c + 1, c };
char ***cpp = cp;
printf("%s\n", **++cpp);
printf("%s\n", *--*++cpp + 3);
printf("%s\n", *cpp[-2] + 3);
printf("%s\n", cpp[-1][-1] + 1);
system("pause");
return 0;
}
解析:
如果还有不懂可参考《c语言深度解剖》这本书!
以上是我的总结,请多指教!