下面文章来自朱兆祺编写的《攻破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);
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. 如果ptr1为0x1000 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个指针型变量的数组(指针数组)