1. c语言指针问题
C语言的大多语法都是简单易懂的,这常常会给初学者一种“别人都说C语言难,我看也不过如此”的感觉。但是这种感觉常常会停止在初学者学到指针时,这是一些读者跟我说的。
鉴于很多C语言初学者都觉得指针非常难,
小明定义了一个C语言函数 int f(int * ),为什么 f(&5) 不能正常工作呢?
如果希望传递 5 给函数 f(),在C99中,可以使用下面这种方法:
f( (int[]){5} );
上面这行C语言代码相当于定义了一个数组,并且数组只有一个元素 5,函数 f() 接收到的参数是该数组,只不过这一过程中的数组名没有“显示”。
抛开C99的这个特性,C语言调用 f(&5) 就不能这么写了,而是需要借助变量:
int five = 5;
f(&five);
在C语言中,接受某个值指针的函数是有可能通过该指针修改该值的(即使程序员无此打算,C语言还是会一直这么认为),因此只有变量才能胜任。在C语言中,常数是只读的,只能作为右值,& 运算符是不能处理常数 5 的,f(&five) 会引发编译错误。
f(&five) 会引发编译错误
表达式 * p++ 增加了 p 指向的数值,还是指针 p 本身?
C语言中的 ++ 和 — 运算符的优先级高于 * 运算符,所以 *p++ 其实就相当于 *(p++)。显然,++ 运算符增加的是指针 p 本身的值,不过在指针 p 自增之前,*p++会先返回 p 指向的值。如果希望 ++ 运算符增加 p 指向的值,应该使用括号运算符:(*p)++。
小明想使用指针操作数组里的数值,下面这段C语言代码有什么问题?
小明预计程序会输出 3,但是程序却输出了“垃圾值”,他的C语言代码如下,请看:
int array[5], i, *ip;
for(i = 0; i < 5; i++)
array[i] = i;
ip = array;
printf("%d\n", *(ip + 3 * sizeof(int)));
2. c语言问题
A
a[3]是5
a单独用实际上表示的是数组第一个元素的地址,所以a+a[3]就是a+5就是数组第六个元素的地址,再在前边加上*取值符号,所以*(a+a[3])就是4,最后的答案就是a[4],即0,选A
3. 如何用指针和数组实现C++的四则运算法则
可选中1个或多个下面的关键词,搜索相关资料。也可直接点“搜索资料”搜索整个问题。
4. C语言中指针引用二维数组元素的问题
下面是本人文库中的文章,关于二维数组与指针的讲解。
1、两条基本准则:
a、首先要明白,指针运算符的作用,我用一言以概之,你在哪里使用都不会错。指针运算符*的作用是求出*后面所指地址里的值。因此只要*后面的变量表示的是一个地址就可以使用*运算符,来求出这个地址中的值,你不用管这个地址的表示形式是怎样的,只要是地址就可以使用*来求出地址中的值。
b、[ ]这个运算符的的运算法则是,把左侧的地址加上[ ]内的偏移量然后再求指针运算,注意有[ ]运算符的地方就有个隐含的指针,比如x[2]表示的就是将指针x偏移2个单位量后再求指针运算。也就说x[2]与*(x+2)是相等的。
2、对二维数组的讲解:
a、对一维数组地址的详细讲解:
比如一维数组a[5],众所周知,一维数组的数组名a表示的是第一个元素a[0]的地址,也就是说数组名与&a[0]是等价的,因此数组名a的地址,并不是这个一维数组的数组的地址。那么&a,表示的又是什么呢?因为,a是一个一维数组,所以&a表示的就是这个一维数组的地址,也就是说&a中的地址是一个包含有4个元素的一维数组的地址。就好比int i中的&i,才表示的是这个变量i的地址一样。
b、对二维数组地址的讲解:
比如二维数组b[3][4],我们首先从一维开始分析,众所周知b[0],b[1]分别表示的是二维数组中第一行第一个元素和第二行第二个元素的地址,也就是说b[0]是与&b[0][0]等价的,b[1]是与&b[1][0]等介的。因此数组名就与&b[0]等介的,也就是说数组名表示的是二维数组中第一行所包含的一维数组的地址,说简单一点,就是说二维数组名是二维数组中第一行的行地址。因此二维数组名b所包含的地址中包含有二维数组中第二维中元素的个数的一维数组,也就是b的地址中包含一个含有4个元素的一维数组的地址(也就是所谓的数组的数组了)。
c、对二维数组中地址的相加运算的讲解:
同样以b[3][4]为例来讲解,在上面讲到b[0]表示的是&b[0][0],因此对b[0]进行相加运算,比如b[0]+1,那么就将使地址偏移一个单位,也就是地址被偏移到了&b[0][1]处,也就是b[0]+1表示的是b[0][1]的地址。上面也讲到数组名b,表示的是一个一维数组的地址,因此对数组名进行偏移,比如b+1,则将使指针偏移一个一维数组的长度,也就是b+1,将是&b[1]的地址,因此b+1的地址,表示的是二维数组中第二行所包含的一维数组的地址,简单点就是第二行的行地址。
d、对二维数组的指针运算:
*b
在上面讲解过,因为b表示的是二维数组中第一行所包含的一维数组的地址,因此*b=*(b+0)=*(&b[0])=b[0],所以*b表示的是二维数组中第一行第一个元素的地址,即*b=&b[0][0],用语言来描术*(b+0)就是,把数组名的地址偏移0个单位,然后再求这个地址所包含的值。在二维数组中,这个值就是指的b[0],因此这个值是与b[0][0]的地址相等的,结果就是*(b+0)与b是相等的,这在多维数组中是个怪现象,至少本人无法理解这是为什么。因为对同一个地址,进行指针运算得到了不同的结果,比如*b和*b[0],因为b和b[0]都是相同的地址,但对他们进行指针运算却得到了不同的结果,*b得到了&b[0][0]的地址,而*b[0]得到了b[0][0]的值,这是对同一个地址进行指针运算却得到了不同的值,对于这个问题,无法理解。
*(b+1)和*(b+0)
对于*(b+1)也和*(b+0)是同样的道理,*(b+1)=b[1]。我们再来看*b[1],因为*b[1]=*(b[1]+0)=*(&b[1][0])=b[1][0],可以看出,这就是二维数组中第二行第一个元素的地址。
*(*(b+1)+1)
因为*(*(b+1)+1)=*(*(&b[1])+1)=*(b[1]+1)=*(&b[1][0]+1)=*(&b[1][1])=b[1][1],语言描术就是,b+1使地址偏移到了二维数组中第二行所包含的一维数组的地址(或第二行的行地址),然后再对这个行地址求指针(或求值)运算,因此就得到第二行第一个元素的地址,然后再对这个地址偏移一个单位,就得到第二行第二个元素的地址,再对这个地址进行指针运算,就得到了这个元素的值,即b[1][1],其他的内容可以以止类推。
e、对二维数组的指针和[ ]的混合运算
在下面的指针和[ ]的混合计算中,要记住两点关键法则,记住了这两点在哪里计算都不会出错
a、对于像b[1]这样的地址,最好应表示为&b[1][0]再进行偏移计算,比如对于b[1]+1,这不是直接在对b[1]加1,也就是b[1]+1不等于b[2],因为b[1]表示的是第二行行1个元素的地址,对其加1,应该表示的是第二行第二个元素的地址,也就是&b[1][1],而b[2]则表示的是第二行第一个元素的地址,因此错误,所以在计算时应把b[1]转换为&b[1][0]之后,才能直接进行地址的偏移,也就是说b[1]+1=&b[1][0]+1=&b[1][1],这样才能得到正确的结果,并且不会出错。
b、对于有小括号的地方,一定不要省略小括号。比如(&b[1])[1]与&b[1][1]将表示的是不同的结果,第二个是显然的,对于第一个(&b[1])[1]=*((&b[1])+1)=*(&b[1]+1)=*(&b[2])=b[2],可以看到,表示的是第3行第1个元素的地址,因此这两个的结果是显然不一样的。因此对于(b+1)[1]这样的运算,不能省略小括号,即(b+1)[1]=(&b[1])[1]=*((&b[1])+1)=*(&b[1]+1)=*(&b[2])=b[2],如果省略了小括号,则是(b+1)[1]=&b[1][1],这将是不易发现的错误。因此这是两个完完全全不同的符案。
c、总结,指针和[ ]混合运算2点关键,
第1:应把是地址的[ ]运算,转换为地址的形式,比如b[1]应转换为&b[1][0]。因为只有这样才能进行直接的地址相加运算,即&b[1][0]+1=&b[1][1],而b[1]+1不等于b[2]。
第2:有小括号的地方不能省略小括号,如(b+1)[1]=(&b[1])[1]=*((&b[1])+1)=*(&b[1]+1)=*(&b[2])=b[2],也&b[1][1]是完全不同的。
(*(b+1))[1] ,(*b)[1]
最简单的理解方法为*(b+1)和语句b[1]等价,即(*(b+1))[1]和语句b[1][1]是相同的,也就是二组数组第2行第2个元素的值b[1][1],理解方法2逐条解释,如上面的解释*(b+1)表示的是二维数组b的第二行第一个元素的地址,也就是b[1],然后再与后面的[1]进行运算,就得到b[1][1]。或者(*(b+1))[1]=*((*(b+1))+1)=*(*(b+1)+1)这个语句上面解释过。同理(*b)[1]=b[0][1]
*(b+1)[1]:
计算方法1把[ ]分解为指针来计算:因为[ ]运算符高于指针,因此应先计算[ ]再计算指针,因为[1]表示的是把左侧的地址偏移1个单位,再求指针,因此(b+1)[1]=*((b+1)+1),最后再计算一次指针运算,也就是*(b+1)[1]=**((b+1)+1)=**(b+2)=*(*(b+2))=*b[2]=b[2][0],可以看到,最后表示的是b中第3行第一个元素的值。
计算方法2把指针化为[ ]来计算:*(b+1)[1]=*(&b[1])[1]=*(*(&b[1]+1))=**(&b[2])=*b[2]=b[2][0],注意*((&b[1])[1])表达式中应把(&b[1])括起来,若不括起来,则[ ]运算符的优先级高于&运算符,因此(&b[1])[1]与&b[1][1]是不一样的,后一个表示的是第二行第二个元素的地址,而头一个(&b[1])[1]则表示的是,对b的第二行的行地址偏移1个单位后再求指针的结果,也就是*(&b[1]+1)=*(&b[2])=b[2],所以性质是不一样的。
(*b+1)[2]
计算方法1化简[ ]运算符:(*b+1)[2]=*((*b+1)+2)=*(*b+3)=*(&b[0][0]+3)=*(&b[0][3])=b[0][3],这里要注意*b表示的是b[0]=&b[0][0],在计算时最好不要代入b[0]来计算,而应把b[0]转换为&b[0][0]后再计算,因为b[0]+3,很容易被错误的计算为b[3],而实际上b[0]指向的是第一行第一个元素的地址,对其偏移3个单位应该是指向第一行第4个元素的地址,即&b[0][3],而b[3],则指向了第3行第1个元素的地址,这是不同的。
计算方法2化简*运算符:(*b+1)[2]=(b[0]+1)[2]=(&b[0][0]+1)[2]=(&b[0][1])[2]=*(&b[0][1]+2)=*(&b[0][3])=b[0][3],注意,在计算过程中小括号最好不要省略,省略了容易出错,因为[ ]运算符的优先给更高,如果省略了,某些地方将无法计算。比如(&b[0][0]+1)[2]=(&b[0][1])[2],如果省略掉括号,则成为&b[0][1][2],这对于二维数组来讲,是无法计算的。
(*(b+1))[5]
计算方法:(*(b+1))[5]=(*(&b[1]))[5]=(b[1])[5]=*(b[1]+5)=*(&b[1][0]+5)=*(&b[1][5])=b[1][5],结果等于第二行第6个元素的值。
f、注意,在二维或者多维数组中有个怪现象,比如对于多维数组a[n][m][i][j],那么这些地址是相同的,即数组名a, a[0], a[0][0], a[0][0][0], &a[0][0][0][0],都是相同的地址。而且对数组名依次求指针运算将得到,比如*a=a[0],*a[0]=a[0][0], *a[0][0]=a[0][0][0], *a[0][0][0]=a[0][0][0][0],可以看到,只有对最后这个地址求指针运算才真正得到了数组中的值,因此对数组名求指针运算,要得到第一个元素的值,应该****a,也就是对4维数组需要求4次指针运算。同样可以看到,对数组名进行的前三次指针运算的值都是相同的,即*a, **a, ***a和a的值都是&a[0][0][0][0]的值,这就是这个怪问题,按理说对地址求指针应该得到一个值,但对多维数组求指针,却得到的是同一个地址,只是这些地址所包含的内容不一样。
3、数组指针与二维数组讲解:
下面我们将以y[4]={1,2,3,4}这个一维数组为例来层层讲解,指针和数组的关系。
1、数组指针:
定义形式为:int (*p)[4];表示定义了一个指向多维数组的指针,即指针p指向的是一个数组,这个数组有4个元素,对这个指针p的赋值必须是有4个int元素的数组的地址,即只要包含有四个元素的数组的地址都能赋给指针p,不管这个数组的行数是多少,但列数必须为4。即int y[4],x[22][4];都可以赋给指针p。赋值方式为p=&y或p=x,对于&y和二维数组数组名前面已讲过,&y中的地址是一个包含有4个元素的一维数组的地址,二维数组名x也是一个含有4个元素的一维数组的地址,因此可以这样赋值。而这样赋值将是错误的p=y,或者p=x[0]; 因为y和x[0]的地址只包含一个元素,他们不包含一个数组,因此出错。
2.注意()必须有,如果没有的话则,int *p[4];则是定义了一个指针数组,表示每个数组元素都是一个指针,即p[2]=&i;指向一个int型的地址,而*p[2]则表示p[2]所指向的元素的值。
3.初始化数组指针p:
a、当把int y[4]赋给指针p时p=y将是错误的,正确的方式为p=&y因为这时编译器会检查赋给指针p的元素是否是含有四个元素的数组,如果是就能正确的赋值,但语句p=y中的y代表的是数组y[4]第一行第一列的元素的地址也就是&y[0]的地址,因此y指向的地址只有一个元素,而指针p要求的是有4个元素的数组的地址,因此语句p=y将出错。而&y也表示的是一个地址,因为数组名y表示的是&y[0]的地址,因此&y=&(&y[0])。可以把&y理解为是数组y[4]的第一行的行地址,即&y包含了数组y[4]的第一行的所有元素,在这里&y包含有4个元素,因则p=&y才是正确的赋值方法,在这里要注意,数组的某行的行地址是第本行的第一个元素的地址是相同的。
b、把x[22][4]赋给指针p有几种方法,方法一:p=x;我们这样来理解该条语句,首先x[0]表示的是二维数组x[22][4]的第1行第一列元素的地址,这个地址包含一个元素,这是显而易见的,而数组名x也表示一个地址,但这个地址包含的是一个数组(即数组的数组),这个数组是包含4个元素的数组,这4个元素就是数组x第一行的4个元素,也就是说x表示的是数组x[22][4]的第1行的行地址,即数组名x就包含了数组x[22][4]第1行的4个元素,因此这种赋值方式是正确的。这时指针p就相当于是数组名一样,比如p[2][1]访问的就是数组x的第3行的第2个元素。
c、方法二:p=x+1或者p=&x[1];注意必须要有地址运算符&,同理语句&x[1]表示的是数组x[22][4]第2行的行地址,因为x[1]表示的是数组x[22][4]第的第2行第1列的元素的地址,因此&x[1]表示的就是数组x的第2行的行地址,因为&x[1]这个地址包含了一个数组,这个数组的起始地址是从x[1]这个地址开始的,这,即数组x[22][4]中x[1]这一行的4个元素。在这一行中包含了4个元素。而x+1本身就是指的x[1]的地址。这时指针p的起始地址是&x[1],所以p[0][1]不再是访问的x的第一行第二个元素,而是访问的x的第二行第二个元素。
d、注意,再次提示,数组的某行的行地址是与本行的第一个元素的地址是相同的。
5. int (*p)()表示
指针的指针
其实是 *(*p)
意思是该指针指向 int *p
数组
以指针为元素的数组
是函数指针
这个函数指针指向的函数的返回类型是一个函数指针构成的数组
参考:
复杂指针解析
因为C语言所有复杂的指针声明,都是由各种声明嵌套构成的。如何解读复杂指针声明呢?右左法则是一个既着名又常用的方法。不过,右左法则其实并不是C标准里面的内容,它是从C标准的声明规定中归纳出来的方法。C标准的声明规则,是用来解决如何创建声明的,而右左法则是用来解决如何辩识一个声明的,两者可以说是相反的。右左法则的英文原文是这样说的:
The right-left rule: Start reading the declaration from the innermost parentheses, go right, and then go left. When you
encounter parentheses, the direction should be reversed. Once everything in the parentheses has been
parsed, jump out of it. Continue till the whole declaration has been parsed.
这段英文的翻译如下:
右左法则:首先从最里面的圆括号看起,然后往右看,再往左看。每当遇到圆括号时,就应该掉转阅读方向。一旦解析完圆括号里面所有的东西,就跳出圆括号。重复这个过程直到整个声明解析完毕。
笔者要对这个法则进行一个小小的修正,应该是从未定义的标识符开始阅读,而不是从括号读起,之所以是未定义的标识符,是因为一个声明里面可能有多个标识符,但未定义的标识符只会有一个。
现在通过一些例子来讨论右左法则的应用,先从最简单的开始,逐步加深:
int (*func)(int *p);
首先找到那个未定义的标识符,就是func,它的外面有一对圆括号,而且左边是一个*号,这说明func是一个指针,然后跳出这个圆括号,先看右边,也是一个圆括号,这说明(*func)是一个函数,而func是一个指向这类函数的指针,就是一个函数指针,这类函数具有int*类型的形参,返回值类型是 int。
int (*func)(int *p, int (*f)(int*));
func被一对括号包含,且左边有一个*号,说明func是一个指针,跳出括号,右边也有个括号,那么func是一个指向函数的指针,这类函数具有int *和int (*)(int*)这样的形参,返回值为int类型。再来看一看func的形参int (*f)(int*),类似前面的解释,f也是一个函数指针,指向的函数具有int*类型的形参,返回值为int。
int (*func[5])(int *p);
func右边是一个[]运算符,说明func是一个具有5个元素的数组,func的左边有一个*,说明func的元素是指针,要注意这里的*不是修饰 func的,而是修饰func[5]的,原因是[]运算符优先级比*高,func先跟[]结合,因此*修饰的是func[5]。跳出这个括号,看右边,也是一对圆括号,说明func数组的元素是函数类型的指针,它所指向的函数具有int*类型的形参,返回值类型为int。
int (*(*func)[5])(int *p);
func被一个圆括号包含,左边又有一个*,那么func是一个指针,跳出括号,右边是一个[]运算符号,说明func是一个指向数组的指针,现在往左看,左边有一个*号,说明这个数组的元素是指针,再跳出括号,右边又有一个括号,说明这个数组的元素是指向函数的指针。总结一下,就是:func是一个指向数组的指针,这个数组的元素是函数指针,这些指针指向具有int*形参,返回值为int类型的函数。
int (*(*func)(int *p))[5];
func是一个函数指针,这类函数具有int*类型的形参,返回值是指向数组的指针,所指向的数组的元素是具有5个int元素的数组。
要注意有些复杂指针声明是非法的,例如:
int func(void) [5];
func是一个返回值为具有5个int元素的数组的函数。但C语言的函数返回值不能为数组,这是因为如果允许函数返回值为数组,那么接收这个数组的内容的东西,也必须是一个数组,但C语言的数组名是一个右值,它不能作为左值来接收另一个数组,因此函数返回值不能为数组。
int func[5](void);
func是一个具有5个元素的数组,这个数组的元素都是函数。这也是非法的,因为数组的元素除了类型必须一样外,每个元素所占用的内存空间也必须相同,显然函数是无法达到这个要求的,即使函数的类型一样,但函数所占用的空间通常是不相同的。
作为练习,下面列几个复杂指针声明给读者自己来解析。
int (*(*func)[5][6])[7][8];
int (*(*(*func)(int *))[5])(int *);
int (*(*func[7][8][9])(int*))[5];
实际当中,需要声明一个复杂指针时,如果把整个声明写成上面所示的形式,对程序可读性是一大损害。应该用typedef来对声明逐层分解,增强可读性,例如对于声明:
int (*(*func)(int *p))[5];
可以这样分解:
typedef int (*PARA)[5];
typedef PARA (*func)(int *);
这样就容易看得多了。
答案,同时给出用typedef的分解方法:
int (*(*func)[5][6])[7][8];
func是一个指向数组的指针,这类数组的元素是一个具有5X6个int元素的二维数组,而这个二维数组的元素又是一个二维数组。
typedef int (*PARA)[7][8];
typedef PARA (*func)[5][6];
int (*(*(*func)(int *))[5])(int *);
func是一个函数指针,这类函数的返回值是一个指向数组的指针,所指向数组的元素也是函数指针,指向的函数具有int*形参,返回值为int。
typedef int (*PARA1)(int*);
typedef PARA1 (*PARA2)[5];
typedef PARA2 (*func)(int*);
int (*(*func[7][8][9])(int*))[5];
func是一个数组,这个数组的元素是函数指针,这类函数具有int*的形参,返回值是指向数组的指针,所指向的数组的元素是具有5个int元素的数组。
typedef int (*PARA1)[5];
typedef PARA1 (*PARA2)(int*);
typedef PARA2 func[7][8][9];
6. C语言行指针
错解了。p没有指向a[0][0],因为使用*p不能输出a[0][0]的值,*p才是指向a[0][0]的,因为**p就是a[0][0]的值,而且*p输出的是&a[0][0]的地址。p是指向的a[0]也就是说p=&a[0]=a+0;的,也就是指向的二维数组的第一行。
p++ = &a[1]=a+1; 也就是指向二维数组中的第二行。
总之,要记住的是a[1]才是指向的二维数组中第2行第1个元素的地址。
而&a[1]表示的就是二维数组中第二行的行地址。
同样,因为a=a+0,表示的是二维数组中第一行的行地址。因此a+1就是表示的是二维数组中第二行的行地址。
还要记住:[ ]这个运算符的的运算法则是,把左侧的地址加上[ ]内的偏移量然后再求指针运算,注意有[ ]运算符的地方就有个隐含的指针,比如x[2]表示的就是将指针x偏移2个单位量后再求指针运算。也就说x[2]与*(x+2)是相等的。