读朱兆祺攻破C语言之六---数组、指针

下面文章来自朱兆祺编写的《攻破c语言笔试和机试难点》的pdf,加上部分自己验证程序。在此感谢这位牛人为我们详尽讲解了C语言细节和难点问题

1.1      数组、数组元素、指针的大小

1. 如程序清单6. 1所示,程序输出什么?
#include <stdio.h>
int main(int argc, char *argv[])
{
    char ca,*pca;
	int ia,*pia,a[100];
    float fa,*pfa;
    double da,*pda;
    /*测试char型变量*/
    printf( "sizeof(ca)       = %d \n"  ,  sizeof(ca));
    printf( "sizeof(pca)       = %d \n"  ,  sizeof(pca));
    printf( "sizeof(*pca)       = %d \n\n"  ,  sizeof(*pca));
    /*测试int型变量*/ 
    printf( "sizeof(ia)       = %d \n"  ,  sizeof(ia));
    printf( "sizeof(pia)       = %d \n"  ,  sizeof(pia));
    printf( "sizeof(*pia)      = %d \n\n"  ,  sizeof(*pia));
    
	printf( "sizeof(a)       = %d \n"  ,  sizeof(a));
    printf( "sizeof(a[100])  = %d \n"  ,  sizeof(a[100]));
    printf( "sizeof(&a)      = %d \n"  ,  sizeof(&a));
    printf( "sizeof(&a[0])   = %d \n\n"  ,  sizeof(&a[0])) ;
    /*测试float型变量*/ 
    printf( "sizeof(fa)       = %d \n"  ,  sizeof(fa));
    printf( "sizeof(pfa)       = %d \n"  ,  sizeof(pfa));
    printf( "sizeof(*pfa)      = %d \n\n"  ,  sizeof(*pfa));

    /*测试double型变量*/ 
    printf( "sizeof(da)       = %d \n"  ,  sizeof(da));
    printf( "sizeof(pda)       = %d \n"  ,  sizeof(pda));
    printf( "sizeof(*pda)      = %d \n"  ,  sizeof(*pda));
    
	return 0;
}
运行结果( C-free软件(windows平台)
  
由上述运行结果可知:
1、任何数据类型的指针都是占4字节。不管是char、int、float、double指针(pca,pia,pfa,pda都占用4个字节)。(这是由编译器规定的,在32位系统中,只需要4个字节就可以实现2^32寻址)
2、而指针指向的数据(即*pca,*pia,*pfa,*pda)占用空间由指向的数据类型决定的。char型占用1字节,int、float占用4个字节,long int 占用4个字节,double占用8个字节。
3、a是数组,除了sizeof(a)之外,当a出现在表达式中时,编译器会将a生成一个指向a[0]的指针,而这里测量的是整个数组的大小。
 
 

1.2      指针、数组地址

程序清单
#include "stdio.h"
int main(int argc, char *argv[])
{
	int iArray[3] = { 1 , 2 , 3 } ;
	int *ptr=iArray; 
	/*输出数组地址、指针地址*/     
	printf("ptr          = %#x\n" , ptr ) ;
	printf("iArray       = %#x\n" , iArray ) ;
	printf("&iArray      = %#x\n" , &iArray ) ;
	printf("&iArray[0]   = %#x\n\n" , &iArray[0] ) ;
	/*修改后输出数组地址、指针地址*/  
	printf("&iArray+1     = %#x\n" , &iArray+1 ) ;
	printf("iArray+1      = %#x\n" , iArray+1 ) ;
	printf("&iArray[0]+1 = %#x\n" , &iArray[0]+1 ) ;
    return 0;
}
输出结果:
 
由上述结果可知:
1、iArray是数组名,同时也是数组首元素的地址,为0x22FF60;&iArray是数组的首地址,这是毫无疑问的。&iArray[0]是数组首元素的地址,也是0x22FF60。也就是说数组的首地址和数组首元素的地址是相等的
 
2、如果数组首地址加1 和数组数组首元素地址加1 有什么区别?
     下面天气预报形式举例说明,如下图所示。
       1、以全国的角度报全国各省政府的天气预报,报完广东省省政府(&iArray是数组的首地址),之后就为湖南省省政府(&iArray+1是偏移一个数组单元
       2、在报广东省各个城市天气预报时,先报省会广州市(即&iArray[0]是数组首元素的地址),之后报深圳市(即&iArray[0]+1是偏移一个数组元素单元)。
   
 

1.3      数组作为函数参数,是传什么进去了

源程序:
#include <stdio.h>
void text(char string[])
{
    printf( "sizeof(string) = %d \n" , sizeof(string) ) ;
}
void main(void)
{
	char str[]={"good\n"};
	char *s="hello";
	/*test of char  length*/
	printf("strlen(str)=%d\n",strlen(str));
	printf("sizeof(str)=%d\n\n",sizeof(str));

	/*test of char string length*/
	printf("strlen(s)=%d\n",strlen(s));
	printf("sizeof(*s)=%d\n",sizeof(*s));

	text(str);    
}
运行结果:

这里考查两个知识点,
其一,sizeof和strlen();sizeof测试一个对象或者类型所占的内存字节数(对于字符串,\0也在计算之内) strlen(char*)函数求的是字符串的实际长度,它求得方法是从开始到遇到第一个'\0',如果你只定义没有给它赋初值,这个结果是不定的,它会从aa首地址一直找下去,直到遇到'\0'停止两者区别:\0是否考虑?sizeof中计算在内,而strlen不计算在内。
其二text(char cArray[])形参到底是什么? 到底是传值还是传址?
传送的是地址。在执行text()函数时,将str[]数组地址(在32位系统中,由4个字节表示)传送给text函数中string[]。
其中sizeof(string),实际测试数组地址的占用内存空间即4个字节。(使用指针更好理解:调用text(string)函数时,待完善)
 
          

1.4      指针相减

1.   如程序清单6. 5程序,输出会是什么?
#include <stdio.h>
int main(int argc, char *argv[])
{
    int  a[2] = { 3 , 6 } ;
    int  *p   = a ;
    int  *q   = p + 1 ;
    printf( "q - p = %d \n" , q-p ) ;
    printf( "(int)q - (int)p = %d \n" , (int)q-(int)p ) ;
   
    return 0;
}
用数学方法到可以做出q-p = 1这个答案,但是(int)q - (int)p 的答案。指针,指针的强大。由于指针加1,内存地址是加sizeof(int),但是int(q)和int(p)就不再是指针,而是一个整形数据。所以(int)q - (int)p  = 4 。

1.5      指针加1到底是加什么

1.   如程序清单6. 6所示,请问p1+5=__;p2+5=__;
程序清单6. 6  指针加1
#include <stdio.h>
int main(int argc, char *argv[])
{
    unsigned char  *p1 ;
    unsigned long   *p2 ;
   
    p1 = (unsigned char *)0x801000 ;
    p2 = (unsigned long *)0x810000 ;
   
    printf( "p1+5 = %#x \n" , p1 + 5 ) ;
    printf( "p2+5 = %#x \n" , p2 + 5 ) ;
   
    return 0;
}
由于p + n = p + n * sizeof(p的数据类型) ,所以答案为:
p1+5 = 0x801005
p2+5 = 0x810014
请按任意键继续. . .

1.6      数组与指针的概念(非常重要)

1.  数组与指针的概念
a) int a;
b) int *a;
c) int **a;
d) int a[10]; 
e) int *a[10];  
f) int (*a)[10];  
g) int (*a)(int);  
h) int (*a[10])(int);
答案:
a)   一个整型数 ;
b)   一个指向整型数的指针;
c)   一个指向指针的指针,它指向的指针是指向一个整型数;
d)   一个有10个整型数的数组;
e)   一个有10个指针的数组,该指针是指向一个整型数的;指针数组 int  *a[10]
f)   一个指向有10个整型数数组的指针数组指针 int (*a)[10]
g)   一个指向函数的指针,该函数有一个整型参数并返回一个整型数;函数指针
h)   一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数。
这个题目很经典,很多公司的笔试题目都会截取上面部分出来考试。特别是e和f,哪一个是数组指针,哪一个又是指针数组;g和h哪一个是函数指针,哪一个又是指针函数。
 
如何区分是数组、指针? 
由运算符的结合顺序决定,第一个同变量a结合的运算符决定其类型,是指针还是数组。
1、int *a[10]  由于[]运算优先级高于*,所以首先[]同变量a结合,定义为数组即10个指针的数组。指针数组
2、int  (*a)[10] 由于()与[]属于同一个优先级,而该优先级的是从左向右的结合顺序,所以首先*同变量a先结合,定义为指针
10个整型数组的指针。数组指针
3、int (*a)(int); 由于同一优先级,结合顺序是从左向右结合,所以定义为指针即指向的函数的指针。函数指针
4、int (*a[10])(int);  由上面可知,定义为数组即有10个指针的数组,该指针指向一个函数。
5、int *a(int);由于()优先级高于*,所以定义为函数即函数a(),返回值为int 指针。 指针函数

1.7      数组与指针的糅合

1.  如程序清单6. 8所示,输出会是什么?
程序清单6. 8  数组与指针的糅合应用1
int arr[] ={6,7,8,9,10};   
int *ptr   =arr;  
*(ptr++)   +=123;  
printf(%d,%d,*ptr,*(++ptr));
这个题目来源于华为的一道C语言笔试题,答案是:8,8。 *ptr = arr ,这里ptr取得是数组arr[]的首元素地址,*(ptr++) +=123 ,这里是ptr++,此时*(ptr)即为:6,那么*(prt++)+123=129,执行完*(ptr++)+=123之后,*(ptr) = 7。跟*(++ptr)之后为8这个值是没有半点关系,由于执行了*(++ptr),所以此刻的*(ptr)为8,所以输出为:8,8。
考查两个知识点:
1、单目运算优先级顺序(从右向左)。*ptr++等同*(ptr++)   1、*ptr   2、ptr=ptr+1,而*++p 等于1、ptr=ptr+1   2、*p
2、指针++ 运算顺序(是先执行还是先运算)。根据运算符的位置决定的。
例如printf(%d,%d,*ptr,*(++ptr));执行过程,自己认为输出是7,8。(先输出第一个*ptr,然后ptr++,在输出第二个*ptr) 实际不是。
 
2. 如程序清单6. 9所示,输出会是什么?
int main( int argc , char *argv[] )
{  
   int a[5] = { 1 , 2 , 3 , 4 , 5 };  
   int *ptr = (int *)( &a + 1 );  
   printf( "%d,%d" , *(a+1) , *(ptr-1) );
}
这个题目要求对指针的理解要比较透彻。由于*(a+1)和a[1]是等效的,则*(a+1)=a[1] = 2。&a指的是指向整个数组变量的指针,&a+1不是首地址+1,系统会认为加了一个a数组的偏移量,即偏移了一个数组的大小,因此ptr指向的是a[5],即是*(ptr+5),既然如此,那么*(ptr-1)当然就是a[4] = 5咯。所以这个题目的答案是: 2 , 5 。
 
其实这个题目还有一个延伸,int *ptr = (int *)( (int) a + 1 ), *ptr是多少。答案是:2 00 00 00。(经典)
假设 &a[0] = 0x1000 0000,由于存储方式是小端模式,那么a[0] = 1和a[1] = 2的存储方式如图6. 2所示。
因为a = 0x1000 0000,而(int)a将这个地址强制转化为了int型数据,((int)a + 1) = 0x1000 0001,经过(int *)((int)a + 1)成了地址,ptr = (int *)((int)a + 1),由于ptr是指向int型的指针,*ptr占4个字节,*ptr所占字节即为:0x00,0x00,0x00,0x02,那么*ptr即为0x02000000。
数据储存示意图
           
 
 
 
3. 如果ptr10x1000 0000,那么三个输出分别为多少?
int iArray[7] = { 1 , 2 , 3 , 4 , 5 , 6 , 7 } ;
int *ptr1     = iArray ;
int *ptr2     = &iArray[5] ;
printf( " ptr2       = %#x \n" , ptr2 ) ;
printf( " ptr1       = %#x \n" , ptr1 ) ;
printf( "ptr2 - ptr1 = %d \n" , ptr2 - ptr1 ) ;
很明显iArray是整型数据类型数组,那么ptr2 = ptr1 + 5*sizeof(int) = 0x1000 0014。很多同学立马就会脱口而出ptr2 – ptr1 = 20嘛!真的是这样吗?其实答案是:5!
解释之前,我们先来看这个程序:
int iArray[7] = { 1 , 2 , 3 , 4 , 5 , 6 , 7 } ;
char *p1      = (char *) iArray ;
char *p2      = (char *) &iArray[5] ;
printf( "p2 - p1     = %d \n" , p2 - p1 ) ;
这个程序的输出是:20。因为指针类型是char*,char是1个字节;而上面*ptr1和*ptr2是int*,所以答案是:5。
如果是:
short *p1      = (short *) iArray ;
short *p2      = (short *) &iArray[5] ;
则p2 – p1 就是为:10。
这里还有一个延伸:
    int iArray[7] = { 1 , 2 , 3 , 4 , 5 , 6 , 7 } ;
    int *ptr1     = iArray ;
    int *ptr2     = &iArray[5] ;
    printf( " ptr2       = %#x \n" , ptr2 ) ;
    printf( " ptr1       = %#x \n" , ptr1 ) ;
    printf( "ptr2 - ptr1 = %d \n" , (int)ptr2 – (int)ptr1 ) ;
这样输出答案是多少呢?20!
数据存储示意图
                  
小结:指针ptr2-指针ptr1的结果
1、计算指针ptr2与指针ptr1的指向地址差值  即地址0x1000 0013与0x1000 0000差值20
2、ptr2-ptr1结果=两个指针的地址差值/指针数据类型占用空间,其中int占用4个字节,char型占用1个字节,short型占用2个字节
      如上:int型指针         ptr2-ptr1=20/4=5
                  char型指针     ptr2-ptr1=20/1=20
                  short型指针     ptr2-ptr1=20/2=10
3、(int)ptr2-(int)ptr1表示直接计算地址差值,不需要考虑指针类型
 

1.8      指针数组

1. 阅读程序,输出什么?
char *cArray[3] = { "abcdef" , "123456" , "jxlgdx" } ;
printf( "sizeof(cArray[0]) = %d \n" , sizeof(cArray[0]) );
我相信有较多的人的答案是:6或者7。原因是忽略了*cArray[3]这是一个指针数组,也就是说cArray[3]数组中存放的是指针,而不是字符串常量。在C语言笔试陷阱与难点第一阶段讲过,只要是指针变量,其大小就是:4。所以这里毋庸置疑,输出应该是:4。
你要是存在怀疑,可以输出cArray[3]数组的各个元素看看是不是指针。
printf( "cArray[0]  = %#x \n" , cArray[0] ) ;
printf( "cArray[1]  = %#x \n" , cArray[1] ) ;
printf( "cArray[2]  = %#x \n" , cArray[2] ) ;
运行程序输出为:
sizeof(cArray[0]) = 4
cArray[0]  = 0x415840
cArray[1]  = 0x415770
cArray[2]  = 0x415768
请按任意键继续. . .
读者亦可输出指针所指向的字符串:
printf( "cArray[0]  = %s \n" , cArray[0] ) ;
printf( "cArray[1]  = %s \n" , cArray[1] ) ;
printf( "cArray[2]  = %s \n" , cArray[2] ) ;
运行输出为:
cArray[0]  = abcdef
cArray[1]  = 123456
cArray[2]  = jxlgdx
请按任意键继续. . .
 
2.   阅读下列程序,输出什么?
typedef int (init_fnc_t) (void);
extern int arch_cpu_init(void);
extern int board_early_init_f(void);
init_fnc_t *init_sequence[] = {
    arch_cpu_init,      
    board_early_init_f,
    NULL,
};
int arch_cpu_init(void)
{
    printf("This is arch_cpu_init \n");
    return 0;
}
int board_early_init_f(void)
{
    printf("This is board_early_init_f \n");
    return 0;
}
void hang (void)
{
    printf("Error! \n");
    while (1) ;
}
int main(int argc, char* argv[])
{
    init_fnc_t **init_fnc_ptr;
    for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr)
    {
       if ( (*init_fnc_ptr)() != 0 )
       {
           hang ();
       }
    }
    return 0;
}
这个题目是我在阅读u-boot-2012.10源码的时候稍作修改从中提取出来的。这个题目将指针数组、函数指针等知识点融为一体。
This is arch_cpu_init
This is board_early_init_f
请按任意键继续. . .

1.9      数组指针

不是很明白。

1. 如程序清单6. 11所示,程序输出什么?
#include <stdio.h>
int main(int argc, char *argv[])
{
    int a[][4]={ 1,3,5,7,9,11,13,15,17,19,21,23};
    int (*p)[4] , i=2 , j=1 ;
    p=a;
    printf( "%d\n", *(*(p+i)+j));
    return 0;
}
答案是:19。
不能理解?好吧,如果我告诉你**(p+1) = 9, *((*p)+1) = 3!到这里能理解了吗?如果还是不能理解,ok,p是指向一个含有4个整型数据的数组,那么p如果加1,地址是不是得偏移4个整形数据的地址,而p等于数组a的首元素地址,a是二维数组,也就意味着p是双重指针了。
示意图:

1.10   再论数组指针与数组首地址

1. 已知&a[0][0] = 0x22fe70,想想会是输出什么?
int main(int argc, char *argv[])
{
    int a[8][8] = {1,2,3,4};
    int (*ptr1)[8]    = a ;
    int (*ptr2)[8][8]  = &a;
    int *ptr3        = &a[0][0];  
    printf(" ptr1     = %#x\n" , ptr1);
    printf(" &a[0]    = %#x\n" , &a[0]);
    printf(" ptr1+1   = %#x\n" , ptr1+1);
    printf(" &a[0]+1  = %#x\n\n" , &a[0]+1);
    printf(" ptr2     = %#x\n" , ptr2);
    printf(" &a       = %#x\n" , &a);
    printf(" ptr2+1   = %#x\n" , ptr2+1);
    printf(" &a+1     = %#x\n\n" , &a+1);
    printf(" ptr3        = %#x\n" , ptr3);
    printf(" &a[0][0]    = %#x\n" , &a[0][0]);
    printf(" ptr3+1      = %#x\n" , ptr3+1);
    printf(" &a[0][0]+1  = %#x\n\n" , &a[0][0]+1);
    return 0;
}
这个题目涉及到两个知识点,其一,讲烂了的数组首元素地址数组的首地址;其二,数组指针和指针数组的区别。
先看第三个指针,int *ptr3 = &a[0][0];这个毫无疑问,是将数组a的首元素地址赋给指针ptr3,由于是int型数组,那么ptr3+1则是偏移一个int型大小,即偏移4个字节,那么ptr3这一组的输出即为:
ptr3        = 0x22fe70
&a[0][0]    = 0x22fe70
ptr3+1      = 0x22fe74
&a[0][0]+1  = 0x22fe74
我们再看第二个指针,int (*ptr2)[8][8]  = &a;根据之前我们讲过的,这个是取数组a的首地址,ptr2的解释就是:一个指向二维数组[8][8]的指针,那么ptr2+1则是偏移了一个二维数组[8][8]的地址,即为4*8*8=256(0x100)个字节的偏移。那么ptr2这一组的输出即为:
ptr2     = 0x22fe70
&a       = 0x22fe70
ptr2+1   = 0x22ff70
&a+1     = 0x22ff70
剩下第一个指针,这个和6.9节差不多,int (*ptr1)[8]    = a ;其实它是等价于int (*ptr1)[8]    = &a[8] ;那么ptr1则是一个指向一维数组[8]的指针,如果我们这么理解a[8][8] = {a1[8],a2[8],…a8[8]}(当然这个理解是错误的),那么ptr1就是指向a1[8],那么当ptr1+1就是指向a2[8],也就是偏移了一个含有8个int型数据的数组,即4*8=32(0x20)个字节。那么ptr1这一组的输出即为:
ptr1     = 0x22fe70
&a[0]    = 0x22fe70
ptr1+1   = 0x22fe90
&a[0]+1  = 0x22fe90
这里再一次重复讲一下数组指针和指针数组。
int (*p)[8]        p是指向一个含有8个整型数据的数组的指针(数组指针)
int  *p[8]         p是一个含有8个指针型变量的数组(指针数组)

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值