[C++面试题]之指针与引用(2)
1、下面这个程序测试会有什么结果?
#include<iostream> using namespace std; void GetMemory(char *p,int num) { p=(char *)malloc(sizeof(char) * num); } int main () { char *str = NULL; GetMemory(str,100); strcpy(str,"hello"); return 0; }
解析:毛病出在函数GetMemory中,编译器总是要为函数的每个参数制作临时副本,在本例中,void GetMemory(char *p , int num)中的*p实际上是主函数中str的一个副本,而在函数GetMemory中只是把p所指向的内存地址改变了,但是str丝毫未变,因为函数GetMemory没有返回值,因此str并不指向p所申请的那段内存,所以函数GetMemory并不能输出任何东西,如下图所示。事实上,每次执行一次GetMemory就会申请一块内存,但是申请的内存却不能有效释放,结果是内存一直被独占,最终造成内存泄露。
如果一定要用指针去申请内存,那么应该采用指向指针的指针,传str 的地址给函数GetMemory。代码如下:
#include<iostream> using namespace std; void GetMemory(char **p,int num) { *p=(char *)malloc(sizeof(char) * num); } int main () { char *str = NULL; GetMemory(&str,100); strcpy(str,"hello"); cout << *str << endl; cout << str << endl; cout << &str << endl; return 0; }
这样的话程序就可以运行成功了,我们分别打印 *str 、 str 、 &str 可以发现,结果分别是 h 、 hello 、 0024FA80 。str就是字符串的值;*str 是字符串首字符,&str 就是字符串的地址值。
当然也可以用函数返回值来传递动态内存。这种方法更简单,代码如下:
#include<iostream> using namespace std; char *GetMemory(char *p,int num) { p=(char *)malloc(sizeof(char) * num); return p; } int main () { char *str = NULL; str = GetMemory(str,100); strcpy(str,"hello"); cout << *str << endl; cout << str << endl; cout << &str << endl; return 0; }
我们可以对这道题推而广之,看一下整型变量是如何传值的,代码如下:
#include<iostream> using namespace std; void GetMemory1(int *num) { *num=5; } int main () { int a; GetMemory1(&a); cout << a << endl; return 0; }
GetMemory1把 a 的地址传了进来,*num 是地址里的值,是 a 的副本.通过直接修改地址里的值,不需要有返回值,也把 a 给修改了,因为 a 所指向的地址的值发生了改变.
答案:
程序崩溃.因为GetMemory 并不能传递动态内存,主函数中的 str 一直是NULL。
2、写出下面程序运行的结果。
#include<iostream> using namespace std; int main () { int a[3]; a[0]=0; a[1]=1; a[2]=2; int *p , *q; p=a; q=&a[2]; cout << a[q-p] <<endl; }
解析:本程序的结构如下:
(1)先声明了一个整型数组a[3],然后分别给数组赋值。
(2)又声明了两个整型指针 p、q,但是并没有定义这两个指针所指向的地址。
(3)使整型指针 p 的地址指向 a(注意 a 就是a[0]),使整型指针 q 的地址指向 a[2]。
可实际验证程序如下:
#include<iostream> using namespace std; int main () { int a[3]; a[0]=0; a[1]=1; a[2]=2; int *p , *q; p=a; cout << p <<endl; cout << *p <<endl; q=&a[2]; cout << q <<endl; cout << *q <<endl; cout << a[q-p] <<endl; }
上面的输出结果分别是:
002DFD24
0
002DFD2C
2
2
2
q 的实际地址是 002DFD2C,p 的实际地址是 002DFD24。 002DFD2C-002DFD24=0x08(十六进制减法),相差是 8。
q-p的实际运算是(q的地址值(002DFD2C)- p的地址值(002DFD24))/sizeof(int),即结果为 2 。
答案:
运行结果是 2 。
3、请问下面的代码的输出结果是多少?
#include<iostream> using namespace std; class A { public: A() { m_a = 1; m_b = 2; } ~A(){}; void fun() { printf("%d%d",m_a,m_b); } private: int m_a; int m_b; }; class B { public: B() { m_c=3; } ~B(); void fun() { printf("%d",m_c); } private: int m_c; }; void main () { A a; B *p = (B *)(&a); p->fun(); }
解析:首先可以肯定的是上面的代码是非常槽糕的,无论是可读性还是安全性都很差。写这种代码的人,按照Jarne Stroustrup(C++标志化制定者)的说法,应该“斩立决”。
但是不得不说这也是一道很好考察你对内存偏移的理解的题:
B *p = (B *)(&a);
这是一个野蛮的转化,强制把 a 地址内容看成是一个B类对象,p 指向的是 a 类的内存空间。
B类只有一个元素m_c 但是 A类的内存空间存放第一个元素的位置是 m_a, p指向的是对象的内存首地址,比如:0x22ff58,但p->fun()调用B::fun()来打印m_c时,编译器对m_c的认识就是m_c距离对象的偏移量是 0,于是打印了对象A首地址的偏移量 0x22ff58+0变量值,即就是m_a的值1.
答案:
运行结果是:1
4、下面程序输出结果是什么?
#include<iostream> using namespace std; class A { public: int m_a; A() { m_a = 1; } void print () { printf("%d",m_a); } }; class B : public A { public: int m_a; B() { m_a = 2; } }; int main () { B b; b.print(); printf("%d\n",b.m_a); }
解析:B类中的 m_a 把 A 类中的 m_a 覆盖掉了。在构造 B 类时,先调用 A 类的构造函数,所以 A 类中的 m_a 是1,b.print()打印的是 A 类中的 m_a ,而B类中的 m_a 是2。
答案:
12
5、C++中有了 malloc/free ,为什么还需要 new/delete 呢?
答案:
(1)malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。
(2)对于非内部数据类型的对象而言,只用malloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。
(3)因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new ,以及一个能完成清理与释放内存工作的运算符delete。new/delete 不是库函数而是运算符。
指针与引用部分算是写完了,还有很多没弄明白的,比如:指针函数、指向指针的指针也说得很少。可能上面的内容还有很多错误之处,欢迎指正…