指针是C语言中广泛使用的一种数据类型。运用指针编程是C语言最主要的风格之一。使用指针可以编写出精炼而高效的程序。
学习指针是学习C语言中最重要的一环,同时,指针也是学习C语言中最为困难的一部分。指针可以表示各种数据对象,例如简单变量、数组、结构体、类以及函数等。指针具有不同的类型,这些类型指向不同的数据存储体。
在各种笔试和面试中,许多指针方面的问题都是各个公司的重点考点,例如数组指针、函数指针、常量指针、指针传值 、多维指针等。
本章通过许多实际公司面试题对指针的各个方面的重点、难点进行全面且细致的分析。通过阅读本章,希望读者能解决指针的各个难点。
面试题1 一般变量引用
仔细阅读下面的代码,并分析变量的结果和程序运行结果:
#include <iostream>
#include <string>
using namespace std;
int main(int argc, char* argv[])
{
int a = 10;
int b = 20;
int &rn = a; //声明rn是变量a的一个引用,也就是rn是a的一个别名
int equal;
rn = b;
cout << "a =" << a << endl;
cout << "b =" << b << endl;
rn = 100;
cout << "a =" << a << endl;
cout << "b =" << b << endl;
equal = (&a == &rn) ? 1 : 0;
cout << "equal = " << equal << endl;
return 0;
}
运行结果:
面试题2 指针变量引用
仔细阅读下面的代码,并分析变量的结果和程序运行结果:
#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
int a = 1;
int b = 10;
int *p = &a; //这一行的意思是声明整型的指针变量P,并指向a,也就是p指向a
int* &pa = p; //声明p的一个指针引用pa, pa实际也是指向a
(*pa)++; //pa指向的内容加1
cout << "a = " << a << endl; //2
cout << "b = " << b << endl; //10
cout << "*p = " << *p << endl; //2
pa = &b; //将pa指向变量b的地址,由于pa也是p的引用,此时p也指向了b
(*pa)++;
cout << "a = " << a << endl; //2
cout << "b = " << b << endl; //11
cout << "*p = " << *p << endl; //11
return 0;
}
运行结果:
面试题3 看代码找错误——变量引用
#include <iostream>
using namespace std;
int main()
{
int a = 1, b = 2;
int &c;//声明引用没有初始化
int &d = a;
&d = b;//引用只能在声明的时候被赋值,以后不都能修改引用其它别名
int *p;
*p = 5;//p没有被初始化,因此此时的p是个野指针。
return 0;
}
【解析】
代码第6行,正确,声明并初始化整型变量a和b。
代码第7行,编译错误。声明了一个引用类型的变量c,但是没有初始化,这时会出现编译错误,因为引用类型的变量在声明的同时必须初始化。
代码第8行,正确,声明了一个变量a的引用d。
代码第9行,编译错误,把d作为变量b的别名。这是因为引用只能在声明的时候被赋值,以后都不能再把该引用名作为其他变量名的别名。
代码第10行,正确,声明了一个整型的指针变量p。
代码第12行,运行错误,把p的内容赋为5。由于p没有被初始化,因此此时的p是个野指针,对野指针的内容赋值是非常危险的,会导致程序运行时崩溃。
面试题4 如何交换两个字符串
#include <iostream>
#include <string>
void swap(char *&x, char *&y)
{
char *temp;
temp = x;
x = y;
y = temp;
}
int main()
{
char apr[] = "hello";
char bpr[] = "how are your?";
char *ap = apr;
char *bp = bpr;
std::cout << "*ap === " << ap << std::endl;
std::cout << "*bp === " << bp << std::endl;
swap(ap, bp);
std::cout << "swap ap, bp === " << std::endl;
std::cout << "*ap === " << ap << std::endl;
std::cout << "*bp === " << bp << std::endl;
return 0;
}
这里swap函数是利用传指针引用实现字符串交换的。由于swap函数是指针引用类型,因此传入函数的就是实参,而不是形参。
如果不用指针引用,那么指针交换只能在swap函数中有效,因为在函数体中,函数栈会分配两个临时的指针变量分别指向两个指针参数,对实际的ap和bp没有影响。
函数执行结果为:
从执行结果来看,swap函数确实起到了交换 两个字符串的目的。
当然,如果不用引用,还可以用二维指针达到同样的目的。可以把swap()函数的定义改为下面的形式。
void swap(char **x, char **y)
{
char *temp;
temp = *x;
*x = *y;
*y = temp;
}
把源代码swap(ap, bp);改为swap(&ap, &bp);
用这传指针地址的方式,同样可以起到交换两个字符串的目的。
面试题5 程序查错——参数引用
#include <iostream>
const float pi = 3.14f;
float f;
float f1(float r)
{
f = r * r * pi;
return f;
}
float& f2(float r)
{
f = r * r * pi;
return f;
}
int main()
{
float f1(float = 5);//声明函数f1()的默认参数调用,其默认值为5
float &f2(float = 5);//声明函数f2()的默认参数调用,其默认值为5
float a = f1();//将变量a赋为f1()的返回值。
//float &b = f1();
float c = f2();//f2()函数在返回值时直接将全局变量f返回变量c
float &d = f2();//返回全局变量的引用给d
d += 1.0f;//将d的值加1.0,此时d是全局变量f的引用,因此f的值变成79.5
std::cout << "a = " << a << std::endl;
//std::cout << "b = " << b << std::endl;
std::cout << "c = " << c << std::endl;
std::cout << "d = " << d << std::endl;
std::cout << "f = " << f << std::endl;
}
【解析】
代码第23行,将变量b赋为f1()的返回值。因为在f1()函数里,返回的是一个f的临时变量temp=78.5,对临时变量进行引用会发生错误。
将代码第23行和第30行注释,运行结果如下:
面试题6 参数引用的常见错误
#include <iostream>
using namespace std;
class Test
{
public:
void f(const int& arg);
private:
int value;
};
void Test::f(const int &arg)
{
//arg = 10;//arg是常引用,值不能被修改
cout << "arg = " << arg << endl;
value = 20;
}
int main()
{
int a = 7;
const int b = 10;
//int &c = b;//b是常量类型,不能赋给非常量的引用
const int &d = a;
a++;
//d++;//不能使用常量引用修改变量的值
Test test;
test.f(a);
cout << "a = " << a << endl;
return 0;
}
面试题7 指针和引用有什么区别
解析】
区别如下:
(1)初始化要求不同。引用在创建的同时必须初始化,即引用到一个有效的对象;而指针在定义的时候不必初始化,可以在定义后面的任何地方重新赋值。
(2)可修改性不同。引用一旦被初始化为指向一个对象,它就不能被改变为另一个对象的引用;而指会在任何时候都可以改变为指向另一个对象。给引用赋值并不是改变它的原始对象的绑定关系 。
(3)不存在NULL引有,引用不能使用指向空值的引用,它必须总是指向某个对象;而指针则可以是NULL,不需要总是指向某些对象,可以把指针指向任意的对象,所以指针更加灵活,也容易出错。
(4)测试需要的区别。由于引用不会指向空值,这意味着使用引用之前不需要测试它的合法性;而指针则需要经常进行测试。因此使用引用的代码效率比使用指会的要高。
(5)应用的区别。如果是指一旦指向一个对象后就不会改变指向,那么应该使用引用。如果有存在指向NULL(不指向任何对象)或在不同的时刻指向不同的对象这些可能性,应该使用指针。
实际上,在语言层面,引用的用法和对象一样;在二进制层面,引用一般都是通过指针来实现的,只不过编译器帮我们完成了转换。总体来说,引用既具有指针的效率,又具有变量使用的方便性和直观性。
面试题8 为什么传引用比传指针安全
【解析】
由于不存在空引用,并且引用一旦初始化为指向一个对象,它就不能被改变为另一个对象的引用,因此引用很安全。
对于指针来说,它可以随时指向别的对象,并且可以不被初始化,或为NULL,所以不安全。const指针仍然存在空指针,并且有可能产生野指针。
面试题9 复杂指针的声明
【答案】
a. int a;//An integer
b. int *a; // A pointer to an integer
c. int **a; //A pointer a pointer to an integer
d. int a[10]; //An array of 10 integers
e. int *a[10]; //An array of 10 pointer to integeres
f. int(*a)[10];//A pointer to an array of 10 integers
g. int(*a)(int);//A pointer to a function a that takes an integer argument and retrns an integer
h. int(*a[10])(int);//An array of 10 pointers to functions that take an integer argument and return an integer
面试题10 看代码写结果——用指针赋值
#include <stdio.h>
int main()
{
char a[] = "hello,world";
char *ptr = a;
printf("%c\n", *(ptr+4));
printf("%c\n", ptr[4]);
printf("%c\n", a[4]);
printf("%c\n", *(a+4));
*(ptr+4) += 1;
printf("%c\n", *(ptr+4));
printf("%s\n", a);
return 0;
}
面试题11 指针加减操作
#include <stdio.h>
int main()
{
int a[5] = {1, 2, 3, 4, 5};
int *ptr = (int *)(&a + 1);
printf("%d\n", *(a+1));
printf("%d\n", *(ptr-1));
return 0;
}
【解析】
代码第6行,ptr是一个int型的指针&a+1,即取a的地址,该地址的值加sizeof(a)的值,即&a+5*sizeof(int),也就是a[5]的地址,显然,当前指针已经越过了数组的界限。(int*)(&a+1)则是把上一步计算出来的地址,强制转换为int*类型,赋值给ptr.
面试题12 指针比较
#include <iostream>
using namespace std;
int 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";
char *str7 = str1;
char *str8 = str2;
cout << "(str1 == str2) = " << (str1 == str2) << endl;
cout << "(str3 == str4) = " << (str3 == str4) << endl;
cout << "(str5 == str6) = " << (str5 == str6) << endl;
cout << "(str6 == str7) = " << (str6 == str7) << endl;
cout << "(str7 == str8) = " << (str7 == str8) << endl;
cout << "str1 == " << &str1 << endl;
printf("str1====%p\n", str1);
cout << "str2 == " << &str2 << endl;
printf("str2====%p\n", str2);
cout << "str3 == " << &str3 << endl;
printf("str3====%p\n", str3);
cout << "str4 == " << &str4 << endl;
printf("str4====%p\n", str4);
cout << "str5 == " << &str5 << endl;
printf("str5====%p\n", str5);
cout << "str6 == " << &str6 << endl;
printf("str6====%p\n", str6);
cout << "str7 == " << &str7 << endl;
printf("str7====%p\n", str7);
cout << "str8 == " << &str8 << endl;
printf("str8====%p\n", str8);
return 0;
}
面试题13 看代码找错误——内存访问违规
#include <iostream>
using namespace std;
int main()
{
char a;
char *str1 = &a;
const char *str2 = "AAA";
strcpy(str1, "hello");
cout << "str1====" << str1 << endl;
//str2[0] = 'B';
cout << "str2====" << str2 << endl;
return 0;
}
【解析】
代码第10行,str1指向一个字节大小的内存区。由于复制"hello"字符串最少需要6个字节,显然内存大小不够。因此会因为越界进行内存读写而导致程序崩溃。
代码第13行,str2指向”AAA“这个字符串常量,因为是常量,所以对str2[0]的赋值操作是不合法的,也会导致程序崩溃。