C++面试题-指针-指针数组与数组指针
问:int (*p)[n]与int *p[n]的区别?
答:
- int (*p)[n]是数组指针,指向某n个元素所组成的整块的数组,返回值是整型指针类型的。
- int *p[n]是指针数组,指向数组里面的每个元素,即p[0]指向第一个元素,p[1]指向第二个元素,以此类推,p[n-1]指向第n-1个元素,返回值是整型的。
问:下面代码的输出结果是什么?
void main()
{
int arr[][3] = { 1, 2, 3, 4, 5, 6 };
int(*ptr)[3] = arr;
printf("%d %d\n",(*ptr)[0],(*ptr)[1]);
ptr++;
printf("%d %d\n", (*ptr)[0], (*ptr)[1]);
}
答:
1 2 4 5
分析:
int(*ptr)[3] = arr;指向整型数组的指针,数组包含3个元素,也就是说ptr这个指针指向包含了3个元素的一维数组,并且指向arr的首地址。
如果ptr++,ptr指向数组的下一个包含3个元素的一维数组。
小结:int (*ptr)[n]指向int a[n]这整块数组。
问:下面代码的输出结果是什么?
void main()
{
int arr[6] = { 1, 2, 3, 4, 5, 6 };
int(*ptr)[6] = &arr;
printf("%d,%d,%d,%d,%d,%d\n", (*ptr)[0], (*ptr)[1], (*ptr)[2], (*ptr)[3], (*ptr)[4], (*ptr)[5]);
ptr++;
printf("%d %d\n", (*ptr)[0]);
}
答:
1,2,3,4,5,6
-858993460(随机)
分析:
void main()
{
int arr[6] = { 1, 2, 3, 4, 5, 6 };
int(*ptr)[6] = &arr; // 这里一定要用取地址符&,因为arr是int *类型的,而&arr是int (*)[6]类型
printf("%d,%d,%d,%d,%d,%d\n", (*ptr)[0], (*ptr)[1], (*ptr)[2], (*ptr)[3], (*ptr)[4], (*ptr)[5]);//(*ptr)[0]与*(ptr[0])结果相同
ptr++;
printf("%d %d\n", (*ptr)[0]); // 不再指向arr数组
}
根据上一试题与这道题结合分析:
int (*ptr)[]是指向由整型数据组成的数组的指针,也就是说ptr指向的是整型元素的数组。
在二维数组当中:
假如int a[2][3]={1,2,3,4,5,6},ptr声明应为int (*ptr)[3]必须为3,因为在二维数组中,拆分为有两个一位数组,即a[2]和a[3],这里ptr指向必须是与后面a[3]保持一致,由此可得int (*ptr)[3]。ptr指向包含3个元素的一维数组。
注意:由于是二维数组,有a[2],即ptr可以自增指向下一个地址,原先ptr指向的是a[0]中包含3个元素的首地址(即&a[0][0])自增之后,ptr就指向了下一个a[1]中包含3个元素的首地址(即&a[1][0])。
一维数组中:
假如int a[6]={1,2,3,4,5,6},ptr声明应为int (*ptr)[6]必须为6,因为在一维数组中,ptr指向必须与a[6]保持一致.ptr指向包含6个元素的一维数组。
注意:ptr不能自增,因为ptr指针指向了包含6个元素的a数组地址。
ptr是数组指针,指向包含有一个或者一个以上元素的数组的地址。一个ptr对多个元素的数组。
问:下面代码输出结果是什么?
int map[2][3] = { { 10, 20, 30 }, { 40, 50, 60 } };
int(*p)[3] =map;
p++;
cout<< **p << endl;
cout<<*p << endl;
cout<< *p + 1 << endl;
cout<< *(*p + 1) << endl;
答:
40
0x0019FF34(内存地址)
0x0019FF38(内存地址)
50
分析:
int map[2][3] = { { 10, 20, 30 }, { 40, 50, 60 } };
int(*p)[3] =map;//如果int (*p)[3]=&map[1];那下一句的p++就可以省略掉
p++;//指向map[1][0]~map[1][2]的数组段的地址
cout<< **p << endl;//map[1][0]值
cout<<*p << endl;//map[1][0]地址
cout<< *p + 1 << endl;//map[1][1]地址
cout<< *(*p + 1) << endl;// 因为前面p++,所以一直指向第二个数组段,这里指第二个数组段第二个元素
问:求下面代码的输出结果?
int(*p)[4];
int a[4] = { 5, 66, 123, 589 };
p = &a;
cout << p << endl; //注意,p不等于p[0]
cout << "p[0]是a的地址:" << p[0] << " a的地址:" << &a << endl;
cout << "a[0]的内容:" << *(p[0]) << endl;
cout << "a[1]的内容:" << *(p[0] + 1) << endl;
答:
0027F740
p[0]是a的地址:0027F740 a的地址:0027F740
a[0]的内容:5
a[1]的内容:66
问:下面代码的输出结果是什么?
void main()
{
int arr[3] = { 1, 2, 3 };
int* ptr[3];
ptr[0]= &arr[0];
ptr[1] = &arr[1];
ptr[2] = &arr[2];
printf("%d,%d,%d\n", *ptr[0], *ptr[1], *ptr[2]);
ptr++;//正确吗?
}
答:
1 2 3
分析:
void main()
{
int arr[3] = { 1, 2, 3 };
int* ptr[3];
ptr[0]= &arr[0];
ptr[1] = &arr[1];
ptr[2] = &arr[2];
printf("%d,%d,%d\n", *ptr[0], *ptr[1], *ptr[2]);//*ptr[0]与*(ptr[0])结果相同
//ptr++;错误,不能自增
}
int* ptr[3]:由返回整型数据的指针所组成的数组 ,是指针数组,有3个成员,每个成员都是一个指针,共有3个指针,返回类型为int*。
ptr++错误,因为ptr是指针数组,有指向对象个数的限定,ptr++,就不知道指向哪个内存块了,反正不是数组arr[3]的内存块。
int *ptr[n]与int (*ptr)[n]区别:
第一点:
int (*ptr)[n]声明分析:ptr先与*结合,再与外面()结合,ptr是一个指针,又与[n]结合,ptr是指向数组的指针,指向的返回类型为int,所以ptr是一个指向整型数组的指针,即数组指针。
int * ptr[n]声明分析:ptr先与[n]结合,ptr是一个数组,数组返回类型为int*,意思是ptr是一个由整型指针的数组组成,数组里面的每一个元素都是整型指针,即指针数组。
第二点:
ptr[0]对应一个a[0]的地址,ptr[1]对应一个a[1]的地址,ptr[2]对应一个a[2]的地址。
int *a[n]用在一维数组的方法与直接变量赋值相同,例如:
int *a[3];
int m = 8, n = 4, x = 3;
//a = &m;//错误,
a[0] = &m;
a[1] = &n;
a[2] = &x;
问:请输出下面代码的结果?
int *a[3];
int m[2][3] = { 9, 88, 55, 22, 44, 66};
//a = &m;错误
a[0] = m[1];
cout<<a<<endl;
cout<<a[0]<< endl;
cout<<*a[0]<<endl;
cout<<*(a[0]+1)<<endl;
答:
0x0019FF34
0x0019FF28
22
44
分析:
int *a[3];
int m[2][3] = { 9, 88, 55, 22, 44, 66};
//a = &m;错误
a[0] = m[1];//指定m[1]的一维数组
cout<<a<<endl;
cout<<a[0]<< endl;//a[0]是含有三个元素的m[1]的地址
cout<<*a[0]<<endl; //*a[0]是含有三个元素的m[1]的m[1][0]首地址的内容
cout<<*(a[0]+1)<<endl;//*(a[0]+1)是含有三个元素的m[1]的m[1][1]的值
//cout<<"a+1是a[1],指针没有指向的地址:"<<**(a+1)<<endl;//出错
问:下面代码的输出结果是什么?
#include<stdio.h>
void main()
{
int arr[2][3] = { 1, 2, 3 ,4,5,6};
int* ptr[1];
ptr[0]= arr[1];
printf("%d,%d\n", *ptr[0],ptr[0][2]);
}
答:
4 6
分析:
void main()
{
int arr[2][3] = { 1, 2, 3 ,4,5,6};
int* ptr[1];
ptr[0]= arr[1];//arr[1]首地址赋值给ptr[0]指针
printf("%d,%d\n", *ptr[0],ptr[0][2]);//ptr[0][2]是arr[1]中的第三个元素
}
int *ptr[n]的用法其实和int *ptr差不多。
问:下面代码的各个值的输出结果是什么?
void main(){
int** a;
int *b[1];
a = new int*[2];
int m[3] = { 22, 33, 44 };
int n[4] = { 4, 5, 6, 7 };
a[0] = m;
a[1] = n;
b[0] = m;
for (int i = 0; i < 3; i++)
{
cout << " a[0][i]是m[3]数组的第i个元素:" << a[0][i]
<< " 和a[0][i]等价的是*(b[0] + i):" << *(b[0] + i)
<< endl << endl;
}
}
答:
22 22 22
33 33 33
44 44 44
分析:
void main(){
int** a;
int *b[1];
a = new int*[2];//动态二维数组
int m[3] = { 22, 33, 44 };
int n[4] = { 4, 5, 6, 7 };
a[0] = m; // 等价于*a = m;
a[1] = n; // 等价于*(a+1) = n;
b[0] = m;// b[0]和a[0]运用效果一样的,但是类型不一样。b[0]是int *b[0],而a[0]是int **a.
for (int i = 0; i < 3; i++)
{
cout << " a[0][i]是m[3]数组的第i个元素:" << a[0][i]
<< " 和a[0][i]等价的是*(b[0] + i):" << *(b[0] + i)
<< endl << endl;
}
}
该题的代码是动态二维数组,就是在堆上开辟一块内存,然后赋值给二级指针,使用的方法类似于int *a[n]。
问:数组与指针的区别?
答:数组是多个元素的集合,在内存中分布在地址相连的单元中,所以通过其下标访问不同单元的元素。指针是一种变量,只不过它的内存单元中保存的是一个标识其他位置的地址。由于地址也是整数,在32位平台下,指针默认为32位。
问:说说char a1[]=“hello”和char *a2="hello"的区别?
答:
a1是一个数组,定义时开辟了数据内存,把常量“hello”赋值给a1(即拷贝),所以常量占有一块内存,而a1又开辟了一块相等的内存。
a2是一个字符类型的指针,指向一个常量"hello"的地址,仅仅只有常量占有内存,而指针本身占有内存为4个字节。(在32位机器中)
问:下面的输出结果是?
void *fun(char *arr)
{
char *arr2 ="world";
arr = arr2;
return arr;
}
int main()
{
char *ch = "hello";
fun(ch);
cout << ch << endl;
return 0;
}
答:
hello
分析:这道题主要考指针作为形参的知识点,考时容易出错。由于arr是形参,属于局部变量,作用只在fun函数里面,调用函数完了之后,arr就被释放掉了。实参ch指向仍然不会改变指向。
问:下面哪个语句不合法?
char a[10] = {'a'};
char b[10] = { 'b' };
a= b;
答:a=b;语句不合法。因为这里数组名a和b是字符指针类型的(char*),a作为左值时,是不可修改的值。
分析:当数组名为左值时,它的类型是字符数组;当数组名为右值时,它的数据类型是字符指针。
问:下面代码的输出结果是什么?
int pstr(char str[])
{
return (int)(sizeof(str)-1);
}
void main()
{
char a[20] = "Hello world";
printf("%d",pstr(a));
}
答:
3
分析:当用函数传递数组指针的时候就自动退化为指针了,而指针的长度是4,你减去1自然就是3了。
修改如下:
void main()
{
char a[] = "Hello world";
int len = sizeof(a)-1;
printf("%d",len);
}
问:下面代码输出结果是什么?
void main(void)
{
char *b = "hello";
cout << sizeof(b) << endl;
cout << sizeof(*b) << endl;
}
答:
4 1
分析:sizeof(b) 中b是char*类型的,输出的结果是指针类型的长度。而sizeof(*b) 中的*b是char类型的,长度为1.
cout<<sizeof(char)<<endl;结果是1
cout<<sizeof(int)<<endl;结果是4
cout<<sizeof(unsigned int)<<endl;结果是4
cout<<sizeof(long int)<<endl;结果是4
cout<<sizeof(short int)<<endl;结果是2
cout<<sizeof(float)<<endl;结果是4
cout<<sizeof(double)<<endl;结果是8
问:下面语句的输出结果是什么?
char ch[]="Hello";
char *p=&ch[0];
cout<<p;
答:
Hello
分析:因为数组第一个元素(即ch数组中序号为0的元素)的地址就是数组的首地址,因此,使用指针指向数组第一个元素的地址就是指向数组的首地址。当输出的时候,指针指向的数组元素都输出来了。不过有读者会有疑问,为什么p直接输出数组的全部内容了,而不是数组的首地址呢。详细分析,请看下一道的...下一道题目的分析。
问:说说为什么下面代码中“char *p2=&ch;”和“char *p2”是错误的?
char ch[]="hello";
char *p1=ch;
char *p2=&ch;
char *ch2="hello";
char *p3=ch2;
char *p4=&ch2;
答:
char ch[]="hello";
char *p1=ch;//正确
char *p2=&ch;//错误
因为&取地址符的ch是char(*)[6]类型的,也就是数组指针类型,意思是一个指向整块数组的指针。所以指针p2指向的变量的数据类型不一致。因此需要强制转换才能通过,即char *p
char *ch2="hello";
char *p3=ch2;//正确
char *p4=&ch2;//编译通过,但输出结果错误
因为&取地址符的ch2是char **类型的,也就是二级指针。p4指针指向的变量的数据类型不一致。但是,如果强制转换的话,输出的结果和第一个是不一样的,会出现乱码。
把&ch和&ch2强制转换成char*类型的区别:
由于ch本身是一个字符数组,取地址符并强制转换之后,指针仍然指向该数组ch的首地址。而&ch2是一个二级指针,强制转换之后,指向的是ch2本身的地址,而不是“hello”这个常量的地址。实践时,把char类型换成int型就容易理解了。
问:下面代码有错误吗?
char ch[]="hello";
char *p=ch;
ch[4]=p[0];
printf("%c\n",ch[4]);
printf("%s\n",ch);
答:没有出现错误。
char ch[]="hello";
char *p=ch;
ch[4]=p[0];//p[0]相当于p+sizeof(char)*0
printf("%c\n",ch[4]);//结果:h
printf("%s\n",ch);//hellh
p++;
printf("%s\n",p);//结果:ellh
问:下面代码的输出结果是什么?
void main(){
char*ch="Hello"; // 与上一题char[]="Hello"一样的,即假如ch="Hello"=0x1001(0x1001是ch数组首地址)
char *p =ch;//也可以直接char *p="Hello";
char *p0 = &ch[0];
char *p1 = &ch[1];
// 输出指定的地址对应的内容+指向地址后的几个字符
cout << "p:"<<p<<endl;
cout << "p0:"<<p0 << endl;
cout << "p1:"<<p1<<endl;
//单个字符的输出
cout << "*p:"<<*p << endl;
cout << "*p0:"<<*p0 << endl;
cout <<"*p1:" <<*p1 << endl;
}
答:输出结果:
p:Hello
p0:Hello
p1:ello
*p:H
*p0:H
*p1:e
分析:
由于在C语言中没有真正的字符串类型的,但是正是因为数组元素的地址是连续的,所以可以通过字符数组来表示字符串。由于字符串常量的本质是一个地址,编译器就会为该字符串常量分配地址,比如说分配ch数组的内容“Hello”地址,依次是H:0x1001,e:0x1002,l:0x1003,l:0x1004,o:0x1005,/0:0x1006。
而指针变量保存的是地址,它保存该数组ch的首地址,当指针输出结果时候,输出来的字符串当作是地址一样显示出来了,也就是说指针指向的字符数组哪一段地址,就会把这地址后面的内容全部显示出来,假如p1=&ch[1],就会把ch数组中的第2个元素的地址以及以后的地址都显示出来。
问:下面代码输出结果是?
void main()
{
char a[]="hello";
char *s=a;
s[0]='B';//正确
printf("%s\n",a);
char a2[]="world";
char *s2=a2;
a2[2]=s2[0];//把"r"改变为"w"
printf("a2的第三个字符:%c\n",a2[2]);
printf("%s\n",s2);
}
答:输出结果:
Bello
a2的第三个字符:w
wowld
问:下面代码哪条语句是错误的?
char ah[]="hello";
char *aw="world";
char *sh=NULL;
ah=sh;
ah[2]=aw[2];
char *sw=NULL;
aw=sw;
printf("%s",aw);
答:
char ah[]="hello";
char *aw="world";
char *sh=NULL;
ah=sh;//错误,不能把指针赋值给数组ah。因为ah本身是一个数组,数组一旦创建之后,ch一定是该数组的首地址,不能是其他的地址,所以不能把sh这个指针指向的地址赋值给ch。
ah[2]=aw[2];
赋值给a[2],a[2]的值会改变。
char *sw=NULL;
aw=sw;
printf("%s",aw);//结果:(null)
问:下面代码的输出结果?
void main()
{
char a[]="hello";
char *p="B";
a[2]=*p;
printf("%s\n",a);
char *b="world";
b+=2;
a[0]=*b;
printf("%s\n",a);
}
答:
heBlo
reBlo
分析:
void main()
{
char a[]="hello";
char *p="B";
a[2]=*p;//正确
printf("%s\n",a);
char *b="world";
b+=2;//b指向的常量中的第3个字符地址。
a[0]=*b;//正确
printf("%s\n",a);
}
问:下面代码的输出结果是?
char a[]="hello";
char *s=a;
for (int i = 0;i<strlen(a);i++)
{
printf("%c", s[i]);
printf("%c", *s++);
}
printf("%s\n",s);
答:
char a[]="hello";
char *s=a;
for (int i = 0;i<strlen(a);i++)
{
printf("%c\n", s[i]);//输出的结果是H e l l o
printf("%c\n", *s++);//输出的结果是H e l l o
}
printf("%s\n",s);//输出同样的结果:He110
//注意:printf("%s", *s);执行程序会崩溃,因为%s输出的是字符串,而不是单字符
问:为什下面代码中,a可以赋值给s?
char* a[]={"heng","yang","shi","fan"};
char **s=a;
答:可以。
由于数组a的首地址可以赋值给s,而且数组a的首地址的内容也可以是另外字符串的地址,即a数组的首地址是“Hello”而“Hello”首地址是“H”。
分析:
char* a[] = { "heng", "yang", "shi", "fan" };//其实a指针数组可以看成**a就容易理解了
char **s = a;
printf("%s\n", *s);///输出“Heng”是字符串
printf("%c\n", **s);//注意%c,输出“H”是一个字符
printf("%s\n", *a);//输出“Heng”是字符串
printf("%c\n", **(a + 1));// 输出“y”是一个字符
printf("%c\n", *(*a+1));//注意%c,输出“e”是一个字符
对于char**s="Hello world";是错误的,因为s是char**类型,而“Hello world”是char*类型。
问:下面代码的输出结果是 什么?
void main(){
char str1[] = "abc";
char str2[] = "abc";
const char str3[] = "abc";
const char str4[] = "abc";
const char *str5 = "abc";
const char *str6 = "abc";
char *str7 = "abc";
char *str8 = "abc";
cout << (str1 == str2) << endl;
cout << (str3 == str4) << endl;
cout << (str5 == str6) << endl;
cout << (str7 == str8) << endl;
}
答:
0 0 1 1
分析:
这道题主要考察了笔者对指针的理解。
由于str1和str2这两个字符数组的首地址是不同的,所以等号为假,即结果是0;
由于str3和str4这两个常量字符数组的首地址是不同的,所以等号为假,即结果是0;
由于str5和str6这两个指针都指向字符串“常量”的地址,所以这两个指针保存的地址是相同的。
由于str7和str8这两个常量指针都指向字符串“常量”的地址,所以这两个常量指针保存的地址相同。
问:下面代码的输出结果是什么?
void func(char**p);
void main()
{
char *arr[] = {"ab","ce","ef","gh","ij","kl"};
func(arr);
}
void func(char **p)
{
char *t;
t = (p += 3)[-1];
printf("%s",t);
}
A.ab B.cdC.ef D.gh
答:C
分析:
p+=3即p=p+3,指向a[3],然后又移到上一个元素a[2].具体分析,在以上那几道题目。
问:请问以下代码有什么问题?
#inlcude<string.h>
int main()
{
char a;
char *str = &a;
strcpy(str, "hello");
printf(str);
return 0;}
答:
书上答案,在gcc或者VC6.0中:
int main(){ char a[20]; char *str = &a; strcpy(str, "hello"); printf(str); return 0;}
在VS环境中实践得到的答案:
int main(){ char a[20]; char *str = a; strcpy_s(str, 20,"hello"); printf(str); return 0;}
分析:
由于str并没有分配内存空间,会发生异常。问题出在一个吻字符串赋值进一个字符变量指针所指地址。虽然可以正确输出结果,但因为越界进行内存读写而导致程序崩溃。
问:写出下面代码的输出结果?
#include<stdio.h>
int main()
{
int arr[] = { 1, 2, 3, 4, 5 };
int *p = arr;
printf("%d ",arr[2]);
printf("%d ", *(arr+2));
printf("%d ",p[2]);
printf("%d ",*(p+2));
*(p + 2) += 3;
printf("%d ", *(p+2));
return 0;
}
答:
输出结果:
3 3 3 3 6
分析:
#include<stdio.h>
int main()
{
int arr[] = { 1, 2, 3, 4, 5 };
int *p = arr;
printf("%d ", arr[2]);
printf("%d ", *(arr + 2));
printf("%d ", p[2]);//作用和arr[2]一样,p[]数组与aa[]用法相似
printf("%d ", *(p + 2));//
*(p + 2) += 3;//相当于*(p+2)=*(p+2)+3,即*(p+2)=3+3
printf("%d ", *(p + 2));//只是把*(p+2)当成变量来输出结果
p++;
printf("%d ", *p);//这里的结果是2,是arr[1].p指向下一个元素
第一,其实当指针作为运算时,把arr当作p指针处理不难理解。例如*(arr+2)和*(p+2)的作用是一样的,虽然数组与指针的本质不相同。
第二,p++是指向下一个元素的地址,p改变成下一个元素的指针。而当p+2是指向下第二个元素的地址,与arr+2分析相同,但是p指向还是第一个元素。即p++为p=p+1,而p+2始终不能改变p指向,除非p=p+2
*(p+i) = p[i]= arr[i]=*(arr+i)
注意:p[]与a[]
return 0;
}
问:下面代码的输出结果是()
main()
{
char *p;
char a[10]={1,2,3,4,5,6,7,8,9};
p=&(a+1)[3];
printf("%d",*p);
}
A.2 B.3C.4 D.5
答:D
分析:
在上一题目可知,在这道题中,a+1是数组首元素的下一个元素的地址,a+1可看成是指针(本质上与指针有区别),(a+1)[3]=(a+1)+3所以(a+1)[3]输出的是数组a中第五个元素,即a[4]
问:下面代码的输出结果是什么?
int arr[]={1,2,3,4,5};
int *ptr=arr;
*(ptr++)+=10;
printf("%d %d ",*ptr,*(++ptr));
答:
3 3
分析:本题考察了ptr++与++ptr的区别。ptr++:先输出ptr,再自增。而++ptr:先自增,再输出ptr。
*(ptr++)+=10;语句很容易分析错误,更不能拆分为*(ptr++)=*(ptr++)+10来分析,因为它们两之间还是有区别的。
*(ptr++)+=10分析:
第一步,由于优先级()高于*,所以ptr++提取出来,其他先不管。
第二步,ptr++首先输出的是还没有自增的ptr,所以就会有*ptr+=10,这时还没有自增的ptr指向arr首地址,所以*ptr就是arr[0]的值为11,然后ptr才自增,这时ptr指向arr数组的下一个元素,即arr[1]。
*(ptr++)=*(ptr++)+10分析:
第一步,由于优先级()高于*,所以ptr++提取出来,其他先不管。
第二步,ptr++首先输出的是还没有自增的ptr,所以就会有*ptr=*ptr+10,这时还没有自增的ptr指向arr首地址,所以*ptr就是arr[0]的值为11,然后ptr自增两遍,即左右值都要自增,得到的最终结果ptr指向arr[2].
printf("%d %d ",*ptr,*(++ptr));也容易出错。
输出的规则是从右到左,所以*ptr是执行*(++ptr)之后输出的。
问:设有“int w[3][4];”, p表示指向数组w的数组指针,则p的初始化语句是什么?
答:
int *p=w;
分析:指针初始化有两种方式:
1.int *p;
p=w;
2.int *p=w;
第一种方式是通过两条语句通过的,而第二种只通过一条语句完成,即初始化语句。所以选择第二种。
问:下面代码是否正确?
int a[2][3]={1,2,3,4,5,6};
int *p=a;
int *p=a[1];
cout<<*p<<endl;
答:
int a[2][3]={1,2,3,4,5,6};
int *p=a;//错误,数据类型不一致
int *p=a[1];///指向a[1],也就是a[1][0],a[1][2],a[1][3]的合段,而&a[1]是不合法的
cout<<*p<<endl;//结果是4,输出a[1][0],默认数组首地址的内容
问:函数中的void和指针中的void*有什么区别?
答:在函数中的void是表示无类型或无返回类型。void指针是通用指针,用来存放任何数据类型的引用。
分析:
void真正发挥的作用在于:
- 对函数返回的限定。
- 对函数参数的限定。
<函数返回值> <函数名>(参数1,参数2,参数3,.......){内容;}
int sum(int a,int b)
{
int c;
return c;
}
其中第一个int是返回值 就是别的函数调用此函数时这个函数给他的一个值。
1.如果调用时不需要返回值,则函数写为void sum(int a,int b){....},此时函数没有返回值。
如果不需要参数,则int sum(void){...}《或者int sum(){...}》,此时void的意义为空,就是没有参数的意思。
3.如果都不要,则为void sum(void);《或者void sum();》
4.
main()
{
return 0;//需要返回值,而且为int型。是因为该函数隐含是返回的是整型类型的。但是在c++上安全检查严格不允许没有int,但是在vc6.0当中,编译器是会通过的。
}
void的用:
- 对函数返回的限定,当函数不需要返回值时,用void限定,如void fun();
- 对函数的参数的限定,当函数不需要接受参数时,用void限定,如int fun(void)
void指针的作用:
1.函数返回值是任意类型的指针,如void *fun();
2.定义函数指针pfun,如void(*pfun)(),如果该函数指针指向这类函数(即void函数),例如:
void fun()//这类函数
{
}
void main()
{
void(*pfun)();
pfun = fun; //指向某个函数
pfun();//调用方法1
(*pfun)(); //调用方法2
}
3.void指针不能复引用,也就是不能取得它指向的地址的内容。
void *pvoid;
int *pint;
printf("%d",*pint);//正确
printf("%d", *pvoid);//错误
由于pint是整型变量指针,解引用取得该指向地址的内容是整型的,知道从第一字节到第四个字节的内存,而且从低到高保存整数的32补码。
而pvoid是指向还不知道数据类型的地址的通用指针,复引用取得的内容不清楚是什么数据类型,内容占用的内存多大都不清楚。
扩展知识:double数据类型复引用是从第一字节到第八字节的一块内存,从低到高保存double数的浮点数符号位、阶符、阶码和尾数。
问:下面代码中哪个地方是错误的?
#include<iostream>
using namespace std;
void main()
{
void *pvoid=NULL;
int *pint;
int m = 2;
pint = &m;
cout << pint << endl;
cout << pvoid << endl;
pvoid = pint;
pint = pvoid;
cout << pint << endl;
cout << pvoid << endl;
}
答:
#include<iostream>
using namespace std;
void main()
{
void *pvoid=NULL;
int *pint;
int m = 2;
pint = &m;
cout << pint << endl;
cout << pvoid << endl;
pvoid = pint;
pint = (int *)pvoid;//pint=pvoid错误,赋值给pint,pvoid需要强制转换成int类型的。
cout << pint << endl;
cout << pvoid << endl;
}
问:关于通用指针与解引用的解决问题,以下代码哪些语句是错误的?
void main()
{
int i=100;
void *p=&i;
*p=0;
//----
int a=100;
void *p2=&a;
*(int*)p2=0;
cout<<a<<endl;
}
答:由于通用指针可以存放任意数据类型的地址,而编译器无法确定该指针指向内存地址中的原始数据类型,因此通用指针无法解引用。
void main()
{
int i=100;
void *p=&i;
*p=0;//错误,通用指针不能解引用
//----
int a=100;
void *p2=&a;
*(int*)p2=0;//正确,由于通过强制转换来指定数据类型,就可以实现解引用
cout<<a<<endl;
}
问:引用与指针有什么区别?
答:
- 引用必须初始化,指针可以不用。
- 引用初始化以后就不能被改变(即只能初始化一次,始终只指向指定的对象),而指针可以改变所指向的对象。
- 不存在指向空值的引用,而指针可以指向一个空值,即空指针。
- 因为引用不能指向空值,这意味着使用引用之前不需要测试其合法性;而指针则需要经常进行测试。所以使用引用的代码效率要比使用指针的效率高,同时也使引用具有更高的安全性。
- 引用不占有内存,而指针是占内存的。
- 引用是单个变量的别名,而指针是个实体。
- sizeof+引用:引用指向的对象的内存大小。sizeof+指针变量:指针本身的内存大小。
- 引用不存在引用常量,指针存在指针常量。
分析:
void main()
{
int var = 2;
int &reference;//错误,引用不能没有初始化。
int *p;//正确,指针可以不初始化,但是隐含安全性问题,比如由于使用该指针时,它没有指向某个对象,会造成野指针。
int &reference = var;//正确,指向一个对象,即变量。
int &reference = 0;//错误,不能指向空值
int &reference = 2;//错误,引用不能指向常量
//
int *p = 0;//正确,指针可以指向空值
int &reference = *p;//不报错,但是隐含非法,因为p指针是个野指针,当输出p的值(没有指向对象的值)就出错
int &reference = p;//错误,int *类型不能初始化int &类型
int &reference = (int &)p;//强转化类型,与int *无关。
cout << &reference << endl;//&reference 是引用指向变量的地址
cout << reference << endl;//reference 是引用指向变量的值
const int &a//是正确的,常引用,表示不能通过常引用来该变指向目标变量的值
int const &a//是错误的,引用常量,由于引用本身一旦初始化就不能被改变,并不需要const来声明。
}
问:下面的代码是否正确?
void main()
{
int b=123;
const int &a=b;
b=555;
printf("%d\n",a);
a=666;
}
答:
void main()
{
int b=123;
const int &a=b;//常引用,即引用指向目标变量的值
b=555;
printf("%d\n",a);//输出结果是555
a=666;//编译错误,不能通过常引用来改变b的值
}
问:下面代码是否正确?若不正确,请问哪条语句是错的?
void main()
{
int &a=100;
const int &b=100;
}
答:
void main()
{
int &a=100;//错误
const int &b=100;//正确
const int &b=100//引用指向的是常量,代表的是引用b指向一个const int类型,这个int型的值是不能被改变的。
int &a=100//100是个立即数,由于立即数不是C语言的常量,立即数本身存在于代码段,并不是一个对象或变量,所以引用不能指向它的。
}
问:下面代码哪条语句是错误的?
void main()
{
const int ci=1024;
const int &rl=ci;
rl=42;
int &r2=ci;
const int &r3=ci;
const int &r4=r3*2;
}
答:
void main()
{
const int ci=1024;
const int &rl=ci;//正确,引用以其对应的对象都是常量
rl=42;//错误,rl是对常量的引用
int &r2=ci;//错误,试图让一个非常引用指向一个常量对象
const int &r3=ci;//正确
const int &r4=r3*2;//正确
//1.常量对象不能被非常引用指向
//2.防止通过常引用来改变目标变量(非常量)的值
}
问:引用的优势?
答:
- 引用作为函数参数传递时,实际传递的是实参,可直接对实参进行改变。而指针作为函数参数传递时,传递的时实参的地址。根据引用实参的地址来对其进行操作。不管是引用作为函数参数还是其他地方,由于引用是不占用内存并且无须像指针那样还要寻址,节约时间和空间。
- 由于引用一旦定义就必须初始化,还有常引用可以保护指向的目标变量的值不轻易被修改,运用比指针安全。
问:设void f1(int *m,long &n);int a;long b;则以下调用合法的是()。
A.f1(a,b) B.f1(&a,b)
C.f1(a,&b)D.f1(&a,&b)
答:B
分析:指针类型的函数参数的作用:将一个变量的地址传送到函数中,调用时,要求实参是一个地址,所以上面的函数,实参前面加取地址符。也可以这样理解:int *m=&a;
引用作为函数参数的作用:形参是变量名的别名,传递数据的作用。调用时,要求实参是一个变量名,所以上面的函数,实参直接使用变量名。也可以这样理解:long &n=b;
问:什么是指针?
答:指针是用来存储内存地址的变量,它指向单个对象的地址,除了void指针类型之外,指针的数据类型与所指向地址的变量数据类型须保持一致。不论指针指向的数据类型是哪一种,他本身永远是整型,保存的是地址。
分析:
int *ip;
const intt *ip2;
ip和ip2都是指针变量名,int表示该指针变量的类型是整型。*表示指针变量。
问:如何初始化指针并对其赋值?
答:指针的初始化就是给指针赋初值,&符号可以用来获取对象的内存地址,并且赋值给指针变量。指针变量的初始化和赋值都可以通过运算符”=“来实现。
分析:指针可以初始化为0(NULL),,没有初始化的指针指向是随机的,它可能导致随机修改了程序的值。
变量的数据类型和指针变量的数据类型要保持一致。所以以下代码是错误的:
int a=123;
long *p;
p=&a;
问:是否可以确定指针指向一个对象?
答:指针用于指向对象,一个指针只指向一个对象的内存地址。
问:指针和迭代器主要的区别?
答:指针和迭代器都是提供其所指对象的间接访问。区别是:指针用于指向单个对象,而迭代器只用于访问容器内的元素。
问:运用好指针有哪些优点?
答:
- 提高程序的编译效率和执行速度。
- 通过指针可使用主调函数和被调函数之间共享变量或数据结构,便于实现双向数据通讯。
- 可以实现动态的存储分配。
- 便于表示各种数据结构,编写高质量的程序。
- 使表达式变得紧凑和简洁。
问:使用指针不恰当的会出现哪些问题?举些例子。
答:
- 访问数组和其他数据结构时越界。
- 自动变量消失后被引用。
- 堆上分配的内存释放后被引用。
- 内存分配之前解引用。
问:指针是一种特殊的变量,只能用来保存地址。这句话对么?
答:对的。
问:一个32位的机器,该机器的指针是多少位?
答:4位。指针变量的位数根据机器地址总线位数而定,对于32位地址总线的机器指针的位数就是4个字节。
分析:系统为指针变量分配一定的内存空间,无论指针变量指向何种类型的数据,指针变量的长度一般是一个机器的字长。所以,在32位的机器就是4,但是其他位数的机器就不一定是这个结果了。(注意:分配的内存与编译器有重大的相关,比如在win32环境下编译,在操作系统64位环境下,仍然是4字节。)
问:字符指针、浮点数指针、函数指针这三种类型的变量哪个占用的内存最大?为什么?
答:三者占用的内存一样大,因为所有指针变量所占的内存单元数量都是相同的。在32位平台下,三个不同类型的指针占用的内存都是4个字节。
问:用变量a给出下面的声明和定义
- a.一个整型数。答:int a;
- b.一个指向整型数的指针。答:int *a;
- c.一个指向指针的指针,它指向的指针是指向一个整型数。答:int **a;
- d.一个有10个整型数的数组。答:int a[10];
- e.一个有10个指针的数组,该指针是指向一个整型数的。答:int *a[10];
- f.一个指向有10个整型数数组的指针。答:int (*a)[10];
- g.一个指向函数的指针,该函数有一个整型参数并返回一个整型数。答:int (*a)(int);
- h.一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数。答: int (*a[10])(int);
分析:
分析同下一道面试题。
问:各种复杂指针的声明,下面的数据声明都代表什么意思?
a. float(**fa)[10]
答:fa是一个二级指针,它指向的是一个一维数组的指针,数组的元素都是float类型。
b. double *(*sp)[10]
答:sp是一个指针,它指向一个一维数组,该数组元素都是double*。
c. double (*arr[10])()
答:arr是一个数组,arr有10个元素,元素都是函数的指针,指向的函数类型是没有参数且返回double的函数
d. int *((*a)[10])
答:与“int *(a)[10]”一样的,a是一维数组的指针。
e. long (*fun)(int)
答:函数指针。
f. int (*(*f)(int,int))(int)
答:f是一个函数的指针,指向的函数的类型是有两个int 参并且返回一个函数指针的函数,返回的函数指针指向一个int参数且返回int类型的函数。
分析:
笔者对于指针声明总结了一下技巧,有重要的两点:
第一点,用优先级来分析指针声明语句,优先级从高到低为:()>[]>*。第二点,从里到外分析,比如(*a),a先与*结合,再到()。
接下来让笔者为大家分析一下怎么看懂多种指针的声明,比如最难的最后面那个指针声明:int (*(*f)(int,int))(int)
1. 找到变量f,由于*在()里面,f先与*号结合,然后是(),说明f是一个指针变量。即:(*f)
2. f再与右边的(int,int)结合,并且(int,int)中有两个参数,说明f指向一个有两个int参数的函数,即f是一个函数指针,(*f)(int ,int)。
3. 如果觉得太长,难理解,笔者在这里把(*f)(int ,int)当作一个变量名N,N先与*号结合,然后是外面的(),即(*N)是一个指针(确切地说函数指针N返回类型不是指针类型,通过(*N)之后,这个函数指针的返回类型是指针。 )。
4. (*N)与右边(int)结合,并且(int)有一个参数,说明(*N)指向有一个int参数的函数,即函数指针的返回类型指针指向一个有一个int参数的函数。
5. 由于最左边是int,则函数指针返回类型指针指向的是返回值为int型且有一个int参数的函数。
其实笔者认为在f点当中也可以这么说:含有返回两个int参数的函数指针返回类型指针指向一个int参数且返回int类型的函数。
只是这样说,易于理解。但是标准的说法是,把整个声明语句都拆分来讲的,比如函数指针也要拆开来说明,新手因为复杂理解比较困难而已。但是没关系,这并不重要,在程序设计时一般是不会使用这么复杂的定义的,因为这使程序的可读性变差,在这里只是为了学会分析即可。
问:指针指向的变量,那么请说说指针与数值运算的问题。
答:指针可能会指向意想不到的内存地址。
using namespace std;
int main()
{
int a = 100;
int b = 200;
int *p = &a;
p = p - 1;//由于整型变量占4个字节,指针应该移动1*4
cout << &a << endl;
cout << &b << endl;
cout << p << endl;//此时指针指向了变量b,也有可能不是
printf("%d\n",&a);
printf("%d\n", &b);
printf("%d\n", p);
//注意,指针与数值进行运算适合运用在在数组,因为数组的元素是连续排列的。
//而在这里,由于编译器处理内存不一样,可能要指向的地址就不会准确(在内存管理机制中,变量之间可能会有空隙空间,所以会留出空隙地址)。
return 0;
}
问:指针与变量的类型不一致时,请问怎么解决这个问题?
答:由于指针与变量的类型不一致,指针会指向不正确的内存地址,需要强制转换成和指向的对象的数据类型保持一致。
分析:
#include<iostream>
using namespace std;
int main()
{
//当p的类型和a不一样时
char a[15]= "mygirlislovely";
int *p = (int *)&a;
p++;//由于指针p是整型类型的,它移动4个字节,指向下一个地址会,指向了字符数组中的“r”元素
cout << (char)*p << endl;//需要强制转换char类型可以输出字符
return 0;
}
问:分析一下,下面中定义的指针,说出指针类型、指针所指向的类型、指针的值和指针本身所占据的内存区?
答:
int a= 4;
int *p= &a;
指针类型:int* 指针所指向的类型:int 指针的值:4
char *p;
指针类型:char* 指针所指向的类型:char
int **p;
指针类型:int** 指针所指向的类型:int
int (*p)[3];
指针类型:int (*)[3] 指针所指向的类型:int ()[3]
指针本身所占据的内存区:以上指针本身所占的内存大小,在32位程序中,为4个字节。
分析:
指针类型:把指针声明语句的指针名字去掉,就是指针类型,例如int *p;指针类型为int*。
指针所指向的类型:把指针声明语句的指针名字和名字左边的*号都去掉。例如:int *p;指针所指向的类型是int。
指针的值:指针所指向的变量的值。
问:什么是野指针?
答:“野指针”是在定义指针后没有对其进行初始化,或者指针指向的内存被释放,而指针没有被设置为NULL。野指针随机地指向一个地址,使用这个指针进行操作时,就会更改该内存的数据,造成程序数据的破坏,严重威胁着程序的安全。
问:什么是悬浮指针?
答:当所指向的对象被释放或者收回,但是对该指针没有做任何的修改,以至于该指针仍旧指向已经回收的内存地址,是悬浮指针,也称作是迷途指针,是造成野指针的一部分,属于野指针。
分析:
假如在进程A中,释放了指针所指向的内存M,然后操作系统把原来一部分已经释放掉的内存M重新分配给进程B。如果通过进程A中的迷途指针修改内存M的数据,由于内存M后来已经分配给进程B的,这将会产生无法预料的后果。这种错误是不易被发现的。
问:下面代码会编译通过吗?
char *tabHeader = "Sound";
*tabHeader = 'L';
printf("%s\n", tabHeader);
答:
//字符串字面值作为常量,在vs编译器的环境下不能修改,出错。
//在gcc环境下可以修改,即改变的是新的一块内存地址(拷贝一份),原来的常量的值并没有被改变。但是char前面加上const,编译也不会通过,
char *tabHeader = "Sound";
*tabHeader = 'L';//tabHeader指向的是常量的地址,不能修改
printf("%s\n", tabHeader);
问:请根据以下代码讲述一下“*p,*p(++),(*p)++,*++p”之间的区别以及含义?
void main()
{
int b[5] = {9, 2, 3, 4, 5 };
int *p = b;
for (int j = 0; j <4; j++)
{
cout <<b[j+1]<< "指针指向的内容:" << *++p << endl;//先p++,后*p
}
p = b;//指针指向恢复原来数组的首地址,因为上一次循环会改变p的地址值
cout <<endl;
for (int i=0;i<5;i++)
{
//*p(++)语法错误
cout << b[i]<<"指针指向的内容:" << *p++ << endl;//先*p,后p++
}
p = b;
cout << endl;
//分开执行是因为下面循环的*p的内容改变
for (int m = 0; m < 5; m++)
{
cout << "值:" << (*p)++ << endl;//指针所指向的内容加1
}
}
问:观察下面代码,说说“pp++”编译之后,为何没有输出结果?
char a[6] = "hello";
char *p = a;
char **pp = &p;
printf("%c\n", **pp);
pp++;
printf("%c\n", **pp);
答:因为pp++之后,pp指向的变量不再是p,指向的是p变量前面的一块未知内存,非法。